Body step 2.2: fix the 2.1 headset instabilities

Spike now runs real input (90 Hz sample-and-hold tracking + jitter under 144 Hz
physics), which reproduced the playtest. Root causes fixed:
- PoseFilter/RagdollDriver: timestamped extrapolation of tracking, one tick
  pipeline shared by PlayerRig and the spike
- low-pass all feed-forwards (ground control amplified jitter by body mass)
- arm muscles back to local targets (physical-frame aim fed chest rotation back)
- structural hand strength only when braced or holding
- no free-path view chasing while grounded (8 m crouch slide); stick hold-back
- explicit inertia for the Legs body (shape-less tuck tumbled the pelvis)
- world grab: Box3D shape queries return a null collider, use collider_id
- new crouch-hold phases; world-grab detection asserted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-02 08:37:56 +03:00
1 parent 2aff2b61da
commit 6006c5de98
8 files changed
+352 -81

No files matched your search

+7 -6
View File
@@ -11,7 +11,7 @@ public partial class PropGrabber : Node
private const float BreakTime = 0.25f;
private const float BreakDistanceBoth = 0.4f;
private const float GripRipDistance = 0.8f;
private const float WorldGrabRadius = 0.05f; // 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)
private static PropGrabber _instance;
@@ -197,7 +197,7 @@ public partial class PropGrabber : Node
_breakTimer[hand] = 0f;
}
private static Vector3 PalmPoint(RigidBody3D handBody, int hand) =>
public static Vector3 PalmPoint(RigidBody3D handBody, int hand) =>
handBody.GlobalPosition + handBody.GlobalTransform.Basis * new Vector3(PalmOffset(hand), 0f, 0f);
private static float PalmOffset(int hand) => hand == 0 ? PalmGrabOffset : -PalmGrabOffset;
@@ -214,11 +214,12 @@ public partial class PropGrabber : Node
nearest = NearestProp(handBody.GlobalPosition, CenterGrabRadius);
if (nearest != null)
return nearest;
return WorldUnderPalm(palm, handBody.GetWorld3D());
return WorldUnderPalm(palm, handBody.GetWorld3D()) ?? WorldUnderPalm(handBody.GlobalPosition, handBody.GetWorld3D());
}
// Climbing: any static body touching the palm (except the "no_grab" group) can be held.
private static StaticBody3D WorldUnderPalm(Vector3 palm, World3D world)
// Climbing: any static body near the palm (except the "no_grab" group) can be held.
// NOTE (Box3D wrapper): shape-query results carry a null "collider" — resolve "collider_id".
public static StaticBody3D WorldUnderPalm(Vector3 palm, World3D world)
{
PhysicsShapeQueryParameters3D query = new()
{
@@ -228,7 +229,7 @@ public partial class PropGrabber : Node
};
foreach (Godot.Collections.Dictionary hit in world.DirectSpaceState.IntersectShape(query, 8))
{
if (hit["collider"].AsGodotObject() is StaticBody3D body && !body.IsInGroup(NoGrabGroup))
if (GodotObject.InstanceFromId((ulong)hit["collider_id"]) is StaticBody3D body && !body.IsInGroup(NoGrabGroup))
return body;
}
return null;