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-declarative-mechanic-state-contract / specs / declarative-mechanic-state / spec.md
spec.md 7.8K rendered
1## ADDED Requirements23### Requirement: Mechanic metadata SHALL be declarative and host validated45The host SHALL accept only normalized mechanic definitions with a stable ID,6supported type, version, static configuration, and a declared state contract.7The declaration SHALL use `state: 'derived'` or `state: 'stateful'`. A derived8mechanic has no mutable runtime fields; the host adapter for a stateful mechanic9SHALL provide its complete registered scalar schema and initial values. A10CommonJS pack MAY declare supported mechanic metadata but SHALL NOT supply11callbacks, functions, URLs, or executable lifecycle behavior.1213#### Scenario: A supported stateful declaration loads1415- **WHEN** a pack declares a supported mechanic with valid static metadata and16 a complete state schema17- **THEN** normalization preserves it and the level can create a game with a18 complete initialized mechanic state1920#### Scenario: A supported derived declaration loads2122- **WHEN** a pack declares a supported derived mechanic with valid static23 metadata and no mutable state fields24- **THEN** normalization preserves it and the level creates no redundant25 runtime mechanic value for the derived projection2627#### Scenario: Unsupported mechanic behavior fails closed2829- **WHEN** a pack declares an unknown mechanic type or an unsupported field30- **THEN** validation rejects the pack with the declaration path and supported31 feature information, before catalog installation or play3233#### Scenario: Executable mechanic data is rejected3435- **WHEN** a mechanic declaration contains a function, callback, URL, or other36 executable value37- **THEN** validation rejects it and the CommonJS loader never exposes it as a38 host lifecycle hook3940### Requirement: Mechanics SHALL have one transactional settled state4142The engine SHALL initialize every stateful mechanic in `state.mechanics` and43carry that envelope through accepted turns, Undo, reset, and restore. Derived44mechanics SHALL recompute their projections from authoritative settled objects,45rules, and authored metadata; their projections SHALL NOT be persisted as46redundant mutable state. A rejected or complexity-blocked turn SHALL leave47mechanic state, objects,48automata phases, and history unchanged.4950#### Scenario: Reset restores authored mechanic state5152- **WHEN** a game with a mechanic has accepted turns and then receives reset53- **THEN** objects, rules, automata phases, moves, and mechanic state match the54 authored initial state5556#### Scenario: Undo restores mechanic state5758- **WHEN** an accepted turn changes a mechanic field and the player invokes Undo59- **THEN** the field returns to its prior value and the returned event reports a60 restored transition without mutating the snapshot returned earlier6162#### Scenario: Restore keeps a settled mechanic state inert6364- **WHEN** a valid snapshot containing mechanic state is restored65- **THEN** the engine accepts that state without applying a noun transform,66 advancing an automaton, or invoking a later turn phase6768#### Scenario: Rejected turns are atomic6970- **WHEN** a mechanic or rule parser rejects a turn after speculative movement71- **THEN** the complete prior settled state, including mechanic fields, is72 restored and no undo entry is added7374#### Scenario: Derived state restores from the board7576- **WHEN** a valid snapshot containing a derived mechanic's authoritative77 objects and authored metadata is restored78- **THEN** the same derived projection is recomputed without applying a hidden79 bit, advancing an automaton, or invoking a later turn phase8081### Requirement: Mechanic lifecycle phases SHALL be deterministic8283Host-owned mechanics SHALL receive named lifecycle phases around the existing84turn pipeline: before movement, after movement, after transforms, before85contacts, after contacts, and settled finalization. The phase order SHALL be86stable and a mechanic SHALL declare which phases it uses.8788#### Scenario: A mechanic observes the documented order8990- **WHEN** an accepted turn moves an object, changes a rule, and resolves a91 contact92- **THEN** the mechanic observes the phases in the documented order and its93 state transition is committed once at settled finalization9495#### Scenario: Mechanics do not run on an invalid input9697- **WHEN** input has an invalid direction or no controllable entity can move98- **THEN** no mutating mechanic phase commits state or emits a transition99100### Requirement: Settled events SHALL expose mechanic transitions safely101102The engine SHALL include bounded, cloned mechanic transition records in settled103events when stateful mechanic state or derived mechanic effects change. Levels104without105mechanic changes SHALL preserve the existing event shape and behavior.106107#### Scenario: A mechanic change is observable108109- **WHEN** a settled turn changes a registered mechanic value110- **THEN** the event identifies the mechanic, stable subject when applicable,111 prior and new values or an approved event payload, and a bounded cause token112113#### Scenario: A derived mechanic effect is observable without hidden state114115- **WHEN** a derived projection changes after a settled turn116- **THEN** the event exposes the approved concrete transition records while117 the snapshot contains no redundant projection bit118119#### Scenario: Event data cannot mutate state120121- **WHEN** presentation code mutates a returned mechanic event or snapshot122- **THEN** a later `getState()` call and the next turn remain unchanged123124### Requirement: Mechanic metadata preservation125Pack normalization, catalog registration, CommonJS export/import, and data-only sharing SHALL preserve validated mechanic metadata. Shared data SHALL contain declarative JSON only and SHALL be revalidated on install.126127#### Scenario: Export and share preserve a declaration128129- **WHEN** a valid pack with mechanic metadata is exported and shared, then130 loaded through each supported path131- **THEN** the normalized level metadata and initial mechanic state are132 equivalent to the source pack133134#### Scenario: Shared unsupported metadata is rejected135136- **WHEN** a data-only share payload declares an unsupported mechanic type or137 field138- **THEN** decoding rejects it before installation and does not execute code139140### Requirement: Solver state SHALL register every dynamic mechanic field141142The solver SHALL derive or receive a deterministic codec registry for every143stateful mechanic. Encoding SHALL fail on unknown IDs, unknown fields, missing144registrations, invalid values, or unsupported schema versions. A compact key145SHALL include every registered value that can affect a future transition.146Derived mechanics SHALL register their projection inputs or an explicit147no-hidden-state assertion; their derived output SHALL NOT be redundantly148encoded as a second mutable value.149150#### Scenario: Same board with different mechanic state stays distinct151152- **WHEN** two nonterminal snapshots have identical objects and automata but153 different registered mechanic state154- **THEN** their compact solver keys differ155156#### Scenario: Unregistered dynamic state fails closed157158- **WHEN** a snapshot contains mechanic state without a matching registry entry159- **THEN** codec encoding fails with an actionable unregistered-state error160161#### Scenario: Mechanic codec round-trips through restore162163- **WHEN** a compact solver state containing mechanic values is decoded and164 restored165- **THEN** the next public and fast transitions match direct replay from the166 original settled state167168#### Scenario: Derived projections preserve public and fast parity169170- **WHEN** a derived mechanic snapshot is decoded and restored before the next171 transition172- **THEN** public and fast transitions recompute the same projection and event173 records without a serialized derived-state bit174175#### Scenario: Hidden derived bits fail closed176177- **WHEN** an implementation attempts to add an unregistered mutable field for178 a derived mechanic179- **THEN** codec or engine validation rejects it before search or play