Body step 2: the player is a muscle-driven ragdoll (Project TX)
PlayerRagdoll: 9 bodies, spine/neck ball joints, shoulder drives (girdle + muscle), elbow hinges, torque-capped motor PDs toward the True Body, chest->hand guides, implicit balance motor, mass-scaled ground control. PlayerRig: XR driver with stick move / snap turn and Bonelab-style push-back (eye->camera sphere sweep). Replaces the chase bodies in main.tscn; RagdollSpike regression (14 phases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0b50fbc41
commit
d83273dd2f
16 files changed
+1487
-28
No files matched your search
@@ -0,0 +1,65 @@
|
||||
# Body step 2 — the muscle-driven ragdoll player (2026-10-02)
|
||||
|
||||
Implementation record for Nimso's Project TX player rebuilt on Box3D (design source:
|
||||
`nimso-project-tx.md` §2; joint mapping: `godot-box3d-6dof.md`). Code: `scripts/PlayerRagdoll.cs` (input-agnostic
|
||||
body), `scripts/PlayerRig.cs` (XR driver + stick locomotion), regression `scenes/ragdoll_spike.tscn`
|
||||
("RAGDOLL: overall PASS", 14 phases). It replaces PhysicsHands / PhysicsTorso / PhysicsArms / BodyRig in
|
||||
`main.tscn`. Those scripts stay for their own spikes.
|
||||
|
||||
## What was built
|
||||
|
||||
| Part | Implementation | Why |
|
||||
|---|---|---|
|
||||
| Bodies | 9 boxes, each with its origin at its centre of mass: pelvis (pelvis + abdomen + both legs' Dempster mass, 36 kg at 60 kg total), chest 13 kg, head + neck 4.9 kg, upper arm 1.7, forearm 1.0, hand 0.36 | Legs aren't physical yet (Nimso's leg collision is off by default too), but their mass belongs to the body the ground carries |
|
||||
| Spine, neck | `Generic6DOFJoint3D` BALL (cone/twist, twist axis = up) + muscle motor | Local muscles, Nimso: "each bone drives itself" |
|
||||
| Shoulder | `Generic6DOFJoint3D` DRIVE chest→upper arm. The linear velocity motor (2000 N) holds the humeral head on the chest point + the True Body girdle offset; the angular velocity motor (45 N·m) is the shoulder muscle | One joint carries both the moving girdle (shrug/protraction = real reach) and the muscle. See "light links" below |
|
||||
| Elbow | `HingeJoint3D` 0..150° with a muscle motor (45 N·m) on the flexion angle | A real hinge can't flip or hyper-extend |
|
||||
| Wrist | BALL, cone 80°, twist ±90°, no motor | The hand guide orients the hand |
|
||||
| Hand guide | DRIVE chest→hand. Linear velocity motor 40 N with controller-velocity feed-forward, angular 25 N·m | "Tracking is a guide". The reaction goes into the chest, so pushing a wall pushes you |
|
||||
| Balance | DRIVE from a shapeless world anchor to the pelvis, angular motor only (400 N·m grounded, 30% airborne) | Upright/yaw hold. See "explicit torques" below |
|
||||
| Ground control | Ray down from the pelvis. If ground is within hip height + 0.3 m (normal.y > 0.6): critically damped 4 Hz PD on the pelvis toward its pose height above the actual ground, with gravity compensation for the whole body mass. Support is clamped to [0, 2 × weight], horizontal force to friction × support | Nimso's "leg strength" scales with mass and only pushes. No ground → falls |
|
||||
| Muscle law | Motor target velocity = feed-forward (the target's own relative spin) + Kp·RotationError(target, current) in joint frame A, capped at 15 rad/s; force/torque cap = strength | Proven in `sixdof_spike`. Feed-forward keeps moving poses from lagging by speed/Kp |
|
||||
| Targets | From `TrueBodySolver`: trunk rebuilt top-down (upper chest → lumbar → hips, hips take 50% of the crouch lean). Head = camera orientation tilted so the body's eye lands on the camera. Arms = ArmSolver shoulder/elbow/wrist; hinge axis = Pole × arm direction | A pose the physical chain can actually reach |
|
||||
| Camera / play space | Camera stays 1:1. Each tick the driver applies `OriginCorrection`: a 3 cm sphere swept from the head body's eye to the camera. If geometry is in the way, the view is put back at the free point (walls stop both stick and real walking). If the path is free, the camera may lead the body by up to 0.2 m (leaning over a table whose edge holds your hips), beyond that it follows. Grounded: horizontal only, the floor eases toward the ground under the pelvis. Airborne: 3D (falls carry the view) | Owner: Bonelab push-back, no fade |
|
||||
| Discrete moves | Origin jumps > 0.5 m or > 0.1 rad in one tick (spawn, teleport markers) and snap turns carry the body with `TeleportBy` + `TrueBodySolver.Shift` | No body flung across the gap |
|
||||
| Locomotion | Left stick head-relative 2.2 m/s (8 m/s² accel, no air control); right stick snap 30° (`PlayerRig.SmoothTurn` → 2.5 rad/s). A stick whose hand points at the menu scrolls it instead | Owner scope: Boneworks-style stick only, no jump |
|
||||
|
||||
## Measured findings (RagdollSpike, Box3D fork, 90 Hz, 4 substeps)
|
||||
|
||||
1. **Light links between two strong motors oscillate.** The first build had a 0.6 kg neck link (two neck joints)
|
||||
and a 0.8 kg clavicle body per arm (chest→clavicle drive + clavicle→upper-arm ball). The neck pitch oscillated
|
||||
with growing amplitude (±11 rad/s head spin while standing still). Raising the shoulder to 200 N·m made the arms
|
||||
*worse* (0.94 m hand lag vs 0.47 m). Box3D runs one solver iteration per substep, and a light body squeezed
|
||||
between two motors with large caps doesn't converge. **Fix: no light intermediate bodies.** The neck merged into
|
||||
the head (single joint at the neck base); the clavicle was replaced by one shoulder DRIVE carrying both the girdle
|
||||
translation and the muscle. Result: standing error ~0, swing lag 0.13 m, random-motion eye error 2.3 cm. Rule
|
||||
for the legs and anything later: keep every body that sits between two motors ≳ 1 kg, or merge it.
|
||||
2. **Explicit world torques on the pelvis vs an implicit motor.** The explicit 3 Hz PD torque balance looked fine
|
||||
until the arms got strong. With the light links gone, the implicit balance motor is stable and holds the pelvis
|
||||
rigidly against arm reaction torques.
|
||||
3. **Hand-guide strength is the "cheat budget".** With a 120 N guide the arms looked perfect, but 12 kg at arm's
|
||||
length sagged only 2 cm (Nimso: it must not be holdable). At 40 N guide + 45 N·m shoulder/elbow (≈ human):
|
||||
12 kg held close sags 1.3 cm, at arm's length 25 cm. Fast swings still track (0.13 m peak lag at 1.5 Hz ±0.25 m).
|
||||
4. **Test poses must be anatomically possible.** Controllers pointing forward with the arms out sideways need a 90°
|
||||
wrist bend. The old strong guide hid that; with honest strength the wrist limit wins. RagdollSpike's reach poses
|
||||
now aim the controllers along the arm.
|
||||
5. **Unreachable targets stop at the arm's end.** That is correct, not a bug: a push target 0.91 m from a 0.57 m
|
||||
arm leaves the hands at full reach.
|
||||
6. Hinge sign: positive `HingeJoint3D` motor velocity rotates the child about the joint's +Z (spike phase 0 measures
|
||||
it; `PlayerRagdoll.HingeSign`).
|
||||
|
||||
## Not done yet / known gaps
|
||||
|
||||
- **Legs**: no physical or visual legs; the pelvis floats on ground control. Step 3 = procedural legs (Nimso:
|
||||
"feet don't hold the player up" — visuals + optional collision).
|
||||
- **Shoulder has no cone limit** (Box3D's motor joint has none). A strong external force can bend the arm into the
|
||||
chest; muscles return it. If it shows, add a BALL limit via a second, collision-only constraint, or a clavicle
|
||||
heavy enough (≥ 1 kg) to be stable.
|
||||
- **Climbing**: grabs are props only (`PropGrabber`); welding to static geometry is not wired. The physics would
|
||||
allow it: the hand guide reacts into the chest.
|
||||
- **Strength values are guesses** at human scale (Nimso's are unpublished). One global strength scalar (Nimso's
|
||||
"Crysis mode") is not exposed yet.
|
||||
- **144 Hz**: not switched. Physics runs at the headset rate. Decoupling it needs physics interpolation, and today
|
||||
`AnchorMoveMode`/`Locomotion` move the XR origin in `_Process`, which would jitter under interpolation.
|
||||
- **Anchor-grab / teleport legacy locomotion** still drive the origin. Teleports carry the body. Grab-flying upward
|
||||
now fights gravity (the body falls and the view follows it), so it effectively only pulls you along the ground.
|
||||
Reference in new issue
Block a user