13 KiB
Phase 4 Bug Fixes — High-Level Plan
1. Overview
This plan addresses eight defects discovered after Phase 4 shipped. Bug 1 requires a design decision (reactive-action interrupt policy); Bugs 2–8 are diagnosed defects with clear fix directions.
The plan is a design brief for the architect. Implementation details belong in the resulting spec, not here.
2. Bug 1 — Reactive Event Interrupt Policy
2.1. Problem Statement
A walk is in progress; a rule fires (When [Stickman] collides with [Prop] → Ragdoll). The reactive action is appended to the queue tail and only executes after the walk finishes. The user expects immediate response.
2.2. Design Decision — Three Interruption Roles
Every action — whether in the sequential queue or arriving reactively — fits one of three roles:
| Role | Behavior when triggered reactively |
|---|---|
| Sequential | Cancel the current action; run this action; resume the queue after |
| Overlay | Run alongside the current action; the queue is not touched |
| Takeover | Cancel everything (including overlays); take over the body state |
2.3. Role Assignment Per Action Type
| Action | Role |
|---|---|
walk_to |
Sequential |
wait |
Sequential |
ragdoll |
Takeover |
recover |
Takeover |
speak |
Overlay by default — director can promote to Sequential via a per-rule toggle |
2.4. Director-Facing Behavior
The rule builder shows a single checkbox on speak actions only:
"Stop what he's doing?" (default OFF)
- Unchecked (default) → speak is an Overlay. The stickman keeps walking / waiting / whatever he was doing. The speech plays alongside.
- Checked → speak becomes Sequential. The stickman stops what he's doing, says his line, then resumes the queue.
No checkbox on walk_to, wait, ragdoll, or recover — their roles are fixed and unambiguous.
2.5. Why This Model
- Covers every combination — walk + speak, speak + walk, walk + ragdoll, speak + speak, wait + walk, etc. Each reduces to a role pairing.
- Kid-friendly — one checkbox with plain-language wording on the only action where both behaviors are meaningful.
- Chain reactions handle "both at once" — a director who wants "say 'Ouch!' AND walk away" adds two actions to the rule: overlay speak + sequential walk.
- Forward compatible — the registry gains an
overlay_eligibleflag on action templates. Today onlyspeakis marked overlay-eligible. Future actions likegesture,emote, oraimcan be added later without changing the architecture.
2.6. Overlay Conflict Rule
When two overlay speaks arrive in the same window (e.g., two props hit at once), the new speech replaces the old — no queue pileup, one bubble at a time.
2.7. Takeover Cleanup
ragdoll and recover (as Takeover actions) explicitly cancel any active overlay speech on entry. The bubble hides and the queue pauses until the takeover state resolves.
2.8. Acceptance Criteria (Bug 1)
Walk → collides → Ragdoll— ragdoll fires on contact, walk is cancelledWalk → collides → Speak "Ouch!"(checkbox OFF) — walk continues, speech playsWalk → collides → Speak "Ouch!"(checkbox ON) — walk stops, speech plays, then queue resumesSpeak → collides → Walk— speak cancels, walk startsWalk → collides → [Speak (overlay), Walk]— speech and walk fire togetherWalk → collides → Ragdollwhile a speak overlay is active — speech cancels, ragdoll fires- Sequential queue resumes after every interrupt
clear_reactive_actions()still strips reactive actions on Edit-mode return
3. Bug 2 — Default Pose Changes on Placement
Problem: The stickman's default pose (both arms up) changes to arms-down after placement.
Suspected area: Placement path differences in IK target state, modification stack enabled timing, or animation player auto-start behavior between the reference rig and the spawned rig.
Fix direction: Ensure a newly placed stickman's pose matches the reference/default pose exactly at spawn time. Diagnose the delta between the reference rig and a spawned rig on the same frame.
4. Bug 3 — Animation Continues After Scene Stop
Problem: When the user switches from PLAY to DIRECT/EDIT mid-flinch, the head visibly tweens back to its resting position instead of snapping.
Suspected area: Reaction-decay tweens are not being cancelled on mode exit.
Fix direction: All in-flight pose tweens (reaction decay, stand-up, etc.) must be cancelled and the affected bones set directly to their captured base rotations on mode exit. No animation should play during state restoration.
5. Bug 4 — Props Cannot Be Moved or Rotated
Problem: A placed prop can be selected (rotate gizmo appears), but neither drag nor rotate works.
Suspected area: The edit-mode freeze state (FREEZE_MODE_KINEMATIC) may be blocking transform writes, or the drag/rotate input path is not routing to props, or another system is resetting the transform after the drag.
Fix direction: Audit the prop drag/rotate input path end-to-end. Verify the frozen state permits global_position/global_rotation writes for edit-time manipulation, and that nothing re-sets the transform after commit.
6. Bug 5 — Ghost Pose ≠ Placed Pose
Problem: The placement ghost shows the stickman with bent legs. The placed stickman has straight legs.
Suspected area: Shared root cause with Bug 2 — the ghost is a static render (modification stack disabled), while the placed rig is live and the IK solver drives a different pose.
Fix direction: The placed stickman's pose must match the ghost's pose at spawn. This is likely resolved by fixing Bug 2.
7. Bug 6 — Speech Bubble Mirrors with LEFT Profile
Problem: When facing LEFT (Master.scale.x = -1), the speech bubble and its text render mirrored — backwards text.
Suspected area: The speech bubble is a child of the rig root; the whole-rig X-mirror propagates into its _draw().
Fix direction: Isolate the speech bubble from the rig's mirror. Either move it out of the mirrored subtree (world-space child of the stage, following the head bone) or apply a counter-mirror inside its draw. UI must not inherit anatomy mirroring.
8. Bug 7 — Snap Uses Object Size Instead of Grid
Problem: With snap enabled, dragging objects (steps, etc.) snaps at intervals equal to the object's own width/height, not the grid.
Suspected area: The drag-snap function is quantizing to the dragged object's AABB dimensions rather than the stage grid — likely residual block-unit paint stride logic applied incorrectly to the general drag path.
Fix direction: Object drags must snap to the stage grid (_grid_size) only. Block-unit stride is exclusive to terrain drag-painting; it must not leak into the general drag path.
9. Bug 8 — Single Terrain Placement Uses Block-Unit Snapping
Problem: Placing a single terrain block (one click) snaps to the block's own width/height intervals even when Snap is OFF.
Suspected area: Same as Bug 7 — the single-placement path reuses the block-unit paint stride instead of the grid or no-snap.
Fix direction: Single terrain placement must follow the standard object snapping rules:
- Snap ON → snap to grid
- Snap OFF → no snapping; place at cursor exactly
Block-unit stride is for drag-painting only.
10. Bug Grouping
| Group | Bugs | Theme |
|---|---|---|
| A — Reactive model | 1 | Design decision + runner/action role refactor |
| B — Pose consistency | 2, 5 | Placement pose must match default/ghost |
| C — Animation stop | 3 | Snap pose on mode exit, no tween |
| D — Prop interaction | 4 | Restore drag/rotate in Edit |
| E — Mirror isolation | 6 | UI must not inherit rig mirror |
| F — Snapping source | 7, 8 | Grid vs. block-unit stride confusion |
11. Suggested Implementation Order
| Order | Group | Rationale |
|---|---|---|
| 1 | B (#2, #5) | Likely one root cause; visible, high-impact on first-use impression |
| 2 | E (#6) | Localized, low-risk, easy to verify |
| 3 | F (#7, #8) | Likely one root cause; snapping is a daily-use frustration |
| 4 | D (#4) | May need investigation; blocks prop interaction |
| 5 | C (#3) | Interacts with reaction system; do after A's mechanism is understood |
| 6 | A (#1) | Requires the reactive-role refactor; touches the runner, the registry, the rule builder, and the reaction system |
Bug 1 is last because it is the largest and touches the most systems. It also benefits from the clean foundation the other fixes restore.
12. Open Questions for the Architect
- Bug 1 — Overlay speak mechanics: Should the overlay speak still respect the
_speech_activestate, or should the state be generalized to a list of active overlays? (Recommendation: single active overlay; new replaces old.) - Bug 1 — Chain reactions with mixed roles: When a rule contains
[overlay speak, sequential walk], the overlay speak should fire first and the sequential walk should interrupt it. Confirm the ordering. - Bug 2/5 — Ghost vs. placed rig: Should the ghost be a live rig (always matches placed) or a static preview (placed rig is initialized to match)? Live ghost is more accurate but heavier.
- Bug 4 — Prop freeze: Confirm whether the freeze state is the actual blocker, or whether the input path is broken independently.
13. Acceptance Summary
- Every bug has a clear repro, a fix direction, and verifiable acceptance criteria.
- Bug 1's design is locked in: three roles (Sequential, Overlay, Takeover), one director-facing checkbox on speak, forward-compatible registry flag.
- Bugs 2–8 are diagnosed defects with localized fixes.
- Suggested order minimizes risk: local fixes first, cross-cutting refactor last.
14. Final implementation record
The approved implementation is complete. The runtime behavior is:
| Area | Final behavior |
|---|---|
| Reactive actions | StickmanRig keeps authored actions in the editor queue and runs rule actions in a private lane. Overlay speech runs concurrently in one replaceable slot; Sequential actions cancel the current primary action and continue with the authored future; Takeover actions cancel primary and overlay work before taking control. |
| Prop transforms | SandboxStage routes the exact mouse event position through _viewport_to_world(). StageGizmos writes translation/rotation continuously, and frozen RigidBody2D props are synchronized through PhysicsServer2D during the drag and for three post-release settle frames. |
| Grid snapping | Translation snap is based only on the configured grid size. The drag delta is accumulated from the original press, so sub-grid mouse events remain smooth instead of requiring an object-length drag. |
| Walk completion | LEFT-facing walks stop on the exact current animation marker/bone frame when the queue reaches the end; no factory-pose restore occurs at arrival. Explicit stage reset (stop_queue() + snap_to_standing()) restores the captured factory spawn pose. |
| Stop/flinch cleanup | Mode exit cancels reaction and recovery tweens, restores captured base rotations immediately, and collision dispatch is PLAY-only so delayed contacts cannot recreate a flinch after STOP. |
| Walk To summary | Rule summaries read the waypoint from params.target and keep the actor instance id in the top-level target, with type guards for malformed/legacy payloads. |
Validation recorded for this hand-off:
- Focused Phase 4 final-bugs regression: 81/81 passed.
- Grid regression: 7/7 passed.
scenes/sandbox_stage.tscnheadless smoke test: passed.
End of Phase 4 Bug Fixes — High-Level Plan