clone

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

docs(openspec): track ink stage contract and design gate

Dami committed 6580a7f
commit
6580a7f22de207161b61732f5acd72f7f82d28de
parent
5bab7e9ab534ffd53e4a2207975cdcf12de9ce37
author
Dami <dami.etoile@gmail.com>
7 files changed, +388 -0
Aopenspec/changes/build-ink-signal-stage/.openspec.yaml2+
Aopenspec/changes/build-ink-signal-stage/design.md90+++++++++++++++++++
Aopenspec/changes/build-ink-signal-stage/proposal.md28++++++
Aopenspec/changes/build-ink-signal-stage/specs/game-presentation/spec.md41+++++++++
Aopenspec/changes/build-ink-signal-stage/specs/game-stage/spec.md139++++++++++++++++++++++++++++++
Aopenspec/changes/build-ink-signal-stage/specs/playable-puzzles/spec.md42+++++++++
Aopenspec/changes/build-ink-signal-stage/tasks.md46++++++++++
A openspec/changes/build-ink-signal-stage/.openspec.yaml +2 -0
A openspec/changes/build-ink-signal-stage/design.md +90 -0
@@ -0,0 +1,90 @@
1+## Context2+3+The engine, catalog, pack service, input adapter, INDEX data model, board renderer, juice system, audio facade, font asset, and scene descriptors now exist as separate modules. The running browser still has an interim DOM shell and a small stage overlay; these modules are not yet integrated into one full-screen game loop or verified against the v0.3 prototype. This change is the shell integration milestone for the designer's ink-and-signal direction.4+5+## Goals / Non-Goals6+7+**Goals:**8+9+- Integrate a DPR-capped full-screen canvas stage with a scene stack and accessible live region.10+- Deliver Title → INDEX → Proof → Win → INDEX flow plus Pause, Workshop, and Gift overlays.11+- Use the shared input adapter for immediate keyboard, gamepad, and touch intents with the specified repeat behavior.12+- Render data-driven INDEX sheets and persistent progress keyed by stable pack/level ids.13+- Render the board with no-overlap spacing, E3 sentence bars, stacks, walls, ledger, HUD, and no-YOU recovery.14+- Feed settled engine events to juice and Web Audio with reduced-motion handling and music off by default.15+- Connect pack-service hooks to Workshop, drag/drop, and pending Gift confirmation.16+- Self-host the Recursive MONO 1/CASL 0 font and run browser acceptance against the prototype contract.17+18+**Non-Goals:**19+20+- Changing the engine's rule grammar, event schema, sentence-span algorithm, or pack serialization contract.21+- Replacing the designer's visual reference, copy deck, timings, colors, scene order, or input values with a new design.22+- Adding server-side sharing, accounts, multiplayer, or remote module execution.23+- Treating the current stage overlay or sidebar as the final player-facing layout.24+25+## Decisions26+27+### 1. Use one stage coordinator with narrow module boundaries28+29+`src/stage.js` owns the canvas loop, scene stack, resize/DPR handling, scene transitions, and accessibility announcements. `src/input.js` emits intents and owns repeat timing. `src/scenes/*.js` owns scene state and menu decisions. `src/render/board.js` draws only from simulation state and geometry. `src/juice.js` consumes settled events for visual interpolation, while `src/audio.js` maps event categories to preloaded buffers. `src/pack-service.js` remains presentation-independent and is called by Workshop/Gift adapters.30+31+The existing `main.js` shell becomes an integration adapter during migration; it must not duplicate input repeat, pack validation, map progress, or event animation logic.32+33+### 2. Treat stage input as an intent stream, never a queue34+35+The coordinator consumes immediate `move`, `undo`, `confirm`, `restart`, `menu`, and `mute` intents. Before applying an intent it asks juice to snap old tweens to their endpoints. The engine result is then passed to juice and audio, while the next intent can be accepted on the same frame. The engine remains the only state transition authority.36+37+### 3. Drive INDEX from catalog records and progress storage38+39+INDEX consumes `catalog.getLevels()` and the map layout/progress APIs. It lays out at most five nodes per row and three rows per sheet, uses stable `packId/levelId` keys in local storage, marks later proofs locked, and keeps pack sheets connected. Scene code owns cursor and navigation; the catalog owns content identity.40+41+### 4. Draw board geometry in fixed zones42+43+The board renderer computes an integer cell size clamped to the documented range and allocates separate gutters for tile lips, sentence bars, ledger, and HUD. It draws walls, things, YOU things, words, spans, particles, HUD, and overlays in a stable order. Emoji metrics and Pretext text fitting never alter engine coordinates. Real engine stacks are the only permitted overlap.44+45+The sentence bar uses the physical span supplied by the engine, with its horizontal extent inset from 0.13 to 0.91 of a cell. Board labels use the bundled Recursive MONO 1 face so glyph metrics remain stable offline.46+47+### 5. Make effects projections of settled events48+49+Juice receives the E2 result and current state, maps `pushedBy`, blocked reason, full rule deltas, transformations, destruction details, `controlLost`, and win state to timing categories, and exposes a frame snapshot to renderers. Audio receives the same categories and uses a single unlocked Web Audio context with preloaded buffers. Reduced motion sets durations to zero or one-frame outlines; it does not change engine outcomes.50+51+Sentence juice is keyed by physical cell ids joined with `>` and carries forming, active, and removing phases. Removed spans retain frozen pre-move cells while their animation completes. Movement snapping does not cut rule formation, and the frame carries the reviewed bounce, transform pop, sink, defeat shake, undo tint, restart wipe, win beat, rule spark, and destruction-letter details.52+53+### 6. Keep pack entry points explicit54+55+Workshop calls `listPacks`, `importPackText`, `exportPack`, `packLink`, `decodePackLink`, `installPack`, and `removePack`. A dropped or chosen `.cjs` file is previewed before trusted execution and installation. A `#pack=` hash decodes as data into a pending Gift state; only `Keep and play` installs it. Duplicate ids route to Replace, Keep both as a copy, or Cancel. Interim sidebar controls are rendered only when `?dev` is present; player-facing Workshop/Gift scenes own the default flow.56+57+Workshop and Gift consume the pack service's normalized preview view, including pack name, author, level count, and full first-level data. Duplicate previews expose `{ id, name }` conflict metadata before installation. Player-facing hooks use no-throw result envelopes with readable messages and raw diagnostic details for Copy details.58+59+### 7. Keep accessibility alongside canvas rendering60+61+The canvas is visual output, not the only interface. A polite live region announces scene, proof, rule, no-YOU, win, pack, and error state. Keyboard, gamepad, touch swipe, and coarse-pointer thumb controls share input semantics. Reduced motion, static labels, and self-hosted font behavior are tested independently from pixel layout.62+63+Scene copy follows the deck: drawn keycaps are paired with verbs, middle-dot separators are absent, Gift uses `A PACK FOR YOU` and sentence-case bylines, and content is called proofs. The title remains a playable micro-proof with `MIAU IS TRUE`, `DOOR IS WIN`, and a cat the player can walk into the door; Enter remains a deliberate INDEX skip.64+65+## Risks / Trade-offs66+67+- [The old DOM shell and new stage can both consume input] → Attach one stage input adapter, gate the legacy adapter behind `?dev`, and assert one engine action per intent in browser checks.68+- [Canvas resizing changes visual placement] → Recompute DPR and integer geometry on resize while keeping state coordinates unchanged; test desktop and mobile viewports.69+- [Events can be consumed after state has moved again] → Snap before each intent and pass immutable settled result snapshots to juice/audio.70+- [A missing sentence span makes bars disagree with rules] → Render from `state.sentences` and test duplicate, stacked, horizontal, and vertical spans.71+- [Audio autoplay or missing assets can block play] → Unlock on first gesture, preload opportunistically, report load errors, and keep gameplay working without sound.72+- [Gift or Workshop can execute code too early] → Decode URLs data-only, preview local CJS before install, and require explicit confirmation.73+- [Designer visual work can be churned by shell implementation] → Keep scene semantics and module contracts in this change; use `docs/design.html` as the fidelity reference and leave pixel tuning review to the designer.74+75+## Migration Plan76+77+1. Wire stage resize, canvas loop, input adapter, scene coordinator, and live region while keeping `?dev` legacy controls available.78+2. Integrate INDEX layout/progress and proof selection with the catalog and engine.79+3. Replace board drawing with the ink-and-signal renderer and consume E3 spans and E2 events.80+4. Connect juice and Web Audio, then apply reduced-motion and mute/music defaults.81+5. Connect Workshop/Gift to pack service, including drag/drop, pending hash confirmation, and duplicate choices.82+6. Self-host Recursive and remove the player-facing remote font dependency.83+7. Run unit tests, production build, and browser acceptance at desktop, mobile, reduced-motion, and `?dev` entry points.84+85+Rollback leaves the engine, catalog, pack service, and existing dev shell usable by disabling the default stage entry and restoring the interim adapter; no authored pack data migration is required.86+87+## Open Questions88+89+- Which exact stage coordinator API should the scene modules use after the first browser acceptance pass?90+- Which browser acceptance harness will provide a stable gamepad and reduced-motion simulation in CI?
A openspec/changes/build-ink-signal-stage/proposal.md +28 -0
@@ -0,0 +1,28 @@
1+## Why2+3+The current browser shell can exercise the engine and pack services, but it still presents the game as a sidebar-driven web page. The v0.3 direction needs a coherent full-screen game stage where the player walks the INDEX, plays proofs, receives event-driven feedback, and manages packs through Pause, Workshop, and Gift scenes.4+5+## What Changes6+7+- Integrate the full-viewport DPR canvas stage and hide DOM chrome during normal play, retaining only the accessibility live region, Workshop file input, and `?dev` tools.8+- Add scene flow for playable Title, INDEX map, Proof level, Pause, Win card, Workshop, and Gift confirmation with reduced-motion transitions.9+- Integrate owned keyboard/gamepad/touch input repeat, immediate intent handling, and no queued moves.10+- Render the ink-and-signal board, sentence bars from E3 spans, HUD, ledger, stacks, no-YOU state, INDEX path, and proof progress.11+- Consume settled engine events through juice and Web Audio systems with reduced-motion behavior and music off by default.12+- Integrate Workshop/Gift pack-service hooks, drag-and-drop `.cjs` import, pending `#pack=` confirmation, and in-canvas feedback.13+- Self-host the Recursive font and complete browser acceptance against the designer reference.14+15+## Capabilities16+17+### New Capabilities18+19+- `ink-signal-stage`: Full-screen stage, scene stack, input, map navigation, board rendering, event-driven juice/audio, and pack scenes.20+21+### Modified Capabilities22+23+- `game-presentation`: Replace interim DOM/sidebar presentation with the v0.3 canvas stage and visual contracts.24+- `playable-puzzles`: Add INDEX map layout, stable progress keyed by `packId/levelId`, locked proof navigation, and scene progression.25+26+## Impact27+28+Integrates existing stage, input, map, renderer, juice, audio, font, and pack-service modules with the real engine and catalog. Adds scene and browser acceptance coverage. The designer’s `docs/design.md` and `docs/design.html` remain the visual authority; this change does not alter their ownership or edit them.
A openspec/changes/build-ink-signal-stage/specs/game-presentation/spec.md +41 -0
@@ -0,0 +1,41 @@
1+## MODIFIED Requirements2+3+### Requirement: The app SHALL render the simulation as a readable emoji grid4+The renderer SHALL draw the simulation on the ink-and-signal full-screen canvas with fixed integer cell geometry, hard-edged board and tiles, stable stack order, and no decorative overlap. Object nouns SHALL use emoji mappings or readable text fallbacks, word entities SHALL remain legible, and physical sentence spans SHALL drive sentence bars.5+6+#### Scenario: Stacked entities remain visible7+- **WHEN** multiple objects or words occupy one cell8+- **THEN** the renderer preserves their identity with the documented stack offset and does not treat decorative elements as simulation occupancy9+10+#### Scenario: A modded noun has no emoji fallback11+- **WHEN** a noun has no declared emoji mapping12+- **THEN** the renderer uses a readable text fallback within its fixed cell13+14+#### Scenario: A sentence bar follows state spans15+- **WHEN** the engine exposes `state.sentences`16+- **THEN** the renderer draws maximal horizontal or vertical bars from those physical spans, including exact duplicates17+18+### Requirement: The app SHALL show active rules and turn feedback19+The presentation SHALL render the current proof HUD, ledger, hints, keycap controls, active/inactive words, and no-YOU state on the stage. It SHALL consume settled engine events for blocked movement, undo, reset, transformations, defeat, victory, and rule changes without changing simulation state.20+21+#### Scenario: Active rules update after a word push22+- **WHEN** a word tile is pushed into or out of a sentence23+- **THEN** the stage updates sentence bars and ledger from the settled event and current state24+25+#### Scenario: Victory is readable26+- **WHEN** the engine enters the won state27+- **THEN** the stage shows the winning sentence, `PROVEN IN N STEPS`, and explicit continue/replay actions28+29+### Requirement: Text effects SHALL support variable-length labels without driving grid geometry30+The app MAY use Pretext or an equivalent text-effect layer for proof names, hints, ledger labels, pack feedback, and scene copy. Text effects SHALL be laid out in their owning HUD or scene region and SHALL not determine integer simulation coordinates or collision geometry.31+32+#### Scenario: A long proof title does not move cells33+- **WHEN** a proof title or hint is longer than its HUD region34+- **THEN** it fits, wraps, or truncates according to the stage copy rules while every grid cell retains its coordinate and size35+36+### Requirement: The presentation SHALL provide accessible control feedback37+The app SHALL expose a polite live region and readable text equivalents for movement, undo, restart, menu, scene, rule, level, pack, and outcome status. Keyboard, gamepad, and touch users SHALL be able to reach every player-facing action, and reduced motion SHALL preserve status without animation.38+39+#### Scenario: Controls are understandable without animation40+- **WHEN** reduced motion is enabled or animation is unavailable41+- **THEN** the board, INDEX, active rules, current proof, pack status, and controls remain understandable from static text and tile labels
A openspec/changes/build-ink-signal-stage/specs/game-stage/spec.md +139 -0
@@ -0,0 +1,139 @@
1+## ADDED Requirements2+3+### Requirement: The app SHALL run normal play in a full-screen stage4+The player-facing app SHALL use one full-viewport canvas sized for the device pixel ratio capped at two. During normal play the stage SHALL hide the interim header, sidebar, footer, scroll, selection, and context menu behavior; the remaining DOM SHALL be limited to an accessibility live region, the hidden Workshop file input, and developer tools behind `?dev`.5+6+#### Scenario: The player enters a proof7+- **WHEN** the app opens a proof scene at a desktop or mobile viewport8+- **THEN** the canvas fills the viewport at the capped device pixel ratio and no default sidebar controls are shown9+10+#### Scenario: Developer controls are opt-in11+- **WHEN** the app URL does not include `?dev`12+- **THEN** interim DOM controls are hidden while normal keyboard, pad, touch, and scene controls remain available13+14+### Requirement: The stage SHALL provide the v0.3 scene flow15+The stage SHALL support Title, INDEX, Proof, Pause, Win card, Workshop, and pending Gift scenes. Scene changes SHALL be driven by explicit input and SHALL use a cat-centered iris transition, with a straight cut under reduced motion. A win SHALL wait for explicit continue or replay input.16+17+#### Scenario: A solved proof returns through the win card18+- **WHEN** a proof reaches the engine's won state19+- **THEN** the stage shows the winning sentence and `PROVEN IN N STEPS`, then waits for Enter before returning to INDEX20+21+#### Scenario: Pause opens from a proof22+- **WHEN** the player presses Escape, P, or the mapped menu action during a proof23+- **THEN** the stage opens Pause without changing the engine state and offers Resume, Restart, INDEX, Workshop, sound, and music actions24+25+#### Scenario: A shared pack opens as a Gift26+- **WHEN** the app opens with a valid `#pack=` payload27+- **THEN** the stage presents a Gift preview with `Keep and play` and `Not now`, and does not install the pack before confirmation28+29+### Requirement: Input SHALL be immediate, repeatable, and never queued30+The stage SHALL use the shared input adapter for keyboard, Standard gamepad, and touch. A press SHALL emit on the same frame, browser repeat events SHALL be ignored, only the most recently held direction SHALL repeat after 175ms and every 110ms, undo SHALL accelerate from 240ms toward a 55ms floor, and a new intent SHALL snap an in-flight visual tween before starting the next action.31+32+#### Scenario: Held direction uses owned repeat33+- **WHEN** the player holds Right and no newer direction is held34+- **THEN** one move emits immediately, the first repeat waits 175ms, and subsequent repeats occur 110ms apart35+36+#### Scenario: A newer direction takes over37+- **WHEN** the player holds Right, presses Down, and releases Down38+- **THEN** Down is the active repeating direction while held and Right resumes after Down releases39+40+#### Scenario: Touch and gamepad use the same intent path41+- **WHEN** the player swipes at least 26px or moves the gamepad stick beyond the 0.55 deadzone42+- **THEN** the stage emits the same direction intent path used by keyboard input43+44+### Requirement: INDEX SHALL be data-driven and preserve progress45+The INDEX map SHALL lay out levels from catalog data on a serpentine path with at most five nodes per row and three rows per sheet. Progress SHALL be stored by `packId/levelId`; the next unsolved proof SHALL lock until its predecessor is solved, while additional sheets remain reachable through pack path connections.46+47+#### Scenario: A catalog populates INDEX sheets48+- **WHEN** a catalog contains more than fifteen levels49+- **THEN** INDEX creates additional sheets, keeps at most fifteen nodes per sheet, and connects adjacent sheets50+51+#### Scenario: Solved progress unlocks the next proof52+- **WHEN** the player solves `core/intro` and returns to INDEX53+- **THEN** progress marks that node solved and unlocks the next proof under the same stable key54+55+#### Scenario: A locked proof rejects entry56+- **WHEN** the player walks onto a locked proof before its predecessor is solved57+- **THEN** INDEX indicates the lock, emits an error feedback event, and keeps the cursor on the prior proof58+59+### Requirement: The board SHALL render ink-and-signal geometry without decorative overlap60+The board renderer SHALL draw a flat near-black slab with hard offset lip, corner dots, merged walls, fixed cell geometry, and stable draw order. Word tiles SHALL use 74% cells, emoji things 60%, operators outline pills, properties chamfered tiles, and sentence bars SHALL come from physical spans in `state.sentences` in the tile gutters. The horizontal sentence bar SHALL span from 0.13 to 0.91 of each cell, and player-facing board text SHALL use Recursive MONO 1. Only real engine stacks may overlap.61+62+#### Scenario: A sentence bar follows a physical span63+- **WHEN** `state.sentences` contains a horizontal or vertical span64+- **THEN** the board draws its bar along that span and does not reconstruct a semantic rule from text65+66+#### Scenario: No-YOU remains recoverable67+- **WHEN** no object has `YOU` after a settled turn68+- **THEN** the board dims, displays `NOTHING IS YOU`, and shows undo/restart recovery hints69+70+#### Scenario: Unknown nouns remain readable71+- **WHEN** an object noun has no emoji mapping72+- **THEN** the renderer displays the noun as text within the fixed cell without changing grid geometry73+74+### Requirement: Juice and audio SHALL consume settled events75+Visual motion and audio SHALL be derived from settled engine results and SHALL never mutate simulation state. Movement, pushes, blocks, rule formation/removal, transformations, sink, defeat, win, undo, restart, and scene changes SHALL have deterministic timing categories. Reduced motion SHALL snap animations or use a one-frame outline. Audio SHALL preload buffers, unlock on first gesture, keep music off by default, and persist mute state.76+77+#### Scenario: Settled events drive feedback without changing state78+- **WHEN** the engine returns an accepted or blocked settled event79+- **THEN** juice and audio derive their projections from that event and leave the simulation state unchanged80+81+### Requirement: Juice SHALL animate physical sentence spans and event details82+Juice SHALL expose sentence frames keyed by the physical cell ids joined with `>`, with `phase` values `forming`, `active`, or `removing`, progress, and frozen cells for a removed span. Snapping SHALL settle movement only and SHALL allow rule bars and particles to finish. The frame SHALL expose source tile bounce at 1.14x for 200ms, transform or restore pop from 0.55x to 1x with a 240ms back ease, 4px sink and defeat shake, 140ms undo tint, 260ms restart wipe, a 260ms win beat, and rule sparks containing text, cell, color role, and start time alongside destruction letters.83+84+#### Scenario: A removed sentence keeps its physical trace85+- **WHEN** a settled event removes a rule sentence86+- **THEN** the juice frame reports the sentence by its cell-id key in `removing` phase with frozen pre-move cells until its removal animation completes87+88+#### Scenario: A new input snaps movement without cutting rule motion89+- **WHEN** the player issues a new intent while a movement tween and a rule formation are active90+- **THEN** movement reaches its endpoint immediately while the rule formation remains available to complete91+92+#### Scenario: A pushed entity gets distinct feedback93+- **WHEN** an accepted event contains a movement with non-null `pushedBy`94+- **THEN** juice marks it as pushed for the shorter squash and audio can play the push/clack category95+96+#### Scenario: A blocked move gives reason-specific feedback97+- **WHEN** a settled event reports a `stop` or `bounds` block98+- **THEN** the stage nudges and shakes with the corresponding amplitude and chooses the matching sound category without incrementing moves99+100+#### Scenario: Reduced motion removes tween waits101+- **WHEN** reduced motion is enabled102+- **THEN** movement, rule, contact, and scene visuals settle immediately while state and accessibility feedback remain correct103+104+### Requirement: Workshop and Gift SHALL connect to the pack service105+The stage SHALL connect Workshop actions to the presentation-independent pack service hooks, support drag-and-drop `.cjs` preview/import, keep URL intake pending until Gift confirmation, and expose in-canvas feedback for success, validation errors, collisions, copy, export, and removal. Workshop and Gift layout SHALL follow the designer reference and remain separate from pack validation logic.106+107+#### Scenario: Workshop imports a trusted module108+- **WHEN** the player chooses Import or drops a `.cjs` file and confirms the trusted path109+- **THEN** the stage previews and installs it through pack-service hooks, then offers the new pack on INDEX110+111+#### Scenario: Duplicate pack uses explicit choice112+- **WHEN** a pack id already exists during Workshop or Gift installation113+- **THEN** the stage offers Replace, Keep both as a copy, or Cancel and does not silently mutate the registry114+115+#### Scenario: Pack errors are readable116+- **WHEN** validation fails with more than five details117+- **THEN** the stage shows the first five plain-language errors, an `and N more` summary, and a Copy details action118+119+#### Scenario: Gift shows a normalized preview and collision120+- **WHEN** a valid share or local module preview returns a pack that already exists121+- **THEN** Gift shows the pack name, author, level count, first-level preview, and `ALREADY HERE` with Replace, Keep both as a copy, or Not now choices before installation122+123+### Requirement: The stage SHALL provide accessible and font-stable output124+The stage SHALL keep a polite live region for rule, level, no-YOU, win, and pack status; all functions SHALL be reachable from keyboard, pad, and touch; and the player-facing font SHALL be the self-hosted Recursive instance with `MONO 1` and `CASL 0`. Reduced-motion and contrast behavior SHALL preserve readable text independent of animation or color.125+126+#### Scenario: A rule change is announced127+- **WHEN** a settled event adds or removes a rule128+- **THEN** the live region announces the formed or broken sentence and the current rule state in readable text129+130+#### Scenario: A font is available offline131+- **WHEN** the app is loaded without network access132+- **THEN** player-facing stage text uses the bundled Recursive font rather than a remote font request133+134+### Requirement: Stage copy SHALL use drawn keycaps and proof language135+Player-facing control hints SHALL render keycaps with adjacent verbs, such as `[↑↓] choose [ENTER] select [ESC] not now`, rather than typed middle-dot separators. Gift copy SHALL use `A PACK FOR YOU`, sentence-case bylines such as `by Dami`, and `proofs`; scene metadata and headings SHALL not call the content a garden. The title scene SHALL be playable by walking the cat into `DOOR IS WIN`; Enter may skip to INDEX.136+137+#### Scenario: Reduced motion preserves control copy138+- **WHEN** reduced motion is enabled139+- **THEN** the same drawn keycap and verb hints remain readable without relying on transition animation
A openspec/changes/build-ink-signal-stage/specs/playable-puzzles/spec.md +42 -0
@@ -0,0 +1,42 @@
1+## MODIFIED Requirements2+3+### Requirement: The app SHALL provide a catalog-backed INDEX of proofs4+The app SHALL expose valid catalog levels through the INDEX scene rather than a flat level list. The original three MIAU teaching proofs SHALL remain in the built-in catalog, while additional curated or installed packs add proof nodes and sheets. Each proof SHALL retain stable `packId` and `levelId` identity and ordered progress metadata.5+6+#### Scenario: The built-in catalog starts at MIAU teaching proofs7+- **WHEN** the app opens without imported content8+- **THEN** INDEX selects the first MIAU proof and lays out the rest of the built-in catalog from data9+10+#### Scenario: A pack adds an INDEX sheet11+- **WHEN** a valid pack is installed12+- **THEN** its stable proof ids appear on an INDEX sheet without replacing the built-in teaching proofs13+14+#### Scenario: Completing a proof advances only by explicit input15+- **WHEN** the active proof reaches its win state16+- **THEN** the win card waits for Enter before returning to INDEX and marking the proof solved17+18+### Requirement: The app SHALL expose reset, undo, and cardinal controls19+The app SHALL provide reset and undo actions and SHALL accept up, down, left, and right intents from keyboard, Standard gamepad, and touch input. Controls SHALL use the shared input adapter and SHALL report blocked, undo, reset, and win outcomes through stage feedback without queuing actions behind animation.20+21+#### Scenario: Keyboard and touch produce the same move22+- **WHEN** the player uses a keyboard arrow or a qualifying touch swipe23+- **THEN** the engine receives the same cardinal intent and the resulting state is identical24+25+#### Scenario: Gamepad confirms a proof26+- **WHEN** the player presses Standard gamepad A on an available INDEX node27+- **THEN** the stage opens that proof through the same selection path as Enter28+29+#### Scenario: Reset is available after a deadlock30+- **WHEN** the player has made moves that cannot reach the goal31+- **THEN** restart restores the initial proof and move count without reloading the page32+33+#### Scenario: Undo is disabled at the initial state34+- **WHEN** the current proof has no successful moves35+- **THEN** undo emits no engine action and its stage hint is unavailable36+37+### Requirement: Level definitions SHALL be validated before play38+The app SHALL reject a catalog level whose dimensions, coordinates, entity ids, word text, or object nouns are invalid, and SHALL report the proof identity and validation problem in developer-visible diagnostics.39+40+#### Scenario: An out-of-bounds entity is rejected41+- **WHEN** a level definition places an entity outside its declared width or height42+- **THEN** the proof does not start and validation reports its pack/level identity and offending entity
A openspec/changes/build-ink-signal-stage/tasks.md +46 -0
@@ -0,0 +1,46 @@
1+## 1. Stage integration2+3+- [x] 1.1 Integrate a full-viewport DPR-capped canvas loop with resize handling, pointer behavior, and the accessibility live region.4+- [x] 1.2 Connect `src/stage.js` and scene modules to one scene stack for Title, INDEX, Proof, Pause, Win, Workshop, and Gift.5+- [x] 1.3 Hide header/sidebar/footer during default play and keep interim controls available only behind `?dev`.6+- [x] 1.4 Add cat-centered iris transitions, explicit win continuation, and reduced-motion cuts.7+8+## 2. Input and INDEX progression9+10+- [x] 2.1 Implement and unit-test immediate keyboard repeat, most-recent-direction takeover, undo acceleration, gamepad mapping, and touch swipe thresholds in `src/input.js`.11+- [x] 2.2 Wire the input adapter into the stage so no legacy listener duplicates an engine intent or queues behind juice.12+- [x] 2.3 Implement and unit-test serpentine INDEX layout, multi-sheet navigation, stable `packId/levelId` progress, and locked-node behavior in `src/scenes/map.js`.13+- [x] 2.4 Integrate INDEX navigation and progress into the stage scene flow, including solved proof return and pack sheet traversal.14+15+## 3. Ink-and-signal board and copy16+17+- [x] 3.1 Implement and unit-test board geometry, hard-edged slab, no-overlap tile roles, stacks, walls, unknown noun fallback, and no-YOU rendering in `src/render/board.js`.18+- [x] 3.2 Integrate board rendering into the stage with integer cells, HUD, ledger, hint, keycap controls, and responsive desktop/mobile zones.19+- [x] 3.3 Consume `state.sentences` for maximal horizontal/vertical sentence bars, including exact duplicates and stacked-word paths; use the 0.13–0.91 gutter bar and Recursive MONO 1.20+- [ ] 3.4 Verify copy deck labels, accessibility live-region announcements, contrast, and static reduced-motion output against the designer reference.21+- [x] 3.5 Replace typed middle-dot hints with drawn keycap and verb pairs, use proof terminology and Gift copy, and verify the playable title micro-proof.22+23+## 4. Event-driven juice and audio24+25+- [x] 4.1 Implement and unit-test event-driven movement, push, rule, block, destruction, undo, and reduced-motion timing in `src/juice.js`.26+- [x] 4.2 Connect juice snapshots to stage frames and snap active tweens before each new engine intent.27+- [x] 4.3 Implement Web Audio preload/unlock, mute persistence, event categories, and music-off default in `src/audio.js`.28+- [x] 4.4 Map the supplied CC0/Kenney clips to the final stage event categories, list copied clips in `public/audio/LICENSE.txt`, and verify graceful missing-audio behavior.29+- [x] 4.5 Add sentence-keyed juice frames with forming/active/removing phases and frozen removed spans, plus bounce, transform pop, sink/defeat shake, undo tint, restart wipe, win beat, and rule spark details.30+31+## 5. Workshop, Gift, and pack entry points32+33+- [x] 5.1 Implement and unit-test presentation-independent pack-service hooks and explicit preview-before-install behavior.34+- [x] 5.2 Connect Workshop Import, Play, Share link, Export `.cjs`, Remove, and duplicate Replace/Keep-both choices to scenes.35+- [x] 5.3 Connect drag-and-drop `.cjs` preview and trusted import to the stage without executing URL payloads.36+- [x] 5.4 Route `#pack=` to pending Gift confirmation with `Keep and play` and `Not now`; remove automatic installation from the player flow.37+- [x] 5.5 Keep legacy pack controls hidden by default and available only through `?dev` while stage integration is reviewed.38+- [x] 5.6 Consume normalized preview views, duplicate conflict metadata, and structured no-throw service errors in Workshop and Gift feedback.39+40+## 6. Font and browser acceptance41+42+- [x] 6.1 Add the self-hosted Recursive font asset and CSS declaration for the documented font settings.43+- [x] 6.2 Remove the player-facing remote font import and verify offline font loading.44+- [x] 6.3 Run browser acceptance for title/index/proof/pause/win flow, keyboard/gamepad/touch input, rule changes, undo/reset, sentence bars, pack Workshop/Gift, drag/drop, export, URL confirmation, and error feedback.45+- [x] 6.4 Repeat browser acceptance at mobile width, reduced motion, `?dev`, and with audio unavailable; confirm no console errors.46+- [x] 6.5 Run the full Node suite and production build after stage integration and record any designer review follow-ups.