Arms playtest-2 fixes: flip-proof elbow (angle clamp + rate limit, controller-driven), Stage 2b hand coupling (blocked arm holds the hand back)

- Elbow stored as an angle around the arm axis vs a torso-frame reference: 85% hand-suggested,
  from the controller pose (not the physics palm), clamped +-110 deg, 6 rad/s rate limit
  (flip spike: 1.8 cm/tick max vs 13.8 unclamped)
- PhysicsArms.ConstrainHand: while an arm segment is in contact the hand may lead the
  forearm end by 3 cm; PhysicsHands applies it and suspends stuck-snap while active
- ArmSpike: forearm-rest-over-anvil (owner pose: collider fine, drawn forearm 3.9 cm -> 0),
  flip sweep, bar test now asserts the hand is held back; all six spikes PASS x3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-02 01:27:29 +03:00
1 parent 464cda2f71
commit 7bc6a866eb
5 files changed
+193 -51

No files matched your search

+25 -8
View File
@@ -77,7 +77,8 @@ TERMINOLOGY (owner-corrected — use precisely):
jointed hand yanks the prop — the grabber's grip break runs first instead, rip at 0.8 m).
Hand ghosts: translucent palm boxes shown at the controller pose when the physical hand lags
> 0.1 m — ghosts belong to the HANDS, not props. Static `GetBody(controller)` lookup used by
grabber and rig.
grabber and rig. The chase target passes through `PhysicsArms.ConstrainHand` first (arm
coupling); the stuck snap is also suspended while that coupling is active.
- **PropGrabber.cs** — grab v5.3: SOLVER-OWNED RIGID WELD via a LOCALLY PATCHED addon (needs
`research/godot-box3d-weld.md` + the v5.2 history in `research/vr-two-handed-grab.md` — the
pin joint's hidden spring targets IDENTITY and made every earlier iteration a "winch":
@@ -145,11 +146,12 @@ TERMINOLOGY (owner-corrected — use precisely):
NO joints (Box3D has no usable 3-DOF shoulder/wrist joint — PinJoint's identity spring, no
Generic6DOF — and AGENTS.md forbids joints between tracked bodies): each segment PD-chases
its analytic 2-bone IK pose, solved EVERY PHYSICS TICK from the PHYSICAL chest shoulders to
the PHYSICAL hand bodies (IK moved here from BodyRig: soft IK 0.98; elbow picked on its circle, 75% HAND-DRIVEN —
the forearm continues behind the wrist along the aim pose's forward axis, which gives
outward elbows for wraps/hugs — 25% torso-frame down/back/out; smoothed by bounded-rate
ROTATION around the arm axis (can't pass through zero and flip); capped at
max(shoulder, wrist) + 5 cm height). **Hierarchical
the PHYSICAL hand bodies (IK moved here from BodyRig: soft IK 0.98; elbow picked on its circle as an ANGLE around the arm axis relative to a torso-frame
down/back/out reference: 85% of the angle the HAND suggests — the forearm continues behind
the wrist along the CONTROLLER's (aim pose) forward axis, not the physics palm's, so a palm
fighting contact can't swing the elbow — clamped to ±110° (never through the inward/up side)
and rate-limited to 6 rad/s (no 180° flips; spike: 1.8 cm/tick max vs 13.8 unclamped);
capped at max(shoulder, wrist) + 5 cm height). **Hierarchical
budgets** (Nimso muscle model) from `BodyMassStat` 60 kg: hand 90 N ≺ forearm 180 N ≺ upper
arm 360 N ≺ chest 2400 N. Orientation = slewed angular-velocity servo via
`BodyMath.RotationError`; segment roll follows the body's current X axis (no flips).
@@ -160,7 +162,12 @@ TERMINOLOGY (owner-corrected — use precisely):
closed loop (lean from the body's own shoulders) made the chest hunt back and forth —
owner-reported, spike-measured 1.1 m/s → 0.019 m/s after the fix. Props held by a hand get
a collision exception with that side's arm segments (`SetHeld`, called by PropGrabber) —
a 180 N forearm shoving a prop welded to a 90 N hand was a fight. Exposes
a 180 N forearm shoving a prop welded to a 90 N hand was a fight. **Stage 2b hand coupling**
(`ConstrainHand`, called by PhysicsHands before chasing): while an arm segment is in
contact, the hand may lead the forearm's end by at most `HandArmSlack` 3 cm — a blocked
forearm holds the hand back and the hand ghost shows the controller. Free arms never limit
the hand. Fixes the owner's "palm behind the anvil, forearm rests on it" clip (drawn
forearm 3.9 cm into the anvil → 0; the collider was always fine at ≤ 6 mm). Exposes
`Shoulder/Elbow/Wrist/ElbowTarget/WristTarget(side)`; `Elbow` = where the two physical
segments meet. Spike: `scenes/arm_spike.tscn` (stretch-beyond-reach chest settle, hug elbows out
0.17 m (old pole: 0.02), held 50 kg anvil stable + forearm out of it, track gap, fast swing, arm pushes a prop
@@ -278,6 +285,14 @@ Floor StaticBody mask 1|3|4|5 (collision_mask 29).
held anvil) was NOT reproduced headless across 3 anvil heights — the drawn forearm is a
straight line from the PHYSICAL elbow to the hand wrist, so any elbow below a held object's
near edge can still draw through it; re-check in-headset.
- **Body Stage 2 arms — owner playtest 2 (2026-10-02)**: jitter fixed, elbows "pretty
good". Reported: occasional 180° elbow flip → angle clamp + rate limit, elbow now from the
controller pose; forearm clipping when RESTING the forearm on the anvil with the palm behind
it → measured: collider fine, DRAWN forearm 3.9 cm in (the hand was somewhere a straight
forearm can't connect to) → hand coupling. Owner raised max_physics_steps_per_frame to
12–14: harmless but NOT a quality knob (only catch-up ticks); Box3D's solver quality is
compiled in (`SUB_STEP_COUNT` 4 in `src/spaces/box3d_space_3d.cpp`, contactHertz 30) — the
fork could expose them if contact stiffness ever matters.
- **Body Stage 2 (physical arms) — remaining watch-list.** Things to watch in the first
playtest: elbow drawn displaced when a segment is blocked (that's the honest physics view),
arms sweeping props when seated at the table, any stutter at fast swings (spike: ≤ 6 cm
@@ -353,7 +368,9 @@ Floor StaticBody mask 1|3|4|5 (collision_mask 29).
| `PalmGrabOffset` | PropGrabber.cs | ±0.045 m along hand +X (left +, right −) | grab point on the palm (taste choice) |
| Grip/trigger thresholds | several files | 0.6 | polled Float edges |
| `SoftIKFactor` | PhysicsArms.cs | 0.98 | shoulder→hand distance clamp before IK solve (lower toward 0.95 if shake persists) |
| `PoleHandWeight` / `PoleSmoothRate` | PhysicsArms.cs | 0.75 / 12 | elbow pole: hand-axis share (forearm continues behind the wrist) vs torso-frame down/back/out; bounded-rate rotation smoothing |
| `PoleHandWeight` / `PoleSmoothRate` | PhysicsArms.cs | 0.85 / 12 | share of the hand-suggested elbow angle (from the controller's forward axis); exp smoothing of the angle |
| `PoleMaxAngle` / `PoleMaxSpeed` | PhysicsArms.cs | 110° / 6 rad/s | elbow angle clamp around the down/back/out reference + rate limit (anti-flip) |
| `HandArmSlack` | PhysicsArms.cs | 0.03 m | how far a hand may lead its forearm's end while the arm is in contact |
| `ElbowRiseAllowance` | PhysicsArms.cs | 0.05 m | elbow capped at max(shoulder, wrist) height + this (no chicken wings) |
| `TorsoYawHandsWeight` / `TorsoYawForwardness` / `TorsoYawSmoothRate` | PhysicsTorso.cs | 0.6 / 0.3 m / 12 | torso yaw spring: hands blend / forwardness normalizer / exp smoothing (≈200 ms) |
| `TorsoMass` / `MaxForce` / `MaxSpeed` | PhysicsTorso.cs | 30 kg / 2400 N / 6 m/s | chest chase budget (force-clamped PD) |