# VR Messs — Agent Handoff Read `AGENTS.md` in the repo root first — it is the authoritative project brain. This file orients you; AGENTS.md governs. Verify every claim here against the code when you touch it (code wins over both docs if they disagree). ## 1. What this project is VR anime-character **scene viewer** — Koikatsu-VR-class, but with real physics hands. **Not a game**: no loops, quests, progression. Explore, interact, touch, pose — that's the product. This repo is the experimentation lab: designs are hypotheses validated with cheap throwaway spikes. TERMINOLOGY (owner-corrected — use precisely): - **"getting a body"** = the physical rig (torso/arms/hands, the interaction body). - **"embodying"** = viewing from a character's perspective in an animation (view-follow + avatar). Two separate systems. Never call the rig milestone "embodying". ## 2. Toolchain & environment - **Godot 4.7.2 mono** (binary `godot-mono`), **C# / .NET 8** (dotnet SDK 10 present). PCVR, desktop-only. - **Physics: Box3D** via `addons/godot-box3d` GDExtension (bearlikelion server-replacement, v0.2.6); setting `physics/3d/physics_engine="Box3D Physics"`. Exactly ONE `.gdextension` file may exist — a stray duplicate double-loads the library and breaks the server ("required virtual method" errors). **The addon is LOCALLY PATCHED** (grab v5.3 depends on it): the wrapper now maps Godot's ConeTwistJoint3D → Box3D's b3WeldJoint (rigid grab weld). Originals in `addons/godot-box3d/bin-stock/`; source + rebuild recipe + patch in `.vendor/` (`.vendor/box3d-weld.patch`, `.vendor/godot-box3d/`, see `research/godot-box3d-weld.md`) — **only the Linux binary is patched**; Windows/macOS binaries are stock (ConeTwist still errors there until rebuilt). Jolt fallback = flip that one project-setting line; all gameplay code stays standard RigidBody3D/StaticBody3D/CharacterBody3D so the flip is lossless. - Runtime stack: **WiVRN + WayVR** on Linux. Headset + runtime support hand tracking (treat as available). - Renderer **Forward+** (Mobile is the docs' VR recommendation; deferred until profiling justifies). - Build: `dotnet build`. Headless validation — physics: `godot-mono --headless --path . scenes/physics_spike.tscn` must print **"SPIKE: overall PASS"**; grab: `godot-mono --headless --path . scenes/grab_spike.tscn` must print **"GRAB: overall PASS"**. Both PASS as of this handoff. Headless asset import: `godot-mono --headless --import`. - Engine logs: `~/.local/share/godot/app_userdata/VR Messs/logs/godot.log` — **read after user playtests instead of guessing**. - **Docs rules (project's #1 failure mode)**: only the local pinned 4.7 docs at `/home/foxyz/Projects/Games/.shared/godot-docs-4.7` (full manual + class ref in `.rst`; rg it). Web fallback only `https://docs.godotengine.org/en/4.7/...` — version goes IN the URL, never `/latest/`. NEVER trust training-memory API names. Known traps: `AudioStreamWAV` is `AudioStreamWav` in C#; collision layer/mask live on `CollisionObject3D`, NOT `CollisionShape3D`; `SkeletonIK3D` is deprecated in 4.7. If docs and compiler disagree, grep the shipped `/usr/lib/godot-mono/GodotSharp/Api/Debug/GodotSharp.dll`. ## 3. Current systems (scripts/) - **Main.cs** — OpenXR startup: finds `OpenXRInterface`, `UseXR`, VSync off, VRS XR mode; wires session signals (begun/visible/focussed/stopping/`PoseRecentered` → re-emitted as C# signals FocusLost/FocusGained/PoseRecentered). On session begin: picks highest refresh rate ≤ `MaximumRefreshRate` (export, 90) and sets `Engine.PhysicsTicksPerSecond` to it. Pauses tree on focus lost. No OpenXR → flat preview fallback (PushWarning, scene still runs). - **Locomotion.cs** — locomotion manager. Spawn anchoring ONCE on first `FocusGained` (HMD xz to `SpawnPoint` + spawn yaw; real head height and pitch kept — `PlacePlayer` pins arrival height). `PoseRecentered` is deliberately IGNORED for placement (WiVRN spams it at ~90 Hz; logged once). Teleport: aim + trigger edge, fade to black 0.12 s in / 0.25 s out, near markers within `MarkerNearDistance` (1.2 m) hide fully (disc/beam/label) and are not targetable. Lasers via `UpdatePointer`: shown only when aiming at something interactive (marker or panel) AND beyond `LaserMinDistance` (0.35 m). Also builds the stylus pointer tip per controller. `ReturnToSpawn()` is public (panel Spawn knob calls it). - **AnchorMoveMode.cs** — grab-world movement, ANCHOR semantics. One-hand grip moves the player OPPOSITE to hand motion (full 3D); both grips = anchor translation + in-place yaw turn around the head. Grip is suppressed while that hand prop-grabs (`PropGrabber.IsGrabbing`) or the panel is near (`WorldPanel.IsBlocking`). **All deltas and turn pivots in WORLD space** (tracking-space math made turns orbit the map center — do not regress). Drives `XROrigin3D` directly. - **PhysicsHands.cs** — physical palm bodies chasing the controller poses: 0.4 kg boxes (flat slab, thumb-side face), gravity off, CCD, **layer 4 (player hands)/mask 1|4** — NOT colliding with the chest (layer 3), excluded both ways. Chase = desired velocity `(target-pos)/dt` force-clamped to `HandMaxForce` 90 N; rotation hard-locked by setting angular velocity to the controller's rotation error (clamped to 120 rad/s). Safety: stuck (> 0.2 m for 1 s) → snap + log; > `SnapDistance` 2 m → silent teleport snap — BOTH SUSPENDED for a hand while `PropGrabber.IsGrabbing(controller)` (a joint legitimately holds the hand back; teleporting a 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. - **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": point constraint + rotator, which the owner rejected as not-how-grab-works: ball orbiting the palm, anvil surface drifting under the palm). Grip edge grabs nearest prop in group "prop" from palm point (`GrabRadius` 0.16 m, fallback hand center `CenterGrabRadius` 0.12 m) — palm-proximity only. On grab: creates a **`ConeTwistJoint3D`** (mapped by the patch to Box3D's `b3WeldJoint`) parented under PropGrabber, NodeA = physical hand body, NodeB = prop, `SwingSpan = 0`, `TwistSpan = 0`, `Position` = palm point, `ExcludeNodesFromCollision = true`. The weld LOCKS THE RELATIVE POSE AT CREATION (the patch pre-corrects frame B with both bodies' rotations — no identity-target snap). Configure Position + NodeA + NodeB + spans + bias BEFORE `AddChild` — mandatory ordering (configuring after raced Box3D and produced dead joints). **NO script forces/torques/damp anywhere** — v5.2's whole servo (PD, gravity joints) — v5.2's whole servo (PD, gravity feed-forward, MaxHeldSpin, AngularDamp raise) is DELETED; the solver owns everything and weight flows through the weld into the hand's 90 N budget honestly. **NO per-tick joint re-positioning** — the weld is not a hand-following pivot (moving the node after creation does nothing). **BOTH hands weld RIGID (bias 0) — v5.3.1** (the owner rejected the soft secondary: it gave ~10-15 cm, "feels like a joint again"; hand₁+prop+hand₂ rigidly welded is one composite body, and incompatible controller demands resolve through the HANDS lagging, not the object stretching — spike-measured anchors 2–3 mm under full opposite tug). If a future jitter appears under wrist-conflict, reintroducing a soft secondary bias is the fallback. NO promotion machinery — with both welds rigid from grab, takeover is automatic (the survivor's weld simply persists). Grip break: prop-local anchor vs live palm point > `GripRipDistance` (0.8 m) instant rip; > `BreakDistance` (0.25 m) sustained > `BreakTime` (0.25 s); both hands > `BreakDistanceBoth` (0.4 m) breaks two-handed (no promotion on simultaneous break); with a rigid weld these rarely fire — they are the stretch/anomaly safety net; when a two-handed hold releases one hand, the composite rings briefly (~3° peak, decays < 1 s — stored weld stress discharging). NEW escape hatch: hand body lagging its controller beyond `PhysicsHands.SnapDistance` (2 m) → "grip dragged off" instant break (heavy props can drag the hand since PhysicsHands' snap is suspended while gripping). - **PropShop.cs** — builds table (StaticBody3D) + spawns prop table from `Specs[]` (ping pong 2.7 g → anvil 50 kg), Label3D captions, CCD for < 0.1 kg, group "prop". `Respawn()` = Reset knob. - **WorldPanel.cs** — prop-UI panel, PARENTED UNDER `XROrigin3D` (fixed in player's room; revert = reparent to Main + fix node paths). Summoned/dismissed via `ax_button` (A/X; menu_button belongs to the runtime). Knobs: Spawn (return to spawn + recall panel in front of head), Ping, Reset (PropShop.Respawn), Close. Panel/knob bodies: **CollisionLayer 2, CollisionMask 0** (props pass through); all CollisionShapes disabled while hidden (`SetShapesEnabled`, deferred). Pointer tip is a 12 cm stylus cone; knobs hover-highlight and PRESS ON CONTACT (tip within 4.5 cm, 400 ms cooldown) — physical buttons; trigger still presses laser-hovered knobs. Click = code-generated `AudioStreamWav` PCM (no asset). Draggable direct or via laser at fixed ray distance, re-orients toward head while gripped. `IsBlocking(controller)` (within 0.45 m) makes Locomotion defer 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; the first playtest's NaN collapse is root-caused + fixed (servo math, see §5). 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 body mass (force-clamped PD, `MaxForce` 2400 N ⇒ 80 m/s², `MaxSpeed` 6 m/s). Orientation = slewed angular-velocity servo toward the yaw target (Kp 20 rad/s per rad, spin cap 10 rad/s, alpha cap 150 rad/s²) — self-rights when shoved, holds yaw. `SnapDistance` 2 m snap safety covers teleports. Camera stays KINEMATIC — the torso chases the head, never the reverse (Boneworks sickness lesson). Yaw spring (HMD + hands blend) MOVED here from BodyRig. Collision: layer 3 (player body), mask 1|3 — props/floor touch it, own hands do NOT (hands moved to layer 4; excluded both ways — no self-collision, VRChat/Nimso precedent). Owns its torso mesh (hidden until the body first arrives at target). Spike: `scenes/body_spike.tscn` must print **"BODY: overall PASS"** (pair-rule proofs: one-way floor contact, hand-through-chest exclusion, prop-contact two-way; track/yaw/impact/snap/ self-right asserts). - **BodyRig.cs** — placeholder rig VISUALS only now (v1.3): head cube (HMD-driven), 2-bone analytic arms (soft IK 0.98, pole as before), shoulders derived from the PhysicsTorso BODY pose (+ ShoulderRise 0.25, ± body X). The torso mesh and the torso yaw spring moved into PhysicsTorso; BodyRig computes the reach budget (hysteresis unchanged) and feeds it to `PhysicsTorso.ReachOffset`. Hidden until calibrated. - **GrabSpike.cs** + `scenes/grab_spike.tscn` — headless grab regression (v5.3): a hand stand-in (0.4 kg body, `HandMaxForce` 90 N chase) grabs a 1 kg prop via ConeTwist weld (server type verified, configure-before-AddChild); drag 2 m + 360° fast-spin sub-phase — asserts RIGID relative pose (grab-rotation error < 2° drag / < 3.5° spin, anchor separation < 1 cm — the winch/orbit regression; 2° is the gravity-on floor for an off-center grab, don't tighten below that); two-handed phase (second weld soft, bias 0.3): primary anchor < 1 cm rigid under full tug, tug give bounded < 0.25 m (recovers), follow-regime anchor < 0.05 m; then primary releases, survivor weld recreated rigid (takeover) and rigidity re-asserted. Reach-timeout guards; must pass 3× in a row (never accept flaky). - **WeldSpike.cs** + `scenes/weld_spike.tscn` — regression for the PATCHED ADDON itself: two bodies welded via ConeTwistJoint3D (bias 0 rigid, then bias 0.5 soft); asserts relative-pose rigidity under torque/force (rot < 2°, sep < 1 cm), bounded excursion + full recovery for the soft weld. Run this FIRST whenever the addon binaries change. - **BodySpike.cs** + `scenes/body_spike.tscn` — headless regression for the physical torso: pair-rule proofs on Box3D (one-way floor contact; hand-config body passes through the chest with the chest undisturbed; prop-config body transfers contact), then track (walk pattern, error < 6 cm), yaw (90°/1 s turn: peak < 15°, settled < 4°), impact (5 kg at 3 m/s: dip < 0.2 m, recovers < 2 cm, never tunnels, rebounds), teleport snap, self-right (< 3° after a pitch impulse). Must print **"BODY: overall PASS"** 3× deterministic. Research (`research/`): seven full sourced reports — `vr-arm-ik.md`, `vr-arm-ik-deep.md`, `vr-grabbing.md`, `vr-two-handed-grab.md` (grab v5.0→5.2 history + the verified Box3D pin joint internals + dated CORRECTION section), `godot-box3d-weld.md` (the addon patch: weld mapping, param table, build recipe, risks), `vr-body-physics-hugs.md` (player-body physics + hug prior art: Boneworks' one-powered-body lesson, active-ragdoll recipes, hug = wrap + mutual force + reactivity; phased design), `vr-body-physics-godot.md` (verified Box3D capabilities: no Generic6DOF, hinge VELOCITY motors work, PD-follow proven by the hands). **READ these before re-researching those topics or changing the grab/IK/body implementations** — they carry the reasoning behind the current designs. Stink-O's box3d-godot (custom components, 8 joint types) remains the documented plan-B if the patched-wrapper path is ever abandoned (owner pointed at it; adopting it means leaving the standard RigidBody3D stack). Scene wiring (`scenes/main.tscn`): Main → WorldEnvironment, Sun, Floor, XROrigin3D (XRCamera3D, LeftHand, RightHand, WorldPanel), SpawnPoint, 3 TeleportMarkers, Locomotion, AnchorMoveMode, PropShop, PropGrabber, PhysicsHands, PhysicsTorso, BodyRig, FadeLayer/Fade. Action map: `openxr_action_map.tres`. Floor StaticBody scans layer 3 (collision_mask 13). ## 4. Architecture decisions that must not regress - **World-space grab/turn math**: all grab deltas and two-hand turn pivots computed in WORLD space. Tracking-space math made two-hand turns orbit the map center. Do not regress. - **Box3D PinJoint3D quirks (grab history v5.0–v5.2)**: Box3D's PinJoint3D is a TRUE ball joint with ANCHORS CAPTURED AT CREATION (not vanilla Godot origin-pinning). The joint node's position is READ EVERY TICK (re-setting `GlobalPosition` each tick = hand-following pivot). Its hidden rotational spring (hertz = bias × 30) targets IDENTITY — aligns the two bodies' world orientations, uncapped, hits both bodies; that was the root of the v5.0–v5.2 grab misbehavior (see the CORRECTION section of `research/vr-two-handed-grab.md`). The positional (point-to-point) tuning (engine default 60 Hz/ζ2.0 clamped to 0.25×tick) is NOT reachable from the wrapper. IMPULSE_CLAMP is a warned no-op. - **Box3D WeldJoint via the local patch (grab v5.3 lives on it)**: the patch maps Godot's ConeTwistJoint3D → `b3WeldJoint`, PRE-CORRECTING frame B with both bodies' rotations so the weld locks the relative pose at creation. Bias semantics are INVERTED vs the pin joint: bias 0 = MAX stiffness (rigid), bias 0.3 = 9 Hz give. Swing/twist spans > 0 and softness/relaxation are warned no-ops (the weld has no limits). The weld is NOT a hand-following pivot — moving the joint node after creation does nothing, and none is needed. Only the Linux binary is patched (`bin-stock/` holds originals; `.vendor/` the source, patch, and rebuild recipe). Pin/hinge/slider paths untouched — grab v5.2.1 remains runnable as fallback (git history). Stink-O's box3d-godot (custom components, more joint types) is the plan-B. - **Collision layers**: 1 = world, 2 = UI. Panel/knob bodies are layer 2 mask 0 (props pass through the menu); **1 = world+props, 2 = UI, 3 = player body (chest), 4 = player hands** — chest↔own hands excluded BOTH ways (no self-collision; props mask 1|3|4, floor mask 1|3|4, hands 4/1|4, chest 3/1|3). Layers/masks live on `CollisionObject3D`, never `CollisionShape3D`. The body_spike proves the pair rules on Box3D. - **Ghosts belong to hands, not props** — the whole arm gets a ghost later. - **Standard-RigidBody3D-only** for scene objects and tracked bodies, so the Jolt fallback stays lossless. - **`pose_recentered` is never used for placement** — WiVRN fires it continuously (~90 Hz); reacting to it was the original view-locked-forward bug. - Panel parented under `XROrigin3D` (deliberate feel); grip thresholds polled as Floats (0.6), bool actions unreliable across runtimes. ## 5. Known issues / open questions - **Body Stage 1 chest collapse — ROOT-CAUSED + FIXED (2026-10-01), awaiting owner re-playtest.** The first in-headset session NaN'd the chest ("head detaching from the body"). Cause was SCRIPT MATH, not the solver: the orientation servo used `Quaternion.GetAngle()` = `2*acos(W)` (Godot C# `Mathf.Acos` is unclamped) → NaN when rounding put W at 1.0000001, which a seated still player reaches because `GetAxis()` (raw xyz when W > 1−1e-6) collapsed the servo gain inside 0.16° and parked the body where a slowly drifting target crosses it. Fixed with `BodyMath.RotationError` (atan2-based) in torso + hands, plus `BodyMath.ResetIfNonFinite` escape hatch on every driven body. Deterministic repro + regression: `scenes/collapse_spike.tscn` (old code 42/49, fixed 49/49). Solver stacking (chest in static table + two-hand-welded 50 kg anvil) was tested and is stable. Full record: `research/body-stage1-failure-brief.md` (RESOLUTION section). **RULE: never use `Quaternion.GetAngle()/GetAxis()` for servo errors — use `BodyMath.RotationError`.** - **Seated calibration** — BodyRig calibrates proportions from the head height at first frame; seated (1.01 m) clamps arm length to the 0.50 m minimum, so normal reaches saturate `ReachOffset` and lean the chest into the table (stable, but wrong proportions). Needs a standing calibration step or a seated mode — owner's call. - **Chest plows table props** when seated at the table (props thrown up to ~3.8 m/s by the 2400 N chest). Honest physics; tune by feel. - **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 thumbstick movement if the body-physics track lands (Nimso-style) — see next-steps item about the locomotion decision; don't over-invest in anchor-loco until that's decided. - **Teleport + lasers are BROKEN** — owner report; both were experiments anyway and are NOT current issues. Locomotion.cs's marker/laser machinery may be resurrected or deleted when the locomotion direction is decided; WorldPanel's laser-hover knobs still reference the laser system, so a deletion needs that decoupling first. - **Locomotion direction undecided** — owner's lean: full physics thumbstick movement if the body track goes the Nimso way (stick pushes the physics body; sway/jog detection class of solutions). Anchor-grab locomotion was a scene-viewer-era design; the fix-or- replace decision rides on the body-physics track outcome. - **Project direction note (owner, 2026-10-01)** — initially a scene viewer; messing with physics made gameplay-first a live possibility (scene viewer second). NOT decided — hinges on whether the body-physics track works painlessly. Affects prioritization only; nothing to change in code/docs until decided. - **Grab v5.3.1 — VALIDATED in-headset (owner: "it just works")** — the grab saga is closed. Solver-owned rigid weld via the locally patched addon (ConeTwistJoint3D → b3WeldJoint), rigid two-handed, takeover automatic. Headless: grab/weld/physics spikes all PASS 3×. Known honest physics, not bugs: ~1.2° elastic sag on off-center grabs under gravity (lever-dependent, mass-independent), brief ~3° ring when one hand of a two-handed hold releases, and a heavy prop genuinely strains/drags the 0.4 kg hand (that IS the weight feel). Angular hold strength on heavy props one-handed = Box3D's solver stiffness floor (0.25×tick clamp ≈ 22.5 Hz at 90 Hz) — the documented next lever is raising `Engine.PhysicsTicksPerSecond` above the refresh rate (global experiment, only with owner go-ahead). - **Hand reaction / weight-through-hand** — restored for real in v5.3: the weld transmits prop weight/forces through the solver into the 0.4 kg hand's 90 N chase budget. Validate the heft in-headset. - Palm offset signs (left +, right −) are taste choices — flip if grabs feel mirrored. - Near-extension arm shake should be fixed by soft IK (`SoftIKFactor` 0.98) — unvalidated in-headset; lower toward 0.95 if shake persists. - One-frame arm jumps on hand-body snap/teleport (stuck snap, SnapDistance, teleport). - IK research applied except anything marked open in `research/vr-arm-ik*.md`. - Hand tracking spike not started (`XRHandTracker` through WiVRN, see `openxr_hand_tracking.rst`). - Head cube front face can peek at extreme down-angles (bump `HeadBackOffset` if it bothers). - Real character model: import pipeline owned elsewhere (consumes Skeleton3D + AnimationPlayer). Migration path for IK: `SkeletonIK3D` deprecated in 4.7 → custom `SkeletonModifier3D` (`_process_modification_with_delta`) with our own analytic 2-bone solve (no built-in supports an elbow pole) + `LookAtModifier3D` for head→neck look distribution (head authoritative, neck cosmetic, stacked above head's). HL:Alyx shipped hands-only deliberately (arms mislead without proprioception) — keep arms cheap and honest. ## 6. Tuning knobs | Constant | File | Value | Controls | |---|---|---|---| | `HandMaxForce` | PhysicsHands.cs | 90 N | hand chase force; also used by GrabSpike stand-in | | `HandMass` | PhysicsHands.cs | 0.4 kg | hand body mass | | `HandMaxSpin` | PhysicsHands.cs | 120 rad/s | hand rotation-lock clamp | | `StuckDistance` / `StuckTime` | PhysicsHands.cs | 0.2 m / 1.0 s | stuck snap (logged) — SUSPENDED while that hand grips | | `SnapDistance` | PhysicsHands.cs | 2 m | silent teleport snap of hand body — SUSPENDED while that hand grips | | `GhostLagDistance` | PhysicsHands.cs | 0.1 m | when the hand ghost shows | | `RigidWeldBias` | PropGrabber.cs | 0 | weld bias for BOTH hands (v5.3.1): 0 = MAX stiffness (rigid) — INVERTED vs pin-joint bias semantics; raise only as a fallback if two-handed wrist-conflict jitters | | `GripRipDistance` | PropGrabber.cs | 0.8 m | instant release, no timer (checked before the break timer) | | `BreakDistance` / `BreakTime` / `BreakDistanceBoth` | PropGrabber.cs | 0.25 m / 0.25 s / 0.4 m | grip break: sustained anchor separation (single hand) / both-hands threshold | | `GrabRadius` / `CenterGrabRadius` | PropGrabber.cs | 0.16 m / 0.12 m | palm-point / hand-center grab range | | `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` | BodyRig.cs | 0.98 | shoulder→hand distance clamp before IK solve (lower toward 0.95 if shake persists) | | `PoleHandWeight` / `PoleSmoothRate` | BodyRig.cs | 0.25 / 10 | elbow pole: hand-forward push / exp smoothing | | `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) | | `OrientKp` / `OrientMaxSpin` / `OrientMaxAlpha` | PhysicsTorso.cs | 20 / 10 rad/s / 150 rad/s² | chest orientation servo (yaw hold + self-right) | | `SnapDistance` | PhysicsTorso.cs | 2 m | chest teleport snap safety | | `armTotal` | BodyRig.cs | clamp(0.46×headHeight, 0.5, 0.72) | total arm length, 50/50 split | | `ReachPushSpeed` / `ReachDecaySpeed` | BodyRig.cs | 0.8 / 0.4 m/s | torso lean toward / back from overreach | | `ReachDecayFactor` | BodyRig.cs | 0.92 | hysteresis band (decay only below 92% of reach) | | `ReachBudgetMax` | BodyRig.cs | 0.25 m | horizontal torso offset clamp | | `ReachVerticalMin/Max` | BodyRig.cs | −0.05 / +0.12 m | vertical torso offset clamp (stops upward oscillation) | | `HeadBackOffset` / `TorsoBackOffset` / `TorsoDrop` | BodyRig.cs | 0.06 / 0.06 / 0.42 m | keeps boxes out of the view | | `LaserMinDistance` | Locomotion.cs | 0.35 m | laser hidden nearer than this (tip touch instead) | | `MarkerNearDistance` | Locomotion.cs | 1.2 m | near markers hide + become untargetable | | Fade times | Locomotion.cs | 0.12 s in / 0.25 s out | teleport fade | | `PressContactDistance` / `PressCooldownMs` | WorldPanel.cs | 0.045 m / 400 ms | knob press on contact | | `DirectGrabRadius` / `TipHoverRadius` / `TipLength` | WorldPanel.cs | 0.45 / 0.07 / 0.12 m | panel grab / tip hover / stylus reach | | `MaximumRefreshRate` | Main.cs | 90 | physics tick = chosen refresh rate | ## 7. Next steps (in order) 1. **Owner re-playtest of the body (chest fix + Stage 2 arms)** — the Stage-1 collapse is fixed (`collapse_spike` 49/49); feel is unvalidated in-headset. Read the log after. 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 movement if the body track lands. Fix-or-replace rides on item 1's outcome; teleport/ laser machinery gets resurrected-or-deleted with it (WorldPanel's laser-hover knobs must decouple first if deleted). 3. **Grab points on special objects** — grab is palm-proximity to the prop origin today; special objects may want authored grab anchors (see `research/vr-grabbing.md` taxonomy). 4. **Physical torso/head capsules** — folded into the body-physics track (item 1): the torso chase body IS the capsule replacement; head/body colliders join it in the same stage. 5. **Hand tracking spike** — `XRHandTracker` through WiVRN; controller tip and fingertip must feed the same cursor concept (interaction must not assume controllers). 6. **Neck/head look system** — `LookAtModifier3D` head→neck distribution (needs a real skeleton). 7. **EMBODYING** — view-follow + avatar remapping (chest calibration transform); experiments only after head/neck look override exists. Theory until proven. 8. **UI milestone** — deliberate full action-map pass + SubViewport/composition-layer decision for dense screens (at most ONE such screen; everything else stays props). ## 8. Working agreements - Read `AGENTS.md` fully before working; it overrides this file where they differ. - Agent-orchestration workflow: the orchestrator delegates to subagents and reviews their output. - **Escalation channel**: the owner has a smart external agent for hard problems/number crunching — the orchestrator says so when stuck or when a problem exceeds its own capability, and the owner forwards. It starts with ZERO project context, so problem statements must be fully self-contained (constants, constraints, desired output; no repo references). Likely first candidates: per-joint PD gain derivation for the hierarchical muscle budgets; Box3D stiffness-clamp vs physics-tick-rate math. - The USER playtests in VR and reports feel — nothing "works" until the owner confirms in-headset. - Engine logs (`~/.local/share/godot/app_userdata/VR Messs/logs/godot.log`) are the diagnosis source after playtests — read them instead of guessing. - Spikes are cheap and throwaway; a failed experiment is progress, not rework. - No GDScript-only addon dependencies (Godot XR Tools is reference-only); C# everywhere.