# Switches, plates and logic gates (puzzle.circuits) ## What it is for `puzzle.circuits` is a small, deterministic logic engine for puzzles: switches and pressure plates feed boolean gates, on-delays, pulses and latches, and the outputs of the graph tell your game which doors or mechanisms to open or close. It holds the rules only. Your game owns the meshes, the colliders, the weight of what stands on a plate, the animation and the sounds. For the sibling kit that traces light beams and for how the two compose, read [the puzzles guide](guide:puzzles). ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "puzzle.circuits" } ``` It needs no other kit. The kit is a service with up to 128 independent instances (one per room or mechanism); the instance `default` exists from the start, built from the kit configuration. `{}` gives one `input` node named `switch` and one output named `door` fed by it. ## First example The test sends `step` with the input `switch` set to `true`, then `sync`, which answers an `action` with a `token`. The game tries to open the door; here the door is blocked, so it answers `resolve` with `success: false`. The signal stays committed (`signals.switch` is `true`) but `applied.door` stays `false`. A later `sync` proposes the change again; this time the game applies it and answers `resolve` with `success: true`, and `applied.door` becomes `true`. A second `resolve` with the same token is an error, and a `sync` with nothing left to do answers a null `action`. ## Settings and defaults | Setting | Type | Default | Meaning | |---|---|---|---| | `nodes` | list, 1 to 128 | one `input` named `switch` | the signal graph, acyclic; declaration order does not matter | | `outputs` | list, 0 to 64 | one output `door` sourced from `switch` | `{ id, source }`: actuators fed by a node | Node kinds (every node has an `id` of 1 to 128 bytes and a `kind`; unknown fields are rejected): | Kind | Fields | Meaning | |---|---|---| | `input` | `initial` (false) | a boolean the game sets | | `plate` | `threshold` (1) | true when the observed load in kg reaches it | | `and`, `or`, `xor` | `inputs` (2 to 16) | gates; `xor` is odd parity | | `not` | `inputs` (1) | inverts a signal | | `delay` | `inputs` (1), `seconds` (1) | true after its input has been high for that long | | `pulse` | `inputs` (1), `seconds` (1) | a rising edge starts a pulse of that length | | `latch` | `inputs` `[set, reset]` | holds its state; reset wins | Durations are 0.001 to 3600 seconds. Cycles are refused; a `latch` supplies memory. A configuration can also be `{ "preset": "" }`, which takes a document of the presets registry of the project; the kit ships `switchDoor`, `twoPlates`, `timedDoor` and `latchedDoor`. Other keys given next to `preset` win over the preset. ## Services, events, variables The service `puzzle.circuits`: - `create(id, config)`: a new instance; answers its id. Refused beyond 128 instances, for a duplicate id, or for an invalid graph. - `request(id, command)`: runs a command (below) and answers its result. - `remove(id)`: answers `true` if it existed. - `ids()`: the instance ids. Commands (`op`): `step` (`dt` 0.000001 to 0.1 s, `inputs` as absolute observations: `{ id, value }`, a boolean for `input`, kg for `plate`), `sync` (answers `{ state, action }`; `force: true` sends every output), `resolve` (`token`, `success`), `status`, `config`, `reset`, `preview`, `save` and `load`. `state` holds `inputs`, `memory`, `signals` and `applied`. Events, each named with the prefix of the kit id (for example `puzzle.circuits` followed by a dot and the word below): `created` (`instance`, `preset`), `removed` (`instance`), `changed` (`instance`, `op`, `result`) after a command that changes the instance, and `refused` (`instance`, `op`, `by`, `reason`) when a tap refused a command. The kit has no variables. For scripts and rules, the action and the condition of the kit send a command to an instance and ask whether it exists (`instance` defaults to `default`). ## Hooks and modding - `puzzle.circuits.create`: `{ instance, preset, config }`, run before an instance is created. A tap may change `config`, or cancel: `create` then fails with "creation refused". - `puzzle.circuits.request`: `{ instance, op, command }`, run before a command that changes the instance (not `status`, `config`, `save`, `preview`, `load`, `step`, `sync`). A tap may change `command`, or cancel: the call answers `{ ok: false, cancelled: true, by, reason }` and the event `refused` is emitted. ## Engines One Rust core is used on the web, the native player and any engine through the C library; the same graph gives the same signals everywhere with the same fixed `dt` and the same observations. The engine side reads the plates, applies the output batch and animates. ## When it does not do what you expect - The door never moves: you did not call `sync` after `step`, or you did not `resolve` the token. - A plate stays lit: the game must send `0` when it empties, and must count a body once. - A configuration is refused: a cycle, an unknown reference, a duplicate edge, too many inputs, or an id longer than 128 bytes. - A command fails while an action is pending: resolve it first; changes, `save` and `load` are refused until then. - Timing changes between runs: the tick size changed; use one fixed `dt`. ## Limits - There is no hidden clock: nothing advances unless you send `step`. - The kit never moves a door itself and does not query the physics. - Signal graphs are bounded: 128 nodes, 64 outputs, 16 inputs per gate, 128 instances. ## Versions Kit version 1.1.0 (service contract 1.1.0). 1.1.0 added the hooks, the presets, the outcome events and the script verbs.