Refactor ragdoll recovery system: implement joint bend limits and recovery path overrides
- Updated `DEBUG_RECOVERY` to false for production. - Introduced `RAGDOLL_JOINT_BAND_LIMITS` for customizable joint bend limits based on facing profile. - Added `RECOVERY_LEG_SIDE_FORCE` for manual control of leg IK bend direction during recovery. - Implemented `RECOVERY_JOINT_PATH` to allow optional tween path overrides for elbow/knee recovery. - Removed debug print statements related to recovery visuals to clean up output. - Adjusted `_apply_ragdoll_joint_limits` to utilize new bend limits configuration. - Enhanced documentation in `ragdoll-cleanup.md` to reflect changes in the recovery pipeline and tuning tables.
This commit is contained in:
@@ -409,3 +409,88 @@ Verified against the user's RIGHT data: RightLeg knee −159.7° (world −97.5
|
||||
minimal-world-sweep choice).
|
||||
- [ ] `[stand-up]` lines show every segment sweep ≤ 180°; end pose exactly pre-ragdoll.
|
||||
- [ ] No console errors. Flip `DEBUG_RECOVERY` to `false` once verified.
|
||||
|
||||
---
|
||||
|
||||
# Verified final state
|
||||
|
||||
Shipped and verified working. The round-by-round history above is retained for context; the
|
||||
design below is what is actually in `scripts/stickman_rig.gd` and documented in `AGENTS.md` /
|
||||
`README.md`.
|
||||
|
||||
## Ragdoll fall
|
||||
|
||||
- **Bodies / joints:** 10 `RigidBody2D` (torso capsule r12 mass 8.0, head circle r100 mass 2.0,
|
||||
8 limb capsules r8 masses 1.0–2.0) + 9 `PinJoint2D` (`softness = RAGDOLL_TARGET_SOFTNESS`),
|
||||
built in code and reparented into `"RagdollBodyContainer"` under the rig's parent; layer/mask 1.
|
||||
- **Visual cloning:** `_clone_part_visuals()` reads each body entry's `visual_path` `Body/*` node
|
||||
and clones every `Line2D`/`Polygon2D` child with points baked into the ragdoll body's local
|
||||
frame — no gray bones unless the part is missing/empty (fallback only).
|
||||
- **Ground probe:** `_probe_ground_y()` fans **7** rays (`GROUND_PROBE_X_OFFSETS`) around the
|
||||
landing X, from `top_y − GROUND_PROBE_START_OFFSET` to `bottom_y + GROUND_PROBE_END_OFFSET`,
|
||||
accepting only hits inside `GROUND_PROBE_TOLERANCE` of the ragdoll extent and excluding the
|
||||
ragdoll body RIDs (`query.exclude`). Called in `_capture_ragdoll_pose()` (landing ground) and
|
||||
again per-ankle in `_snap_skeleton_to_pose()` (ankle clamp).
|
||||
- **Joint angular limits:** `_apply_ragdoll_joint_limits()` reads the editable
|
||||
`RAGDOLL_JOINT_BAND_LIMITS[profile][joint] = { "lower": deg, "upper": deg }` table (the joint's
|
||||
allowed relative angle; 0 = straight; a small side ~5 resists hyperextension and a large side
|
||||
~150 is the fold; narrow/locked bands such as `{60, 100}` force a permanently-bent limb).
|
||||
FORWARD arms `{-150°, +5°}`, legs `{-5°, +150°}`; LEFT/RIGHT override both sides. Joints
|
||||
missing from the table fall back to the `RAGDOLL_JOINTS` `limit` string
|
||||
(`elbow_knee`/`elbow_knee_ccw`). Shoulder/hip ±160°, neck free.
|
||||
|
||||
## Recovery pipeline (final)
|
||||
|
||||
1. **Pre-ragdoll capture** — `_enter_ragdoll()` → `_capture_pre_ragdoll_state()` snapshots into
|
||||
`_pre_ragdoll_state`: the 6 IK marker values, the 8 limb bone rotations, the head bone
|
||||
rotation, the facing profile, the 4 live bend flags, the head LookAt enabled state, per-leg
|
||||
knee side + flag (`leg_sides`), and the chain-space entry offsets `torso_chain_offset` /
|
||||
`head_chain_offset`. Captured at entry so the snap needs **no authored geometry constants**,
|
||||
and the LEFT mirror's ambiguous `global_rotation` decomposition (±π) cancels out. This is the
|
||||
recovery **END target** (possibly a mid-walk pose; the animation is not resumed).
|
||||
2. **Ragdoll pose capture** — `_capture_ragdoll_pose()` records each body's rig-local
|
||||
`{pos, rot, half}` and probes the terrain; the probe context is stored in
|
||||
`_probe_top_y` / `_probe_bottom_y` / `_probe_exclude`.
|
||||
3. **Re-anchor** — `_reanchor_root_to_landing()` moves the root so the standing feet sit on the
|
||||
ground at the landing X (`new_root = (_captured_landing_center.x, _captured_ground_y) +
|
||||
FOOT_OFFSET`) and re-bases the captured positions with a **mirror-aware local shift**
|
||||
`local_shift = to_local(shift) − to_local(Vector2.ZERO)`.
|
||||
4. **Snap** — `_snap_skeleton_to_pose()` writes the captured pose: torso marker
|
||||
`position = to_local(center_world − spine_dir·half)`, `rotation = chain(spine) −
|
||||
torso_chain_offset`; limb bones `rot_u = chain(u) − θ_t − bone_angle_u`,
|
||||
`rot_l = chain(l) − chain(u) + bone_angle_u − bone_angle_l`; head `chain(head) −
|
||||
head_chain_offset − θ_t`. All snapped rotations normalized into `(−π, π]` because Godot's
|
||||
`Skeleton2D` re-wraps bone rotations internally (otherwise the tween's captured START is
|
||||
corrupted). Ankles clamped onto the terrain. Re-shows `Body/*`, `_rearm_ik_stack()`, then
|
||||
disables the 4 arm mods + head LookAt and enables the 2 leg IK mods with
|
||||
`flip_bend_direction` matched to the captured knee side (`leg_sides`) or forced by
|
||||
`RECOVERY_LEG_SIDE_FORCE`.
|
||||
5. **Stand-up** — `_play_stand_up()` runs a **2.0 s** sine-in-out tween of the 6 markers → their
|
||||
**pre-ragdoll** values and the arm + head bones → the `_recovery_bones` targets computed by
|
||||
the snap. Upper arm bones use the shortest path; lower arm bones use
|
||||
`_natural_joint_target(current, target, offset)` (`offset = bone_angle_lower −
|
||||
bone_angle_upper`; through-straight rule evaluated in the joint's rendered space; a
|
||||
`"shortest"` entry in `RECOVERY_JOINT_PATH` bypasses it). The **legs are IK-driven** toward
|
||||
their tweening, ground-clamped ankle markers; the arm mods + head LookAt stay disabled.
|
||||
6. **Finish** — `_on_stand_up_finished()` restores the pre-ragdoll facing profile + per-joint
|
||||
bend flags + head LookAt state (`_restore_head_lookat_state()`), re-enables the arm mods,
|
||||
re-shows `Body/*`, sets `state = ANIMATED`, and emits. The resumed IK solve reproduces the
|
||||
pre-ragdoll pose exactly (including mid-walk poses).
|
||||
|
||||
`_pin_mirrored_head_rotation()` pins the head only while `ANIMATED` (during `RECOVERING` the
|
||||
stand-up tween drives the head bone itself). `set_ragdoll(true)` during `RECOVERING` kills the
|
||||
tween and rebuilds the ragdoll; `set_ragdoll(false)` during `RAGDOLL` routes through recovery;
|
||||
calls are idempotent. `is_in_ragdoll()` stays `state == RAGDOLL`.
|
||||
|
||||
## Tuning tables (EDIT ME)
|
||||
|
||||
Every per-joint bend decision of the cycle is editable without touching pipeline code:
|
||||
|
||||
| Const | Shape | Purpose |
|
||||
|---|---|---|
|
||||
| `RAGDOLL_JOINT_BAND_LIMITS` | `[profile][joint] = { "lower": deg, "upper": deg }` | Fall knee/elbow `PinJoint2D` band. Missing entries fall back to the `RAGDOLL_JOINTS` `limit` string. |
|
||||
| `RECOVERY_LEG_SIDE_FORCE` | `[profile][leg] = bool` | Optional leg IK `flip_bend_direction` override (`true` = inverted). Missing = match the captured fall pose's knee side. |
|
||||
| `RECOVERY_JOINT_PATH` | `[profile][joint] = "shortest"` | Optional elbow/knee tween-path override. Missing = pass through straight when the shortest path would cross ±180°. |
|
||||
|
||||
`const DEBUG_RECOVERY := false` (shipped OFF); `_recovery_dbg()` remains as the gated trace and
|
||||
the temporary per-step debug prints have been stripped.
|
||||
|
||||
Reference in New Issue
Block a user