Add Phase 4 Final Bugs Specification and Tests
- Introduced a new architecture specification for Phase 4 final bugs, detailing scope, decisions, runtime data contracts, action roles, execution lanes, speech ownership, and signal flow. - Implemented changes in `scripts/action_registry.gd`, `scripts/action_editor.gd`, `scripts/sandbox_stage.gd`, `scripts/stage_gizmos.gd`, `scripts/stickman_factory.gd`, and `scripts/stickman_rig.gd` to address identified bugs. - Created a new test suite `tests/test_phase4_final_bugs.gd` to validate the implementation, covering action roles, grid snapping, prop manipulation, reactive runtime lane, spawn pose preservation, and more. - Added a unique identifier for the test file to ensure proper tracking and management.
This commit is contained in:
@@ -383,8 +383,11 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
and restores them for RECOVERING/ANIMATED. `apply_collision_reaction()` uses contact position
|
||||
plus local force quadrant to choose mirrored side/upward/downward flinch direction, applies a
|
||||
capped `REACTION_MAX_ANGLE` offset from a captured base rotation, and decays without cumulative
|
||||
full rotations. PLAY→DIRECT restoration also resets ANIMATED rigs' authored transform, facing,
|
||||
IK markers, and torso rotation. Null-guards + `push_warning` prefixed `StickmanRig:` throughout.
|
||||
full rotations. `snap_to_standing()` kills reaction/recovery tweens and restores captured base
|
||||
rotations synchronously; the stage gates collision dispatch to PLAY so delayed contacts cannot
|
||||
recreate a flinch after STOP. PLAY→DIRECT restoration also resets ANIMATED rigs' authored
|
||||
transform, facing, IK markers, and torso rotation. Null-guards + `push_warning` prefixed
|
||||
`StickmanRig:` throughout.
|
||||
- **Phase 3a director functionality:** adds navigation/walking, speech, an action queue, and a
|
||||
queue-runner state machine. Enums `RunnerState { IDLE, EXECUTING }` and `ActionPhase { NONE,
|
||||
WALKING, SPEAKING, WAITING, RAGDOLLING, RECOVERING }`. Constants `FOOT_OFFSET := (0, -385)`
|
||||
@@ -406,8 +409,9 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
countdown; `ragdoll` → `set_ragdoll(true)` then wait `is_ragdoll_at_rest()`; `recover` →
|
||||
`request_recovery()` then wait `state == ANIMATED`). `is_ragdoll_at_rest()` is a new public
|
||||
query exposing the rest result **regardless of `auto_recover`** (the runner polls it; auto-recovery
|
||||
logic is unchanged). `_enter_ragdoll()` additionally calls `_cancel_walking()` (no stale walk/path
|
||||
state) and resets `_ragdoll_at_rest = false`. **Walk fix (2026-08-29, hybrid policy):**
|
||||
logic is unchanged). `_enter_ragdoll()` additionally calls `_cancel_walking(false)` (no stale
|
||||
walk/path state while preserving the current solved pose for instant ragdoll handoff) and resets
|
||||
`_ragdoll_at_rest = false`. **Walk fix (2026-08-29, hybrid policy):**
|
||||
`_update_walking` defers all nav reads until `NavigationServer2D.map_get_iteration_id(...) != 0`
|
||||
(map-sync guard), forces the path query via `get_next_path_position()` **before** any empty-path /
|
||||
finished check (the read-only `get_current_navigation_path()` alone never triggers a
|
||||
@@ -423,12 +427,18 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
(`global_position.move_toward`); movement composes with the canonical `walk_right` in-place limb
|
||||
animation, played for every direction — X-mirrored by the rig root for LEFT (facing is set
|
||||
explicitly by `walk_to()`/`set_facing_profile()`; `walk_left` is no longer used at runtime and
|
||||
the animation `.:facing_profile` tracks are neutralized/removed).
|
||||
the animation `.:facing_profile` tracks are neutralized/removed). `_finish_walk()` captures the
|
||||
eight current limb-bone rotations, calls `AnimationPlayer.stop(true)`, and leaves all six IK
|
||||
marker properties at their current values; no marker reset occurs at queue completion. LEFT idle
|
||||
disables mirrored TwoBoneIK while preserving that stopped marker/bone frame; it does not restore
|
||||
the factory pose. `snap_to_standing()` is the explicit stage-reset path that restores the
|
||||
factory-captured spawn pose.
|
||||
- **Phase 4 triggers:** the `arrived` signal gained a `target: Vector2` payload (emits
|
||||
`_walk_target_feet`) so the stage can match waypoints for `arrived_at_waypoint` rules; new
|
||||
`enqueue_reactive(actions: Array[Dictionary]) -> void` appends reactive actions to the action
|
||||
queue and, if the runner is `IDLE`, resumes at the **first newly-appended action** (no replay of
|
||||
the already-consumed queue prefix). Sequential Phase 3a queues are untouched.
|
||||
`_walk_target_feet`) so the stage can match waypoints for `arrived_at_waypoint` rules; new
|
||||
`enqueue_reactive(actions: Array[Dictionary]) -> void` dispatches reactive actions into a
|
||||
private runtime lane without mutating `action_queue`: Overlay speech runs concurrently in one
|
||||
replaceable slot, Sequential actions cancel the current primary action while retaining authored
|
||||
future work, and Takeover actions cancel both primary and overlay work before taking control.
|
||||
- `scripts/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
|
||||
factory** and the **runtime entry point** (Phase 9, **not used by the editor**) that turns a
|
||||
`.stk` file into a live, rigged `master_rig.tscn` instance:
|
||||
@@ -695,7 +705,10 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
**Grid/snap:** an optional `StageGrid` overlay (drawn behind `World`) plus `Grid`/`Snap`
|
||||
`CheckBox` toggles and a `Size` `SpinBox`, persisted to `user://sandbox_settings.json`
|
||||
(`grid_size`/`snap_to_grid`/`show_grid`); snapping rounds the placement cursor and translate
|
||||
drags via `_snap_to_grid()` / `StageGizmos.snap_size`. The grid is visible only in EDIT mode
|
||||
drags via `_snap_to_grid()` / `StageGizmos.snap_size`. Gizmo input uses the exact mouse-event
|
||||
coordinates converted by `_viewport_to_world()` for press, motion, and release. Frozen props
|
||||
are synchronized to the edited `global_transform` through `PhysicsServer2D` during drag and
|
||||
for three post-release settle frames. The grid is visible only in EDIT mode
|
||||
(`_apply_grid_settings()` sets `_grid.visible = _show_grid and current_mode == EDIT`, re-applied
|
||||
on mode change). **Touchpad:** `_input` handles `InputEventMagnifyGesture` (pinch zoom ×factor)
|
||||
and `InputEventPanGesture` (two-finger pan, `delta × 3 / zoom`) alongside the mouse wheel/middle
|
||||
@@ -747,7 +760,12 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
so cycling the menus never walks down the screen at the live cursor. The anchor is cleared on
|
||||
confirm (`_finalize_rule`), cancel (`_cancel_rule_build`), or Direct-mode/flow exit
|
||||
(`_clear_director_pending`), but **not** by `_reset_rule_builder()` (the Back-to-actions path
|
||||
intentionally reuses it).
|
||||
intentionally reuses it). **Phase 4 final-bugs runtime:** rule actions are dispatched into
|
||||
`StickmanRig`'s private runtime lane: Overlay speech runs concurrently in one replaceable
|
||||
slot, Sequential actions interrupt the current primary action while retaining authored future
|
||||
work, and Takeover actions cancel both before taking control. Collision callbacks are PLAY-only,
|
||||
so delayed contacts after STOP cannot recreate a flinch. Walk To summaries read the waypoint
|
||||
from `params.target`, separate from the top-level actor instance id.
|
||||
- **Phase 4c area-entry reliability:** `entered_area` matching requires both the source movable
|
||||
instance ID and target area instance ID; `source == -1` remains the explicit wildcard. Area
|
||||
entry uses visible world-AABB overlap for ANIMATED stickmen and unfrozen props, so an object can
|
||||
@@ -928,7 +946,11 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
directly by the root; `set_targets(nodes: Array[Node2D])` holds the current multi-selection,
|
||||
and `begin_translate_drag(node, world_pos)` starts a TRANSLATE drag that moves **all** selected
|
||||
nodes together (`drag_to` drives `global_position` with optional grid snap via `snap_size`,
|
||||
snapping the drag anchor and applying the same delta to the rest). The **rotate ring** is drawn
|
||||
snapping the drag anchor and applying the same delta to the rest). Translation accumulates from
|
||||
the drag origin, so sub-grid mouse events remain smooth; snapping uses only `snap_size`, never
|
||||
object dimensions. `transform_started(nodes)` fires at drag start and
|
||||
`transform_updated(nodes)` fires on every drag update so the stage can synchronize frozen
|
||||
physics bodies. The **rotate ring** is drawn
|
||||
and hit-tested only when exactly one node is selected (`hit_test` → `Handle.ROTATE`).
|
||||
The rotate drag is **absolute-from-start** — `drag_to()` sets `global_rotation` to
|
||||
`_rotate_start_rotation + (current − start angle)`, with holding **Ctrl** snapping to
|
||||
|
||||
Reference in New Issue
Block a user