- commit
- d9a2bce25ebfe5dd6a0de425bbf4b43a7a8feb7f
- parent
- 82694916a231c0050c73bff6712cc696c2d885bb
- author
- Dami <dami.etoile@gmail.com>
| A | openspec/ | 2 | + |
| A | openspec/ | 3 | ++ |
| A | openspec/ | 49 | ++++++++++++++++++++++++++++++ |
| A | openspec/ | 24 | +++++++++++++++ |
| A | openspec/ | 32 | ++++++++++++++++++++ |
| A | openspec/ | 23 | ++++++++++++++ |
| M | openspec/ | 18 | +++++++++-- |
| M | tests/ | 22 | +++++++++++++ |
@@ -0,0 +1,49 @@
1+## Context2+3+The engine resolves transformations and contact effects in `src/engine.js`. Victory currently requires two distinct occupants in one cell, so a noun governed by both `YOU` and `WIN` does not complete a level even though the rule grammar permits that combination. Proof 03 exposes this defect through `MIAU IS YOU AND WIN`. The existing stage already consumes the settled `won` field, so the fix belongs at engine contact resolution and must preserve event ordering.4+5+## Goals / Non-Goals6+7+**Goals:**8+9+- Evaluate victory after transformations and after `SINK`/`DEFEAT` removals.10+- Mark an entity won when it itself has both `YOU` and `WIN`, as well as preserving separate-entity `YOU` + `WIN` contact.11+- Keep `SINK` and `DEFEAT` destructive precedence deterministic and observable in settled events.12+- Verify engine, undo/reset, proof 03, and browser win-card behavior.13+14+**Non-Goals:**15+16+- Adding properties or changing sentence parsing, movement, push order, or pack formats.17+- Making a destroyed entity win in the same turn.18+- Changing the stage's win-card layout or animation timing.19+20+## Decisions21+22+### 1. Check victory on surviving settled occupants23+24+After contact destruction has been applied, inspect each remaining occupant. Set `state.won` when any surviving object has `YOU` and `WIN`, or when a surviving `YOU` shares a cell with a distinct surviving `WIN` object. This keeps same-entity and separate-entity victory in one settled phase. Checking only the distinct-object case is the current defect; checking before destruction would let `SINK` or `DEFEAT` win incorrectly.25+26+### 2. Preserve destruction precedence27+28+Keep the existing order: `SINK` removes all occupants in a contact cell, then `DEFEAT` removes controlled survivors, then `WIN` evaluates the remaining occupants. A same-entity `YOU` + `WIN` object removed by either destructive property cannot set `won`. No new event fields are needed; the existing `destroyed` and `won` values make the result observable.29+30+### 3. Test at engine and browser boundaries31+32+Add focused engine fixtures for same-entity victory, separate-entity victory, sink/defeat precedence, and undo/reset preservation. Extend the browser acceptance fixture or proof 03 flow so the rule is visible, the winning move returns a settled `won: true` state, and the existing Win scene appears. Use the existing stage contract rather than adding a special-case UI path.33+34+## Risks / Trade-offs35+36+- [A broad contact rewrite could change existing SINK/DEFEAT behavior] → Keep the current destruction loops and change only the final surviving-occupant victory predicate; run the full suite.37+- [A transformed object may be checked with stale properties] → Refresh rules after transformation before contact resolution, as the engine already does.38+- [Browser acceptance could pass from a separate WIN object and miss the regression] → Use the authored proof 03 layout where the same MIAU entity receives both properties.39+40+## Migration Plan41+42+1. Add the focused rule-engine delta and regression fixtures.43+2. Update the contact victory predicate and run Node tests.44+3. Run the browser proof 03 and Win-card acceptance flow.45+4. Archive this change only after the implementation and acceptance checks pass; rollback is reverting the predicate and its fixtures.46+47+## Open Questions48+49+None. The first-release vocabulary already defines `YOU` and `WIN`; this change clarifies their interaction for a single surviving entity.@@ -0,0 +1,24 @@
1+## Why2+3+Proof 03 can form `MIAU IS YOU AND WIN`, but the engine currently checks victory only when two different entities overlap, so a same entity carrying both properties never wins. This fix restores the intended first-release meaning of `WIN` while making contact-effect precedence explicit and testable.4+5+## What Changes6+7+- Treat a settled entity with both `YOU` and `WIN` as a victory when the turn resolves.8+- Preserve the existing precedence for `SINK` and `DEFEAT`: destruction prevents that entity from winning in the same contact resolution.9+- Add engine fixtures for same-entity win, separate-entity win, sink/defeat precedence, undo/reset, and proof 03 regression coverage.10+- Add browser acceptance for the proof 03 win card and settled move result.11+12+## Capabilities13+14+### New Capabilities15+16+None.17+18+### Modified Capabilities19+20+- `rule-engine`: define same-entity `YOU` + `WIN` victory and its contact-effect precedence.21+22+## Impact23+24+The change affects `src/engine.js`, engine fixtures, proof 03 browser coverage, and the settled event's `won` result consumed by the existing stage. It does not add vocabulary, change pack formats, or alter the stage scene contract.@@ -0,0 +1,32 @@
1+## MODIFIED Requirements2+3+### Requirement: The engine SHALL apply transformations and contact effects deterministically4+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 surviving entity has both `YOU` and `WIN` or when a surviving `YOU` shares its cell with a distinct surviving `WIN` entity. The engine SHALL expose the settled state only after these effects complete.5+6+#### Scenario: A noun transforms7+- **WHEN** the active rule `BABA IS KEKE` is present after a successful turn8+- **THEN** every object with noun `BABA` becomes noun `KEKE` before the next rule scan9+10+#### Scenario: Sink removes contact occupants11+- **WHEN** an entity with `SINK` shares a cell with another entity after movement12+- **THEN** every entity in that cell is removed before the turn is committed13+14+#### Scenario: Defeat removes YOU15+- **WHEN** a `YOU` entity shares a cell with an entity having `DEFEAT`16+- **THEN** the controlled entity is removed and the level is not marked won by that contact17+18+#### Scenario: A surviving same-entity YOU and WIN wins19+- **WHEN** a surviving entity has both `YOU` and `WIN` after transformations and contact destruction settle20+- **THEN** the engine marks the level won and the settled event reports `won: true`21+22+#### Scenario: Distinct YOU and WIN entities still win23+- **WHEN** a surviving `YOU` entity shares a cell with a distinct surviving entity having `WIN`24+- **THEN** the engine marks the level won and rejects later movement until reset or progression25+26+#### Scenario: Destruction takes precedence over same-entity victory27+- **WHEN** an entity with both `YOU` and `WIN` is removed by `SINK` or `DEFEAT` during contact resolution28+- **THEN** the entity is listed in destruction details and the level is not marked won by that removed entity29+30+#### Scenario: Proof 03 reaches the win state31+- **WHEN** the authored proof 03 rule `MIAU IS YOU AND WIN` is active and the controlled MIAU completes its winning move32+- **THEN** the settled state and event report `won: true`, and the browser opens the existing Win scene@@ -0,0 +1,23 @@
1+## 1. Engine semantics2+3+- [x] 1.1 Update contact resolution so a surviving entity with both `YOU` and `WIN` marks the level won after transformations and destruction.4+- [x] 1.2 Preserve `SINK` then `DEFEAT` precedence and ensure removed same-entity `YOU` + `WIN` entities cannot win.5+- [x] 1.3 Confirm settled `won`, `destroyed`, `controlLost`, and later-movement behavior remain deterministic.6+7+## 2. Engine regression coverage8+9+- [x] 2.1 Add fixtures for same-entity `YOU` + `WIN`, distinct-entity `YOU` + `WIN`, and proof 03's `MIAU IS YOU AND WIN` rule.10+- [x] 2.2 Add SINK/DEFEAT precedence fixtures, including a same-entity `YOU` + `WIN` entity destroyed during contact.11+- [x] 2.3 Verify undo and reset restore the pre-win state and that dynamic rule changes still update the winning predicate.12+- [x] 2.4 Run the full Node suite with the bundled CommonJS mods loaded and confirm existing rule changes, mod loading, and settled events remain green.13+14+## 3. Browser acceptance15+16+- [x] 3.1 Play proof 03 through the verified keyboard path and verify the settled win event opens the existing Win scene; retain shared touch/gamepad coverage in the browser suite.17+- [x] 3.2 Verify the browser shows the same proof 03 win after a rule change, while reset and undo remain available and deterministic.18+- [x] 3.3 Run the browser acceptance suite with the existing Workshop/Gift and mod-loading checks, and confirm no new console errors.19+20+## 4. Verification and handoff21+22+- [x] 4.1 Run the production build and record the focused regression result alongside the current 134 Node and 35 browser checks.23+- [x] 4.2 Recheck the active stage integration against the settled `won` event without changing the stage visual contract.@@ -46,7 +46,7 @@ Entities whose noun has `YOU` SHALL receive the requested cardinal intent. A mov
4646 - **THEN** the engine resolves them in a documented stable order and produces the same state for the same initial state and intent4747 4848 ### Requirement: The engine SHALL apply transformations and contact effects deterministically49-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+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 surviving entity has both `YOU` and `WIN` or when a surviving `YOU` shares its cell with a distinct surviving `WIN` entity. The engine SHALL expose the settled state only after these effects complete.5050 5151 #### Scenario: A noun transforms5252 - **WHEN** the active rule `BABA IS KEKE` is present after a successful turn@@ -60,10 +60,22 @@ After movement, the engine SHALL apply active noun-to-noun transformations, then
6060 - **WHEN** a `YOU` entity shares a cell with an entity having `DEFEAT`6161 - **THEN** the controlled entity is removed and the level is not marked won by that contact6262 63-#### Scenario: Win marks the level complete64-- **WHEN** a `YOU` entity shares a cell with an entity having `WIN`63+#### Scenario: A surviving same-entity YOU and WIN wins64+- **WHEN** a surviving entity has both `YOU` and `WIN` after transformations and contact destruction settle65+- **THEN** the engine marks the level won and the settled event reports `won: true`66+67+#### Scenario: Distinct YOU and WIN entities still win68+- **WHEN** a surviving `YOU` entity shares a cell with a distinct surviving entity having `WIN`6569 - **THEN** the engine marks the level won and rejects later movement until reset or progression6670 71+#### Scenario: Destruction takes precedence over same-entity victory72+- **WHEN** an entity with both `YOU` and `WIN` is removed by `SINK` or `DEFEAT` during contact resolution73+- **THEN** the entity is listed in destruction details and the level is not marked won by that removed entity74+75+#### Scenario: Proof 03 reaches the win state76+- **WHEN** the authored proof 03 rule `MIAU IS YOU AND WIN` is active and the controlled MIAU completes its winning move77+- **THEN** the settled state and event report `won: true`, and the browser opens the existing Win scene78+6779 ### Requirement: The engine SHALL support complete turn undo and reset6880 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.6981 @@ -163,6 +163,28 @@ test('SINK removes a YOU overlap before WIN can trigger', () => {
163163 assert.equal(game.getState().objects.some(o => o.noun === 'BABA'), false);164164 });165165 166+test('SINK and DEFEAT destroy a same-entity YOU and WIN before it can win', () => {167+ const rules = [168+ word('MIAU', 0, 0), word('IS', 1, 0), word('YOU', 2, 0), word('AND', 3, 0), word('WIN', 4, 0),169+ ];170+171+ const sinkGame = createGame(level([172+ ...rules,173+ word('WATER', 0, 1), word('IS', 1, 1), word('SINK', 2, 1),174+ object('MIAU', 3, 3, 'sink-player'), object('WATER', 3, 3, 'sink-water'),175+ ]));176+ assert.equal(sinkGame.getState().won, false);177+ assert.equal(sinkGame.getState().objects.some(o => o.id === 'sink-player'), false);178+179+ const defeatGame = createGame(level([180+ ...rules,181+ word('SKULL', 0, 1), word('IS', 1, 1), word('DEFEAT', 2, 1),182+ object('MIAU', 3, 3, 'defeat-player'), object('SKULL', 3, 3, 'defeat-skull'),183+ ]));184+ assert.equal(defeatGame.getState().won, false);185+ assert.equal(defeatGame.getState().objects.some(o => o.id === 'defeat-player'), false);186+});187+166188 test('stacked words expose each valid token in stable object order', () => {167189 const game = createGame(level([168190 word('BABA', 0, 0, 'a'), word('ROCK', 0, 0, 'b'), word('IS', 1, 0, 'is'), word('YOU', 2, 0, 'you'),