| `battery_pct` | 12 | how full the battery bar *looks* at the start (visual only) |
| `debug` | false | reveal everything normally hidden — a tile grid with col/row labels, faded `-` one-way platforms, tinted fake blocks / invisible walls / warps (with a line to their destination), dormant phase blocks, not-yet-sprung spikes, crumbled-block ghosts, and moving block paths. The `--debug` CLI flag forces this on for every level. Combine with `F5` to iterate on a level without relaunching. |
## Trap reference
All traps take `at: [col, row]`. Where a trap draws its own block (fake, moving,
patrol, crumble, shooter), leave that map cell **empty** — otherwise a real
solid sits underneath it.
Any trap can also take:
- **`invisible: true`** — stays fully functional but isn't drawn (revealed under
the level's `debug` view). An invisible solid `block` is a wall you can't see.
- **`count: [nx, ny]`** (or a single int for a horizontal line) — place a
line/grid of copies, offset by **`spacing: [sx, sy]`** tiles (default 1). Only
`at` is shifted per copy, so `move` is relative and works; an absolute `path`
is shared. Handy for a row of spikes or a rectangle of (invisible) blocks.
| `type` | Key parameters | Behavior |
|-----------------|----------------|----------|
| `spike` | `direction` (`up`/`down`/`left`/`right`), `trigger`, `delay` | Deadly spike; active while its `trigger` holds. The sprite auto-rotates to point the way `direction` faces. |
| `path` [[col,row],…] | waypoints it travels between (default just `[at]` = stationary) |
| `move` [dcol,drow] | shorthand for a 2-point path `[at, at+move]` (a slider) |
| `mode` | `once` (default): advance to the last point while triggered, retreat to the first when not — the slider/dropper. `loop` / `pingpong`: cycle the whole path continuously (a patrol). |
| `trigger` | when it activates (default `always`). A patrol is just a path + the default `always` trigger. A block that both moves and phases can give each its own trigger via a per-target map (see Triggers). |
| `deadly` (bool) | `true` → a hazard (spikes) instead of a solid. On a phase block the hazard is live only once materialised. |
| `fake` (bool) | looks solid but you fall straight through it |
| `crumble` (bool) + `crumble_delay`, `respawn` | gives way `crumble_delay` s after you stand on it, vanishes, then reappears after `respawn` s (killing you if you're standing where it re-forms) |
| `phase` (bool) + `fade` | invisible/intangible until triggered, then fades into a solid (or a hazard, if `deadly`) over `fade` s (default 0.3) and back out when the trigger releases. Forming into you is lethal, but a non-deadly one first shoves you clear if you're only clipping an edge; if you die while one is mid-fade it snaps fully visible for the death freeze. |
| `sprite` | override sprite (default: `spike_block` if deadly, `fake_block` if fake, `crumble_block` if crumble, `phase_block` if phase, else `moving_block`) |
| `delay` / `release` | hold `delay` s to activate (may be a per-target map); in `once` motion, be clear `release` s (default 0.1) to start retracting — stops boundary jitter |
| `sense` | `home` (default): a slider senses its trigger from its resting cell, so moving away can't toggle its own trigger. `current`: senses from the live position — for a block you *ride* (e.g. a dropper) so it stays put while ridden instead of pulling back |
```yaml
- type: block # stationary spike block
at: [13, 4]
deadly: true
- type: block # dropper: extends down while you're above it, rides with you
| `{ dir: …, inclusive: true }` | …also count the trap's own tile as being on that side (default `false`: the player must be strictly past the near edge) |