Ground under the body: sphere footprint, clamber by pressure (Nimso), physics objects climbable, arms keep colliding

- Ground = centre ray + stance-wide sphere cast (face normal from a ray inside the contact):
  planks and lips keep support.
- Higher ground is stood on when the hips are over it and the body presses onto it (hands
  braced, a just-finished press, legs tucked over it, or airborne) - no hips-well-above rule.
- Bracing counts heavy/frozen physics objects (>= 15 kg), not what the hand carries.
- Knee up: braced + hips held back tucks the legs collider.
- Support goal capped at straight legs (tiptoe/over-tall head floated the body).
- Arms never get collision exceptions with held objects (they disabled whole crag rocks).
- Spike: heavy-box mantle, 15 cm plank, chest-high ledge two hands yes / one hand no.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-02 20:14:11 +03:00
1 parent 3e300c7f04
commit 2d986d08ac
2 files changed
+172 -52

No files matched your search

+75 -10
View File
@@ -100,7 +100,10 @@ public partial class RagdollSpike : Node3D
_phases.Add(("a table can't be walked over", SetupTableObstacle, _ => _stickVelocity = new Vector3(0f, 0f, -1.5f), 3f, FinishBlockedLow));
_phases.Add(("walks up a 0.25 m step", () => SetupObstacle(0.25f, 8f), _ => _stickVelocity = new Vector3(0f, 0f, -1.2f), 3f, FinishStep));
_phases.Add(("two-handed mantle onto a 0.9 m ledge (crouch, hands on the edge, stand and press)", () => SetupObstacle(0.9f), _ => TickHumanMantle(both: true), 7f, () => FinishMantle(true)));
_phases.Add(("one hand can't mantle", () => SetupObstacle(0.9f), _ => TickHumanMantle(both: false), 7f, () => FinishMantle(false)));
_phases.Add(("two-handed mantle onto a chest-high 1.2 m ledge", () => SetupObstacle(1.2f), _ => TickHumanMantle(both: true), 7f, () => FinishMantle(true)));
_phases.Add(("one hand can't mantle the 1.2 m ledge (the hips must be lifted)", () => SetupObstacle(1.2f), _ => TickHumanMantle(both: false), 7f, () => FinishMantle(false)));
_phases.Add(("mantles onto the playground's heavy box (25 kg physics object, 0.7 m)", SetupCrateLedge, _ => TickHumanMantle(both: true), 8.5f, FinishCrateMantle));
_phases.Add(("walks a 15 cm plank swaying the hips without falling off", SetupPlank, TickPlank, 6f, FinishPlank));
_phases.Add(("two-handed pull-up on a grabbed bar", () => SetupBarHang(), t => TickPullUp(both: true), 3.5f, () => FinishPullUp(true)));
_phases.Add(("one-handed pull-up is weaker", () => SetupBarHang(), t => TickPullUp(both: false), 3.5f, () => FinishPullUp(false)));
_phases.Add(("crawls through a 0.6 m tunnel", SetupTunnel, TickCrawl, 7f, FinishCrawl));
@@ -531,9 +534,59 @@ public partial class RagdollSpike : Node3D
private void SetupObstacle(float height, float depth = 2f)
{
Rest();
_ledgeBox = null;
_ledgeTop = height;
if (height >= 1f)
_origin = new Transform3D(Basis.Identity, new Vector3(0f, 0f, ObstacleFront + 0.4f));
_stage.AddChild(MakeStatic(new Vector3(3f, height, depth), new Vector3(0f, height * 0.5f, ObstacleFront - depth * 0.5f), 29));
}
// Playground.HeavyBox (25 kg, 0.7 m cube) as the ledge, same human mantle as onto the static ledge.
private void SetupCrateLedge()
{
Rest();
_ledgeBox = MakeBox(Vector3.One * 0.7f, 25f, new Vector3(0f, 0.35f, ObstacleFront - 0.35f));
_ledgeTop = 0.7f;
}
private RigidBody3D _ledgeBox;
private float _ledgeTop = 0.9f;
private bool FinishCrateMantle()
{
GD.Print($"RAGDOLL: crate mantle: pelvis rose up to {_maxPelvisRise:F3} m, feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
_maxPelvisRise = 0f;
return _finite && NewSnaps == 0 && _ragdoll.GroundY > 0.6f && _origin.Origin.Y > 0.55f;
}
private const float PlankY = 0.3f;
private float _plankMinFoot;
private void SetupPlank()
{
Rest();
_origin = new Transform3D(Basis.Identity, new Vector3(0f, PlankY, 0f));
_stage.AddChild(MakeStatic(new Vector3(0.15f, PlankY, 8f), new Vector3(0f, PlankY * 0.5f, -3f), 29));
_plankMinFoot = float.MaxValue;
}
// Walk along it with the head (and so the hips) swaying ±9 cm side to side.
private void TickPlank(float dt)
{
float sway = _t > 1f ? 0.09f * Mathf.Sin(Mathf.Tau * 0.7f * (_t - 1f)) : 0f;
_headLocal = new Transform3D(Basis.Identity, new Vector3(sway, EyeHeight, 0f));
_stickVelocity = _t > 1f && _t < 5f ? new Vector3(0f, 0f, -0.6f) : Vector3.Zero;
_measureFrom = float.MaxValue;
if (_ragdoll.Built && !_needBuild && _t > 0.5f)
_plankMinFoot = Mathf.Min(_plankMinFoot, _ragdoll.Grounded ? _ragdoll.GroundY : -1f);
}
private bool FinishPlank()
{
GD.Print($"RAGDOLL: plank: lowest supported foot level {_plankMinFoot:F3} (plank top {PlankY}), pelvis {_ragdoll.Pelvis.GlobalPosition}, eye error {_ragdoll.Eye.DistanceTo(Camera.Origin):F3}, snaps {NewSnaps}");
return _finite && NewSnaps == 0 && _plankMinFoot > PlankY - 0.03f && _ragdoll.Pelvis.GlobalPosition.Z < -1.8f;
}
private void SetupTableObstacle()
{
Rest();
@@ -562,7 +615,7 @@ public partial class RagdollSpike : Node3D
private bool FinishMantle(bool both)
{
GD.Print($"RAGDOLL: mantle ({(both ? "two hands" : "one hand")}): pelvis rose up to {_maxPelvisRise:F3} m, feet y {_ragdoll.GroundY:F3}, origin y {_origin.Origin.Y:F3}, snaps {NewSnaps}");
bool onTop = _ragdoll.GroundY > 0.85f && _origin.Origin.Y > 0.8f;
bool onTop = _ragdoll.GroundY > _ledgeTop - 0.05f && _origin.Origin.Y > _ledgeTop - 0.1f;
_lastRise = _maxPelvisRise;
_maxPelvisRise = 0f;
return _finite && NewSnaps == 0 && (both ? onTop : !onTop); // standing tall also lifts the hips; one hand never gets you up
@@ -949,20 +1002,32 @@ public partial class RagdollSpike : Node3D
private void TickHumanMantle(bool both)
{
float t = _t;
_stickVelocity = t < 1f || (t > 4.5f && t < 5.5f) ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero; // stop before the ledge's far end
float crouch = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 1f) / 0.8f, 0f, 1f));
// Walk up to a waist-high ledge (stop before its far end); a chest-high one starts within reach.
_stickVelocity = (t < 1f && _ledgeTop < 1f) || (t > 4.5f && t < 5.5f) ? new Vector3(0f, 0f, -0.8f) : Vector3.Zero;
// Crouch to load the legs for a waist-high ledge; at a chest-high one you reach up instead.
float crouch = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 1f) / 0.8f, 0f, 1f)) * (_ledgeTop < 1f ? 1f : 0f);
float rise = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 2.2f) / 1.6f, 0f, 1f));
float eye = EyeHeight - 0.25f * crouch + 0.35f * rise;
float lean = 0.25f * rise;
float lean = (_ledgeTop < 1f ? 0.25f : 0.55f) * rise; // over a chest-high edge you put your head well over it
_headLocal = new Transform3D(new Basis(Vector3.Right, -0.3f * crouch), new Vector3(0f, eye, -lean));
float handZ = ObstacleFront - 0.08f - _origin.Origin.Z;
float handY = t < 1f ? 1.0f : Mathf.Lerp(0.95f, 0.55f, rise) - 0.05f * crouch;
// Hands on the top just past the near edge (of the box itself when it's a movable one).
float front = _ledgeBox != null ? _ledgeBox.GlobalPosition.Z + 0.35f : ObstacleFront;
float handZ = front - 0.08f - _origin.Origin.Z;
float handY = t < 1f ? 1.0f : Mathf.Lerp(_ledgeTop + 0.05f, _ledgeTop - 0.35f, rise) - 0.05f * crouch - _origin.Origin.Y;
Basis down = Aim(Vector3.Down + Vector3.Forward * 0.3f);
_leftLocal = both ? new Transform3D(down, new Vector3(-0.2f, handY, handZ)) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
_rightLocal = new Transform3D(down, new Vector3(0.2f, handY, handZ));
// Walking up, the hands are down at the sides; then they go up over the edge onto the top.
float reach = Mathf.Clamp((t - 0.8f) / 0.4f, 0f, 1f);
Vector3 rest = new(0.2f, 1.0f, -0.3f), onTop = new(0.2f, Mathf.Max(handY, _ledgeTop + 0.08f - _origin.Origin.Y), handZ);
Vector3 r = reach < 1f ? rest.Lerp(new Vector3(0.2f, _ledgeTop + 0.12f - _origin.Origin.Y, -0.3f), reach) : new Vector3(0.2f, handY, handZ);
if (reach >= 1f && t < 1.4f)
r = onTop;
_leftLocal = both ? new Transform3D(down, new Vector3(-r.X, r.Y, r.Z)) : new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
_rightLocal = new Transform3D(down, r);
if (_ragdoll.Built && !_needBuild)
_maxPelvisRise = Mathf.Max(_maxPelvisRise, _ragdoll.Pelvis.GlobalPosition.Y - 0.99f);
if (t > 4.5f)
if (OS.HasEnvironment("MANTLETRACE") && _ragdoll.Built && ((int)(t * 144f)) % 14 == 0)
GD.Print($"MT t {t:F2} pelvis {_ragdoll.Pelvis.GlobalPosition} v {_ragdoll.Pelvis.LinearVelocity.Y:F2} o {_origin.Origin.Y:F2} j {_ragdoll.Jumping} braced {_ragdoll.Braced} lifted {_ragdoll.Lifted} legs {_ragdoll.LegsSolid} g {_ragdoll.Grounded} foot {_ragdoll.GroundY:F2} hands {_ragdoll.Hand(0).GlobalPosition.Y:F2}/{_ragdoll.Hand(1).GlobalPosition.Y:F2} {(_stage.GetChildCount() > 0 && _stage.GetChild(0) is RigidBody3D rb ? rb.GlobalPosition.ToString() : "")}");
if (t > (_ledgeTop < 1f ? 4.5f : 5.5f))
{
_leftLocal = new Transform3D(Basis.Identity, new Vector3(-0.2f, 1.0f, -0.3f));
_rightLocal = new Transform3D(Basis.Identity, new Vector3(0.2f, 1.0f, -0.3f));