Grab buffer (squeeze early, catch on contact), elbow can't fold inward when the controller turns out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-02 21:31:14 +03:00
1 parent ca60df3d75
commit b8365ee95f
4 files changed
+48 -5

No files matched your search

+6
View File
@@ -241,3 +241,9 @@ engine does all the work"; higher ground "doesn't move my body up … until I pu
| **Render-clock tracking timestamps** (`PoseFilter.Sample(..., sampleTime)`, `PlayerRig.TrackingAge`, bible v2 recipe) | At 1 m/s the per-tick speed spread is 1.25 m/s by arrival vs 0.013 m/s render-timed — every feed-forward beat at 18 Hz. Constant ~2-tick lag instead. Toggle in the menu to compare | | **Render-clock tracking timestamps** (`PoseFilter.Sample(..., sampleTime)`, `PlayerRig.TrackingAge`, bible v2 recipe) | At 1 m/s the per-tick speed spread is 1.25 m/s by arrival vs 0.013 m/s render-timed — every feed-forward beat at 18 Hz. Constant ~2-tick lag instead. Toggle in the menu to compare |
| **max_physics_steps_per_frame 14 → 6** | One hitch could queue 14 ragdoll ticks in a frame (bible v2) | | **max_physics_steps_per_frame 14 → 6** | One hitch could queue 14 ragdoll ticks in a frame (bible v2) |
| Tried and reverted: arm aim from the physical shoulder vertically while climbing/tilted | Free-hand error 6.4 → 6.2 cm only, and it weakened pulls. The "tracking off when tilted" screenshots need a recording to diagnose | | Tried and reverted: arm aim from the physical shoulder vertically while climbing/tilted | Free-hand error 6.4 → 6.2 cm only, and it weakened pulls. The "tracking off when tilted" screenshots need a recording to diagnose |
**2.7 follow-ups (owner playtest):** grab buffer — a squeeze that finds nothing keeps trying for 0.4 s while held
(`PropGrabber.GrabBuffer`; launching up a ladder left too short a window to catch a rung; Nimso's videos only
mention arm collision saving missed grabs). Elbow pole clamp asymmetric: 110° outward, 35° inward
(`ArmSolver.PoleMaxInward`; controller turned out folded the elbow 5.6 cm inward → 2.6 cm; 17° broke the crawl's
pillar arms). Known, minor: the procedural legs lag and jitter during very fast falls.
+6 -2
View File
@@ -11,7 +11,8 @@ public sealed class ArmSolver
public const float SoftIKFactor = 0.98f; public const float SoftIKFactor = 0.98f;
public const float PoleHandWeight = 0.85f; public const float PoleHandWeight = 0.85f;
public const float PoleSmoothRate = 12f; public const float PoleSmoothRate = 12f;
public const float PoleMaxAngle = 1.92f; // 110 deg either way from down/back/out public const float PoleMaxAngle = 1.92f; // 110 deg outward from down/back/out
public const float PoleMaxInward = 0.6f; // 35 deg inward: a controller turned outward must not fold the elbows toward each other (owner)
public const float PoleMaxSpeed = 6f; // rad/s public const float PoleMaxSpeed = 6f; // rad/s
public const float ElbowRiseAllowance = 0.05f; public const float ElbowRiseAllowance = 0.05f;
@@ -76,7 +77,10 @@ public sealed class ArmSolver
targetAngle = raw * PoleHandWeight * confidence; targetAngle = raw * PoleHandWeight * confidence;
} }
} }
targetAngle = Mathf.Clamp(targetAngle, -PoleMaxAngle, PoleMaxAngle); // Asymmetric: the elbow may swing far out, but only a little in toward the body's midline.
float outward = refB.Dot(side); // > 0: a positive angle swings the elbow outward
float inwardLimit = Mathf.Lerp(PoleMaxAngle, PoleMaxInward, Mathf.Clamp(Mathf.Abs(outward) / 0.5f, 0f, 1f));
targetAngle = outward >= 0f ? Mathf.Clamp(targetAngle, -inwardLimit, PoleMaxAngle) : Mathf.Clamp(targetAngle, -PoleMaxAngle, inwardLimit);
float step = (targetAngle - _poleAngle) * (1f - Mathf.Exp(-delta * PoleSmoothRate)); float step = (targetAngle - _poleAngle) * (1f - Mathf.Exp(-delta * PoleSmoothRate));
_poleAngle += Mathf.Clamp(step, -PoleMaxSpeed * delta, PoleMaxSpeed * delta); _poleAngle += Mathf.Clamp(step, -PoleMaxSpeed * delta, PoleMaxSpeed * delta);
Vector3 pole = refN.Rotated(dir, _poleAngle); Vector3 pole = refN.Rotated(dir, _poleAngle);
+13 -2
View File
@@ -11,6 +11,10 @@ public partial class PropGrabber : Node
private const float BreakTime = 0.25f; private const float BreakTime = 0.25f;
private const float BreakDistanceBoth = 0.4f; private const float BreakDistanceBoth = 0.4f;
private const float GripRipDistance = 0.8f; private const float GripRipDistance = 0.8f;
// Grab buffer (owner: launching up a ladder leaves a short window to catch the next rung): a
// squeeze that finds nothing keeps trying while held, so you can squeeze early and catch the
// hold the moment the palm reaches it.
private const float GrabBuffer = 0.4f; // s
private const float WorldGrabRadius = 0.07f; // palm sphere for grabbing static world (ledges, bars, holds) private const float WorldGrabRadius = 0.07f; // palm sphere for grabbing static world (ledges, bars, holds)
public const string NoGrabGroup = "no_grab"; // static bodies that can't be grabbed (the floor) public const string NoGrabGroup = "no_grab"; // static bodies that can't be grabbed (the floor)
@@ -26,7 +30,8 @@ public partial class PropGrabber : Node
private readonly uint[] _grabSeq = new uint[2]; private readonly uint[] _grabSeq = new uint[2];
private readonly float[] _breakTimer = new float[2]; private readonly float[] _breakTimer = new float[2];
private readonly bool[] _gripPrev = new bool[2]; private readonly bool[] _gripPrev = new bool[2];
private readonly SoftGrab[] _easing = new SoftGrab[2]; // grabbed, being eased into the hand (no weld yet) private readonly SoftGrab[] _easing = new SoftGrab[2];
private readonly double[] _bufferUntil = new double[2]; // grabbed, being eased into the hand (no weld yet)
private uint _grabCount; private uint _grabCount;
public override void _Ready() public override void _Ready()
@@ -86,12 +91,18 @@ public partial class PropGrabber : Node
bool grip = controller.GetFloat("grip") > GripThreshold; bool grip = controller.GetFloat("grip") > GripThreshold;
int other = 1 - hand; int other = 1 - hand;
if (grip && !_gripPrev[hand] && _held[hand] == null && _easing[hand] == null) double now = Time.GetTicksMsec() / 1000.0;
if (grip && !_gripPrev[hand])
_bufferUntil[hand] = now + GrabBuffer;
if (grip && now <= _bufferUntil[hand] && _held[hand] == null && _easing[hand] == null)
{ {
PhysicsBody3D prop = FindProp(controller, hand); PhysicsBody3D prop = FindProp(controller, hand);
RigidBody3D handBody = PhysicsHands.GetBody(controller); RigidBody3D handBody = PhysicsHands.GetBody(controller);
if (prop != null && handBody != null) if (prop != null && handBody != null)
{
_easing[hand] = SoftGrab.Begin(handBody, hand, prop); // welded once it's in the hand (_PhysicsProcess) _easing[hand] = SoftGrab.Begin(handBody, hand, prop); // welded once it's in the hand (_PhysicsProcess)
_bufferUntil[hand] = 0.0;
}
} }
if (!grip) if (!grip)
_easing[hand] = null; _easing[hand] = null;
+23 -1
View File
@@ -84,6 +84,7 @@ public partial class RagdollSpike : Node3D
_phases.Add(("hinge sign", SetupHinge, TickHinge, 0.3f, FinishHinge)); _phases.Add(("hinge sign", SetupHinge, TickHinge, 0.3f, FinishHinge));
_phases.Add(("pose filter: render-timed tracking moves evenly (no 18 Hz beat)", Rest, _ => { }, 0.05f, FinishPoseFilter)); _phases.Add(("pose filter: render-timed tracking moves evenly (no 18 Hz beat)", Rest, _ => { }, 0.05f, FinishPoseFilter));
_phases.Add(("elbow: a controller turned outward doesn't fold the elbow inward", Rest, _ => { }, 0.05f, FinishElbowOut));
_phases.Add(("stands still, head on the camera, hands on the controllers", Rest, _ => { }, 4f, FinishStand)); _phases.Add(("stands still, head on the camera, hands on the controllers", Rest, _ => { }, 4f, FinishStand));
_phases.Add(("reaches forward, out and overhead", Rest, TickReach, 4.5f, FinishReach)); _phases.Add(("reaches forward, out and overhead", Rest, TickReach, 4.5f, FinishReach));
_phases.Add(("fast arm swings stay attached", Rest, TickSwing, 3f, FinishSwing)); _phases.Add(("fast arm swings stay attached", Rest, TickSwing, 3f, FinishSwing));
@@ -234,6 +235,26 @@ public partial class RagdollSpike : Node3D
private int NewSnaps => _ragdoll.Snaps - _snapsAtStart; private int NewSnaps => _ragdoll.Snaps - _snapsAtStart;
// Right arm reaching forward-down, controller yawed 0..90° outward: the elbow must stay on the
// outer side of the shoulder→wrist line (toward +X), never toward the other arm.
private static bool FinishElbowOut()
{
float worst = float.MaxValue;
for (int deg = 0; deg <= 90; deg += 15)
{
ArmSolver arm = new();
Vector3 shoulder = new(0.2f, 1.45f, 0f), target = new(0.25f, 1.05f, -0.4f);
Basis hand = new Basis(Vector3.Up, -Mathf.DegToRad(deg)) * new Basis(Vector3.Right, -0.6f);
for (int i = 0; i < 200; i++)
arm.Solve(shoulder, target, Basis.Identity, Vector3.Right, hand, 0.3f, 0.27f, 1f / 144f);
Vector3 dir = (arm.Wrist - shoulder).Normalized();
Vector3 mid = shoulder + dir * (arm.Elbow - shoulder).Dot(dir);
worst = Mathf.Min(worst, arm.Elbow.X - mid.X); // < 0 = elbow toward the midline
}
GD.Print($"RAGDOLL: elbow vs the arm line with the controller turned 0..90° outward: most inward {worst:F3} m (limit -0.04)");
return worst > -0.04f;
}
// A controller moving at 1 m/s, sampled once per 90 Hz frame (predicted for that frame's render // A controller moving at 1 m/s, sampled once per 90 Hz frame (predicted for that frame's render
// time), read by 144 Hz ticks. Per-tick output steps should all be 1/144 m: timestamped by // time), read by 144 Hz ticks. Per-tick output steps should all be 1/144 m: timestamped by
// arrival they alternate (1 or 2 ticks late), timestamped by render time they don't. // arrival they alternate (1 or 2 ticks late), timestamped by render time they don't.
@@ -1117,7 +1138,8 @@ public partial class RagdollSpike : Node3D
{ {
float t = _t; float t = _t;
// Walk up to a waist-high ledge (stop before its far end); a chest-high one starts within reach. // 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; // (A 70 cm box is too small to walk on after climbing it.)
_stickVelocity = (t < 1f && _ledgeTop < 1f) || (t > 4.5f && t < 5.5f && _ledgeBox == null) ? 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. // 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 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 rise = Mathf.SmoothStep(0f, 1f, Mathf.Clamp((t - 2.2f) / 1.6f, 0f, 1f));