Add headless regression tests for Phase 4b features
- Implement test for popup anchor behavior in rule-builder menus to ensure consistent anchor positioning during menu transitions. - Create tests for stage logic, including mode transitions, toolbar visibility, and status bar updates. - Add terrain drag-painting tests to verify correct block placement behavior and conflict handling. - Introduce walk waypoint tests to check for arrival conditions and position stability after navigation.
This commit is contained in:
@@ -23,7 +23,11 @@ This document tracks known technical debt, optimization opportunities, and minor
|
||||
| 11 | **Stage Freeze Abstraction** — Sandbox Stage EDIT‑mode freezing is type‑specific: `RigidBody2D.freeze_mode = FREEZE_MODE_KINEMATIC` for props, `StickmanRig.set_ragdoll(false)` for stickmen, nothing for `StaticBody2D` terrain. There is no unified "freeze" abstraction over the mixed physics population. | Low | Open | A future physics type (e.g. `Area2D`‑based sensors) will need another case in `scripts/sandbox_stage.gd` `_enter_edit_mode()` / `_enter_play_mode()`. Consider a duck‑typed `set_simulating(bool)` interface once more physical object kinds appear. (2026‑08‑27) |
|
||||
| 12 | **Stage AABB Selection Precision** — `StageSelection.get_world_aabb` uses conservative world‑space AABBs (polygon point union / fixed rig rect), not point‑in‑polygon. | Low | Open | Clicks in the bounding‑box corners of large or rotated terrain may select a block even outside its polygon, and overlapping blocks can mis‑select. Refine with `Geometry2D.is_point_in_polygon()` for `TerrainBlock`/`PropBlock` polygons (and circle distance for ball props) once selection precision matters. (2026‑08‑27) |
|
||||
| 13 | **Slope-aware walking physics & dynamic obstacle avoidance (deferred)** — Phase 3a bakes a real navigation mesh (per-`TerrainBlock` polygon decomposition into a code-built `NavigationRegion2D`) and `walk_to` follows `NavigationAgent2D` paths, but the figure walks the path with an upright pose (no tilt to the slope, no physics sliding) and `avoidance_enabled = false`, so it can path through props and other stickmen. | Medium | Open | A stickman crossing a ramp/stair follows the sloped footprint but looks flat-footed, and does not avoid moving props or each other. Future: tilt/rotate the figure to the path slope and enable RVO avoidance (`avoidance_enabled`, avoidance layers, `velocity` handling) once props/other stickmen are registered as obstacles. (2026‑08‑29) |
|
||||
| 14 | **Phase 4 event engine is O(rigs×props + areas×movables) per physics frame** — `sandbox_stage.gd` `_update_area_entry()` / `_update_stickman_prop_collision()` run all-pairs geometric tests every physics frame in PLAY. | Low | Open | Fine at sandbox scale, but degrades quadratically with dozens of dynamic objects. Future: add a spatial hash / broadphase grid keyed by world cell to cull candidate pairs before the AABB/feet-point tests. (2026‑08‑30) |
|
||||
| 14 | **Phase 4 event engine is O(rigs×props + areas×movables) per physics frame** — `sandbox_stage.gd` `_update_area_entry()` / `_update_stickman_prop_collision()` run all-pairs geometric tests every physics frame in PLAY. | Low | Open | Fine at sandbox scale, but degrades quadratically with dozens of dynamic objects. A cell-keyed spatial broadphase (e.g. `_grid_cells`) can cull candidate pairs before the AABB/feet-point tests. Phase 4b implemented that index (see #16, now Resolved) but the event engine does **not yet query it** — #14 remains Open until `_update_area_entry()` / `_update_stickman_prop_collision()` use it. (2026‑08‑30 / note updated 2026‑09‑02) |
|
||||
| 15 | **Walk arrival jitter (mode re-evaluated per frame + arrival-radius mismatch)** — `StickmanRig._update_walking()` re-computes `_walk_mode` from `is_target_reachable()` every physics frame, and finishes via `is_navigation_finished()` (`NAV_TARGET_DESIRED_DISTANCE` 12 px, feet-space) *or* the `ARRIVE_DISTANCE` 8 px root-space guard. Near the nav-mesh boundary (clicked waypoints often sit just off placed terrain) `is_target_reachable()` can flip, swapping `root_target` between the nav path point and the raw waypoint — two points with a small vertical offset → up/down jitter. | Medium | ✅ Resolved | Phase 4b: `StickmanRig._update_walking()` now **latches `_walk_mode` once per walk** (probes up to `LATCH_PROBE_MAX_FRAMES` = 6 after the map syncs, latching `"nav"` when reachable or `"direct"` on the bound) so it can never flip between frames, unifies arrival on the **final target** at `ARRIVE_DISTANCE` 8 px (snap-on-arrive), steers straight at the final target when within `2 × ARRIVE_DISTANCE`, and re-asserts the standing markers one extra physics frame after stop (`_settle_walk_markers()`) to clear any residual body-bob. Exactly one `arrive` fires; `global_position` is unchanged after arrival. See `plans/PHASE_4b_SPEC.md` §3. (2026-09-02) |
|
||||
| 16 | **No grid spatial dictionary** — terrain placement (`sandbox_stage.gd` `_place_at`) is single-click with no occupancy tracking; the 3-state "empty/same-type/blocked" drag-paint query and any spatial broadphase need a cell→nodes index. | Low | ✅ Resolved | Phase 4b adds an **advisory** grid spatial dictionary `_grid_cells` (cell `Vector2i` @ `TERRAIN_GRID_SIZE` 16 → `Array[Node2D]`) plus `_rasterize_aabb_to_cells()` to index every world AABB's covered cells; it drives the terrain drag-painting 3-state ghost query and the director target-validity test (skipping cells whose nodes include a `TerrainBlock`). It is populated on place, rebuilt on move/rotate/delete, and is **never authoritative** (the `World` tree is). It is **not yet wired into the Phase 4 event engine** — #14 stays Open for that. See `plans/PHASE_4b_SPEC.md` §2.6. (2026-09-02) |
|
||||
| 17 | **Thumbnail caching has no eviction / cap; render is deferred one-per-frame** — Phase 3b (`stickman_library.gd` / `prop_library.gd` + `thumbnails/*`) caches rig/prop thumbnails to `user://thumbnails/` keyed by stickman basename+mtime and prop `id_v<PROP_VERSION>`. | Low | Open | PNGs accumulate unboundedly on disk (only `clean_stale_stickmen` prunes superseded basenames; nothing caps total bytes), and in-headless the renderers return `null` → a placeholder is shown until a **manual F6 run** populates real captures. Future: a disk-size/LRU eviction policy, a version/cleanup sweep on stage open, and an explicit cache-warm pass (or skip-the-placeholder note) for headless/CI. (2026-09-03) |
|
||||
| 18 | **Asset selection is session-only, not saved** — `StageSpawner.selected_stickman_path` / `selected_prop_id` reset on scene reload (Phase 3b decision, per spec). | Low | Open | Deliberate for Phase 3b (spec §2 decision 1: no disk save, do not extend `_save_settings`). A future persistence phase could persist the last-chosen stickman/prop to `user://sandbox_settings.json` for convenience. (2026-09-03) |
|
||||
|
||||
---
|
||||
|
||||
@@ -58,6 +62,11 @@ This document tracks known technical debt, optimization opportunities, and minor
|
||||
| 2026-08-29 | Phase 3a (Core Director Functionality) spec written (`docs/phase_3a_spec.md`). Logged #13 (slope-aware walking physics + dynamic obstacle avoidance deferred — the nav mesh itself is built in 3a). Notable non-debt decisions recorded in the spec: Play mode now runs the director script instead of auto-ragdolling stickmen; `walk_to(target)` treats `target` as a feet/ground destination via `FOOT_OFFSET`, with the `NavigationAgent2D` child placed at the feet `(0,+385)` so it paths on the ground-level nav mesh. |
|
||||
| 2026-08-29 | **`walk_to` stops-after-a-few-px bug fixed.** `_update_walking` (Phase 3a) now defers nav reads until `NavigationServer2D.map_get_iteration_id(...) != 0` (map-sync guard) and forces the path query via `get_next_path_position()` before any empty-path/finished check. **Follow-up (same day):** the initial "warn + finish in place" unreachable-target policy was itself reported as "stickman stands still with a waypoint" and was replaced by **hybrid nav/direct steering** — an on-mesh target follows the nav path (`_walk_mode = "nav"`), an off-mesh/unreachable target walks **straight to the clicked waypoint** (`_walk_mode = "direct"`, root target = waypoint + `FOOT_OFFSET`), with **no** `push_warning`; `_walk_path_grace` removed (map-sync guard + forced path query replace it); the debug trace now carries `mode=nav|direct`. Off-by-default `DEBUG_WALK` (`stickman_rig.gd`) / `DEBUG_STAGE` (`sandbox_stage.gd`) traces added. #13 remains **Open** (slope physics + RVO avoidance are still deferred; the fix only changes unreachable-target handling). Logged in `BUGS.md`; verified with a 44-assertion headless regression suite. |
|
||||
| 2026-08-30 | Phase 4 (Triggers & Event System) implemented: `scripts/trigger_area.gd` (NEW placeable sensor), `sandbox_stage.gd` rule system (`_event_rules`, geometric event engine `_update_area_entry` / `_update_stickman_prop_collision`, rule-builder UI state machine, `_cleanup_rules_for_nodes`), `stickman_rig.gd` (`arrived` gains a `target` payload; new `enqueue_reactive`), `prop_block.gd` (`collided` physics signal), `stage_director_visuals.gd` rule visualization, `stage_spawner.gd` / `stage_selection.gd` `"area"` palette + duck-typed `get_area_rect`. Logged #14 (all-pairs event engine scales O(rigs×props + areas×movables); spatial hash suggested). |
|
||||
| 2026-09-02 | Phase 4b (Polish) spec written (`plans/PHASE_4b_SPEC.md`). Logged #15 (walk-arrival jitter: `_walk_mode` re-evaluated per frame + arrival-radius mismatch) and #16 (no grid spatial dictionary; Phase 4b adds one for terrain drag-painting + event-engine broadphase). |
|
||||
| 2026-09-02 | Phase 4b (Polish) implemented. Resolved **#15** (walk-mode latch once per walk, unified `ARRIVE_DISTANCE` arrival with snap-on-arrive, steer-to-final-when-close, one-frame marker re-assert) and **#16** (grid spatial dictionary `_grid_cells` @ `TERRAIN_GRID_SIZE` for the drag-painting 3-state query + director target-validity). #14 remains **Open** — the dictionary is not yet wired into the Phase 4 event engine (note updated). Added `res://sandbox_theme.json`, `scripts/stage_placement_overlay.gd`, the 3-segment mode switcher / status bar / badge / frame / cursors, and director tooltip/trajectory/reticle UX. |
|
||||
| 2026-09-03 | Phase 3b (Asset Library) implemented (`docs/phase_3b_asset_grid_spec.md`): `scripts/stickman_library.gd` / `prop_library.gd`, `scripts/thumbnails/thumbnail_cache.gd` / `stickman_thumbnail.gd` / `prop_thumbnail.gd`, `scripts/asset_selector.gd` + `scenes/asset_selector.tscn`. The Stickman/Prop palette buttons now open visual selector grids (pagination 12/page, session-only selection, Browse/Refresh); `StageSpawner` registry becomes `ground/ramp/step/prop/stickman/area` (`crate`/`ball` removed) with `selected_*` session state + per-path stickman cache; `SandboxStage` owns the selector open/close flow, Esc priority, and the one-per-frame lazy thumbnail drain. Logged #17 (thumbnail cache growth / headless placeholder) and #18 (session-only selection). Verified with the new headless suite `tests/test_phase3b_library.gd` plus the updated `tests/test_phase4b1_fixes.gd`. |
|
||||
| 2026-09-03 | **Selector UI bugfix round** (5 bugs, documented via `tests/test_phase3b_ui_fixes.gd` + docs in `docs/phase_3b_asset_grid_spec.md` / `README.md` / `AGENTS.md`): `AssetSelector.open()` drops its selection params (`selected_path`/`selected_id`) — no cell is pre-highlighted on open (selected stylebox removed); the selector re-centers on window resize (`size_changed` → `popup_centered()`); the stage adds a dim backdrop `_selector_dim` (`SELECTOR_DIM_ALPHA` 0.5) behind the grid; the selector's `popup_hide` routes to `_on_selector_cancelled()` (idempotency-guarded) so an outside-click un-presses the palette button; and the Direct-mode action popup opens **right of the clicked stickman** (`_world_to_screen` + 24 px) instead of at the cursor. Reviewed #17 and #18 — neither is obsolete (both concern thumbnail-cache growth/headless placeholders and session-only *persistence*, orthogonal to these UI fixes), so both remain **Open** unchanged; no duplicate rows introduced. |
|
||||
| 2026-09-03 | **Rule-builder popup-anchor bugfix round** (`sandbox_stage.gd`, documented in `README.md` / `AGENTS.md`): the Phase 4 rule-builder context menus used to re-pop at the live mouse position on every re-open, so cycling "⚡ When…" → "⬅ Back to actions" → "When…" walked the menu down the screen. Now a **session anchor** records the first context menu's screen position (`_popup_anchor: Rect2i` / `_popup_anchor_set`; Direct first menu = right of the clicked stickman; rule-label edit entry = the click position) and all child popups reuse it via `_set_popup_anchor(rect)` / `_clear_popup_anchor()` / `_popup_anchor_rect()`, until cleared on confirm (`_finalize_rule`), cancel (`_cancel_rule_build`), or Direct-mode/flow exit (`_clear_director_pending`) — but **not** on `_reset_rule_builder()` (Back-to-actions reuses it). No numbered debt row described the old cursor-following behavior, so no row was flipped to Resolved and no duplicates introduced; recorded here in the Change Log only. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user