# 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

```json
{
  "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

```json
{
  "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

```json
{ "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

```json
{ "x": 2, "y": 2 }
```

Simple position. Walls are impassable — blocks stop when they would move into a wall cell.

### Special Tile Object

```json
{ "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

```json
{ "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:

```json
{
  "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.

```json
{
  "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.

```json
{
  "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.

```json
{
  "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:

1. `id` is unique across all levels
2. `grid.width` and `grid.height` are between 3 and 10
3. All block, wall, and special tile positions are within grid bounds
4. No two objects occupy the same cell
5. `par` is a positive integer
6. `objective.type` is one of the defined types
7. If `objective.type` is `"clear_color"`, the specified color exists in `blocks`
8. At least 2 blocks are present (a puzzle requires at minimum something to collide)
9. 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.
