- Disabled constraints for the stickman rig's head bone to allow for smoother animations. - Improved mode transitions in the sandbox stage to preserve stickman poses between EDIT and DIRECT modes. - Added side profile stabilization for knee bends in the ragdoll component to prevent unnatural poses during physics interactions. - Enhanced the snap-to-standing functionality to restore the stickman's original pose accurately. - Introduced new tests for validating pose preservation during mode transitions and ragdoll behavior during walking animations. - Created a new scene for testing stickman bending and ragdoll transitions.
199 lines
11 KiB
Markdown
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.
|