From 519cacaf43c67e5bf0a51d62d6156fe1d067eb0f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 1 Aug 2026 12:40:19 +0000 Subject: [PATCH] docs: bring the README back in line with the code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An audit of every factual claim in the README against the repo. Fixes one outright error and several omissions that had accumulated over the recent refactors and features. Corrected: - "Run `npm run lint` (`tsc --noEmit`)" — wrong on both counts. `lint` is oxlint; `typecheck` is the tsc gate. The advice pointed at the wrong command entirely. Filled in: - File tree was missing rng.ts, stash.ts and particles.ts, and still listed ParticlePool under entities.ts (it moved to the render layer). - scripts/ listed only validate-data.mjs — smoke-test.mjs and headless.mjs were undocumented. - The components list omitted difficulty-modal, heat-modal and controls-modal. - storage.ts description now mentions the Daily Rift streak and BrowserStash. - `npm run verify` was described as five gates; it runs seven (headless and smoke were missing from both the one-liner and the "individually" list). - Table of contents didn't include the sim/host boundary subsection. Rewritten: - The Test suite section claimed the only automated coverage was Vitest unit tests and that everything else needed manual browser checking. There are now three layers — unit tests (node env, no jsdom), the headless sim boundary guard, and the Playwright smoke test — so it's a table explaining what each one actually catches, and why the UI layer having no unit harness is a deliberate choice rather than a gap. Verified against the code: grid dimensions, every named data file, every component, and both design docs (docs/fidchell.md's 7×7 board and every-7th- floor rule, docs/causeway-duel.md) are accurate and needed no changes. All ToC anchors resolve. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01V6kP5vUAcNbxLK5EnPKAhv --- README.md | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 14b3a0c..30aab38 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Built with TypeScript + Vite as an installable PWA. Rendering is a single `/HTML icon rendering) + HtmlUtils (shared HTML-escaping) storage.ts localStorage (high XP, run history, mute, reduced-motion, - ghosts, lore codex) + ghosts, lore codex, Daily Rift streak) + BrowserStash, + the browser StashPort implementation audio.ts Web Audio SFX errorReporting.ts Fatal-error normalization (DOM-free) wired into the crash-recovery modal @@ -201,6 +208,11 @@ scripts/ validate-data.mjs Validates every src/data/*.json against its schema; runs standalone (`npm run validate-data`) and as the "prebuild" step before every `npm run build` + smoke-test.mjs Playwright smoke test: boots the built app, starts a + run, drives real input, asserts no uncaught errors + headless.mjs Boots the simulation in plain Node with the browser + globals booby-trapped — the runtime half of the + sim/host boundary guard (`npm run headless`) public/ icons/ PWA icon set (48–512px, maskable variants, favicon, apple-touch-icon) @@ -336,23 +348,34 @@ After any content change: `npm run lint && npm test && npm run build` (the build **One command runs every quality gate** — the same set CI enforces: ```bash -npm run verify # typecheck + lint + schema validation + tests w/ coverage thresholds + build +npm run verify # typecheck · lint · schema validation · tests w/ coverage + # thresholds · headless sim · build · Playwright smoke ``` -Individually: `npm run typecheck` (tsc), `npm run lint` (oxlint, warnings are errors), `npm run validate-data` (AJV against `schema/*.json`), `npm run test:coverage` (vitest + v8 coverage with ratchet thresholds in `vitest.config.ts` — raise them as coverage grows, CI fails if a change drops below). +Individually: `npm run typecheck` (tsc), `npm run lint` (oxlint, warnings are errors), `npm run validate-data` (AJV against `schema/*.json`), `npm run test:coverage` (vitest + v8 coverage with ratchet thresholds in `vitest.config.ts` — raise them as coverage grows, CI fails if a change drops below), `npm run headless` (plays the simulation in Node with no DOM), `npm run smoke` (Playwright against the built app). **Versioning**: every Pages deploy is stamped `1.0.` (shown on the start screen and pause menu, `dev` locally), so bug reports can name the exact build. ### Test suite -Unit tests live in `src/__tests__/` and run on **Vitest** (`npm test`). They cover the pure game logic — combat math, spawning, line clears, boons/brands/curses (including the JSON effect resolver), the Gorgoth endgame, and monster AI. There's no jsdom/happy-dom configured, so **the UI/component/renderer layers have no unit harness** — verify those changes live in the browser (`npm run dev`), driving the actual DOM rather than poking internals. +Three layers of automated checking, each aimed at a different failure mode: + +| Gate | What it covers | +|---|---| +| **Unit tests** — `src/__tests__/`, Vitest (`npm test`) | The simulation: combat math, spawning, line clears, boons/brands/curses (including the JSON effect resolver), save/restore, seeded runs, the Gorgoth endgame, monster AI. Runs in `environment: 'node'` with **no jsdom** — which is possible precisely because the sim has no DOM dependency. | +| **Headless sim** — `npm run headless` | Boots a real `Game` in plain Node with `document`/`window`/`localStorage`/`navigator` booby-trapped to throw, plays 1500 turns, then round-trips `serialize()` → `applySave()`. Catches a browser dependency creeping into the simulation. | +| **Smoke test** — `npm run smoke` | Playwright against the *built* app: starts a run, drives real keyboard/touch input, asserts the canvas renders and nothing throws. | + +The **UI/component/renderer layers have no unit harness** by design — they're covered by the smoke test and by driving the real thing (`npm run dev`) rather than poking internals. When changing them, verify in a browser. + +Coverage thresholds (`vitest.config.ts`) are scoped to the simulation layer only, and ratchet upward — CI fails if a change drops below. --- ## Notes & gotchas -- **`npm run build` does not type-check** (esbuild). Run `npm run lint` (`tsc --noEmit`) before trusting a build. +- **`npm run build` does not type-check** (the bundler strips types without checking them). Run `npm run typecheck` (`tsc --noEmit`) before trusting a build — `npm run lint` is oxlint, which is a separate gate. - **Movement is orthogonal only** for everyone — a diagonally-adjacent enemy must step to a cardinal tile before it can attack. - **The Gorgoth fight has no line clears** (blocks stop), so line-clear-oriented builds don't contribute during it — combat/dodge/crit/sustain/ranged builds carry the finale. - **Modals are Light DOM custom elements, not Shadow DOM.** Every existing `id`/`class` on a modal's markup is preserved verbatim when it moved from a static `
` in `index.html` into a component's `template()` string — `style.css` needs no per-component changes. Keep it that way: don't introduce element-type-qualified CSS selectors (`div.modal-overlay`), or a future component could stop matching them.