Area editor: load Armory + Treasury and apply edits back to the game #69

Merged
Nimby merged 1 commit from task/68-real-areas into main 2026-10-05 10:23:24 -04:00
Collaborator

Closes #68

Extraction

  • extract_area.py turns a shipped .tscn into a descriptor, copying item and stand node names exactly (saves are keyed by item node name) and merging the item types into the catalog, which now has all 14.
  • It's strict: after extracting, it regenerates and compares section by section, numbers by value. Any difference means nothing is written.
  • New tscn_textlib helpers: parse_tscn, compare_scenes (matched by node path/resource id), and load_steps_problem.

One-time rewrite of armory.tscn / treasury.tscn

  • So later applies give minimal diffs, both scenes are regenerated from their descriptors here. Changes are number formatting only (0.700 to 0.7, 1.0 to 1: 30 lines in Armory, 1 in Treasury), plus Armory's load_steps header 46 to 45. It had been one too high since the pedestal-color commit; there are 45 resources.
  • Proof: zero semantic differences, all 47 + 5 item names and 9 + 5 stand names identical, clean headless boots, and before/after renders pixel-identical. Renders were first confirmed deterministic by rendering the same scene twice. No gameplay change, so no playtest release is needed.

Apply to game (editor + POST /api/apply)

  • A dry run lists items/stands moved, added or removed, the spawn, and any other changes. Nothing is written until Confirm.
  • The write is atomic, followed by a headless boot check; on any Godot error the previous scene is restored byte-identical.
  • Drift guard: the descriptor's scene_sha256 must match the scene. A hand edit or a pull that changed the scene blocks applying (409) with a banner; Re-extract (POST /api/reextract, confirm required) recovers.
  • generate_area.py --mode full does the same from the CLI, with the drift check.
  • scene_path/scene_sha256 are server-owned; a browser save can't change them. _sample_room has no scene, so it stays preview-only.

Verified (headless Chromium against a scratch clone)

  • _sample_room shows as preview-only.
  • In Armory, dragged SwordItem1; the dry run listed exactly that one move; after Confirm the boot check passed and git diff armory.tscn was a single transform line; the area stays in sync; a second apply says "Nothing to apply".
  • Hand-edited treasury.tscn: warning shown, Apply disabled, API returns 409, and the hand edit is untouched. Re-extract picked it up (CrownItem y=1.4) and the area is back in sync.
  • Rendered Armory from the editor.
  • A forced boot failure restored the scene byte-identical with scene_sha256 unchanged.
  • A tampered PUT of scene_sha256/scene_path was ignored.

🤖 Generated with Claude Code

Closes #68 **Extraction** - `extract_area.py` turns a shipped `.tscn` into a descriptor, copying item and stand node names exactly (saves are keyed by item node name) and merging the item types into the catalog, which now has all 14. - It's strict: after extracting, it regenerates and compares section by section, numbers by value. Any difference means nothing is written. - New `tscn_textlib` helpers: `parse_tscn`, `compare_scenes` (matched by node path/resource id), and `load_steps_problem`. **One-time rewrite of `armory.tscn` / `treasury.tscn`** - So later applies give minimal diffs, both scenes are regenerated from their descriptors here. Changes are number formatting only (`0.700` to `0.7`, `1.0` to `1`: 30 lines in Armory, 1 in Treasury), plus Armory's `load_steps` header 46 to 45. It had been one too high since the pedestal-color commit; there are 45 resources. - Proof: zero semantic differences, all 47 + 5 item names and 9 + 5 stand names identical, clean headless boots, and **before/after renders pixel-identical**. Renders were first confirmed deterministic by rendering the same scene twice. No gameplay change, so no playtest release is needed. **Apply to game** (editor + `POST /api/apply`) - A dry run lists items/stands moved, added or removed, the spawn, and any other changes. Nothing is written until Confirm. - The write is atomic, followed by a headless boot check; on any Godot error the previous scene is restored byte-identical. - Drift guard: the descriptor's `scene_sha256` must match the scene. A hand edit or a pull that changed the scene blocks applying (409) with a banner; **Re-extract** (`POST /api/reextract`, confirm required) recovers. - `generate_area.py --mode full` does the same from the CLI, with the drift check. - `scene_path`/`scene_sha256` are server-owned; a browser save can't change them. `_sample_room` has no scene, so it stays preview-only. **Verified** (headless Chromium against a scratch clone) - `_sample_room` shows as preview-only. - In Armory, dragged SwordItem1; the dry run listed exactly that one move; after Confirm the boot check passed and `git diff armory.tscn` was a single `transform` line; the area stays in sync; a second apply says "Nothing to apply". - Hand-edited `treasury.tscn`: warning shown, Apply disabled, API returns 409, and the hand edit is untouched. Re-extract picked it up (CrownItem y=1.4) and the area is back in sync. - Rendered Armory from the editor. - A forced boot failure restored the scene byte-identical with `scene_sha256` unchanged. - A tampered PUT of `scene_sha256`/`scene_path` was ignored. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
extract_area.py converts a shipped scene into a descriptor (exact node
names, so saves keyed by item name stay valid) and merges its item types
into the catalog, which now has all 14. It refuses any scene the
generator can't reproduce: after extracting it regenerates and compares
section by section, numbers by value.

Both shipped scenes are regenerated from their descriptors once here, so
later applies give minimal diffs. The rewrite is number formatting only
(0.700 -> 0.7, 1.0 -> 1) plus Armory's load_steps header (46 -> 45, the
real resource count); node names are identical and before/after renders
are pixel-identical.

The editor gains Apply to game: a dry-run summary, an atomic write, a
headless boot check that restores the previous scene on any error, and a
scene_sha256 drift guard so a stale descriptor can never overwrite newer
scene changes (Re-extract recovers). generate_area.py gains --mode full
with the same drift check. Browser saves can't change scene_path or
scene_sha256.

Closes #68

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nimby merged commit 23b4bd8282 into main 2026-10-05 10:23:24 -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!69
No description provided.