Area editor: drag, add, delete, and save edits #63

Merged
Nimby merged 1 commit from task/58-editor-editing into main 2026-10-05 00:04:06 -04:00
Collaborator

Closes #58

Editing

  • Drag stands, items, and the player spawn on the top-down canvas (X/Z, clamped inside the walls). The inspector edits exact X/Y/Z.
  • Palette + Add stand / Add item modes. One stand per type is enforced in the UI and on the server.
  • New items are placed at floor height with a suggested name {Type}Item{N}, N = one past the highest existing suffix (a bare CrownItem counts as 0), so nothing already saved is renumbered. The name is editable before adding and read-only after, because the save file keys placed items by node name.
  • Delete for the selected stand/item, with a confirm.

Beyond #58's list (small, and called out here): the spawn marker is drawn and draggable, since it's where the render-preview camera sits; and Delete exists so a misplaced add doesn't mean hand-editing JSON.

Server

  • PUT /api/area/<name> validates (required keys; catalog types; unique stand per type; non-empty, unique, [A-Za-z0-9_] item names; finite 3-number positions), then runs generate_area.build() in-process, so a saved descriptor always generates. Writes atomically with indent=2. A 400 lists every problem and writes nothing.
  • The UI chains saves so they land in order. On a rejected save, it reloads the last saved version and shows the problems. Render and Generate wait for pending saves first.
  • Canvas math moved to static/editor_logic.js, a pure module that can also be require()d from Node.

Verified in the sandbox

  • Node checks of editor_logic.js: layout round-trip, hit-testing (spawn > items > stands), name suggestions (SwordItem1..3 gives SwordItem4, CrownItem gives CrownItem1, a gap 1,7 gives 8, none gives 1, coin_pile gives CoinPileItem1), clamping, resting height.
  • curl: each of 10 bad PUTs returns 400/404 and leaves the file byte-identical; a valid PUT round-trips and still generates.
  • Headless Chromium over the DevTools protocol, with real mouse events: dragged an item (X/Z saved, Y and name unchanged), added a crown via the palette (CrownItem1, y=0.15, name read-only afterwards), duplicate stand refused, Y edit, spawn drag, delete with the confirm dialog, server rejection reverting the canvas, no console errors. I reviewed the screenshots; then rendered the edited area, and the dragged sword appears in the real Godot frame where it was placed on the canvas.

Still to check on a real machine: how dragging feels, and readability on your own display.

🤖 Generated with Claude Code

Closes #58 **Editing** - Drag stands, items, and the **player spawn** on the top-down canvas (X/Z, clamped inside the walls). The inspector edits exact X/Y/Z. - Palette + **Add stand** / **Add item** modes. One stand per type is enforced in the UI and on the server. - New items are placed at floor height with a suggested name `{Type}Item{N}`, N = one past the highest existing suffix (a bare `CrownItem` counts as 0), so nothing already saved is renumbered. The name is editable before adding and read-only after, because the save file keys placed items by node name. - **Delete** for the selected stand/item, with a confirm. **Beyond #58's list** (small, and called out here): the spawn marker is drawn and draggable, since it's where the render-preview camera sits; and Delete exists so a misplaced add doesn't mean hand-editing JSON. **Server** - `PUT /api/area/<name>` validates (required keys; catalog types; unique stand per type; non-empty, unique, `[A-Za-z0-9_]` item names; finite 3-number positions), then runs `generate_area.build()` in-process, so a saved descriptor always generates. Writes atomically with `indent=2`. A 400 lists every problem and writes nothing. - The UI chains saves so they land in order. On a rejected save, it reloads the last saved version and shows the problems. Render and Generate wait for pending saves first. - Canvas math moved to `static/editor_logic.js`, a pure module that can also be `require()`d from Node. **Verified in the sandbox** - Node checks of `editor_logic.js`: layout round-trip, hit-testing (spawn > items > stands), name suggestions (`SwordItem1..3` gives `SwordItem4`, `CrownItem` gives `CrownItem1`, a gap 1,7 gives 8, none gives 1, `coin_pile` gives `CoinPileItem1`), clamping, resting height. - `curl`: each of 10 bad PUTs returns 400/404 and leaves the file byte-identical; a valid PUT round-trips and still generates. - Headless Chromium over the DevTools protocol, with real mouse events: dragged an item (X/Z saved, Y and name unchanged), added a crown via the palette (`CrownItem1`, y=0.15, name read-only afterwards), duplicate stand refused, Y edit, spawn drag, delete with the confirm dialog, server rejection reverting the canvas, no console errors. I reviewed the screenshots; then rendered the edited area, and the dragged sword appears in the real Godot frame where it was placed on the canvas. **Still to check on a real machine:** how dragging feels, and readability on your own display. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Stands, items and the player spawn can be dragged on the top-down canvas,
with exact X/Y/Z editable in an inspector. A palette adds stands and items;
new items get a suggested {Type}Item{N} name (one past the highest existing
suffix, so nothing already saved is renumbered) and their names are
read-only afterwards, since GameProgress keys saves by item node name.
Selected stands/items can be deleted.

Edits save via a new PUT /api/area/<name>, which validates the descriptor
(catalog types, unique valid item names, one stand per type, finite
positions) and then runs generate_area.build() in-process so a saved
descriptor always generates. Writes are atomic; rejected saves change
nothing and the editor reloads the last saved version. Pure canvas logic
moved to editor_logic.js so it can be tested in Node.

Closes #58

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nimby merged commit cd0e2baf06 into main 2026-10-05 00:04:06 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
scruffydude/project-awry!63
No description provided.