- commit
- 3efbb1c2049ef9006a1c651891a5af68e4cc760b
- parent
- 714880d03eb1e5809fe6d7442094498456ce1973
- author
- Dami <dami.etoile@gmail.com>
| A | openspec/ | 2 | + |
| A | openspec/ | 145 | ++++++++++++++++++++++++++++++ |
| A | openspec/ | 41 | ++++++++ |
| A | openspec/ | 121 | +++++++++++++++++++++++++ |
| A | openspec/ | 44 | +++++++++ |
@@ -0,0 +1,145 @@
1+## Context2+3+The current turn lifecycle moves player-controlled things, reparses and4+transforms rules, advances automata, reparses and transforms again, then5+resolves contacts. A frail tile is a normal object with an authored stable ID;6+it is not an underlying floor layer and does not shield a cell from contacts.7+The mechanic therefore uses the existing object noun and the foundation's8+mechanic lifecycle rather than adding a second board representation.9+10+## Goals and non-goals11+12+### Goals13+14+- Make `FRAIL` a normal rule-driven property.15+- Make departure, handover, and leftover timing deterministic and visible.16+- Count things and words consistently with existing movement and SINK rules.17+- Keep `leaves` attached to entity identity through transforms, contacts,18+ Undo/reset, shared payloads, and solver replay.19+- Support chains without introducing a numeric counter or renderer-only state.20+21+### Non-goals22+23+- A floor layer beneath an object.24+- A durability counter, crack decoration, or automatic multi-step timer.25+- A new contact precedence rule for SINK, DEFEAT, or WIN.26+- JavaScript callbacks in a CommonJS module.27+28+## Decisions29+30+### Frail is a parsed property31+32+The host recognizes `FRAIL` as a supported property. A level may author33+`DRIFT IS FRAIL`, `SAND AND SHOAL IS FRAIL`, or another valid noun/property34+sentence. Active FRAIL membership is derived from the settled rules and current35+noun, just like other rule-driven properties. The property word can therefore36+be pushed or removed to change future behavior.37+38+### Departure is sampled at a turn boundary39+40+For an accepted turn, the host captures the set of objects occupying each cell41+before movement. It then resolves player movement, reparses/transforms, and42+resolves active automata using the ordinary atomic push rules. After the final43+transform phase and before contacts, it evaluates every object that is FRAIL at44+that checkpoint:45+46+1. The object was a FRAIL object in the starting settled cell.47+2. A distinct occupant that was not FRAIL at the start moved away from that48+ cell during this turn. A pushed word, pushed thing, player, or automaton49+ qualifies.50+3. No object occupies the frail cell at the crumble checkpoint.51+52+If all three conditions hold, the frail entity crumbles. A mover entering the53+cell in the same accepted turn is a handover and prevents crumbling. This54+avoids evaluating the same plank once per mover and makes a pushed word leaving55+one plank while YOU enters it safe.56+57+The moving FRAIL entity itself never triggers crumbling for its old cell. If a58+FRAIL entity is pushed or walks away, it remains the entity at its new cell;59+its own movement is not a departure trigger. Transforming a frail object away60+does not count as departure, but the object's `leaves` metadata remains on its61+stable ID for later use if it becomes FRAIL again.62+63+### Leftovers are nouns, not counters64+65+An authored object may carry `leaves: '<NOUN>'`. The field is validated as a66+known noun token and is preserved by entity ID through noun transforms. When67+the object crumbles, `from` is its current noun and:68+69+- with `leaves`, it changes into that noun at the same coordinate and keeps its70+ ID and metadata;71+- without `leaves`, it is removed from the object set.72+73+The resulting noun is part of the same settled turn. If it is also FRAIL and a74+later turn has a distinct occupant depart its cell, it can crumble again. This75+expresses multi-crossing designs as an authored chain, for example76+`SAND(leaves: SHOAL) -> SHOAL(leaves: WATER) -> WATER`, without hidden counts.77+78+### Contacts follow the crumble checkpoint79+80+Crumbled leftovers or removed objects participate in the existing contact81+phase. A `leaves: 'WATER'` result is therefore subject to the current82+`WATER IS SINK` rule on that same turn, with the existing contact precedence.83+Nothing can be authored as a frail object sitting on a water hazard to bypass84+initial contacts: reset still resolves initial contacts before the first frame,85+and there is no floor layer.86+87+### State and event contract88+89+The mechanic's static definition holds the authored `leaves` mapping by stable90+object ID; the dynamic noun and existence remain in `state.objects`. The91+settled result adds `crumbled`, ordered deterministically by stable entity ID,92+with `{ id, from, to }`, where `to` is the leftover noun or `null` for removal.93+The event is cloned with the snapshot and is presentation-only. Undo and reset94+restore the prior object noun/existence and leaves metadata exactly; sharing95+and CommonJS export preserve the validated metadata; the solver key includes96+object ID, noun, position, existence, and the registered FRAIL mechanic state.97+98+### Automata and solver99+100+Automata are ordinary movers for departure and handover purposes. Their action101+is resolved before FRAIL evaluation, so a word or thing moved by an automaton102+can spend a frail cell and a handoff remains safe. The compact solver uses the103+same transition order and replays `crumbled` results through authoritative104+restore. A same-board state with a different current noun or object existence105+is distinct.106+107+## Tidewater proof scenarios108+109+The Tidewater draft fixtures remain world-team owned; these scenarios define110+the engine acceptance cases without editing the pack:111+112+- **Draft A, One plank each:** `DRIFT IS FRAIL`, three `DRIFT` entities with113+ `leaves: WATER`, and a word pushed across them. Each word departure is safe114+ while YOU hands over the cell; after YOU leaves the final plank it becomes115+ water. Breaking `DRIFT IS FRAIL` keeps the bridge.116+- **Draft B, Two tides:** `SAND(leaves: SHOAL)` and117+ `SHOAL(leaves: WATER)` prove a chain and show a deliberately pushed ROCK118+ consumes one crossing.119+- **Word and automaton crossing:** a pushed word and an active automaton each120+ leave distinct FRAIL cells; both produce stable crumble events.121+- **Initial SINK:** a `leaves: WATER` result and a frail object authored on a122+ `WATER IS SINK` cell use existing reset/contact behavior, with no shielding123+ exception.124+125+## Risks and mitigations126+127+- **Half-turn evaluation crumbles a handover.** Evaluate once after all128+ movement and transforms, then require the destination cell to be empty.129+- **A leftover chain disappears from the solver.** Keep current noun and130+ existence in the authoritative object state and include them in the compact131+ key; add encode/decode/restore parity fixtures.132+- **Unknown metadata changes sharing.** Validate `leaves` against the host133+ noun vocabulary and reject malformed values atomically.134+- **Contact ordering becomes ambiguous.** Keep crumble before the existing135+ contact phase and test SINK, DEFEAT, and WIN with a same-turn leftover.136+137+## Open questions138+139+- When several entities crumble in one accepted turn, is stable ID ordering140+ sufficient for the renderer's animation and event list, or should authored141+ cell/reading order be the presentation order? The simulation result is142+ independent of that ordering, but the final presentation contract needs a143+ design choice before the renderer is implemented.144+- The exact Tidewater fixture IDs and visual glyphs for `DRIFT`, `SAND`, and145+ `SHOAL` remain with the worlds/design teams.@@ -0,0 +1,41 @@
1+## Why2+3+Tidewater needs one-step sandbars and driftwood to make movement itself a4+resource. The current engine has rules, transforms, contacts, Undo, sharing,5+and a compact solver, but no declarative way for a cell to leave a deliberate6+leftover after a mover crosses it. Adding this as an ad hoc level feature would7+make the visual result differ from the authoritative state and would lose the8+mechanic during Undo, reset, shared play, or search.9+10+## What Changes11+12+- Add the `FRAIL` property behavior for rule-driven objects and words.13+- Interpret `DRIFT IS FRAIL` (and equivalent noun/property rules) at the14+ settled end of a turn, after player/automata movement and transforms but15+ before SINK, DEFEAT, and WIN contacts.16+- Crumble a frail occupant's cell only when a distinct non-FRAIL occupant that17+ stood there at turn start has departed and the cell is empty at the crumble18+ checkpoint. A handover, where another mover enters the cell in that turn,19+ keeps the frail object in place.20+- Count every mover, including pushed words and automata, while a moving FRAIL21+ entity itself never causes its own cell to crumble.22+- Preserve authored `leaves` metadata by stable entity ID. On crumble, the23+ entity becomes the declared noun or is removed when no leftover is declared;24+ a leftover may itself be frail, allowing authored chains such as25+ `SAND -> SHOAL -> WATER`.26+- Emit settled `crumbled: [{ id, from, to }]` records and include the resulting27+ noun/removal in normal snapshots, history, sharing, replay, and solver keys.28+- Add Tidewater proof fixtures and differential scenarios for step-off,29+ handover, pushed words, automata, initial SINK contacts, chains, and Undo.30+31+This change depends on the lifecycle, snapshot, sharing, and codec boundaries32+in `add-declarative-mechanic-state-contract` (`9e91d42`). It defines FRAIL33+behavior only; gates, switches, plates, floor layers, and numeric durability34+counters remain separate changes.35+36+## Impact37+38+The turn resolver, normalized level/mod metadata, event shape, snapshot and39+restore paths, solver transition/key logic, and Tidewater fixtures change.40+Existing levels without a `FRAIL` rule or `leaves` metadata retain current41+behavior. Rendering consumes the settled event; it does not own crumble state.@@ -0,0 +1,121 @@
1+## ADDED Requirements2+3+### Requirement: Levels MAY declare rule-driven frail leftovers4+A normalized level SHALL permit the supported `FRAIL` property and MAY attach5+`leaves: '<NOUN>'` to an authored object with a stable ID. The host SHALL6+validate every leaves value as a known noun, preserve it by object ID, and7+reject unsupported fields or executable values. A missing leaves value SHALL8+mean removal on crumble.9+10+#### Scenario: A valid drift object is normalized11+- **WHEN** a level declares `DRIFT IS FRAIL` and object `drift-1` as12+ `{ noun: 'DRIFT', leaves: 'WATER' }`13+- **THEN** the normalized level retains the rule and the ID-bound leaves14+ metadata, and the initial object state is unchanged15+16+#### Scenario: Invalid leaves metadata is rejected17+- **WHEN** a pack declares `leaves: 'NOT_A_NOUN'` or a function-valued leaves18+ field19+- **THEN** registration fails with the pack and level path and installs none of20+ the invalid level's FRAIL metadata21+22+### Requirement: Frail objects crumble after distinct departure23+For an accepted turn, the host SHALL evaluate FRAIL once after player and24+automata movement and all movement-triggered transforms, before SINK, DEFEAT,25+and WIN contacts. A frail object SHALL crumble only when a distinct occupant26+that was not FRAIL at turn start departed its starting cell and no object27+occupies that cell at the checkpoint. A handover SHALL prevent crumbling. A28+moving FRAIL object SHALL NOT crumble itself.29+30+#### Scenario: Standing on a plank is safe31+- **WHEN** YOU stands on a DRIFT cell under `DRIFT IS FRAIL` and pushes a word32+ ahead without leaving the cell33+- **THEN** the DRIFT object remains and no `crumbled` record is emitted34+35+#### Scenario: A handover is safe36+- **WHEN** a pushed word leaves a FRAIL cell and YOU enters that same cell in37+ the accepted turn38+- **THEN** the FRAIL object remains because the destination is occupied at the39+ crumble checkpoint40+41+#### Scenario: A distinct departure crumbles the cell42+- **WHEN** a non-FRAIL word, thing, or player leaves a FRAIL cell and the cell43+ is empty after all movement and transforms44+- **THEN** the FRAIL object crumbles exactly once before contacts45+46+#### Scenario: Moving FRAIL is not self-crumbling47+- **WHEN** a FRAIL object is pushed or moved by YOU or an automaton48+- **THEN** it remains at its destination and its old cell does not emit a49+ `crumbled` record merely because the FRAIL object moved50+51+### Requirement: Leftovers preserve identity and chain through nouns52+On crumble, an object with `leaves` SHALL become that noun at the same cell53+while retaining its stable ID and authored leaves metadata. An object without54+leaves SHALL be removed. The resulting noun SHALL be eligible for later FRAIL55+rules, allowing repeated authored chains without a hidden counter.56+57+#### Scenario: A drift plank becomes water58+- **WHEN** a DRIFT object with `leaves: 'WATER'` crumbles59+- **THEN** the same ID is present as WATER at the same coordinate and the60+ settled event reports `{ id, from: 'DRIFT', to: 'WATER' }`61+62+#### Scenario: A two-link chain advances over two departures63+- **WHEN** SAND with `leaves: 'SHOAL'` first crumbles and the resulting SHOAL64+ later crumbles with `leaves: 'WATER'`65+- **THEN** the first and second accepted turns report DRIFT-like transitions66+ for the same ID, with `from/to` of SAND/SHOAL and SHOAL/WATER respectively67+68+#### Scenario: No leftover removes the object69+- **WHEN** a FRAIL ROCK without leaves crumbles70+- **THEN** its stable ID is absent from the settled object state and the event71+ reports `{ id, from: 'ROCK', to: null }`72+73+### Requirement: Crumbling precedes existing contacts74+The leftover state SHALL be settled before the existing SINK, DEFEAT, and WIN75+contact phase. A leftover SHALL not create a floor layer or shield any object76+from contact, including initial reset contacts.77+78+#### Scenario: Water leftover uses SINK79+- **WHEN** a frail object becomes WATER under `WATER IS SINK`80+- **THEN** the existing contact phase consumes the water according to normal81+ SINK precedence and the final state/events match an authored WATER object82+83+#### Scenario: Initial water contact remains destructive84+- **WHEN** a level places an object on a cell containing `WATER IS SINK`85+- **THEN** reset resolves the existing contact before the first frame; FRAIL86+ does not shield or defer it87+88+### Requirement: Settled events and snapshots expose crumble results89+Every accepted turn that crumbles one or more objects SHALL expose a cloned90+`crumbled` array of `{ id, from, to }` records, deterministically ordered by91+stable entity ID. Undo, reset, sharing, replay, and restore SHALL reproduce the92+authoritative object noun/existence and leaves metadata exactly.93+94+#### Scenario: Undo restores the plank95+- **WHEN** a turn crumbles a DRIFT object and the player chooses Undo96+- **THEN** the prior DRIFT object, coordinate, leaves metadata, rules, and97+ history are restored and no crumble event remains active98+99+#### Scenario: Shared replay preserves a chain stage100+- **WHEN** a shared level is imported after one leftover link has resolved101+- **THEN** the decoded snapshot and next transition distinguish its current102+ noun and stable ID from the pre-crumble board and produce the same event103+104+### Requirement: Solver transitions include frail state105+The compact solver SHALL use the same movement, transform, FRAIL checkpoint,106+and contact ordering as the public engine. Its key SHALL distinguish object107+identity, position, current noun, existence, and any registered mechanic state108+that affects a future crumble. Solver replay SHALL reproduce `crumbled` and109+chain transitions without JSON-order or presentation-only state.110+111+#### Scenario: Same board different chain stages remain distinct112+- **WHEN** two search states have identical positions but one object is SAND113+ and the other is SHOAL114+- **THEN** the solver retains them as distinct states and explores their115+ different future leftovers116+117+#### Scenario: Public and compact transitions agree118+- **WHEN** a differential fixture runs a handover, pushed-word departure,119+ automaton departure, and two-link chain through both transition paths120+- **THEN** each path has the same settled objects, rules, contacts, and121+ `crumbled` records@@ -0,0 +1,44 @@
1+## 1. Foundation and validation2+3+- [ ] 1.1 Register the FRAIL mechanic type and validate supported property and4+ `leaves` noun tokens in the normalized level/mod boundary.5+- [ ] 1.2 Preserve authored `leaves` metadata by stable object ID through6+ CommonJS load/export, catalog storage, data-only sharing, and restore.7+- [ ] 1.3 Reject malformed, unknown, duplicate, or executable FRAIL metadata8+ atomically with an actionable pack/level path.9+10+## 2. Turn lifecycle11+12+- [ ] 2.1 Capture pre-turn occupancy and resolve player and automata movement,13+ rule reparsing, and transforms before FRAIL evaluation.14+- [ ] 2.2 Evaluate distinct non-FRAIL departures once at the end-of-movement15+ checkpoint, preserve handovers, and ensure a moving FRAIL entity cannot16+ crumble itself.17+- [ ] 2.3 Apply `leaves` as an ID-preserving noun transform or remove the18+ entity, including repeated leftover chains and normal same-turn contacts.19+- [ ] 2.4 Emit cloned settled `crumbled: [{ id, from, to }]` records in stable20+ deterministic order without changing legacy events.21+22+## 3. State, sharing, and solver23+24+- [ ] 3.1 Include FRAIL noun/existence and authored leaves metadata in accepted25+ snapshots, Undo, reset, replay, and authoritative restore.26+- [ ] 3.2 Register FRAIL state with the compact solver codec and distinguish27+ same-board states with different current nouns, existence, or chain stage.28+- [ ] 3.3 Differential-test public and compact transitions for handovers,29+ pushed words, automata movers, transforms, contacts, and chained leftovers.30+- [ ] 3.4 Prove shared/imported data and solver replay reproduce the same31+ `crumbled` events and next contact result.32+33+## 4. Fixtures and handoff34+35+- [ ] 4.1 Add focused engine scenarios for step-off versus standing, word and36+ automaton movers, moving FRAIL entities, handover, and blocked movement.37+- [ ] 4.2 Add SINK, DEFEAT, WIN, initial-contact, removal, and two-link chain38+ scenarios, including reset and Undo.39+- [ ] 4.3 Coordinate Draft A/B Tidewater proof IDs and authored glyphs with the40+ worlds/design teams without changing their owned pack files in this change.41+- [ ] 4.4 Run the full Node suite, production build, strict OpenSpec validation,42+ and the bundled-world solver gate before enabling a shipped proof.43+- [ ] 4.5 Resolve the event ordering presentation question and post the44+ accepted contract to the design/worlds inboxes.