- commit
- 603832b3e1655019ea44703ffdd83bb4e0750392
- parent
- 79f2d2c820218a0c5ae419815f08b11ac0b2f3bb
- author
- Dami <dami.etoile@gmail.com>
@@ -11,7 +11,7 @@ npm run dev
1111 1212 Open the local URL printed by Vite. Run `npm test` for the engine, pack, sharing, and content fixtures, and `npm run build` to produce `dist/`.1313 14-The current [public test build](https://fcbc.n.zip/) is revision 8 at the stable14+The current [public test build](https://fcbc.n.zip/) is revision 9 at the stable1515 nzip address `public:miau-is-true`. It is a static test origin that can support1616 a later shoo.dev identity integration. The current game has no account or1717 sign-in flow.@@ -60,8 +60,8 @@ decoded without evaluating CommonJS source.
6060 6161 The full-screen stage, scene flow, input routing, audio events, local font,6262 mobile behavior, reduced-motion behavior, and developer flow are implemented.63-Revision 8 passes 170 Node tests, the production build, and focused automata64-browser checks against the public URL on desktop and mobile. The designer63+Revision 9 passes 183 Node tests, the production build, and desktop/mobile browser64+checks for the campaign, sharing, automata, and Undo after death. The designer6565 accepted the earlier stage visual gate. The automata visual review remains in6666 the active `add-rule-driven-automata` OpenSpec change.6767 @@ -75,6 +75,7 @@ To try the automata mechanic, import [the FENN clock example](mods/automata-exam
7575 7676 - [Design backbone](docs/design-backbone.md): vocabulary, turn order, puzzle design research, and extension boundaries.7777 - [Roadmap](docs/roadmap.md): verified work, active OpenSpec changes, and long-term plans.78+- [Architecture review](docs/architecture-review.md): runtime ownership, scaling risks, and the staged refactor plan.7879 - `openspec/`: behavioral specs and the history of proposed changes. Start a new feature with `openspec new change <name>`, read `openspec instructions` for each artifact, validate before implementation, then archive after verification.7980 8081 Pretext sizes variable-length hint copy. Grid coordinates are fixed integers, so emoji and text metrics never change collision geometry. UI sounds come from the supplied Kenney UI Pack (CC0; license in `public/audio/LICENSE.txt`).@@ -0,0 +1,90 @@
1+# Runtime architecture review2+3+2026-09-23. Scope: browser runtime, deterministic engine, mod/share data, test and authoring boundaries. This review records the baseline and the migration contract for [the active OpenSpec change](../openspec/changes/modularize-runtime-boundaries/proposal.md). The visual design and world files have separate owners.4+5+## Baseline6+7+The project has useful seams already: `createGame()` does not import DOM or canvas; mod data is validated before registration; sharing carries data rather than CommonJS source; engine snapshots support Undo and the solver; `render_game_to_text()` exposes playable state to browser tests. At review start, 170 Node tests and the production build passed.8+9+The source tree's file sizes did not match its responsibility boundaries:10+11+| File | Baseline lines | Responsibilities combined | Risk |12+| --- | ---: | --- | --- |13+| `src/stage.js` | 1,526 | Scene state and transitions, all scene drawing, layout, keyboard/pointer/intent routing, DOM overlays | High |14+| `src/render/board.js` | 660 | Geometry, words, entities, sentence bars, facing, effects, loss state | High |15+| `src/juice.js` | 572 | Event interpretation, tweens, facing/Undo history, rule and automata effects | High |16+| `src/engine.js` | 427 | Validation, parsing, movement, automata, contacts, snapshots, event assembly | High |17+| `src/main.js` | 278 | Bootstrap, catalog and packs, DOM view, game lifecycle, second developer renderer, audio | High despite line count |18+| `src/scenes/map.js` | 335 | Progress storage, map layout, navigation | Medium |19+| `src/mods.js` | 275 | CommonJS evaluation, validation, normalization | Medium, security-sensitive |20+| `src/input.js` | 258 | Keyboard, repeat, swipe, gamepad, listener lifecycle | Medium |21+| `src/style.css` | 1 minified line | Most base application styling | Medium for maintainability |22+23+Raw line count is a signal, not a target. `main.js` demonstrates the sharper problem: dense one-line lifecycle handlers hide branches and make changes difficult to review. Large files should be split at ownership seams rather than sliced into arbitrary line ranges.24+25+## Intended boundaries26+27+```mermaid28+flowchart LR29+ Input[Keyboard touch gamepad] --> Stage[Stage action router]30+ Stage --> App[Application session]31+ App --> Engine[Deterministic engine]32+ Engine --> State[Settled state and events]33+ State --> App34+ App --> View[Stage projection]35+ View --> Render[Canvas renderers and animation]36+ Packs[Trusted local CommonJS or shared data] --> Validate[Data validation and normalization]37+ Validate --> App38+ State --> Solver[Replay and solver state key]39+```40+41+The session owns the game instance and authoritative state. A stage view is a projection of that state plus animation frames. A renderer never changes gameplay state. Named actions cross from input to app once. Mod loading and sharing feed normalized data to the same catalog boundary. The engine has no dependency on application, rendering, audio, or browser APIs.42+43+## Findings and concrete changes44+45+### 1. Duplicate stage action paths46+47+`stage.js` has overlapping keyboard, pointer, and `handleIntent` branches. Pause and Index behavior appears in multiple branches; the Index also has both a capture listener and document listener. This is the most likely source of one platform missing a new action or accepting it twice. Introduce one scene action dispatcher; have device adapters decode to named actions. Let renderers return named hit regions as the pointer contract. Verify a key, a tap, and a programmatic intent each cause exactly one equivalent action.48+49+### 2. Mirrored and ambiguous runtime state50+51+`main.js` owns `game`, while `stage.js` also stores board state, game handle, palette, glyphs, and render frame. `setBoard()` is an implicit synchronization point. Keep the engine snapshot authoritative and make the stage projection explicitly read-only. `juice.js` owns visual history, including facing, that can diverge during Undo or scene transitions; future mechanics must not add hidden state there. Refactor command lifecycle so move, wait, Undo, restart, and next level use the same projection refresh contract.52+53+### 3. Engine complexity and cost54+55+Rule parsing, push chains, automata, transformation, contacts, and settled event construction share one closure. Movement repeatedly filters the object array or finds IDs, and property queries scan rules repeatedly. Current tutorial boards are small, but larger mods and several automata will multiply these scans. Add turn-local maps for IDs, cells, and subject properties; rebuild at explicit mutation phases. Preserve the existing player → parse/transform → automata → parse/transform → contacts behavior and event ordering. A later change should split turn phases into named modules and specify simultaneous automata conflicts before changing their semantics.56+57+### 4. Data and validation drift58+59+Catalog, pack library, pack service, share, Gift, and Workshop repeat JSON clone helpers. A shared data-only clone helper should make ownership and mutability clear. Engine and mod loader also validate overlapping automata fields differently; that schema should be consolidated in a separate change before additional mechanics fields are accepted. Trusted local CommonJS executes via `new Function`; shared links must stay data-only. Any future community/remote executable modules need an explicit isolation model rather than reusing this trusted-local path.60+61+### 5. Scaling contracts for future mechanics62+63+Gates, switches, and one-step tiles introduce persistent state and turn-order questions. Every stateful mechanic must define: normalized authored data, collision/contact phase, deterministic conflict order, settled events, snapshot/restore representation, solver key, replay behavior, and Undo/reset behavior. The existing solver stores full snapshots in BFS nodes and keys only objects plus automata phase; its state codec will need to include any new mechanic counters or gate states. Do not add a mechanics field only to rendering or mod data.64+65+## Sequence and acceptance gates66+67+1. **Current refactor:** stage action dispatch, app session seam, engine lookup seam, shared data clone semantics, focused tests, and this architecture contract. Preserve public APIs and current level behavior.68+2. **Presentation follow-up:** extract stage scene renderers and layout/hit geometry; split board passes and `juice` event interpretation from animation timelines. Keep designer-owned visual behavior and mobile layout verified.69+3. **Engine follow-up:** version a mechanics snapshot envelope, centralize pack/engine validation, define automata collision policy, and introduce an effect interface for switches/gates/crumble. Extend solver state codec before shipping authored examples.70+4. **Project hygiene:** format and split minified CSS as a mechanical commit, separate map progress storage from drawing, add a supported solver command and CI authoring gate when the world-authoring OpenSpec change lands, and watch bundle size and import cycles.71+72+Acceptance gates for each behavior-facing step: Node fixtures remain green; production build succeeds; desktop and mobile browser flows cover navigation, move/wait, rule changes, Undo, and imported CommonJS/URL data; screenshots and `render_game_to_text()` agree; no new console errors. Performance work should measure representative larger boards rather than assert timing from one device. No feature is complete if Undo, restore, replay, or solver identity omits its state.73+74+## Implemented in this pass75+76+| Boundary | Result |77+| --- | --- |78+| Stage | `stage.js` fell from 1,526 to 648 lines. HUD, Proof, Pause, and Win drawing live in focused render modules. Overlay keyboard, pointer, and programmatic actions share one dispatcher; pause actions have stable IDs separate from labels. |79+| App | Turn lifecycle, scene intent routing, and the developer canvas view moved out of `main.js`. Its line count is similar because the controller wiring is explicit; the dense gameplay and drawing branches are gone. |80+| Engine | Pure rule parsing and turn-local ID/cell/property indexes moved out of `engine.js`. An Undo browser regression exposed a stale index after restoring history; Undo now rebuilds the indexes. |81+| Presentation | `board.js` fell from 660 to 432 lines with a cached render model and entity pass. `juice.js` delegates visual tween projection and effect construction to pure helpers. |82+| Data and structure | Pack/share cloning has one data boundary. INDEX progress persistence is separate from map layout. Base and stage CSS are formatted for review. |83+84+The final architecture pass passed 183 Node tests, the production build, and browser flows for the built-in chapter, imported CommonJS packs, data-only sharing, Gift/Workshop, Win/next proof, mobile controls, automata WAIT, and death followed by Undo. The first browser run caught a missing stage renderer import; the second caught the stale engine index after Undo. Both were corrected and rerun. Screenshots of Proof, Win, mobile Gift, mobile automata, and restored MIAU were visually inspected, with no console errors in the final runs.85+86+Remaining work is specific rather than a reason to keep expanding this refactor: stage pointer targets still use viewport fractions on mobile; `juice.consume()` remains a large event reducer; mod and engine automata validation overlap; the solver stores full BFS snapshots and needs a mechanics-aware state codec; automata conflict ordering is sequential. Those should be handled in separate changes with authored scenarios and regression fixtures before gates, switches, or crumble tiles join the default campaign.87+88+## Review boundary89+90+The concurrent designer controls `docs/design.md` and `docs/design.html`; the world team controls `docs/worlds/`, `mods/worlds/`, and its OpenSpec change. This review does not rewrite those files. Design or level-authoring consequences should be sent through the existing inbox files. The current architecture work retains the public nzip URL and existing mod format.@@ -0,0 +1,5 @@
1+# Completion note2+3+The runtime-boundaries capability becomes a live spec with this archive. The refactor preserved existing gameplay, CommonJS and share data, snapshots, and public engine/stage/board entry points. Verification passed 183 Node tests, the production build, and desktop/mobile browser flows covering rules, Pause/Index/Workshop, Gift, Win/continue, automata WAIT, and Undo after death.4+5+The browser loop found and fixed a missing extracted renderer import and a stale turn index after Undo. The architecture review at `docs/architecture-review.md` records the remaining work: geometry-driven pointer hit regions, further juice decomposition, shared pack/engine schema validation, a mechanics-aware snapshot/solver codec, and an explicit automata conflict policy. Gates, switches, and crumble tiles remain future behavior changes.@@ -0,0 +1,65 @@
1+## Context2+3+`createGame()` is already independent of DOM and canvas, which is the strongest boundary in the codebase. The presentation path is less clear: `main.js` owns a live game, `stage.js` mirrors a board state and handles all scenes, and `juice.js` interprets settled events for animation. `stage.js` is 1,526 lines, `main.js` uses dense inline lifecycle handlers, and the engine repeatedly filters or searches objects during each movement step. The designer and world authors work in parallel; their owned files and authored behavior must remain stable.4+5+## Goals / Non-Goals6+7+**Goals:**8+9+- Keep one authoritative simulation state in the game session; treat stage board data as a presentation projection.10+- Route stage commands through a single action vocabulary regardless of device.11+- Extract focused modules at existing seams while preserving `createStage()`, `createGame()`, mod, share, and browser debug APIs.12+- Reduce repeated object/rule scans in turn hot paths without changing deterministic order.13+- Make data-only copying semantics consistent for manifests, levels, and share payloads.14+- Establish measured gates for future stateful mechanics.15+16+**Non-Goals:**17+18+- New gameplay vocabulary or gates, switches, and crumble behavior in this change.19+- A new rendering framework, rewrite of the stage design, or edits to designer-owned visuals.20+- A different automata conflict policy; current turn ordering remains until its own spec change.21+- Running untrusted remote CommonJS code. Local module execution remains explicitly trusted.22+23+## Decisions24+25+### Preserve the public façades26+27+`createGame()` and `createStage()` remain the entry points. Internals can move into `src/engine/`, `src/stage/`, and `src/app/` without forcing all callers or mods to migrate. A large public API rewrite would increase regression risk without helping near-term world authoring.28+29+### Separate intent, simulation, and presentation30+31+Inputs produce named actions. A stage dispatcher maps an action in the current scene to one callback; the application session accepts game commands and updates the single engine instance; rendering consumes snapshots plus animation frames. This removes three similar but drifting keyboard, pointer, and programmatic command paths. Renderer-returned hit regions are the eventual source for pointer targets.32+33+### Keep turn semantics stable while indexing34+35+The current player → rule refresh → automata → rule refresh → contacts pipeline and event shape are the compatibility contract. Turn-local maps by ID/cell/subject can replace repeated scans, but indexes must be invalidated or rebuilt after movement, transformation, and destruction. Simultaneous automata resolution is a separate behavior decision and requires a dedicated OpenSpec change.36+37+### Treat snapshots as the extension boundary38+39+All future stateful mechanics need normalized data, deterministic turn phase, snapshot/restore fields, solver state keys, and presentation events before campaign use. The existing automata phase shows why this must be planned before adding gates or one-step tiles. A versioned mechanics envelope is a future design step, not a silent addition in this refactor.40+41+### Make cleanup incremental and reviewable42+43+Each commit owns one seam: architecture contract, stage dispatch, app session, engine indexing, and data copy semantics. Existing browser and Node gates verify behavior between seams. A single source-tree reshuffle would obscure behavior changes and collide with parallel level/design work.44+45+## Risks / Trade-offs46+47+- [Risk] Moving input routing changes a key or pointer action twice or not at all. → Conformance tests and browser flows check one action per accepted input, Pause/Index navigation, and mobile controls.48+- [Risk] Cached engine indexes become stale after a push or transformation. → Keep indexes turn-local with explicit rebuild points and compare settled state/events against existing fixtures.49+- [Risk] Pure JSON clones can alter values outside the data contract. → Limit the shared helper to already validated level/manifest/share data and keep existing error behavior.50+- [Risk] Another team changes visual files during refactor. → This change does not edit `docs/design.*`, world files, or their active OpenSpec changes.51+- [Risk] The first extraction does not make every large file small. → Track remaining hotspots, line counts, owners, and migration order in the architecture review.52+53+## Migration Plan54+55+1. Record baseline module sizes, ownership, dependencies, and behavior gates.56+2. Extract action dispatch and app/session seams behind existing façades.57+3. Introduce engine lookup helpers and shared data cloning without changing serialized state.58+4. Run Node, build, and browser controls/rules/mod/Undo checks; inspect rendered frames and console.59+5. Follow with separate OpenSpec changes for scene renderers, snapshot schema/mechanics, and automata conflict policy as needed.60+61+## Open Questions62+63+- What simultaneous automata conflict policy should apply before larger world packs?64+- Should passive pushing turn every directional actor, or only actors configured for that behavior?65+- Which gates/switch/crumble effects run before contact resolution? World-authored scenarios must answer this before schema changes.@@ -0,0 +1,27 @@
1+## Why2+3+The game now has dynamic rules, automata, sharing, and several stage scenes. Its two composition files (`stage.js` and `main.js`) mix simulation-facing commands with rendering and input, while engine hot paths repeatedly scan whole boards. This makes future mechanics such as gates, switches, and crumbling tiles difficult to add safely.4+5+## What Changes6+7+- Give stage actions one dispatch path across keyboard, pointer, and programmatic intents.8+- Separate game session work, scene presentation, and pack data copying into focused modules while preserving current public APIs and play behavior.9+- Extract board rendering passes, stage scene drawing, animation projections, and INDEX progress storage at existing module seams.10+- Format the minified application styles so layout changes are reviewable.11+- Add reusable engine lookup structures for rule and movement work without changing turn order or event shape.12+- Record architecture boundaries and a staged plan for stateful mechanics, snapshots, solver keys, and further renderer extraction.13+- Add focused conformance checks for rule changes, controls, mod loading, Undo, and scene transitions.14+15+## Capabilities16+17+### New Capabilities18+19+- `runtime-boundaries`: stable simulation, application, stage, and data ownership; one action dispatch and deterministic observable behavior across entry points.20+21+### Modified Capabilities22+23+None. This refactor preserves the current gameplay and sharing requirements.24+25+## Impact26+27+`src/stage.js`, `src/main.js`, `src/engine.js`, `src/render/board.js`, `src/juice.js`, `src/scenes/map.js`, pack data helpers, CSS, focused tests, and architecture documentation. Existing mods, snapshots, public `createGame()` and `drawBoard()` shapes, and the game URL remain compatible. No new runtime dependency is required.@@ -0,0 +1,42 @@
1+## ADDED Requirements2+3+### Requirement: Scene actions share semantic dispatch4+5+The stage SHALL resolve Pause and Index actions through shared semantic methods across keyboard, pointer, and programmatic entry points. The application SHALL route normalized gameplay intents to named commands. One accepted movement or wait action SHALL cause at most one engine turn.6+7+#### Scenario: Equal commands from different devices8+9+- **WHEN** the player invokes `undo` from a key, the mobile button, or the stage intent API after one successful move10+- **THEN** the same previous board state is restored exactly once and the visible board matches it11+12+#### Scenario: Menu command is scoped to the active scene13+14+- **WHEN** the player invokes the Index navigation action while a proof is active15+- **THEN** it does not select another proof or advance the current game turn16+17+### Requirement: Simulation owns gameplay state18+19+The game session SHALL own the authoritative engine state. Stage renderers SHALL consume a projection of settled state and animation data without mutating the engine, and scene changes SHALL NOT change object positions or dynamic rules.20+21+#### Scenario: Pause and resume preserve a rule change22+23+- **WHEN** a word move changes a dynamic rule and the player pauses and resumes24+- **THEN** the engine state, rendered board, and active rule display agree with the state before the pause25+26+### Requirement: Compatible data and event boundaries27+28+The refactor SHALL preserve accepted CommonJS pack data, data-only shared payloads, snapshot restore, and settled event fields. Data cloning SHALL not retain mutable references to caller-owned pack objects.29+30+#### Scenario: Import and Undo after a pack turn31+32+- **WHEN** a valid local pack is imported, played, and a successful move is undone33+- **THEN** the pack remains installed, the game restores the prior board and automata phase, and the shared payload contains data rather than executable source34+35+### Requirement: Deterministic engine lookups36+37+Turn-local lookup structures SHALL reflect the same object and rule state as the public snapshot after each movement, transformation, and contact phase. Lookup optimization SHALL NOT alter deterministic turn order or settled events.38+39+#### Scenario: Rules change during an accepted turn40+41+- **WHEN** a push breaks a rule before an automaton acts42+- **THEN** the automaton observes the refreshed rules, and replay plus Undo reproduce the same positions, phase, and events@@ -0,0 +1,22 @@
1+## 1. Review and contracts2+3+- [x] 1.1 Audit module size, state ownership, dependency direction, duplicated semantics, and future mechanics risks.4+- [x] 1.2 Define an OpenSpec runtime-boundary contract and staged architecture design.5+- [x] 1.3 Record the review findings, module ownership, and follow-up migration path in repository documentation.6+7+## 2. Focused extraction8+9+- [x] 2.1 Consolidate Pause and Index keyboard, pointer, and programmatic actions around shared semantic methods while preserving controls.10+- [x] 2.2 Extract app session/lifecycle work from the bootstrap module and clarify the stage projection boundary.11+- [x] 2.3 Reduce repeated full-board scans in engine turn work through turn-local lookups without changing events or snapshots.12+- [x] 2.4 Centralize data-only clone semantics used by catalog, pack, and share boundaries.13+- [x] 2.5 Extract pure rule parsing, board render-model/entity passes, and visual effect projections while preserving their public APIs.14+- [x] 2.6 Extract stage HUD and scene drawing from the stage controller while preserving canvas output and accessibility.15+- [x] 2.7 Separate INDEX progress persistence from map layout/navigation and format the application styles.16+17+## 3. Verification18+19+- [x] 3.1 Verify stage navigation and one-action behavior on desktop and mobile, including Pause, Index, wait, and Undo.20+- [x] 3.2 Verify rule changes, automata, CommonJS mod loading, data-only sharing, and Undo in Node and browser checks.21+- [x] 3.3 Run production build, inspect gameplay screenshots and text state, and check console errors.22+- [x] 3.4 Validate and archive this OpenSpec change only after implemented tasks and behavior gates pass.@@ -0,0 +1,46 @@
1+# runtime-boundaries Specification2+3+## Purpose4+TBD - created by archiving change modularize-runtime-boundaries. Update Purpose after archive.5+## Requirements6+### Requirement: Scene actions share semantic dispatch7+8+The stage SHALL resolve Pause and Index actions through shared semantic methods across keyboard, pointer, and programmatic entry points. The application SHALL route normalized gameplay intents to named commands. One accepted movement or wait action SHALL cause at most one engine turn.9+10+#### Scenario: Equal commands from different devices11+12+- **WHEN** the player invokes `undo` from a key, the mobile button, or the stage intent API after one successful move13+- **THEN** the same previous board state is restored exactly once and the visible board matches it14+15+#### Scenario: Menu command is scoped to the active scene16+17+- **WHEN** the player invokes the Index navigation action while a proof is active18+- **THEN** it does not select another proof or advance the current game turn19+20+### Requirement: Simulation owns gameplay state21+22+The game session SHALL own the authoritative engine state. Stage renderers SHALL consume a projection of settled state and animation data without mutating the engine, and scene changes SHALL NOT change object positions or dynamic rules.23+24+#### Scenario: Pause and resume preserve a rule change25+26+- **WHEN** a word move changes a dynamic rule and the player pauses and resumes27+- **THEN** the engine state, rendered board, and active rule display agree with the state before the pause28+29+### Requirement: Compatible data and event boundaries30+31+The refactor SHALL preserve accepted CommonJS pack data, data-only shared payloads, snapshot restore, and settled event fields. Data cloning SHALL not retain mutable references to caller-owned pack objects.32+33+#### Scenario: Import and Undo after a pack turn34+35+- **WHEN** a valid local pack is imported, played, and a successful move is undone36+- **THEN** the pack remains installed, the game restores the prior board and automata phase, and the shared payload contains data rather than executable source37+38+### Requirement: Deterministic engine lookups39+40+Turn-local lookup structures SHALL reflect the same object and rule state as the public snapshot after each movement, transformation, and contact phase. Lookup optimization SHALL NOT alter deterministic turn order or settled events.41+42+#### Scenario: Rules change during an accepted turn43+44+- **WHEN** a push breaks a rule before an automaton acts45+- **THEN** the automaton observes the refreshed rules, and replay plus Undo reproduce the same positions, phase, and events46+@@ -27,3 +27,7 @@ TODO: Review the audio mix by ear and keep the spacing rule for future teaching
2727 2026-09-23: Public revision 7 at https://fcbc.n.zip/ preserves the existing nzip target and expiry (2026-10-23T23:16:58Z). Its focused desktop/mobile browser run passes 9 checks with no console errors. The final mobile WAIT control is labelled plainly and fits above Restart. The active automata OpenSpec change remains open for deterministic solver and visual fixtures, plus designer review of the final frames. The designer was asked to replace the old fox token in its owned reference docs and prototype with FENN.2828 2929 2026-09-23: Public revision 8 corrects MIAU's intermittent backward pose. The proof renderer now recognizes the built-in 🐈 as native-left even through a same-glyph plain palette string, and every accepted horizontal displacement (including pushes) sets the actor's facing from travel; Undo/reset still restore prior/native direction. The FENN face emoji example is neutral. The new `fix-miau-forward-facing` OpenSpec change is strictly valid and complete after 170 Node tests, the production build, the existing browser flow, and the focused desktop/mobile facing flow against the public URL. Vertical moves retain the side-profile's last horizontal pose. The designer received the corrected D18 contract for its owned reference docs.30+31+2026-09-23: Started the runtime architecture review and `modularize-runtime-boundaries` OpenSpec change. Read-only audits found stage action duplication, dense application turn orchestration, engine full-array lookups, repeated pack clone code, and minified CSS as the highest maintenance risks. `docs/architecture-review.md` records state ownership, boundaries, risks for gates/switches/crumble, and staged follow-up work. Pack data cloning is centralized in commit `f014da5`; CSS formatting is isolated in commit `2dfb250`. Stage, app, and engine extraction slices are under integration review before their individual commits. The designer and world team files remain separately owned.32+33+2026-09-23: Completed and archived `modularize-runtime-boundaries`. Stage is 648 lines (from 1,526), board renderer 432 (from 660), engine 378 (from 427), and INDEX progress is separate from map navigation. The app has dedicated turn, intent, and developer-view modules; juice has pure visual/effect projections; pack cloning has one data boundary. The browser loop found a missing proof-renderer import and stale engine indexes after Undo, both fixed and covered. Final local gates: 183 Node tests, Vite build, full share/campaign browser flow, automata desktop/mobile flow, death-Undo visual flow, inspected screenshots, and no console errors. OpenSpec strict validation passed all 11 active/live items. Stable public preview https://fcbc.n.zip/ is revision 9 (existing expiry retained). Next architecture work: geometry-derived pointer targets, further juice event-reducer split, unified mod/engine validation, mechanics-aware snapshots and solver keys, and an explicit automata conflict policy before gates/switches/crumble.