# 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-sitio.vercel.app/

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

To download everything at once: https://lostmagic-sitio.vercel.app/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/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 and what changes inside each one |
| `/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/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/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,
  "attack_interval_ticks": 60,
  "attack_reach_px": 0,
  "radius_px": 8,
  "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,
  "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).
- `up_cost` (what it takes from the Line Up's capacity) and `squad_size` (how many units one squad brings).
- `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`).

  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.

## 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.

- `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)`.
- `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) and `levels`, the chance of each level.

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.

## 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.

### 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 and by `(rank + 4) / 4`), `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).
- `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 `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`.

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`). `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. `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.

## 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.

**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).

## 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.

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`).

`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.

## 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, 8 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.
