# LostMagic: game data for building your own version

LostMagic (Taito, 2006, Nintendo DS) is a tactics game. Isaac, the hero, casts spells by drawing runes on the touch screen and fights alongside the monsters he recruits, through a story whose choices lead to four endings.

These files hold what was extracted from the game's cartridge (US release): sprites and animations of every unit, character portraits, battle maps with where each enemy starts, the full story with its choices and the logic that orders the missions, texts in English, Spanish, French and German, music, sound effects and the rest of the art. Use them to build a version of the game on any platform.

Base URL: https://lostmagic.skapxd.dev/

Paths inside the JSON files start with `/` and are relative to the base URL.

To download everything at once: https://lostmagic.skapxd.dev/data/files.txt (one URL per line).

## Files

| File | Contents |
|------|----------|
| `/data/units.md`, `/data/units.json` | The 84 units (77 with a name, the rest unnamed appearances), each linking to its details |
| `/data/units/<slug>.md`, `/data/units/<slug>.json` | One unit: its stats, sprite sheet, the exact place of every frame, and its animations |
| `/data/levels.md`, `/data/levels.json` | Levels 1 to 50: experience, the growth curves of the stats and the Line Up capacity |
| `/data/rules.md`, `/data/rules.json` | The game's limits: team, monsters, items, levels and experience |
| `/data/ai.md`, `/data/ai.json` | Movement and AI: routes, target search and the units' orders |
| `/data/duel.md`, `/data/duel.json` | The duel mode: the ready-made teams and the Wi-Fi rating |
| `/data/items.md`, `/data/items.json` | The 8 items: what they raise, how much at each level and which chests hold them |
| `/data/chest.md`, `/data/chest.json` | The treasure chest as a sprite: its closed picture and its five animations |
| `/data/appear-seals.md`, `/data/appear-seals.json` | The appear seals that glow on the floor of some battles, and how a unit comes out |
| `/data/spells.md`, `/data/spells.json` | The 396 spells: runes, MP, aiming, power, status, terrain and their numbers |
| `/data/spell-families.md`, `/data/spell-families.json` | The families of similar spells, what changes inside each one and each spell's total effect |
| `/data/best-spells.md`, `/data/best-spells.json` | The best spells for the player: yield per MP, big jumps, the ones not worth moving up to, a stroke score for each rune and spell, and the ranking by battle situation |
| `/data/spell-scenes.md`, `/data/spell-scenes.json` | Scenes: graphics, palettes, frames and sounds for 396 spells; 90 units and 141 statuses |
| `/data/runes.md`, `/data/runes.json` | The 18 runes: the strokes the game accepts, where they are learned and the MP of each |
| `/data/rune-strokes.md`, `/data/rune-strokes.json` | 640 test strokes for the rune recognizer and what the game gives for each |
| `/data/statuses.md`, `/data/statuses.json` | Status effects: what they do, how long they last and which spells cause them |
| `/data/combat.md`, `/data/combat.json` | Battle constants: opposite elements, normal attacks, shots and element-level growth |
| `/data/camera.md`, `/data/camera.json` | The battle camera: world bounds, D-pad, chant recentring, scripts, shake, top screen and each map's backdrop speed |
| `/data/aiming.md`, `/data/aiming.json` | Aiming a spell: how each mode is aimed, the arrow of the shots, the reticle, the areas, the icons and the range shade, with their pictures |
| `/data/damage-numbers.md`, `/data/damage-numbers.json` | The damage and healing numbers over units: digits, colours, place and how long they stay, with their pictures |
| `/data/chant-screen.md`, `/data/chant-screen.json` | The chant screen: the magic circle and the layer that dims the field, the stroke and the top screen's rune list, with their pictures |
| `/data/battle-end.md`, `/data/battle-end.json` | The end of a battle: the WIN, LOSE, DRAW and TIME OVER banners, their timing and music, and the results screen after a win, with their pictures |
| `/data/title-screen.md`, `/data/title-screen.json` | The title screen: both backgrounds, the five buttons with their label in each language, how they come in, the touch and where each one leads |
| `/data/enemy-marker.md`, `/data/enemy-marker.json` | The letter next to every enemy's HP bar: picture per language, position and when it shows |
| `/data/characters.md`, `/data/characters.json` | The 19 story characters and their portrait expressions |
| `/data/maps.md`, `/data/maps.json` | The 55 battle maps and their 176 battles: missions and starting units |
| `/data/world-map.md`, `/data/world-map.json` | The world map: 44 places, 50 paths and what each place offers in each chapter |
| `/juego/data.json` | The story and its logic: chapters, missions, scenes, battles, flags and endings |
| `/juego/textos/<en\|es\|fr\|de>.json` | Every text of `/juego/data.json` in one language |
| `/data/audio.md`, `/data/audio.json` | 24 songs and 147 sound effects |
| `/data/gallery.md`, `/data/gallery.json` | 818 more pictures: runes, spells and effects, backgrounds, world map, interface, unused art |

## Conventions

- Every file under `/data/` comes in two formats with the same content: `.md`, as tables, easier to read and lighter in tokens, and `.json`, to load the data in a program. The fields described below are the JSON keys; the Markdown tables use the same names as column headers (`name.en`, `name.es` for the two languages).
- Each of the DS's two screens is 256×192 px. Story scenes are drawn for the top one.
- Coordinates are in pixels, from the top-left corner, with y growing downwards.
- Durations come in ticks of 1/60 s, the console's frame rate. `ms` fields are the same, rounded.
- The art is pixel art: scale it by whole numbers with nearest-neighbour sampling to keep it sharp.
- Names come in English and Spanish as `{ "en": …, "es": … }`. The story has four languages.
- Pictures are PNG; animated GIFs are previews only. Audio is AAC in `.m4a`: convert it if your platform does not play AAC.

## Units and their animations

`/data/units.json` lists every unit: `uid` (the id battles and the story use; `null` for unnamed appearances), `slug`, `name`, `element` (`fire`, `wind`, `water`, `earth`, `dark`, `light` or `null`), a `portrait` picture, the `sheet`, and `details`, the URL of its full data.

Each unit has one **sprite sheet**: a PNG holding every frame the unit uses. `sheet.cells` gives the exact rectangle of each frame on it (`x`, `y`, `w`, `h`); the position in that list is the frame's number.

An **animation** is a loop of frames drawn on a fixed **canvas** of `width` × `height`:

- Each item of `frames` says which sheet frame to show (`cell`), where its top-left corner goes on the canvas (`x`, `y`) and for how long (`ticks`). The same cell can appear several times.
- `origin` is the unit's anchor point on the canvas. It stays fixed while the frames move around it, which is how jumps and attacks move the body. Put the canvas so that `origin` falls on the unit's position.
- `action` and `facing` say when the game plays it: the unit's action and which way it faces (`down` front view, `up` back view, `left`, `right`; empty when the action has no facing). `used` is `false` for the ones that are drawn but never played.
- `preview` is the same animation as a GIF, to see what it shows. GIFs store times in 1/100 s and merge repeated frames, so use `frames` for timing.

The actions:

| `action` | When |
|---|---|
| `idle` | Standing still. Usually a single frame, on the sheet but not as an animation |
| `walk` | While it moves |
| `attack` | Its normal attack, up close or from a distance |
| `chant` | While the runes are drawn (Isaac and bosses) |
| `cast` | When the spell is released |
| `knocked_back` | A spell that knocks units down hits it; `get_up` follows |
| `get_up` | Standing up again after the fall |
| `sleep` | A spell puts it to sleep |
| `dance` | Isaac, with the dancing spells |
| `trapped` | A capture trap holds it |
| `captured` | The capture works |
| `die` | Its HP reaches 0. Plays once; half a second later the unit fades out |
| `run`, `victory` | Drawn, never used (`used: false`) |

When a unit has no animation of its own for an action, it keeps its `idle` pose. Right-facing animations are separate drawings, not a mirror made at play time.

```json
{
  "uid": 1,
  "slug": "isaac",
  "name": {
    "en": "Isaac",
    "es": "Isaac"
  },
  "element": null,
  "description": {
    "en": "A 14 year old boy. His face plainly displays his emotions.",
    "es": "Un joven. Su rostro muestra emociones."
  },
  "stats": {
    "hp": [
      300,
      450
    ],
    "mp": [
      30,
      130
    ],
    "attack": [
      80,
      80
    ],
    "defense": [
      25,
      38
    ],
    "magic": [
      30,
      130
    ],
    "magic_defense": [
      87,
      92
    ],
    "speed": [
      21,
      23
    ],
    "accuracy": [
      100,
      100
    ],
    "evasion": [
      0,
      30
    ]
  },
  "growth": "linear",
  "up_cost": 1,
  "squad_size": 1,
  "flying": false,
  "ranged": false,
  "can_capture_crystals": true,
  "attack_interval_ticks": 60,
  "attack_reach_px": 0,
  "radius_px": 8,
  "head_px": 19,
  "speed_px_per_tick": [
    0.65625,
    0.71875
  ],
  "attack_strike_ticks": 8,
  "attack_element": null,
  "projectile_type": null,
  "shot_start_px": null,
  "sight_px": 0,
  "keep_distance_px": 0,
  "terrain_spared": [],
  "attack_used_by": "dance_spells",
  "attack_note": {
    "en": "Never attacks: its Attack is used only by the dance spells. Isaac deals damage with his spells and the creatures he sends.",
    "es": "Nunca ataca: su Ataque solo lo usan los hechizos de baile. Isaac hace daño con sus hechizos y con las criaturas que manda."
  },
  "portrait": {
    "url": "/unidades/isaac/retrato.png",
    "width": 125,
    "height": 125
  },
  "sheet": {
    "url": "/unidades/isaac/hoja.png",
    "width": 1012,
    "height": 156,
    "cells": [
      {
        "x": 6,
        "y": 10,
        "w": 32,
        "h": 32
      },
      "…"
    ]
  },
  "animations": [
    {
      "index": 1,
      "action": "walk",
      "facing": "down",
      "used": true,
      "preview": "/unidades/isaac/01.gif",
      "width": 35,
      "height": 34,
      "origin": {
        "x": 20,
        "y": 23
      },
      "frames": [
        {
          "cell": 4,
          "x": 3,
          "y": 2,
          "ticks": 15,
          "ms": 250
        },
        "…"
      ]
    },
    "…"
  ],
  "battles": [],
  "casts_spells": []
}
```

`battles` lists the story battles the unit takes part in.

To draw frame *i* of an animation: take the rectangle `sheet.cells[frames[i].cell]` and paste it on the canvas at `(frames[i].x, frames[i].y)`. Show it for `frames[i].ticks` / 60 seconds, then go to the next frame and back to the first at the end.

### Stats

Each named unit's `/data/units/<slug>.json` also has its numbers from the game's tables:

- `stats`: `hp`, `mp`, `attack`, `defense`, `magic`, `magic_defense`, `speed`, `accuracy` and `evasion`, each as `[level 1, level 50]`. At level L a stat is `level1 + floor((level50 − level1) × factor / 128)`, where `factor` is the value of the unit's `growth` curve (`early`, `late` or `linear`) for level L in `/data/levels.json`.
- `speed_px_per_tick`: how far it walks per tick at levels 1 and 50; it is `speed / 32`. Flying units use the same speed.
- `flying`, `ranged` (its normal attack is a projectile), `attack_interval_ticks` (time between attacks), `attack_reach_px` (it attacks when the gap between the two units' edges is below this) and `radius_px` (its body is a circle of this radius).
- `head_px`: the height of its head above its point; the HP bar and the status icons go over it.
- `up_cost` (what it takes from the Line Up's capacity) and `squad_size` (how many units one squad brings).
- `attack_used_by`: what its `attack` stat is for. `normal_attack`, its normal attack. The units that never attack (`attack_reach_px` 0) have `dance_spells` (Isaac: only the dance spells use it, hitting with his full attack minus a quarter of the target's defense) or `nothing` (the mages, the story bosses and the Villager: it is never used). `attack_note` explains it in English and Spanish, and says how the unit deals damage. `/data/units.json` has both fields for every unit.
- `element` is the unit's element, and `description` the game's text about it, in English and Spanish.

`/data/levels.json` has, per level: `exp_to_next` (the experience to reach the next one), the factors of the three curves (`growth`, out of 128) and `lineup_up`, how many UP of units the Line Up holds at that level of Isaac's.

## Movement and AI

`/data/ai.json` describes how units move and what they do on their own. It is read from the game's code (`evidence: static`); it was not observed in an emulator.

- `movement`: routes are straight (`route: straight`), with no path finding and up to `route_points_max` points; a unit reaches a point when it is less than `point_reached_distance_px` away. A group sent to a point spreads around it: each unit goes to the point plus its direction from the group's centre × (its radius + `group_spacing_extra_px`). A unit walks `speed / 32` px per tick, without acceleration; obstacles and other units push it out.
- Target: every `retarget_every_ticks` ticks, in the `states_with_target_search` states, it looks for enemies less than `search_px` away and picks the nearest, the one with most HP or the one with least HP (these two prefer targets within its reach).
- `orders`: each order (`order`, with the game's classes in `classes`) sets a decision state (`state`):
  - `wait` with mode 0 (state 0) attacks without chasing whoever comes within its reach + `attack_in_place_extra_px`; with another mode (`state_other_mode`, 1) it chases within its sight radius.
  - `move` follows the route without attacking; `move_auto` attacks on the way without leaving it; `move_attack` chases within its sight radius or follows the route; `move_wait_auto` follows the route, chases and, once stopped, waits.
  - `guard` (states 9 and 10) watches over a point: `guard` says when it walks back (`distance > sight`) and how far (`distance <= sight / 2`).
  - `escape` (11) runs straight away while the distance between centres is under the unit's `keep_distance` (`/data/units.json`).
  - `take_crystal` (8) goes to the nearest mana crystal of another side within `order_list.take_crystal_radius_px` and stays until it is its side's.

  The sight radius is each unit's `sight`, and missions change it.
- `unresolved_states`: states that exist but whose producing order is not known yet.

### Unit orders in battles

Each unit of a battle in `/data/maps.json` has `orders`, its list of orders; when it is empty, the unit gets none at the start. `unit_orders` in `/data/ai.json` describes each order: `state` is the state it leaves the unit in (`null`: unchanged), `target` how it gives the point and `ends` when it ends.

- Points: `x`, `y` is a map point in px; `dx`, `dy` are added to the unit's position when the order starts. The unit walks straight to it.
- `wait`: `ticks` (`null`: forever); `chase: false` is state 0 (attacks without moving), `true` state 1 (chases within its sight).
- `back`: goes back `steps` orders, never before the first one. This is how patrols repeat.
- `target_nearest`, `target_most_hp`, `target_least_hp`: the rule it picks targets by from then on. `set_sight`: its sight radius becomes `sight_px`.
- `change_max_hp`: its max HP becomes `floor(max × percent / 100)`; `set_max_hp`: `hp`. Its HP becomes that same value, never above the max it had.
- `guard` never ends: the list stays there.

How the list runs (`order_list`), one step per tick (`steps_per_tick`):

1. It starts at the first order. The tick an order starts in does not count towards ending it.
2. `ends`: `at_once` ends in the tick it starts; `route`, the first tick after the unit reaches the point (on arrival it is in state `route_end_state`); `ticks`, once `ticks` ticks have been counted after the start tick, on the next one; `crystal`, when the crystal is its side's, or at once when none is that close; `jump` is `back`; `removed`, like `at_once`, and the order also leaves the list.
3. The next order starts on the next tick. With a `wait` of N `ticks`, the next order starts N + 2 ticks after the wait started.
4. `back` counts only the orders still in the list: `removed_when_done` are gone.
5. After the last one ends, the unit keeps the state it is in and gets no more orders.

Example, the first Misty Mush of battle 1: `change_max_hp` 75 (leaves the list), `move` to (344, 153), `wait` 30, `move` to (344, 133), `wait` 30, `back` 4. It patrols between the two points and stays at each one for the wait's 30 ticks plus the ticks the list takes to step; it does not attack while it walks and attacks without moving while it waits.

## Duel

`/data/duel.json` describes the duel mode, where two people play each other. It comes from the game's data and code (`DB/chrSelScnDat.cssd`, `DB/eloRate.elo`); it was not observed in an emulator.

- `presets`: the 8 ready-made teams a player can pick. Each has `level` (the player's level, 40), `element_levels` (the six element levels, which the game clamps to 1–50) and `squads`, three squads (`unit_id` of `/data/units.json`, `name`, `slug` and `level`). The cartridge does not name them: they are numbered 1 to 8.
- `rating`: the rating of Wi-Fi matches, between `min` and `max`. Each of the `rows` has the bound on the difference (`difference`, own rating − the opponent's) and the points a win (`win`), a draw (`draw`) or a loss (`loss`) adds. A negative difference uses the first row whose bound is at least the difference; any other, the last row whose bound is at most it. Then the rating is kept between `min` and `max`. `elo_k` is 50 because all 53 rows match, within one point, the Elo system with K = 50: `K × (result − 1 / (1 + 10^(−difference/400)))`, with result 1, 0.5 or 0.

## Game rules

`/data/rules.json` has the game's limits, read from its code:

- `team`: `squads_max`, the squads that go with Isaac, never more than the map's squad start points; `player_units_max`; `formation_px`, where each unit of a squad starts from its start point (y grows downwards); `capacity_up_by_isaac_level`, the UP the team holds at each of Isaac's levels, from level 1.
- `roster`: `monsters_max`, the monsters that can be kept, and `captures_per_battle_max`.
- `items`: `inventory_max`, `found_level_max` (the highest level of a found item) and `found_per_battle_max`.
- `levels`: `level_max` of Isaac and the monsters, `element_level_max` and `enemy_level_max`.
- `experience`: `per_kill_per_level` (each unit that dies gives that number × its level), `battle_max` and `level_1_to_max`.
- `battle`: `default_time_limit_s`, the limit when the mission sets none.
- `new_game_plus`: the levels enemies gain in each chapter (1 to 5, capped at `levels.enemy_level_max`) and the extra levels of chest items (capped at `items.found_level_max`).
- `checkpoints`: how many map missions and scenes and how many free battles take a checkpoint when they start.

The rules that are not a number:

- Isaac is always in the team and carries no item. Each squad carries one item, which works on every unit in it, and has as many units as its species' `squad_size`.
- UP is only checked when the team is put together, not in battle.
- Only regular enemy units can be captured, not bosses or allies. They join after a win, at their battle level with 0 experience. Repeated species in the list and repeated items in the inventory are allowed; with the list full, the new one replaces one that is not in a squad, or is dropped.
- Every unit of the enemy side or allied unit of the map that dies gives experience, whoever kills it; captured ones give nothing. On a win, Isaac and every squad each get the whole amount; it is not split.
- A defeat or a draw discards everything gained in the battle. Continuing puts the story and the runes back to the checkpoint, taken when a mission, scene or free battle starts from the map; level, monsters and items are kept, and the mission replays from its start.
- Retiring from the pause menu discards the battle and goes to the title, with no continue.
- In New Game+ Isaac keeps his level, experience, element levels, runes, monsters, team and items; the story starts again with harder battles.
- The results screen, only after a win, shows in order: experience and element levels, items found, captured monsters and new spells. There are no ranks or grades.
- There is no money or shop, and no maximum of units on the field or of active spells. Isaac can cast as soon as the previous cast animation ends; MP is the limit.

## Items

`/data/items.json` has the 8 items. Each squad carries one, applied to every unit in it when the battle starts; Isaac carries none.

`icon` is the 16 × 16 px icon the menus show. It depends on the stat the item raises, so several items share one. Battles draw no item icon: chests always look the same.

`pictures` are each item's icons at levels 1 to 6, made with AI for this site: they are not in the game. `sheet` is the 120 × 160 px sheet and `by_level` gives each level's 20 × 20 px cell (`x`, `y`, `w`, `h`). From level 2 the icon has an aura of another color, and from level 4, sparkles. To keep them sharp, scale them by whole numbers with `image-rendering: pixelated`.

- `stat`: the stat it changes (`hp`, `speed`, `attack`, `interval`, `accuracy`, `evasion`, `defense` or `magic_defense`).
- `bonus_by_level`: the value *v* at levels 1 to 10, out of 128. Chests only give levels 1 to `items.found_level_max` of `/data/rules.json`.
- `effect`, how *v* applies to the unit's stat *x*:
  - `multiply`: `x + floor(x × v / 128)`. With `hp`, the unit also starts with its HP full.
  - `reduce`: `x − floor(x × v / 128)`, at least 1.
  - `double`: `x × 2`, at any level.
  - `gap_to_100`: `x + floor((100 − x) × v / 128)`.
- `in_battle` (Wind Shield, Hunter Eye, Angel Coat and Dragon Scale; `null` for the rest): what the item changes in a battle, in `en` and `es`: `what` (what the stat is), `examples` (worked examples with level-1 units), `good`, `bad`, `caution`, `level` (when the item's level does not matter) and `anchor` (the section of `/logic/combat/` with the rule). `examples` has the same examples as numbers: `accuracy`, `evasion` and `hit_percent` as [without the item, with it].
- `chests`: the story chests that can give the item: `battle` (the same number as in `/data/maps.json`), `map`, the position `x`, `y` in map pixels, `chance` (0 to 1), `levels`, the chance of each level, and `new_game_plus_levels`, the same chances in New Game+.

Level 6 is almost never found in the story: chests give up to level 5, except one Demon Horn chest on map 038 (1.1 %). In New Game+ the game adds `new_game_plus.chest_level_bonus` (2) to the chest's level, capped at `items.found_level_max` (6), so chests of levels 4 and 5 give level 6.

How accuracy and evasion decide a hit (stats at a level in `/data/units/<slug>.json`; Hunter Eye doubles the attacker's accuracy, Angel Coat the defender's evasion, at any item level):

- Melee hits when a random number from 0 to 100, plus accuracy, minus evasion, reaches 100: a chance of `clamp(accuracy − evasion + 1, 0, 101) / 101`.
- A shot hits when a random number from 0 to 99 is under `(accuracy − evasion) & 0xff`: the game keeps one byte of the difference. So it always hits when evasion is higher than accuracy, and from a difference of 256 it starts again from 0 (a shooter with Hunter Eye can hit less). This comes from reading the game's code (ov1 0x2130f40); it was not tested in an emulator.

`godot` says how each item will work in our Godot port (approved design, version 1), apart from the original's fields: `same_as_original`, `effect` and `bonus_by_level` as above (plus `add`: `x + bonus_by_level`, in points), `line_up_shows_effect` (the Line Up screen shows the effect of all 8), the text in `en` and `es` (`same`, the same as `same_as_original`; `change`, what changes and why, or "Same as the original"; `examples`; `notes`) and, for Hunter Eye and Angel Coat, `examples` as numbers: `hit_percent` without the item, `by_level` with it at levels 1, 3, 5 and 6, and `original_hit_percent` with the original's item. In Godot:
- Hunter Eye: `multiply`, accuracy × (1 + v/128), like Demon Horn.
- Angel Coat: `add`, 4 points of evasion a level (`v × 40 / 128`, rounded).
- A shot hits with a chance of `clamp(accuracy − evasion, 0, 100) / 100`: with higher evasion, it always misses.
- The other items, the levels (1 to 6, +2 in New Game+) and who carries items stay as in the original.

When the battle loads, the game draws each chest's item and level. The item goes to the player's unit that opens the chest and is received on a win. In New Game+ the level rises by 2, with the same cap.

`/data/chest.json` is the chest as a sprite:

- `closed` is the closed picture the maps draw, placed at the chest's point plus (`dx`, `dy`).
- `sheet` holds `cells` equal cells of `cell.width` × `cell.height` px in `columns` columns. The chest's point is at `cell.origin` in each cell.
- `animations`: `idle0` (closed), `open` (opening), `loop` (open with the reward), `suka` (smoke) and `emptybox` (empty). Each `frames` is a list of `cell` and `ticks` of 1/60 s. `loop` says whether it repeats, and `preview` is a GIF or PNG to look at.
- `flow`: with a reward the game goes from `idle0` to `open` and stays in `loop`; without one, from `idle0` to `emptybox`. `suka` has `used_by_game` `false`: the game never picks it.

## Spells and runes

### Runes

`/data/runes.json` has the 18 runes: `id` (1 to 18), `element`, `level` (1 to 3), its picture, `spell` (the spell it casts alone) and `learned` (the missions where it is learned). Isaac casts a spell by drawing 1 to 3 runes in order while holding L (R with the left-handed option); releasing the button ends the chant. Each rune is a single stroke: lifting the stylus closes it.

`templates` are the strokes the game accepts for each rune: points on a 0–63 grid (y down), in drawing order. `from_dot` marks the ones that start at the dot of the rune's picture; the rest are the same drawing from another start or in the other direction.

To recognize a stroke:

1. Keep one point per frame while the screen is touched. After the first, keep only the points that move at least 1 px in x from the last one kept: the game ignores movement in y. A stroke of fewer than 4 points cancels the chant.
2. Take a square centred on the stroke, with side `max(width, height)`, so the proportions are kept. Simplify it: keep both ends and, piece by piece, the point farthest from the chord if it is `max(1, 2 × side / 64)` px away or more, up to 40 points. Map it to the 0–63 grid with `(p − origin) × 64 / (side + 1)` and drop repeated consecutive points.
3. For each point k ≥ 1 compute `angle[k]`, the direction of the segment that reaches it, and `ratio[k]`, the share of the total length that segment is. Point 0 takes the direction of the first segment.
4. Compare the stroke (I, with nI points) with each template (P, with nP points) by walking both lines together along their length:

```
i = j = 1; remP = ratioP[1]; remI = ratioI[1]; score = 0
repeat nP + nI − 3 times:
  A = 1 − |angleP[j] − angleI[i]| / 180°        # angle difference wrapped to 0–180°
  if remP <= remI:
    remI −= remP
    q = I[i] if remI < ratioI[i] / 2 else I[i − 1]
    score += remP × A × (1 − manhattan(q, P[j]) / 128)
    j += 1; remP = ratioP[j]
  else:
    remP −= remI
    q = P[j] if remP < ratioP[j] / 2 else P[j − 1]
    score += remI × A × (1 − manhattan(I[i], q) / 128)
    i += 1; remI = ratioI[i]
```

5. The template with the highest score (0 to 1) wins; on a tie, the first one. If it reaches `accept` (0.70), the stroke is its rune. Rotation is not forgiven: about 30° off is already at the limit.

A recognized rune must also be learned, the runes drawn so far must begin some spell, the runes-per-spell limit cannot be passed (`runes_per_spell`: the missions that allow 2 and 3) and there must be enough MP for that rune. If anything fails, the chant is cancelled.

A spell's **quality** is the average score of its runes. If it reaches `great` (0.85), 0.5 is added: the factor goes from 0.70–0.85 to 1.35–1.50.

`/logic/runes/practice/` lets you practise drawing with this recognizer, ported to TypeScript with the same integer arithmetic: it gives the rune, the score, whether it passes `accept` or reaches `great`, the factor and the three closest templates. `/data/rune-strokes.json` holds 640 test strokes with what the game's recognizer gives for each; a port must give the same. Each one is `samples` (one point per frame at 60 Hz, in pixels of the 256 × 192 touch screen) and `kept` (how many the capture keeps); with fewer than 4, `cancelled` is `true`; else `top` is the three closest templates as [rune, template, score], the template numbered as in `templates` of `/data/runes.json` (runes in order) and the score out of 4096: accepted from 2868, great from 3482.

### Spells

`/data/spells.json` has the 396 spells:

- `name`: the cartridge's name in English and Spanish. The Spanish ones are often abbreviated (none is longer than 12 characters, for example «Torbell. f.»); the site keeps them as they are, without expanding or translating them, and shows the English one beside them.
- `runes`: its runes, in order. `element`: its element, which is its last rune's: 294 of the 396 mix runes of different elements.
- `kind`: the family (`cloud`, `wall`, `bullet`, `blast`, `falling_objects`, `aura`, `zone`, `unit_effect`, `object`, `dragon`). `variant`: the exact behaviour within the family.
- `mp_by_rank`: the MP cost with the element at rank 0 to 4. Rank is `floor((element level − 1) / 10)`. The cost is the sum of its runes: `mp_per_rune[position][rank of that rune's element][rune level − 1]` from `/data/runes.json`. Each rune is paid as it is drawn.
- `targeting`: how it is aimed (`circle`: centre and radius; `line`: start and end; `fixed_circle` and `point`: a point; `direction`: it leaves Isaac towards where the stylus is released; `unit`: a unit; `instant`: no aiming). `targets`: `enemies`, `allies` or `all`; `null` for the three that touch no unit (Transport and Flight move whoever casts them, Restore only changes the ground). `numbers.cast_range_px` is the farthest distance from Isaac.
- `effect_group`: only on `unit_effect` (`null` for the other kinds), what it does to units. `heal` restores HP or removes statuses: the `heal` flag, a status of group `cure` or `regeneration`, or the variants that move HP to the caster's side (`life_thread`, `life_shine`, `light_transfer`). `boost` puts a status on allies and `weaken` on enemies: every status the game casts on allies helps them and every one it casts on enemies hinders them. `other` is the rest: no status and no healing, or a status on both sides. It is the spells page's filter, not a field of the cartridge.
- `power`: with `power_scaled`, the power at cast is `floor(floor((power + caster's magic) × quality factor) × (rank + 4) / 4)`; without it, `power` is used as it is. With the `heal` flag, it heals.
- `status`: the status it can cause: its record (`id`), `group`, `chance` (at rank 0 and factor 1; multiply by the factor, by `(rank + 4) / 4` and by the element factor in `elements.status_chance` of `/data/combat.json`), `duration_ticks` and `power`.
- `terrain`: how it changes the ground (`heat` turns it into magma, `harden` hardens magma, `freeze` freezes water, `dry` dries swamp into sand, `swamp` turns quicksand into swamp, `restore` restores it).
- `terrain_overlay`: what the spell draws over the map while its zone lives, `lasts_ticks` (1200, 20 s). The zone is a circle: `area.radius_px`, or one circle of `area.per_object_radius_px` for each falling object. The 8 × 8 px cells whose centre is inside the circle are painted. `paints` is the layer it puts and on which surface (`on`); `image` is its 32 tiles animated, each frame lasting `frame_ticks` (`null`: still). `clears` are the layers it removes inside its circle while it lives, even when another spell put them, before or after. That is how opposites cancel: Storm removes Fire Rain's magma, Fire Rain removes Ice Maiden's ice, and `restore` removes everything. It appears at once and, when the zone ends, vanishes with no animation: there is no entry or exit animation, and the `frame_ticks` frames loop the whole layer. It is only the picture: the terrain statuses give the effect on units. `on_any_map` says whether that surface exists on any map. `dry` paints `mf04` on swamp, but no map has swamp (`on_any_map: false`): in the game it only removes the swamp `swamp` leaves.
- `flags`: `piercing`, `homing`, `rapid` (bursts), `spread` (fan), `lock_on` (one shot per target), `flying_only` and `walking_only` limit who it affects, `knockback`, `knockdown`.
- `numbers`: its family's numbers, in ticks of 1/60 s and px.

### Spell families

`/data/spell-families.json` groups the 396 spells by the game code that runs them: the effect class and subkind in their data. The generic spell on units (`direct`) is split by role (`heal`, `damage` or `status-only`) and status group, and Prank, Joke and Trick go together as `tub`. Each family has `page` (its page on the site, `/logic/families/<family>/`, or `/es/logic/families/<family>/` in Spanish: its types and its spells in an element × variant matrix (rows such as Level I, Rapid or King, and «Others» for the spells that fit none) when most of them fit, or as cards, each with its animation and a panel with all its numbers; `/logic/families/` is the catalog of all of them), `spells` (the `slug`s of `/data/spells.json`), `same` (the fields equal in all, with their value) and `differs` (the ones that change). The fields are those of `/data/spells.json`: `numbers.<name>` and `status.<field>` are flattened, and `runes` is how many it has. `power` is only compared within a family, because each one uses it differently. `note` (EN/ES), when present, says when one pays off over another, only where the research supports it; it comes from reading the code, not from playing. Families of one spell have empty `same` and `differs`.

`total_effect` gives, per spell, what one unit receives over the whole spell, at the fixed reference in `total_effect_reference`: the caster has magic 30 (Isaac at level 1), the element at rank 0 (level 1–10) and a perfect stroke (quality 1, factor 1.5); the receiver has defense and magic defense 0, a neutral element and no weakness or shield; no random factor. `kind` is `damage`, `heal`, `status` or `other`; `hp` is the damage or healing in HP when there is one (else `null`); `text` (EN/ES) says how it adds up or what it does. The rules: the damage per hit is `floor(floor((power + 30) × 1.5) × (0 + 4) / 4)`, except clouds, which hit with their raw `power` `numbers.hits` times; burn and poison add `status.power` every 60 ticks, `floor((status.duration_ticks − 1) / 60)` times, if the status lands (the unit's status pass counts the life down before each tick), and regeneration heals once more because its first one is at once; the status chance is `status.chance` × 1.5, at most 1. `up_to` marks a maximum: every shot or object lands (bursts and falling objects), or the unit stays where the spell reaches it. Normal poison only ticks while the unit moves or attacks. A dragon flies through a unit, goes on 192 px and comes back, so it hits a unit that stands still on every pass: `numbers.passes_max` at most, `passes_typical` in most spots, `passes_min` in the worst. An armor hits an enemy that stays within its pulse `numbers.touches_max` times with the power and `numbers.aura_ticks_max` times with `status.power`. A dance is `numbers.hits_max` hits of `numbers.hit_attack` (Isaac's attack at level 1, against defense 0). A whirlwind hits once per contact and only steers to units ahead, so it hits a unit again only if that unit comes back into its path. `confirmed: false` marks what the code read so far does not settle; since #256 no spell has it.

Each family has `description` (EN/ES): a sentence with no numbers on what it does, built from its data with a template per `kind` or `variant` and the effects of at least half its spells («some also…» adds up to three of the rest). What each spell does, worked out from its data and never written by hand, comes in two levels: `tags`, specific effects (`damage`, `heal`, `protect`, `empower`, `haste`, `weaken`, `burn`, `poison`, `freeze`, `sleep`, `charm`, `pacify`, `immobilize`, `knock`, `capture`, `summon`, `movement`, `terrain`), and `types`, their eight general groups (`attack`, `heal`, `buff`, `debuff`, `control`, `summon`, `movement`, `terrain`). The `tags` and `types` at the top of the file give the name (EN/ES), the rule and, for each tag, its `type`. The rules: `damage` if its total effect is damage (or `switch_life`); `heal` with `heal` in `flags`, a total effect that heals, a `cure` or `regeneration` status, or `light_transfer`; the status it applies, with every branch of a `random` status, gives the rest: a stat change up protects (defense, magic defense, evasion), strengthens (attack, magic, accuracy) or speeds up (speed, or less `interval`), and down slows (speed; all of it or a bubble roots) or weakens (anything else, weakness, virus, recoil, forget); burn and poison are `burn` and `poison`; sleep, freeze and charm, their own; no attacking and terror, `pacify`; berserk strengthens an ally and, cast on an enemy, counts as `charm`. `knockdown` and `knockback` are `knock`; capture traps, `capture`; `kind` `wall`, `object` or `dragon`, `summon`; `variant` `transport`, `gather` or `self_cannon`, `movement`; `terrain` or a terrain status, `terrain`. Holy Pulse applies no status of its own: it strengthens the allies and weakens the enemies. In each family, `tags` and `types` list its spells', the most common first, with `spells` = how many have it; `spell_types` gives each spell's.

### Best spells

`/data/best-spells.json` says which spells pay off for the player, worked out from the data (`evidence: static`), not from playing. Each spell's effect is `hp_expected`: the total effect of `/data/spell-families.json` at the same reference, with burn and poison counted by their chance (for clouds, the chance it lands at least once over their hits). `spells` gives per spell its `mp`, `chapter` (the chapter in which Isaac has learned all its runes and can chain that many), `stroke`, `reach_px` (the larger of the cast range and the shot range), `area_px` (its radius, if any) and `badges`: `per_mp`, `jump`, `easy` and `not_worth`.

- `rankings.per_mp`: effect per MP at least twice its family's median (`vs_family`), or effect at least twice the median of the other spells of its family with the same MP (`vs_same_mp`).
- `rankings.jumps`: the next spell by MP of the same family and element, with effect ×2 or more and `effect_ratio / mp_ratio` ≥ 1.5.
- `rankings.not_worth`: another spell of its family and element (`better`) costs less MP and is as good in everything; when `gains_only` is not empty, the dearer one only gains in those numbers.
- `rankings.easy`: every spell, from the easiest stroke to the hardest.
- By situation, only spells that deal damage: `grouped` (with an area, by the effect on each unit inside), `scattered` (no area that also hits allies; by `mp_per_kill` = MP × casts to take the weak enemy's HP, then by stroke, then by reach) and `boss` (by the effect on one unit). The weak enemy (`reference.weak_enemy`) has the median HP of the unnamed units in the battles of chapters 1 and 2, at their level.

The stroke score (`method.stroke`) is an estimate of how hard a rune is to draw by hand, not a value of the game. For each shape the rune accepts (`templates` of `/data/runes.json`, 0..63 grid = one rune width), points closer than `min_step` to the previous one are dropped and the score adds: the length in widths, `w_sharp` per corner sharper than `sharp_deg` degrees, `w_turn` per full turn (the sum of turning angles / 360°) and `w_against` per width the stroke goes up or to the left. A rune takes its easiest shape (`runes[].template`, `runes[].points`) and a spell adds its runes plus `w_extra_rune` per rune after the first. The easiest quarter of the spells gets the `easy` badge.

Spells with rules of their own, by `variant`:

| `variant` | Rule |
|---|---|
| `bomb_man` | Puts a bomb on each target unit, of both sides. After 180 ticks each living bomb hits every unit within 15 px with the power (fire), once per unit, its carrier included |
| `dance` | Isaac walks to the target at 2 px/tick and takes no damage (gives up after 180 ticks). Within 12 px he dances 300 ticks: hits on ticks 9, 19 and 30 of each 43-tick cycle, with his whole attack minus a quarter of the defense. It ends if the target dies or gets more than 34 px away |
| `light_transfer` | Dark damage; Isaac recovers twice the damage dealt, up to 32,767 |
| `life_thread` / `life_shine` | Heals what the target lacks, taken in equal shares from the other allies / the enemies, never below 1 HP |
| `holy_pulse` | 600 ticks: the caster's side gains 25/128 attack and the other side loses 25/128 defense inside the circle. Casting it again restarts the time |
| `transport` | The caster jumps to the point if it fits (otherwise it fails); no receiver, no impact. Departure flash on casting, arrival flash at 20 ticks and position change at 30; until then it can be hit where it stood. Flight is the same jump with a longer range (250 px instead of 100) |
| `self_cannon` | Isaac flies at 2 px/tick for up to 240 ticks; within 12 px, 620 wind magic damage to the target (not scaled) and 255 to Isaac |
| `free_lift` | Lifts the target (not Isaac or bosses) for 64 ticks; when it drops, the power (wind) and a strong burn to enemies within 40 px |
| `switch_life` | Swaps Isaac's current HP and the target's, each capped at the receiver's max HP |
| `gather` | Brings each ally next to Isaac, one every 6 ticks, at a random angle, both radii plus 0–31 px away |
| `death_gamble` | Picks a random living unit of either side after 282 ticks; it takes 2 × the power (fire) and others within 50 px the power |
| `prank`, `trick` | One tub per target; after 20 ticks it takes exactly 1 HP, which counts as damage |
| `mine` | Each mine lasts 920 ticks and goes off when a walking enemy comes within 14 px: the power (earth) and a knock-down to enemies within 14 px. A new mine disarms the side's earlier ones |
| `capture_trap` | Catches the first regular enemy within `trigger_radius_px`. Under `capture_hp_below` the capture always succeeds; `intended_chance_*` are not used. A new trap cancels the side's earlier one |
| `elemental_wall` | Posts have no HP; each post hurts a unit touching it at most once every 120 ticks |

## Spell scenes

`/data/spell-scenes.json` (also `.md`) maps every spell to graphics, sounds and actual bosses, with 90 unit appearances and 141 statuses. Each spell page offers caster, receiver, four facings, distance, stroke quality, selected outcome, replay and sound. Receiver affiliation follows `targets`; example outcomes/status application are user choices, not battle rolls. Transport and Flight have no receiver or outcome: their scene moves the caster to the destination.

- `assets[id]`: PNG, CAN/NCER/NANR and exact palette; `cells` holds [x,y,w,h] sheet rectangles and `origins` their anchor-relative positions. Empty frames still consume their time.
- `sequences[].frames`: [cell,ticks,dx,dy,rotation degrees,scaleX,scaleY]. Single-frame animations are retained. `mode`: 1 once,2 loop,3 ping-pong once,4 ping-pong loop; `loop_start` is the loop's starting frame. `ticks` and `seconds` retain original durations.
- `units[].actions[action][facing]`: sequence, `own` and `fallback`. Monsters without chant/cast actions use idle. `launch_px` positions the shot; `boss_spells` identifies actual battle use.
- `spells[].graphics`: role, atlas, sequences, palette, evidence and confidence. `times_seconds` converts spell time fields; distances remain px. Projectile travel is distance/speed, rounded up to a tick.
- `sounds`: audio, anchor (chant,stroke,rune,cast,release,shot,hit,status,death,vanish,capture,capture_close), offset seconds/ticks, condition and confidence. `timing_confidence` separates sound dispatch from example phase placement. Rune/stroke repeat per rune; hit per shot. Quality <0.85 uses KAKUNIN/INVOKE; ≥0.85 uses MENU_MAGIC/INVOKE2. Sound requires interaction.
- `statuses[].overlay`, `popup`, `sounds` and `duration_seconds` describe each status. `/data/statuses.json` also includes this as `visual`.
- Knockdown requires flag 0x800: 40 ticks grounded and 10 getting up. Push requires 0x400 and decelerates over 30 ticks. Defeat plays die, waits 30 ticks, uses puff 272 for monsters, then removes after 80. Resolved capture uses `capture_timing`: success closes at tick 2 and removes the unit at tick 33; failure closes/releases at tick 60. Book sequences 199/200 determine the end, independently of armed trap lifetime.

Walls (class 1) create ground structures, without a receiver or hit outcome. `structure_timing` preserves the 5 ticks between posts; `targeting` distinguishes line and circle. Elemental statuses apply on contact, not placement. The viewer allows adjusting line length.

Projectile orientation is in `graphics[].rotation`: the original artwork points upward and the ROM adds 90° to its travel angle. Trails inherit that angle; dark/light spin 16.875°/tick. The preview fixes the random initial phase to 0. The manifest has a `revision` to prevent browser reuse of earlier exports.

Spatial composition is illustrative, not a complete battle simulation. `preview` declares drawing speed (0.8 s/rune) and conventions; `confidence`/`evidence` distinguish decoded behavior from probable assignments. Follow-up `extraccion/investigacion/visuals/SCENE_DISPATCH.md` confirms chant/drawing sound dispatch, byte-7 status icons and Fallthing sound tables in code. Zero entries skip that sound. Other families have independent calls; `fall` and `object_hit` repeat per object, using a 20-tick descent as a preview convention.

`visual_behavior` distinguishes shared feedback (Holy Pulse: icon 11; Life Thread/Life Shine: icon 7), actor motion (Self Cannon: `unit_motion`, 2 px/tick, 12 px collision gap) and conditional statuses (Betrayal/Mutiny/Uprising: `choices`). `dance` (the six dances, 361–366) marks Isaac as the dancer: he walks to the receiver at `approach_px_per_tick` (2), dances beside it for `numbers.duration_ticks` (300; in the spell's record, as in `/data/spells.json`) once the gap is `start_gap_px` (12) or less, and hits on `hit_ticks` (9, 19, 30) of each `cycle_ticks` (43) cycle: 21 hits. The receiver does not dance. The element aura starts where Isaac stands (`aura_origin`); `aura_pending` says what is not decoded yet. `caster_teleport` (Transport and Flight) marks the caster's jump: departure flash on casting, arrival flash at `arrival_effect_ticks` (20) and position change at `move_ticks` (30), with no receiver or impact. Their probabilities are conditional on success; the viewer selects a branch without simulating a roll. `own_graphic_confirmed` describes dispatch in the original report, not whether shared feedback exists.

Blast/dragon composition and knockdown still require in-game verification. Phase synchronization, Missile's absolute phase placement, life donors and Self Cannon's self-damage are not fully reconstructed. Follow-up `extraccion/investigacion/visuals/SCENE_RENDERING.md` adds `dragon_assembly` (14 indexed parts, emergence 23+index and scales), `explosion_assembly` (main, ring and inward charging particles) and `lock_timing` (ticks 8/16/24). Dragon routing and preliminary Explosion positions remain illustrative. How each effect ends is in `extraccion/investigacion/visuals/SCENE_ENDINGS.md`: `rain_assembly` (the 6 spells with `MagicEffectRain`) gives 20 clouds, 10 drops with their alpha cycle (`drop_alpha`) and 10 splashes, and `removed_after_hit_ticks` (63) is when the owner deletes itself and takes everything with it. For the other blast and falling-object families, when the effect is removed is not decoded; the scene removes it 120 ticks after the impact. The 27 terrain spells carry `terrain_scene`:
- `map` and `origin` say on which game map and spot the scene stands, so the terrain the spell acts on shows;
- `zone` says when the zone is born: on the impact (`hit`), at each falling object (`object_hits`) or when the area appears (`release`);
- `radius_px` and `lasts_ticks` (1200) give its circle and its duration;
- `prior`, for the spells that only remove, is the earlier overlay the scene paints so you can see what they remove.

`terrain` holds each material's rule, the layer the game picks, the neighbour tile tables (`tiles`), each layer's sheet with its ticks per frame and the NHIT grid of each map used (details in `extraccion/investigacion/terrain_morph/STATIC_REVIEW.md`). `sounds[].duration_ticks` stops chanting at cast and drawing at each stroke end; `*-sustain` clips use ROM samples/instruments with a 3 s preview hold. Resume continues held notes with their current offset and remaining duration. None of these findings is emulator-validated.

## Aiming a spell

`/data/aiming.json` says what the battle shows between the end of the chant and the cast, by the spell's `targeting` (see "Spells and runes"). It is read from the code (`evidence: static`), not measured in an emulator; `confidence` says how sure each point is. Positions are px of the bottom screen (256 × 192), times are ticks of 1/60 s and angles are degrees with 0 = right and 90 = down (y grows downward).

- **Modes** (`modes`). Each `targeting` has its icon (`icon`) and its way to confirm (`confirm`). `arrow` says whether it uses the arrow; `range_graphics_3d`, whether it draws something on the ground (circles or shade). `direction` draws nothing on the ground: only the arrow and the icon.
- **Arrow** (`arrow`, `direction` only: Fireball and the other shots). The picture `image` (32 × 56) points up. Its point `pivot_px` (15, 36), the centre of the ring, goes on Isaac's point (`anchor: caster_point`, his feet) and the picture turns around that point. The tip is `tip_px` (36) px ahead of Isaac and the handle `tail_px` (15) px behind, so the arrow comes out from under him. Drawing angle = `atan2(dy, dx)` in degrees + `rotation_offset_deg` (90), where (dx, dy) goes from Isaac's point to the stylus, turning clockwise on screen: the arrow always points at the stylus. The turn is free (`turn_steps` 4096 per turn), with no snapping to 4 or 8 directions. It does not stretch or scale with distance (`scale` 1) and is opaque (`alpha` 1). It is drawn under Isaac (`layer: under_caster`). Colours: the picture's own, the same for every element.
- **When** (`direction_flow`). The arrow appears as soon as aiming starts, pointing up (`rest.end_offset_px` (0, −10): −90°). Once the stylus touches the screen (anywhere), every tick the arrow points at the stylus and Isaac turns toward it in 4 directions (right, down, left, up, split at 45°). Lifting the stylus casts the spell from Isaac toward the stylus's last point, and the arrow and the icon disappear. There is no distance limit (`range_limit: null`): the shot travels its `shot_range_px` from `/data/spells.json`. Before touching, pressing the chant button (L, or R with the left-handed option) cancels: `SE_CANCEL` (effect 4) plays and nothing is cast; once touched, it can no longer be cancelled.
- **Icon** (`icon`). While aiming, the mode's icon (32 × 32, point at its centre) stays put on the screen at `at_px`: (20, 172), bottom left, or (236, 172) with the left-handed option, above the field. If the arrow points into that corner (between the angles of `move_when_arrow_deg`: 90–180 right-handed, 0–90 left-handed) it jumps to `moved_at_px` (236, 20 or 20, 20) so the hand does not cover it.
- **Circle** (`circle_flow`, `targeting: circle`). Outside the reach (`cast_range_px` from Isaac) the field is shaded with `shade_outside_range` (#525252, alpha 20/31). A touch outside the reach does not count; one inside sets the centre. Dragging picks the radius, from `radius.min_px` (5) up to the spell's `radius_px`; releasing casts with that centre and radius. The area is coloured by `targets` (`area_colour_by_targets`: red enemies, blue allies, yellow all; `area_colour_other` for the spells it lists) and with `area_colour_at_max_radius` once it reaches the largest radius. Shape (`area`): a 24-sided disc on the chosen centre, of the dragged radius, alpha 24/31. While the radius is below the largest it has four small triangles (`handles`) 4 px outside the edge, 5 px long with an 8 px base, pointing out and turned so that one points at the stylus; at the largest radius they go away. The shade outside the reach only shows before the touch.
- **Instant** (`instant_flow`). Any touch casts the spell; the chant button cancels.
- **Line** (`line_flow`, walls such as Fire Wall). The first touch has to land strictly inside the reach; it sets the start. A touch outside does nothing. From then on the end follows the stylus, at most the spell's `max_length_px` long. If the stylus leaves the reach, the end stops where the line from the start to the stylus crosses the reach's edge: the whole line stays inside. Isaac turns toward the stylus only before the start is set. Releasing casts from the start to the end (the same call as the arrow). It draws (`graphics`, colour #e6b552, alpha 28/31) an 8 px radius disc on each end and an 8 px wide band between them, sticking out 4 px past each end. The shade outside the reach always shows.
- **Point** (`point_flow`) and **fixed circle** (`fixed_circle_flow`) are the same code. The first touch has to land strictly inside the reach. Then the point follows the stylus and, if the stylus leaves the reach, stays on the edge in the stylus's direction. Isaac turns toward the stylus. Releasing casts at the point. `point` draws the reticle (`reticle`, 16 × 16 px at scale 1.5: 24 × 24 on screen, centred on the point, above the field) and no area. `fixed_circle` draws at the point a 24-sided disc of the spell's radius (`radius_px`, `trigger_radius_px` or `ring_radius_px` by class), always #ffa5a5 alpha 24/31 and without triangles, because the code draws it at the largest radius. It would only show the reticle with radius 0, which no spell has. The cast call also gets a fixed 20 px radius (`cast_radius_arg_px`); what it does with it is unknown. The shade outside the reach always shows.
- **Unit** (`unit_flow`). Any touch starts. A cursor follows the stylus; if the stylus leaves the reach, the cursor stays on the edge. Isaac turns toward the stylus. Every tick the nearest unit to the cursor is picked among those `targets` allows whose touch box holds the cursor. Box: in x, the unit's x ± (`touch_box_by_unit[slug][0]` + 4 px); in y, from the unit's y − (`[1]` + 4 px) to its y + (the unit's `radius_px` + 4 px). It draws a three-line marker (`marker.lines_px`, px from its point) in red #ff5252 on the picked unit, also for ally spells, or in yellow #ffff52 on the cursor when there is none. Releasing casts on that unit. With no unit it goes back to waiting for a touch, and the chant button cancels again. The shade outside the reach always shows.
- `cross` (a cross) is loaded for every mode but none shows it. `numbers` are digits from the same file that aiming does not use: they are the damage numbers' digits (see "Damage numbers").

## Status effects

`/data/statuses.json` has the 141 status records; a spell's `status.id` points to one. `duration_ticks` is how long it lasts (0: it acts once) and `caused_by`, the spells that cause it. The strength of the ones that hurt or heal is the spell's `status.power`. `key` says what it does:

| `key` | Effect |
|---|---|
| `stats` | Applies `changes` while it lasts |
| `virus` | `changes`; units within `spread_px` of a first victim catch it for the time it has left |
| `berserk` | `changes`; attacks its target and ignores orders. Cancels `charm` |
| `burn`, `strong_burn` | Fire damage equal to the strength every `every_ticks`, the first after `every_ticks`. It does not work in fords; water damage puts it out; it removes `freeze` and `bubble`. `strong_burn` replaces `burn` |
| `poison` | Dark damage every `every_ticks`, only while the unit moves or attacks |
| `vile_poison` | Dark damage every `every_ticks` |
| `regeneration` | Heals the strength every `every_ticks`, the first at once |
| `pulse` | `element` magic damage equal to the strength at once and after `every_ticks` |
| `wall_contact` | On touching a post: `element` damage (`heal`: healing) equal to the wall's strength, at most once every 120 ticks per post |
| `freeze`, `long_freeze` | Cannot attack or move; fire damage ends it |
| `sleep`, `deep_sleep` | Cannot attack or move; any damage wakes it |
| `bubble` | Cannot attack or move until it is hit twice |
| `charm` | Drops its orders and fights its own side. Cancels `berserk` |
| `terror` | Runs away from the unit that threatens it |
| `forget` | Stands still |
| `no_attack`, `no_move`, `no_damage` | Cannot attack / cannot move / takes no damage |
| `invincible` | Takes no damage, cannot attack or move, and cannot be pushed |
| `hidden` | Fades out over 127 ticks |
| `recoil` | Each hit it lands costs it `share` × the damage it deals |
| `weakness`, `halving` | ×2 / ×½ `element` magic damage; one cancels the other |
| `cure` | Removes `removes` (`all`: every status and stat change; `stats`: stat changes, curses and auras) and applies `changes` if it has them |
| `terrain` | Marks the ground with `change` |
| `random` | Applies one of `options` by its `chance` |
| `none` | No effect |

Each item of `changes` has a `stat` (`attack`, `defense`, `speed`, `magic`, `magic_defense`, `interval` = time between attacks, `accuracy`, `evasion`) and a `mode`: `percent` → `stat × (1 + amount)`; `flat` → `stat + amount`; `gap` → `stat + (100 − stat) × amount`.

- Statuses do not stack: applying one again restarts its time. Stat changes, auras and curses do stack, and they multiply.
- Every frame stats are rebuilt from the unit's own, applying its statuses from newest to oldest, then clamped: none below 0; attack, defense, speed, magic and `interval` at most twice the unit's own (`interval` at least 1); magic defense at most 99; accuracy and evasion at most 150 over the unit's own.
- Isaac and bosses are immune to `berserk`, `charm`, `terror` and `forget`.

## Combat

Battle logic runs at 60 ticks per second. `/data/combat.json` has the constants from the code; each unit's own are in `/data/units/<slug>.json`.

**Normal attack.** `gap` = distance between centres − both units' `radius_px`.
- It starts a swing at `gap < attack_reach_px` and keeps it while `gap < attack_reach_px + attack.reach_pad_px`. With `attack_reach_px` 0 it never attacks; when chasing it stops at `attack.stop_gap_px`.
- Rhythm: wait `attack_interval_ticks`, play the `attack` animation; the hit or the shot comes out `attack_strike_ticks` after it starts; when the animation ends, wait again.
- Melee hit: `random(0..100) + accuracy − evasion ≥ 100`. Ranged: `random(0..99) < (accuracy − evasion) mod 256` (a negative difference always hits).
- Damage: `d = floor(attack / 2) − floor(defense / 4)`; if `d ≤ 0`, nothing. With an `attack_element`: ×½ against the same element, ×1.5 against the opposite one (`opposite`). Shots do ×1.5 to flying units. Finally `floor(d × (128 + r) / 128)`, with `r` a random integer from −10 to 10.

**Shots** (the unit's `projectile_type`, `projectiles` in `/data/combat.json`): they leave from `shot_start_px` for the facing, straight towards where the target's centre was, at `speed_px_per_tick`, without following it. They travel at most `attack_reach_px + reach_pad_px`. They hit the nearest opposing unit within `shot_hit_radius_px` + its radius and do not go through. High obstacles stop them; water and pits do not. `on_hit`: `stick` stays `stick_ticks` on the target, `vanish` disappears. On a miss they bounce back and fade in `shot_fade_ticks`.

**Sight.** Every `search_every_ticks` a unit looks for enemies within `max(sight_px, search_px)` and chases while `gap < sight_px`. Mission scripts often change `sight_px`.

**Spell damage.** With the power at cast P (see "Spells and runes"), on each target:
1. Element: no element, P; same element as the target, `−floor(P / 8)`; opposite, `floor(3P / 2)`.
2. If above 0, `× (100 − magic defense) / 100` (magic defense ≤ 99); if 0 or less, halve it (the same element heals about P/16).
3. `× (128 + r) / 128`, with `r` random from −10 to 10.
4. If the target has more `weakness` than `halving` statuses for that element, ×2; with more `halving`, ×½.
Spells with `heal` add P with no element, defense or randomness.

**Element against element** (`elements` in `/data/combat.json`). The unit's `element` (`null`: no element) is compared with the hit's. `elements.spell_table` is step 1 above, by the spell's element (rows) and the target's (columns): `-0.125` is the same element, which heals; `1.5`, the opposite one; `1`, the rest, and always when either side has no element. A fire spell heals a fire creature instead of hurting it, whatever its magic defense. No unit is immune to an element's damage.
- Normal attacks (`elements.attack`): ×½ against the same element, ×1.5 against the opposite one.
- Chance of the spell's status (`elements.status_chance`): ×½ if the target has the spell's element, ×1.5 if it has the opposite one. The spell's element is what counts, not the status's. No status has an element exception: a fire creature can be burned and a water one can be frozen, at half the chance when the spell is of its element.
- Damage dealt by statuses (`elements.status_damage`): burn is fire and poison is dark, and both go through steps 1 to 4. On a fire creature, each burn tick heals it instead (1 or 2 HP at power 10); on a water one it does ×1.5. Freeze carries water but deals no damage.

**Bosses.** Only each battle's boss unit casts spells (`casts_spells` in its file), when its script tells it to; there is no range check. It has its own MP (its `mp` stat), pays each rune at rank 0, and its power is `power + magic`, with no quality or rank.

**Isaac's element levels.** They start at `growth.start`. They only change on a battle's results screen. With `old` the level before the battle, `c` the spells of that element cast in it (one per spell, its last rune's element; cancelled chants don't count) and `fav` the element cast strictly the most (none on a tie):
- `old ≤ low_band_max`: +`low_band_many_gain` if `c ≥ many_casts`, +1 if `1 ≤ c < many_casts`; +1 more if it is `fav`.
- `old ≤ mid_band_max`: +1 if `c ≥ many_casts`; +1 more if it is `fav`.
- Otherwise: +1 only if it is `fav`, and then the opposite element loses 1 if its level was `decay_min` or more.
- Then each level is clamped to `level_min`–`level_max`.
The level only changes the rank (`floor((level − 1) / 10)`), and the rank only changes the runes' MP, the power (×(rank + 4) / 4) and the status chance (×(rank + 4) / 4).

## HP bar

`hp_bar` in `/data/combat.json` is the HP bar the game draws over every unit on the field, all the time. It is made of solid rectangles, with no picture (`drawn_by_site: true`). Sizes are in px from the unit's point (`x`, `y` in `/data/maps.json`) raised by its head height, `head_px` in `/data/units/<slug>.json`: a rectangle with `left_px` and `top_px` spans `width_px` × `height_px` from `(x + left_px, y − head_px + top_px)`.

1. `frame`: the frame, in `frame.color`, under everything. What the bar does not cover shows that colour.
2. `bar`: the HP, `floor(width_px × HP / max HP)` px wide, with the effective max HP (after `change_max_hp`). Its colour, `color_by_side`: `player` for Isaac's side, `enemy` for enemies, `ally` for allies and characters on no side.
3. `change`: for `change.ticks` ticks after the HP changes, another bar at the same place and height sits between the frame and `bar`. Only the last change counts.
   - After damage, `bar` shows the new HP and the one under it the HP before, in `damage_color.enemy` on enemies and `damage_color.other` on the rest.
   - After healing, `bar` shows the HP before and the one under it the new HP, in `heal_color`.

The bar does not rise with units lifted off the ground. A defeated unit keeps it through its `die` animation and the 30-tick wait, and loses it when the puff it vanishes in starts (`hidden_from: vanish`).

## Enemy marker

`/data/enemy-marker.json` is the small letter the game draws just left of every enemy's HP bar. It marks the side: only enemies carry it; Isaac, his squads and allies carry none (`carried_by`).

- **Picture.** `letters`: "E" with the game in English, French or Spanish and "F" in German (`languages`; the game takes the console's language and any other counts as English). Each is 8 × 8 px (`size_px`), pink and white, not animated.
- **Position.** From the unit's point (`x`, `y` in `/data/maps.json`) raised by its head height (`head_px`): the picture's `pivot_px` goes at `(x + offset_px[0], y − head_px + offset_px[1])`, that is `(x − 14, y − head_px − 6)`. It sits `gap_to_hp_bar_frame_px` (5 px) left of the HP bar's frame. Scale 1, opaque, in front of its unit. Example (`example`): on a Misty Mush (`head_px` 14) the picture spans `(x − 18, y − 24)` to `(x − 11, y − 17)`.
- **When it shows.** While the enemy is on the field and on screen, all the time; it does not depend on selection or damage. Every unit has an appear counter from 0 to `fade_max` (127): it enters the field at 63 and goes up 1 per tick to 127; while the unit blinks as it appears, the letter stays steady (`blinks: false`). An enemy with the `hidden` status goes down 1 per tick and the letter goes when it reaches 0. When it dies, the letter stays during the vanishing puff, even though the HP bar is no longer drawn, and goes when the unit is removed.

## Mana crystals

Mana crystals (MP pillars) stand on the field in many battles. Each battle in `/data/maps.json` has `crystals`: the position `x`, `y` in map px and `owner`, who holds it when the battle starts (`player`, `enemy` or `neutral`). The rules are in `mana_crystals` of `/data/combat.json`, in px and ticks of 1/60 s:

- It is a solid circle of `radius_px` that stops walking and flying units and shots. It has no HP: it only changes hands.
- **Capture.** Every tick, each unit that can capture (`can_capture_crystals` in `/data/units/<slug>.json`), alive and within `capture_radius_px`, adds `gauge_per_unit_per_tick` to the gauge. When nobody adds, the gauge drops `gauge_decay_per_tick`. Over `capture_gauge.neutral` (nobody's crystal) or `capture_gauge.owned` (a held one), it goes to the side taking it, and the gauge goes back to 0.
- **Blocking.** Nobody takes it while a unit of its owner is within `presence_radius_px`. A crystal of nobody does not move on either while units of two sides are that close.
- **Sides.** `sides_that_capture`: in the story only Isaac and his squads take crystals; the enemies never take or defend them.
- **Healing.** Its owner's units within `heal.radius_px` recover `heal.hp` HP every `heal.every_ticks` while they are hurt.
- **MP.** Every `mp_regen.every_ticks`, Isaac's MP rises by `mp_regen.base_mp`, plus `mp_regen.mp_per_crystal` for each crystal he holds, plus 1 for each earlier rise in a row without chanting, up to `mp_regen.streak_max`. Without crystals he still recovers: the base and the streak. After a chant the first rise comes after `mp_regen.chant_wait_ticks`; with crystals, after `chant_wait_ticks − chant_wait_cut_ticks_per_crystal × crystals`, at least `chant_wait_min_ticks`.
- **Picture.** `pictures` has one picture per owner, of `picture_size`. Its top-left corner goes at the crystal's point plus `picture_offset`.
- **Capture bar.** `capture_bar` describes the bar the game draws with rectangles, not with a picture. `drawn_by_site` is `true` because the site redraws it from these rules:
  - It is `height_px` tall and sits `above_px` above the crystal's point.
  - The background, in `colors.background`, starts at `background.<neutral|owned>.left_px` and is `width_px` wide.
  - Over it, from the same edge and in `colors.remaining`, goes what is left: `(capture_gauge − gauge) / remaining_divisor` px, with the gauge of the last push.
  - Each push shows it for `fade_ticks` ticks, with opacity `ticks left / 2` out of `alpha_max`.
- **Missions.** The objective "Purify the Mana Crystals!" is won when they are all the player's (`all_crystals` in `conditions`).

## Battle camera

`/data/camera.json` has the constants from the code, in px of the map and px per frame (60 a second). The view is `view_px` (256 × 192): the bottom screen, the touch screen, where the game puts the main graphics engine (3D and the map's backgrounds). The top screen is covered by "Top screen" below.

- **Position.** It is the view's top-left corner. It goes from 0 to each map's `view_x_max_px` and `view_y_max_px` in `maps` (the map's size minus the view); every move is clamped to those limits. They are the world bounds (`world_bounds.always`): there is nothing outside the map, so the camera stops on each axis when it reaches the edge and keeps going on the other axis if it still can. The game has a flag to lift them, but only the camera's init writes it, with 0; nothing turns it on. Units and effects are drawn at `position on the map − camera position`, in whole px. A unit more than 40 px outside the view (`unit_draw_margin_px`) is not drawn.
- **It follows nobody.** The camera does not move by itself when Isaac walks, and it does not follow spells, targets or captured creatures. Only the four cases below move it, with no smoothing or dead zone: constant speed and an exact arrival.
- **At the start of a battle** it centres Isaac: it puts him at `battle_start_isaac_at_px` (128, 96) of the screen, clamped to the limits.
- **D-pad.** While held, the view moves `dpad.px_per_frame` (7) that way, or `dpad.diagonal_px_per_frame_per_axis` (4.9) per axis on a diagonal. With the left-handed option, A, Y, B and X act as right, left, down and up. There is no automatic return: the view stays where the player leaves it. It does not move while a spell is being prepared or chanted, or when the mission turns the D-pad off.
- **Chant.** When L is held (R with the left-handed option) and Isaac is outside the screen rectangle `chant_recentre.isaac_rect_px` (x 20–236, y 20–172), the chant waits and the view moves toward him at `chant_recentre.px_per_frame` (10) in a straight line, until he is at `target_px` (128, 96). With less than `jump_below_px` (10) to go, it jumps there. It stops early if Isaac enters the rectangle or a map edge stops the view. During the chant it keeps correcting the same way.
- **Missions.** A script reads the view's position and moves it in a straight line to `(x, y)` at `speed × script_pan.px_per_frame_per_speed_unit` (speed / 4) px per frame; missions almost always use 24, that is 6 px per frame. It reports when it arrives. It does not go back to Isaac by itself. If the target is past the limits, the view stops at the edge.
- **Shake.** Big Drop moves the view by `shake.big_drop`: ±2 px horizontally, switching side every 2 frames, for 30 frames, then back to 0. The earthquake effect (`earthquake_unused`, three phases) is in the code, but no spell uses it. The shake does not change the camera position, only what is drawn.
- **Top screen** (`top_screen`). It depends on the chant state. In states 0, 1 and 2 (no chant) it shows the minimap, which places four marks from the view's position. While drawing (states 7 and 8) it shows the rune list: a `rune_grid` of 6 columns, one per element, by 3 rows, one per rune level, next to a bar of Isaac's MP. When the chant gives a spell (state 9) it shows that spell: its number, name and description. It goes back to the minimap as soon as the state is 0, 1 or 2 again. That unlearned runes show as "?" was seen in the game, not traced in the code.
- **Backdrops.** Every map layer gets the view's position. `BACK` (the map) and `ANIM` use it as it is. `NORM` multiplies it by `speed_factor` (`PARM` / 256): with mode 3 or 4 it is a backdrop behind the map (sky, sea, lava) that moves at that fraction; with mode 1 it goes in front. On a map with `ANIM`, the `NORM` layer is not shown (`shown: false`). Reading the code did not confirm whether a backdrop repeats when it reaches its edge.

## Damage numbers

`/data/damage-numbers.json` says how the number that appears over a unit when its HP changes is drawn. It is read from the code (`evidence: static`); the colour, the place, the duration and the hundreds slip were also checked in the emulator with Fireball in the first battle. `confidence` says how sure each point is. Positions are screen px (y grows downward) and times are ticks of 1/60 s.

- **When** (`shown_when: hp_changes`). Every time a unit's HP changes, from a spell, a normal attack, a shot, a status, the terrain or a crystal's healing (`sources`), on any side (Isaac, his squads and the enemies alike). It shows the absolute value of the change (`value.shown`), with no sign, up to `value.max` (9999). It is the damage worked out, not the HP the unit had left: a hit of 347 on a unit with 20 HP shows 347. A change of 0 shows nothing, and a miss does not change HP, so it shows nothing either.
- **A slip of the game at 1000 and above** (`value.drops_zero_hundreds`). The digits are picked like this (`value.digits_drawn`): the thousands if the value is over 999; the hundreds only if the rest (value mod 1000) is over 99; the tens if the rest (mod 100) is over 9 or a digit was already drawn; and the units. The hundreds step does not check for a thousands digit, so a 0 in the hundreds is lost: 1050 shows "150", 1005 "105" and 2000 "200". To draw it as the DS does, copy that slip.
- **Digits** (`font`). Only the digits 0–9, in 8 × 8 px cells with their point at (4, 4), the centre. They are the same in the four languages. The pictures in `kinds.<kind>.image` are 80 × 8 strips with digit d at x = 8 × d.
- **Colour** (`kinds`). It depends only on the sign, not on the side or the element: `damage` (HP lost) is pink-red `#de1852` with a white outline; `healing` (HP gained) is green `#18bd73` with a white outline. Damage to Isaac or an ally looks the same as damage to an enemy.
- **Where** (`place`). The number's centre goes on the unit's point (its feet) plus that unit's `radius_px` downward (`/data/units/<slug>.json`). It is worked out every tick, so it follows the unit if it moves. It is not drawn while the unit is not drawn (more than 40 px outside the view).
- **How the digits are laid out** (`layout`). With `n` digits, the centre of digit `k` (from 0, left to right) is at x = `−(3n − 3) + 6k` from the number's centre, and y = 0. They are 6 px apart (`advance_px`) although each cell is 8 wide, so the white outlines overlap: each digit is drawn over the one before.
- **Motion** (`timing`). None: it does not rise, bounce, scale or fade. It appears whole, stays `lasts_ticks` (40, two thirds of a second) and disappears at once. If the hit kills the unit (`timing.killing_blow`), the countdown stops while it dies: the number stays until the unit vanishes in its cloud (about 190 ticks for the first battle's Misty Mush).
- **Several at once** (`stacking`). Each unit has one number. If its HP changes again before it goes, the new value replaces it and the 40 ticks start over; numbers are not added up or stacked. Numbers of different units are independent.
- **Layer** (`layer`). It is sorted with the rest of the field as if it were 20 px lower than the unit: it is in front of the unit and of whatever is less than 20 px below it.
- **Sound.** Damage (not healing) plays `damage_sound` (`SE_UNIT_DAMAGE`, effect 50).
- **Example** (`example`). Fireball takes 347 from a Misty Mush (`radius_px` 6): three pink digits with their centres at (−6, 6), (0, 6) and (6, 6) from the mushroom's feet, for 40 ticks. `example.image` is that number already put together (20 × 8), with its centre at `point_px` (10, 4).

## Chant screen

`/data/chant-screen.json` has the chant screen's graphics, read from the code (`evidence: static`), not measured in an emulator. Positions are screen px (256 × 192), times are ticks of 1/60 s, and alphas go from 0 (clear) to 1 (opaque; the DS uses 0 to 31).

- **When.** The circle and the dimming layer appear when drawing starts and vanish at once when the drawing ends, together with the strokes. They do not follow the camera: they stay put on the bottom screen.
- **Dimming layer** (`dim_layer`). A rectangle over the whole screen, colour `colour` (#525252), alpha-blended over the field. Its alpha starts at `alpha_from` (10/31) and rises 1/31 every `step_every_ticks` (2) ticks up to `alpha_to` (20/31): it gets there in 20 ticks. This is what tints the field; the circle does not tint it.
- **Circle** (`circle`). The picture `image` (256 × 192, its own orange palette) goes with its point `pivot_px` (128, 79) on `centre_px` (128, 96); the drawing fills `art_box_px` of the picture, so its centre lands on (128, 103) of the screen. It is drawn opaque (alpha 1) above the dimming layer. When it appears, for `appear.ticks` (30) it grows from almost 0 by `scale_per_tick` (0.0332) per tick and turns `turns_per_tick` (1/30 of a turn per tick): one full turn. On tick 31 it is at scale 1 and angle 0, and it stays still. The direction of the turn is not confirmed. `fade_unused` is a spinning fade-out that is in the code but the game does not use.
- **Stroke** (`stroke`). Each stroke is a ribbon `width_px` (8) wide around the stylus points (one per tick, up to `max_points`), with round caps reaching `cap_px` (4) past the first and last point. It is painted with the texture `texture` (32 × 32: `rim` yellow edge, `core` white centre) shrunk to the ribbon's width: the texture's left half is the start cap, its right half the end cap, and the middle columns stretch along the stroke. The texture colour is multiplied by the stroke colour (per channel, in 0..31: `(texture + 1) × (stroke + 1) / 32 − 1`) and alpha-blended.
- **Stroke colour.** While drawing it is white and opaque: the texture shows as it is. When a stroke is recognised as a rune, over `rune.ticks` (11) ticks each channel drops by `step31` per tick down to its element's colour (`by_element`, in 0..31) and the alpha drops from 31 to 20 (`alpha_to`); it stays like that. A stroke that is no rune (`not_a_rune`) stays white and loses 1/31 of alpha per tick down to 1/31 in 30 ticks.
- **Rune list** (`rune_list`, top screen, while drawing). Over the background `background`, which already has a "?" in every cell and the MP bar's track, go the six element icons (`elements`) and one 32 × 32 picture per learned rune in its cell (`cells`: column = element, row = level). A rune not learned draws nothing, so the "?" shows. A learned one Isaac cannot pay with his current MP shows `no_mp` (an X) instead of the rune. `preview` is the list with all 18.
- **Numbers.** Each rune that can be paid carries its MP cost, `cost.digits` (2) digits at `cost.offset_px` (18, 18) from its cell's corner; the cost is `mp_per_rune` of `/data/runes.json` for the chant's next rune. Isaac's current MP goes with 3 digits at `mp.at_px` (217, 45). The font of those digits is not in this data.
- **MP bars** (`bars`). Two 64 × 64 pictures (grey and yellow) with their point `pivot_px` (29, 64) on `at_px` (224, 155): they grow upward and, full, are `full_height_px` (88) tall. Both follow Isaac's MP; which one is in front is not confirmed.
- **Sparkle** (`sparkle`). A strip of 32 × 32 frames with their length in `ticks`, placed over the cell of the rune just drawn.
## Battle end

`/data/battle-end.json` says what the DS shows from the moment a battle is decided to the end of the results screen. It is read from the code (`evidence: static`), not measured in an emulator; `confidence` says how sure each point is. Screen px (256 × 192 per screen), ticks of 1/60 s.

- **Outcomes** (`outcomes`). A mission's trigger ends the battle with one of four actions (`action_id`, the action number of the mission scripts). Each one sets the word the field shows (`word`), the song (`song`) and whether the results screen follows (`results_screen`). `GameClearStart` (3) is a win: WIN and song 11. `GameOverStart` (2) is a defeat: LOSE, or TIME OVER (`word_when_time_limit`) when the trigger that fired was the time limit. `GameLostClearStart` (5) shows LOSE but counts as a win. `GameEvenStart` (6) is a draw: DRAW. All but the win play song 12.
- **The battle stops** (`battle`). From that tick no unit moves, thinks or attacks and no trigger runs; the units are still drawn. There is no victory pose. A chant in progress is cancelled and its sound (`stops_sound`, `SE_MAGIC_EISHO`) stops. The battle waits `settle_ticks` (30) and until the effects in flight are over.
- **Banner** (`banner`). Then the song starts and the banner runs, on the bottom screen, fixed to the screen at `at_px` (128, 96); each picture of `words` (one per language, from the console's language: French, German, Spanish, any other one English) goes with its `pivot_px` on that point.
  1. For `ticks.circle_only` (31) only the chant's magic circle and dim layer show, entering as when Isaac chants (`/data/chant-screen.json`, `circle` and `dim_layer`). The word is not drawn yet.
  2. The word appears, opaque, for `ticks.shown` (31) and stays `ticks.held` (61) more.
  3. Then the word goes at once (the code means to fade it, but its alpha starts at 0; seen in the emulator), and for `ticks.circle_leaving` (31) the circle leaves (`circle.exit`): it turns 24° per tick and its alpha drops 1/31 per tick.
  4. At `ticks.total` (154) the circle and the dim layer go at once.
- **Then.** With song 12 the battle also waits for that song to end (`waits_while_song_plays`; it plays once, 4.287 s), so a defeat lasts longer than a win. Last, both screens fade to black (`fade_out`, 30 ticks). After a win comes the results screen; after a defeat or a draw, Continue (section "Game rules").
- **START** (`banner.start`). Before play the battle shows START the same way, unless the mission calls native 46 first. The first battle (`qc01_m001`) calls it, so it starts with no banner; its New Game+ version shows START.

### Results screen

`results`. It only follows a win. Song `results.song` (10, `SEQ_QUESTCL`) starts with it and plays through all of it. It has up to four phases in the order of `results.phases`; a phase with nothing to show is skipped. Each phase fades in from black and out to black (30 ticks). After the last one comes the chapter title, if one is pending, or the world map.

**Experience** (`results.exp`), always shown. `previews` has both screens put together from these fields, in English and Spanish, without the text. The game's menu font is not decoded: texts are drawn with fixed 7-px characters from their point, in a font of your choice.
- Top screen: `top.background` (it already has Isaac's portrait, the LV box and three dark rows). Isaac: the LV label at `isaac.lv_label_at_px` (the `_fr_es` one in French and Spanish), his level as `%2d` at `level_text_at_px`, the EXP bar at `exp_bar_at_px` (`bar_frame_64`, then `bar_back_64`, then `bar_fill` stretched to `exp_bar_fill_px` (62) × EXP / EXP to the next level). Each squad of the team, in roster order, at most 3: its unit's icon at `squads.icon_at_px`, the plate, its name and level and its bar at the offsets of `squads` from the icon (bar 46 px, `bar_frame_48` and `bar_back_48`). The element levels are not on the top screen.
- Bottom screen, page 1 (`bottom.pages` 1): the four `bars` at their `at_px`, and the `texts`: `clear_time` the battle time in seconds (at most its time limit) as `hh:mm:ss`; `defeated` the kills; `survival` the squad monsters alive × 10000 / those deployed, as `%3d.%02d%%`, or `---.--%` with no squads; `exp_pool` the EXP still to give, which counts down.
- Bottom screen, page 2 (`bottom.pages` 2, 192 px further down): the element radar. The six element icons at `radar.icons`, and two translucent hexagons centred on `radar.centre_px`, one vertex per element (top light, upper right fire, lower right earth, bottom dark, lower left water, upper left wind). Red: radius `floor((level + 10) × 68 / 50)`; green: `floor(rank × 10 × 68 / 50)`, rank `floor((level − 1) / 10) + 1`. The green one is drawn in front of the red one (seen in the emulator); their alpha is not decoded.
- Timeline (`waits_ticks` 30, 60, 30, 60):
  1. Wait 30 ticks.
  2. Count-up, every tick: `step = max(1, floor(pool / 80))`, pool − step, and Isaac and every squad each get `step`. Sound `count_step` (18, `SE_KASAN`) every tick; each level gained plays `level_up` (19, `SE_LEVELUP`) and shows `level_up` ("UP!", its `frames` move it by `dy_px`) at Isaac's `level_up_at_px` or on the squad's icon. Bars, levels and `exp_pool` change every tick. At 0 the Next button shows and the screen waits.
  3. Wait 60 ticks, then page 2 scrolls up into view in 64 ticks (3 px per tick).
  4. Wait 30 ticks, then the element levels go from the old to the new ones (growth rule in "Game rules") in 30 ticks, the hexagons redrawn each tick; `element_step` (20, `SE_ZOKUSEI`) plays at 0, 10 and 20 ticks. The Next button shows and the screen waits.
  5. Wait 60 ticks and fade out.
- Input. The Next button (`next_button`, by language) is at (232, 168), or (24, 168) with the left-handed option; it fires when the stylus is lifted within 20 px of that point. A new press of A, B, X, Y or the D-pad also counts as Next. During the count-up any touch or one of those buttons gives the rest at once (each member gets `ceil(pool / members)`, a quirk of the game) and skips that Next. A touch during the scroll jumps to page 2.

**Items and monsters** (`results.getting`): one screen per item found, then one per monster captured, in the order the battle got them. Bottom screen: the background (`item` or `monster`), the title text (70 "You found an item!" or 75 "You captured a monster!", centred at x 128, y 24), the found row at `found_row_at_px` (its background `button_images` 11 for items, 10 for monsters; icon, name 16 px to the right, LV and level), and the two option buttons `buttons.options` (104 × 48, texts 71/72 or 76/77). "Add" adds it to the list; with the list full the title becomes 73/78 and Next opens the list (`item_list`/`monster_list`, 6 rows 18 px apart, `list_images`, page arrows 30/31) to pick one to replace; equipped items and monsters in a squad can't be picked. "Throw away"/"Let go" asks with `buttons.confirm` (YES 8, NO 9). Touch only; a button fires when the stylus is lifted inside it. The top screen shows the item's name and description (`item_top`, `item_top_window`).

**New magic** (`results.magic`): one screen per new spell, spells learned first and then the ones only seen (their runes show as "?"). Bottom: `background` and a row at `row_at_px` (32, 76): bar coloured by element (`images` 9–14), `%03d:` number, name 32 px right, runes at 132, 152 and 172 px (`images` 15–32, "?" 34). The top screen shows the spell as the battle's top screen does. Next button or A/B/X/Y/D-pad goes on.

`/data/battle-end.md` has the same data as tables.

## Title screen

`/data/title-screen.json` is the menu that follows TOUCH TO START: the logo above and five buttons below. It is read from the code and checked in an emulator (`evidence: static_and_emulator`); `confidence` says how sure each point is. Screen px (256 × 192 per screen, from the top-left) and ticks of 1/60 s.

- **Backgrounds.** `top.background` already holds the logo and "©TAITO CORP. 2005, 2006"; `bottom.background` the sky, Isaac on the hill, the staff and the birds. Both go at (0, 0) and nothing moves: the birds are painted.
- **Buttons** (`buttons`, in order: New Game, Load Game, Multiplayer, Wi-Fi Connection, Hand Settings). Each one is the panel `panel` (128 × 24) with its text (`label`, en, es, fr, de) centred at `label.offset_px` (64, 4) from its corner. `images` has the finished button in each language, captured from the emulator: the menu font is not decoded. The game picks the language from the console's (French, German, Spanish; any other one, English).
- **Entry** (`entry`). When the menu appears both screens brighten from black in 30 ticks (`fade_in`) and, at the same time, each button moves in a straight line from `from_px` to `to_px` in 30 ticks: New Game and Multiplayer from the left, Load Game and Wi-Fi from the right, Hand Settings from below. The step per tick is `step_px`, truncated as the game does (`entry.position`), so New Game ends at x 29 and Multiplayer at x 40, 1 px short of `table_to_px`. Touches don't count until all of them stop.
- **Touch** (`press`). Touch screen only: the D-pad and the buttons do nothing here. A touch that starts inside a button (`hit_box`, the edge not included) marks it; while the stylus stays inside, the button is drawn 2 px right and 2 px down, and it goes back 1 px per tick once released. Lifting the stylus inside chooses it: `press.sound` (3, `SE_KETTEI`) plays and the other buttons stop responding. There is no cursor or cancel sound.
- **On choosing** (`choose`). The buttons don't leave: both screens fade to black in 30 ticks and the game goes where `does` says. New Game starts the prologue. Load Game opens the load screen even with no save: its three slots say "No Data". The menu never disables a button; a disabled one would have grey text (`label.colour_bgr555.disabled`) and not respond.
- **Music.** The song `music` (21, `SEQ_DM_TITLE`) is started by the title demo, before TOUCH TO START, and the menu doesn't start another. Touching TOUCH TO START plays 2 (`SE_CURSOR`), fades to black in 30 ticks and stays black about 57 while the menu loads.
- **Idle** (`idle`). Left alone nothing changes and it doesn't go back to the demo.
- **Hand Settings** (`hand_settings`). It doesn't leave the menu: the five buttons go back to their `from_px` in 30 ticks, Hand Settings returns to its place, and the question ("Left/Right handed?", `question`) and the two hands (`hands`, from `chr/select_hand.can`) come in. Touching a hand plays 3, saves 1 for the left one and 0 for the right one, and the menu comes back. Which value means left-handed is not confirmed.
- On the DS this screen has no Options or Quit.

`/data/title-screen.md` has the same data as tables.

## Characters

`/data/characters.json` lists the 19 characters who speak in the story, with their `name`, how many lines they have (`lines`) and `faces`: one picture per expression, in the game's order. The story scenes use these portraits through `/juego/data.json` (see "Scenes").

## Battle maps and battles

`/data/maps.json` lists each battle map: the full `image`, the game's `minimap`, and its `battles`. A battle has its number (`battle`), the story `missions` that use it, and the `units` on the field when it starts:

- `uid` and `unit` (the slug in `/data/units.json`) say who it is.
- `x`, `y` is its position on the map. With the sprite's `origin` on that point, units stand on the walkable ground of the map.
- `level`, and `named`: `true` for story characters (bosses and allies), `false` for regular monsters.
- `orders`: what it does on its own from when it enters the field (see "Unit orders in battles").

Each mission in `missions` also has `objective` (the text the game shows), `conditions` (how it ends: `result` `victory`, `defeat`, `defeat_passed` —a defeat that counts as passed— or `draw`, when `trigger` is met: `no_enemies`, `boss_dies`, `isaac_dies`, `time` (`value` in seconds), `all_crystals`, `no_crystals`, `crystals_held` (`value` crystals), `isaac_reaches_area`, `boss_reaches_area`, `spell_cast` (`value` = spell id), `chest_opened`, `capture`, `draw`…), `time_limit_s` (3600 when it sets none) and `music` (the song's `n` in `/data/audio.json`). When a mission has several battles, the list covers them all.

`chests` are the battle's chests: position `x`, `y` and `odds`, what it can give (`item` from `/data/items.json`, `chance` and the chance of each level in `levels`).

`seals` are the appear seals on the battle's floor: point `x`, `y` and `units`, the indexes in `units` of the hidden units that stand on it. How to draw them is in "Appear seals".

`animation` is set on the maps the game animates: falling snow (tile set `ac03`, 8 frames of 11 ticks) and running sand (`ac04`, 4 frames of 7 ticks). Each frame lasts one tick more than the value in the file: the game's clock counts down to 0 and changes frame on the next tick. `base` is the map without that layer; `frames` holds the frames stacked top to bottom, each the size of `base` and transparent where the layer is empty. Draw frame `i` over `base` for `ticks[i]` ticks of 1/60 s, in a loop. `image` is `base` with the first frame. On other maps `animation` is `null`.

`decorations` are the game's objects drawn over the map: bridges, tree tops and others. Each has its point `x`, `y` in map px, `alpha` (opacity 0 to 31) and `height`: the game draws them in order of `y + height`. `frames` is a strip of frames of `frame_size`, stacked top to bottom; `origin` is the point within each frame. Frame `i` shows for `ticks[i]` ticks, in a loop; with one frame the decoration is still. `sprite` groups those that share a picture.

`lava` is set on the magma maps (025–028, 044, 305): the lava glows because the game changes the colours of one row of its palette. `mask` is a picture the size of the map: its red is that row's colour (1–15) at each pixel, and 0 outside the lava. Frame `i` paints those pixels with `palettes[i]` (16 colours `#rrggbb`; 0 is unused) for `ticks[i]` ticks, in a loop. Frame 0 is the map's picture.

`terrain` is the map's collision and terrain, in map px (shapes may sit off the edge). There is no grid: units are pushed out of shapes every frame.

- `boxes` (centre `x`, `y` ± `half_width`, `half_height`) and `circles` (`x`, `y`, `radius`), each with its `kind`:
  - `high_obstacle`: stops walking and flying units and shots. `low_obstacle`: walking units only.
  - Zones, for walking units only (flying units are never affected): `magma` and `damage_floor` deal 50 damage every 60 ticks (fire and no element); `ground` the same, only when a spell heated it; `ford` half speed unless frozen, puts out and blocks burns; `ice` nothing, half speed when a spell melts it; `bog` half unless dried; `sand` nothing, half when turned to swamp; `quicksand` pushes 0.25 px per tick towards `push`.
  - The 50 damage is multiplied by (118 + random(0..20)) / 128; a unit of the same element heals 6 and one of the opposite element takes 75. Water units ignore the ford and melted-ice slowing; earth units ignore bog, sand and swamp slowing and the push.
  - `event_area` (with its number `area`): missions ask whether units are inside; it blocks nothing.
- `squad_starts`: where the player's squads appear. A squad's units are placed at that point plus `team.formation_px` from `/data/rules.json`.
- Terrain spells (`terrain` in `/data/spells.json`) change how zones behave inside a circle for 20 s; they do not change the shapes.

`terrain_effects` lists the zones with an effect on each map (`kind`, plus the `push` directions of `quicksand`). The full rules are in `terrain` of `/data/rules.json`:

- `effects` says what each `kind` does to a walking unit inside it: `push_px_per_tick`, `speed_multiplier`, or `damage` every `every_ticks` with its `element`. `spared_elements` are the elements spared its slowing or push, and `altered` is the effect while a terrain spell changes it (`heat`, `harden`, `swamp`, `dry`, `freeze`).
- `damage`: a unit of the same element heals `same_element_heals_fraction` of the damage, one of the opposite element takes `opposite_element_multiplier`, and the result is multiplied by a random value between the two in `random_multiplier`.
- `flying_units_affected` is `false`, and `sides_affected` says Isaac, his squads and the enemies are treated alike.
- The animated map layers only draw: the running sand matches the `quicksand` zones, the snow has no zone of its own and the lava is `magma`.
- Each unit has `terrain_spared` in `/data/units/<slug>.json`: `all` if it flies, the `kind` whose slowing or push it ignores, `fire_heals` if fire terrain damage heals it and `fire_x1.5` if it takes 1.5 times.

## Appear seals

Some story battles draw hexagram seals on the floor at the points where enemies appear (98 seals in 34 battles; battle 1 has six, one under each Misty Mush). `/data/appear-seals.json` has the pictures, the rules and, in `battles`, each battle's seals with the units that stand on them; `seals` in `/data/maps.json` has the same points. Map px, ticks of 1/60 s, alpha out of 31 (opacity = alpha / 31). It comes from the game's code, and the colours, the depth, the blink and the light column were checked in an emulator on battle 1.

- **When.** Every seal is on the floor from the start of the battle to its end. It does not react to the units: nothing spawns from it and nothing changes when a unit appears on it.
- **Picture.** `seal.image` is a strip of 32 × 25 frames, left to right `base`, `fire`, `wind`, `water`, `earth`, `light`. Put the frame's `pivot` (16, 12) on the seal's point. The seal is drawn under every unit.
- **Base.** Frame `base` (grey), always opaque.
- **Colour layer.** One colour frame over the base, starting with `fire`. Every `step_ticks` (4) a step counter goes up; the layer's alpha is `alpha_by_step[step − 1]` (1 up to 30, then down to 2). After `steps_per_color` (59) steps, `color_ticks` (236), it passes to the next colour in `colors`, and from `light` back to `fire`. So the seal slowly glows and fades in fire (yellow-orange), wind (green), water (blue), earth (orange) and light (yellow).
- **Size.** The seal does not change size (`scale` 1).

**A unit appearing.** A hidden unit comes out when the mission's script makes it appear, on its own point (in battle 1 that is a seal) or on another. Nothing sounds. Two things happen at once, from tick 1:

- **Blink.** A counter goes from `counter_from` (63) to `counter_to` (127), one per tick. While it rises, the unit is drawn only when `counter & 2` is not 0: 2 ticks shown, 2 hidden, for 64 ticks. Then it stays opaque.
- **Light column.** `column.image` holds two 32 × 40 frames with the pivot (16, 32): `back`, drawn behind the unit, and `front`, drawn in front of it. Put the pivot on the unit's point moved down by its radius (`radius_px` in `/data/units/<slug>.json`), with alpha 16. Both mirror horizontally every other tick (`mirror_frames`). For ticks 1–29 their height is t / 30 of the full height, growing up from the pivot; from tick 30 they are full size and their alpha is 16 − floor(0.4 × (t − 30)); after tick 70 they are gone. The column follows the unit.

## World map

`/data/world-map.json` describes the map Isaac travels between missions. `picture` is `/juego/mundo.png`; positions are pixels of that picture.

- `places`: the 44 places, with `id`, `x`, `y` and `neighbors`, the places a path joins to it. `region` is its position in `regions`: the name and description the game shows (`name`, `description`, in `en`, `es`, `fr` and `de`); several places share a region.
- `availability_group` (0 to 15) is the switch that enables the place, and `unlock_flag` the flag of `/juego/data.json` that turns it on. According to the code, a place is enabled when its flag is on, and a path when both its places are.
- `paths`: the 50 paths, as pairs of `id`. They are straight and go both ways. Isaac only goes to a place joined by a path, and each leg takes `movement.leg_frames` (60) frames, short or long.
- `offers`: the missions and towns the map offers on each place.
  - `chapter` is the chapter of `/juego/data.json` (`qc01` to `qc06`; `sc01` to `sc03` for the side chapters) and `step`, the step inside the chapter. `optional` is `true` when completing it does not close the step.
  - `objective` is the text of its battles, empty when it only has scenes. `scenes` are ids of `/juego/data.json` and `battles` carries `battle` and `map` of `/data/maps.json` (`null` when the battle has no map page).
  - The same script can appear on two places: the game offers it on one or the other depending on a condition.
- `free_battles`: the free battles the game registers on each place, with their chapter.
- Arriving at a place that holds something starts it, and the game takes a checkpoint.
- `markers`: the map's three markers. Red marks a place where a mission or a town is offered; blue, another kind of event, not identified; for green, what shows it is not known.
- `confidence` says how each part is known: `rom` comes from the game's data; `code`, from reading its code, not checked in a play session.

## The story: `/juego/data.json`

This file is the game's story logic, read from its scripts: which missions are open, what each one plays, what the player's answers change and which ending they reach. Paths inside it are relative to `/juego/`. Texts are in `/juego/textos/<language>.json`, under the same ids.

### Game state

A game is four things, all starting empty:

- **flags**: numbered switches, on or off.
- **counters**: numbered values from 0 to 127. Counter `rules.compassion` is the one that decides between two endings.
- **answers**: for each scene, the option chosen in each choice slot.
- **done**: the missions completed.

### Conditions

Many entries carry a list of conditions; all must hold. Each is `[negate, test]` (when `negate` is `true`, the test must fail):

| Test | Holds when |
|------|------------|
| `["flag", n]` | Flag n is on |
| `["counter", n, op, k]` | Counter n compared with k (`<`, `<=`, `>`, `>=`, `==`, `!=`) is true |
| `["answer", scene, slot, option]` | In that scene, that option was chosen in that slot |
| `["done", mission]` | That mission was completed |
| `["result", k]` | The mission's last battle ended by condition k: 2, no enemy is left alive; 3, the boss died. Both are wins |

### Chapters and missions

`chapters` lists the chapters in order: `qc01` to `qc06` for the main story, `sc01` to `sc03` for side missions. Each chapter is a list of **steps** on the world map; each step offers missions as `[mission, offer, conditions]` and has `flags` to turn on when it ends:

- Only missions whose conditions hold are offered.
- `offer` 159 marks an optional mission. A step ends when one of its other missions is done (or any mission, if all are optional); then its `flags` turn on and the next step opens.
- If no step is open (something must still be found), the last step that offered missions stays open so its places can be visited again.
- The current chapter is the first `qc0N` whose flag in `rules.chapterFlags` (one per chapter, 1 to 4) is off. With all four on, the story continues in `qc06` if flag `rules.route6Flag` is on, and in `qc05` otherwise. Side chapters run at the same time.

`missions` describes each mission: `kind` (`m` battle, `t` town, `r` free battle) and a `timeline` played in order, skipping entries whose conditions (the last element) fail:

| Entry | Effect |
|-------|--------|
| `["scene", id, how, conditions]` | Play that scene; `how` is 32 for a scene on its own, 29 or 31 for one shown at the battle that follows |
| `["battle", id, conditions]` | Fight that battle |
| `["flag", n, value, conditions]` | Set flag n to value (1 is on) |
| `["counter", n, delta, conditions]` | Add delta to counter n, kept between 0 and 127 |

At the end the mission counts as done. Mission and chapter names are in the text files (`missions`, `chapters`).

### Scenes

`scenes[id].blocks` holds the scene's blocks; it starts in `main` and runs its items in order:

| Item | Meaning |
|------|---------|
| `["s", background, cameraX, cameraY, cast]` | What the top screen shows from now on |
| `["l", speaker, line]` | A line of dialogue: `names[speaker]` says `scenes[id].lines[line]` |
| `["c", slot, [[line, block], …]]` | The player chooses: each option's text is `scenes[id].lines[line]`. Record the option's position as the answer for that slot, then continue in its block |
| `["n", block]` | Continue in that block |

The scene ends when a block runs out of items. Lines keep the game's own line breaks (`\n`). Isaac is the hero's default name. `[L]`, `[B]` and `[+Control Pad]` (in each language's words) stand for the console's buttons.

To draw the top screen (256×192):

- **Background**: `bgs[background]` is `[picture, width, height]`. Show the part that starts at `x = clamp(-cameraX, 0, width - 256)`, `y = clamp(-cameraY, 0, height - 192)`.
- **Characters**: each `[actor, x, y, expression, mirror]` of `cast` is a portrait. `portraits["<actor>_<expression>"]` is `[picture, dx, dy, width, height]`: its top-left corner goes at `(x + dx, 160 + y + dy)`, mirrored horizontally when `mirror` is 1. `names[actor]` is who it is.

### Endings

When a mission turns on flag `endings.gameOverFlag`, the game ends: the ending scene is the one paired with the first flag that is on in `endings.byFlag` (`[flag, scene]`), or `endings.default`. Their titles are in the text files (`endings`).

### Battles in the story

`battles[id]` is a battle of the story: `map` (its picture), `size`, and `units` as `[uid, x, y, level, character]` (`character` is not 0 for story characters). The same battles, with names, are in `/data/maps.json`.

### Text files

`/juego/textos/<language>.json` has, for that language: `names` (speakers, by number), `scenes` (each scene's `title` and `lines`), `missions`, `chapters`, `endings` and `units` (unit names by `uid`). Its `ui` holds the labels the site's novel uses; you can ignore it.

## Audio

`/data/audio.json` has `music` (`code` is the song's name in the game) and `effects` grouped by `category`. Songs that loop are recorded playing the loop twice and fading out. In the game, a song with `loop_end_s` jumps back to `loop_start_s` when it reaches `loop_end_s` and plays forever; one with `end_s` plays once. `missions` and `scenes` count the missions and scenes that use it (each mission's is in `/data/maps.json`), and `screen` is the fixed screen that uses it: `world_map`, `mission_complete`, `victory` and `defeat` (when a battle ends), `game_over`, `title` or `ending`.

`sound_events` says what plays the sound effects of the menus, the world map, the results screen, treasure chests and mana crystals. It is read from the game's code (`evidence: static`): each place that plays a sound and the number it loads; it was not observed in an emulator. The game plays sound number N as entry N − 1 of the selected sound archive. That is normally `SEQ_SE`, the "Game" category of `effects`.
- `events`: `group` (`menu`, `world_map`, `results`, `chest`, `crystal`), `event`, `sounds` (`id`, `name`, `url`) and `calls`, the places in the code that play it. In menus, `cursor`, `confirm` and `cancel` are identified; `other` gathers the remaining menu sounds, whose triggering action on each screen was not traced. On the map: `move` (one leg of a path), `enter_event` (arriving at a scene or a town) and `enter_battle`. Results: `exp_count` (each step of the experience count) and `level_up`. Chest: `open` and `item` (the item obtained). Crystal: `captured` and `lost`.
- `download`: while the duel mode's Download screen is open, the game uses another archive, `SEQ_SE_CURSORS`. In it, numbers 2, 3, 4 and 108 play their own versions (`replaces` is the normal sound of that number). `url` is `null` when the site has no clip of it.

## Other pictures

`/data/gallery.json` groups the rest of the art by section: runes (the shape of each one, as a picture), spells and battle effects, backgrounds, world map and its markers, interface, and unused or test art. `/juego/mundo.png` is the world map.

## Not in the data

Your version has to define these:

- Which orders produce the AI's states 6, 7 and 12 (`unresolved_states` in `/data/ai.json`) and how the AI looks in a battle: what is published comes from the code, not from an emulator.

The graphics, texts and music belong to Taito and Ubisoft.
