# 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.