clone

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

Specify channel linked gate behavior

Dami committed 714880d
commit
714880d03eb1e5809fe6d7442094498456ce1973
parent
1ee6bd0340f7be0ecc7f63331e9b3fadd558c2df
author
Dami <dami.etoile@gmail.com>
4 files changed, +383 -0
Aopenspec/changes/add-channel-linked-gates/design.md151++++++++++++++++++++++++++++++
Aopenspec/changes/add-channel-linked-gates/proposal.md46+++++++++
Aopenspec/changes/add-channel-linked-gates/specs/channel-linked-gates/spec.md139++++++++++++++++++++++++++++
Aopenspec/changes/add-channel-linked-gates/tasks.md47+++++++++
A openspec/changes/add-channel-linked-gates/design.md +151 -0
@@ -0,0 +1,151 @@
1+## Context2+3+The Crate Works drafts use ordinary `PLATE` and `GATE` nouns linked by a small4+visible channel palette. A plate is pressed while another entity occupies its5+cell. A gate is traversable when its channel has a pressed plate, or while6+another entity occupies the gate cell after contacts. The latter is a jam, not7+hidden history: every settled gate state is recomputed from the current board.8+9+The current engine plans movement through `planAutomatonMove()`, treats all10+objects in a cell as contacts, and resolves SINK/DEFEAT before final victory.11+The gate contract must use the prior settled gate projection while planning a12+turn, then recompute after contacts. This makes pressing a plate take effect on13+the next turn and lets a presser destroyed by SINK or DEFEAT release its plate14+on the same turn.15+16+## Goals and non-goals17+18+### Goals19+20+- Make channels data-only and stable across noun transformations.21+- Keep circuit membership determined by the current noun, with no remembered22+ open bit that can disagree with the visible board.23+- Make anything except the plate entity itself a valid presser.24+- Make closed gates stop movement regardless of current rule properties.25+- Preserve behavior through snapshots, Undo, reset, restore, sharing, and the26+ compact solver transition boundary.27+- Provide bounded settled events for design-owned animation and audio.28+29+### Non-goals30+31+- Edge-triggered switches or toggles.32+- Gate crushing, damage, or automatic destruction of occupants.33+- A new grammar such as `PLATE IS SWITCH` or `GATE IS SHUT`.34+- Arbitrary channel names or channel colors supplied by a mod.35+- Deciding the separate FRAIL/crumbling mechanic.36+37+## Decisions38+39+### Channel metadata stays with the authored entity ID40+41+An authored object may declare `channel` using one of the host's fixed channel42+tokens. The normalized object retains that field through reset, restore, noun43+transformation, CommonJS export, catalog installation, and data-only sharing.44+The current noun controls participation: an entity participates as a plate only45+while its noun is `PLATE`, and as a gate only while its noun is `GATE`.46+47+When `GATE IS CRATE` transforms a gate, it becomes an ordinary CRATE, leaves48+the circuit, and has no remembered open state. If a later rule transforms it49+back to GATE, it rejoins using its authored channel and recomputes its state at50+the end of that turn from the current plate and gate occupancy.51+52+### Pressure is occupancy, not entry53+54+At the post-contact settled boundary, a plate is pressed when at least one55+other surviving entity occupies its cell. Words count, regardless of their56+properties. The plate entity itself does not press itself. A plate can therefore57+be pressed by a non-PUSH YOU object, a pushed word, or an automaton actor.58+59+For each active gate, openness is recomputed as:60+61+```text62+pressed(channel) || occupiedByOther(gate cell)63+```64+65+The occupancy term is the jam rule. It prevents crushing and keeps a gate open66+until the cell is empty. There is no persistent gate bit whose value can differ67+from this settled projection.68+69+### Movement reads the prior settled projection70+71+Before player or automaton movement, the engine derives gate blockers from the72+prior settled state. A closed active gate rejects entry as an unconditional73+STOP, regardless of `GATE` properties or other rule predicates. An open gate74+has no movement property. Gate changes never occur mid-turn, so pressing a75+plate does not permit crossing the gate during that same turn.76+77+After movement, rule transforms, automata, and SINK/DEFEAT contacts complete,78+the engine recomputes active plates and gates. A destroyed presser therefore79+releases its plate on that turn. A gate transformed away is removed from the80+projection; if transformed back, its state is recomputed from the final cells.81+82+### Events describe settled changes83+84+Settled results add these bounded arrays when a value changes:85+86+```js87+gateChanged: [{ id, open, jammed }]88+plateChanged: [{ id, pressed }]89+```90+91+`jammed` is true when the gate is open because another surviving entity occupies92+its cell while no channel plate is pressed. An active/inactive noun transition93+may emit a false/closed change for the prior circuit member so presentation can94+remove its state marker. Event records use stable authored IDs and are cloned95+before returning to callers.96+97+### Channel markers are neutral shape data98+99+The host maps its fixed channels to neutral shape markers such as `▲`, `●`,100+`■`, and `◆`. They are drawn in the foreground color and do not use property101+colors; a pink or blue gate would be read as DEFEAT or SINK. The renderer may102+show the marker on plates and gates and list the channel in the ledger, but103+shape mapping is presentation data derived from the validated channel token.104+105+### Solver identity remains memoryless106+107+Channel is static authored metadata and current gate openness is derived from108+the settled object state. The solver codec must not add a hidden mutable `open`109+bit to the search key. Instead, the gate mechanic registry declares its110+deterministic derived projection and the codec fails if an implementation tries111+to introduce an unregistered dynamic gate field. Public `step()` and the fast112+solver transition must derive the same prior projection and produce the same113+post-contact events.114+115+## Risks and mitigations116+117+- **A gate is treated as a normal STOP object.** Keep gate blocking in the118+ movement mechanic boundary so rule precedence cannot reopen a closed gate.119+- **A presser disappears during contacts.** Recompute only after contacts and120+ add a SINK/DEFEAT release fixture.121+- **A transformed gate retains stale state.** Derive participation from noun122+ every turn and clear inactive projection entries.123+- **The solver encodes derived state inconsistently.** Add same-board parity124+ tests and reject unregistered mutable mechanic fields.125+- **Channel colors imply properties.** Keep shape markers neutral and test the126+ presentation payload separately from property palette colors.127+- **Adjacent plate and gate trivialize a level.** Keep the content warning in128+ world authoring and validate only structural channel consistency here.129+130+## Migration and rollback131+132+1. Add channel metadata validation and data-boundary round trips.133+2. Add prior-settled gate blocking and post-contact recomputation.134+3. Add events, reset/Undo/restore, and public/fast parity fixtures.135+4. Add the D1–D3 and transformed-gate proofs to Crate Works after solver136+ acceptance and design review.137+138+Rollback removes the gate mechanic registration and leaves ordinary objects,139+rules, and levels valid. A level using channel metadata must not be bundled140+ until the host feature is available.141+142+## Open questions143+144+- Should an active gate or plate with no matching channel partner be a load145+ warning, a validation error, or an allowed inert circuit?146+- If a gate is destroyed rather than transformed, should the event include an147+ inactive `gateChanged` record or only the existing destruction record?148+- If multiple entities enter and leave the same gate cell during one accepted149+ turn, is final post-contact occupancy the sole jam evidence? The current150+ proposal assumes yes.151+- Should channel tokens be exposed in pack vocabulary or remain metadata-only?
A openspec/changes/add-channel-linked-gates/proposal.md +46 -0
@@ -0,0 +1,46 @@
1+## Why2+3+Crate Works needs pressure plates and gates as a real Sokoban stressor for the4+engine. The mechanic must make a crate on a plate readable, support words and5+multiple YOU objects as pressers, and preserve rule-driven transformations of6+the gate and plate nouns. It also needs to survive the same boundaries as every7+future mechanic: contacts, Undo, restore, sharing, and solver replay.8+9+## What Changes10+11+- Add channel metadata to authored objects and validate it against the host12+ channel palette.13+- Treat current-noun `PLATE` and `GATE` entities as channel-linked circuit14+ members while retaining the authored channel on their stable entity IDs.15+- Make plate pressure occupancy-based and recompute gate openness at the end of16+ each turn after contacts.17+- Make a closed gate an unconditional movement blocker using the prior settled18+ state; never add a synthetic `GATE IS STOP` rule.19+- Define jammed gates, transformed-away gates, channel isolation, and settled20+ `gateChanged`/`plateChanged` events.21+- Preserve channel metadata through reset, Undo, restore, CommonJS export,22+ pack installation, data-only sharing, and solver parity.23+24+The change does not implement switches with edge-triggered toggles, crushing,25+custom gate predicates, or visual styling beyond the neutral shape marker26+contract. It assumes the declarative mechanic state foundation in27+`9e91d42`; this change adds gate behavior on that foundation.28+29+## Capabilities30+31+### New Capabilities32+33+- `channel-linked-gates`: occupancy plates, channel linking, unconditional34+ closed-gate blocking, memoryless jam behavior, and mechanic events.35+36+### Modified Capabilities37+38+None.39+40+## Impact41+42+The engine movement/contact phases, object and mod schemas, normalized pack and43+sharing paths, settled events, solver fixtures, and board presentation data44+will change. Existing levels without channel metadata remain unchanged. Gate45+and plate visuals are consumed by the design-owned renderer after the engine46+contract is accepted.
A openspec/changes/add-channel-linked-gates/specs/channel-linked-gates/spec.md +139 -0
@@ -0,0 +1,139 @@
1+## ADDED Requirements2+3+### Requirement: Channel metadata SHALL be stable and declarative4+5+Authored channel metadata SHALL be validated against the host channel palette,6+remain attached to its authored entity ID through noun transformations, and7+contain no executable behavior.8+9+#### Scenario: A channel-linked pair loads10+11+- **WHEN** a level declares a PLATE and GATE with the same supported channel12+- **THEN** both entities load with their stable channel metadata and participate13+ in one circuit14+15+#### Scenario: A malformed channel fails closed16+17+- **WHEN** a pack uses an unknown channel, unsupported metadata field, or18+ executable channel value19+- **THEN** validation rejects the pack before installation with the declaration20+ path21+22+#### Scenario: Channel survives a noun transform23+24+- **WHEN** `GATE IS CRATE` transforms an authored gate25+- **THEN** the entity becomes an ordinary CRATE while retaining its authored26+ channel for a later transformation back to GATE27+28+### Requirement: Plate pressure SHALL use final occupancy29+30+At the end of an accepted turn, after movement, transforms, automata, and contacts, a current-noun PLATE SHALL be pressed when any other surviving entity occupies its cell. Words and objects count regardless of properties.31+32+#### Scenario: Anything can press a plate33+34+- **WHEN** a word, non-PUSH YOU object, pushed object, or automaton actor ends35+ on a plate36+- **THEN** the plate is pressed at the settled boundary37+38+#### Scenario: A plate does not press itself39+40+- **WHEN** a plate has no other surviving entity in its cell41+- **THEN** it is unpressed even though the plate entity occupies the cell42+43+#### Scenario: SINK releases a plate on the same turn44+45+- **WHEN** the only presser on a plate is destroyed by SINK or DEFEAT during a46+ turn47+- **THEN** the settled plate is unpressed and its linked gate is recomputed from48+ the released final occupancy49+50+### Requirement: Closed gates SHALL block from the prior settled state51+52+During movement planning, a current-noun GATE SHALL block entry as an53+unconditional STOP when its prior settled projection is closed. Gate changes54+SHALL not take effect mid-turn.55+56+#### Scenario: Pressing a plate does not open the gate mid-turn57+58+- **WHEN** a mover enters a plate on the same turn that it would cross the59+ linked closed gate60+- **THEN** the gate blocks that turn and is available on the following turn if61+ the plate remains pressed62+63+#### Scenario: Rules cannot reopen a closed gate64+65+- **WHEN** a closed gate has a rule that would otherwise remove or override STOP66+- **THEN** movement into the gate remains blocked67+68+#### Scenario: An open gate is traversable69+70+- **WHEN** the prior settled projection is open71+- **THEN** the gate contributes no movement blocker and does not crush an entity72+ entering or occupying its cell73+74+### Requirement: Gate openness SHALL be memoryless and channel linked75+76+After contacts, each current-noun GATE SHALL be open exactly when its channel77+has a pressed current-noun PLATE or another surviving entity occupies the gate78+cell. A transformed-away gate SHALL leave the circuit; a gate transformed back79+SHALL recompute its state from the final board without remembered openness.80+81+#### Scenario: A jam keeps a gate open82+83+- **WHEN** a gate would lose its pressed plate but another surviving entity84+ occupies the gate cell after contacts85+- **THEN** the gate remains open and is marked jammed until that cell is empty86+87+#### Scenario: A gate closes after the jam clears88+89+- **WHEN** the jammed gate cell becomes empty and no linked plate is pressed90+- **THEN** the settled gate becomes closed91+92+#### Scenario: Transforming away and back recomputes93+94+- **WHEN** a gate transforms to CRATE, later transforms back to GATE, and the95+ final channel plate and gate cell are empty96+- **THEN** it rejoins closed; no prior open state affects the result97+98+### Requirement: Gate and plate changes SHALL be observable in settled events99+100+Settled results SHALL expose cloned `gateChanged` and `plateChanged` arrays when101+their final projections change. Gate records SHALL contain `id`, `open`, and102+`jammed`; plate records SHALL contain `id` and `pressed`.103+104+#### Scenario: A circuit transition emits both records105+106+- **WHEN** a crate leaves a plate and the linked gate closes107+- **THEN** the settled result reports the plate release and gate closure with108+ stable authored IDs109+110+#### Scenario: Returned events cannot mutate state111+112+- **WHEN** a caller edits a returned gateChanged or plateChanged record113+- **THEN** the engine's next `getState()` and turn use the original projections114+115+### Requirement: Gate state SHALL survive history and solver replay116+117+Reset, Undo, restore, public transitions, and fast solver transitions SHALL118+derive identical gate and plate projections from the same settled object/rule119+state. The compact solver identity SHALL not omit any value that affects the120+prior settled blocker projection and SHALL reject hidden mutable gate fields.121+122+#### Scenario: Undo restores a jam and release123+124+- **WHEN** a turn opens or jams a gate and the player invokes Undo125+- **THEN** the prior plate and gate projections return and the next movement126+ matches direct replay127+128+#### Scenario: Solver parity preserves the next gate decision129+130+- **WHEN** a compact snapshot is decoded and restored before a movement that131+ approaches a gate132+- **THEN** public and fast transitions agree on accepted status, object state,133+ gate projection, and emitted gate/plate changes134+135+#### Scenario: Two channels remain independent136+137+- **WHEN** an amber plate is pressed while a teal plate is not138+- **THEN** only amber gates open or jam; teal gates remain governed by teal139+ occupancy
A openspec/changes/add-channel-linked-gates/tasks.md +47 -0
@@ -0,0 +1,47 @@
1+## 1. Metadata and data boundaries2+3+- [ ] 1.1 Add validated channel metadata to normalized object records and the4+ host fixed channel palette.5+- [ ] 1.2 Preserve channels through `object()` helpers, reset, restore, noun6+ transforms, catalog views, CommonJS export/import, and data-only sharing.7+- [ ] 1.3 Validate duplicate IDs, malformed channels, and channel metadata on8+ unsupported object kinds; add actionable diagnostics for unmatched circuits.9+- [ ] 1.4 Add neutral channel shape mapping as presentation data without10+ property-color coupling.11+12+## 2. Gate movement and settled projection13+14+- [ ] 2.1 Add prior-settled gate blocker checks to the shared movement/push15+ planner; closed gates block like STOP regardless of rules.16+- [ ] 2.2 Define current-noun participation and retain authored channel through17+ transforms while clearing inactive gate/plate projections.18+- [ ] 2.3 Recompute plate pressure and gate openness after movement, transforms,19+ automata, and SINK/DEFEAT contacts.20+- [ ] 2.4 Implement memoryless jam openness from final gate-cell occupancy and21+ ensure a gate never crushes or destroys its occupant.22+- [ ] 2.5 Keep plate pressing excluded from the plate entity itself while23+ accepting words, YOU objects, pushed objects, and automata as pressers.24+25+## 3. Events and state boundaries26+27+- [ ] 3.1 Emit cloned `gateChanged: [{ id, open, jammed }]` records for settled28+ projection changes.29+- [ ] 3.2 Emit cloned `plateChanged: [{ id, pressed }]` records for settled30+ pressure changes, including SINK/DEFEAT release.31+- [ ] 3.3 Add reset, Undo, restore, rejected-turn, and noun-transform fixtures32+ for gate and plate projections.33+- [ ] 3.4 Verify transformed-away and transformed-back gates lose remembered34+ state and recompute from the final board.35+36+## 4. Solver and world acceptance37+38+- [ ] 4.1 Register the gate projection with the declarative mechanic codec and39+ reject hidden mutable gate fields.40+- [ ] 4.2 Prove public and fast transition parity for D1 door stop, D2 hand-off,41+ D3 jam, SINK-released plates, and two isolated channels.42+- [ ] 4.3 Prove compact solver identities and shortest routes do not merge43+ distinct object/rule states with different gate projections.44+- [ ] 4.4 Add the accepted Crate Works gate proofs only after the world team's45+ solver gate and design visual review pass.46+- [ ] 4.5 Run the full Node suite, production build, browser regression, and47+ strict OpenSpec validation.