Files
VR-Mess/scripts/RagdollDriver.cs
T
FoxyzandClaude Opus 5.5 6006c5de98 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>
2026-10-02 08:37:56 +03:00

94 lines
4.3 KiB
C#

using Godot;
// Smooths one tracked pose for the physics tick. Tracking updates at the display rate (90 Hz) but
// physics runs at 144 Hz: sampled raw, every second or third tick sees the pose frozen and the next
// one jumped, and every velocity feed-forward downstream (muscles, hand guides, ground control)
// turns that into jerks — the step-2.1 "wobbling more and more / sliding around" in the headset.
// Each new sample is timestamped; between samples the pose is extrapolated with the (smoothed)
// sample velocity. Works in the PLAY-SPACE-LOCAL frame, so moving the origin never looks like motion.
public sealed class PoseFilter
{
public const float MaxExtrapolation = 1.5f / 90f; // s
public const float VelocitySmoothing = 0.3f; // EMA weight of each new sample
public const float JumpDistance = 0.5f; // m between samples = recenter, not motion
private Transform3D _last;
private double _tLast;
private bool _has;
public Vector3 Velocity { get; private set; } // play-space-local m/s
public Vector3 AngularVelocity { get; private set; } // play-space-local rad/s
public void Reset() => _has = false;
public Transform3D Sample(Transform3D raw, double now)
{
if (!_has)
{
_has = true;
_last = raw;
_tLast = now;
Velocity = AngularVelocity = Vector3.Zero;
return raw;
}
if (raw != _last)
{
float interval = (float)(now - _tLast);
if (interval > 1e-4f)
{
if (raw.Origin.DistanceTo(_last.Origin) > JumpDistance)
{
Velocity = AngularVelocity = Vector3.Zero;
}
else
{
Vector3 v = (raw.Origin - _last.Origin) / interval;
Vector3 w = BodyMath.RotationError(raw.Basis.GetRotationQuaternion(), _last.Basis.GetRotationQuaternion()) / interval;
Velocity = Velocity.Lerp(v, VelocitySmoothing);
AngularVelocity = AngularVelocity.Lerp(w, VelocitySmoothing);
}
}
_last = raw;
_tLast = now;
}
float age = Mathf.Min((float)(now - _tLast), MaxExtrapolation);
Basis spin = AngularVelocity.LengthSquared() > 1e-10f
? new Basis(AngularVelocity.Normalized(), AngularVelocity.Length() * age) : Basis.Identity;
return new Transform3D(spin * _last.Basis.Orthonormalized(), _last.Origin + Velocity * age);
}
}
// The per-physics-tick body pipeline, shared by PlayerRig (headset) and RagdollSpike so the
// regression runs exactly what the game runs: filter the tracked poses → play-space correction
// (push-back / ground follow / falls) → True Body solve → (re)build → ragdoll muscles.
// Locomotion (moving the origin by the stick, turning, teleports) happens before Tick.
public sealed class RagdollDriver
{
public readonly PoseFilter Head = new(), Left = new(), Right = new();
public TrueBodySolver Solver;
public PlayerRagdoll Ragdoll;
public bool RebuildRequested = true;
private double _now;
// origin = play space (modified by the correction); raw* = origin-local tracked poses as last
// reported (repeat between tracking updates); moveVelocity = intended origin velocity (stick),
// the only origin motion that is real hand motion for the guides' feed-forward.
public void Tick(BodyProfile profile, ref Transform3D origin, Transform3D rawHead, Transform3D rawLeft, Transform3D rawRight,
Vector3 moveVelocity, float dt)
{
_now += dt;
Transform3D head = Head.Sample(rawHead, _now), left = Left.Sample(rawLeft, _now), right = Right.Sample(rawRight, _now);
if (Ragdoll.Built && !RebuildRequested)
origin.Origin += Ragdoll.OriginCorrection((origin * head).Origin, origin.Origin.Y, dt);
Transform3D h = origin * head, l = origin * left, r = origin * right;
Solver.Solve(profile, h, l, r, origin.Origin.Y, dt);
if (RebuildRequested || !Ragdoll.Built)
{
Ragdoll.Build(profile, Solver, l, r);
RebuildRequested = false;
}
Vector3 lv = origin.Basis * Left.Velocity + moveVelocity, rv = origin.Basis * Right.Velocity + moveVelocity;
Ragdoll.Step(Solver, l, r, lv, rv, origin.Origin.Y, dt);
}
}