# Phase 16 — Move To: Walk and Run > **Status:** Architecture specification; the DIRECT-mode target-first quick action and its readability pass are implemented. Remaining Phase 16 behavior and visual checks are listed in section 6. > **Inputs:** `docs/plans/phase_16_running_stickman.md`, accepted ActionEditor UI proposal (Walk/Run buttons), approved DIRECT-mode dropdown flow, and the Phase 1a architecture decisions. > **Target:** Godot 4.7.1 Sandbox Stage and the existing modular stickman rig. ## 1. Goal and decisions A child chooses Walk or Run on each **Move To** action, places one destination, and sees the style in queue and rule summaries. Run has a faster fixed pace and its own grounded, pushing, airborne, and recovering poses. The destination, waypoint identity, navigation behavior, and arrival trigger remain independent of style. Approved decisions: 1. The canonical action type is `move_to`, with persisted `style: "walk" | "run"`. New actions use it. Loading old `walk_to` actions and action-completion filters converts them to `move_to`/Walk before validation. 2. Normal arrival has a brief pose settle. The physical arrival event fires when the root reaches the target; action completion fires when the settle ends. Ragdoll and other interruptions can preempt it. 3. Visual verification covers **every existing terrain shape** (Ground, Ramp, Step), their seams and edges, both facing directions, and existing ledge/fall/landing behavior. This does not add pathfinding or slope navigation. The Walk/Run choice is a string in saved dictionaries because `.scene` is JSON. A new `Resource` hierarchy or a second navigation component would add conversions and scene dependencies to a two-choice field. The existing `ActionRegistry`, `StageSceneIO`, `StickmanActionRunner`, and `StickmanLocomotion` remain the ownership boundaries. ## 2. Data contracts and migration The authoritative in-memory queue action is: ```gdscript {"type": "move_to", "target": Vector2(640.0, 385.0), "style": "run"} ``` The equivalent rule consequence separates the acting stickman ID from the destination: ```gdscript { "type": "move_to", "target": actor_instance_id, # int, -1 while choosing an actor "params": {"target": Vector2(640.0, 385.0), "style": "run"}, } ``` The saved queue entry is `{"type":"move_to","target":{"x":640,"y":385},"style":"run"}`. The saved rule entry has `type: "move_to"`, its existing `target_id` actor reference, and `params.target` as an `{x,y}` object plus `params.style`. `target` remains a feet/ground destination, as in the current walking action. Neither form stores a numeric pace. Existing `.scene` `FILE_VERSION` can remain `1.1` because these fields are additive and old types are handled explicitly at decode; no other document shape changes. ### 2.1 `ActionRegistry` (`class_name ActionRegistry extends RefCounted`) Replace the menu template for `walk_to` with one `move_to` template labeled **Move To**, with `target` position and `style` choice (default Walk). Keep `walk_to` out of `types()` so children never add the legacy type. Define statically typed helpers: ```gdscript const MOVE_TO: String = "move_to" const WALK: String = "walk" const RUN: String = "run" static func canonical_type(type: String) -> String # walk_to -> move_to; otherwise unchanged static func checked_style(value: Variant) -> String # null/missing -> walk; invalid -> "" static func is_move_action(action: Dictionary) -> bool ``` `checked_style` accepts exactly the two lowercase strings; missing or null means Walk for legacy data. An explicit unknown or wrong-type style is invalid at the file boundary and must not silently become Walk. `is_move_action()` accepts `move_to` and legacy `walk_to` at raw-input boundaries. `to_rule_action()` and `from_rule_action()` always produce canonical `move_to` with both `target` and `style`. `summarize()` produces `Move To · Walk` or `Move To · Run` for flat and nested rule shapes without exposing coordinates. Completion-filter labels use the same child-facing **Move To** wording. ### 2.2 `StageSceneIO` (`class_name StageSceneIO extends RefCounted`) `_decode_flat_action()` canonicalizes `walk_to` **before** `ActionRegistry.has_type()` rejects unknown action types, validates the target vector, supplies Walk when style is absent, and rejects explicit invalid styles. `_encode_flat_action()` writes only canonical `move_to`; it also accepts an in-memory legacy `walk_to` input and normalizes it. Rule encode/decode must continue using the registry's flat converters so queue and rule payloads cannot diverge. No migration changes action order, rule ID, actor reference, or waypoint coordinates. The `action_finished` trigger stores its filter in `trigger.params.action_type`. `_decode_trigger()` maps a legacy filter value of `walk_to` to `move_to`; `_encode_trigger()` canonicalizes it on save. `SandboxStage._rule_matches()` also compares canonicalized filter/action types so an in-memory rule supplied by a script retains its behavior. `arrived_at_waypoint` triggers keep matching `waypoint_pos` and fire for either style. The empty action filter continues to mean “any action.” ### 2.3 Runtime and programmatic compatibility `StickmanActionRunner` should normalize any legacy action it receives from a programmatic queue or reactive rule before execution and emission, so `action_started`/`action_finished` report canonical `move_to` consistently. `StickmanRig.walk_to(target, speed)` stays as the public direct Walk API and may retain its positive speed override for existing scripts. The child-authored `move_to` action never contains `speed`: Walk uses the existing default pace and Run uses its paired fixed pace. Legacy `walk_to` with an explicit positive `speed` supplied programmatically can retain that override for compatibility during execution; `.scene` serialization remains style-only. Do not let that compatibility case create a speed control in the editor. ## 3. Authoring UI and stage flows ### 3.1 `ActionEditor` (`class_name ActionEditor extends PopupPanel`) Keep the existing `committed(action: Dictionary)`, `cancelled()`, and `target_requested()` signals. Its committed Move To dictionary is fully typed by convention: `type: String`, `target: Vector2`, `style: String`. Add a private `_move_style: String = ActionRegistry.WALK`; `open_new()` resets it to Walk, and `open_edit(action)` reads canonical or legacy movement data, selecting Run when appropriate. `set_walk_target(pos: Vector2)` can remain as a compatibility method name, but it changes only the destination and must preserve `_move_style`. Cancel and Escape discard the draft without mutating a queue or rule action. Layout inside `_params_box` for Move To: ```text ActionEditor PopupPanel └─ MarginContainer / VBoxContainer ├─ Type: [Move To ▾] ├─ VBoxContainer (Move To parameters) │ ├─ Label: How should they move? │ ├─ HBoxContainer: [🚶 Walk] [🏃 Run] │ └─ HBoxContainer: Place to go: / Place chosen [🚩 Choose a Place / Change Place] └─ HBoxContainer: [Cancel] [Done] ``` The Walk and Run buttons are equal-width choices, each at least 44×44 logical pixels. If the available popup width cannot contain both at that size, stack them vertically without shrinking the hit areas. Use `SandboxTheme`'s DIRECT amber accent, a pressed state, and a visible border for the selected option; use the theme surface for the other. Hover and keyboard focus remain visible, and text/icon/pressed state convey selection without color alone. Footer buttons are at least 44 px high. The existing theme font and emoji font pipeline applies to new controls. Keep the popup's current minimum dialog size unless the controls actually require a larger theme-driven size. The destination row uses **Place to go:** and **🚩 Choose a Place** when unset, then **Place chosen** and **🚩 Change Place** once set. It never shows raw coordinates. Changing Walk/Run retains the destination. **Done** with no destination starts placement. Explicit placement hides the popup, retains the draft style and old target, and restores the popup after a valid stage click. The stage cursor pill says `🚩 Click where they should walk` or `🚩 Click where they should run`; existing line/ghost, green valid/red invalid target feedback, placement cursor, and prominent cancel action remain. Canceling capture reopens the popup with the old target and style. The final **Done** commits once. ### 3.2 `SandboxStage` (`class_name SandboxStage extends Node2D`) All child-facing action menus say **Move To**. In DIRECT mode, the user chooses the single **Move To** command and places the destination first. That click stages the target and opens a compact follow-up panel with a **Movement** dropdown, **Add Move To**, and **Cancel**; no queue action is added until Add is selected. The dropdown is populated from the registered movement-style choices (currently Walk and Run), defaults to Walk for a stickman's first draft, and remembers the last successfully committed style per stickman while the scene is open. Cancel, Escape, right-click, popup dismissal, and leaving DIRECT mode discard the draft; canceled choices do not update the remembered style. The readability-approved panel is 380×172 px, with a 23 pt label, 25 pt selector/style emoji, 22 pt buttons, a 210×54 px selector, and 52 px-high buttons. Queue Add/Insert opens `ActionEditor` and permits either style. Queue Edit and waypoint **Edit this Move** must open the editor with the existing target and style; the current `_begin_walk_edit()` stage-only shortcut bypasses pace selection and should be replaced or reserved solely for a clearly separate Change Place gesture. Resolve the edited action by existing `action_id` at commit, preserving action identity and consumed-action restrictions. Waypoint **Erase this Move** removes that same action. Marker hit testing, path drawing, and target re-placement work for both styles. The rule-builder quick path currently creates a `walk_to` consequence and captures a position without opening `ActionEditor`. Route Move To through the shared editor after actor selection (or an equivalent shared draft using that same control), then put the returned target/style into `params` and resume the existing Add another/finish rule flow. Existing rule Edit/Add paths through `ActionRegistry` and `ActionEditor` must preserve the style in both directions. `SandboxStage._action_for_rig()` should delegate Move To conversion to `ActionRegistry.from_rule_action()` so reactive playback does not drop style. Normal direct movement remains Walk. `StageDirectorVisuals` recognizes canonical `move_to` for every waypoint/path/hit-test branch and uses `ActionRegistry.summarize()` or equivalent for `Move To · Walk/Run` queue and rule descriptions. `WaypointContext` uses **Edit this Move** and **Erase this Move**. The marker itself represents the destination only and has no Walk/Run icon. Rule completion filters present Move To as one action type; both styles match it. Apply the same child-facing words to status hints and the rule builder. DIRECT-mode style choices are remembered only for the current open scene and are not serialized. ```mermaid flowchart LR UI[ActionEditor Walk/Run + destination] -->|committed flat action| ST[SandboxStage] ST -->|queue action| AR[StickmanActionRunner] ST -->|rule conversion| RG[ActionRegistry] RG -->|rule consequence| IO[StageSceneIO save/load] IO -->|canonical move_to| RG RG -->|flat reactive action| AR AR -->|style + target| LC[StickmanLocomotion] LC -->|arrival| ST AR -->|action_finished| ST ST -->|queue and rule display| DV[StageDirectorVisuals] ``` ## 4. Locomotion, gait, and terrain ### 4.1 Ownership and API `StickmanActionRunner` keeps the existing action queue, `ActionPhase.WALKING`, status, interruption, and `action_started`/`action_finished` signals. It passes the normalized style to `StickmanLocomotion`. `StickmanLocomotion extends Node` keeps the single `NavigationAgent2D`, target/arrival logic, terrain following, and foot contacts. Add a typed movement entry such as: ```gdscript func move_to(target: Vector2, style: String = ActionRegistry.WALK, speed_override: float = -1.0) -> bool ``` The existing `walk_to(target: Vector2, speed: float = -1.0) -> bool` delegates to `move_to(..., WALK, speed)` for `StickmanRig` and older callers. Keep `consume_walk_completed()` and `cancel_walk()` compatibility names if changing them would cause unnecessary API churn; their behavior covers both styles. Use an active style field and one style table/typed constants for clip name, default pace, cycle duration, and contact windows. This keeps travel speed, animation cadence, and contact logic paired at one boundary. `NavigationAgent2D.max_speed` and root movement use the same selected pace. Start with Walk at the existing 300 px/s and a Run candidate around 500 px/s, then tune Run pace and cycle together against visible foot travel; the final constants belong in locomotion, not in the saved action or UI theme. The current `_stance_weight()` assumes a 0.8-second Walk cosine. Retain that behavior for Walk if it remains visually correct. Run uses an explicit repeating contact schedule aligned with its authored clip. As an initial 0.64-second two-step cycle, put right support at approximately 0.00–0.20 s, left support at 0.32–0.52 s, and brief flight at 0.20–0.32 and 0.52–0.64 s. Smooth the edge of each contact window; return zero support for both feet in flight. Tuning may change those numbers, but the clip and contact windows must change together. Reset foot locks when a foot leaves support; the terrain solver must not pin a recovering or airborne foot to the ground. Keep the existing collision guard against foot penetration when a surface is present. ### 4.2 Baked animation and facing `scripts/tools/create_animations.gd` remains the authored IK keyframe baker. Add a dedicated Run pose sequence and bake looped `run_right` (and `run_left` only if the existing asset convention requires it) into the rig's `AnimationLibrary` in `scenes/characters/stickman/stickman_rig.tscn`. The authored cycle has contact/weight-bearing, push-off, a short flight, leg recovery, opposite contact, counter-swinging arms, modest forward lean, and controlled torso/head bounce. The final keyframe matches the first for seamless looping. It must be an actual distinct gait, not Walk played faster. The generated clip in the committed rig scene is the runtime asset; the tool source and baked scene must agree. Runtime walking currently uses `walk_right` for both directions after changing `StickmanRig.FacingProfile`; preserve that proven facing policy for Run if it mirrors the authored pose correctly. Avoid a second independent flip of run marker X values, which would invert the gait twice. Check both directions in the Sandbox Stage, including held props and hand-target ownership if a prop is present. ### 4.3 Start, arrival, cancellation, and terrain state Starting Move To selects the style's clip, sets facing from target X, enables limb modifiers, clears stale foot locks/settle state, and begins the existing nav/direct fallback. For a target already inside `ARRIVE_DISTANCE`, skip gait playback, snap to the target, emit one arrival, and finish without a visible partial stride. For other short targets, allow a partial cycle and settle from its actual final IK pose. On physical arrival, keep the existing final-X snap and emit `arrived(target)` exactly once. Stop root travel and smoothly interpolate the six IK markers and torso rotation toward the standing pose for roughly 0.12–0.18 seconds, using the current pose as the start; then set the existing completion flag that `StickmanActionRunner` consumes. The settle is part of the action so a following queue item does not fight its marker writes. During a pause, its timer stays paused with the runner. Repeated completion checks must not emit arrival again. If terrain following is in a hard landing, defer movement and settle according to the existing landing barrier rather than writing competing foot targets. Interruption cancels gait/settle ownership and releases contact locks. A ragdoll takeover preserves the current solved pose for the established instant physical handoff; it must not first force a standing tween. An interruption that leaves the rig animated can blend to idle only while no newer action owns the markers. Interrupted actions keep the existing interrupted status and emit no `action_finished`. The style and target in authored queue data remain unchanged through pause/resume, reorder, reactive insertion, or target edit. `StickmanLocomotion` continues its ground probing and existing airborne/landing state machine. A gait's brief authored flight on supported terrain is separate from an actual ledge fall: the Run contact schedule releases foot support without entering `TerrainMotion.AIRBORNE`; the existing ledge probe controls real falls and pauses/resumes the active Run clip as it does Walk. Ground, Ramp, and Step must be checked across up/down travel, transitions at seams, and edge departures. Any needed correction should stay in the existing terrain-following component; adding a new path solver is outside Phase 16. ```mermaid stateDiagram-v2 [*] --> Moving: Move To begins, target beyond arrival radius [*] --> Arrived: target already reached Moving --> Moving: nav/direct travel + gait cycle Moving --> Arrived: final root snap Arrived --> Settling: emit arrived once Settling --> Completed: standing pose reached; completion flag Moving --> Interrupted: cancel / ragdoll takeover Settling --> Interrupted: cancel / ragdoll takeover Completed --> [*]: action_finished once Interrupted --> [*]: interrupted status, no finish signal ``` ## 5. Target files and boundaries | File or class | Required change | | --- | --- | | `scripts/stage/action_registry.gd` — `ActionRegistry` | Canonical type/style helpers; one Move To template; conversions and summaries. | | `scripts/stage/stage_scene_io.gd` — `StageSceneIO` | Queue/rule canonical encode/decode; legacy `walk_to` and completion-filter migration. | | `scripts/editors/stage/action_editor.gd` — `ActionEditor` | Two large choice buttons, destination row, draft retention, canonical commit. | | `scripts/editors/stage/sandbox_stage.gd` — `SandboxStage` | Direct Walk action, capture hints, queue/waypoint/rule routes, runtime rule conversion, completion-filter matching. | | `scripts/stage/stage_director_visuals.gd` — `StageDirectorVisuals` | Move waypoint detection and style summaries; destination-only markers. | | `scripts/stage/waypoint_context.gd` — `WaypointContext` | Move wording and edit route. | | `scripts/editors/stage/rule_editor.gd` and queue/rule panels | Filter label and any summary/selection paths that assume `walk_to`. | | `scripts/characters/stickman/stickman_action_runner.gd` — `StickmanActionRunner` | Normalize legacy runtime inputs, pass style to locomotion, preserve completion/interruption semantics. | | `scripts/characters/stickman/stickman_locomotion.gd` — `StickmanLocomotion` | Shared style-aware travel, Run contact timing, arrival settle, terrain ownership. | | `scripts/characters/stickman/stickman_rig.gd` — `StickmanRig` | Retain public Walk facade; add a Move facade only if the runner needs it. | | `scripts/tools/create_animations.gd` and `scenes/characters/stickman/stickman_rig.tscn` | Author and bake the Run clip into the committed rig asset. | | `sandbox_theme.json` / `scripts/stage/sandbox_theme.gd` | Use existing DIRECT accent and dialog tokens; add only the minimum style overrides needed for large selected/focused buttons. | No new autoload, global signal bus, node layer, persisted numeric speed, or separate Run action type is required. ## 6. Implementation and verification contract Implementation order: (1) registry and I/O migration; (2) editor, stage routes, and visuals; (3) authored Run clip and locomotion/contact timing; (4) arrival/interruption settle; (5) focused verification and visual tuning. The developer should run headless syntax verification as required by the spec-pipeline, then fix parse errors immediately. Add focused regression coverage where it checks distinct behaviors rather than repeating implementation internals. Required behavior checks: 1. Load a legacy scene with queued `walk_to`, rule consequence `walk_to`, and an `action_finished` trigger filtered to `walk_to`. Targets, order, actor IDs, waypoint rules, and completion behavior survive; saving writes canonical `move_to`/Walk throughout. 2. Queue and rule Walk/Run actions round-trip through flat/nested conversions and `.scene` save/load. Invalid explicit styles are rejected at the file boundary. New/direct actions default to Walk. - In DIRECT mode, verify destination-first staging, dropdown choice, Add and every draft-cancel path (Cancel, Escape, right-click, popup dismissal, and mode exit). Confirm the first choice defaults to Walk, a successfully committed choice is remembered per stickman in the open scene, and a canceled choice is not remembered. 3. Add/edit from queue and waypoint menu, and add/edit from rule builder. The style survives target capture, target change, capture cancel, editor cancel, and final commit. Editing a consumed action remains blocked. Summaries say `Move To · Walk/Run` and waypoint markers remain destination-only. 4. Run visibly exceeds Walk's pace while its foot cycle tracks root travel on a long level path. Verify short (< one cycle) and zero-distance targets, one arrival and one completion, and no skating or penetration at support. 5. Check both facing directions on Ground, Ramp, and Step; travel over joins and on upward/downward surfaces; ledge fall and landing; reactive interruption, ragdoll takeover, queue pause/resume, and a following queued action. Flight frames show both feet released from contact while a true ledge fall still uses terrain airborne behavior. The Sandbox Stage CLI scene playback in `AGENTS.md` is the visual verification route. Record the final tuned Run speed/cycle/contact windows and any terrain limitation observed during editor review. Phase 3 ends with a user hand-off for Godot Editor verification; documentation updates follow only after the user confirms the feature works. ## 7. Trade-offs and scope - A canonical `move_to` type requires coordinated migration in serialization, trigger filters, editor branches, and waypoint visuals. It gives future movement styles one destination-oriented action and avoids carrying a misleading `walk_to` identifier indefinitely. - A fixed style table keeps pace and gait together and avoids exposing a numeric control to children. Existing programmatic `walk_to(speed)` remains a compatibility route. - Explicit Run contact windows duplicate a small amount of gait timing data between the animation baker and locomotion. This is preferable to pretending Run follows the Walk cosine stance; the values must be tuned and reviewed together. - A short settle makes partial cycles readable but delays `action_finished` slightly after physical `arrived`. The events retain distinct meanings and each fires once. - Full Ground/Ramp/Step visual coverage may require tuning terrain foot placement as well as the clip. It does not authorize new slope pathfinding, dynamic avoidance, or physically simulated locomotion.