Files
stickman/docs/archive/plans/PHASE_8_RIG_MODULARIZATION.md
T

199 lines
11 KiB
Markdown

# Architecture Plan: Rig Relocation & Component Modularization
### **Objective**
Relocate `master_rig.tscn` to `res://scenes/stickman_rig.tscn` and decouple `stickman_rig.gd` into dedicated, single-responsibility sub-components to improve maintainability, testability, and concern separation.
---
### **Phase 1: File Operations & Scene Relocation**
1. Move `res://master_rig.tscn` to `res://scenes/stickman_rig.tscn` within Godot to maintain internal UID mappings and resource links.
2. Verify sub-resource integrity after relocation.
---
### **Phase 2: Script Modularization (`stickman_rig.gd`)**
Extract key responsibilities into sub-nodes or dedicated helper scripts attached to `stickman_rig.tscn`:
- **Ragdoll & Recovery Component (`StickmanRagdoll`)**
- Move ragdoll body/joint tables (`RAGDOLL_BODIES`, `RAGDOLL_JOINTS`) and joint limit bands.
- Move procedural physics node instantiation (`RagdollBodyContainer`), pin joint setups, and impulse application (`apply_ragdoll_velocity_boost`).
- Extract rest detection polling (`REST_LINEAR_THRESHOLD`, `REST_ANGULAR_THRESHOLD`), rest timers, and stand-up recovery tweens (`request_recovery`, `snap_to_standing`).
- **Locomotion & Terrain Subsystem (`StickmanLocomotion`)**
- Extract surface raycast probing (`_probe_ground_y`, `_probe_terrain_fan`) and root elevation adjustments (`_reanchor_root_to_landing`).
- Move airborne physics, horizontal drag calculations (`AIR_DRAG_X`), downward gravity acceleration, and procedural wind-drag pose flailing.
- Extract landing impact velocity capture and knee-compression crouch/spring recovery tweens.
- **Director & Action Runner (`StickmanActionRunner`)**
- Extract the action queue data structure (`action_queue`) and execution state machines (`RunnerState`, `ActionPhase`).
- Move pathfinding coordination (`NavigationAgent2D`), kinematic `walk_to` execution, and `FOOT_OFFSET` alignment.
- Handle speech bubble triggers (`speak`), wait timers, and rule-injected reactive action clearing (`clear_reactive_actions`).
- **Core Rig Coordinator (`StickmanRig`)**
- Retain core rig state management (`ANIMATED`, `RAGDOLL`, `RECOVERING`).
- Focus strictly on facing profiles (`FacingProfile`), TwoBoneIK bend direction toggles (`BendDirection`), and draw-order sorting (`Z_ORDER_BY_PROFILE`).
- Serve as the root coordinator relaying signals and state transitions to sub-components.
---
### **Phase 3: Reference & Script Updates**
Update hardcoded string references from `"res://master_rig.tscn"` to `"res://scenes/stickman_rig.tscn"`:
- **`scripts/stickman_factory.gd`**: Update `spawn_from_data()` instantiation target.
- **`scripts/test_harness.gd`**, **`scripts/stk_rig_adapter.gd`**, **`scripts/create_animations.gd`**: Update script comments, tool headers, and path constants.
- **`tests/` Suite**: Search and replace hardcoded scene path string literals across all unit tests.
---
### **Phase 4: Documentation Updates**
Update path references across project markdown files:
- `README.md`, `AGENTS.md`, `RIGGING.md`, and relevant files in `specs/` and `plans/`.
---
### **Phase 5: Verification & Acceptance Criteria**
1. **Separation & Testability**: Confirm that locomotion raycasting and action runner execution can be tested independently without instantiating ragdoll physics.
2. **Factory Instantiation**: Verify `StickmanFactory.spawn()` loads `res://scenes/stickman_rig.tscn` cleanly without path errors.
3. **Test Harness Verification**: Run `res://scenes/test_harness.tscn` via **F6** to ensure walk, speak, wait, and ragdoll actions execute cleanly.
4. **Automated Suite**: Run the full project unit test suite in `tests/` to confirm all assertions pass.
---
### **Completion Record (verified implementation)**
Phase 8 is implemented. The relocated scene is `res://scenes/stickman_rig.tscn`; its root `Master` node is a `StickmanRig` facade over three authored component children. The facade remains the compatibility boundary for callers while each component owns one subsystem:
```text
Master (StickmanRig)
├── RigComponents
│ ├── Locomotion (StickmanLocomotion)
│ │ ├── terrain probing / ground following
│ │ ├── walking and airborne motion
│ │ └── collision proxies and landing reactions
│ ├── Ragdoll (StickmanRagdoll)
│ │ ├── procedural RigidBody2D + PinJoint2D graph
│ │ ├── rest detection and momentum handoff
│ │ └── capture, re-anchor, and stand-up recovery
│ └── ActionRunner (StickmanActionRunner)
│ ├── queued walk / speak / wait actions
│ └── ragdoll and recovery requests
├── Body/* mounted visuals, driven by RemoteTransform2D nodes
├── Skeleton2D/Torso/* Bone2D chain and IK modifications
├── IK_Targets/* six animation/recovery markers
└── AnimationPlayer canonical walk clips and authored animation data
```
`StickmanRig` owns cross-cutting state (`ANIMATED`, `RAGDOLL`, `RECOVERING`), facing profiles, bend flags, body z-order, and signal/API forwarding. `StickmanRagdoll` owns physics construction and recovery; `StickmanLocomotion` owns terrain/motion; `StickmanActionRunner` owns director queue execution. Component references are resolved and configured in `StickmanRig._ready()` and are null-guarded with warnings.
#### Ragdoll handoff and recovery
The runtime handoff preserves the current solved IK pose before physics takes ownership:
```text
ANIMATED
│ set_ragdoll(true)
▼
capture pre-ragdoll markers, bone rotations, facing, bend flags, knee sides
│ build bodies from current bone endpoints; copy mounted Body/* visuals
▼
RAGDOLL ── rest + timeout ──▶ capture body pose + probe terrain
│ destroy physics graph
│ re-anchor root at captured landing feet
▼
RECOVERING ── tween markers/bones toward captured entry pose ──▶ ANIMATED
```
The important dictionaries have these shapes (vector values are runtime `Vector2`s):
```gdscript
_spawn_pose = {
"Torso": {"pos": captured_torso_pos, "rot": captured_torso_rotation},
"Head": {"pos": captured_head_tip, "rot": 0.0},
"Left_Hand": {"pos": captured_left_wrist, "rot": 0.0},
"Right_Hand": {"pos": captured_right_wrist, "rot": 0.0},
"Left_Leg": {"pos": captured_left_ankle, "rot": 0.0},
"Right_Leg": {"pos": captured_right_ankle, "rot": 0.0},
}
_captured_pose["left_lower_leg"] = {
"pos": rig_local_body_position,
"rot": body_global_rotation - rig_global_rotation,
"half": capsule_half_length,
}
```
`_spawn_pose` is initialized from the adapted skeleton by `StickmanFactory.spawn_from_data()` and is the placement ghost's authored pose; the values therefore depend on the loaded `.stk`, rather than on `STAND_POSE`. `_pre_ragdoll_state` is the recovery endpoint: it stores the six markers, limb/head rotations, facing profile, bend flags, head LookAt state, knee-side information, and chain-space offsets. `_captured_pose` is the fallen physics pose used to reconstruct the skeleton and landing position.
#### Stage mode flow and pose preservation
EDIT and DIRECT are two views of the same frozen, non-simulating world. The mode transition receives `previous_mode` so it can avoid an unnecessary reset:
```text
EDIT ◄──────────────► DIRECT
│ │
└──────────┬───────────┘
▼
PLAY
│
set_mode(EDIT or DIRECT)
│
▼
cleanup + restore spawn pose
```
| Transition | Stage behavior | Rig pose behavior |
|---|---|---|
| EDIT → DIRECT | Hide edit controls; keep world frozen | Preserve exact live pose and IK markers |
| DIRECT → EDIT | Restore edit controls; keep world frozen | Preserve exact live pose and IK markers |
| EDIT/DIRECT → PLAY | Enable simulation, terrain following, and action queues | Begin from the current authored/editor pose |
| PLAY → EDIT/DIRECT | Stop queues, disable terrain following, freeze physics, restore object transforms | `snap_to_standing()` restores the factory-captured `_spawn_pose` |
The root cause of the verified regression was that `snap_to_standing()` restored the legacy hardcoded `STAND_POSE`. That generic pose lowered the arms and rotated the head whenever PLAY cleanup ran. The central fix is to restore `_spawn_pose` when it exists, with `STAND_POSE` retained only as a compatibility fallback for rigs that have no captured spawn pose. Ragdoll recovery is separate: its stand-up tween still targets `_pre_ragdoll_state`, so recovery returns to the pose captured at ragdoll entry rather than the factory pose.
#### Facing, walk-phase bend stabilization, and head tracking
`FacingProfile.LEFT` is a whole-rig X mirror (`Master.scale.x = -1`); RIGHT and FORWARD use identity scale. Runtime walking always plays the canonical `walk_right` clip and mirrors it for LEFT. The profile still applies provisional per-joint bend flags and z-order tables. For side profiles, a walk keyframe can place a leg exactly straight, leaving PinJoint2D with no geometric knee side. The ragdoll component therefore:
- permits the straight hinge boundary (`0°`) and the intended fold range (`±150°`) for LEFT/RIGHT profiles;
- adds only a small 3° profile-relative bias at the near-straight ragdoll handoff; and
- checks each rendered RAGDOLL frame, correcting only an actual wrong-side knee crossing while preserving normal physical motion.
Head tracking is owned by the `SkeletonModification2DLookAt` targeting `IK_Targets/Head`. RIGHT/FORWARD enable it. LEFT disables LookAt and pins the canonical head rotation because the whole-rig mirror must remain mirror-invariant; this is intentional rather than a missing target update. The scene's authored head constraint is disabled so the active LookAt can follow moved markers without the old constraint interpretation pinning it.
#### Regression coverage
The completed implementation was checked with focused headless tests and the existing suites:
| Test | Result | Coverage |
|---|---:|---|
| `tests/test_phase8_rig_modularization.gd` | 23/23 | relocated scene, component ownership, head tracking, facing flags, ragdoll facade |
| `tests/test_phase8_edit_direct_pose.gd` | 45/45 | EDIT↔DIRECT round trip and EDIT→PLAY→DIRECT pose/marker restoration |
| `tests/test_phase8_ragdoll_walk_handoff.gd` | 80/80 | LEFT/RIGHT profiles across multiple walk animation phases and physics frames |
| `tests/test_phase45_collision_bodies.gd` | 223/223 | collision proxies, reactions, and PLAY cleanup to authored pose |
| `tests/test_phase3c_walk_recovery.gd` | 185/185 | walk, ragdoll, recovery, and action integration |
| `tests/test_phase6a_airborne_landing.gd` | 24/24 | airborne pose, landing, and ragdoll cleanup |
| `tests/test_phase4b_stage.gd` | 53/53 | stage mode UI/state transitions and pending-action cleanup |
The plan's original acceptance criteria remain the historical intent; this record captures the as-built ownership and the follow-up fixes verified after user testing.