Gridponder DSL v0.5 — Game Schema
Defines all gameplay content shared across levels: entity kinds, systems, actions, rules, and the level sequence.
Purpose
game.json contains all gameplay content shared across levels: entity kinds, systems, actions, rules, defaults, and the level sequence. It does not repeat identity or presentation metadata — those live exclusively in manifest.json.
Visual presentation and input bindings are defined separately in theme.json — see 06_theme.md.
Top-Level Structure
{
"layers": [ ... ],
"actions": [ ... ],
"entityKinds": { ... },
"systems": [ ... ],
"rules": [ ... ],
"levelSequence": [ ... ],
"defaults": { ... }
}
Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
layers |
array | yes | Board layer definitions. |
actions |
array | yes | Available player action types. |
entityKinds |
object | yes | Catalog of entity kind definitions. |
systems |
array | yes | Enabled engine systems with configuration. |
rules |
array | no | Game-level rules (shared across levels). |
levelSequence |
array | yes | Ordered list of levels and story screens. |
defaults |
object | no | Default values for level fields. |
Layers
Defines the board's layer stack. Each layer has a name and occupancy rule.
"layers": [
{ "id": "ground", "occupancy": "exactly_one", "default": "empty" },
{ "id": "objects", "occupancy": "zero_or_one" },
{ "id": "markers", "occupancy": "zero_or_one" },
{ "id": "actors", "occupancy": "zero_or_one" },
{ "id": "structures","occupancy": "zero_or_one" }
]
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Layer identifier. |
occupancy |
string | yes | "exactly_one" (every cell has a value) or "zero_or_one" (cells may be null). |
default |
string | no | Default cell value for exactly_one layers. Default: "empty". |
The layer order defines rendering order (first = bottom). The avatar is not part of any layer — it is rendered separately on top.
Actions
Declares the abstract action types available in this game.
"actions": [
{
"id": "move",
"params": { "direction": { "type": "direction", "values": ["up", "down", "left", "right"] } }
},
{
"id": "diagonal_swap",
"params": { "direction": { "type": "direction", "values": ["up_left", "up_right", "down_left", "down_right"] } }
},
{
"id": "rotate",
"params": { "rotation": { "type": "enum", "values": ["clockwise", "counterclockwise"] } }
},
{
"id": "flip",
"params": { "axis": { "type": "enum", "values": ["vertical", "horizontal"] } }
},
{
"id": "flood",
"params": {}
},
{
"id": "tap_cell",
"params": { "position": { "type": "position" } }
}
]
Each action:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Unique action identifier. Referenced by controls and systems. |
params |
object | yes | Parameter definitions. Keys are param names, values describe the type. |
Parameter types:
direction— a direction value from the givenvalueslistposition— a[x, y]board coordinateenum— one of the givenvaluesinteger— an integer value
Entity Kinds
Defines all entity types used by this game. Levels reference kinds by their key name.
"entityKinds": {
"empty": {
"layer": "ground",
"tags": ["walkable"],
"sprite": null,
"description": "Walkable floor"
},
"void": {
"layer": "ground",
"tags": [],
"sprite": "assets/sprites/void.png",
"description": "Non-playable area"
},
"wall": {
"layer": "ground",
"tags": ["solid"],
"sprite": "assets/sprites/wall.png"
},
"water": {
"layer": "ground",
"tags": ["liquid", "walkable"],
"sprite": "assets/sprites/water.png"
},
"rock": {
"layer": "objects",
"tags": ["solid", "breakable"],
"sprite": "assets/sprites/rock.png"
},
"wood": {
"layer": "objects",
"tags": ["solid", "burnable", "pushable"],
"sprite": "assets/sprites/wood.png"
},
"torch": {
"layer": "objects",
"tags": ["pickup"],
"sprite": "assets/sprites/torch.png"
},
"pickaxe": {
"layer": "objects",
"tags": ["pickup"],
"sprite": "assets/sprites/pickaxe.png"
},
"metal_crate": {
"layer": "objects",
"tags": ["solid", "pushable"],
"sprite": "assets/sprites/metal_crate.png"
},
"flag": {
"layer": "markers",
"tags": ["goal_target"],
"sprite": "assets/sprites/flag.png"
},
"portal": {
"layer": "objects",
"tags": ["teleport"],
"sprite": "assets/sprites/portal.png",
"params": { "channel": { "type": "string", "required": true } }
},
"number": {
"layer": "objects",
"tags": ["mergeable"],
"sprite": "assets/sprites/number.png",
"params": { "value": { "type": "integer", "required": true } }
},
"spirit": {
"layer": "actors",
"tags": ["npc"],
"sprite": "assets/sprites/spirit.png",
"params": {
"behavior": { "type": "string", "required": true },
"targetTag": { "type": "string" }
}
},
"colored": {
"layer": "objects",
"tags": ["pushable"],
"sprite": "assets/sprites/colored.png",
"params": { "color": { "type": "string", "required": true } }
}
}
Entity Kind Fields
| Field | Type | Required | Description |
|---|---|---|---|
layer |
string | yes | Which board layer this kind belongs to. Must reference a defined layer. |
tags |
array of strings | yes | Semantic labels. Systems use tags for entity selection. |
sprite |
string or null | no | Path to sprite asset. null means invisible/transparent. |
symbol |
string | yes | Single Unicode character used in text grid representations (TextRenderer). Must be unique within a game; @ is reserved for the avatar. Use narrow (display-width 1) characters only — basic ASCII, box-drawing (═║╬), math symbols (≈), or similar. Wide characters (emoji, CJK) break grid alignment and must not be used. |
symbolParam |
string | no | If set, the symbol is taken from this instance parameter at render time instead of symbol. Used for entities whose symbol varies by value (e.g. number tiles show their numeric digit). symbol acts as the legend label and fallback. |
params |
object | no | Parameterized fields that instances may set. Each param defines its type and optionally required. |
description |
string | no | Human-readable description. |
uiName |
string | no | Display name for UI. Defaults to the kind key. |
animations |
object | no | Named animation sequences for this entity. See Animations. |
render |
object | no | Additional rendering hints (opacity, tint). |
Animations
Entity kinds may define named animation sequences triggered by effects or systems. Each animation is a named key mapping to a frame sequence.
"wood": {
"layer": "objects",
"tags": ["solid", "burnable", "pushable"],
"sprite": "assets/sprites/wood.png",
"animations": {
"burning": {
"frames": ["assets/sprites/wood_fire_ignites.png", "assets/sprites/wood_full_burn.png", "assets/sprites/wood_smoke_and_ash.png"],
"duration": 1500,
"mode": "once"
}
}
}
"rock": {
"layer": "objects",
"tags": ["solid", "breakable"],
"sprite": "assets/sprites/rock.png",
"animations": {
"breaking": {
"frames": ["assets/sprites/rock_with_cracks.png", "assets/sprites/rock_broken.png"],
"duration": 1000,
"mode": "once"
}
}
}
Animation fields:
| Field | Type | Required | Description |
|---|---|---|---|
frames |
array of strings | yes | Ordered sprite paths. Played sequentially. |
duration |
integer | yes | Total animation duration in milliseconds. Divided evenly across frames. |
mode |
string | no | "once" (default) — plays once. "loop" — repeats until state changes. |
Effects reference animations by name via the animation field (see 05_rules.md §5). When an effect specifies an animation, the engine plays it before (or during) the state change. If no animation is specified, the state change is instant.
The animation name is scoped to the entity kind at the target position. For example, "animation": "burning" on a destroy effect at a cell containing wood looks up wood.animations.burning.
Tags
Tags are simple semantic labels. They do not carry behavior on their own — systems read tags to decide what to do.
Standard tags for v0.5:
| Tag | Meaning |
|---|---|
solid |
Blocks avatar movement (unless a system handles the interaction) |
walkable |
Avatar can enter this cell |
pickup |
Can be picked up by avatar |
pushable |
Can be pushed by avatar |
breakable |
Can be destroyed by a tool |
burnable |
Can be burned by a fire tool |
liquid |
A terrain type with liquid behavior |
bridge |
Walkable surface created over liquid |
teleport |
Triggers portal system |
mergeable |
Can be merged with matching entities |
goal_target |
Target for reach-type goals |
npc |
Non-player character with autonomous behavior |
target_marker |
Non-blocking marker for NPC targeting |
Games may define custom tags beyond these.
Multi-Cell Object Kinds
Multi-cell objects (e.g., pipes) referenced in level multiCellObjects arrays should also have an entity kind definition in entityKinds. The kind defines tags, sprite, and parameter schema. The layer for multi-cell objects is typically "structures".
"pipe": {
"layer": "structures",
"tags": ["emitter"],
"sprite": "assets/sprites/pipe.png",
"params": {
"queue": { "type": "array", "required": true },
"currentIndex": { "type": "integer" },
"exitPosition": { "type": "position", "required": true },
"exitDirection": { "type": "string" },
"exit2Position": { "type": "position" },
"exit2Direction": { "type": "string" },
"exit2Index": { "type": "integer" }
}
}
When exit2Position is present the pipe is bidirectional: it has two open exits and routes numbers toward the nearer open exit each turn. See §2.5 queued_emitters for the full routing rules.
Systems
Declares which engine systems are active and their configuration.
"systems": [
{
"id": "movement",
"type": "avatar_navigation",
"config": {
"directions": ["up", "down", "left", "right"],
"solidHandling": "delegate"
}
},
{
"id": "push",
"type": "push_objects",
"config": {
"pushableTags": ["pushable"],
"chainPush": false
}
}
]
Each system entry:
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Unique instance identifier for this system. Used by levels for overrides. |
type |
string | yes | Built-in system type. See System Catalog. |
config |
object | yes | System-specific configuration. |
enabled |
boolean | no | Whether the system is active. Default: true. |
See 04_systems.md for the complete system type catalog and config schemas.
Rules (Game-Level)
Game-level rules apply to all levels unless overridden. See 05_rules.md for the full rules model.
"rules": [
{
"id": "water_clears_inventory",
"on": "avatar_entered",
"where": { "position_has_tag": { "layer": "ground", "tag": "liquid" } },
"if": { "avatar": { "hasItem": true } },
"then": [
{ "clear_inventory": {} }
]
}
]
Level Sequence
An ordered list defining the game's playable progression. Entries are either level references or story screens.
"levelSequence": [
{ "type": "story", "title": "Chapter 1", "text": "The rabbit sets out on a journey...", "image": "assets/story/intro.png" },
{ "type": "level", "ref": "fw_001" },
{ "type": "level", "ref": "fw_002" },
{ "type": "story", "text": "The rabbit reaches the river...", "image": "assets/story/river.png" },
{ "type": "level", "ref": "fw_003" },
{ "type": "level", "ref": "fw_004" },
{ "type": "story", "text": "Victory!", "image": "assets/story/victory.png" }
]
Level Entry
| Field | Type | Required | Description |
|---|---|---|---|
type |
"level" |
yes | Marks this as a level. |
ref |
string | yes | Level id. Must match a file in the levels directory. |
Story Entry
| Field | Type | Required | Description |
|---|---|---|---|
type |
"story" |
yes | Marks this as a story screen. |
title |
string | no | Optional heading text. |
text |
string | no | Body text. |
image |
string | no | Path to an illustration. |
At least one of text or image must be present. Story screens are displayed between levels and dismissed by tapping.
Defaults
Default values applied to levels when those fields are not specified. This reduces repetition.
"defaults": {
"avatar": {
"enabled": true,
"facing": "right",
"inventory": { "slot": null }
},
"maxCascadeDepth": 3
}
| Field | Type | Description |
|---|---|---|
avatar |
object | Default avatar configuration. Levels can override. |
maxCascadeDepth |
integer | Maximum cascade passes during rule resolution. Default: 3. |
Complete Example
{
"id": "com.gridponder.flag_worlds",
"dslVersion": "0.1.0",
"title": "Flag Worlds",
"description": "Guide the rabbit to the flag using tools, crates, and portals.",
"layers": [
{ "id": "ground", "occupancy": "exactly_one", "default": "empty" },
{ "id": "objects", "occupancy": "zero_or_one" },
{ "id": "markers", "occupancy": "zero_or_one" },
{ "id": "actors", "occupancy": "zero_or_one" }
],
"actions": [
{ "id": "move", "params": { "direction": { "type": "direction", "values": ["up","down","left","right"] } } }
],
"entityKinds": {
"empty": { "layer": "ground", "tags": ["walkable"], "sprite": null },
"void": { "layer": "ground", "tags": [], "sprite": "assets/sprites/void.png" },
"wall": { "layer": "ground", "tags": ["solid"], "sprite": "assets/sprites/wall.png" },
"water": { "layer": "ground", "tags": ["liquid", "walkable"], "sprite": "assets/sprites/water.png" },
"bridge":{ "layer": "ground", "tags": ["walkable"], "sprite": "assets/sprites/bridge.png" },
"rock": { "layer": "objects", "tags": ["solid", "breakable"], "sprite": "assets/sprites/rock.png",
"animations": { "breaking": { "frames": ["assets/sprites/rock_with_cracks.png", "assets/sprites/rock_broken.png"], "duration": 1000, "mode": "once" } } },
"wood": { "layer": "objects", "tags": ["solid", "burnable", "pushable"], "sprite": "assets/sprites/wood.png",
"animations": { "burning": { "frames": ["assets/sprites/wood_fire_ignites.png", "assets/sprites/wood_full_burn.png", "assets/sprites/wood_smoke_and_ash.png"], "duration": 1500, "mode": "once" } } },
"torch": { "layer": "objects", "tags": ["pickup"], "sprite": "assets/sprites/torch.png" },
"pickaxe": { "layer": "objects", "tags": ["pickup"], "sprite": "assets/sprites/pickaxe.png" },
"metal_crate": { "layer": "objects", "tags": ["solid", "pushable"], "sprite": "assets/sprites/metal_crate.png" },
"flag": { "layer": "markers", "tags": ["goal_target"], "sprite": "assets/sprites/flag.png" },
"portal": { "layer": "objects", "tags": ["teleport"], "sprite": "assets/sprites/portal.png",
"params": { "channel": { "type": "string", "required": true } } }
},
"systems": [
{ "id": "movement", "type": "avatar_navigation", "config": { "directions": ["up","down","left","right"], "solidHandling": "delegate" } },
{ "id": "push", "type": "push_objects", "config": { "pushableTags": ["pushable"], "chainPush": false } },
{ "id": "portals", "type": "portals", "config": { "teleportTags": ["teleport"], "matchKey": "channel", "endMovement": true } }
],
"rules": [
{
"id": "pickup_item",
"on": "avatar_entered",
"where": { "position_has_tag": { "layer": "objects", "tag": "pickup" } },
"then": [
{ "set_inventory": { "item": "$cell.objects.kind" } },
{ "destroy": { "position": "$event.position", "layer": "objects" } }
]
},
{
"id": "torch_burns",
"on": "move_blocked",
"where": { "position_has_tag": { "layer": "objects", "tag": "burnable" } },
"if": { "avatar": { "hasItem": "torch" } },
"then": [
{ "destroy": { "position": "$event.position", "layer": "objects", "animation": "burning" } },
{ "clear_inventory": {} },
{ "resolve_move": {} }
]
},
{
"id": "pickaxe_breaks",
"on": "move_blocked",
"where": { "position_has_tag": { "layer": "objects", "tag": "breakable" } },
"if": { "avatar": { "hasItem": "pickaxe" } },
"then": [
{ "destroy": { "position": "$event.position", "layer": "objects", "animation": "breaking" } },
{ "clear_inventory": {} },
{ "resolve_move": {} }
]
},
{
"id": "object_creates_bridge",
"on": "object_placed",
"where": { "all_of": [
{ "position_has_tag": { "layer": "ground", "tag": "liquid" } },
{ "position_has_tag": { "layer": "objects", "tag": "pushable" } }
]},
"then": [
{ "destroy": { "position": "$event.position", "layer": "objects" } },
{ "transform": { "position": "$event.position", "layer": "ground", "toKind": "bridge" } }
]
},
{
"id": "water_clears_items",
"on": "avatar_entered",
"where": { "position_has_tag": { "layer": "ground", "tag": "liquid" } },
"if": { "avatar": { "hasItem": true } },
"then": [
{ "clear_inventory": {} }
]
}
],
"levelSequence": [
{ "type": "level", "ref": "fw_001" },
{ "type": "level", "ref": "fw_002" },
{ "type": "level", "ref": "fw_003" }
],
"defaults": {
"avatar": { "enabled": true, "facing": "right", "inventory": { "slot": null } },
"maxCascadeDepth": 3
}
}