clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / openspec / changes / archive / 2026-09-24-modularize-runtime-boundaries / design.md
design.md 5.2K rendered
1## Context23`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.45## Goals / Non-Goals67**Goals:**89- 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.1516**Non-Goals:**1718- 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.2223## Decisions2425### Preserve the public façades2627`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.2829### Separate intent, simulation, and presentation3031Inputs 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.3233### Keep turn semantics stable while indexing3435The 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.3637### Treat snapshots as the extension boundary3839All 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.4041### Make cleanup incremental and reviewable4243Each 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.4445## Risks / Trade-offs4647- [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.5253## Migration Plan54551. Record baseline module sizes, ownership, dependencies, and behavior gates.562. Extract action dispatch and app/session seams behind existing façades.573. Introduce engine lookup helpers and shared data cloning without changing serialized state.584. Run Node, build, and browser controls/rules/mod/Undo checks; inspect rendered frames and console.595. Follow with separate OpenSpec changes for scene renderers, snapshot schema/mechanics, and automata conflict policy as needed.6061## Open Questions6263- 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.