1## Context23`createGame()` owns authoritative settled state, history, restore, and turn4resolution. A turn currently moves player-controlled objects, reparses and5transforms rules, advances automata, reparses and transforms again, then6resolves contacts and victory. `state.objects` and automata phases are carried7through snapshots, while the solver's codec can accept dynamic fields only8through an explicit scalar registry. The missing seam is a mechanic lifecycle9that all of those boundaries can consume.1011## Goals and non-goals1213### Goals1415- Keep mechanic definitions data-only, deterministic, and validated before play.16- Give every mechanic one authoritative state envelope and one turn lifecycle.17- Preserve that state through reset, accepted and rejected turns, Undo, restore,18 solver replay, pack export, and data-only share links.19- Make event and solver registration requirements visible before a mechanic is20 eligible for bundled-world acceptance.21- Keep mechanics host-defined. A CommonJS pack declares supported metadata but22 does not provide callbacks or executable behavior.2324### Non-goals2526- Decide the semantics of gates, switches, plates, FRAIL, crumbling, counters,27 or floor layers.28- Add a renderer, audio cue, or visual language for a mechanic.29- Permit remote or untrusted code to implement mechanics.30- Replace the existing object, automaton, rule, or contact model.3132## Decisions3334### Use a normalized declarative definition3536After mod normalization, a level may contain a `mechanics` array. Each entry37has a stable lowercase `id`, a host-supported `type`, a version, static38configuration, and `state: 'derived'` or `state: 'stateful'`. The host adapter,39not the mod, owns a stateful scalar schema and initial values. Static configuration may40identify authored object IDs or channels, but it cannot contain functions,41URLs, or values outside the host schema. The host assigns a unique order by42mechanic ID and validates all object references before creating a game.4344The exact fields for a concrete mechanic are defined by its later capability45spec. This foundation only requires that the host can validate the definition,46initialize state when needed, and supply either a state codec registration or a47derived no-hidden-state assertion.4849### Separate derived projections from stateful mechanics5051The settled snapshot may contain `mechanics`, keyed by mechanic ID, for52stateful adapters. Each stateful value is owned by the host implementation and53contains only declared fields. The engine initializes it from the normalized54level, clones it with the same transaction snapshot as objects and automata,55and rejects unknown IDs or fields during restore.5657A derived adapter declares an empty state contract and recomputes its projection58from settled objects, rules, occupancy, and authored metadata. Derived outputs59such as gate openness, plate pressure, jam status, and FRAIL existence are not60stored as a second mutable truth and are not redundantly encoded in solver61frontier bytes. A mechanic must not hide mutable state in renderer, juice,62module globals, or authored object fields.6364For levels without mechanics, the existing public behavior remains unchanged;65the envelope may be omitted from serialized data when empty. Stateful66mechanics must have a complete initialized value; derived mechanics must have67an explicit empty state contract.6869### Define explicit phase hooks, not arbitrary callbacks7071The engine exposes a host-internal lifecycle with deterministic phases:72731. `beforeMovement`: inspect the settled state and prepare blockers or intent.742. `afterMovement`: observe player movement before the first rule refresh.753. `afterTransforms`: observe the final noun/rule projection after automata76 movement and the second rule refresh and transform.774. `beforeContacts`: apply mechanic effects that intentionally precede SINK or78 DEFEAT contacts.795. `afterContacts`: observe destruction and final occupancy.806. `settled`: finalize state and event data.8182The phase order is part of the engine contract. The existing player refresh and83transform, automata advance, and final refresh and transform remain between84`afterMovement` and `afterTransforms`; no hook sees a half-resolved state in85that interval. A concrete mechanic must state which hooks it uses. Hook86implementations are registered host code selected by a validated type; mod87data cannot add a callback.8889All stateful hook mutation is transactional. Rejected or complexity-blocked90turns do not commit mechanic state. A mechanic91must be deterministic from the authored definition, the prior settled state,92and the current phase inputs.9394### Treat events as the presentation boundary9596Settled results retain the existing movement, rule, transformation,97destruction, and automata fields and gain a normalized `mechanics` collection98only when a mechanic reports a transition. Each record contains the mechanic99ID, stable subject ID when applicable, `from` and `to` state values or a100declared event payload, and a bounded cause/type token. The engine returns101cloned event data so presentation cannot mutate authoritative state.102103The foundation does not prescribe names such as `pressed` or `crumbled`.104Concrete mechanic specs define their event vocabulary and ordering.105106### Make solver registration mandatory107108The solver codec receives the same compiled host-owned mechanic plan as the109engine. Each110stateful registration has a stable mechanic ID, an ordered schema, and a codec111version. The registry must encode every dynamic field that can affect a future112turn. A derived registration instead identifies its authoritative projection113inputs and asserts that no additional mutable bit is required.114Unknown mechanic IDs, fields, types, missing values, or unregistered runtime115state fail with actionable errors. Static authored metadata stays outside the116frontier blob.117118The plan supplies explicit codec noun tokens used by a mechanic's static119configuration. A leftover noun may not occur in the initial board or authored120vocabulary, so the FRAIL adapter must add both map keys and values before121codec token indices are assigned. Arbitrary mechanic strings are not assumed122to be nouns.123124The registry may use fixed authored slots and scalar fields already supported by125the codec. If a future mechanic needs arrays, maps, or more than the scalar126range, it must add an explicit deterministic codec representation rather than127falling back to JSON key order. Encoding and decoding must round-trip through128`game.restore()` without applying a transform or advancing an automaton.129130### Preserve data through pack and share boundaries131132Mod validation, normalization, catalog registration, export, and data-only133sharing preserve the normalized mechanic definitions. Shared links never carry134executable mechanic code. A pack declaring a mechanic type unsupported by the135host fails with a feature diagnostic before installation; it is not treated as136an ordinary noun transformation.137138## Risks and mitigations139140- **State omission merges solver futures.** Fail closed on unknown dynamic141 fields and require a same-board/different-state identity fixture per later142 mechanic.143- **Hook order diverges from gameplay.** Add public/fast parity tests at every144 lifecycle boundary before enabling solver acceptance.145- **Metadata drifts during sharing.** Round-trip normalized pack, exported146 CommonJS, and shared JSON fixtures through the same validator.147- **A generic schema becomes an accidental plugin API.** Keep type handlers148 host-owned and reject functions, callbacks, and unknown fields.149- **A future mechanic needs richer state than scalar fields.** Version the150 registry and require a mechanic-specific codec extension before content use.151152## Migration and rollback1531541. Add metadata validation and normalization with no existing mechanic entries.1552. Add the engine envelope and lifecycle adapter with empty-mechanic parity.1563. Connect restore, Undo, event cloning, pack/share, and solver registry tests.1574. Add later gate and FRAIL behavior changes one at a time.158159Rollback removes the foundation consumers and leaves existing packs valid. A160later mechanic must not be enabled until this contract and its codec fixtures161are present.162163## Open questions for later mechanic specs164165- Which static metadata fields identify entities, channels, or authored slots?166- Which mechanics need `beforeContacts` versus `afterContacts` semantics?167- Does a mechanic's state transition remain valid when a referenced object is168 transformed or destroyed?169- What event payload is sufficient for presentation without exposing internal170 mutable state?171- Which concrete mechanic types should be included in the host feature list?