Phase 14 - Organized directories.

This commit is contained in:
2026-10-03 19:13:39 -04:00
parent 6666173c4f
commit d950fd9d9a
307 changed files with 1452 additions and 1110 deletions
+638
View File
@@ -0,0 +1,638 @@
# 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/editors/prop/prop_editor.gd` | Rename dialog; mode visibility; one snap control; Attach-entry preview default; marker toggle routing; theme/layout application. |
| `scripts/editors/prop/prop_canvas.gd` | Arrange-only empty hint; origin visibility API; remove origin text. |
| `scripts/editors/prop/prop_attach_preview.gd` | Replace synthetic motion and Frame/Facing UI with the approved motion state machine; preserve manual IK and wrist attachment. |
| `scripts/editors/prop/prop_theme.gd` | Add validated inspector background accessor/fallback. |
| `prop_theme.json` | Add `ui.colors.inspector_background: "#262C39"`. |
| `scenes/editors/prop/prop_editor.tscn` | Container-driven editor chrome and responsive top-row margins/minimum sizing. |
| `scenes/editors/prop/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:
```text
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:
```json
{
"ui": {
"colors": {
"inspector_background": "#262C39"
}
}
}
```
Add:
```gdscript
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:
```gdscript
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:
```gdscript
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`:
```gdscript
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:
```gdscript
_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:
```gdscript
_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:
```text
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
```gdscript
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
```mermaid
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
```mermaid
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:
```gdscript
signal shape_changed(shapes: Array[Dictionary])
signal anchors_changed(anchors: Dictionary)
signal anchor_selected(anchor_id: String)
```
Rename flow:
```text
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
8. Origin drawing has no `(0,0)` string path and remains present by default in all three modes.
9. Show Other Markers false hides origin and unselected anchors in draw/hit-test while preserving
selected anchor interaction.
10. Arrange alone shows the empty-canvas right-click hint.
11. Material is visible in Arrange/Reshape and hidden in Attach without changing preset.
12. Attach toolbar contains no Snap Anchors control; top Snap still snaps anchor dragging by the
configured Grid value.
13. 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
14. Default and committed theme return exact inspector background `#262C39`.
15. Custom valid override works; invalid value falls back under current validation.
16. Inspector surface consumes the accessor.
17. Preview panel border widths are all zero.
18. Top rows and Grid/Snap controls meet themed minimum height after layout; their visible rects
are not clipped by parents.
19. Inspector and preview remain adjustable through the existing split panes.
### 16.4 Preview motion and IK
20. Hand items are exactly `Left Hand` and `Right Hand`.
21. Motion items are exactly `Front Pose`, `Walk Left`, and `Walk Right`; no FrameOption or
FacingOption remains.
22. Initial state is FORWARD Front Pose with stopped player and local standing targets.
23. Walk Right selects RIGHT and loop-plays the proven gait clip in place.
24. Walk Left selects LEFT, keeps the solving rig positive, mirrors only PreviewRigRoot, and
loop-plays the proven gait clip in place.
25. Pause preserves current animation position and target values; Resume advances from it.
26. Direction change restarts deterministically at `0.0`.
27. Starting cyan-target drag pauses gait before writing the selected target and leaves it paused
after release.
28. Reset Arm restores standing local target in Front and the frozen animation sample in Walk.
29. Switching/forcing hand keeps motion/play state but clears stale manual override.
30. Held prop continues to map the selected grip exactly to the solved wrist over several walk
frames and after a manual drag.
31. 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.
32. 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/editors/prop/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/guides/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.