From 8a31260ed49eea2f76d82c8237ca1367ee21a150 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Wed, 15 Jul 2026 18:41:52 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20make=20PLAN.md=20true=20=E2=80=94=20its?= =?UTF-8?q?=20header,=20its=20garden=20claim,=20and=20a=20baker=20that=20n?= =?UTF-8?q?o=20longer=20exists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ✅ markers written into PLAN's body are accurate and unusually candid. The prose around them is not. Nothing here changes behaviour. Header said "plan not yet started". Most of it has shipped. It now states what is actually left: F2, F3, G, H, I, K1, K2. "This directory is not a git repository" — false. It is, with history, and as of today it is pushed. For a while the REVERSE hazard applied and nobody could see it: prefab authoring, play-in-editor and terrain painting sat committed on one machine's local main. If a PLAN item says "done", check it is PUSHED. §1.4 called garden "the only game using the runtime loadWorld/instantiateWorld path". Garden references NEITHER, nor bloom/world, nor any .world.json — it predates the world module. `instantiateWorld` has ZERO game consumers: only the editor and the engine's own modules. That is not a nitpick — §C1's acceptance ("garden instantiating a world with water/rivers shows them") was never executable, so the "a river cannot look different in-game than in the editor" guarantee is UNTESTED. §1.4 runtime path, §K2 and §F1's acceptance all targeted `tools/build-world.ts`. That baker and `src/generated/` are DELETED — the shooter loads world JSON at runtime, exactly as §1.6 predicted once the Perry bugs were fixed. So editing a world needs NO rebake and K2 had nothing to hook. K2 is rescoped to the one thing still derived: the terrain VISUAL MESH (bun tools/build-terrain.ts), and only when the heightmap changed. Terrain paint needs no rebake either. §K1: the test that exists is worse than no test, and now says so in both places. testWorldRoundTrip round-trips a SYNTHETIC world with JSON.stringify — it never calls saveWorld, and JSON.stringify is the very idiom quirk #6 says corrupts a parsed graph: the bug that made saveWorld write 0 bytes and report OK. It passes by construction, over the exact hole. Claude-Session: https://claude.ai/code/session_01J1UWgMcrTNvwWeXcJ1T3Fp --- PLAN.md | 33 ++++++++++++++++++++++++++------- src/tests/self-tests.ts | 15 +++++++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/PLAN.md b/PLAN.md index d14e924..872345e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,6 +1,14 @@ # Bloom World Editor — Audit & Completion Plan -**Date:** 2026-07-11 · **Toolchain:** Perry 0.5.1239 · **Status:** audit verified against source; plan not yet started. +**Date:** 2026-07-11 · **Toolchain:** Perry 0.5.1239 · **Status (updated 2026-07-15):** MOSTLY DONE — the ✅ markers through the body are +accurate; this header was not. A/B/C/D/E/F1 and play-in-editor have shipped (and are +now pushed — they sat committed on one machine's local `main` until 2026-07-15). +Still outstanding: **F2** (rename/tint/tags/modelRef editing), **F3** (outliner +depth), **G** (asset thumbnails — `renderAllThumbnails`/`getThumbnail` still have +zero call sites), **H** (recent-projects UI — the write path runs every launch and +nothing reads it), **I** (environment panel — still mutates directly, no +`SetEnvironmentCommand`), **K1** (a REAL round-trip test — see the correction at +§K1) and **K2** (see the correction there — its premise is void). This document is self-contained: it is written for an implementer with no prior context. It replaces the original design plan (which was deleted). Part 1 is a verified audit of the current state, Part 2 defines "done", Part 3 is the work plan. This is a **single unified scope** — do not cut it down to an MVP; if a task has a prerequisite, the prerequisite is in scope too. @@ -16,7 +24,7 @@ This document is self-contained: it is written for an implementer with no prior |---|---| | `../engine/` | Bloom engine: TypeScript API over a Rust/wgpu native library (412 FFI functions listed in `../engine/package.json` under `perry.nativeLibrary.functions`). Shared world module at `../engine/src/world/`. | | `../shooter/` | Arena FPS. Worlds in `assets/worlds/`, editor project file `editor.project.json` at its root. Its `docs/perry-quirks.md` documents historical Perry bugs (now fixed — see §1.6). | -| `../garden/` | 3D collectathon; the only game using the runtime `loadWorld`/`instantiateWorld` path. | +| `../garden/` | 3D collectathon. **Correction 2026-07-15:** the claim that it is *"the only game using the runtime `loadWorld`/`instantiateWorld` path"* is FALSE — garden references neither, nor `bloom/world`, nor any `.world.json` (it predates the world module). **`instantiateWorld` has ZERO game consumers**: only the editor and the engine's own modules. This matters — §C1's acceptance ("garden instantiating a world with water/rivers shows them") was never executable, so the "a river cannot look different in-game than in the editor" guarantee is UNTESTED. | | Perry (installed as `perry` on PATH; source checkout is a sibling of the `bloom/` tree) | The TypeScript→native AOT compiler that builds everything here. | **Build & run:** @@ -29,7 +37,11 @@ PERRY_ALLOW_PERRY_FEATURES=1 perry compile src/main.ts # until task A1 lands Perry 0.5 notes: the CLI is `perry compile ` (there is no `perry build`); it emits `./main` and ignores `perry.toml`'s `out_dir`. Duplicate-symbol `ld` warnings (perry_stdlib vs libbloom_macos both embedding Rust std) are benign — the editor compiles and links clean as of 0.5.1239. The stale `bloom-editor` binary from April 2026 predates all of this. -This directory is **not a git repository** and has no history. Recommended first action: `git init` and commit the current state before changing anything. +~~This directory is **not a git repository** and has no history.~~ **Corrected +2026-07-15:** it is a git repository with history, and as of today it is pushed to +`origin/main`. (For a while the reverse hazard applied: three commits of real work — +prefab authoring, play-in-editor, terrain painting — existed only in this local +clone. If a PLAN item reads "done", check it is *pushed*.) --- @@ -116,7 +128,7 @@ Minor: `label` is imported but unused in `brush-panel.ts` and `environment-panel - `assets/worlds/*.world.json` conform to the schema above and were authored with this editor (shooter's `editor.project.json`: `{ name, gameId, modelsDir: "assets/models", prefabsDir, worldsDir: "assets/worlds", defaultWorld: "arena_01.world.json" }`). `arena_02` ("Outdoor plaza", 32 KB) is the real level: 66 static meshes, 6 water volumes, plus the gameplay entities listed in §1.2(2). - **Gameplay semantics live in `userData`**, discriminated by `userData.kind`: `player_spawn`, `collider_box` (`halfExtents`), `static_mesh` (optional `collider`), `point_light` (`range`,`color`,`intensity`), `enemy_spawner` (`enemyType`,`waveBits`,`maxAlive`,`cooldown`), `weapon_pickup` (`weapon`), `wave_config` (`waves` = escaped-JSON string), `box`. All values are strings. -- **Runtime path:** shooter never calls `loadWorld`. `tools/build-world.ts` (a bun script) bakes a world JSON into flat `export const` arrays at `src/generated/world.ts`, which `src/main.ts` imports. This was a workaround for two Perry 0.4.x runtime bugs (see §1.6 — both now fixed). Editing a world therefore requires a rebake: `bun tools/build-world.ts assets/worlds/arena_02.world.json src/generated/world.ts`. +- **Runtime path (CORRECTED 2026-07-15):** ~~shooter never calls `loadWorld`~~ — it does now. `tools/build-world.ts` and `src/generated/` have both been **DELETED**; `src/world-runtime.ts` reads `assets/worlds/*.world.json` at startup via `loadWorld`. The Perry 0.4.x bugs that forced the bake are fixed, exactly as §1.6 predicted, and the workaround went with them. **There is no rebake step for world data.** What IS still derived: the terrain's *visual mesh* (`bun tools/build-terrain.ts`) — sculpt, save, re-run that one command. Terrain PAINT needs no rebake (the splat uploads at load). - Drawable geometry references GLBs converted offline from Unvanquished assets (`tools/convert-*.ts`, `docs/asset-pipeline.md`). The `_gizmo_box.glb` sentinel is not a file — the baker maps it to "draw a colored box". ### 1.6 Toolchain status (Perry 0.5.1239) @@ -285,7 +297,7 @@ The dividing line, for future schema questions: **lights are engine-universal ### F. Inspector & outliner depth -- **F1. `userData` editor (highest-value item for shooter).** Inspector section listing key/value rows with `textInput` editing, add-row, delete-row. Game-agnostic (free-form strings). Undoable (`SetUserDataCommand`). Acceptance: change an `enemy_spawner`'s `cooldown` in `arena_02`, save, run shooter's `bun tools/build-world.ts`, confirm the value lands in `src/generated/world.ts`. +- **F1. `userData` editor (highest-value item for shooter).** Inspector section listing key/value rows with `textInput` editing, add-row, delete-row. Game-agnostic (free-form strings). Undoable (`SetUserDataCommand`). Acceptance **(corrected 2026-07-15 — the baker is gone):** change an `enemy_spawner`'s `cooldown` in `arena_02`, save, and launch the shooter — the value is live, with no rebake. - **F2.** Entity rename (textInput), tint editing (vec4 field or color widget), tags add/remove, modelRef reassignment via an asset-picker popup. All undoable. - **F3.** Outliner: double-click rename, per-row delete/duplicate, a filter/search box, and sections for entities / water / rivers (per C3). True hierarchy is out of scope (the format has no parenting). @@ -307,8 +319,15 @@ Keep fly-cam as the core mode (matches original scope). Stretch, only if trivial ### K. Shooter round-trip & integration proof -- **K1. Automated lossless round-trip test:** copy both arena JSONs into `src/tests/fixtures/` (or read them from `../shooter` if self-tests can take a path); self-test loads each, saves to a temp path, deep-compares parsed JSON. Must be exact for untouched worlds — any normalization the saver applies is a bug to fix, not a tolerance to add. -- **K2. Bake hook:** ⚠ check whether Perry/Bloom exposes process spawning. If yes: support an optional `postSaveCommand` in `editor.project.json` (shooter would set `bun tools/build-world.ts {world} src/generated/world.ts`) and run it after save, surfacing failures in the status bar. If no: add a status-bar reminder ("world saved — rebake required for shooter") driven by presence of the key, and document the manual command in README. +- **K1. Automated lossless round-trip test:** ⚠️ **STILL OUTSTANDING, and the + test that exists is actively MISLEADING (flagged 2026-07-15).** + `testWorldRoundTrip` in `src/tests/self-tests.ts` round-trips a *synthetic* + 2-entity world with `JSON.stringify` — it never calls `saveWorld`, and + `JSON.stringify` is the very idiom quirk #6 says corrupts a parsed graph (the + bug that made `saveWorld` write 0 bytes and report success). It passes by + construction and covers nothing. A green tick sitting over the exact hole is + worse than no test. The real work, unchanged: copy both arena JSONs into `src/tests/fixtures/` (or read them from `../shooter` if self-tests can take a path); self-test loads each, saves to a temp path, deep-compares parsed JSON. Must be exact for untouched worlds — any normalization the saver applies is a bug to fix, not a tolerance to add. +- **K2. Bake hook:** ⚠ **RESCOPED 2026-07-15 — the original premise is void.** It targeted `bun tools/build-world.ts`, a baker that **no longer exists**: the shooter loads world JSON at runtime, so editing a world needs **no rebake at all** and there is nothing to hook for it. Process spawning was also settled meanwhile (Perry's spawn is a no-op; the engine's `launchProcess` works, and play-in-editor already uses it). What survives is much smaller: the terrain **visual mesh** is still derived (`bun tools/build-terrain.ts`), so a `postSaveCommand` would be worth it *only* for a world whose heightmap changed. Everything else — entities, lights, water, rivers, and terrain paint — is live on the next launch with no command. - **K3. Companion note (shooter repo, optional, not this codebase):** with the Perry bugs fixed (§1.6), shooter can migrate to runtime `loadWorld` + `instantiateWorld` like garden and delete the baker — or keep baking for startup performance. Decision belongs to shooter; the editor only owes lossless files + the hook above. - **Final acceptance walkthrough:** open shooter project → arena_02 fully visible/editable → move a pickup, edit a spawner's userData, add a water volume, save → round-trip test green → rebake → shooter runs with the changes. diff --git a/src/tests/self-tests.ts b/src/tests/self-tests.ts index c6fd9a1..4cfc669 100644 --- a/src/tests/self-tests.ts +++ b/src/tests/self-tests.ts @@ -66,6 +66,21 @@ export function runSelfTests(): number { return failed; } +/// ⚠️ THIS TEST DOES NOT TEST THE SAVE PATH. Read before trusting its green tick. +/// +/// It builds a SYNTHETIC 2-entity world and round-trips it with `JSON.stringify` +/// — which is (a) not what saving does (`saveWorld` → `serialize.ts` hand-writes +/// the text) and (b) the exact idiom `docs/perry-quirks.md` #6 says CORRUPTS a +/// parsed graph, i.e. the bug that made `saveWorld` write 0 bytes and report OK. +/// +/// So this passes by construction on a small fresh object, while covering none of +/// the thing that actually broke. That is worse than no test: it is a green check +/// over the exact hole. +/// +/// PLAN §K1 is the real work and is still OUTSTANDING: load +/// `../shooter/assets/worlds/arena_01|02.world.json`, save them through +/// `saveWorld`, and deep-compare. Per §K1's own wording, any normalisation the +/// saver applies is a bug to fix, not a tolerance to add. No fixture exists yet. function testWorldRoundTrip(): void { const world = createEmptyWorld('test', 'Test World'); world.entities.push(createEntity('ent_1', 'models/tree.glb', [5, 0, 3]));