clone

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

main
miau-is-true / openspec / changes / add-frail-leftover-tiles / design.md
design.md 7.9K rendered
1## Context23The current turn lifecycle moves player-controlled things, reparses and4transforms rules, advances automata, reparses and transforms again, then5resolves contacts. A frail tile is a normal object with an authored stable ID;6it is not an underlying floor layer and does not shield a cell from contacts.7The mechanic therefore uses the existing object noun and the foundation's8mechanic lifecycle rather than adding a second board representation.910## Goals and non-goals1112### Goals1314- 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 the level-scoped noun map attached to the FRAIL mechanic definition;18 keep the transformed noun and stable entity ID in authoritative object state19 through transforms, contacts, Undo/reset, shared payloads, and solver replay.20- Support chains without introducing a numeric counter or renderer-only state.2122### Non-goals2324- A floor layer beneath an object.25- A durability counter, crack decoration, or automatic multi-step timer.26- A new contact precedence rule for SINK, DEFEAT, or WIN.27- JavaScript callbacks in a CommonJS module.2829## Decisions3031### Frail is a parsed property with a level-scoped declaration3233The host recognizes `FRAIL` as a supported property. A level that uses the34mechanic declares one level-scoped entry:3536```js37mechanics: [{38 id: 'frail',39 type: 'frail',40 version: 1,41 state: 'derived',42 leaves: { SAND: 'SHOAL', SHOAL: 'WATER', DRIFT: 'WATER' },43}]44```4546The declaration has no runtime counter or per-object `leaves` field. A level47may author `DRIFT IS FRAIL`, `SAND AND SHOAL IS FRAIL`, or another valid48noun/property sentence. Active FRAIL membership is derived from the settled49rules and current noun, just like other rule-driven properties. The property50word can therefore be pushed or removed to change future behavior.5152### Departure is sampled at a turn boundary5354For an accepted turn, the host captures the set of objects occupying each cell55before movement. It then resolves player movement, reparses/transforms, and56resolves active automata using the ordinary atomic push rules. After the final57transform phase and before contacts, it evaluates every object that is FRAIL at58that checkpoint:59601. The object was a FRAIL object in the starting settled cell and is still a61 FRAIL object at the crumble checkpoint.622. A distinct occupant moved away from that cell during this turn. A pushed63 word, pushed thing, player, or automaton qualifies, even if that distinct64 mover is also FRAIL.653. No other entity occupies the frail cell at the crumble checkpoint.6667If all three conditions hold, the frail entity crumbles. A mover entering the68cell in the same accepted turn is a handover and prevents crumbling. This69avoids evaluating the same plank once per mover and makes a pushed word leaving70one plank while YOU enters it safe.7172The moving FRAIL entity itself never triggers crumbling for its old cell. If a73FRAIL entity is pushed or walks away, it remains the entity at its new cell;74its own movement is not a departure trigger. Transforming a frail object away75does not count as departure; the level-scoped map is consulted only if its76current noun later crumbles while FRAIL.7778### Leftovers are nouns, not counters7980The `frail.leaves` map is validated as a map of syntactically valid noun81tokens. A leftover need not be present among the initially authored objects;82the solver codec includes every map key and value in its level-bound token83table. The map is static level data and is not copied onto objects. When an84object crumbles, `from` is its current noun and:8586- if `frail.leaves[from]` exists, it changes into that noun at the same87 coordinate and keeps its stable ID;88- if the current noun is absent from the map, it is removed from the object89 set.9091The resulting noun is part of the same settled turn. If it is also FRAIL and a92later turn has a distinct occupant depart its cell, it can crumble again. This93expresses multi-crossing designs as an authored chain, for example94`SAND -> SHOAL -> WATER`, using the current noun as the lookup key and without95hidden counts.9697### Contacts follow the crumble checkpoint9899Crumbled leftovers or removed objects participate in the existing contact100phase. A `frail.leaves[from] = 'WATER'` result is therefore subject to the current101`WATER IS SINK` rule on that same turn, with the existing contact precedence.102Nothing can be authored as a frail object sitting on a water hazard to bypass103initial contacts: reset still resolves initial contacts before the first frame,104and there is no floor layer.105106### State and event contract107108The mechanic's static definition holds the authored noun map; the dynamic noun109and existence remain in `state.objects`. The settled result adds `crumbled`,110ordered deterministically by stable entity ID, with `{ id, from, to }`, where111`to` is the mapped leftover noun or `null` for removal. The event is cloned112with the snapshot and is presentation-only. Undo and reset restore the prior113object noun/existence exactly; sharing and CommonJS export preserve the114validated mechanic declaration; the solver key includes object ID, noun,115position, existence, and any registered mechanic projection inputs, without a116separate FRAIL counter or per-object leaves value.117118### Automata and solver119120Automata are ordinary movers for departure and handover purposes. Their action121is resolved before FRAIL evaluation, so a word or thing moved by an automaton122can spend a frail cell and a handoff remains safe. The compact solver uses the123same transition order and replays `crumbled` results through authoritative124restore. A same-board state with a different current noun or object existence125is distinct.126127## Tidewater proof scenarios128129The Tidewater draft fixtures remain world-team owned; these scenarios define130the engine acceptance cases without editing the pack:131132- **Draft A, One plank each:** `DRIFT IS FRAIL`, three `DRIFT` entities, and a133 `frail.leaves` entry of `DRIFT: WATER`, with a word pushed across them. Each134 word departure is safe while YOU hands over the cell; after YOU leaves the135 final plank it becomes water. The accepted fixture pins the FRAIL sentence.136- **Draft B, Two tides:** `frail.leaves` entries `SAND: SHOAL` and137 `SHOAL: WATER` prove a chain as YOU crosses out, then pushes `IS` back over138 the bridge for the second crossing. The accepted fixture has no ROCK.139- **Word and automaton crossing:** a pushed word and an active automaton each140 leave distinct FRAIL cells; both produce stable crumble events.141- **Initial SINK:** a `frail.leaves` WATER result and a frail object authored on a142 `WATER IS SINK` cell use existing reset/contact behavior, with no shielding143 exception.144145## Risks and mitigations146147- **Half-turn evaluation crumbles a handover.** Evaluate once after all148 movement and transforms, then require the destination cell to be empty.149- **A leftover chain disappears from the solver.** Keep current noun and150 existence in the authoritative object state and include them in the compact151 key; keep the noun map level-bound and add encode/decode/restore parity152 fixtures without introducing a hidden crumble counter.153- **Unknown metadata changes sharing.** Validate the FRAIL declaration and154 every map key/value as a noun token and reject malformed values atomically.155 Reject per-object `leaves` as unsupported metadata.156- **Contact ordering becomes ambiguous.** Keep crumble before the existing157 contact phase and test SINK, DEFEAT, and WIN with a same-turn leftover.158159## Open questions160161- When several entities crumble in one accepted turn, is stable ID ordering162 sufficient for the renderer's animation and event list, or should authored163 cell/reading order be the presentation order? The simulation result is164 independent of that ordering, but the final presentation contract needs a165 design choice before the renderer is implemented.166- The exact Tidewater fixture IDs and visual glyphs for `DRIFT`, `SAND`, and167 `SHOAL` remain with the worlds/design teams.