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

11 KiB

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:

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:

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 Vector2s):

_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:

             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.