Gridponder DSL — System Catalog
The ten built-in engine systems, their execution order, and the per-system configuration reference.
1. System Architecture
Execution Phases
Each system declares which phase(s) it participates in. During a turn, phases execute in this fixed order:
| # | Phase | Purpose |
|---|---|---|
| 1 | input_validation |
Engine validates action legality. No system runs here — this is engine-internal. |
| 2 | action_resolution |
Primary action executes (avatar moves, tiles slide, overlay shifts, etc.). |
| 3 | movement_resolution |
Secondary movement triggered by the primary action (pushing, teleporting). |
| 4 | interaction_resolution |
Reserved for future systems. In v0.5, item/environment interactions are handled by rules in phase 5. |
| 5 | cascade_resolution |
Chain effects: rules evaluate, emitters fire, gravity settles. Repeats up to maxCascadeDepth. |
| 6 | npc_resolution |
Autonomous NPC behavior executes. |
| 7 | goal_evaluation |
Win and lose conditions are checked. |
Events
Systems emit events as they modify state. Events accumulate during phases 2–4 and are consumed by rules during phase 5. See 05_rules.md for the full event catalog.
Interaction Protocol
Systems interact through:
- Shared state — all systems read/write the board, avatar, and variables. Phase ordering determines visibility.
- Events — systems emit events; rules (and some systems) react to them.
- Phase ordering — a phase-3 system always sees state changes from phase 2.
Systems never call each other directly.
Config Override
Levels may override specific config fields per system via systemOverrides. Overrides are shallow-merged onto the game-level config.
2. System Catalog
2.1 avatar_navigation
Purpose: Move the avatar one step per move action. Enforce boundaries and solid collisions.
Phase: action_resolution
Events emitted: avatar_entered, avatar_exited, move_blocked
Config:
| Field | Type | Default | Description |
|---|---|---|---|
directions |
array of strings | ["up","down","left","right"] |
Allowed movement directions. |
solidHandling |
string | "block" |
What happens when moving into a solid cell: "block" (reject move) or "delegate" (let later systems handle, e.g. push or consume). |
moveAction |
string | "move" |
Which action id triggers navigation. |
Behavior:
- Compute target position from direction.
- Check bounds — reject if out of grid.
- Check ground layer — reject if
void. - Check
solidtag on objects layer:"block": reject move."delegate": mark the move as pending. Emitmove_blockedwith the target position and blocker kind. Later phases (push) or rules (resolve_moveeffect) may complete or reject the pending move.
- If not blocked, move avatar to target. Emit
avatar_exitedfor old position,avatar_enteredfor new position. - Update
avatar.facingto the movement direction.
2.2 push_objects
Purpose: Allow the avatar to push configured objects into adjacent empty cells.
Phase: movement_resolution
Events emitted: object_pushed, object_placed, avatar_entered, avatar_exited
Config:
| Field | Type | Default | Description |
|---|---|---|---|
pushableTags |
array of strings | ["pushable"] |
Tags identifying pushable entities. |
validTargetTags |
array of strings | ["walkable"] |
Tags the destination ground must have. Also allows null (empty objects layer) cells. |
chainPush |
boolean | false |
Whether pushing into another pushable triggers a chain push. |
toolInteractions |
array | [] |
List of item-based destruction interactions. Each entry: { "item": "<kind>", "targetTag": "<tag>", "consumeItem": false, "animation": "<name>" }. When the avatar holds the specified item and moves into an entity with the specified tag, the entity is destroyed and the avatar enters the vacated cell. consumeItem (default false) controls whether the item is removed from inventory. animation (optional) names an animation defined on the target entity kind to play before removal. Applies before pushable logic — works on any solid entity, not just pushable ones. |
Behavior:
- When avatar movement targets a cell with an entity in the objects layer:
a. Check
toolInteractionsin order. If any interaction matches (avatar holds the required item, entity has the required tag), destroy entity, optionally consume item, play animation if configured, move avatar. Skip remaining push logic. b. If entity is not pushable, movement fails. c. Compute push destination (one cell further in movement direction). c. Check push destination: must be in bounds, ground must have avalidTargetTagstag, objects layer must be empty (or have matching tag ifchainPush). d. If valid: move pushed object, then move avatar into vacated cell. e. If invalid: movement fails, avatar stays. - Emit
object_pushed,object_placedfor pushed object; standard avatar events.
2.3 portals
Purpose: Teleport avatar (or objects) between paired portal entities.
Phase: movement_resolution
Events emitted: avatar_entered, avatar_exited
Config:
| Field | Type | Default | Description |
|---|---|---|---|
teleportTags |
array of strings | ["teleport"] |
Tags identifying portal entities. |
matchKey |
string | "channel" |
Entity parameter used to match portal pairs. |
endMovement |
boolean | true |
Whether teleport ends the move (avatar lands on exit portal). |
teleportObjects |
boolean | false |
Whether pushed objects can also teleport through portals. |
Behavior:
- When avatar enters a cell with a
teleportTagsentity: a. ReadmatchKeyparameter (e.g.,channel: "blue"). b. Find the paired portal with the same value. c. Move avatar to paired portal position. d. IfendMovement: turn continues from portal position. Iffalse: avatar continues moving in original direction. - If
teleportObjectsand an object is pushed onto a portal: teleport object similarly.
2.4 slide_merge
Purpose: Slide all mergeable tiles in the swipe direction; merge matching tiles.
Phase: action_resolution
Events emitted: tiles_slid, tiles_merged, cell_cleared
Config:
| Field | Type | Default | Description |
|---|---|---|---|
mergeableTags |
array of strings | ["mergeable"] |
Tags identifying slideable/mergeable entities. |
mergeAction |
string | "move" |
Which action id triggers sliding. |
mergePredicate |
string | "equal_value" |
When two tiles merge: "equal_value" (same value param). |
mergeResult |
string | "sum" |
Result of merge: "sum" or "double". |
mergeLimit |
integer | 1 |
Max merges per tile per action. |
blockerTags |
array of strings | ["solid"] |
Tags that stop sliding. |
wrapAround |
boolean | false |
Whether tiles wrap around the board. |
Behavior:
- On action, determine slide direction from action params.
- Process rows/columns in slide direction order.
- Each mergeable tile slides until hitting a boundary, blocker, void, or another tile.
- If tile meets another tile with matching
mergePredicate: merge. New tile hasmergeResultvalue. - Each tile can merge at most
mergeLimittimes per action. - Emit events for each slide and merge.
2.5 queued_emitters
Purpose: Release one item per turn from each multi-cell emitter whose exit cell is empty.
Phase: npc_resolution (runs once per turn, after all slides and cascades have settled)
Events emitted: item_released
Config:
| Field | Type | Default | Description |
|---|---|---|---|
emitterKind |
string | "pipe" |
Multi-cell object kind that acts as an emitter. |
Behavior — unidirectional pipe (no exit2Position):
- Check whether the exit cell (
exitPosition) and the spawn cell (one step inexitDirection) are both empty. - If both empty and the queue has remaining items: spawn the next item at the spawn cell, increment
currentIndex, emititem_released. - Only one item is released per emitter per turn.
Behavior — bidirectional pipe (exit2Position present):
A bidirectional pipe has two open exits. Numbers occupy physical slots (cells) within the pipe. Initially, queue[0] is placed in cell 0 (the exit-1 cell), queue[1] in cell 1, and so on. The runtime state is stored in pipeSlots — an array of length pipe_length (one entry per cell, each int | null).
Each turn the pipe runs two phases:
Emit phase: For each exit, if an item occupies the exit cell and the exit's spawn cell is clear, emit it (place it on the board, set the slot to
null). Both exits can emit simultaneously.Move phase: Each remaining item moves one step toward its nearest exit:
- Distance to exit 1 = cell index; distance to exit 2 =
(pipe_length - 1) - cell index. - Closer to exit 1 → shift one cell toward exit 1 (index − 1).
- Closer to exit 2 → shift one cell toward exit 2 (index + 1).
- Equidistant (midpoint of an odd-length pipe):
- If only exit 1 is clear → move toward exit 1.
- If only exit 2 is clear → move toward exit 2.
- If both clear or both blocked → stuck: no movement this turn.
- An item arriving at an exit cell does not emit on the same turn — it exits on the next turn's emit phase.
- Distance to exit 1 = cell index; distance to exit 2 =
Even vs. odd pipe length: In an even-length pipe every cell has a strictly nearer exit, so the stuck condition never arises. It is exclusive to odd-length pipes.
2.6 overlay_cursor
Purpose: Maintain a movable overlay region that region_transform operates on.
Phase: action_resolution
Events emitted: overlay_moved
Config:
| Field | Type | Default | Description |
|---|---|---|---|
size |
[w, h] |
[2, 2] |
Overlay dimensions. |
moveAction |
string | "move" |
Action id that moves the overlay. |
anchorToAvatar |
boolean | false |
If true, the overlay follows the avatar position. Avatar position = top-left (for 2x2) or center (for 3x3). |
boundsConstrained |
boolean | true |
Whether the overlay must stay fully within the board. |
Behavior:
- On
moveAction, shift overlay position in the action's direction. - If
boundsConstrained, clamp to board boundaries. - If
anchorToAvatar, overlay tracks avatar position automatically. - Update
state.overlay.position. - Emit
overlay_moved.
2.8 region_transform
Purpose: Apply spatial transformations (rotate, flip, diagonal swap) to cell contents within the overlay region.
Phase: action_resolution
Events emitted: region_rotated, region_flipped, cells_swapped
Config:
| Field | Type | Default | Description |
|---|---|---|---|
overlaySystemId |
string | — | Id of the overlay_cursor system providing the region. |
affectedLayers |
array of strings | ["objects"] |
Which layers are transformed. |
operations |
object | {} |
Map of operation name → operation config. |
Each operation:
| Field | Type | Description |
|---|---|---|
type |
string | "rotate", "flip", or "diagonal_swap". |
action |
string | Action id that triggers this operation. |
Example config:
{
"overlaySystemId": "overlay",
"affectedLayers": ["objects"],
"operations": {
"rotate": { "type": "rotate", "action": "rotate" },
"flip": { "type": "flip", "action": "flip" },
"swap": { "type": "diagonal_swap", "action": "diagonal_swap" }
}
}
Operation: rotate
Rotates all cell contents within the overlay.
2×2 clockwise rotation:
[0,0] → [1,0]
[1,0] → [1,1]
[1,1] → [0,1]
[0,1] → [0,0]
3×3 clockwise (standard matrix rotation): [x,y] → [size-1-y, x]
Action params: { "rotation": "clockwise" } or { "rotation": "counterclockwise" }.
Operation: flip
Mirrors cell contents along an axis.
Vertical flip: [x, y] → [x, size-1-y]
Horizontal flip: [x, y] → [size-1-x, y]
Action params: { "axis": "vertical" } or { "axis": "horizontal" }.
Operation: diagonal_swap
Swaps two diagonal corner cells based on direction.
Swap mapping (2×2 overlay at [ox, oy]):
| Direction | Cell A | Cell B |
|---|---|---|
up_left |
[ox+1, oy+1] (bottom-right) |
[ox, oy] (top-left) |
up_right |
[ox, oy+1] (bottom-left) |
[ox+1, oy] (top-right) |
down_left |
[ox+1, oy] (top-right) |
[ox, oy+1] (bottom-left) |
down_right |
[ox, oy] (top-left) |
[ox+1, oy+1] (bottom-right) |
Behavior:
- On the configured action, determine the operation type.
- For rotate/flip: collect all entities within the overlay bounds on affected layers, apply the spatial mapping, reposition.
- For diagonal_swap: swap the two mapped cells.
- Emit the corresponding event.
2.9 flood_fill
Purpose: Flood fill from a source position, changing connected same-kind/same-color cells.
Phase: action_resolution
Events emitted: cells_flooded
Config:
| Field | Type | Default | Description |
|---|---|---|---|
floodAction |
string | "flood" |
Action id that triggers flood fill. |
sourcePosition |
string | "avatar" |
"avatar" (avatar position) or "overlay_center". |
affectedLayer |
string | "objects" |
Layer to flood fill on. |
matchBy |
string | "color" |
"color" (match entities with same color param) or "kind" (match same entity kind). |
colorCycle |
array of strings | ["red","blue","green","yellow","purple","orange"] |
Color cycle for matchBy: "color". Current color advances to next in cycle. |
kindTransform |
object | {} |
For matchBy: "kind", maps current kind → new kind. |
Behavior:
- On
floodAction, determine source position. - Read the entity at source position on
affectedLayer. - Find all connected cells with the same match criterion (4-directional adjacency).
- Apply the transformation (advance color in cycle, or transform kind).
- Emit
cells_flooded.
2.10 anchor_point
Purpose: Maintain a single movable anchor on the board. On the configured action: if no anchor entity exists, place one at the avatar's current position; if one exists, teleport the avatar to the anchor and remove it.
Phase: action_resolution
Events emitted: avatar_exited, avatar_entered (no direction field — ice_slide will not trigger a slide after teleport)
Config:
| Field | Type | Default | Description |
|---|---|---|---|
markerKind |
string | — | Required. Entity kind to use as the anchor marker. |
markerLayer |
string | — | Required. Layer to store the anchor on. Must be declared in the game's layers array. |
action |
string | — | Required. Action id that triggers the toggle. |
blockedByTags |
array of strings | ["solid"] |
Tags on the objects layer that prevent teleportation to the anchor cell. If a matching entity is present, the teleport is skipped and the anchor remains. |
Behavior:
- On the configured action, scan
markerLayerfor any entity ofmarkerKind. - If none found: place
markerKindat the avatar's current cell inmarkerLayer. No events emitted. - If found: check the
objectslayer at the marker cell. If any entity there has ablockedByTagstag, do nothing. - Otherwise: remove the marker, teleport avatar to the marker cell. Emit
avatar_exitedfrom the old position andavatar_enteredat the new position (without adirectionfield in the payload).
Note on ice: The avatar_entered event emitted during teleport intentionally omits direction. The ice_slide cascade system skips events without a direction, so teleporting onto ice does not trigger a slide. Rules that react to avatar_entered (pickup, liquid, etc.) still fire normally since they do not require a direction.
Reuse: This system type is game-agnostic. Any game can use it with a different markerKind, markerLayer, and action to implement "save point", "recall beacon", "twin", or similar mechanics.
3. System Summary Table
| System | Type | Phase | Primary Action |
|---|---|---|---|
| Avatar Navigation | avatar_navigation |
action_resolution |
move |
| Push Objects | push_objects |
movement_resolution |
(automatic on move into pushable) |
| Portals | portals |
movement_resolution |
(automatic on portal entry) |
| Slide Merge | slide_merge |
action_resolution |
move |
| Queued Emitters | queued_emitters |
cascade_resolution |
(event-triggered) |
| Gravity | gravity |
cascade_resolution |
(automatic after state changes) |
| Overlay Cursor | overlay_cursor |
action_resolution |
move |
| Region Transform | region_transform |
action_resolution |
rotate, flip, diagonal_swap |
| Flood Fill | flood_fill |
action_resolution |
flood |
| Anchor Point | anchor_point |
action_resolution |
configurable |
Demoted to rule recipes (see 05_rules.md §9): single-slot inventory, consumable interactions, liquid transitions. These use the standard event–condition–effect primitives and no longer require dedicated engine systems.
4. System Combinations by Game Type
Flag-style games (avatar navigation puzzles)
avatar_navigation + push_objects + portals + inventory/consumable/liquid rule recipes
Sokoban-style games with recall mechanic
avatar_navigation + push_objects + anchor_point + object-on-target rule recipe
Number-style games (slide and merge)
slide_merge + queued_emitters + gravity (with sequence_match goal)
Number-style with diagonal swaps
slide_merge + overlay_cursor + region_transform (diagonal_swap op) (with sequence_match goal)
Transformation-style games (pattern matching)
overlay_cursor + region_transform (rotate + flip ops) + flood_fill
Hybrid games
Any combination of the above. The system architecture supports free composition as long as there are no conflicting action handlers.
5. Reserved System Types (v1+)
These types are reserved for future built-in systems:
line_push— push entire rows/columnsmulti_slot_inventory— carry multiple itemstimers— turn-count-based triggerscollectibles— collect N of M itemsrule_tiles— Baba-Is-You-style mutable rule objectsrotate_flip_board— rotate/flip the entire board (not just an overlay region)spawners— periodic entity spawning