9.2 KiB
Collapse Logic — Level Format Specification
Version: 1.0
Last Updated: March 2026
Studio: Vulcara Games
Overview
Levels are defined as JSON files bundled in the app. Each world has a directory of level files. The format is designed to be human-readable for hand-crafting levels and machine-parseable for the game engine.
File Structure
CollapseLogic/
├── Levels/
│ ├── world1/
│ │ ├── level_01.json
│ │ ├── level_02.json
│ │ └── ...
│ ├── world2/
│ │ └── ...
│ └── metadata.json
Level JSON Schema
{
"id": "w1_01",
"world": 1,
"level": 1,
"title": "First Steps",
"grid": {
"width": 5,
"height": 5
},
"blocks": [
{ "x": 1, "y": 1, "color": "red" },
{ "x": 3, "y": 1, "color": "red" },
{ "x": 2, "y": 3, "color": "blue" },
{ "x": 4, "y": 3, "color": "blue" }
],
"walls": [
{ "x": 2, "y": 2 }
],
"special_tiles": [],
"objective": {
"type": "clear_all"
},
"par": 4,
"hints": [
"Try pushing the top-left red block down first."
]
}
Field Definitions
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier. Convention: w{world}_{level:02d} (e.g., w1_01, w3_15) |
world |
integer | Yes | World number (1-6) |
level |
integer | Yes | Level number within the world (1-20) |
title |
string | No | Display name for the level (optional flavor text) |
grid |
object | Yes | Grid dimensions |
blocks |
array | Yes | Array of block objects placed on the grid |
walls |
array | No | Array of wall positions. Default: [] |
special_tiles |
array | No | Array of special tile objects. Default: [] |
objective |
object | Yes | Win condition for the level |
par |
integer | Yes | Target move count for 3-star rating |
hints |
array | No | Array of hint strings (revealed progressively). Default: [] |
Grid Object
{
"width": 5,
"height": 5
}
| Field | Type | Constraints | Description |
|---|---|---|---|
width |
integer | 3–10 | Number of columns |
height |
integer | 3–10 | Number of rows |
Coordinate system: (0, 0) is the top-left cell. x increases rightward, y increases downward.
Block Object
{ "x": 2, "y": 3, "color": "red" }
| Field | Type | Values | Description |
|---|---|---|---|
x |
integer | 0 to width-1 |
Column position |
y |
integer | 0 to height-1 |
Row position |
color |
string | See Color Values | Block color |
Color Values
Primary colors (can merge):
| Value | Display | Hex |
|---|---|---|
"red" |
Ruby | #E63946 |
"blue" |
Sapphire | #457B9D |
"yellow" |
Topaz | #F4D35E |
Secondary colors (result of merges, cannot merge further):
| Value | Created From | Hex |
|---|---|---|
"purple" |
red + blue | #7B2D8B |
"orange" |
red + yellow | #E76F51 |
"green" |
blue + yellow | #2A9D8F |
Secondary-color blocks can appear in level definitions as pre-placed blocks, allowing level designers to create puzzles that start with merged colors already on the board.
Wall Object
{ "x": 2, "y": 2 }
Simple position. Walls are impassable — blocks stop when they would move into a wall cell.
Special Tile Object
{ "x": 3, "y": 4, "type": "mirror", "direction": "horizontal" }
| Field | Type | Values | Description |
|---|---|---|---|
x |
integer | 0 to width-1 |
Column position |
y |
integer | 0 to height-1 |
Row position |
type |
string | See below | Tile type |
| Additional fields vary by type |
Tile types and their extra fields:
mirror
Reverses block direction on contact.
| Field | Values | Description |
|---|---|---|
direction |
"horizontal", "vertical", "both" |
Which axis the mirror reflects |
"horizontal"— reverses left↔right movement; vertical movement passes through"vertical"— reverses up↔down movement; horizontal movement passes through"both"— reverses any direction (block bounces back the way it came)
splitter
Breaks a merged (secondary) block into its two primary components. The two resulting blocks are placed on either side of the splitter along the axis of movement.
No extra fields.
If a primary-color block hits a splitter, it passes through (no effect).
void
Absorbs any block that enters. Single use — the void tile is consumed along with the block.
| Field | Values | Description |
|---|---|---|
charges |
integer (default: 1) | Number of blocks it can absorb before being consumed |
ice
Block slides through without stopping. The block continues moving until it hits a non-ice cell's wall/block/boundary.
No extra fields.
lock
A block that can only be destroyed by a matching key block.
| Field | Values | Description |
|---|---|---|
lock_color |
color string | The color of key required to destroy this lock |
key
When colliding with a matching lock, both are destroyed.
| Field | Values | Description |
|---|---|---|
key_color |
color string | Must match a lock's lock_color to destroy it |
Objective Object
{ "type": "clear_all" }
| Type | Extra Fields | Description |
|---|---|---|
"clear_all" |
None | Remove all blocks from the board |
"clear_color" |
"color": "red" |
Remove all blocks of a specific color |
"reduce_to" |
"count": 1 |
Reduce total blocks to the specified count |
"clear_targets" |
"targets": [{"x":2,"y":3}, ...] |
Clear specific cells (blocks must be destroyed at those positions) |
Metadata File
Levels/metadata.json provides an index of all worlds and levels:
{
"version": "1.0",
"worlds": [
{
"id": 1,
"name": "Primary",
"description": "Learn the basics of pushing and destroying.",
"new_mechanic": null,
"levels": [
{
"id": "w1_01",
"title": "First Steps",
"file": "world1/level_01.json",
"is_challenge": false
},
{
"id": "w1_16",
"title": "Ruby Gauntlet",
"file": "world1/level_16.json",
"is_challenge": true,
"stars_required": 30
}
]
}
]
}
Challenge levels have is_challenge: true and require a cumulative star count (stars_required) to unlock.
Example Levels
Tutorial Level (World 1, Level 1)
Two red blocks on a 4x4 grid. Push one into the other.
{
"id": "w1_01",
"world": 1,
"level": 1,
"title": "First Steps",
"grid": { "width": 4, "height": 4 },
"blocks": [
{ "x": 0, "y": 1, "color": "red" },
{ "x": 3, "y": 1, "color": "red" }
],
"walls": [],
"special_tiles": [],
"objective": { "type": "clear_all" },
"par": 1,
"hints": ["Push the left block to the right."]
}
Intermediate Level (World 1, Level 8)
Walls force an indirect path.
{
"id": "w1_08",
"world": 1,
"level": 8,
"title": "Detour",
"grid": { "width": 5, "height": 5 },
"blocks": [
{ "x": 0, "y": 0, "color": "red" },
{ "x": 4, "y": 0, "color": "red" },
{ "x": 1, "y": 4, "color": "blue" },
{ "x": 3, "y": 4, "color": "blue" }
],
"walls": [
{ "x": 2, "y": 0 },
{ "x": 2, "y": 1 },
{ "x": 2, "y": 3 },
{ "x": 2, "y": 4 }
],
"special_tiles": [],
"objective": { "type": "clear_all" },
"par": 6,
"hints": [
"The wall column splits the board in two.",
"Row 2 is the only crossing point."
]
}
Merge Level (World 2, Level 5)
Create purple to clear it.
{
"id": "w2_05",
"world": 2,
"level": 5,
"title": "Color Theory",
"grid": { "width": 5, "height": 5 },
"blocks": [
{ "x": 0, "y": 2, "color": "red" },
{ "x": 4, "y": 2, "color": "blue" },
{ "x": 2, "y": 0, "color": "purple" }
],
"walls": [],
"special_tiles": [],
"objective": { "type": "clear_all" },
"par": 3,
"hints": [
"You need to make a purple block to match the one already on the board.",
"Merge red and blue first, then align the two purples."
]
}
Validation Rules
A level file is valid if:
idis unique across all levelsgrid.widthandgrid.heightare between 3 and 10- All block, wall, and special tile positions are within grid bounds
- No two objects occupy the same cell
paris a positive integerobjective.typeis one of the defined types- If
objective.typeis"clear_color", the specified color exists inblocks - At least 2 blocks are present (a puzzle requires at minimum something to collide)
- Block colors are valid primary or secondary color strings
Extending the Format
New special tile types can be added by defining a new type string and its associated extra fields. The game engine should gracefully ignore unrecognized tile types (forward compatibility for older app versions loading newer level packs).
New objective types follow the same pattern. The objective object is intentionally flexible — additional fields can be added per type without breaking existing levels.