Body Stage 1 failed first playtest (NaN solver blowup: seated chest-in-table + two-handed anvil weld); failure brief for external agent; docs marked, body track blocked pending diagnosis

This commit is contained in:
Foxyz1302 committed 2026-10-01 23:20:40 +03:00
1 parent 4fdfcfd928
commit ce1e0e3b0c
3 files changed
+167 -17

No files matched your search

+12 -5
View File
@@ -228,18 +228,25 @@ device only; the editor has an XR simulator for dev. Trackers/action map: see `x
(arm IK, IK stability/deep, grabbing, two-handed grab/Box3D internals, addon weld patch,
body physics hugs, body physics Godot facts) — READ before re-researching or changing
grab/IK/body. Stink-O's box3d-godot (custom components) = documented plan-B.
8. **Body physics Stage 1 — PHYSICAL CHEST, implemented 2026-10-01.** `PhysicsTorso.cs`:
8. **Body physics Stage 1 — PHYSICAL CHEST, implemented 2026-10-01, FAILED first playtest.**
`PhysicsTorso.cs`:
one chase-driven RigidBody3D (30 kg box, gravity off, CCD) toward an HMD-derived target —
the PhysicsHands PD pattern at body mass (2400 N budget), orientation = slewed
angular-velocity servo (self-rights, holds yaw), 2 m snap safety, torso-yaw spring moved
here from BodyRig. Camera stays KINEMATIC — torso chases the head, never the reverse
(Boneworks sickness lesson). No self-collision with own hands (layer scheme above).
`BodyRig.cs` is VISUALS-only now (head cube + 2-bone analytic arms, soft IK, elbow pole,
**First in-headset session NaN'd the solver** (owner seated: chest overlapped the table;
two-handed 50 kg anvil weld pressed into it → `!v.is_finite()` transform flood, "head
detaching from the body"). **DO NOT extend the body track until diagnosed** — analysis,
hypotheses and the headless-repro plan live in `research/body-stage1-failure-brief.md`
(self-contained, forwardable to the owner's external agent). `BodyRig.cs` is
VISUALS-only (head cube + 2-bone analytic arms, soft IK, elbow pole,
reach budget feeding `PhysicsTorso.ReachOffset`). Headless regression:
`scenes/body_spike.tscn` must print "BODY: overall PASS" (also proves the Box3D pair
rules: one-way contact, hand-through-chest exclusion, prop contact). Stage 2 (physical
arm chains + hierarchical per-joint budgets, Nimso/Boneworks tier) awaits the owner's
Stage-1 feel verdict; design + prior art in `research/vr-body-physics-hugs.md`,
rules: one-way contact, hand-through-chest exclusion, prop contact) — note its repro gap:
no static-table overlap, no welded heavy props. Stage 2 (physical
arm chains + hierarchical per-joint budgets, Nimso/Boneworks tier) is BLOCKED on the
Stage-1 fix; design + prior art in `research/vr-body-physics-hugs.md`,
stack facts in `research/vr-body-physics-godot.md`.
9. **Getting a body (rig) — placeholder v1.3 (cube character, agent-built).** `BodyRig.cs`:
all-BOX primitives (owner preference) — head 0.15³ behind the eyes, slim torso retracted,
+26 -12
View File
@@ -120,7 +120,10 @@ TERMINOLOGY (owner-corrected — use precisely):
grab/teleport; `IsPanelCollider(node)` identifies interactive colliders for lasers.
- **TeleportMarker.cs** — `scenes/teleport_marker.tscn`: disc + beam + Label3D, group
"teleport_marker", StaticBody pick box. `SetNear` hides visuals, `SetHighlighted` swaps colors.
- **PhysicsTorso.cs** — the player's PHYSICAL CHEST (body-physics Stage 1, implemented 2026-10-01).
- **PhysicsTorso.cs** — the player's PHYSICAL CHEST (body-physics Stage 1). **Implemented
2026-10-01; FAILED first in-headset playtest (seated chest-vs-table overlap + two-handed
50 kg anvil weld → NaN transform flood) — see §5 known issues +**
**`research/body-stage1-failure-brief.md` before touching anything here.**
One RigidBody3D box (0.22×0.5×0.13, 30 kg, gravity off, CCD, contact monitor), chase-driven
toward an HMD-derived target: target = head − 6 cm back + torso-yaw basis·(0, −0.42, +0.06)
+ `ReachOffset` (fed by BodyRig's reach budget). Linear drive = the PhysicsHands pattern at
@@ -213,6 +216,19 @@ Floor StaticBody scans layer 3 (collision_mask 13).
## 5. Known issues / open questions
- **Body Stage 1 (chest) FAILED in-headset on first playtest (2026-10-01) — DO NOT extend the
body track until diagnosed.** Owner report: "it quickly collapsed with my head detaching
from the body"; screenshot = prop table chaos. Log evidence: session calibrated at head
height **1.01 m (seated)** → chest box overlaps the table; the NaN flood
(`!v.is_finite()` at `instance_set_transform`, 22.8k lines) starts right after
**"grabbed 'Anvil · 50 kg' (50 kg, left, two-handed)"** — i.e. chest-vs-statics overlap +
50 kg prop rigid-welded to both 90 N hands pressed into that mess. Headless body spike
passes (it lacks static-table overlap + welded heavy props — that's the repro gap).
Full self-contained analysis for the owner's external agent:
**`research/body-stage1-failure-brief.md`** (evidence chain, hypotheses H1–H3, mitigation
candidates M1–M5, headless-repro plan). Working suspicion: the documented
"script velocity SETs + reaction forces = limit cycle" lesson (grab v5.0) resurfacing at
body scale — 30 kg velocity-write servo braced against statics.
- **AnchorMoveMode NEEDS FIXING (owner playtest report, 2026-10-01)** — two-hand turning
is NOT working as intended (not 2-anchor based) and simultaneous two-hand movement acts
weird. NOTE before fixing: owner is considering replacing grab-locomotion with physics
@@ -295,17 +311,15 @@ Floor StaticBody scans layer 3 (collision_mask 13).
## 7. Next steps (in order)
1. **Full physics body track — Stage 1 (chest body) IMPLEMENTED, validate in-headset** —
`PhysicsTorso.cs` is live: chase chest (30 kg, 2400 N), yaw spring moved in, collision
layers reworked (3 = body, 4 = hands, no self-collision). Owner playtests: chest contact
with props (lean a crate on it, catch the bowling ball against it), shove response
(props push the chest, it recovers — camera never moves), turn following (torso lags
head naturally), weight feel. Hug testing on props as agreed. Headless: `body_spike`
PASS 3×; all other spikes still PASS 3×. **Stage 2 (arm chains, hierarchical budgets,
Nimso/Boneworks tier) awaits Stage-1 feel verdict** — see
`research/vr-body-physics-hugs.md` §5/§7 + `research/vr-body-physics-godot.md`.
Tuning knobs live in HANDOFF §6 (PhysicsTorso rows); first levers if feel is off:
`MaxForce` (gives more/less), `OrientKp` (torso yaw stiffness).
1. **Diagnose + fix the body Stage-1 collapse (BLOCKS the body track)** — first playtest of
the physical chest NaN'd the solver (seated chest-in-table overlap + two-handed 50 kg
anvil weld pressed into it; "head detaching from the body"). All context, hypotheses,
mitigation candidates and the headless-repro plan are in
`research/body-stage1-failure-brief.md` — written self-contained so the owner can forward
it to their external agent verbatim. First move per the brief: extend `body_spike` with
the exact cocktail (chest target overlapping a static slab + two welded 90 N hands
dragging a 50 kg box into the chest) and get a deterministic NaN repro before touching
PhysicsTorso.cs. Owner re-playtests feel only after the fix.
2. **Locomotion decision** — owner report 2026-10-01: two-hand anchor turning broken (not
2-anchor based), simultaneous two-hand movement acts weird, teleport + lasers broken
(experiments, not current issues). Owner's candidate replacement: physics thumbstick
+129
View File
@@ -0,0 +1,129 @@
# Body Stage 1 failure brief — chest collapse in-headset (2026-10-01)
SELF-CONTAINED brief for an external agent (zero project context assumed). Godot 4.7.2 mono,
C#, PCVR at 90 Hz (physics tick = 90), physics engine = **Box3D** (bearlikelion's
`godot-box3d` GDExtension — a full PhysicsServer3D replacement; locally patched so Godot's
ConeTwistJoint3D maps to Box3D's `b3WeldJoint` with rigid stiffness at bias 0). Everything
below is verified code or log evidence unless marked [H] (hypothesis).
## What was just built (body-physics Stage 1)
The player's virtual chest became a physical body. `PhysicsTorso.cs` (Node) spawns:
- One RigidBody3D, box 0.22×0.5×0.13 m, **Mass 30**, GravityScale 0, CCD on, contact monitor,
CollisionLayer = layer 3 (player body), CollisionMask = layers 1|3 (world+props | body).
- Every physics tick (90 Hz), target = head position (from the HMD camera node, kinematic)
minus 6 cm along flattened camera forward, then offset by the torso-yaw basis (a yaw-only
basis smoothed toward a blend of HMD yaw and hands-midpoint yaw) by (0, −0.42, +0.06),
plus a small "reach offset" fed by the visual rig (≤ 0.25 m horizontal clamp).
- Linear drive — force-clamped PD, the same pattern the hand bodies use:
```csharp
Vector3 desiredVelocity = (target - _body.GlobalPosition) / dt; // dt = 1/90
if (desiredVelocity.Length() > MaxSpeed) desiredVelocity = desiredVelocity.Normalized() * MaxSpeed; // 6
Vector3 dv = desiredVelocity - _body.LinearVelocity;
Vector3 force = dv / dt * TorsoMass; // 30 kg
if (force.Length() > MaxForce) force = force.Normalized() * MaxForce; // 2400 N
_body.LinearVelocity += force / TorsoMass * dt; // DIRECT velocity write
```
- Orientation drive — slewed angular velocity servo toward the yaw target:
```csharp
Quaternion qErr = torsoBasisQ * _body.GlobalTransform.Basis.GetRotationQuaternion().Inverse();
if (qErr.W < 0f) qErr = -qErr; // shortest arc
Vector3 spinDesired = axis(qErr) * min(angle(qErr) * OrientKp /*20*/, OrientMaxSpin /*10*/);
Vector3 wErr = spinDesired - _body.AngularVelocity;
if (wErr.Length() > OrientMaxAlpha * dt /*150/90*/) wErr = wErr.Normalized() * OrientMaxAlpha * dt;
_body.AngularVelocity += wErr; // DIRECT angular velocity write
```
- Snap safety: if body-to-target distance > 2 m → teleport body to target, zero velocities.
- Head/camera stays kinematic; the torso chases the head, never the reverse.
Context bodies:
- **Hands**: two RigidBody3D palms (0.4 kg, layer 4, mask 1|4), same velocity-write chase at
90 N, rotation hard-locked via angular velocity sets (cap 120 rad/s). They chase XR
controllers. Stuck-snap and teleport-snap safeties are SUSPENDED for a hand while it grips.
- **Grab**: grabbing a prop creates a ConeTwistJoint3D (= b3WeldJoint, bias 0 = RIGID,
locks relative pose) between hand body and prop. Two-handed grab = prop welded to BOTH
hands rigidly. Props: layer 1, mask 1|3|4. Statics (floor/table): layer 1.
- **No self-collision**: hands (4) and chest (3) exclude each other both ways. Chest DOES
collide with props (layer 1) and static world (layer 1) — deliberate, chest is the
contact surface for hugs/leaning.
- Headless regression (body spike: track/yaw/5kg-impact/teleport/self-right) PASSES 3× —
but the spike has NO static table and NO welded heavy prop touching the chest.
## The failure (owner playtest, first session with the chest)
Owner report: "it quickly collapsed with my head detaching from the body". Screenshot shows
the prop table in chaos (props piled/thrown, panel knobs displaced). Log
(`godot.log`, ~45.7k lines, 22.8k of them the error below):
- Calibration line: **head height 1.01 m** → the player was SEATED/crouching (standing
calibration would be ~1.6-1.7 m). Chest target therefore ≈ 0.59 m height; chest box spans
≈ 0.34-0.84 m; the prop table top is at 0.70-0.75 m → **the seated chest overlaps the
table geometry** (chest mask includes statics). [H] constant penetration + 2400 N servo.
- Normal play: grabbed/released bowling ball, brick, apple — all fine.
- Then: `grabbed 'Anvil · 50 kg' (50 kg, left)`, released, then
**`grabbed 'Anvil · 50 kg' (50 kg, left, two-handed)`** — and within ticks the log floods:
```
ERROR: Condition "!v.is_finite()" is true.
at: instance_set_transform (servers/rendering/renderer_scene_cull.cpp:1000)
```
×22,829 — some node's transform went NaN/Inf (rendering rejects it). The visual result
matches the chest body (and/or anvil) transforming to garbage → the torso mesh vanishes/
teleports → "head detached from the body" (head cube rides the camera, body went away).
- Owner kept grabbing/releasing the anvil afterwards (releases logged at 0.00-0.01 m/s —
the anvil was braced/nearly immobile), then removed the headset (focus lost).
## Hypotheses (ranked)
- **H1 — solver blowup from stacked authorities [most likely]**: the chest (velocity-write
servo, 2400 N) pressed against the STATIC table (seated overlap) while the 50 kg anvil
rigid-welded to both 90 N velocity-write hand bodies was grabbed/moved into that mess.
Three script-side velocity servos + rigid welds + persistent static contact in one
constraint island. Box3D's solver gets conflicting infinite-stiffness demands → energy
injection → NaN. This project has PRIOR ART for exactly this class: documented in
`research/vr-two-handed-grab.md` — "script velocity SETs + reaction forces = limit
cycle" (grab v5.0-era instability). The hands survive the same pattern only because
they're 0.4 kg, rarely braced, and have stuck-snap escape hatches. The chest is 30 kg,
overlaps the table whenever the owner sits at it, and has no escape.
- **H2 — chest vs table penetration alone**: even without the anvil, seated chest-in-table
= constant deep penetration vs a static body; the velocity servo never yields (position
error persists → max force every tick) → pressure + friction against statics pumps the
solver. The anvil grab was just the straw.
- **H3 — CCD tunneling/spike**: 30 kg body with CCD in deep contact + welded 50 kg chain —
CCD contact generation bug under stress. [H] least likely but cheap to test.
## Candidate mitigations (evaluate, don't just pick one)
- **M1 — ApplyForce instead of velocity writes for the chest** (solver-owned integration;
keeps the 2400 N clamp). The hands keep velocity writes (they have escape hatches).
Boneworks' design rule "no infinite forces" supports solver-integrated forces.
- **M2 — collision-aware target or compliance**: if the chest is in persistent contact
(contact monitor reports statics) with saturated force, soften (reduce effective budget)
or stop pushing (target clamps out of penetration). Generic "yield when braced".
- **M3 — seated reality**: the chest target height derives purely from HMD (−0.42). When
seated at a table the body legitimately overlaps world geometry. Options: chest collision
disabled vs statics (mask 3 only — loses leaning on tables), or per-body contact disabling
when penetration is self-inflicted (chase target inside a static), or pushing the TABLE
(chest becomes heavy enough to matter? 30 kg vs static = never).
- **M4 — saturation watchdog**: like the hands' stuck-snap: if force saturated AND position
error persistent AND in contact for > N ms → log + temporarily reduce budget / exclude the
offending pair. Keeps M1's solver honesty while adding the escape hatch.
- **M5 — reproduce headless first**: extend the body spike with the exact cocktail — chest
target set so the box overlaps a static table slab; a 50 kg box welded rigid to two
0.4 kg "hand" bodies (90 N chases) pressed into the chest; then sweep. A deterministic
NaN repro beats every hypothesis debate. The full grab spike already emulates hands+welds
headless (GrabSpike.cs pattern) — compose it with the chest.
## Questions to answer
1. Can a deterministic headless repro be built (M5)? If yes → does M1 alone stop the NaN?
2. Is the velocity-write chase on a contact-rich 30 kg body categorically unsafe on Box3D,
or only under simultaneous weld+static stacking? (Box3D source available in .vendor/.)
3. What does the solver do with direct LinearVelocity writes on a body that is ALSO in an
active contact manifold — are the writes applied pre- or post-solve, and can the contact
impulse + next-tick write loop escalate?
4. Seated-at-table is a first-class scenario for this product (scene viewer). What's the
right long-term chest-vs-world story (M2/M3) that still allows leaning on tables?
Owner directive: painlessness bar = no multi-iteration fundamental sagas. Deliver the
analysis, the repro, and the smallest honest fix — the owner playtests feel afterwards.