Add unique identifier for test_phase3c_walk_recovery.gd
This commit is contained in:
@@ -123,3 +123,34 @@ When applying .stk v1.4 data to master*rig.tscn, shapes are severely distorted:G
|
||||
- **Symptom:** in Play, the stickman advances one `move_toward` step then stops; the walk never reaches its target.
|
||||
- **Root cause:** `_update_walking` checked `is_navigation_finished()` before the map had synchronized. An unsynced `NavigationAgent2D` (map iteration id `0`) reports an empty, already-finished path, so the walk was ended after a single step. Additionally, `get_current_navigation_path()` alone never triggers a path computation — only `get_next_path_position()` forces the agent's internal `_update_navigation()` to re-query the map for a fresh target, so the empty-path guard would misjudge an off-mesh target as "unreachable" immediately.
|
||||
- **Fix:** (1) defer all nav reads until `map_get_iteration_id(...) != 0`; (2) call `get_next_path_position()` **before** the empty-path / finished checks so the path is actually computed; (3) make walking **hybrid** — an on-mesh target follows the nav path (`_walk_mode = "nav"`), while an off-mesh/unreachable target (path empty **or** `is_target_reachable()` false) switches to **direct straight-line steering** toward the clicked waypoint (`_walk_mode = "direct"`, root target = waypoint + `FOOT_OFFSET`), so the stickman always reaches the waypoint the user clicked; (4) **no** `push_warning` for an off-mesh waypoint (a supported case — logged only via `_walk_dbg`); the original "warn + finish in place" policy was itself reported as "stickman stands still with a waypoint" and was superseded by this DIRECT branch; (5) `_finish_walk(reason: String)` internal param for the debug trace. The `_walk_path_grace` counter is **removed** — the map-sync guard + forced path query replace it.
|
||||
|
||||
## Sandbox Stage (Phase 3c)
|
||||
|
||||
> FIXED — 2026-09-04: all five bugs fixed (developer stage). Bugs 1 and 4 (implementation defects — panel rows never parented into the list container) fixed by adding the row to the list container; bugs 2 and 3 (spec/design defects — unconditional entry-point items) fixed by gating popup items behind an `about_to_popup` refresh; bug 5 (spec/design defect — theme schema) fixed by extending `sandbox_theme.json`'s `fonts` block + adding `apply_font(...)` to the Phase 3c panels/editors. See per-bug notes below. Spec amendments: `docs/phase_3c_editor_spec.md` §2 decision 8, §5.3, §11, and §14.
|
||||
|
||||
1. **"Edit Queue" shows no actions (implementation defect).** `queue_panel.gd` `refresh()` builds each row via `_make_row()` into `_rows` but never `_list.add_child(...)`s it, so rows are tracked for reorder but never rendered. Fix: add `_list.add_child(panel)` in `_make_row()` (or in `refresh()`).
|
||||
- **FIXED (2026-09-04):** rows are now parented into the list container (`_list.add_child(panel)` in `_make_row()`), so the queue rows render.
|
||||
2. **"Edit Queue…" should not appear when the stickman has no actions (spec/design).** The Direct action popup (`sandbox_stage.gd:1389`) and the stickman right-click menu (`:1475`) add "📋 Edit Queue…" unconditionally; no gating is specified in the plan/spec. Fix: hide/disable the item when `rig.get_queue().is_empty()`, refreshed on `about_to_popup`.
|
||||
- **FIXED (2026-09-04):** the popup items are now gated via an `about_to_popup` refresh — "📋 Edit Queue…" is hidden/disabled when `rig.get_queue().is_empty()`.
|
||||
3. **"Edit Rules…" should not appear when an object has no rules (spec/design).** Same unconditional add (`sandbox_stage.gd:1390`, `:1476`); no gating specified. Fix: hide/disable the item when no rule has `trigger.source == rig.get_instance_id()`.
|
||||
- **FIXED (2026-09-04):** "⚡ Edit Rules…" is now gated via the same `about_to_popup` refresh — hidden/disabled when no rule has `trigger.source == rig.get_instance_id()`.
|
||||
4. **"Edit Rules" shows no rules (implementation defect).** `rule_panel.gd` `refresh()` has the identical missing `_list.add_child(panel)` as bug 1. Fix: add `_list.add_child(panel)` in `_make_row()`.
|
||||
- **FIXED (2026-09-04):** rows are now parented into the list container (`_list.add_child(panel)` in `_make_row()`), so the rule rows render.
|
||||
5. **Font styles/sizes not configurable in `sandbox_theme.json` (spec/design).** The `fonts` block only carries sizes + font paths; no style (bold/italic) and no keys for the Phase 3c panels/editors (which currently get no font override at all). `action_popup_emoji_size` is a dead key. Fix: extend the schema (per-widget `{size, bold, italic}` + Phase 3c sizes/style flags) and add `apply_font(...)` to `QueuePanel`/`RulePanel`/`ActionEditor`/`RuleEditor`; consume `action_popup_emoji_size`.
|
||||
- **FIXED (2026-09-04):** the `fonts` schema is extended (per-widget `{size, bold, italic}` object form + Phase 3c size/style flags); the Phase 3c panels/editors now accept `apply_font(...)`; `set_item_font_size` is replaced with `add_theme_font_size_override`; and stale bold/italic are reset on theme reload.
|
||||
|
||||
## Stickman rig (head mirror + recovery re-anchor)
|
||||
|
||||
> FIXED — 2026-09-04: two runtime bugs in `scripts/stickman_rig.gd` fixed (developer stage), covered by a new 76-assertion headless regression suite `tests/test_phase3c_walk_recovery.gd`. See per-bug notes below. No spec changes; no `.stk` / scene / `.tscn` file touched.
|
||||
|
||||
1. **Head does not mirror when the rig faces LEFT (implementation defect).** `_apply_head_flip()` reflected the mirror through the Head **Pivot** node's negative-determinant scale, which Godot re-decomposed into a per-frame Y-scale flip as the Head bone rotated under `LookAt` (the driver only forwards `update_rotation = true`, so the negative scale leaked into the rotation channel). Root cause: folding the reflection into a driver whose transform is applied through the rotation channel. Fix: keep the Pivot's transform identity for **all** profiles (scale `(1,1)`, rotation `0`) and mirror the mounted geometry directly on `Body/Head.scale.x` (`-1` for LEFT, `(1,1)` for RIGHT/FORWARD); the driver's `update_scale = false` never clobbers this manual scale. Note: the driver may canonicalize the mirror to `(1,-1)` — the X-mirror semantics are unchanged.
|
||||
- **FIXED (2026-09-04):** `_apply_head_flip()` now resets the Pivot transform to identity for every profile and applies the LEFT mirror as `Body/Head.scale.x = -1` (RIGHT/FORWARD → `(1,1)`).
|
||||
2. **Figure slides back to its pre-ragdoll position when it stands up after a fall (implementation defect).** The ragdoll bodies spawn under a world sibling (the rig's parent), so the rig root never moves while the figure falls; recovery captured only rig-local body poses and re-solved them against the root's original world position, so the stand-up tween dragged the standing figure back to where it was **before** the ragdoll instead of where it **landed**. Root cause: recovery never translated the rig root to the ragdoll's landing spot. Fix: `_capture_ragdoll_pose()` additionally records `_captured_hip_world` (the ragdoll torso's world-space hip — `torso.global_position − spine_dir·half`); `_start_recovery()` calls a new `_reanchor_root_to_landing()` which translates the rig root so `STAND_POSE`'s hip lands on that captured world hip, then re-bases the captured rig-local positions by the root shift — the figure stands up **in place** where the ragdoll landed.
|
||||
- **FIXED (2026-09-04):** `_capture_ragdoll_pose()` records `_captured_hip_world`; `_start_recovery()` calls `_reanchor_root_to_landing()` before the snap/tween.
|
||||
3. **Recovery buries the standing figure when the ragdoll lands lying flat (implementation defect).** The 2026-09-04 re-anchor fix anchored `STAND_POSE`'s hip onto a **spine-direction hip** (`torso.global_position − spine_dir·half`), which is only correct while the torso is upright. When the ragdoll lies flat, `spine_dir` is horizontal, so the derived hip sits at ground level (torso center.y + capsule radius ≈ ground), and `_reanchor_root_to_landing()` places the standing hip at ground level — burying the standing feet ~363 px into the ground and making the stand-up tween read as a ground-level pivot instead of a lying→standing rise. Root cause: the landing anchor assumed an upright torso. Fix: replace `_captured_hip_world` with a **landing-center + ground-contact anchor** — `_captured_landing_center` = the ragdoll torso's world center, `_captured_ground_y` = `torso.center.y + RAGDOLL_TORSO_RADIUS`; `_reanchor_root_to_landing()` then sets `new_root = (landing_center.x, ground_y) + FOOT_OFFSET` so the standing figure's **feet** sit on the ground at the landing X.
|
||||
- Also per user request, `STAND_UP_DURATION` is bumped **0.8 s → 2.0 s** so the lying→standing stand-up tween is clearly visible before returning to `ANIMATED`. (Docs updated: README §17, AGENTS.md Phase 11.)
|
||||
|
||||
## Stickman rig (whole-rig Y-axis mirror — design change)
|
||||
|
||||
> **DESIGN CHANGE (proposed by user, 2026-09-05):** replace the per-part/head mirroring for `FacingProfile` LEFT/RIGHT with mirroring the **entire stickman** over the Y-axis (`Master.scale.x = -1` for LEFT, `(1,1)` otherwise), so the head AND body mirror together and face the correct direction. `_apply_head_flip()` and the `Body/Head.scale.x` mirror are **removed**; per-joint `flip_bend_direction` flags (`PROFILE_FLAGS`) and `Z_ORDER_BY_PROFILE` are **kept provisionally** (unchanged). **Walk-clip mapping — Option A (single canonical clip):** `walk_right` is the canonical walk; for `FacingProfile.LEFT` the rig root is X-mirrored and the **same `walk_right`** clip plays mirrored (`walk_left` becomes unused at runtime; the animation `.:facing_profile` tracks are neutralized/removed and facing is set explicitly by `set_facing_profile()`/`walk_to()`). See `docs/phase9_task4_refactor_spec.md` §9a and the `AGENTS.md` stickman_rig section.
|
||||
> **Follow-up (2026-09-05):** under the LEFT root mirror the head was displaced/flipped — (a) the head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`) set `update_scale = false`, a partial-channel push that re-canonicalized `Body/Head.scale` under the mirrored root (per-frame Y-flips/wrap-jumps), and (b) the head `SkeletonModification2DLookAt` is **not mirror-invariant**, writing a bone rotation 180° off the FORWARD aim that flips the head to hang below the neck. Fixed: the head driver now pushes the **full transform** like every other `Body` driver (`update_scale` no longer `false`), and `_apply_head_lookat_mirror_mode()` **disables** the LookAt when facing LEFT, pinning the head bone to the FORWARD canonical aim (π) with `_pin_mirrored_head_rotation()` re-asserting the pin each `_physics_process` frame while ANIMATED/RECOVERING (RIGHT/FORWARD re-enable the LookAt). Interactive head-aiming is intentionally static while facing LEFT.
|
||||
|
||||
Reference in New Issue
Block a user