Add ramp scene and implement Phase 6 for dynamic IK and terrain-following locomotion

- Created a new scene file `ramp.scene` with terrain and stickman objects, including event rules for ragdoll and recovery actions.
- Documented Phase 6 implementation in `PHASE_6_GROUND_SNAPPING.md`, detailing the continuous dynamic IK, terrain-following locomotion, and constraints for stickman rigs.
- Defined scene structure, layered IK ownership, probe contracts, and per-frame solve order for terrain interaction.
- Verified functionality for terrain snapping, stance/swing blending, pelvis/root compensation, and navigation synchronization.
This commit is contained in:
2026-09-16 09:33:07 -04:00
parent a7c58705e1
commit 08247eadef
7 changed files with 1088 additions and 44 deletions
+190
View File
@@ -0,0 +1,190 @@
# Phase 6 — Continuous Dynamic IK & Terrain-Following Locomotion
Status: implemented and verified in the Godot editor. This document records the shipped runtime behavior and supersedes the rejected authoring-time normalization design.
## 1. Goal and scope
During PLAY, every animated stickman continuously conforms its feet and pelvis to TerrainBlock ground. An unsupported rig enters a kinematic, gravity-driven free-fall; supported rigs independently sample both lower-leg endpoints, hold stance contacts, preserve lifted swing arcs, and compensate the root for ramps and steps. The lower-leg endpoints are the foot contacts: Phase 6 adds no foot/ankle bones or new visual geometry.
This is runtime locomotion. The phase does not change palette placement or stored walk-action coordinates; both remain existing feet-space values. Ragdoll physics and recovery stay independent from animated terrain contacts.
## 2. Constraints
- StickmanRig owns live IK_Targets and runs physics processing; SandboxStage owns the EDIT-to-PLAY handoff.
- TerrainBlock is a solid StaticBody2D. Terrain, props, and rig collision proxies share layer 1, so probes accept only TerrainBlock hits with upward-facing normals.
- The walk_right and legacy walk_left clips write the layered `AnimationTargets/*_Base` markers; terrain code is the sole runtime writer of the live leg IK targets.
- NavigationAgentAnchor remains at (0, 385) and FOOT_OFFSET remains the sole feet-space to root-space conversion.
- Navigation is baked from exposed upward-facing terrain edges as ribbons, with bounded bridges for valid steps; solid terrain interiors are not route surfaces.
## 3. Layered IK ownership
### 3.1 Scene structure
`master_rig.tscn` contains base-pose markers which are not read by the Skeleton modification stack:
Master (StickmanRig)
├── IK_Targets
│ ├── Left_Leg # final target; TwoBoneIK reads this
│ └── Right_Leg # final target; TwoBoneIK reads this
└── AnimationTargets
├── Left_Leg_Base # authored walk animation writes here
└── Right_Leg_Base # authored walk animation writes here
The two leg-position tracks in the walk clips write the `AnimationTargets` markers. Existing hand, torso, and head tracks keep their current targets. The rig is then the sole runtime writer of the live `IK_Targets/Left_Leg` and `IK_Targets/Right_Leg` positions.
### 3.2 Rig controller API and state
StickmanRig owns a private controller with these constants/state:
TERRAIN_PROBE_UP = 96
TERRAIN_PROBE_DOWN = 700
TERRAIN_NORMAL_MIN_Y = -0.15
FOOT_CONTACT_EPSILON = 2
FOOT_BLEND_RESPONSE = 14
TERRAIN_SUPPORT_RANGE = 160
TERRAIN_MAX_FOOT_SURFACE_DROP = 72
TERRAIN_FOOT_GUARD_HEIGHT = 24
PELVIS_MAX_OFFSET = 240
PELVIS_RESPONSE = 10
WALK_CYCLE_DURATION = 0.8
_leg_base_markers # Left_Leg/Right_Leg -> Marker2D
_foot_contacts # leg -> {valid, world_pos, normal, locked}
_terrain_pelvis_offset
New methods:
begin_terrain_fall() -> void
snap_to_terrain() -> void # compatibility alias; arms the PLAY controller
set_terrain_following_enabled(enabled: bool) -> void
_update_terrain_following(delta) -> void
_probe_terrain_below(world_point) -> Dictionary
`begin_terrain_fall()` is called on PLAY entry and is safe/idempotent: it enables the controller, clears contacts, resets fall velocity, and starts unsupported rigs in free-fall. `snap_to_terrain()` remains a compatibility alias. `set_terrain_following_enabled(false)` clears contacts, resets terrain state, and copies base markers back to live targets. Missing nodes/world state warn with a `StickmanRig:` prefix and skip safely.
### 3.3 Probe contract
The center support sample uses a three-ray horizontal fan at offsets `[-24, 0, +24]`. Each lower-leg endpoint uses its own three-ray fan at `[-4, 0, +4]`; each ray starts 96 px above its world X and ends 700 px below. A result is accepted only when:
1. its collider is a TerrainBlock;
2. its normal has y <= TERRAIN_NORMAL_MIN_Y; and
3. it is in the stated probe span.
The fan selects the highest valid surface (smallest world Y). Queries use layer 1, reject non-`TerrainBlock` colliders, reject normals below the upward-facing threshold, and exclude the rig's own collision-proxy RIDs. Invalid rays never invent a floor. World/global transforms preserve LEFT root mirroring.
## 4. Per-frame solve order
In `StickmanRig._physics_process()`, terrain following runs only while terrain following is enabled and `state == ANIMATED`, after walking moves the root and before collision proxies synchronize:
AnimationPlayer updates AnimationTargets base pose
↓
walking moves root along nav/direct baseline
↓
terrain controller:
sample contacts → gait weights → foot locks → pelvis offset → final targets
↓
Skeleton TwoBoneIK solves final targets
↓
collision proxies synchronize
The shipped ordering reads the base markers, applies walking/root motion, solves terrain contacts, then synchronizes proxies. `RAGDOLL` and `RECOVERING` do not run the animated terrain controller or write the live leg targets.
RECOVERING and RAGDOLL are excluded because they own or hide the same targets; the terrain controller does not write them in those states.
## 5. Stance/swing blending
The current walk clips are 0.8 seconds and alternate legs. The controller computes complementary continuous cosine weights over that cycle:
| Cycle interval | Right leg | Left leg |
| --- | --- | --- |
| 0.00–0.40 | stance weight 1 → 0 | swing weight 0 → 1 |
| 0.40–0.80 | swing weight 0 → 1 | stance weight 1 → 0 |
The complementary cosine weights provide a continuous transition; idle uses stance weight 1.0 for both legs.
- At stance entry, capture the valid terrain point as a world-space foot lock. While planted, transform the stable world point back to rig-local space; this prevents horizontal foot skating as the root moves.
- In swing, retain the animation base target and apply only the terrain influence needed to prevent penetration. A 24 px predictive foot guard ramps that correction in before the endpoint reaches the surface.
- Between phases, blend base and lock/contact targets with response limiting to avoid knee pops at step edges.
- At idle both legs are stance. They can independently lock to a ramp or two different stair heights; the root does not rotate.
## 6. Pelvis/root compensation
Calculate a desired vertical correction from the centerline terrain fan, clamp it to `PELVIS_MAX_OFFSET` (240 px), and ease it with `PELVIS_RESPONSE`.
runtime root Y += terrain pelvis correction
Runtime root elevation accumulates directly as the controller advances across consecutive ramps and steps; the last-frame correction variable is cleared each frame and is not subtracted from the root. If the centerline has no supported TerrainBlock, the root free-falls with `TERRAIN_GRAVITY` up to `TERRAIN_MAX_FALL_SPEED`, and walk animation pauses until landing. Landing applies the full support correction to prevent tunneling. Feet too far below the center support surface are ignored so an overlapping lower floor cannot pull a leg through a ramp.
## 7. Play entry and walkable-surface navigation
On PLAY entry, after props unfreeze, `SandboxStage` calls `begin_terrain_fall()` for every rig before `start_queue()`. This is the only mode that enables terrain free-fall/following. EDIT and DIRECT call `set_terrain_following_enabled(false)` and restore the captured authored transform/standing pose; terrain following never mutates authored placement.
### 7.1 Walkable-surface ribbon
SandboxStage replaces the full-solid NavigationPolygon bake with a derived walkable-surface ribbon:
1. Transform every TerrainBlock polygon into stage-world coordinates.
2. Extract only upward-facing boundary edges from the clockwise TerrainBlock polygons; discard vertical edges and undersides.
3. Expand each accepted edge into a narrow, configurable navigation strip just above the collision surface.
4. Bridge neighboring surface endpoints only when the horizontal gap is at most 64 px and the height difference is at most 96 px, connecting authored stair treads without making cliffs walkable.
5. Bake the resulting strips as the NavigationPolygon. Large cliffs, terrain interiors, walls, undersides, and unsupported gaps remain disconnected.
The ribbon follows ramps continuously and connects valid stair treads while deliberately refusing impossible steps. Terrain collision remains authoritative for the final ankle probe; the ribbon supplies only route topology.
### 7.2 Walking alignment
Walking retains `NavigationAgentAnchor` at `(0, 385)` and synchronizes `NavigationAgent2D.max_speed` with the selected walk speed. Navigation supplies horizontal progress; terrain following owns vertical root motion. Arrival compares horizontal/root baseline distance and does not let terrain Y correction create waypoint jitter. On-mesh paths follow the ribbon; unreachable/off-mesh targets use latched direct steering.
### 7.3 Trigger and reactive-action behavior
`SandboxStage._update_area_entry()` tests the visible stickman's world AABB and unions it with the previous physics-frame AABB. This swept bound catches `entered_area` events during fast PLAY-only free-fall instead of relying on a single sampled pose. Rules still dispatch while the rig is `ANIMATED` and airborne.
Reactive takeover actions preserve their authored order within one rule batch. The first takeover cancels existing primary/overlay work; later takeover actions remain queued, so a `[ragdoll, recover]` rule executes ragdoll first, waits for rest, then recovers. The runtime terrain trace is gated by `DEBUG_TERRAIN := false` and is off by default.
## 8. Files and ownership
| File | Implemented responsibility |
| --- | --- |
| scripts/stickman_rig.gd | Base-marker resolution, PLAY-only kinematic free-fall, center/per-foot terrain fans, contact locks, gait weights, predictive foot guard, accumulated root elevation, pelvis controller, and terrain-state cleanup. |
| master_rig.tscn | `AnimationTargets/Left_Leg_Base` and `Right_Leg_Base` markers plus redirected leg animation tracks. |
| scripts/sandbox_stage.gd | PLAY terrain-controller handoff; EDIT/DIRECT authored-state restoration; exposed-surface ribbon baking; swept trigger-area bounds; and reactive action dispatch. |
| scripts/terrain_utils.gd | Add pure world-polygon edge classification/strip helpers if extracting that geometry keeps SandboxStage focused on lifecycle and orchestration. |
| scripts/terrain_block.gd, scripts/stage_spawner.gd | No behavioral change. |
## 9. Milestones and acceptance
### 1. Play Snap
- [x] PLAY-only hovering rigs free-fall onto flat ground, ramps, and stair treads before/while queues run.
- [x] EDIT/DIRECT disable terrain following and restore the authored transform.
### 2. Idle Slope IK
- [x] Independent lower-leg endpoint contacts visibly flex knees across ramps/stair edges.
- [x] Probes accept only TerrainBlock upward surfaces and exclude rig proxies; RAGDOLL/RECOVERING are excluded.
### 3. Walk Cycle Blending
- [x] Stance feet remain planted while opposite feet follow lifted swing arcs with continuous weights and a predictive guard.
- [x] Uphill/downhill and stairs avoid dragging, penetration, and one-frame knee/pelvis pops in editor verification.
- [x] Cancel, ragdoll, and recovery safely clear controller state.
### 4. Navigation Sync
- [x] The navigation anchor stays (0, 385); navigation speed follows the selected walk speed and terrain code owns vertical motion.
- [x] The baked navigation mesh covers exposed ribbons and valid connected steps, not solid terrain interiors or undersides.
- [x] Flat, ramps, stairs, and off-mesh arrival remain deterministic without terrain-Y waypoint jitter.
- [x] Collision proxies synchronize after final terrain IK.
- [x] Swept visible bounds detect trigger-area entry during fast free-fall; reactive `[ragdoll, recover]` batches execute in order.
## 10. Verification record
Editor verification covered hovering PLAY entry, flat ground, ramps, consecutive ramps, steps, walking climbs, falling/landing, trigger-area entry during free-fall, ragdoll/recover reactive rules, and EDIT/DIRECT restoration. The shipped design confirms:
- the two-layer animation/final-IK target structure;
- world-space stance locks with phase-weighted swing blending;
- additive, clamped runtime pelvis correction without authored/nav-offset changes;
- terrain-only contacts with center/per-foot fans and PLAY-only free-fall; and
- a derived walkable-surface ribbon that replaces full-solid navigation baking.
The temporary terrain diagnostics remain available behind `DEBUG_TERRAIN`, but the gate is `false` in the shipped build.