clone

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

docs: archive verified dynamic-rule OpenSpec change

Dami committed f218e31
commit
f218e31713a62f13c79e009daf3bd94fb763f9a9
parent
a3ca476136883d0b424d6a59b4ea91a946ff0ab6
author
Dami <dami.etoile@gmail.com>
13 files changed, +321 -44
Ropenspec/changes/build-dynamic-rule-engine/.openspec.yaml → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/.openspec.yaml0
Ropenspec/changes/build-dynamic-rule-engine/design.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/design.md9+-
Ropenspec/changes/build-dynamic-rule-engine/proposal.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/proposal.md0
Ropenspec/changes/build-dynamic-rule-engine/specs/commonjs-mods/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/commonjs-mods/spec.md0
Ropenspec/changes/build-dynamic-rule-engine/specs/game-presentation/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/game-presentation/spec.md0
Ropenspec/changes/build-dynamic-rule-engine/specs/playable-puzzles/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/playable-puzzles/spec.md0
Ropenspec/changes/build-dynamic-rule-engine/specs/rule-engine/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/rule-engine/spec.md40++++++++++
Aopenspec/changes/archive/2026-09-23-build-dynamic-rule-engine/tasks.md43+++++++++++
Dopenspec/changes/build-dynamic-rule-engine/tasks.md40----------
Aopenspec/specs/commonjs-mods/spec.md34+++++++++
Aopenspec/specs/game-presentation/spec.md41++++++++++
Aopenspec/specs/playable-puzzles/spec.md38++++++++++
Aopenspec/specs/rule-engine/spec.md120++++++++++++++++++++++++++++++
R openspec/changes/build-dynamic-rule-engine/.openspec.yaml → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/.openspec.yaml +0 -0

No content changes.

R openspec/changes/build-dynamic-rule-engine/design.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/design.md +5 -4
@@ -32,15 +32,15 @@ Levels contain `width`, `height`, metadata, and an `objects` array. Each object
3232 3333 ### 3. Parse only straight `NOUN IS VALUE` lines with `AND`3434 35-Rule scanning examines left-to-right and top-to-bottom cardinal lines. `AND` expands adjacent nouns or values into multiple rule records. Values are either one of the six properties or a noun. Malformed lines are ignored. The active rule table is replaced atomically after word movement and after noun transformation. A map keyed by subject/value pair removes duplicate records while retaining deterministic discovery order.35+Rule scanning examines left-to-right and top-to-bottom cardinal lines. `AND` expands adjacent nouns or values into multiple rule records. Values are either one of the six properties or a noun. Malformed lines are ignored. The active rule table is replaced atomically after word movement and after noun transformation. Each rule records source word IDs in reading order and an axis for board highlights. A map keyed by subject/value pair removes duplicate records while retaining deterministic discovery order and source occurrences.3636 3737 ### 4. Resolve one accepted cardinal intent as one transaction3838 39-The engine snapshots before movement, resolves controlled entities in a stable direction-aware order, recursively plans push chains, and commits only successful movement. It then re-scans rules, applies noun transformations, re-scans again, resolves sink/defeat/win contacts, and emits presentation events. A failed push is a no-op and does not consume a move. This order makes the consequences of editing a rule visible on the next settled state.39+The engine snapshots before movement, resolves controlled entities in a stable direction-aware order, recursively plans push chains, and commits only successful movement. It then re-scans rules, applies noun transformations, re-scans again, resolves sink/defeat contacts, evaluates win, and returns a settled event record. Every result uses `kind: 'step' | 'undo' | 'reset'`. Movement entries are `{ id, from, to, pushedBy }`; direct movers use `pushedBy: null`, while each pushed entity names its immediate pusher. A physical block is `{ id, dir, blockerId, reason }`, with `blockerId: null` at a boundary and `reason: 'stop' | 'bounds'`. Rule deltas are full records with `subject`, `predicate`, `sourceIds`, and `axis`. Destruction entries retain `{ id, noun|text, at, cause: 'sink' | 'defeat' }`, and settled results include `controlLost` plus the final win state. Arrays preserve resolution order: movement, transformation, destruction, then win evaluation. A failed push returns `kind: 'step'`, `accepted: false`, is a no-op, and does not consume a move. This order makes the consequences of editing a rule visible on the next settled state.4040 4141 ### 5. Make undo a snapshot stack4242 43-Successful moves push a deep clone of the complete pre-turn state. `undo` restores the last snapshot, including rules, effects, move count, and win state. Reset rebuilds the original state and clears history. The initial MVP can cap history at a fixed size, provided the cap is documented and deterministic.43+Successful moves push a deep clone of the complete pre-turn state. `undo` restores the last snapshot, including rules, effects, move count, win state, and control state, and returns `kind: 'undo'` with reverse movement and rule deltas. Reset rebuilds the original state, clears history, and returns `kind: 'reset'`. The initial MVP can cap history at a fixed size, provided the cap is documented and deterministic.4444 4545 ### 6. Keep the CommonJS manifest narrow4646
@@ -57,8 +57,9 @@ Small level fixtures cover parser expansion, push success/failure, transformatio
5757 ## Risks / Trade-offs5858 5959 - [Overlapping words create ambiguous sentences] → Scan only cardinal straight lines, require explicit `IS`, and deduplicate exact rule records; add fixtures for shared cells.60-- [Multiple YOU entities can produce order-dependent pushes] → Sort by direction projection and stable id, document the ordering, and test multi-entity moves.60+- [Multiple YOU entities can produce order-dependent pushes] → Sort by direction projection and stable id, document the ordering, include `pushedBy` on each movement entry, and test multi-entity moves.6161 - [Contact effects can conflict] → Use a fixed MVP order and explicit win/defeat behavior; postpone extensible priorities until a concrete level requires them.62+- [Event consumers can animate the wrong phase] → Preserve movement, transformation, destruction, and win ordering in the event contract and keep full rule source records for sentence bars.6263 - [Trusted CommonJS can execute arbitrary browser code] → Keep the loader local and opt-in, validate manifests, isolate module errors, and document that untrusted mods are unsupported.6364 - [Emoji widths and animation can harm readability] → Use fixed cells, text fallbacks, accessible labels, and reduced-motion-safe static feedback.6465 - [A tiny vocabulary may constrain level design] → Preserve the manifest vocabulary extension point and add capabilities only with new fixtures and OpenSpec changes.
R openspec/changes/build-dynamic-rule-engine/proposal.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/proposal.md +0 -0

No content changes.

R openspec/changes/build-dynamic-rule-engine/specs/commonjs-mods/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/commonjs-mods/spec.md +0 -0

No content changes.

R openspec/changes/build-dynamic-rule-engine/specs/game-presentation/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/game-presentation/spec.md +0 -0

No content changes.

R openspec/changes/build-dynamic-rule-engine/specs/playable-puzzles/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/playable-puzzles/spec.md +0 -0

No content changes.

R openspec/changes/build-dynamic-rule-engine/specs/rule-engine/spec.md → openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/specs/rule-engine/spec.md +40 -0
@@ -74,3 +74,43 @@ The engine SHALL save the complete pre-turn state before each successful movemen
7474 #### Scenario: Reset returns to the level start7575 - **WHEN** the caller invokes `reset` after any sequence of moves and effects7676 - **THEN** the initial entities and rules are restored, move count is zero, win is false, and undo history is empty77+78+### Requirement: Active rules SHALL identify their source words79+Each active rule SHALL expose the word entity IDs that formed it in reading order and whether the sentence runs horizontally or vertically. Duplicate sentences at different locations SHALL retain deterministic source information.80+81+#### Scenario: A horizontal sentence reports its source82+- **WHEN** adjacent word entities form `MIAU IS YOU` from left to right83+- **THEN** the resulting rule identifies those word IDs in order and reports horizontal axis84+85+#### Scenario: A vertical sentence reports its source86+- **WHEN** adjacent word entities form `ROCK IS PUSH` from top to bottom87+- **THEN** the resulting rule identifies those word IDs in order and reports vertical axis88+89+### Requirement: Engine actions SHALL return settled turn events90+`step`, `undo`, and `reset` SHALL return an event record after resolving state. Every record SHALL have `kind: 'step' | 'undo' | 'reset'`, `accepted`, ordered `moved`, full-record `rulesAdded` and `rulesRemoved`, ordered `transformed`, ordered `destroyed`, `controlLost`, and final `won` fields. A blocked or invalid step SHALL retain `kind: 'step'`, report `accepted: false`, and leave state unchanged.91+92+For movement, each entry SHALL be `{ id, from, to, pushedBy }`, where `pushedBy` is the immediate pusher's entity id or `null` for a directly controlled mover. A physical block SHALL be `{ id, dir, blockerId, reason }`, where `blockerId` is the blocking entity id or `null` at the board edge and `reason` is `stop` or `bounds`. Rule delta entries SHALL be complete rule records with `subject`, `predicate`, `sourceIds`, and `axis`. Destroyed entries SHALL be `{ id, noun|text, at, cause }` with `cause` `sink` or `defeat`. Arrays SHALL preserve engine resolution order: movement, transformation, destruction, then win evaluation. `controlLost` SHALL be true when the settled state has no `YOU` entity.93+94+#### Scenario: Breaking a rule produces a rule delta95+- **WHEN** a successful push moves a word out of a sentence96+- **THEN** the step result lists the word movement with `from`, `to`, and `pushedBy`, and lists the removed rule as a full record retaining its former `sourceIds` and `axis`97+98+#### Scenario: A blocked push produces no state change99+- **WHEN** a push chain is blocked by `STOP` or the board edge100+- **THEN** the step result has `kind: 'step'`, reports `accepted: false`, identifies `id`, `dir`, `blockerId`, and `reason`, and leaves move count and history unchanged101+102+#### Scenario: Undo reports a restored state103+- **WHEN** a successful move is undone104+- **THEN** the undo result has `kind: 'undo'`, reports `accepted: true`, the reverse movement with `pushedBy: null`, the rule changes, `controlLost`, and the restored win state105+106+#### Scenario: Contact destruction includes presentation details107+- **WHEN** a `SINK` or `DEFEAT` interaction removes an entity108+- **THEN** the step result lists its id, noun or text, cell coordinate, and `cause` in the destruction order109+110+#### Scenario: Control loss is explicit111+- **WHEN** a settled step removes the last entity with the `YOU` property112+- **THEN** the step result reports `controlLost: true` even though the state remains inspectable for the loss feedback113+114+#### Scenario: Event arrays preserve resolution order115+- **WHEN** one accepted step moves entities, transforms an object, destroys contacts, and evaluates a win116+- **THEN** the event arrays retain movement order first, transformation order second, destruction order third, and the final win result after those phases
A openspec/changes/archive/2026-09-23-build-dynamic-rule-engine/tasks.md +43 -0
@@ -0,0 +1,43 @@
1+## 1. Engine contract and fixtures2+3+- [x] 1.1 Define the level, entity, rule, and snapshot data shapes used by the engine and tests.4+- [x] 1.2 Add fixtures for straight-line `IS` parsing, `AND` expansion, malformed lines, duplicate words, and shared cells.5+- [x] 1.3 Add fixtures for the six properties and noun-to-noun transformation, including re-scan after a word move.6+- [x] 1.4 Preserve source word IDs and sentence axis on active rule records, including stacked and duplicate sources.7+8+## 2. Deterministic turn resolution9+10+- [x] 2.1 Implement cardinal `YOU` intent handling with stable ordering for multiple controlled entities.11+- [x] 2.2 Implement atomic push-chain planning, default pushability for word entities, bounds checks, and `STOP` blocking.12+- [x] 2.3 Implement post-move transformation and deterministic `SINK`, `DEFEAT`, and `WIN` contact effects.13+- [x] 2.4 Implement complete snapshot undo, reset, move counts, win lockout, and no-op behavior for invalid or blocked intents.14+- [x] 2.5 Verify engine fixtures cover every requirement scenario and run the engine test command.15+- [x] 2.6 Return settled `step`, `undo`, and `reset` events with `kind`, accepted state, ordered movement, full rule deltas, transformations, destruction details, `controlLost`, and win state.16+- [x] 2.7 Add event fixtures for `blocked: { id, dir, blockerId, reason }`, direct versus pushed movement via `pushedBy`, and ordered movement/transform/destroy/win phases.17+18+## 3. Three built-in levels and input19+20+- [x] 3.1 Define and validate the three ordered MIAU teaching puzzles covering movement, pushing, rule editing, `STOP`, `WIN`, and `AND` subject expansion.21+- [x] 3.2 Add the app shell and level selection state for the three built-in levels.22+- [x] 3.3 Route keyboard arrows, touch direction controls, undo, reset, and continue through the common engine API.23+- [x] 3.4 Display move count, undo availability, level title/hint, victory, loss of control, and blocked-move feedback.24+25+## 4. CommonJS mod boundary26+27+- [x] 4.1 Define the local CommonJS export shape for `manifest`, `levels`, `vocabulary`, and `palette`.28+- [x] 4.2 Implement manifest validation with duplicate, coordinate, vocabulary, and level diagnostics.29+- [x] 4.3 Register valid mod content atomically and isolate loading or import failures without removing built-in content.30+- [x] 4.4 Add fixtures for a valid noun/emoji/level mod and rejected or throwing modules.31+32+## 5. Emoji and text presentation33+34+- [x] 5.1 Implement fixed-coordinate grid rendering with stable stack order and noun text fallback.35+- [x] 5.2 Render word tiles and the active rule table from settled engine state.36+- [x] 5.3 Add Pretext layout for hints and visual emphasis for rules and turn feedback without coupling to grid geometry.37+- [x] 5.4 Add accessible labels, readable text status, and reduced-motion-safe presentation for all controls and outcomes.38+39+## 6. Integration verification40+41+- [x] 6.1 Run the full test suite and production build after wiring the browser shell.42+- [x] 6.2 Run browser smoke checks for built-in start, keyboard/touch movement, word push/rule update, undo/reset, win progression, and mod fallback.43+- [x] 6.3 Update implementation notes or the relevant OpenSpec artifacts for any behavior discovered during verification.
D openspec/changes/build-dynamic-rule-engine/tasks.md +0 -40
@@ -1,40 +0,0 @@
1-## 1. Engine contract and fixtures2-3-- [ ] 1.1 Define the level, entity, rule, snapshot, and settled turn event types used by the engine and tests.4-- [ ] 1.2 Add fixtures for straight-line `IS` parsing, `AND` expansion, malformed lines, duplicate words, and shared cells.5-- [ ] 1.3 Add fixtures for the six properties and noun-to-noun transformation, including re-scan after a word move.6-7-## 2. Deterministic turn resolution8-9-- [ ] 2.1 Implement cardinal `YOU` intent handling with stable ordering for multiple controlled entities.10-- [ ] 2.2 Implement atomic push-chain planning, default pushability for word entities, bounds checks, and `STOP` blocking.11-- [ ] 2.3 Implement post-move transformation and deterministic `SINK`, `DEFEAT`, and `WIN` contact effects.12-- [ ] 2.4 Implement complete snapshot undo, reset, move counts, win lockout, and no-op behavior for invalid or blocked intents.13-- [ ] 2.5 Verify engine fixtures cover every requirement scenario and run the engine test command.14-15-## 3. Three built-in levels and input16-17-- [ ] 3.1 Define and validate the three ordered MIAU teaching puzzles covering movement, pushing, rule editing, `STOP`, `WIN`, and `AND` subject expansion.18-- [ ] 3.2 Add the app shell and level selection state for the three built-in levels.19-- [ ] 3.3 Route keyboard arrows, touch direction controls, undo, reset, and continue through the common engine API.20-- [ ] 3.4 Display move count, undo availability, level title/hint, victory, defeat, and blocked-move feedback.21-22-## 4. CommonJS mod boundary23-24-- [ ] 4.1 Define the local CommonJS export shape for `manifest`, `levels`, `vocabulary`, and `palette`.25-- [ ] 4.2 Implement manifest validation with duplicate, coordinate, vocabulary, and level diagnostics.26-- [ ] 4.3 Register valid mod content atomically and isolate loading or import failures without removing built-in content.27-- [ ] 4.4 Add fixtures for a valid noun/emoji/level mod and rejected or throwing modules.28-29-## 5. Emoji and text presentation30-31-- [ ] 5.1 Implement fixed-coordinate grid rendering with stable stack order and noun text fallback.32-- [ ] 5.2 Render word tiles and the active rule table from settled engine state.33-- [ ] 5.3 Add Pretext-backed or equivalent effects for hints, rule emphasis, transformations, and turn feedback without coupling to grid geometry.34-- [ ] 5.4 Add accessible labels, readable text status, and reduced-motion-safe presentation for all controls and outcomes.35-36-## 6. Integration verification37-38-- [ ] 6.1 Run the full test suite and production build after wiring the browser shell.39-- [ ] 6.2 Run browser smoke checks for built-in start, keyboard/touch movement, word push/rule update, undo/reset, win progression, and mod fallback.40-- [ ] 6.3 Update implementation notes or the relevant OpenSpec artifacts for any behavior discovered during verification.
A openspec/specs/commonjs-mods/spec.md +34 -0
@@ -0,0 +1,34 @@
1+# commonjs-mods Specification2+3+## Purpose4+TBD - created by archiving change build-dynamic-rule-engine. Update Purpose after archive.5+## Requirements6+### Requirement: The app SHALL load local CommonJS mods through a validated manifest7+The mod loader SHALL load local CommonJS source and read its `module.exports` object containing a required `manifest` (`id`, `name`, and semantic `version`), one or more `levels`, and optional `vocabulary` and `palette` records. The host SHALL validate the complete export before registering any content and SHALL keep built-in content available when a mod fails validation.8+9+#### Scenario: A valid mod registers a level10+- **WHEN** a local module exports a valid manifest, level definition, vocabulary, and palette11+- **THEN** the loader registers its level and uses its palette for noun presentation under that mod id12+13+#### Scenario: A malformed mod is rejected14+- **WHEN** a module has no id, duplicate identifiers, invalid coordinates, or unsupported vocabulary values15+- **THEN** the loader rejects it with a diagnostic and does not partially register its content16+17+### Requirement: Mods SHALL use the host engine contract18+Mods SHALL define vocabulary, palette, and level data through the exported object. Mods SHALL NOT directly mutate the active rule table, DOM, renderer scheduler, or persisted engine state; the host SHALL pass validated level data to the engine.19+20+#### Scenario: A mod adds a noun and emoji21+- **WHEN** a manifest declares noun `KEKE` and its emoji representation22+- **THEN** levels from that mod can place `KEKE` objects and the presentation uses the declared mapping23+24+#### Scenario: A mod cannot bypass the engine state contract25+- **WHEN** a mod supplies malformed level state or attempts to change state through data outside the documented manifest fields26+- **THEN** the host registers only validated manifest, vocabulary, palette, and level data and keeps engine state under host control27+28+### Requirement: Mod loading SHALL be deterministic for the local MVP29+The loader SHALL run local mods with the host's documented CommonJS compatibility layer, allow only the host's supported helper import, and avoid URL or package resolution. Module execution and validation errors SHALL be isolated to the affected mod and shown in diagnostics.30+31+#### Scenario: A mod error does not break built-in play32+- **WHEN** a local module throws while loading or requests an unsupported import33+- **THEN** the app reports the mod error and keeps built-in levels playable34+
A openspec/specs/game-presentation/spec.md +41 -0
@@ -0,0 +1,41 @@
1+# game-presentation Specification2+3+## Purpose4+TBD - created by archiving change build-dynamic-rule-engine. Update Purpose after archive.5+## Requirements6+### Requirement: The app SHALL render the simulation as a readable emoji grid7+The renderer SHALL display each grid cell at a stable coordinate with stacked entities in a consistent visual order. Object nouns SHALL use emoji mappings, word entities SHALL remain legible as text tiles, and the board SHALL expose enough contrast and spacing to distinguish adjacent cells and stacked words.8+9+#### Scenario: Stacked entities remain visible10+- **WHEN** multiple objects or words occupy one cell11+- **THEN** the renderer preserves their identity and indicates the stack without changing simulation occupancy12+13+#### Scenario: A modded noun has no emoji fallback14+- **WHEN** a noun has no declared emoji mapping15+- **THEN** the renderer uses a readable text fallback containing the noun instead of hiding the entity16+17+### Requirement: The app SHALL show active rules and turn feedback18+The presentation SHALL display the currently active rules beside or below the board and SHALL update them after each settled turn. It SHALL provide visible feedback for blocked movement, undo, reset, transformations, defeat, and victory without changing engine state.19+20+#### Scenario: Active rules update after a word push21+- **WHEN** a word tile is pushed into or out of a sentence22+- **THEN** the displayed rule list reflects the engine's new active rule table after the turn23+24+#### Scenario: Victory is readable25+- **WHEN** the engine enters the won state26+- **THEN** the app shows a victory message and a clear continue or reset action27+28+### Requirement: Text effects SHALL support variable-length labels without driving grid geometry29+The app MAY use Pretext or an equivalent text-effect layer for animated labels, rule emphasis, hints, and feedback. Text effects SHALL be laid out within the UI surface and SHALL not determine the integer coordinates or collision geometry of the simulation grid.30+31+#### Scenario: A long hint does not move cells32+- **WHEN** a level hint is longer than the available sidebar width33+- **THEN** the hint wraps or scrolls while every grid cell retains its original coordinate and size34+35+### Requirement: The presentation SHALL provide accessible control feedback36+The app SHALL expose text labels or accessible names for movement, undo, reset, and continue controls and SHALL communicate rule and level status in a readable text representation in addition to animation and emoji.37+38+#### Scenario: Controls are understandable without animation39+- **WHEN** reduced motion is enabled or animation is unavailable40+- **THEN** the board, active rules, current level, and control actions remain understandable from static text and emoji41+
A openspec/specs/playable-puzzles/spec.md +38 -0
@@ -0,0 +1,38 @@
1+# playable-puzzles Specification2+3+## Purpose4+TBD - created by archiving change build-dynamic-rule-engine. Update Purpose after archive.5+## Requirements6+### Requirement: The app SHALL provide three built-in MIAU teaching puzzles7+The app SHALL ship exactly three deterministic ordered built-in levels using `MIAU` as the initial controlled noun. The puzzles SHALL cover movement and pushing, `YOU`, `PUSH` and `STOP`, word manipulation, `WIN`, and `AND` subject expansion; each level SHALL include dimensions, entities, and title or hint metadata.8+9+#### Scenario: The first MIAU level is playable without a mod10+- **WHEN** the app is started with no mod selected11+- **THEN** it loads the first MIAU level and displays its board, word tiles, and current rules12+13+#### Scenario: Completing a teaching puzzle advances the campaign14+- **WHEN** the active level reaches its win state and the player chooses continue15+- **THEN** the app loads the next built-in level while preserving the campaign order16+17+### Requirement: The app SHALL expose reset, undo, and cardinal controls18+The app SHALL provide reset and undo actions and SHALL accept up, down, left, and right intents from keyboard input and touch controls. Controls SHALL call the same engine intent API and SHALL show the current move count and whether undo is available.19+20+#### Scenario: Keyboard and touch produce the same move21+- **WHEN** the player uses a keyboard arrow or the corresponding touch direction control22+- **THEN** the engine receives the same cardinal intent and the resulting state is identical23+24+#### Scenario: Reset is available after a deadlock25+- **WHEN** the player has made moves that cannot reach the goal26+- **THEN** reset restores the initial level and move count without reloading the page27+28+#### Scenario: Undo is disabled at the initial state29+- **WHEN** the current level has no successful moves30+- **THEN** the undo control is visibly unavailable and invoking it has no effect31+32+### Requirement: Level definitions SHALL be validated before play33+The app SHALL reject a built-in level whose dimensions, coordinates, entity ids, word text, or object nouns are invalid, and SHALL report the level id and validation problem in developer-visible diagnostics.34+35+#### Scenario: An out-of-bounds entity is rejected36+- **WHEN** a level definition places an entity outside its declared width or height37+- **THEN** the level does not start and validation reports the offending entity38+
A openspec/specs/rule-engine/spec.md +120 -0
@@ -0,0 +1,120 @@
1+# rule-engine Specification2+3+## Purpose4+TBD - created by archiving change build-dynamic-rule-engine. Update Purpose after archive.5+## Requirements6+### Requirement: The engine SHALL represent a deterministic stacked grid state7+The engine SHALL represent a level as a bounded rectangular grid containing uniquely identified object and word entities. Multiple entities, including words, SHALL be allowed in one cell, and a turn snapshot SHALL include positions, entity kinds, word text, active rules, move count, win state, and the complete history needed for undo.8+9+#### Scenario: Multiple words share a cell10+- **WHEN** two word entities occupy the same coordinate11+- **THEN** both remain addressable and rule scanning considers both word texts at that coordinate12+13+#### Scenario: Invalid movement is rejected14+- **WHEN** a caller requests a diagonal move, an out-of-bounds move, or a move after the level is won15+- **THEN** the engine leaves state and undo history unchanged16+17+### Requirement: The engine SHALL derive the MVP rule vocabulary from movable words18+The engine SHALL scan horizontal and vertical straight lines for `NOUN IS VALUE` sentences, with adjacent `AND` terms expanding the noun side or value side. The initial semantic properties SHALL be exactly `YOU`, `PUSH`, `STOP`, `WIN`, `SINK`, and `DEFEAT`; a noun-to-noun value SHALL be treated as a transformation. Rules SHALL be recomputed from the current word positions after a turn that changes board state.19+20+#### Scenario: An AND sentence expands to multiple properties21+- **WHEN** the board contains `BABA IS YOU AND PUSH` in one straight line22+- **THEN** the active rule table contains `BABA IS YOU` and `BABA IS PUSH`23+24+#### Scenario: A moved word changes the active rules25+- **WHEN** a legal move changes the position of a word entity26+- **THEN** the engine replaces the active rule table with rules parsed from the new word positions before the next player intent27+28+#### Scenario: A malformed line is ignored29+- **WHEN** a word line lacks `IS` or has no noun and value on both sides30+- **THEN** the line produces no active rule and does not make the turn fail31+32+### Requirement: The engine SHALL resolve controlled movement and pushes atomically33+Entities whose noun has `YOU` SHALL receive the requested cardinal intent. A moving entity SHALL push a contiguous chain of `PUSH` entities when every destination is in bounds and free of `STOP` entities; a blocked chain SHALL not move any member. Word entities SHALL be pushable by default so that players can edit rules.34+35+#### Scenario: A legal push moves the complete chain36+- **WHEN** a `YOU` entity moves toward one or more adjacent `PUSH` entities with an empty in-bounds destination37+- **THEN** the chain moves one cell in the requested direction and the move count increases by one38+39+#### Scenario: STOP blocks a push40+- **WHEN** a requested movement would push an entity into a `STOP` entity or outside the grid41+- **THEN** no entity moves and the move count does not increase42+43+#### Scenario: Several YOU entities use stable ordering44+- **WHEN** more than one `YOU` entity receives the same intent45+- **THEN** the engine resolves them in a documented stable order and produces the same state for the same initial state and intent46+47+### Requirement: The engine SHALL apply transformations and contact effects deterministically48+After movement, the engine SHALL apply active noun-to-noun transformations, then resolve contact effects using the MVP properties. `SINK` SHALL remove all occupants of a contact cell, `DEFEAT` SHALL remove controlled entities sharing its cell, and `WIN` SHALL mark the level won when a controlled entity shares a cell with a winning entity. The engine SHALL expose the settled state only after these effects complete.49+50+#### Scenario: A noun transforms51+- **WHEN** the active rule `BABA IS KEKE` is present after a successful turn52+- **THEN** every object with noun `BABA` becomes noun `KEKE` before the next rule scan53+54+#### Scenario: Sink removes contact occupants55+- **WHEN** an entity with `SINK` shares a cell with another entity after movement56+- **THEN** every entity in that cell is removed before the turn is committed57+58+#### Scenario: Defeat removes YOU59+- **WHEN** a `YOU` entity shares a cell with an entity having `DEFEAT`60+- **THEN** the controlled entity is removed and the level is not marked won by that contact61+62+#### Scenario: Win marks the level complete63+- **WHEN** a `YOU` entity shares a cell with an entity having `WIN`64+- **THEN** the engine marks the level won and rejects later movement until reset or progression65+66+### Requirement: The engine SHALL support complete turn undo and reset67+The engine SHALL save the complete pre-turn state before each successful movement and SHALL restore it with `undo`. `reset` SHALL restore the original level, clear history, clear the win state, and rebuild rules and derived effects.68+69+#### Scenario: Undo restores a rule edit70+- **WHEN** a successful push moves a word and the caller invokes `undo`71+- **THEN** the word positions, active rules, objects, move count, and win state match the state before that push72+73+#### Scenario: Undo with no history is harmless74+- **WHEN** the caller invokes `undo` before any successful move75+- **THEN** the engine returns a false or equivalent no-op result and leaves the state unchanged76+77+#### Scenario: Reset returns to the level start78+- **WHEN** the caller invokes `reset` after any sequence of moves and effects79+- **THEN** the initial entities and rules are restored, move count is zero, win is false, and undo history is empty80+81+### Requirement: Active rules SHALL identify their source words82+Each active rule SHALL expose the word entity IDs that formed it in reading order and whether the sentence runs horizontally or vertically. Duplicate sentences at different locations SHALL retain deterministic source information.83+84+#### Scenario: A horizontal sentence reports its source85+- **WHEN** adjacent word entities form `MIAU IS YOU` from left to right86+- **THEN** the resulting rule identifies those word IDs in order and reports horizontal axis87+88+#### Scenario: A vertical sentence reports its source89+- **WHEN** adjacent word entities form `ROCK IS PUSH` from top to bottom90+- **THEN** the resulting rule identifies those word IDs in order and reports vertical axis91+92+### Requirement: Engine actions SHALL return settled turn events93+`step`, `undo`, and `reset` SHALL return an event record after resolving state. Every record SHALL have `kind: 'step' | 'undo' | 'reset'`, `accepted`, ordered `moved`, full-record `rulesAdded` and `rulesRemoved`, ordered `transformed`, ordered `destroyed`, `controlLost`, and final `won` fields. A blocked or invalid step SHALL retain `kind: 'step'`, report `accepted: false`, and leave state unchanged.94+95+For movement, each entry SHALL be `{ id, from, to, pushedBy }`, where `pushedBy` is the immediate pusher's entity id or `null` for a directly controlled mover. A physical block SHALL be `{ id, dir, blockerId, reason }`, where `blockerId` is the blocking entity id or `null` at the board edge and `reason` is `stop` or `bounds`. Rule delta entries SHALL be complete rule records with `subject`, `predicate`, `sourceIds`, and `axis`. Destroyed entries SHALL be `{ id, noun|text, at, cause }` with `cause` `sink` or `defeat`. Arrays SHALL preserve engine resolution order: movement, transformation, destruction, then win evaluation. `controlLost` SHALL be true when the settled state has no `YOU` entity.96+97+#### Scenario: Breaking a rule produces a rule delta98+- **WHEN** a successful push moves a word out of a sentence99+- **THEN** the step result lists the word movement with `from`, `to`, and `pushedBy`, and lists the removed rule as a full record retaining its former `sourceIds` and `axis`100+101+#### Scenario: A blocked push produces no state change102+- **WHEN** a push chain is blocked by `STOP` or the board edge103+- **THEN** the step result has `kind: 'step'`, reports `accepted: false`, identifies `id`, `dir`, `blockerId`, and `reason`, and leaves move count and history unchanged104+105+#### Scenario: Undo reports a restored state106+- **WHEN** a successful move is undone107+- **THEN** the undo result has `kind: 'undo'`, reports `accepted: true`, the reverse movement with `pushedBy: null`, the rule changes, `controlLost`, and the restored win state108+109+#### Scenario: Contact destruction includes presentation details110+- **WHEN** a `SINK` or `DEFEAT` interaction removes an entity111+- **THEN** the step result lists its id, noun or text, cell coordinate, and `cause` in the destruction order112+113+#### Scenario: Control loss is explicit114+- **WHEN** a settled step removes the last entity with the `YOU` property115+- **THEN** the step result reports `controlLost: true` even though the state remains inspectable for the loss feedback116+117+#### Scenario: Event arrays preserve resolution order118+- **WHEN** one accepted step moves entities, transforms an object, destroys contacts, and evaluates a win119+- **THEN** the event arrays retain movement order first, transformation order second, destruction order third, and the final win result after those phases120+