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 source

ADDED Requirements

Requirement: Mechanic metadata SHALL be declarative and host validated

The host SHALL accept only normalized mechanic definitions with a stable ID, supported type, version, static configuration, and a declared state contract. The declaration SHALL use state: 'derived' or state: 'stateful'. A derived mechanic has no mutable runtime fields; the host adapter for a stateful mechanic SHALL provide its complete registered scalar schema and initial values. A CommonJS pack MAY declare supported mechanic metadata but SHALL NOT supply callbacks, functions, URLs, or executable lifecycle behavior.

Scenario: A supported stateful declaration loads

  • WHEN a pack declares a supported mechanic with valid static metadata and a complete state schema
  • THEN normalization preserves it and the level can create a game with a complete initialized mechanic state

Scenario: A supported derived declaration loads

  • WHEN a pack declares a supported derived mechanic with valid static metadata and no mutable state fields
  • THEN normalization preserves it and the level creates no redundant runtime mechanic value for the derived projection

Scenario: Unsupported mechanic behavior fails closed

  • WHEN a pack declares an unknown mechanic type or an unsupported field
  • THEN validation rejects the pack with the declaration path and supported feature information, before catalog installation or play

Scenario: Executable mechanic data is rejected

  • WHEN a mechanic declaration contains a function, callback, URL, or other executable value
  • THEN validation rejects it and the CommonJS loader never exposes it as a host lifecycle hook

Requirement: Mechanics SHALL have one transactional settled state

The engine SHALL initialize every stateful mechanic in state.mechanics and carry that envelope through accepted turns, Undo, reset, and restore. Derived mechanics SHALL recompute their projections from authoritative settled objects, rules, and authored metadata; their projections SHALL NOT be persisted as redundant mutable state. A rejected or complexity-blocked turn SHALL leave mechanic state, objects, automata phases, and history unchanged.

Scenario: Reset restores authored mechanic state

  • WHEN a game with a mechanic has accepted turns and then receives reset
  • THEN objects, rules, automata phases, moves, and mechanic state match the authored initial state

Scenario: Undo restores mechanic state

  • WHEN an accepted turn changes a mechanic field and the player invokes Undo
  • THEN the field returns to its prior value and the returned event reports a restored transition without mutating the snapshot returned earlier

Scenario: Restore keeps a settled mechanic state inert

  • WHEN a valid snapshot containing mechanic state is restored
  • THEN the engine accepts that state without applying a noun transform, advancing an automaton, or invoking a later turn phase

Scenario: Rejected turns are atomic

  • WHEN a mechanic or rule parser rejects a turn after speculative movement
  • THEN the complete prior settled state, including mechanic fields, is restored and no undo entry is added

Scenario: Derived state restores from the board

  • WHEN a valid snapshot containing a derived mechanic's authoritative objects and authored metadata is restored
  • THEN the same derived projection is recomputed without applying a hidden bit, advancing an automaton, or invoking a later turn phase

Requirement: Mechanic lifecycle phases SHALL be deterministic

Host-owned mechanics SHALL receive named lifecycle phases around the existing turn pipeline: before movement, after movement, after transforms, before contacts, after contacts, and settled finalization. The phase order SHALL be stable and a mechanic SHALL declare which phases it uses.

Scenario: A mechanic observes the documented order

  • WHEN an accepted turn moves an object, changes a rule, and resolves a contact
  • THEN the mechanic observes the phases in the documented order and its state transition is committed once at settled finalization

Scenario: Mechanics do not run on an invalid input

  • WHEN input has an invalid direction or no controllable entity can move
  • THEN no mutating mechanic phase commits state or emits a transition

Requirement: Settled events SHALL expose mechanic transitions safely

The engine SHALL include bounded, cloned mechanic transition records in settled events when stateful mechanic state or derived mechanic effects change. Levels without mechanic changes SHALL preserve the existing event shape and behavior.

Scenario: A mechanic change is observable

  • WHEN a settled turn changes a registered mechanic value
  • THEN the event identifies the mechanic, stable subject when applicable, prior and new values or an approved event payload, and a bounded cause token

Scenario: A derived mechanic effect is observable without hidden state

  • WHEN a derived projection changes after a settled turn
  • THEN the event exposes the approved concrete transition records while the snapshot contains no redundant projection bit

Scenario: Event data cannot mutate state

  • WHEN presentation code mutates a returned mechanic event or snapshot
  • THEN a later getState() call and the next turn remain unchanged

Requirement: Mechanic metadata preservation

Pack 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.

Scenario: Export and share preserve a declaration

  • WHEN a valid pack with mechanic metadata is exported and shared, then loaded through each supported path
  • THEN the normalized level metadata and initial mechanic state are equivalent to the source pack

Scenario: Shared unsupported metadata is rejected

  • WHEN a data-only share payload declares an unsupported mechanic type or field
  • THEN decoding rejects it before installation and does not execute code

Requirement: Solver state SHALL register every dynamic mechanic field

The solver SHALL derive or receive a deterministic codec registry for every stateful mechanic. Encoding SHALL fail on unknown IDs, unknown fields, missing registrations, invalid values, or unsupported schema versions. A compact key SHALL include every registered value that can affect a future transition. Derived mechanics SHALL register their projection inputs or an explicit no-hidden-state assertion; their derived output SHALL NOT be redundantly encoded as a second mutable value.

Scenario: Same board with different mechanic state stays distinct

  • WHEN two nonterminal snapshots have identical objects and automata but different registered mechanic state
  • THEN their compact solver keys differ

Scenario: Unregistered dynamic state fails closed

  • WHEN a snapshot contains mechanic state without a matching registry entry
  • THEN codec encoding fails with an actionable unregistered-state error

Scenario: Mechanic codec round-trips through restore

  • WHEN a compact solver state containing mechanic values is decoded and restored
  • THEN the next public and fast transitions match direct replay from the original settled state

Scenario: Derived projections preserve public and fast parity

  • WHEN a derived mechanic snapshot is decoded and restored before the next transition
  • THEN public and fast transitions recompute the same projection and event records without a serialized derived-state bit

Scenario: Hidden derived bits fail closed

  • WHEN an implementation attempts to add an unregistered mutable field for a derived mechanic
  • THEN codec or engine validation rejects it before search or play