Files
stickman/specs/phase_9c_prop_editor_ux.md
T
ryan 00abacc7b2 Add regression tests for prop attachment, centering, and editor UX
- Implemented test_phase9a_prop_attach.gd to validate anchor schema, alignment math, and preview isolation.
- Implemented test_phase9b_prop_centering.gd to check canonical origin presentation, bounds, centering, and save threshold.
- Implemented test_phase9c_prop_editor_ux.gd to assess the user experience in the prop editor, including anchor renaming and UI responsiveness.
- Added corresponding .uid files for each test.
- Created sync_sources.py script to manage markdown file synchronization with the notebook.
- Added .gitignore for virtual environment files.
2026-09-21 16:48:24 -04:00

31 KiB
Raw Blame History

Phase 9c — Prop Editor UX and Walking Hold Preview

Status: approved specification — ready for implementation (2026-09-21).

Phase 9c is a Prop Editor-only refinement discovered during Phase 9a/9b user verification. It does not amend their saved-file contracts and does not add Sandbox Stage or gameplay behavior.

1. Approved decisions

The user approved these interaction choices:

  1. Replace the preview's separate Facing/Frame controls with one Preview Motion selector: Front Pose, Walk Left, and Walk Right, plus a dynamic Pause Walk / Resume Walk button.
  2. Rename anchors through a modal, focused text-entry dialog. The current automatic old_name_renamed behavior is removed.
  3. Rename Show Inactive Gizmos to Show Other Markers. When off, it hides the passive origin marker and all unselected anchor markers while the selected editable anchor remains visible.

This work is named Phase 9c because Phase 9a and 9b are already implemented and verified. It changes preview motion ownership and established UI contracts rather than retroactively changing those completed specifications.

2. Complete requested refinement set

# Requirement Phase 9c contract
1 Rename accepts a name Open a modal LineEdit dialog, normalize the entered value, preserve the anchor payload, and select the renamed anchor.
2 Remove (0,0) copy Keep only the small passive ring/cross at the canonical origin.
3 Inspector background #262c39 Add ui.colors.inspector_background to prop_theme.json and a validated accessor.
4 No add-shape hint in Reshape The empty-canvas right-click hint is Arrange-only.
5 Clear hand names Preview items read Left Hand and Right Hand.
6 Remove ineffective frame choice Remove Upper Body/Full Figure and always frame the complete rig plus visible prop.
7 Replace synthetic arm animation Preview Front Pose, Walk Left, and Walk Right; the old control becomes Pause/Resume Walk.
8 Preserve manual arm IK The cyan target remains draggable; starting a drag pauses gait playback first.
9 Hide Material in Attach Group the full Material UI and hide it only in Attach.
10 Remove duplicate Snap Anchors Top-row Snap is the sole snap control and source of truth.
11 Preview checked on Attach entry Every transition into Attach checks and shows the preview; the user may hide it for that Attach session.
12 Make marker visibility meaningful Show Other Markers controls passive origin plus unselected anchor gizmos.
13 Fix clipped Grid/Snap Use container-driven top rows, themed minimum heights, and vertical margins.
14 Remove preview blue border Retain the dark preview surface but use no colored border.

3. Scope and preserved contracts

3.1 In scope

  • PropEditor, PropCanvas, PropAttachPreview, Prop Editor scene layout, and PropTheme UI presentation.
  • Preview-only use of the existing stickman_rig.tscn animation and IK nodes.
  • Regression tests for all new UI state and preview ownership behavior.

3.2 Explicitly out of scope

  • Any Sandbox Stage, PropBlock, spawner, runtime equip/release, physics, collision, terrain, director, or action-queue change.
  • Navigation or actual root translation in the holding preview. Walking is an in-place visual gait check.
  • A new animation asset, animation blending layer, or simultaneous animation/manual ownership of the same hand IK target.
  • .prop schema/version changes. Anchor IDs remain the persisted names; no separate display-name field is added.
  • Persistent UI preferences for preview motion, playback, marker visibility, or preview visibility.
  • Document-wide undo/redo, already tracked by debt item #37.

3.3 Saved-document behavior

Only a successful anchor rename mutates document data. It marks dirty once, arms the existing autosave debounce once, refreshes the inspector/canvas/preview, and is included automatically in ordinary save, Save As, autosave, recovery, and load round trips.

Preview motion, paused state, chosen preview hand, Show Stickman Preview, and Show Other Markers are session-only tool state. Changing them must not mark dirty, arm autosave, or alter serialized shape/anchor data. Hiding the Material section never changes the current material preset.

4. Existing rig and animation constraints

stickman_rig.tscn contains looped walk_left and walk_right clips. The actual StickmanLocomotion.walk_to() path selects a LEFT/RIGHT facing profile and plays walk_right for both directions. LEFT is represented through the rig's facing/mirroring policy, not by playing walk_left during runtime locomotion.

Phase 9c must follow that proven runtime gait rule:

  • Walk Right: RIGHT facing, positive preview wrapper, play walk_right.
  • Walk Left: LEFT facing, keep the solving skeleton positive, mirror PreviewRigRoot, play walk_right.
  • Front Pose: FORWARD facing, player stopped, authored standing targets restored.

The existing walk_left asset is not deleted or modified. The preview must not call StickmanRig.walk_to(): doing so would introduce NavigationServer/map dependencies and unwanted root translation. It directly owns only its private AnimationPlayer.

The existing isolation contract remains mandatory:

  • rig physics processing disabled;
  • action queue stopped and cleared;
  • no terrain following or navigation request;
  • all collision layers/masks disabled;
  • preview prop contains visual CanvasItems only;
  • no reference to Sandbox Stage state.

5. Target files and typed contracts

File Required change
scripts/prop_editor.gd Rename dialog; mode visibility; one snap control; Attach-entry preview default; marker toggle routing; theme/layout application.
scripts/prop_canvas.gd Arrange-only empty hint; origin visibility API; remove origin text.
scripts/prop_attach_preview.gd Replace synthetic motion and Frame/Facing UI with the approved motion state machine; preserve manual IK and wrist attachment.
scripts/prop_theme.gd Add validated inspector background accessor/fallback.
prop_theme.json Add ui.colors.inspector_background: "#262C39".
scenes/prop_editor.tscn Container-driven editor chrome and responsive top-row margins/minimum sizing.
scenes/prop_attach_preview.tscn No structural requirement beyond retaining the scripted PanelContainer shell.
tests/test_phase9c_prop_editor_ux.gd New Phase 9c unit/integration suite.
Existing Prop Editor tests Replace assertions for removed controls and add token/layout regressions where they naturally belong.

No change is required in prop_file.gd, prop_anchor_data.gd, stickman_rig.gd, stickman_locomotion.gd, or stickman_rig.tscn unless implementation uncovers a verified defect independent of this feature. Such a discovery must be reported rather than folded into Phase 9c.

6. Node and layout design

Keep dialogs as direct children of the editor root. Put visible editor chrome under one full-rect VBoxContainer so font and hit-target tokens determine row height instead of fixed 32 px offsets:

PropEditor (Control)
├── EditorChrome (VBoxContainer, full rect)
│   ├── TopRow (HBoxContainer, min height >= button_min_height + margins)
│   │   ├── MenuBar (expand)
│   │   └── TopRightMargin (MarginContainer)
│   │       └── TopRightControls (HBoxContainer)
│   │           ├── Grid label
│   │           ├── Grid SpinBox
│   │           ├── Snap CheckBox
│   │           └── Center Prop Button
│   ├── TopBar (HBoxContainer, themed minimum height)
│   │   ├── Prop Name label
│   │   └── PropNameEdit
│   ├── MainLayout (HSplitContainer, expand/fill)
│   │   ├── CanvasPanel
│   │   │   └── CanvasArea
│   │   │       ├── ModeBar
│   │   │       ├── AttachTools (FlowContainer)
│   │   │       │   ├── Show Stickman Preview
│   │   │       │   └── Show Other Markers
│   │   │       └── CanvasWorkspace (HSplitContainer)
│   │   │           ├── PropCanvas
│   │   │           └── PropAttachPreview
│   │   └── InspectorColumn
│   │       └── InspectorScroll
│   │           └── InspectorList
│   │               ├── MaterialSection
│   │               ├── ShapeSection
│   │               ├── AddSection
│   │               └── AnchorSection
│   └── StatusBar
├── SaveDialog / LoadDialog / ErrorDialog
├── ExitDialog / RecoveryDialog / OffOriginSaveDialog
└── RenameAnchorDialog

Existing unique names remain unique so controller lookup continues through %NodeName. Path-based tests must migrate to unique lookup or the revised hierarchy instead of forcing the old fixed layout to remain.

TopRightMargin uses small vertical margins (nominally 4 px top and bottom). Grid SpinBox, Snap CheckBox, Center Prop, and both top rows use at least PropTheme.button_min_height(). The layout must remain unclipped at the committed 18–22 px font tokens and when a custom theme raises those font sizes. Attach tools use a FlowContainer so the two longer kid-facing labels wrap instead of extending outside a narrow canvas pane.

7. Theme and surface styling

Extend both PropTheme.DEFAULT_CONFIG and the committed JSON:

{
  "ui": {
    "colors": {
      "inspector_background": "#262C39"
    }
  }
}

Add:

func inspector_background_color() -> Color:
    return color("ui.colors.inspector_background", Color("#262C39"))

The InspectorColumn panel style derives its background only from this accessor. Keep the subtle theme border and split grabber so the adjustable inspector boundary remains legible.

PropAttachPreview retains its dark surface but sets all border widths to zero. It must not use the accent color as a frame border. Pane separation is supplied by the split grabber and the contrast between preview and inspector backgrounds.

8. Anchor rename workflow

8.1 Dialog contract

Add typed state:

var _rename_anchor_dialog: ConfirmationDialog
var _rename_anchor_edit: LineEdit
var _rename_anchor_source_id: String = ""

Rename is enabled only for a selected editable non-origin anchor. Pressing it:

  1. stores the selected ID in _rename_anchor_source_id;
  2. fills the LineEdit with that ID;
  3. opens the dialog;
  4. defers focus and selects all text so typing replaces the old name.

The dialog title is Rename Anchor, confirmation text is Rename, and cancel closes without mutation. The confirm button is disabled when the trimmed entry is empty.

8.2 Commit algorithm

On confirm:

var remaining := _anchors.duplicate(true)
var value := remaining[_rename_anchor_source_id].duplicate(true)
remaining.erase(_rename_anchor_source_id)
var new_id := PropAnchorData.normalize_id(_rename_anchor_edit.text, remaining)
  • Human input is normalized with the existing lowercase-snake-case rules.
  • A collision receives the existing deterministic _2, _3, ... suffix.
  • Renaming to the same normalized ID is a no-op: do not dirty or autosave.
  • Otherwise insert the unchanged payload at new_id, normalize anchors once, replace selection, update canvas selection, mark dirty once, refresh the inspector, and sync the preview once.
  • Position, role, allowed hands, rotation, and Z offset must remain byte/value equivalent.
  • Clear _rename_anchor_source_id on confirmed commit, cancel, hide, New, and load so a stale dialog cannot rename another document's anchor.

No display_name field is introduced. The normalized ID displayed in the list is the saved ID.

9. Marker and canvas behavior

9.1 Passive origin marker

Add to PropCanvas:

var _show_origin_marker: bool = true

func set_show_origin_marker(enabled: bool) -> void:
    _show_origin_marker = enabled
    queue_redraw()

_draw_origin_marker() retains its constant-screen-size dark halo, ring, and cross, but removes the font lookup and (0,0) draw_string() call. It remains non-interactive and is drawn at world Vector2.ZERO in Arrange, Reshape, and Attach whenever _show_origin_marker is true.

9.2 Show Other Markers

The Attach toolbar contains one default-on Show Other Markers CheckBox. Its callback routes the same value to:

_canvas.set_show_inactive_anchors(enabled)
_canvas.set_show_origin_marker(enabled)

The anchor overlay continues to draw and hit-test the selected editable anchor when false while hiding every unselected editable anchor. The origin marker follows the setting in all modes, including after leaving Attach. The setting is session-only and is not reset by switching modes, New, load, or save.

9.3 Empty-canvas hint

Draw Right-click to add a shape only when all are true:

  • shapes.is_empty();
  • mode is EditMode.TRANSFORM (Arrange);
  • the canvas font is available.

Reshape and Attach show no add-shape hint. The origin marker is evaluated independently afterward.

10. Inspector and mode synchronization

Wrap the existing Material title, preset OptionButton, mass, friction, and bounce labels in _material_section: VBoxContainer.

Mode visibility is authoritative in _sync_mode_controls(mode):

Section/control Arrange Reshape Attach
MaterialSection visible visible hidden
ShapeSection selected shape only selected shape only hidden
AddSection / Erase Shape visible hidden hidden
AnchorSection / AttachTools hidden hidden visible
Holding Preview hidden hidden visible when checked

Delete _snap_anchors_check, _on_snap_anchors_toggled(), and the reverse synchronization from _on_top_snap_toggled(). The existing top Snap CheckBox remains the only writer of PropCanvas.set_snap_enabled(). Anchor drag already consumes the canvas's shared snap state and grid size; this behavior must not be duplicated in the controller.

Track the last canvas mode. On a real transition from Arrange/Reshape into Attach:

_show_preview_check.set_pressed_no_signal(true)

then show/sync the preview. Toggling it off while already in Attach stays off until the user leaves and re-enters Attach. Repeated inspector refreshes or document updates while remaining in Attach must not force it back on.

11. Holding-preview UI

The dynamic preview hierarchy becomes:

PropAttachPreview
└── VBoxContainer
    ├── Header (FlowContainer)
    │   ├── Holding Preview title
    │   ├── HandOption: Left Hand / Right Hand
    │   └── MotionOption: Front Pose / Walk Left / Walk Right
    ├── HintLabel
    ├── SubViewportContainer
    └── Footer (FlowContainer)
        ├── WalkPlaybackButton: Pause Walk / Resume Walk
        ├── Reset Arm
        └── Reset View

Remove _facing_option, _frame_option, FrameMode, and _animate_check. The initial hand remains Right Hand. The initial motion is Front Pose. The control hit rectangles continue to use the full themed minimum height and MOUSE_FILTER_STOP.

The motion choice is a preview tool preference: set_document() does not reset it, so authors can compare multiple props in the same walk. Opening a fresh editor instance begins at Front Pose. New/load do not serialize or dirty this state.

12. Preview motion and IK ownership state machine

12.1 Typed state/API

enum PreviewMotion { FRONT_POSE, WALK_LEFT, WALK_RIGHT }

var _motion: PreviewMotion = PreviewMotion.FRONT_POSE
var _walk_paused: bool = true
var _manual_arm_override: bool = false
var _hand_target_rests_local: Dictionary = {}

func set_preview_motion(motion: int) -> void
func set_walk_paused(paused: bool) -> void
func toggle_walk_playback() -> void
func reset_arm_pose() -> void

Standing hand target rest values are captured as each Marker2D's local position, never as global positions. Animation tracks also write these local positions, so reset remains correct under the LEFT preview-wrapper mirror.

12.2 State transitions

stateDiagram-v2
    [*] --> FrontPose
    FrontPose --> WalkPlaying: choose Walk Left / Walk Right
    WalkPlaying --> WalkPaused: Pause Walk
    WalkPlaying --> WalkPaused: begin cyan-target drag
    WalkPaused --> WalkPlaying: Resume Walk
    WalkPaused --> FrontPose: choose Front Pose
    WalkPlaying --> FrontPose: choose Front Pose
    WalkPlaying --> WalkPlaying: switch walk direction
    WalkPaused --> WalkPlaying: switch walk direction (restart selected gait)

Exact transition behavior:

  • Front Pose entry: stop AnimationPlayer with reset, set FORWARD profile/wrapper, restore all standing target local positions, enable arm IK, set _walk_paused = true, clear manual override, update marker/prop, and reframe.
  • Walk Left/Right entry: clear manual override, apply the requested profile/wrapper, enable arm IK, start walk_right from time 0.0, set _walk_paused = false, update button copy, and reframe. Changing between walk directions restarts at 0.0 for deterministic pose and mirror state.
  • Pause: call AnimationPlayer pause without resetting targets or playhead. The currently solved pose freezes; button becomes Resume Walk.
  • Resume: resume the current walk clip from its saved playhead, clear manual override, and change the button to Pause Walk.
  • Manual drag begin: if walking, pause first; then set manual override true and allow the selected hand target to be clamped/dragged. Mouse release ends pointer capture but intentionally leaves walking paused and preserves the authored inspection pose.
  • Reset Arm in Front: restore both hand targets from local standing rests, clear manual override, remain stopped.
  • Reset Arm during a paused walk: re-seek/reapply the current animation playhead so the dragged target returns to the frozen gait pose, pause again, clear manual override, and keep the same direction/playhead.
  • Reset Arm during a playing walk: first pause, then apply the paused-walk rule.
  • Hand selection/forced compatibility change: keep the current motion direction and playback state, clear any manual override by reapplying the current front/gait pose, switch the cyan marker and prop binding, and do not restart a playing clip unless reapplication requires it.

Pause Walk is disabled in Front Pose and carries a tooltip such as Choose Walk Left or Walk Right to preview walking. Reset Arm remains enabled when a compatible grip is visible.

12.3 Frame ownership and prop following

flowchart LR
    Motion[Preview Motion selector] --> Player[Private AnimationPlayer]
    Player --> Targets[IK target Marker2D positions]
    Drag[Cyan target drag] -->|pauses player, then owns selected target| Targets
    Targets --> IK[TwoBoneIK solve]
    IK --> Wrist[Solved lower-arm visual endpoint]
    Wrist --> Solve[PropAnchorData.solve_attachment_transform]
    Solve --> Prop[Full multi-shape preview prop]

There is exactly one writer for the selected hand target at a time. Phase 9c deliberately does not blend manual offsets additively over a moving animation. This avoids frame-order fights between AnimationPlayer and pointer input.

Every preview frame still updates the cyan marker and derives the held prop transform from _solved_wrist_transform(), never directly from the IK target. It therefore follows arm swing, paused gait, and manual IK poses. Direction changes retain the positive-scale solver/wrapper mirror strategy and never mutate stored anchor coordinates.

13. Camera behavior

Delete the upper-body filtering branch. _reframe_camera() always unions:

  • every visible rig polygon/line, including legs;
  • every visible authored prop polygon/line/circle visualization.

Grow the union by the existing safe margin, center the Camera2D, and fit it to the viewport. Reset View calls this one function. Wheel zoom and middle-mouse pan remain unchanged. Resizing the preview pane may refit as it does today. The removed frame choice carries no persisted state.

14. Signals and update flow

No public document signal payload changes:

signal shape_changed(shapes: Array[Dictionary])
signal anchors_changed(anchors: Dictionary)
signal anchor_selected(anchor_id: String)

Rename flow:

Rename button
  -> RenameAnchorDialog + focused LineEdit
  -> normalize entered ID against anchors excluding old ID
  -> replace dictionary key, retain payload
  -> canvas.set_anchor_data + canvas.select_anchor
  -> mark dirty once
  -> refresh anchor inspector
  -> preview.set_document

Preview controls communicate internally through direct typed methods because the preview owns the complete private visualization. No global signal bus or runtime component is introduced.

15. Edge cases

  • No selected editable anchor: Rename remains disabled and cannot open a stale source ID.
  • Rename entry empty/whitespace: confirmation disabled.
  • Rename normalizes to current ID: no-op and no dirty event.
  • Rename collides after normalization: deterministic numeric suffix; no existing anchor replaced.
  • Rename while autosave countdown exists: one ordinary document mutation resets the debounce.
  • Origin: never appears in the editable list and cannot be renamed.
  • No shapes in Reshape: blank grid plus optional origin marker; no add-shape hint.
  • Show Other Markers off with no selection: all editable markers and origin are hidden.
  • Show Other Markers off with selection: selected marker and its rotation dot remain visible and hit-testable; all others and origin are hidden.
  • Preview hidden, then Attach exited/re-entered: re-entry checks and shows it again.
  • Attach document has no compatible grip: preview shows its existing selection guidance; gait controls may animate the rig, but prop and cyan hold target remain unavailable as appropriate.
  • Hand restriction forces another hand during a paused/manual pose: current preview pose is reapplied before binding the new hand, preventing stale global target placement.
  • Walk paused at loop boundary: resuming remains looped and does not restart unless direction changes.
  • Prop extends far beyond the figure: full framing includes it; no separate frame selector.
  • Custom large UI fonts: top controls grow through containers instead of clipping.
  • Invalid/missing new theme token: accessor returns exact #262C39 fallback and warns only under the existing PropTheme rules.

16. Automated validation

Add tests/test_phase9c_prop_editor_ux.gd as a headless SceneTree suite and update affected existing suites. Assertions must verify state and values, not only node counts.

16.1 Rename

  1. Rename opens a ConfirmationDialog with the current ID selected in a focused LineEdit.
  2. Empty/whitespace input disables confirmation.
  3. Main Handle becomes main_handle and preserves every anchor payload field.
  4. A collision becomes main_handle_2 without replacing the existing anchor.
  5. Same-normalized-name commit emits no dirty mutation.
  6. Successful rename updates list selection, canvas selection, preview selected ID, save output, and load round trip.
  7. Cancel/New/load clears pending rename source and does not mutate the document.

16.2 Canvas, modes, and markers

  1. Origin drawing has no (0,0) string path and remains present by default in all three modes.
  2. Show Other Markers false hides origin and unselected anchors in draw/hit-test while preserving selected anchor interaction.
  3. Arrange alone shows the empty-canvas right-click hint.
  4. Material is visible in Arrange/Reshape and hidden in Attach without changing preset.
  5. Attach toolbar contains no Snap Anchors control; top Snap still snaps anchor dragging by the configured Grid value.
  6. A real transition into Attach checks/shows preview; ordinary refresh while hidden does not re-check it; leaving and re-entering does.

16.3 Theme/layout

  1. Default and committed theme return exact inspector background #262C39.
  2. Custom valid override works; invalid value falls back under current validation.
  3. Inspector surface consumes the accessor.
  4. Preview panel border widths are all zero.
  5. Top rows and Grid/Snap controls meet themed minimum height after layout; their visible rects are not clipped by parents.
  6. Inspector and preview remain adjustable through the existing split panes.

16.4 Preview motion and IK

  1. Hand items are exactly Left Hand and Right Hand.
  2. Motion items are exactly Front Pose, Walk Left, and Walk Right; no FrameOption or FacingOption remains.
  3. Initial state is FORWARD Front Pose with stopped player and local standing targets.
  4. Walk Right selects RIGHT and loop-plays the proven gait clip in place.
  5. Walk Left selects LEFT, keeps the solving rig positive, mirrors only PreviewRigRoot, and loop-plays the proven gait clip in place.
  6. Pause preserves current animation position and target values; Resume advances from it.
  7. Direction change restarts deterministically at 0.0.
  8. Starting cyan-target drag pauses gait before writing the selected target and leaves it paused after release.
  9. Reset Arm restores standing local target in Front and the frozen animation sample in Walk.
  10. Switching/forcing hand keeps motion/play state but clears stale manual override.
  11. Held prop continues to map the selected grip exactly to the solved wrist over several walk frames and after a manual drag.
  12. Preview root does not translate during gait; queues stay empty, physics stays disabled, collision layers/masks stay zero, and preview prop children are non-physics CanvasItems.
  13. Reset View frames the complete figure and intersects the visible held-prop bounds.

16.5 Regression commands

Run all Prop Editor suites from AGENTS.md, particularly:

  • tests/test_prop_editor_file.gd
  • tests/test_prop_editor_theme.gd
  • tests/test_prop_editor_canvas.gd
  • tests/test_prop_editor_thumb.gd
  • tests/test_prop_editor_round3.gd
  • tests/test_phase9a_prop_attach.gd
  • tests/test_phase9b_prop_centering.gd

Update old assertions that explicitly expect Animate Arm, FrameOption, FacingOption, Snap Anchors, the blue preview border, or (0,0) label. Do not weaken anchor round-trip, origin canonicalization, wrist alignment, preview isolation, or centering tests.

Run Godot 4.7.1 --headless --check-only for every changed script and git diff --check.

17. Manual acceptance checklist

Launch res://scenes/prop_editor.tscn with F6 and verify:

  1. Select an editable anchor, click Rename, type a new name, and confirm the entered normalized name appears; it no longer appends _renamed automatically.
  2. Rename to a name already in use and confirm a safe numeric suffix is applied.
  3. Cancel Rename and confirm nothing changes; rename/save/reload and confirm persistence.
  4. The passive center marker has no (0,0) text in Arrange, Reshape, or Attach.
  5. Show Other Markers off hides that passive marker and unselected anchors but retains the selected anchor and rotation dot; turn it on and confirm they return.
  6. Inspector background is visibly #262C39 and distinct from canvas/preview.
  7. Empty Arrange shows Right-click to add a shape; empty Reshape does not.
  8. Material controls are hidden in Attach and return unchanged in Arrange/Reshape.
  9. Attach contains only Show Stickman Preview and Show Other Markers; no Snap Anchors duplicate.
  10. Top Snap with a non-default Grid value still snaps an anchor drag.
  11. Hide the preview, leave Attach, and re-enter; it is checked and shown again.
  12. Grid and Snap have comfortable vertical padding and are not clipped at top or bottom.
  13. Resize the outer inspector split and inner preview split in both directions.
  14. Preview has a dark surface without a blue border.
  15. Hand choices read Left Hand and Right Hand.
  16. There is no Upper Body/Full Figure or separate Facing control.
  17. Initial preview is a static Front Pose.
  18. Choose Walk Right and confirm an in-place full-body walking gait while the complete prop follows the holding wrist.
  19. Choose Walk Left and confirm the stickman faces/walks left without inverted limbs and the complete prop still follows the wrist.
  20. Pause Walk freezes the exact pose and changes to Resume Walk; Resume continues rather than jumping to the beginning.
  21. While walking, drag the cyan hand target. Walking pauses first, the arm follows the pointer, the prop follows the solved wrist, and the pose remains paused on release.
  22. Reset Arm restores the appropriate front/frozen-gait arm pose without changing anchor data.
  23. Switch Left Hand/Right Hand and test a both grip plus left-only/right-only restrictions.
  24. Reset View includes the entire figure and visible prop; wheel zoom and middle-pan still work.
  25. Change preview controls and verify the document does not become dirty.
  26. Confirm save/load/autosave/recovery retain shapes, material, and renamed anchors exactly.

18. Architectural trade-offs

Decision Benefit Cost / future work
Separate Phase 9c Preserves completed 9a/9b history and gives behavior changes focused regressions User guide spans another verified phase before its next update
One Preview Motion selector Removes ineffective/ambiguous combinations and is kid-readable Facing is no longer independently previewable without gait
Runtime gait rule, preview in place Matches current gameplay direction/mirror semantics without navigation dependencies Not a terrain, speed, arrival, or root-motion simulation
Pause before manual IK One deterministic owner writes the hand target No live additive hand posing while gait continues
Local rest targets Stable under wrapper mirroring and matches animation track coordinates Existing global-rest code must be replaced carefully
Full figure only One reliable frame and fewer controls Authors cannot request a tighter automatic upper-body crop; wheel zoom remains available
Modal rename Focused, touch-friendly, and hard to trigger accidentally One additional dialog lifecycle must be cleared on document changes
One snap source Eliminates contradictory UI state Attach toolbar no longer exposes snap next to anchors; top control must remain visible
Show Other Markers controls origin too Toggle has visible meaning even with one anchor A setting changed in Attach also affects the passive marker in other modes
Container-driven chrome Adapts to theme/font changes and prevents clipping Scene hierarchy and path-based tests require a targeted update

19. Implementation order and pipeline handoff

  1. Add theme token/accessor and convert editor chrome to responsive containers.
  2. Group mode sections; remove duplicate/obsolete controls; implement marker visibility and hint rules.
  3. Add the rename dialog and document transaction tests.
  4. Replace preview synthetic motion with the exact PreviewMotion/playback/IK state machine.
  5. Add Phase 9c tests, update affected expectations, run all regressions/static checks.
  6. Hand the implementation to the user for Godot Editor verification.

Do not update the user guide or README during implementation handoff. After the user confirms Phase 9c is verified and working, the writer must revise docs/prop_editor_user_guide.md to remove the old automatic Rename, Snap Anchors, Facing, Frame, and Animate Arm instructions and document the approved Phase 9c controls.