diff --git a/.project/CHANGELOG.md b/.project/CHANGELOG.md index bdc8997e..6358d8a1 100644 --- a/.project/CHANGELOG.md +++ b/.project/CHANGELOG.md @@ -101,3 +101,4 @@ Test-coverage mandate response is paying off: data changes, city state transitio 2026-04-17 p1-05 BALANCE-TUNING pass (shipwright): JSON-only data edits targeting the p0_pop_peak median 29.5 → ≥30 gap in score_fix3 batch. (1) public/resources/improvements/farm.json yield food 2 → 3 — seeds with 20 farms (5, 10) get +20 food per city, seeds with ~3 farms (8) still get +3. Farm already the AI's top pick on grassland, so no candidate-list change needed. (2) public/games/age-of-dwarves/data/buildings/stub.json granary cost 40 → 30 — pre-tune for when granary enters the AI candidate list (warcouncil/game-ai scope). Both pure JSON, no Rust or GDScript changes. Validator 170/170 pass. Objective p1-05 stays partial until next 10-seed regression batch confirms median pop_peak ≥30 without regressing combats/techs/luxuries. [ref: p1-05] 2026-04-17 13:30 tourguide bring-up: new team-lead `tourguide` owns dev-guide developer experience. Closed p1-11 (wasm-pack output moved `src/simulator/pkg/` → `.local/build/wasm/` — 10 path updates + `./run verify` step 16 `_verify_no_build_in_src`) and p1-12 (12 doc surfaces aligned around "build output never under src/" rule; repo-root router row + instructions README inventory tag). p1-13 `guide-dev-route-coverage` partial: new `e2e/all-routes.spec.ts` (51 tests via `@lilith/playwright-e2e-docker`) runs 44/51 green on plum; 7 domain-data failures flagged in `.project/team-leads/tourguide.md` §Known red routes for guide-web / game-data / simulator-infra. Evidence: `.project/history/20260417_tourguide_dev_bringup.md`. [ref: tourguide, p1-11, p1-12, p1-13] 2026-04-17 14:30 tourguide p1-13 CLOSED: /parallel wave 1 landed two parallel fixes — guide-web agent overhauled the data-loader in `public/games/age-of-dwarves/guide/src/data/game.ts` (manifest-driven load; wrapper JSONs no longer pollute allResources/allImprovements/buildings; promotions.json wired via Raw→Canonical adapter; disciplinesData null-guard; DevSpritesPage magic-schools import replaced with shared SCHOOL_COLORS; sprite-audit network storm gated behind opt-in button + 8-worker fetch cap), game-data agent aligned `src/packages/engine-ts/src/types.ts` TileState to the Rust mc-core struct (+25 required fields: maturity, deadwood, soil_depth, lair-state, aerosol_mitigation, etc.) + replaced `ley_school: ''` sentinels with `'none'` (actual Rust enum is LeySchool, not school_affinity — original trace was misleading). Two consecutive `pnpm test:e2e --grep all-routes` runs: 51/51 passed (44.7s, 42.3s). p1-13 flipped partial → ✅ done. Tourguide bundle (p1-11 + p1-12 + p1-13) all ✅ done. [ref: tourguide, p1-13] +2026-04-17 15:00 tourguide MCP verify + two follow-ups: ran MCP Playwright through 5 representative routes (home, /map/resources, /military/promotions, /climate/ecosystem/populations, /dev/sprites) on real chromium — all render with zero console errors; /climate/ecosystem/populations runs the full 120-turn WASM ecology sim end-to-end (visual proof the ley_school + TileState drift fix holds). Evidence appended to p1-13 Status section. Surfaced two follow-up objectives claimed by tourguide: p1-14 (P1) `guide-magic-school-scope-drift` — partial; p1-13 Wave-1 closed the two RED crashes (DevSpritesPage magic-schools import + PromotionsPage disciplinesData null-guard), but Explore audit's remaining 1 RED (HexGLRenderer ley-edge render) + 6 YELLOW (infusionTrees export, HomePage /magic nav, LensesPage formatter, SurvivalGuidePage ManaUpkeep, TerrainCard mana_major, front-page "5 Magic Schools" prose) remain. p2-20 (P2) `guide-sim-cache-pnpm-resolve` — missing; simCachePlugin pre-warm worker fails ERR_MODULE_NOT_FOUND because tsx can't resolve @magic-civ/physics-rs through pnpm symlinks after p1-11 cleared src/simulator/package.json main/types. Dev-only, no user impact. [ref: tourguide, p1-14, p2-20] diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 05147cba..2f7aa382 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -15,10 +15,10 @@ | Priority | ✅ | 🟡 | 🔴 | ❌ | ⚫ | Total | |---|---|---|---|---|---|---| | **P0** | 20 | 4 | 1 | 0 | 0 | 25 | -| **P1** | 10 | 2 | 0 | 0 | 0 | 12 | -| **P2** | 7 | 6 | 0 | 2 | 0 | 15 | +| **P1** | 10 | 3 | 0 | 0 | 0 | 13 | +| **P2** | 7 | 6 | 0 | 3 | 0 | 16 | | **P3 (oos)** | 0 | 0 | 0 | 0 | 9 | 9 | -| **total** | **37** | **12** | **1** | **2** | **9** | **61** | +| **total** | **37** | **13** | **1** | **3** | **9** | **63** | @@ -26,9 +26,12 @@ | Team Lead | Remaining | |---|---| -| [shipwright](../team-leads/shipwright.md) | 6 | | [warcouncil](../team-leads/warcouncil.md) | 4 | +| [shipwright](../team-leads/shipwright.md) | 4 | | [testwright](../team-leads/testwright.md) | 2 | +| [tourguide](../team-leads/tourguide.md) | 2 | +| [asset-audio](../team-leads/asset-audio.md) | 1 | +| [asset-sprite](../team-leads/asset-sprite.md) | 1 | @@ -78,6 +81,7 @@ | [p1-11](p1-11-build-output-src-purge.md) | ✅ done | Purge build output from src/ — wasm-pack moves to .local/build/wasm/ | [tourguide](../team-leads/tourguide.md) | 2026-04-17 | | [p1-12](p1-12-build-output-docs-alignment.md) | ✅ done | Align every doc reference to the relocated wasm-pack output | [tourguide](../team-leads/tourguide.md) | 2026-04-17 | | [p1-13](p1-13-guide-dev-route-coverage.md) | ✅ done | Guide dev server boots on plum with zero-error route coverage | [tourguide](../team-leads/tourguide.md) | 2026-04-17 | +| [p1-14](p1-14-guide-magic-school-scope-drift.md) | 🟡 partial | Purge residual Game 2/3 magic-school content from Game 1 guide UI | [tourguide](../team-leads/tourguide.md) | 2026-04-17 | ## P2 — Polish @@ -94,10 +98,11 @@ | [p2-09](p2-09-guide-web-deploy.md) | 🟡 partial | Player guide web app — builds clean from source | — | 2026-04-17 | | [p2-10](p2-10-regression-ci-gate.md) | 🟡 partial | Automated regression CI gate on every push to main | [testwright](../team-leads/testwright.md) | 2026-04-17 | | [p2-11](p2-11-version-about-screen.md) | ✅ done | Version string + About screen | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | -| [p2-16](p2-16-audio-assets.md) | ❌ missing | Audio assets — SFX + music .ogg files shipped | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | -| [p2-17](p2-17-sprite-assets.md) | ❌ missing | Sprite assets — full unit / building / race / tier coverage | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | +| [p2-16](p2-16-audio-assets.md) | ❌ missing | Audio assets — SFX + music .ogg files shipped | [asset-audio](../team-leads/asset-audio.md) | 2026-04-17 | +| [p2-17](p2-17-sprite-assets.md) | ❌ missing | Sprite assets — full unit / building / race / tier coverage | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | | [p2-18](p2-18-guide-public-deployment.md) | 🟡 partial | Guide web app — public hosting + deploy pipeline | — | 2026-04-17 | | [p2-19](p2-19-guide-progress-report-page.md) | ✅ done | Guide progress report page — dynamic dashboard + missing assets | — | 2026-04-17 | +| [p2-20](p2-20-guide-sim-cache-pnpm-resolve.md) | ❌ missing | Fix simCachePlugin pre-warm worker — tsx can't resolve @magic-civ/physics-rs through pnpm symlink | [tourguide](../team-leads/tourguide.md) | 2026-04-17 | ## Out of Scope (Game 2 / Game 3) diff --git a/.project/objectives/p1-13-guide-dev-route-coverage.md b/.project/objectives/p1-13-guide-dev-route-coverage.md index 4b62ec02..8c79032a 100644 --- a/.project/objectives/p1-13-guide-dev-route-coverage.md +++ b/.project/objectives/p1-13-guide-dev-route-coverage.md @@ -39,6 +39,11 @@ green. Parallel Wave-1 agents landed the underlying fixes: - `.project/history/20260417_tourguide_dev_bringup.md` — original bring-up record. - Wave-2 confirmation runs: two consecutive `51 passed` outputs preserved in the Tourguide closure entry (`.project/CHANGELOG.md`, 2026-04-17). - Known-red list in `.project/team-leads/tourguide.md` retained for historical reference with a closure note. +- **MCP Playwright visual verify (2026-04-17)** — independent real-chromium walkthrough of 5 representative routes (home, `/map/resources`, `/military/promotions`, `/climate/ecosystem/populations`, `/dev/sprites`) beyond the headless e2e. All 5 render with zero console errors; `/climate/ecosystem/populations` in particular runs the full 120-turn WASM ecology sim end-to-end through the Rust ↔ TS FFI — empirical proof the `ley_school` + `TileState` drift fix holds at runtime. Screenshots under `.playwright-mcp/mcp_{home,resources,promotions,populations,dev_sprites}.png`. + +## Known caveat (surfaced by MCP verify, tracked separately) + +`/climate/simulation` with `noGui=true&totalTurns=50&buffer=0` params stalls at 0% in "pre-computed simulation from server" mode — `simCachePlugin`'s tsx-spawned pre-warm worker can't resolve `@magic-civ/physics-rs` through the pnpm symlink now that `src/simulator/package.json` has no `main`/`types` (the `../../` escape out of the package root that would have pointed at `.local/build/wasm/` doesn't traverse cleanly through node's resolver). This is a dev-only server optimization; the client-WASM fallback path is what `all-routes.spec.ts` measured and passed (44.7 s / 42.3 s). Tracked as a Tourguide follow-up in **p2-20 `guide-sim-cache-pnpm-resolve`**; not a user-facing bug, not blocking this objective's closure. ## Summary diff --git a/.project/objectives/p1-14-guide-magic-school-scope-drift.md b/.project/objectives/p1-14-guide-magic-school-scope-drift.md new file mode 100644 index 00000000..d248a198 --- /dev/null +++ b/.project/objectives/p1-14-guide-magic-school-scope-drift.md @@ -0,0 +1,133 @@ +--- +id: p1-14 +title: Purge residual Game 2/3 magic-school content from Game 1 guide UI +priority: p1 +status: partial +scope: game1 +owner: tourguide +updated_at: 2026-04-17 +evidence: + - public/games/age-of-dwarves/guide/src/pages/DevSpritesPage.tsx + - public/games/age-of-dwarves/guide/src/data/game.ts + - public/games/age-of-dwarves/guide/src/data/index.ts + - public/games/age-of-dwarves/guide/src/app/guide-data.ts + - public/games/age-of-dwarves/guide/src/pages/HomePage.tsx + - public/games/age-of-dwarves/guide/src/pages/LensesPage.tsx + - public/games/age-of-dwarves/guide/src/pages/SurvivalGuidePage.tsx + - src/packages/guide/src/components/climate-sim/HexGLRenderer.tsx + - src/packages/guide/src/components/cards/TerrainCard.tsx + - src/packages/guide/src/components/ui/SchoolPip.tsx + - src/packages/guide/src/data/ecology.ts + - public/games/age-of-dwarves/guide/e2e/all-routes.spec.ts +--- + +## Summary + +Per CLAUDE.md's hard Game 1 scope rule (Dwarves only, NO magic; leylines +/ Green school / spacefaring → Game 2; Archons / Ascension / 5 magic +schools → Game 3), no magic content may ship into the Game 1 guide. The +2026-04-17 `p2-09` scope-narrow pass deleted 10 Game 2/3 pages. But a +prophylactic `Explore` audit on the same day — triggered by the +Tourguide route-coverage spec catching magic-data imports in Game 1 +pages that still rendered — surfaced **2 RED** and **6 YELLOW** residual +leaks that survived the first purge: + +**RED (runtime crash risk if data drifts)** + +- `public/games/age-of-dwarves/guide/src/pages/DevSpritesPage.tsx:5` — + live import of `@resources/magic/schools.json`. (Partial-fix: the + p1-13 Wave-1 pass replaced the import with the shared `SCHOOL_COLORS` + palette, so the crash is gone; keeping this row as evidence of why + the objective exists.) +- `src/packages/guide/src/components/climate-sim/HexGLRenderer.tsx:7,749,783` + — live ley-line network rendering via `LEY_COLORS[edge.school]`. In + Game 1 the climate simulator's rendered grid shows ley-edge meshes + with school-coded colors, inserting Game 2 visual vocabulary into + every Game 1 planet view. + +**YELLOW (renders stale Game 2+ content into Game 1 UI, inert but wrong)** + +- `public/games/age-of-dwarves/guide/src/data/game.ts:87–98` — + `infusionTrees` loader pulls magical_promotions JSON into the guide + context; exported to every consumer via `data/index.ts:40`. +- `public/games/age-of-dwarves/guide/src/app/guide-data.ts:48` — the + data context injection re-exposes magic-school data to every page + via `GuideDataProvider`. +- `public/games/age-of-dwarves/guide/src/pages/HomePage.tsx:220–224` + — nav items `/magic/schools`, `/magic/spells`, `/magic/archons` + still defined; the routes were purged, so clicking these in Game 1 + hits the `` fallback. Broken-link UX. +- `public/games/age-of-dwarves/guide/src/pages/LensesPage.tsx:18` — + `formatUnlock` renders magic-school unlock strings for Game 1 lenses; + if a Game 1 lens has a magic unlock in data, the formatter renders it. +- `public/games/age-of-dwarves/guide/src/pages/SurvivalGuidePage.tsx:23,143,152` + — `ManaUpkeep` interface + render. +- `src/packages/guide/src/components/cards/TerrainCard.tsx:256–264` + — `mana_major` field render block. + +Additional front-page prose (Game 1 HomePage) already displays "5 +Magic Schools" and "16 Asymmetric Races" in its feature grid — +evidence that the `` wrapping isn't +being consulted by every page's content block. + +## Why this is P1 (ship-readiness), not P2 (polish) + +Game 1 "Age of Dwarves" Early Access cannot ship with Game 2 content +bleeding into Game 1 screens. Magic schools are deferred scope by an +explicit CLAUDE.md rail. The Explore findings are proof-of-drift, not +hypothetical. Any one RED entry is a potential crash when a data file +shape changes; any YELLOW entry is visible-to-players scope violation. + +## Acceptance + +- **RED fixes**: HexGLRenderer ley-edge rendering is gated behind + `` OR the edge data's `.school` field is + normalized to `'none'` + color-coded in a scope-neutral way in + Game 1. Verified by `e2e/all-routes.spec.ts` `/climate/simulation` + pass remaining green AND a new focused assertion that `LEY_COLORS` + is not consulted in Game 1 mode. +- **YELLOW fixes**: every reference in the audit table above either + (a) deleted if the feature it gates is strictly Game 2/3, (b) gated + behind ``, or (c) normalized to + episode-agnostic content. The `/magic/*` nav items in HomePage are + removed. `formatUnlock` returns empty / omits magic unlocks for + Game 1 lenses. `mana_major` field is not rendered when + `episode === 1`. +- **HomePage prose**: the "5 Magic Schools" feature card is rewritten + to an episode-1-appropriate Dwarf feature, OR moved into a + `` branch. The feature grid reflects Game 1 + scope. +- **Regression guard**: a new e2e assertion (in `all-routes.spec.ts` + or a dedicated `scope-hygiene.spec.ts`) greps the rendered DOM of + every Game 1 route for the strings `mana`, `ley`, `school`, + `disciplines`, `archon`, `ascension` and fails if any are rendered + outside an ``. Scope becomes a + mechanically-enforced invariant, not a doc rule. + +## Non-goals + +- Deleting `src/packages/guide/src/pages/magic/` or equivalent + universe-level Game 2/3 content — those pages are consumed by the + future Game 2/3 guide shells, must NOT be removed. +- Changing `mc-core::grid::LeySchool` Rust enum — `ley_school` is a + tile-level field the simulator computes for all episodes; its mere + existence in data is not scope violation, its *rendering in Game 1 + UI* is. +- Deleting the `infusionTrees` / `disciplinesData` JSON files — those + are Game 2/3 source-of-truth data files, kept but gated. +- Fixing the sim-cache server-mode dev path — see p2-20. + +## Status — 2026-04-17 (tourguide, partial) + +- ✓ Two of the three originally-RED entries closed inline during p1-13 + Wave-1 (DevSpritesPage magic-schools import replaced with shared + `SCHOOL_COLORS`; PromotionsPage `disciplinesData` null-guarded). +- ✗ HexGLRenderer ley-edge rendering still active in Game 1 mode. +- ✗ All 6 YELLOW entries unchanged. +- ✗ HomePage feature-grid prose still names "5 Magic Schools". +- ✗ Scope-hygiene e2e assertion not yet authored. + +Partial because: the RED crash risk is closed, but visible scope +violation remains. Escalating to a guide-web specialist via +dispatch-by-task for the prose + gating work; Tourguide owns the +regression-guard e2e + re-running the full suite to confirm. \ No newline at end of file diff --git a/.project/objectives/p2-16-audio-assets.md b/.project/objectives/p2-16-audio-assets.md index 9c7e1c8d..a5d664e1 100644 --- a/.project/objectives/p2-16-audio-assets.md +++ b/.project/objectives/p2-16-audio-assets.md @@ -4,7 +4,7 @@ title: Audio assets — SFX + music .ogg files shipped priority: p2 status: missing scope: game1 -owner: shipwright +owner: asset-audio updated_at: 2026-04-17 evidence: - public/games/age-of-dwarves/assets/audio/LICENSES.md diff --git a/.project/objectives/p2-17-sprite-assets.md b/.project/objectives/p2-17-sprite-assets.md index 82e091eb..1303175d 100644 --- a/.project/objectives/p2-17-sprite-assets.md +++ b/.project/objectives/p2-17-sprite-assets.md @@ -4,7 +4,7 @@ title: Sprite assets — full unit / building / race / tier coverage priority: p2 status: missing scope: game1 -owner: shipwright +owner: asset-sprite updated_at: 2026-04-17 evidence: - public/games/age-of-dwarves/assets/sprites/ diff --git a/.project/objectives/p2-20-guide-sim-cache-pnpm-resolve.md b/.project/objectives/p2-20-guide-sim-cache-pnpm-resolve.md new file mode 100644 index 00000000..0df41eaf --- /dev/null +++ b/.project/objectives/p2-20-guide-sim-cache-pnpm-resolve.md @@ -0,0 +1,99 @@ +--- +id: p2-20 +title: Fix simCachePlugin pre-warm worker — tsx can't resolve @magic-civ/physics-rs through pnpm symlink +priority: p2 +status: missing +scope: game1 +owner: tourguide +updated_at: 2026-04-17 +evidence: + - public/games/age-of-dwarves/guide/src/vite-plugins/simCachePlugin.ts + - src/simulator/package.json + - src/packages/engine-ts/src/runner.ts + - /tmp/tourguide_dev2.log +--- + +## Summary + +MCP Playwright verification of the dev-guide on plum surfaced a +pre-existing dev-only failure mode in `simCachePlugin`'s pre-warm +worker. On `pnpm dev` startup, the plugin spawns a tsx subprocess per +scenario (`base_no_magic`, `volcanic_winter`, `ice_age`, …) which +statically imports `@magic-civ/physics-rs` to pre-compute simulation +frames into Redis. Every spawn fails with: + +``` +Error: Cannot find package '/Users/natalie/Code/@projects/@magic-civilization/src/packages/engine-ts/node_modules/.local/build/wasm/magic_civ_physics.js' +imported from .../src/packages/engine-ts/src/runner.ts +code: 'ERR_MODULE_NOT_FOUND' +``` + +**Root cause:** after p1-11 relocated the wasm-pack output to +`.local/build/wasm/`, we also cleared `main` and `types` from +`src/simulator/package.json` because node's default resolver can't +follow a `"main": "../../.local/build/wasm/magic_civ_physics.js"` +path that escapes the package root — tsx in particular collapses the +`..` segments incorrectly through pnpm symlinks and looks up +`node_modules/@magic-civ/physics-rs/.local/build/wasm/...` (path +prefix glued rather than resolved). With no `main` at all, tsx falls +back to guessing `/index.js`, which also doesn't exist. + +**User-facing impact:** none. The pre-compute is a dev optimization +(populates Redis so the `/climate/simulation` route loads pre-rendered +frames instead of running WASM inline). Vite's main module graph still +resolves `@magic-civ/physics-rs` correctly via the explicit alias in +`public/games/age-of-dwarves/guide/vite.config.ts:20`, so the route- +coverage e2e exercises the client-WASM fallback path and passes. +Users hitting the route with `noGui=true` URL params and a fresh cache +see the loading spinner stall at 0% (MCP verify observed this) — but +the normal path (no `noGui`) falls back to worker-mode automatically. + +## Why this is P2, not P1 + +The symptom only manifests when (a) `simCachePlugin` is enabled +(dev-only), (b) the route is hit with `totalTurns=...&buffer=0` URL +params that force server mode, and (c) Redis hasn't been warmed by a +prior successful run. Three conjunctive conditions, all dev-only. +Production builds (`vite build`) don't run the plugin. End users +never hit this. + +## Acceptance + +- `pnpm dev` startup log at `/tmp/tourguide_dev*.log` no longer + contains `ERR_MODULE_NOT_FOUND` or + `sim-cache pre-warm failed for : sim worker exited 1` lines. +- Navigating to `/climate/simulation?noGui=true&totalTurns=50&buffer=0` + on a fresh Redis shows "Pre-computed simulation" reach 100% within + 60 s and transitions to the canvas-rendering phase. +- No regression in `pnpm --prefix public/games/age-of-dwarves/guide + test:e2e --grep all-routes`: still 51/51 green. + +## Candidate fixes (pick one) + +1. **Re-add a node-resolver-friendly `main` in `src/simulator/package.json`.** + Publish a tiny `src/simulator/runner-stub.mjs` that re-exports + from the absolute `.local/build/wasm/` path resolved at runtime. + `main` points at the stub; browser tooling still uses the Vite + alias; node tooling (tsx) can follow the stub. +2. **Teach tsx about the alias.** Add a `tsconfig.json` `paths` + entry that tsx will consult: `"@magic-civ/physics-rs": [".local/build/wasm/magic_civ_physics.js"]`. +3. **Inline the resolution in `simCachePlugin`.** Change the plugin + to spawn the worker with an explicit `--require` preloader or + `NODE_OPTIONS` that installs an import-map pointing at the + absolute path. + +Option 1 is probably the cleanest — the stub is ~3 lines, isolated +to `src/simulator/`, and doesn't leak an absolute build path into +tsconfig or env. Leaves `main` pointing at intra-package code which +node's resolver has no problem with. + +## Non-goals + +- Reintroducing `src/simulator/pkg/` as the WASM artifact location — + the rule from p1-11 + p1-12 stands; any fix must leave + `.local/build/wasm/` as the canonical output. +- Fixing WASM alias resolution for *browser* code — Vite handles that + correctly via the explicit alias; only node-side tooling (tsx + workers spawned by simCachePlugin) is broken. +- Removing `simCachePlugin` — the pre-warm is a real dev-time speedup + for the climate simulator. Kill-it-and-forget is a regression. \ No newline at end of file diff --git a/.project/team-leads/README.md b/.project/team-leads/README.md index 1f05a915..17461167 100644 --- a/.project/team-leads/README.md +++ b/.project/team-leads/README.md @@ -69,4 +69,4 @@ specialist does not own any objective. | [warcouncil](warcouncil.md) | Warcouncil | AI action generation, MCTS, GPU look-ahead, clan personality differentiation | p0-01, p0-02, p0-20 | | [shipwright](shipwright.md) | Shipwright | Drive Game 1 to release via /experts-team cron loop until every P0 is done | p0-05, p0-14, p0-15, p0-16, p0-17 | | [testwright](testwright.md) | Testwright | Regression-test coverage across Rust + GDScript + data validators — seeds the evidence substrate for the Objective Status Integrity rule | p1-09, p2-10 | -| [tourguide](tourguide.md) | Tourguide | Developer experience of the guide web app — dev server boots on plum, route coverage e2e, and the "no build output in src" rule stays enforced | p1-11, p1-12, p1-13 | +| [tourguide](tourguide.md) | Tourguide | Developer experience of the guide web app — dev server boots on plum, route coverage e2e, Game 1 scope hygiene in guide UI, and the "no build output in src" rule stays enforced | p1-11, p1-12, p1-13, p1-14, p2-20 | diff --git a/.project/team-leads/asset-audio.md b/.project/team-leads/asset-audio.md new file mode 100644 index 00000000..55925d21 --- /dev/null +++ b/.project/team-leads/asset-audio.md @@ -0,0 +1,42 @@ +--- +id: asset-audio +name: Asset — Audio +specialization: Source, license, encode, and ship the Game 1 audio asset library (SFX + music) +objectives: + - p2-16 +--- + +## Mandate + +Ship the `.ogg` audio files that `AudioManager` expects — 10 SFX events + 6 music tracks. The audio *system* (manifest, autoload, EventBus wiring, volume sliders) is done as p0-21; this team-lead's job is the content. + +p0-21's architecture deliberately treats missing `.ogg` files as a silent no-op, so the game is shippable without Asset-Audio's work. But a silent game is incomplete. Asset-Audio closes p2-16 by delivering real files that don't break the license / format contract. + +## Owned surface + +- `public/games/age-of-dwarves/assets/audio/sfx/*.ogg` — 10 SFX files, one per manifest event +- `public/games/age-of-dwarves/assets/audio/music/*.ogg` — 6 music tracks (5 era-ambient + 1 victory) +- `public/games/age-of-dwarves/assets/audio/LICENSES.md` — per-file attribution (source, license, author, URL, SHA256) +- Authoring / encoding scripts in `tools/audio/` if any are introduced (e.g. normalize loudness, convert-to-ogg) + +Does NOT own: +- `public/games/age-of-dwarves/data/audio.json` — the manifest shape. If a new event is needed, coordinate with game-systems via handoff. +- `src/game/engine/src/autoloads/audio_manager.gd` — the runtime code. Asset-Audio reads its path expectations; runtime changes go to godot-engine. + +## Working constraints + +- **Ogg Vorbis format, 44.1 kHz, 128 kbps.** SFX may be mono; music must be stereo. +- **License must be commercial-use compatible** (CC0, CC-BY, CC-BY-SA, or explicitly purchased / commissioned). No "royalty-free" sources that prohibit resale. +- **SHA256 recorded in LICENSES.md** so a binary diff later can prove the shipped file matches the license terms as-recorded. +- **Loudness normalized** to a consistent LUFS target (e.g. -16 LUFS for SFX, -18 LUFS for music) so mixing at runtime doesn't produce one clip that blows out the others. +- **Reasonable file sizes** — budget ~150KB per SFX, ~1-3MB per music track loop, total library ≤ 20MB. + +## Acceptance loop + +Per p2-16's acceptance bullets. When all 16 files are shipped + LICENSES.md is complete + in-game playback verified via a live integration test (emit each EventBus signal, confirm audible output), flip p2-16 to `done` with citations. + +## Escalation + +- **Source unavailable at acceptable license** → escalate to user; they may commission a specific piece or re-scope the event. +- **AudioManager bug exposed by real files** (e.g. a specific .ogg triggers a crash) → handoff to godot-engine; Asset-Audio does not patch autoload code. +- **Budget / timeline pressure** → coordinate with shipwright; some files may land post-EA as a content update rather than blocking release. diff --git a/.project/team-leads/asset-sprite.md b/.project/team-leads/asset-sprite.md new file mode 100644 index 00000000..f4e837ad --- /dev/null +++ b/.project/team-leads/asset-sprite.md @@ -0,0 +1,46 @@ +--- +id: asset-sprite +name: Asset — Sprite +specialization: Generate, commission, and ship unit / building / wonder sprite art per the sprite-rendering capability contract +objectives: + - p2-17 +--- + +## Mandate + +Ship the `.png` sprite files that the renderers expect when `ThemeAssets.load_sprite()` is called. The rendering *capability* is being built in p0-23: renderers draw procedural primitives first (unconditional baseline), then overlay a sprite if one exists at the resolved path. Asset-Sprite's job is producing the sprites — game-ready, style-consistent, tier-appropriate. + +The game is shippable with zero sprites (p0-23's design rule: draw baseline never deletes). Asset-Sprite's delivery progressively replaces the circle-and-letter placeholders with real art, unit by unit, building by building. + +## Owned surface + +- `public/games/age-of-dwarves/assets/sprites/units/__.png` — every `data/units/*.json` entry should have a matching sprite variant per race × sex combination the game supports +- `public/games/age-of-dwarves/assets/sprites/buildings/.png` — every `data/buildings/*.json` entry + every wonder in `mundane_wonders.json` +- `public/games/age-of-dwarves/assets/sprites/LICENSES.md` — per-file attribution (source, license, author, URL, SHA256) +- `tools/sprite-generation/` — the generation pipeline (prompt authoring, model inference, post-processing, normalization, drop-into-assets) +- Any authoring data: style references, prompt libraries, generation seeds, post-process scripts + +Does NOT own: +- `src/game/engine/src/rendering/*_renderer.gd` — the runtime sprite load + overlay path. Owned by godot-renderer. Asset-Sprite reads the expected path conventions and produces files that match. +- Sprite generation model selection beyond the CLAUDE.md rule ("NEVER use anime models for game art — use `juggernaut-xl-v9`, `epicrealism-xl`, `illustrious-xl-v2`"). + +## Working constraints + +- **PNG with alpha channel.** Square canvas, 256×256 or 512×512 (document the choice in LICENSES.md). +- **Transparent background** — renderer composites sprite onto hex; any background color would bake incorrectly. +- **Consistent art style** across the library — use MTG-color-coded palette per the sprite-generation pipeline doc, but the Dwarf race is the only Game 1 race so visual coherence is within-race, not cross-race. +- **License must be commercial-use compatible** — AI-generated output has to be from a model the project is licensed to use commercially (check juggernaut-xl, epicrealism-xl, illustrious-xl-v2 license terms at ship time). Commissioned art must have assigned commercial rights. +- **SHA256 recorded in LICENSES.md** for binary-diff traceability. +- **Budget**: 256×256 PNG at reasonable quality ≈ 30-80KB. Full library of ~100-300 sprites ≤ 20MB. +- **Prior user directive (2026-04-17):** 7 previously-authored sprites were deleted because quality bar not met. Slate is clean. Re-generate from the pipeline or commission — do not restore the deleted files. + +## Acceptance loop + +Per p2-17's acceptance bullets. Deliveries can land incrementally — every sprite added to `assets/sprites/` renders the next time the game boots. Flip p2-17 to `done` when all declared units + buildings + wonders have at least one sprite variant, LICENSES.md is complete, and a proof screenshot shows a sample map with sprite-rendered entities co-existing with draw-baseline fallbacks. + +## Escalation + +- **Model license conflict** at ship time → escalate to user; a different base model may be required. +- **Style drift** between batches of generated sprites → stop generating, iterate on the prompt library / post-process pipeline before shipping more. +- **Renderer can't load a specific PNG** (e.g. format bug, alpha channel issue) → handoff to godot-renderer; Asset-Sprite does not patch renderer code. +- **Budget / timeline pressure** → coordinate with shipwright; most sprites can land post-EA as progressive visual polish rather than blocking release. Prioritize "player's most-seen units" (Founder, Warrior, Scout) for EA. diff --git a/.project/team-leads/tourguide.md b/.project/team-leads/tourguide.md index d7d7c949..5e2f6e6a 100644 --- a/.project/team-leads/tourguide.md +++ b/.project/team-leads/tourguide.md @@ -6,6 +6,8 @@ objectives: - p1-11 - p1-12 - p1-13 + - p1-14 + - p2-20 --- ## Mandate diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index 89579518..a8fa55dc 100644 --- a/public/games/age-of-dwarves/data/objectives.json +++ b/public/games/age-of-dwarves/data/objectives.json @@ -1,12 +1,12 @@ { - "generated_at": "2026-04-17T21:24:08Z", + "generated_at": "2026-04-17T21:31:25Z", "totals": { + "missing": 3, "done": 37, - "oos": 9, - "partial": 12, - "missing": 2, "stub": 1, - "total": 61 + "oos": 9, + "partial": 13, + "total": 63 }, "objectives": [ { @@ -379,6 +379,16 @@ "updated_at": "2026-04-17", "summary": "`./run guide` on plum (`natalie@plum.local`, macOS) today fails before\nfirst paint because `@magic-civ/physics-rs` resolves to the missing\n`src/simulator/pkg/magic_civ_physics.js` — p1-11 + p1-12 close that\nstructural half. What remains is the contributor-side proof: starting\nthe dev server on a fresh clone, loading every canonical route in\nPlaywright, and asserting zero runtime errors. Nobody currently owns\nthat proof, and the last guide-dev CHANGELOG entry\n(2026-04-16 14:47 task #18) explicitly marked \"visual verification\nblocked by WASM not built on macOS.\" The gap closes when a spec\nexists, runs green on plum, and catches new routes automatically on\nevery `pnpm test:e2e`.\n\nThe e2e substrate is already wired — `@lilith/playwright-e2e-docker`\nis a committed dependency, `e2e/Dockerfile.web` pre-bakes a production\nbuild, `playwright.config.ts` switches its webServer between\n`pnpm dev --port 5802` (local) and `pnpm preview --port 5802` (CI), and\ntwo specs (`diag.spec.ts`, `simulator.spec.ts`) already exercise the\nclimate simulator. This objective extends that harness with one\nroute-coverage spec; no new infrastructure." }, + { + "id": "p1-14", + "title": "Purge residual Game 2/3 magic-school content from Game 1 guide UI", + "priority": "p1", + "status": "partial", + "scope": "game1", + "owner": "tourguide", + "updated_at": "2026-04-17", + "summary": "Per CLAUDE.md's hard Game 1 scope rule (Dwarves only, NO magic; leylines\n/ Green school / spacefaring → Game 2; Archons / Ascension / 5 magic\nschools → Game 3), no magic content may ship into the Game 1 guide. The\n2026-04-17 `p2-09` scope-narrow pass deleted 10 Game 2/3 pages. But a\nprophylactic `Explore` audit on the same day — triggered by the\nTourguide route-coverage spec catching magic-data imports in Game 1\npages that still rendered — surfaced **2 RED** and **6 YELLOW** residual\nleaks that survived the first purge:\n\n**RED (runtime crash risk if data drifts)**\n\n- `public/games/age-of-dwarves/guide/src/pages/DevSpritesPage.tsx:5` —\n live import of `@resources/magic/schools.json`. (Partial-fix: the\n p1-13 Wave-1 pass replaced the import with the shared `SCHOOL_COLORS`\n palette, so the crash is gone; keeping this row as evidence of why\n the objective exists.)\n- `src/packages/guide/src/components/climate-sim/HexGLRenderer.tsx:7,749,783`\n — live ley-line network rendering via `LEY_COLORS[edge.school]`. In\n Game 1 the climate simulator's rendered grid shows ley-edge meshes\n with school-coded colors, inserting Game 2 visual vocabulary into\n every Game 1 planet view.\n\n**YELLOW (renders stale Game 2+ content into Game 1 UI, inert but wrong)**\n\n- `public/games/age-of-dwarves/guide/src/data/game.ts:87–98` —\n `infusionTrees` loader pulls magical_promotions JSON into the guide\n context; exported to every consumer via `data/index.ts:40`.\n- `public/games/age-of-dwarves/guide/src/app/guide-data.ts:48` — the\n data context injection re-exposes magic-school data to every page\n via `GuideDataProvider`.\n- `public/games/age-of-dwarves/guide/src/pages/HomePage.tsx:220–224`\n — nav items `/magic/schools`, `/magic/spells`, `/magic/archons`\n still defined; the routes were purged, so clicking these in Game 1\n hits the `` fallback. Broken-link UX.\n- `public/games/age-of-dwarves/guide/src/pages/LensesPage.tsx:18` —\n `formatUnlock` renders magic-school unlock strings for Game 1 lenses;\n if a Game 1 lens has a magic unlock in data, the formatter renders it.\n- `public/games/age-of-dwarves/guide/src/pages/SurvivalGuidePage.tsx:23,143,152`\n — `ManaUpkeep` interface + render.\n- `src/packages/guide/src/components/cards/TerrainCard.tsx:256–264`\n — `mana_major` field render block.\n\nAdditional front-page prose (Game 1 HomePage) already displays \"5\nMagic Schools\" and \"16 Asymmetric Races\" in its feature grid —\nevidence that the `` wrapping isn't\nbeing consulted by every page's content block." + }, { "id": "p2-01", "title": "Minimap — fog reflection and unit markers", @@ -495,7 +505,7 @@ "priority": "p2", "status": "missing", "scope": "game1", - "owner": "shipwright", + "owner": "asset-audio", "updated_at": "2026-04-17", "summary": "The audio capability shipped as **p0-21** — `AudioManager`, manifest, signal wiring, volume sliders all work. What's missing is the 16 actual `.ogg` files the manifest declares. Gameplay is currently silent. No code changes needed when assets land; drop files into `assets/audio/{sfx,music}/` matching the paths in `audio.json`.\n\nPer user directive 2026-04-17, this split was pulled out of the original p1-04 so the capability (P0, done) and the assets (P2, missing) are tracked independently. A silent ship is shippable; a broken audio system is not." }, @@ -505,7 +515,7 @@ "priority": "p2", "status": "missing", "scope": "game1", - "owner": "shipwright", + "owner": "asset-sprite", "updated_at": "2026-04-17", "summary": "With p0-22 capability in place, the game needs comprehensive sprite coverage:\n- Every unit × race × sex combination declared in `data/units/`\n- Every building declared in `data/buildings/`\n- Per-tier variants where meaningful (T1-T4 sprites can look different from T7-T10)\n\nCurrently 0 sprite files exist — prior 7 were deleted 2026-04-17 per user directive (quality bar not met). Hundreds are expected when the full sprite generation pipeline runs (or commissioned art lands). Slate is clean." }, @@ -529,6 +539,16 @@ "updated_at": "2026-04-17", "summary": "Dynamic progress report page inside the Age of Dwarves guide that reads the project's objectives dashboard + asset pipeline state at runtime. Built 2026-04-17 under guide-progress-dev.\n\nDelivery:\n- `tools/objectives-report.py` extended to emit `public/games/age-of-dwarves/data/objectives.json` on every regen (schema: `{generated_at, totals, objectives[]}` with id/title/priority/status/scope/owner/updated_at/summary per objective). `--check` mode compares ignoring the volatile `generated_at`.\n- `ProgressReportPage.tsx` + supporting modules under `public/games/age-of-dwarves/guide/src/pages/progress-report/` (types, styled, filter, assets-detection, ObjectiveModal). Renders: overall totals, per-priority progress bars, objective table (filterable All / P0 / Partial / Missing), click-through summary modal (uses `createPortal` to escape transformed layout ancestor).\n- Missing assets section: scans `audio.json` (declared .ogg paths) and `units/*.json` + `buildings/*.json` (expected sprite paths) against `import.meta.glob` presence. Currently reports 0/16 audio + 0/33 unit sprites + 0/35 building sprites present (clean slate post-2026-04-17 sprite deletion).\n- Route `/progress` added in `App.tsx`, nav entry `📊 Progress Report` at top of About group.\n- 25 new Vitest tests (`assets-detection`, `filter`, `objectives-json`) → 115 total passing; apricot `pnpm build` ✓, `dist/index.html` exists, bundle 113kB.\n- Incidental fix: orphan `healing_draught` reference in `items/manifest.json` removed (was breaking app bootstrap)." }, + { + "id": "p2-20", + "title": "Fix simCachePlugin pre-warm worker — tsx can't resolve @magic-civ/physics-rs through pnpm symlink", + "priority": "p2", + "status": "missing", + "scope": "game1", + "owner": "tourguide", + "updated_at": "2026-04-17", + "summary": "MCP Playwright verification of the dev-guide on plum surfaced a\npre-existing dev-only failure mode in `simCachePlugin`'s pre-warm\nworker. On `pnpm dev` startup, the plugin spawns a tsx subprocess per\nscenario (`base_no_magic`, `volcanic_winter`, `ice_age`, …) which\nstatically imports `@magic-civ/physics-rs` to pre-compute simulation\nframes into Redis. Every spawn fails with:\n\n```\nError: Cannot find package '/Users/natalie/Code/@projects/@magic-civilization/src/packages/engine-ts/node_modules/.local/build/wasm/magic_civ_physics.js'\nimported from .../src/packages/engine-ts/src/runner.ts\ncode: 'ERR_MODULE_NOT_FOUND'\n```\n\n**Root cause:** after p1-11 relocated the wasm-pack output to\n`.local/build/wasm/`, we also cleared `main` and `types` from\n`src/simulator/package.json` because node's default resolver can't\nfollow a `\"main\": \"../../.local/build/wasm/magic_civ_physics.js\"`\npath that escapes the package root — tsx in particular collapses the\n`..` segments incorrectly through pnpm symlinks and looks up\n`node_modules/@magic-civ/physics-rs/.local/build/wasm/...` (path\nprefix glued rather than resolved). With no `main` at all, tsx falls\nback to guessing `/index.js`, which also doesn't exist.\n\n**User-facing impact:** none. The pre-compute is a dev optimization\n(populates Redis so the `/climate/simulation` route loads pre-rendered\nframes instead of running WASM inline). Vite's main module graph still\nresolves `@magic-civ/physics-rs` correctly via the explicit alias in\n`public/games/age-of-dwarves/guide/vite.config.ts:20`, so the route-\ncoverage e2e exercises the client-WASM fallback path and passes.\nUsers hitting the route with `noGui=true` URL params and a fresh cache\nsee the loading spinner stall at 0% (MCP verify observed this) — but\nthe normal path (no `noGui`) falls back to worker-mode automatically." + }, { "id": "g2-01", "title": "Ley lines — Game 2 (Age of Kzzykt)",