# LostMagic: datos del juego para hacer tu propia versión

LostMagic (Taito, 2006, Nintendo DS) es un juego de táctica. Isaac, el protagonista, lanza hechizos dibujando runas en la pantalla táctil y pelea junto a los monstruos que recluta, en una historia cuyas decisiones llevan a cuatro finales.

Estos archivos tienen lo que se extrajo del cartucho del juego (versión de EE. UU.): los sprites y las animaciones de cada unidad, los retratos de los personajes, los mapas de batalla con dónde empieza cada enemigo, la historia completa con sus decisiones y la lógica que ordena las misiones, los textos en inglés, español, francés y alemán, la música, los efectos de sonido y el resto del arte. Úsalos para hacer una versión del juego en cualquier plataforma.

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

Las rutas dentro de los JSON empiezan con `/` y son relativas a la URL base.

Para descargarlo todo de una vez: https://lostmagic.skapxd.dev/data/files.txt (una URL por línea).

## Archivos

| Archivo | Contenido |
|---------|-----------|
| `/data/units.md`, `/data/units.json` | Las 84 unidades (77 con nombre; el resto, apariencias sin nombre), cada una con el enlace a su detalle |
| `/data/units/<slug>.md`, `/data/units/<slug>.json` | Una unidad: sus estadísticas, hoja de sprites, el lugar exacto de cada cuadro y sus animaciones |
| `/data/levels.md`, `/data/levels.json` | Los niveles 1 a 50: experiencia, curvas de crecimiento de las estadísticas y capacidad del equipo |
| `/data/rules.md`, `/data/rules.json` | Los límites del juego: equipo, monstruos, objetos, niveles y experiencia |
| `/data/ai.md`, `/data/ai.json` | Movimiento e IA: rutas, búsqueda de objetivo y las órdenes de las unidades |
| `/data/duel.md`, `/data/duel.json` | El modo duelo: los equipos predefinidos y la puntuación por Wi-Fi |
| `/data/items.md`, `/data/items.json` | Los 8 objetos: qué mejoran, cuánto en cada nivel y en qué cofres aparecen |
| `/data/chest.md`, `/data/chest.json` | El cofre del tesoro como sprite: su imagen cerrada y sus cinco animaciones |
| `/data/appear-seals.md`, `/data/appear-seals.json` | Los sellos de aparición que brillan en el suelo de algunas batallas y cómo sale una unidad |
| `/data/spells.md`, `/data/spells.json` | Los 396 hechizos: runas, MP, apuntado, potencia, estado, terreno y sus números |
| `/data/spell-families.md`, `/data/spell-families.json` | Las familias de hechizos parecidos, qué cambia dentro de cada una y el efecto total de cada hechizo |
| `/data/best-spells.md`, `/data/best-spells.json` | Los mejores hechizos para el jugador: rendimiento por MP, grandes saltos, los que no vale la pena subir, el puntaje de trazo de cada runa y hechizo, y la clasificación por situación |
| `/data/spell-scenes.md`, `/data/spell-scenes.json` | Escenas: gráficos, paletas, cuadros y sonidos de los 396 hechizos; 90 unidades y 141 estados |
| `/data/runes.md`, `/data/runes.json` | Las 18 runas: los trazos que acepta el juego, dónde se aprenden y el MP de cada una |
| `/data/rune-strokes.md`, `/data/rune-strokes.json` | 640 trazos de prueba del reconocedor de runas y lo que da el juego en cada uno |
| `/data/statuses.md`, `/data/statuses.json` | Los estados alterados: qué hacen, cuánto duran y qué hechizos los causan |
| `/data/combat.md`, `/data/combat.json` | Constantes de batalla: elementos opuestos, ataques normales, proyectiles y crecimiento de los niveles de elemento |
| `/data/camera.md`, `/data/camera.json` | La cámara de batalla: límites del mundo, cruceta, recentrado del canto, guiones, temblor, pantalla de arriba y la velocidad del fondo de cada mapa |
| `/data/aiming.md`, `/data/aiming.json` | El apuntado de un hechizo: cómo se apunta cada modo, la flecha de los disparos, la retícula, las áreas, los iconos y la sombra del alcance, con sus imágenes |
| `/data/damage-numbers.md`, `/data/damage-numbers.json` | Los números de daño y curación sobre las unidades: cifras, colores, posición y cuánto duran, con sus imágenes |
| `/data/chant-screen.md`, `/data/chant-screen.json` | La pantalla del canto: el círculo mágico y la capa que oscurece el campo, el trazo y la lista de runas de la pantalla de arriba, con sus imágenes |
| `/data/battle-end.md`, `/data/battle-end.json` | El fin de la batalla: los carteles de victoria, derrota, empate y fin del tiempo, sus tiempos y su música, y la pantalla de resultados tras una victoria, con sus imágenes |
| `/data/title-screen.md`, `/data/title-screen.json` | La pantalla de título: los dos fondos, los cinco botones con su texto en cada idioma, su entrada, el toque y a dónde lleva cada uno |
| `/data/enemy-marker.md`, `/data/enemy-marker.json` | La letra junto a la barra de vida de cada enemigo: imagen por idioma, posición y cuándo se ve |
| `/data/characters.md`, `/data/characters.json` | Los 19 personajes de la historia y las expresiones de sus retratos |
| `/data/maps.md`, `/data/maps.json` | Los 55 mapas de batalla y sus 176 batallas: misiones y unidades al empezar |
| `/data/world-map.md`, `/data/world-map.json` | El mapa del mundo: 44 lugares, 50 caminos y lo que ofrece cada lugar en cada capítulo |
| `/juego/data.json` | La historia y su lógica: capítulos, misiones, escenas, batallas, banderas y finales |
| `/juego/textos/<en\|es\|fr\|de>.json` | Todos los textos de `/juego/data.json` en un idioma |
| `/data/audio.md`, `/data/audio.json` | 24 canciones y 147 efectos de sonido |
| `/data/gallery.md`, `/data/gallery.json` | 818 imágenes más: runas, hechizos y efectos, fondos, mapa del mundo, interfaz y arte sin usar |

## Convenciones

- Cada archivo de `/data/` viene en dos formatos con el mismo contenido: `.md`, en tablas, más fácil de leer y con menos tokens, y `.json`, para cargar los datos desde un programa. Los campos que se describen abajo son las claves del JSON; las tablas del Markdown usan los mismos nombres en sus columnas (`name.en`, `name.es` para los dos idiomas).
- Cada una de las dos pantallas de la DS mide 256×192 px. Las escenas de la historia se dibujan para la de arriba.
- Las coordenadas están en píxeles, desde la esquina superior izquierda, con la y creciendo hacia abajo.
- Las duraciones vienen en ticks de 1/60 s, la frecuencia de cuadros de la consola. Los campos `ms` son lo mismo, redondeado.
- El arte es pixel art: escálalo por números enteros, con muestreo del vecino más cercano, para que se vea nítido.
- Los nombres vienen en inglés y en español como `{ "en": …, "es": … }`. La historia tiene cuatro idiomas.
- Las imágenes son PNG; los GIF animados son solo vistas previas. El audio es AAC en `.m4a`: conviértelo si tu plataforma no reproduce AAC.

## Unidades y sus animaciones

`/data/units.json` lista todas las unidades: `uid` (el número que usan las batallas y la historia; `null` en las apariencias sin nombre), `slug`, `name`, `element` (`fire`, `wind`, `water`, `earth`, `dark`, `light` o `null`), un retrato (`portrait`), la hoja (`sheet`) y `details`, la URL con todos sus datos.

Cada unidad tiene una **hoja de sprites**: un PNG con todos los cuadros que usa. `sheet.cells` da el rectángulo exacto de cada cuadro en la hoja (`x`, `y`, `w`, `h`); su posición en la lista es el número del cuadro.

Una **animación** es una secuencia de cuadros que se repite, dibujada sobre un **lienzo** fijo de `width` × `height`:

- Cada elemento de `frames` dice qué cuadro de la hoja se muestra (`cell`), dónde va su esquina superior izquierda en el lienzo (`x`, `y`) y durante cuánto tiempo (`ticks`). Un mismo cuadro puede aparecer varias veces.
- `origin` es el punto de anclaje de la unidad en el lienzo. Queda fijo mientras los cuadros se mueven a su alrededor: así se desplaza el cuerpo en los saltos y los ataques. Coloca el lienzo de modo que `origin` caiga en la posición de la unidad.
- `action` y `facing` dicen cuándo la reproduce el juego: la acción de la unidad y hacia dónde mira (`down` de frente, `up` de espaldas, `left`, `right`; vacío si la acción no tiene dirección). `used` es `false` en las que están dibujadas pero el juego nunca reproduce.
- `preview` es la misma animación como GIF, para ver qué muestra. Los GIF guardan los tiempos en 1/100 s y unen los cuadros repetidos, así que usa `frames` para los tiempos.

Las acciones:

| `action` | Cuándo |
|---|---|
| `idle` | Quieta. Casi siempre es un solo cuadro, que está en la hoja pero no como animación |
| `walk` | Mientras se mueve |
| `attack` | Su ataque normal, cuerpo a cuerpo o a distancia |
| `chant` | Mientras se dibujan las runas (Isaac y los jefes) |
| `cast` | Al soltar el hechizo |
| `knocked_back` | La tumba un hechizo que derriba; después viene `get_up` |
| `get_up` | Se levanta después de caer |
| `sleep` | Un hechizo la duerme |
| `dance` | Isaac, con los hechizos de baile |
| `trapped` | La retiene una trampa de captura |
| `captured` | La captura funciona |
| `die` | Su vida llega a 0. Se reproduce una vez; medio segundo después la unidad se desvanece |
| `run`, `victory` | Dibujadas, sin uso (`used: false`) |

Si una unidad no tiene animación propia para una acción, se queda en su pose `idle`. Las que miran a la derecha son dibujos aparte, no un espejo hecho al reproducirlas.

```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` lista las batallas de la historia en las que participa la unidad.

Para dibujar el cuadro *i* de una animación: toma el rectángulo `sheet.cells[frames[i].cell]` y pégalo en el lienzo en `(frames[i].x, frames[i].y)`. Muéstralo durante `frames[i].ticks` / 60 segundos, pasa al siguiente y, al terminar, vuelve al primero.

### Estadísticas

Cada unidad con nombre trae, en `/data/units/<slug>.json`, sus números de las tablas del juego:

- `stats`: `hp` (vida), `mp` (maná), `attack`, `defense`, `magic`, `magic_defense`, `speed`, `accuracy` y `evasion`, cada una como `[nivel 1, nivel 50]`. En el nivel L vale `nivel1 + floor((nivel50 − nivel1) × factor / 128)`, donde `factor` es el valor de la curva `growth` de la unidad (`early`, `late` o `linear`) para el nivel L en `/data/levels.json`.
- `speed_px_per_tick`: cuánto camina por tick en los niveles 1 y 50; es `speed / 32`. Las que vuelan usan la misma velocidad.
- `flying`, `ranged` (su ataque normal es un proyectil), `attack_interval_ticks` (tiempo entre ataques), `attack_reach_px` (ataca cuando la separación entre los bordes de las dos unidades es menor que esto) y `radius_px` (su cuerpo es un círculo de ese radio).
- `head_px`: la altura de su cabeza sobre su punto; la barra de vida y los íconos de estado van encima.
- `up_cost` (lo que ocupa de la capacidad del equipo) y `squad_size` (cuántas unidades trae un escuadrón).
- `attack_used_by`: para qué sirve su estadística `attack`. `normal_attack`, su ataque normal. Las que nunca atacan (`attack_reach_px` 0) traen `dance_spells` (Isaac: solo lo usan los hechizos de baile, que golpean con su ataque completo menos un cuarto de la defensa del objetivo) o `nothing` (los magos, los jefes de la historia y el Aldeano: no se usa nunca). `attack_note` lo explica en inglés y en español, y dice cómo hace daño la unidad. `/data/units.json` trae los dos campos de cada unidad.
- `element` es el elemento de la unidad, y `description` el texto del juego sobre ella, en inglés y en español.

`/data/levels.json` tiene, por nivel: `exp_to_next` (la experiencia para subir al siguiente), los factores de las tres curvas (`growth`, sobre 128) y `lineup_up`, cuántos UP de unidades admite el equipo en ese nivel de Isaac.

## Movimiento e IA

`/data/ai.json` describe cómo se mueven las unidades y qué hacen por su cuenta. Sale de leer el código del juego (`evidence: static`); no se observó en un emulador.

- `movement`: las rutas son rectas (`route: straight`), sin búsqueda de caminos, con hasta `route_points_max` puntos; una unidad llega a un punto cuando está a menos de `point_reached_distance_px`. Un grupo enviado a un punto se reparte alrededor: cada unidad va al punto más su dirección desde el centro del grupo × (su radio + `group_spacing_extra_px`). Una unidad camina `velocidad / 32` px por tic, sin aceleración; los obstáculos y las demás unidades la empujan hacia afuera.
- Objetivo: cada `retarget_every_ticks` tics, en los estados `states_with_target_search`, busca enemigos a menos de `search_px` y elige el más cercano, el de más vida o el de menos vida (estas dos prefieren a los que están a su alcance).
- `orders`: cada orden (`order`, con las clases del juego en `classes`) fija un estado de decisión (`state`):
  - `wait` con modo 0 (estado 0) ataca sin perseguir a quien entre en su alcance + `attack_in_place_extra_px`; con otro modo (`state_other_mode`, 1) persigue dentro de su radio de visión.
  - `move` sigue la ruta sin atacar; `move_auto` ataca de paso sin salirse; `move_attack` persigue dentro de su radio de visión o sigue la ruta; `move_wait_auto` sigue la ruta, persigue y, detenida, espera.
  - `guard` (estados 9 y 10) vigila un punto: `guard` dice cuándo vuelve (`distance > sight`) y hasta dónde (`distance <= sight / 2`).
  - `escape` (11) huye en línea recta mientras la distancia entre centros es menor que la `keep_distance` de la unidad (`/data/units.json`).
  - `take_crystal` (8) va al cristal de maná de otro bando más cercano a menos de `order_list.take_crystal_radius_px` y se queda hasta que es de su bando.

  El radio de visión es el `sight` de cada unidad, y las misiones lo cambian.
- `unresolved_states`: los estados que existen pero de los que no se sabe todavía qué orden los produce.

### Órdenes de las unidades en las batallas

Cada unidad de una batalla de `/data/maps.json` trae `orders`, su lista de órdenes; vacía, la unidad no recibe ninguna al empezar. `unit_orders` de `/data/ai.json` describe cada orden: `state` es el estado en que deja a la unidad (`null`: no lo cambia), `target` cómo da el punto y `ends` cuándo termina.

- Puntos: `x`, `y` es un punto del mapa en px; `dx`, `dy` se suman a la posición de la unidad cuando la orden empieza. La unidad camina en línea recta hasta él.
- `wait`: `ticks` (`null`: para siempre); `chase: false` es el estado 0 (ataca sin moverse), `true` el 1 (persigue dentro de su visión).
- `back`: vuelve `steps` órdenes atrás, nunca antes de la primera. Así se repiten las patrullas.
- `target_nearest`, `target_most_hp`, `target_least_hp`: con qué regla elige objetivo desde ahí. `set_sight`: su radio de visión pasa a `sight_px`.
- `change_max_hp`: su vida máxima pasa a `floor(máxima × percent / 100)`; `set_max_hp`: a `hp`. Su vida actual pasa a ese mismo valor, sin superar la máxima que tenía.
- `guard` no termina nunca: la lista se queda ahí.

Cómo corre la lista (`order_list`), un paso por tic (`steps_per_tick`):

1. Empieza en la primera orden. El tic en que una orden empieza no cuenta para terminarla.
2. `ends`: `at_once` termina el mismo tic en que empieza; `route`, el primer tic después de que la unidad llega al punto (al llegar queda en el estado `route_end_state`); `ticks`, cuando lleva `ticks` tics contados después del de inicio, en el tic siguiente; `crystal`, cuando el cristal es de su bando, o al empezar si no hay ninguno tan cerca; `jump` es `back`; `removed`, como `at_once`, y además la orden sale de la lista.
3. La orden siguiente empieza en el tic siguiente. Con `wait` de `ticks` N, la siguiente empieza N + 2 tics después de que empezó la espera.
4. `back` cuenta solo las órdenes que siguen en la lista: `removed_when_done` ya no están.
5. Al terminar la última, la unidad se queda en el estado en que estaba y no recibe más órdenes.

Ejemplo, el primer Hongo bruma de la batalla 1: `change_max_hp` 75 (sale de la lista), `move` a (344, 153), `wait` 30, `move` a (344, 133), `wait` 30, `back` 4. Patrulla entre los dos puntos y se queda en cada uno los 30 tics de la espera más los tics en que la lista avanza; no ataca al caminar y ataca sin moverse al esperar.

## Duelo

`/data/duel.json` describe el modo duelo, en el que juegan dos personas. Sale de los datos y el código del juego (`DB/chrSelScnDat.cssd`, `DB/eloRate.elo`); no se observó en un emulador.

- `presets`: los 8 equipos predefinidos que se pueden elegir. Cada uno tiene `level` (el nivel del jugador, 40), `element_levels` (los seis niveles de elemento, que el juego limita a 1–50) y `squads`, tres escuadrones (`unit_id` de `/data/units.json`, `name`, `slug` y `level`). El cartucho no les da nombre: se numeran del 1 al 8.
- `rating`: la puntuación de las partidas por Wi-Fi, entre `min` y `max`. `rows` tiene, por fila, el límite de la diferencia (`difference`, puntuación propia − la del rival) y los puntos que suma una victoria (`win`), un empate (`draw`) o una derrota (`loss`). Con diferencia negativa se usa la primera fila cuyo límite es mayor o igual que la diferencia; con cualquier otra, la última fila cuyo límite es menor o igual. Después la puntuación se mantiene entre `min` y `max`. `elo_k` vale 50 porque las 53 filas coinciden, a menos de un punto, con el sistema Elo de K = 50: `K × (resultado − 1 / (1 + 10^(−diferencia/400)))`, con resultado 1, 0,5 o 0.

## Reglas del juego

`/data/rules.json` tiene los límites del juego, leídos de su código:

- `team`: `squads_max`, los escuadrones que entran con Isaac, nunca más que los puntos de inicio de escuadrón del mapa; `player_units_max`; `formation_px`, dónde empieza cada unidad de un escuadrón respecto de su punto de inicio (la y crece hacia abajo); `capacity_up_by_isaac_level`, los UP que admite el equipo en cada nivel de Isaac, desde el nivel 1.
- `roster`: `monsters_max`, los monstruos que se pueden guardar, y `captures_per_battle_max`.
- `items`: `inventory_max`, `found_level_max` (el nivel más alto de un objeto encontrado) y `found_per_battle_max`.
- `levels`: `level_max` de Isaac y los monstruos, `element_level_max` y `enemy_level_max`.
- `experience`: `per_kill_per_level` (cada unidad que muere da ese número × su nivel), `battle_max` y `level_1_to_max`.
- `battle`: `default_time_limit_s`, el límite cuando la misión no fija otro.
- `new_game_plus`: los niveles que suben los enemigos en cada capítulo (del 1 al 5, con el tope de `levels.enemy_level_max`) y los niveles extra de los objetos de los cofres (con el tope de `items.found_level_max`).
- `checkpoints`: cuántas misiones y escenas del mapa y cuántas batallas libres toman un punto de control al empezar.

Las reglas que no son un número:

- Isaac siempre está en el equipo y no lleva objetos. Cada escuadrón lleva un objeto, que afecta a todas sus unidades, y tiene tantas unidades como el `squad_size` de su especie.
- El UP solo se comprueba al armar el equipo, no en la batalla.
- Solo se capturan unidades enemigas comunes, no jefes ni aliados. Se suman al ganar, con su nivel de la batalla y 0 de experiencia. Se permiten especies repetidas en la lista y objetos repetidos en el inventario; con la lista llena, la nueva reemplaza a una que no esté en un escuadrón o se descarta.
- Da experiencia toda unidad del bando enemigo o aliada del mapa que muere, la mate quien la mate; las capturadas no dan nada. Al ganar, Isaac y cada escuadrón reciben el total, sin repartirlo.
- Una derrota o un empate descartan todo lo ganado en la batalla. Al continuar, la historia y las runas vuelven al punto de control, que se toma cuando una misión, escena o batalla libre empieza desde el mapa; nivel, monstruos y objetos se conservan, y la misión se repite desde el principio.
- Retirarse desde la pausa descarta la batalla y lleva al título, sin continuar.
- En la nueva partida+ Isaac conserva nivel, experiencia, niveles de elemento, runas, monstruos, equipo y objetos; la historia vuelve a empezar con batallas más difíciles.
- La pantalla de resultados, solo al ganar, muestra en orden: experiencia y niveles de elemento, objetos encontrados, monstruos capturados y hechizos nuevos. No hay rangos ni notas.
- No hay dinero ni tienda, ni un máximo de unidades en el campo o de hechizos activos. Isaac puede lanzar en cuanto termina la animación del hechizo anterior; el límite es el MP.

## Objetos

`/data/items.json` tiene los 8 objetos. Cada escuadrón lleva uno, que se aplica a todas sus unidades al empezar la batalla; Isaac no lleva ninguno.

`icon` es el ícono de 16 × 16 px que muestran los menús. Depende de la estadística que mejora el objeto, así que varios lo comparten. En la batalla no se dibuja ningún ícono de objeto: los cofres se ven siempre igual.

`pictures` son los íconos de cada objeto en los niveles 1 a 6, hechos con IA para este sitio: no están en el juego. `sheet` es la hoja de 120 × 160 px y `by_level` da la celda de 20 × 20 px de cada nivel (`x`, `y`, `w`, `h`). Desde el nivel 2 el ícono lleva un aura de otro color, y desde el nivel 4, destellos. Para verlos nítidos, escálalos en múltiplos enteros con `image-rendering: pixelated`.

- `stat`: la estadística que cambia (`hp`, `speed`, `attack`, `interval`, `accuracy`, `evasion`, `defense` o `magic_defense`).
- `bonus_by_level`: el valor *v* en los niveles 1 a 10, sobre 128. Los cofres solo dan niveles 1 a `items.found_level_max` de `/data/rules.json`.
- `effect`, cómo se aplica *v* a la estadística *x* de la unidad:
  - `multiply`: `x + floor(x × v / 128)`. Con `hp`, la unidad además empieza con la vida llena.
  - `reduce`: `x − floor(x × v / 128)`, como mínimo 1.
  - `double`: `x × 2`, en cualquier nivel.
  - `gap_to_100`: `x + floor((100 − x) × v / 128)`.
- `in_battle` (Escudo vi., Ojo cazador, Pelliza áng. y Escama drag.; `null` en los demás): qué cambia el objeto en la batalla, en `en` y `es`: `what` (qué es la estadística), `examples` (ejemplos con unidades de nivel 1), `good`, `bad`, `caution`, `level` (cuando el nivel del objeto no importa) y `anchor` (la sección de `/logic/combat/` con la regla). `examples` trae los mismos ejemplos como números: `accuracy`, `evasion` y `hit_percent` como [sin el objeto, con él].
- `chests`: los cofres de la historia que pueden dar el objeto: `battle` (el mismo número que en `/data/maps.json`), `map`, la posición `x`, `y` en píxeles del mapa, `chance` (0 a 1), `levels`, la probabilidad de cada nivel, y `new_game_plus_levels`, la misma probabilidad en la nueva partida+.

El nivel 6 casi no sale en la historia: los cofres dan hasta nivel 5, salvo un cofre de Cuerno dem. en el mapa 038 (1,1 %). En la nueva partida+ el juego suma `new_game_plus.chest_level_bonus` (2) al nivel del cofre, con tope `items.found_level_max` (6), así que los cofres de nivel 4 y 5 dan nivel 6.

Cómo deciden un golpe la precisión y la evasión (las estadísticas de cada nivel están en `/data/units/<slug>.json`; el Ojo cazador duplica la precisión del atacante y la Pelliza áng. la evasión del defensor, en cualquier nivel del objeto):

- Cuerpo a cuerpo acierta cuando un número al azar del 0 al 100, más la precisión, menos la evasión, llega a 100: una probabilidad de `clamp(precisión − evasión + 1, 0, 101) / 101`.
- Un disparo acierta cuando un número al azar del 0 al 99 es menor que `(precisión − evasión) & 0xff`: el juego guarda un solo byte de la diferencia. Así que acierta siempre si la evasión supera a la precisión, y desde una diferencia de 256 vuelve a empezar desde 0 (un tirador con el Ojo cazador puede acertar menos). Sale de leer el código del juego (ov1 0x2130f40); no se probó en un emulador.

`godot` dice cómo funcionará cada objeto en nuestro port a Godot (diseño aprobado, versión 1), aparte de los campos del original: `same_as_original`, `effect` y `bonus_by_level` como arriba (más `add`: `x + bonus_by_level`, en puntos), `line_up_shows_effect` (la pantalla de formación muestra el efecto de los 8), el texto en `en` y `es` (`same`, igual que `same_as_original`; `change`, qué cambia y por qué, o «Igual que el original»; `examples`; `notes`) y, en el Ojo cazador y la Pelliza áng., `examples` como números: `hit_percent` sin el objeto, `by_level` con el objeto en los niveles 1, 3, 5 y 6, y `original_hit_percent` con el objeto del original. En Godot:
- Ojo cazador: `multiply`, precisión × (1 + v/128), como el Cuerno dem.
- Pelliza áng.: `add`, 4 puntos de evasión por nivel (`v × 40 / 128`, redondeado).
- Un disparo acierta con una probabilidad de `clamp(precisión − evasión, 0, 100) / 100`: con la evasión mayor, falla siempre.
- Los demás objetos, los niveles (1 a 6, +2 en nueva partida+) y quién lleva objetos quedan como en el original.

Al cargar la batalla, el juego sortea el objeto y el nivel de cada cofre. El objeto es para la unidad del jugador que abre el cofre y se recibe al ganar. En la nueva partida+, el nivel sube 2, con el mismo tope.

`/data/chest.json` es el cofre como sprite:

- `closed` es la imagen cerrada que dibujan los mapas, puesta en el punto del cofre más (`dx`, `dy`).
- `sheet` tiene `cells` celdas iguales de `cell.width` × `cell.height` px en `columns` columnas. El punto del cofre está en `cell.origin` de cada celda.
- `animations`: `idle0` (cerrado), `open` (se abre), `loop` (abierto con el premio), `suka` (humo) y `emptybox` (vacío). Cada `frames` es una lista de `cell` y `ticks` de 1/60 s. `loop` dice si se repite, y `preview` es un GIF o PNG para verla.
- `flow`: con premio, el juego pasa de `idle0` a `open` y queda en `loop`; sin premio, de `idle0` a `emptybox`. `suka` tiene `used_by_game` en `false`: el juego nunca la elige.

## Hechizos y runas

### Runas

`/data/runes.json` tiene las 18 runas: `id` (1 a 18), `element`, `level` (1 a 3), su imagen, `spell` (el hechizo que lanza sola) y `learned` (las misiones donde se aprende). Isaac lanza un hechizo dibujando de 1 a 3 runas en orden mientras mantiene L (R con la opción para zurdos); soltar el botón termina el canto. Cada runa es un solo trazo: levantar el lápiz la cierra.

`templates` son los trazos que el juego acepta para cada runa: puntos en una cuadrícula de 0 a 63 (y hacia abajo), en el orden en que se dibujan. `from_dot` marca los que empiezan en el punto de la imagen de la runa; los demás son el mismo dibujo desde otro inicio o en el otro sentido.

Para reconocer un trazo:

1. Guarda un punto por fotograma mientras se toca la pantalla. Después del primero, guarda solo los que avanzan al menos 1 px en x respecto del último guardado: el juego no mira el avance en y. Un trazo de menos de 4 puntos cancela el canto.
2. Toma un cuadrado centrado en el trazo, de lado `max(ancho, alto)`, así que la proporción se conserva. Simplifícalo: conserva los dos extremos y, tramo a tramo, el punto más lejano de la cuerda si está a `max(1, 2 × lado / 64)` px o más, hasta 40 puntos. Pásalo a la cuadrícula de 0 a 63 con `(p − origen) × 64 / (lado + 1)` y quita los puntos repetidos seguidos.
3. Para cada punto k ≥ 1 calcula `angle[k]`, la dirección del segmento que llega a él, y `ratio[k]`, qué parte del largo total es ese segmento. El punto 0 toma la dirección del primer segmento.
4. Compara el trazo (I, con nI puntos) con cada plantilla (P, con nP puntos) recorriendo las dos líneas a la vez por su largo:

```
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. Gana la plantilla de mayor puntuación (de 0 a 1); con empate, la primera. Si llega a `accept` (0,70), el trazo es su runa. La rotación no se tolera: unos 30° de giro ya quedan en el límite.

Una runa reconocida además tiene que estar aprendida, las runas dibujadas hasta ahí tienen que formar el comienzo de algún hechizo, no se puede pasar del límite de runas por hechizo (`runes_per_spell`: las misiones que permiten 2 y 3) y tiene que alcanzar el MP de esa runa. Si algo falla, el canto se cancela.

La **calidad** del hechizo es el promedio de las puntuaciones de sus runas. Si llega a `great` (0,85), se le suma 0,5: el factor va de 0,70–0,85 a 1,35–1,50.

En `/es/logic/runes/practice/` se practica el trazado con este reconocedor, portado a TypeScript con la misma aritmética entera: dice la runa, el puntaje, si pasa `accept` o llega a `great`, el factor y las tres plantillas más cercanas. `/data/rune-strokes.json` trae 640 trazos de prueba con lo que da el reconocedor del juego en cada uno; un port tiene que dar lo mismo. Cada uno es `samples` (un punto por fotograma a 60 Hz, en píxeles de la pantalla táctil de 256 × 192) y `kept` (cuántos guarda la captura); con menos de 4, `cancelled` es `true`; si no, `top` son las tres plantillas más cercanas como [runa, plantilla, puntaje], con la plantilla numerada como en `templates` de `/data/runes.json` (las runas en orden) y el puntaje sobre 4096: se acepta desde 2868 y es excelente desde 3482.

### Hechizos

`/data/spells.json` tiene los 396 hechizos:

- `name`: el nombre del cartucho en inglés y en español. Los españoles suelen venir abreviados (ninguno pasa de 12 caracteres, por ejemplo «Torbell. f.»); el sitio los conserva tal cual, sin ampliarlos ni traducirlos, y muestra el inglés al lado.
- `runes`: sus runas, en orden. `element`: su elemento, que es el de su última runa: 294 de los 396 combinan runas de distintos elementos.
- `kind`: la familia (`cloud`, `wall`, `bullet`, `blast`, `falling_objects`, `aura`, `zone`, `unit_effect`, `object`, `dragon`). `variant`: el comportamiento concreto dentro de la familia.
- `mp_by_rank`: el costo en MP con el elemento en el rango 0 a 4. El rango es `floor((nivel del elemento − 1) / 10)`. El costo es la suma de sus runas: `mp_per_rune[posición][rango del elemento de esa runa][nivel de la runa − 1]` de `/data/runes.json`. Cada runa se cobra al dibujarla.
- `targeting`: cómo se apunta (`circle`: centro y radio; `line`: inicio y final; `fixed_circle` y `point`: un punto; `direction`: sale de Isaac hacia donde se suelta; `unit`: una unidad; `instant`: sin apuntar). `targets`: `enemies`, `allies` o `all`; es `null` en los tres que no tocan ninguna unidad (Transport y Flight mueven a quien los lanza, Restore solo cambia el suelo). `numbers.cast_range_px` es la distancia máxima desde Isaac.
- `effect_group`: solo en `unit_effect` (`null` en las demás familias), qué hace sobre las unidades. `heal` recupera vida o quita estados: la bandera `heal`, un estado de grupo `cure` o `regeneration`, o las variantes que pasan vida al propio bando (`life_thread`, `life_shine`, `light_transfer`). `boost` pone un estado a aliados y `weaken` a enemigos: todo estado que el juego lanza a aliados los ayuda y todo el que lanza a enemigos los perjudica. `other` es el resto: sin estado ni curación, o un estado a los dos bandos. Es la clasificación del filtro de la página de hechizos, no un campo del cartucho.
- `power`: con `power_scaled`, la potencia al lanzarlo es `floor(floor((power + magia de quien lo lanza) × factor de calidad) × (rango + 4) / 4)`; sin `power_scaled`, se usa `power` tal cual. Con la bandera `heal`, cura.
- `status`: el estado que puede causar: su registro (`id`), `group`, `chance` (con rango 0 y factor 1; se multiplica por el factor, por `(rango + 4) / 4` y por el factor de elemento de `elements.status_chance` de `/data/combat.json`), `duration_ticks` y `power`.
- `terrain`: cómo cambia el terreno (`heat` vuelve magma el suelo, `harden` endurece el magma, `freeze` congela el agua, `dry` seca el pantano en arena, `swamp` vuelve pantano la arena movediza, `restore` lo restaura).
- `terrain_overlay`: lo que el hechizo dibuja sobre el mapa mientras vive su zona, `lasts_ticks` (1200, 20 s). La zona es un círculo: `area.radius_px`, o un círculo de `area.per_object_radius_px` por cada objeto que cae. Se pintan las celdas de 8 × 8 px cuyo centro cae dentro del círculo. `paints` es la capa que pone y sobre qué superficie (`on`); `image` son sus 32 teselas animadas, cada cuadro de `frame_ticks` (`null`: fija). `clears` son las capas que borra dentro de su círculo mientras vive, aunque las haya puesto otro hechizo, antes o después. Así se anulan los opuestos: Storm borra el magma de Fire Rain y Fire Rain borra el hielo de Ice Maiden, y `restore` borra todo. Aparece de golpe y, al terminar la zona, desaparece sin animación: no hay animación de entrada ni de salida, y los cuadros de `frame_ticks` son un bucle de toda la capa. Es solo el dibujo: el efecto sobre las unidades lo dan los estados del terreno. `on_any_map` dice si esa superficie existe en algún mapa. `dry` pinta `mf04` sobre pantano, pero ningún mapa tiene pantano (`on_any_map: false`): en el juego solo borra el pantano que deja `swamp`.
- `flags`: `piercing` atraviesa, `homing` persigue, `rapid` ráfaga, `spread` abanico, `lock_on` fija cada objetivo, `flying_only` y `walking_only` limitan a quién afecta, `knockback` empuja, `knockdown` derriba.
- `numbers`: los números de su familia, en tics de 1/60 s y en px.

### Familias de hechizos

`/data/spell-families.json` agrupa los 396 hechizos por el código del juego que los ejecuta: la clase y el subtipo de efecto de sus datos. El hechizo genérico sobre unidades (`direct`) se divide por función (`heal`, `damage` o `status-only`) y grupo de estado, y Prank, Joke y Trick van juntos como `tub`. Cada familia tiene `page` (su página en el sitio, `/logic/families/<family>/`, o `/es/logic/families/<family>/` en español: sus tipos y sus hechizos en una matriz elemento × variante (filas como Nivel I, Rápido o Rey, y «Otros» para los que no encajan) cuando la mayoría encaja, o en tarjetas, cada uno con su animación y un panel con todos sus datos; `/logic/families/` es el catálogo con todas), `spells` (los `slug` de `/data/spells.json`), `same` (los campos iguales en todos, con su valor) y `differs` (los que cambian). Los campos son los de `/data/spells.json`: `numbers.<nombre>` y `status.<campo>` van aplanados, y `runes` es cuántas tiene. `power` solo se compara dentro de una familia, porque cada una lo usa distinto. `note` (EN/ES), cuando existe, dice cuándo conviene cada uno, solo donde la investigación lo sostiene; sale de leer el código, no de jugar. Las familias de un solo hechizo tienen `same` y `differs` vacíos.

`total_effect` dice, por hechizo, lo que recibe una unidad durante todo el hechizo, con la referencia fija de `total_effect_reference`: quien lo lanza tiene magia 30 (Isaac en nivel 1), el elemento en rango 0 (nivel 1–10) y un trazo perfecto (calidad 1, factor 1,5); quien lo recibe tiene defensa y defensa mágica 0, elemento neutro y ni debilidades ni escudos; sin el factor aleatorio. `kind` es `damage`, `heal`, `status` u `other`; `hp` es el daño o la curación en vida cuando lo hay (si no, `null`); `text` (EN/ES) dice cómo se suma o qué hace. Las reglas: el daño por golpe es `floor(floor((power + 30) × 1,5) × (0 + 4) / 4)`, salvo en las nubes, que golpean con su `power` tal cual `numbers.hits` veces; quemadura y veneno suman `status.power` cada 60 tics, `floor((status.duration_ticks − 1) / 60)` veces, si el estado entra (la pasada de estados de la unidad descuenta la vida antes de cada golpe), y la regeneración cura una vez más porque la primera es al instante; la probabilidad del estado es `status.chance` × 1,5, como mucho 1. `up_to` marca un máximo: le llegan todos los disparos u objetos (ráfagas y objetos que caen), o la unidad se queda donde el hechizo la alcanza. El veneno normal solo avanza mientras la unidad se mueve o ataca. El dragón atraviesa a la unidad, sigue 192 px y vuelve, así que a una unidad quieta le pega en cada pasada: `numbers.passes_max` como mucho, `passes_typical` en la mayoría de los sitios, `passes_min` en el peor. Una armadura le pega a un enemigo que se queda dentro de su pulso `numbers.touches_max` veces con el poder y `numbers.aura_ticks_max` veces con `status.power`. Una danza son `numbers.hits_max` golpes de `numbers.hit_attack` (el ataque de Isaac en nivel 1, contra defensa 0). El torbellino pega una vez por contacto y solo persigue unidades que tiene delante, así que vuelve a pegarle a una unidad solo si esta vuelve a cruzarse en su camino. `confirmed: false` marca lo que el código leído no resuelve; desde el #256 ningún hechizo lo tiene.

Cada familia trae `description` (EN/ES): una frase sin números sobre lo que hace, armada desde sus datos con una plantilla por `kind` o `variant` y los efectos de al menos la mitad de sus hechizos («algunos también…» suma hasta tres de los demás). Lo que hace cada hechizo, calculado de sus datos y nunca escrito a mano, va en dos niveles: `tags`, efectos concretos (`damage`, `heal`, `protect`, `empower`, `haste`, `weaken`, `burn`, `poison`, `freeze`, `sleep`, `charm`, `pacify`, `immobilize`, `knock`, `capture`, `summon`, `movement`, `terrain`), y `types`, sus ocho grupos generales (`attack`, `heal`, `buff`, `debuff`, `control`, `summon`, `movement`, `terrain`). Los `tags` y `types` de arriba del archivo traen el nombre (EN/ES), el criterio y, en cada etiqueta, su `type`. Las reglas: `damage` si su efecto total es daño (o `switch_life`); `heal` con `heal` en `flags`, un efecto total que cura, un estado `cure` o `regeneration`, o `light_transfer`; el estado que aplica, con todas las ramas de un estado `random`, da el resto: un cambio de estadística hacia arriba protege (defensa, defensa mágica, evasión), fortalece (ataque, magia, precisión) o acelera (velocidad, o menos `interval`), y hacia abajo frena (velocidad; toda la velocidad o una burbuja inmovilizan) o debilita (lo demás, debilidad, virus, rebote, olvido); quemadura y veneno son `burn` y `poison`; sueño, congelación y encanto, los suyos; no atacar y terror, `pacify`; la furia (`berserk`) fortalece a un aliado y, lanzada a un enemigo, cuenta como `charm`. `knockdown` y `knockback` son `knock`; las trampas de captura, `capture`; `kind` `wall`, `object` o `dragon`, `summon`; `variant` `transport`, `gather` o `self_cannon`, `movement`; `terrain` o un estado de terreno, `terrain`. Holy Pulse no aplica un estado propio: fortalece a los aliados y debilita a los enemigos. En cada familia, `tags` y `types` listan los de sus hechizos, el más común primero, con `spells` = cuántos lo tienen; `spell_types` da los de cada hechizo.

### Mejores hechizos

`/data/best-spells.json` dice qué hechizos le convienen al jugador, calculado a partir de los datos (`evidence: static`), no de jugar. El efecto de cada hechizo es `hp_expected`: el efecto total de `/data/spell-families.json` con la misma referencia, con la quemadura y el veneno contados por su probabilidad (en las nubes, la probabilidad de que caiga al menos una vez en sus golpes). `spells` trae por hechizo su `mp`, `chapter` (el capítulo en que Isaac ya aprendió todas sus runas y puede encadenar esa cantidad), `stroke`, `reach_px` (el mayor entre el alcance para lanzarlo y el del disparo), `area_px` (el radio, si tiene) y `badges`: `per_mp`, `jump`, `easy` y `not_worth`.

- `rankings.per_mp`: efecto por MP al menos el doble de la mediana de su familia (`vs_family`), o efecto al menos el doble de la mediana de los demás hechizos de su familia con el mismo MP (`vs_same_mp`).
- `rankings.jumps`: el siguiente hechizo por MP de la misma familia y elemento, con efecto ×2 o más y `effect_ratio / mp_ratio` ≥ 1,5.
- `rankings.not_worth`: otro hechizo de su familia y elemento (`better`) cuesta menos MP y es igual de bueno en todo; si `gains_only` no está vacío, el más caro solo gana en esos números.
- `rankings.easy`: todos los hechizos, del trazo más fácil al más difícil.
- Según la situación, solo los hechizos que hacen daño: `grouped` (con área, por efecto sobre cada unidad dentro), `scattered` (sin área que golpee también a los aliados; por `mp_per_kill` = MP × lanzamientos para quitarle la vida al enemigo débil, después por trazo y después por alcance) y `boss` (por efecto sobre una unidad). El enemigo débil (`reference.weak_enemy`) tiene la mediana de la vida de las unidades sin nombre de las batallas de los capítulos 1 y 2, a su nivel.

El puntaje de trazo (`method.stroke`) es una estimación de qué tan difícil es dibujar una runa a mano, no un dato del juego. Para cada forma que acepta la runa (`templates` de `/data/runes.json`, rejilla de 0 a 63 = un ancho de runa) se quitan los puntos a menos de `min_step` del anterior y se suma: el largo en anchos, `w_sharp` por cada esquina de más de `sharp_deg` grados, `w_turn` por cada vuelta completa (la suma de los ángulos de giro / 360°) y `w_against` por cada ancho que el trazo sube o va a la izquierda. La runa toma su forma más fácil (`runes[].template`, `runes[].points`) y el hechizo suma sus runas más `w_extra_rune` por cada runa después de la primera. El cuarto más fácil de los hechizos lleva la insignia `easy`.

Hechizos con reglas propias, por `variant`:

| `variant` | Regla |
|---|---|
| `bomb_man` | Pone una bomba en cada unidad objetivo, de los dos bandos. A los 180 tics, cada bomba viva golpea con la potencia (fuego) a toda unidad a 15 px, una vez por unidad, incluida la que la lleva |
| `dance` | Isaac camina al objetivo a 2 px/tic sin recibir daño (se rinde a los 180 tics). A 12 px baila 300 tics: golpea en los tics 9, 19 y 30 de cada ciclo de 43, con todo su ataque menos un cuarto de la defensa. Termina si el objetivo muere o se aleja más de 34 px |
| `light_transfer` | Daño de oscuridad; Isaac recupera el doble del daño causado, hasta 32 767 |
| `life_thread` / `life_shine` | Cura lo que al objetivo le falta, quitándolo en partes iguales a los demás aliados / a los enemigos, sin bajarlos de 1 de vida |
| `holy_pulse` | 600 tics: el bando de quien lo lanza gana 25/128 de ataque y el otro pierde 25/128 de defensa dentro del círculo. Relanzarlo reinicia el tiempo |
| `transport` | Quien lo lanza salta al punto si cabe (si no, falla); sin receptor ni impacto. Destello de salida al lanzarlo, destello de llegada a los 20 tics y cambio de posición a los 30; hasta entonces se le puede golpear donde estaba. Flight es el mismo salto con más alcance (250 px frente a 100) |
| `self_cannon` | Isaac vuela a 2 px/tic, hasta 240 tics; a 12 px, 620 de daño mágico de viento al objetivo (sin escalar) y 255 a Isaac |
| `free_lift` | Levanta al objetivo (no a Isaac ni a jefes) 64 tics; al caer, la potencia (viento) y quemadura fuerte a los enemigos a 40 px |
| `switch_life` | Intercambia la vida actual de Isaac y la del objetivo, cada una limitada a la máxima de quien la recibe |
| `gather` | Trae a cada aliado junto a Isaac, uno cada 6 tics, a ángulo al azar y a sus radios más 0–31 px |
| `death_gamble` | Elige al azar una unidad viva de cualquier bando tras 282 tics; recibe 2 × la potencia (fuego) y las demás a 50 px, la potencia |
| `prank`, `trick` | Una tina por objetivo; a los 20 tics quita exactamente 1 de vida, que cuenta como daño |
| `mine` | Cada mina dura 920 tics y estalla cuando un enemigo que camina entra en 14 px: potencia (tierra) y derribo a los enemigos a 14 px. Una mina nueva desarma las anteriores del bando |
| `capture_trap` | Atrapa al primer enemigo común en `trigger_radius_px`. Con la vida por debajo de `capture_hp_below` la captura siempre funciona; `intended_chance_*` no se usan. Una trampa nueva anula la anterior del bando |
| `elemental_wall` | Los postes no tienen vida; cada poste daña a quien lo toca como mucho una vez cada 120 tics |

## Escenas de hechizos

`/data/spell-scenes.json` (también `.md`) relaciona cada hechizo con sus gráficos y sonidos, sus jefes reales, las 90 apariencias de unidades y los 141 estados. La página de cada hechizo permite elegir lanzador, receptor, cuatro direcciones, distancia, calidad y resultado, repetir y activar el sonido. El receptor es aliado o enemigo según `targets`; los resultados y estados de ejemplo son selecciones del usuario, no tiradas de combate. Transport y Flight no tienen receptor ni resultado: su escena mueve al lanzador al destino.

- `assets[id]`: PNG, archivo CAN/NCER/NANR y paleta exacta; `cells` da rectángulos [x,y,w,h] de la hoja y `origins` su posición respecto al ancla. Un cuadro vacío sigue consumiendo su tiempo.
- `sequences[].frames`: [celda,tics,dx,dy,rotación en grados,escalaX,escalaY]. Incluye animaciones de un solo cuadro. `mode`: 1 una vez, 2 bucle, 3 ida y vuelta una vez, 4 ida y vuelta en bucle; `loop_start` es el cuadro inicial del bucle. `ticks` y `seconds` conservan la duración original.
- `units[].actions[acción][dirección]`: secuencia, `own` y `fallback`. Los monstruos sin conjuro o lanzamiento usan reposo. `launch_px` coloca el disparo. `boss_spells` identifica los hechizos que el jefe usa en batalla.
- `spells[].graphics`: función, hoja, secuencias, paleta, evidencia y confianza. `times_seconds` convierte los campos de tiempo del hechizo. Las distancias siguen en px; un proyectil tarda distancia/velocidad, redondeada al tic siguiente.
- `sounds`: audio, ancla (chant, stroke, rune, cast, release, shot, hit, status, death, vanish, capture, capture_close), desplazamiento en segundos y tics, condición y confianza. `timing_confidence` separa la atribución del sonido de su colocación de ejemplo. Rune/stroke se repiten por runa; hit por disparo. Calidad <0,85 usa KAKUNIN/INVOKE; ≥0,85 usa MENU_MAGIC/INVOKE2. El sonido se activa mediante interacción.
- `statuses[].overlay`, `popup`, `sounds` y `duration_seconds` describen el estado. `/data/statuses.json` también incluye este objeto como `visual`.
- Derribo usa solo la bandera 0x800: 40 tics en el suelo y 10 al levantarse; empuje, solo 0x400 y desacelera 30 tics. Derrota reproduce die, espera 30 tics, usa la nube 272 para monstruos y elimina después de 80. La captura resuelta usa `capture_timing`: cierre exitoso a los 2 tics, retirada de la unidad a los 33, o cierre fallido/liberación a los 60; las secuencias de libro 199/200 determinan el final, sin prolongarlo por la vida de la trampa armada.

Los muros (clase 1) crean estructuras en el terreno, sin receptor ni resultado de impacto. `structure_timing` conserva los 5 tics entre pilares; `targeting` distingue línea y círculo. Los estados elementales se aplican por contacto, no al colocar el muro. El visor permite ajustar la longitud de la línea.

La orientación de los proyectiles está en `graphics[].rotation`: el gráfico original apunta arriba y la ROM añade 90° al ángulo de movimiento. La estela hereda ese ángulo; oscuridad y luz giran 16,875°/tic. La fase inicial aleatoria se fija a 0 en la vista. El manifiesto tiene `revision` para evitar reutilizar datos de una exportación anterior en el navegador.

La composición de la escena es ilustrativa: no reproduce un combate completo. `preview` declara el ritmo de dibujo (0,8 s por runa) y las convenciones; `confidence`/`evidence` distinguen lo leído del código de lo probable. El seguimiento `extraccion/investigacion/visuals/SCENE_DISPATCH.md` confirma por código los sonidos de conjuro/trazo, el icono del byte 7 y las tablas de Fallthing. Las entradas cero omiten ese sonido. Las demás familias usan sus propias llamadas; `fall` y `object_hit` se repiten por objeto, con descenso de 20 tics como convención de la vista.

`visual_behavior` distingue efectos compartidos (Holy Pulse: icono 11; Life Thread/Life Shine: icono 7), movimiento de unidad (Self Cannon: `unit_motion`, 2 px/tic, separación de contacto 12 px) y estados condicionales (Betrayal/Mutiny/Uprising: `choices`). `dance` (los seis bailes, 361–366) marca que quien baila es Isaac: camina hasta el receptor a `approach_px_per_tick` (2), baila a su lado `numbers.duration_ticks` (300; está en el registro del hechizo, como en `/data/spells.json`) cuando la separación es `start_gap_px` (12) o menos y golpea en los `hit_ticks` (9, 19, 30) de cada ciclo de `cycle_ticks` (43): 21 golpes. El receptor no baila. El aura del elemento nace donde está Isaac (`aura_origin`); `aura_pending` dice lo que falta decodificar. `caster_teleport` (Transport y Flight) marca el salto del lanzador: destello de salida al lanzar, destello de llegada a los `arrival_effect_ticks` (20) y cambio de posición a los `move_ticks` (30), sin receptor ni impacto. Sus probabilidades son condicionadas al éxito; el visor permite escoger una rama sin simular la tirada. `own_graphic_confirmed` se refiere al dispatch del informe original, no a la presencia de feedback compartido.

Quedan pendientes la composición visual de explosiones/dragones y el derribo en juego. La sincronización de fases, la colocación absoluta de la fase de Missile, los donantes de vida y el daño propio de Autocañón no se reconstruyen completamente. El seguimiento `extraccion/investigacion/visuals/SCENE_RENDERING.md` añade `dragon_assembly` (tabla de 14 índices, aparición 23+índice y escalas), `explosion_assembly` (núcleo, aro y partículas de carga) y `lock_timing` (8/16/24 tics). La trayectoria del dragón y las posiciones preliminares de Explosion siguen siendo ilustrativas. Cómo termina cada efecto está en `extraccion/investigacion/visuals/SCENE_ENDINGS.md`: `rain_assembly` (los 6 hechizos con `MagicEffectRain`) da 20 nubes, 10 gotas con su ciclo de alfa (`drop_alpha`) y 10 salpicaduras, y `removed_after_hit_ticks` (63) es cuando el dueño se borra y se lleva todo a la vez. En las demás familias de explosiones y objetos que caen no está decodificado cuándo se retira el efecto; la escena lo quita 120 tics después del impacto. Los 27 hechizos de terreno llevan `terrain_scene`:
- `map` y `origin` dicen en qué mapa del juego y en qué punto se pone la escena, para que se vea el terreno sobre el que actúa;
- `zone` dice cuándo nace la zona: al impactar (`hit`), en cada objeto que cae (`object_hits`) o al aparecer el área (`release`);
- `radius_px` y `lasts_ticks` (1200) dan su círculo y su duración;
- `prior`, en los que solo quitan, es el recubrimiento previo que la escena pinta para que se vea qué quitan.

`terrain` trae la regla de cada material, la capa que elige el juego, las tablas de teselas por vecinas (`tiles`), las hojas de cada capa con sus tics por cuadro y la grilla NHIT de cada mapa usado (detalle en `extraccion/investigacion/terrain_morph/STATIC_REVIEW.md`). `sounds[].duration_ticks` corta el conjuro al lanzar y el trazo al terminar cada runa; los clips `*-sustain` conservan muestras/instrumentos de la ROM con una retención de 3 s elegida para la vista. Continuar reanuda las notas sostenidas con su desplazamiento y duración restante. Ninguno de estos hallazgos está verificado en emulador.

## Apuntar un hechizo

`/data/aiming.json` dice qué muestra la batalla entre el final del canto y el lanzamiento, según el `targeting` del hechizo (ver "Hechizos y runas"). Está leído del código (`evidence: static`), no medido en un emulador; `confidence` dice qué tan seguro es cada punto. Posiciones en px de la pantalla de abajo (256 × 192), tiempos en ticks de 1/60 s y ángulos en grados con 0 = derecha y 90 = abajo (la y crece hacia abajo).

- **Modos** (`modes`). Cada `targeting` tiene su icono (`icon`) y su forma de confirmar (`confirm`). `arrow` dice si usa la flecha; `range_graphics_3d`, si dibuja algo en el suelo (círculos o sombra). `direction` no dibuja nada en el suelo: solo la flecha y el icono.
- **Flecha** (`arrow`, solo `direction`: Fireball y los demás disparos). La imagen `image` (32 × 56) apunta hacia arriba. Su punto `pivot_px` (15, 36), el centro del aro, va sobre el punto de Isaac (`anchor: caster_point`, sus pies) y la imagen gira alrededor de ese punto. La punta queda `tip_px` (36) px delante de Isaac y el mango `tail_px` (15) px detrás, así que la flecha sale de debajo de él. Ángulo de dibujo = `atan2(dy, dx)` en grados + `rotation_offset_deg` (90), donde (dx, dy) va del punto de Isaac al lápiz, girando en el sentido de las agujas del reloj en pantalla: la flecha apunta siempre al lápiz. El giro es libre (`turn_steps` 4096 por vuelta), sin pasos de 4 u 8 direcciones. No se estira ni cambia de tamaño con la distancia (`scale` 1) y es opaca (`alpha` 1). Se dibuja debajo de Isaac (`layer: under_caster`). Colores: los de la imagen, iguales para todos los elementos.
- **Cuándo** (`direction_flow`). La flecha aparece en cuanto empieza el apuntado, apuntando hacia arriba (`rest.end_offset_px` (0, −10): −90°). Cuando el lápiz toca la pantalla (en cualquier parte), cada tick la flecha apunta al lápiz e Isaac se gira hacia él en 4 direcciones (derecha, abajo, izquierda, arriba, cortes a 45°). Al levantar el lápiz se lanza el hechizo desde Isaac hacia el último punto del lápiz, y la flecha y el icono desaparecen. No hay límite de distancia (`range_limit: null`): el disparo recorre su `shot_range_px` de `/data/spells.json`. Antes de tocar, pulsar el botón del canto (L, o R con la opción para zurdos) cancela: suena `SE_CANCEL` (efecto 4) y no se lanza nada; una vez tocado, ya no se cancela.
- **Icono** (`icon`). Mientras se apunta, el icono del modo (32 × 32, punto en su centro) va fijo en la pantalla en `at_px`: (20, 172), abajo a la izquierda, o (236, 172) con la opción para zurdos, encima del campo. Si la flecha apunta hacia esa esquina (entre los ángulos de `move_when_arrow_deg`: 90–180 para diestros, 0–90 para zurdos) salta a `moved_at_px` (236, 20 o 20, 20) para no quedar bajo la mano.
- **Círculo** (`circle_flow`, `targeting: circle`). Fuera del alcance (`cast_range_px` desde Isaac) el campo se sombrea con `shade_outside_range` (#525252, alfa 20/31). Un toque fuera del alcance no cuenta; uno dentro fija el centro. Arrastrando se elige el radio, desde `radius.min_px` (5) hasta el `radius_px` del hechizo; al soltar se lanza con ese centro y ese radio. El área se colorea según `targets` (`area_colour_by_targets`: rojo enemigos, azul aliados, amarillo todos; `area_colour_other` para los hechizos que lista) y con `area_colour_at_max_radius` al llegar al radio máximo. Forma (`area`): un disco de 24 lados centrado en el centro elegido, del radio arrastrado, alfa 24/31. Mientras el radio no llega al máximo lleva cuatro triangulitos (`handles`) a 4 px del borde, de 5 px de largo y 8 de base, que apuntan hacia fuera y giran para que uno apunte al lápiz; al llegar al máximo desaparecen. La sombra de fuera del alcance solo se ve antes de tocar.
- **Instantáneo** (`instant_flow`). Cualquier toque lanza el hechizo; el botón del canto cancela.
- **Línea** (`line_flow`, muros como Fire Wall). El primer toque tiene que caer estrictamente dentro del alcance; fija el inicio. Si cae fuera no pasa nada. Desde ahí el extremo sigue al lápiz, con un largo máximo `max_length_px` del hechizo. Si el lápiz sale del alcance, el extremo se queda donde la recta del inicio al lápiz corta el borde del alcance: la línea entera queda dentro. Isaac se gira hacia el lápiz solo antes de fijar el inicio. Al soltar se lanza del inicio al extremo (la misma llamada que la flecha). Se dibuja (`graphics`, color #e6b552, alfa 28/31) un disco de 8 px de radio en cada punta y una banda de 8 px de ancho entre ellas, que sobresale 4 px por cada lado. La sombra de fuera del alcance se ve siempre.
- **Punto** (`point_flow`) y **círculo fijo** (`fixed_circle_flow`) son el mismo código. El primer toque tiene que caer estrictamente dentro del alcance. Después el punto sigue al lápiz y, si el lápiz sale del alcance, se queda en el borde, en la dirección del lápiz. Isaac se gira hacia el lápiz. Al soltar se lanza en el punto. `point` dibuja la retícula (`reticle`, 16 × 16 px a escala 1,5: 24 × 24 en pantalla, centrada en el punto, encima del campo) y ningún área. `fixed_circle` dibuja en el punto un disco de 24 lados del radio del hechizo (`radius_px`, `trigger_radius_px` o `ring_radius_px` según la clase), siempre en #ffa5a5 alfa 24/31 y sin triangulitos, porque el código lo dibuja con el radio máximo. Solo mostraría la retícula con radio 0, y ningún hechizo lo tiene. La llamada de lanzamiento recibe además un radio fijo de 20 px (`cast_radius_arg_px`); no se sabe para qué lo usa. La sombra de fuera del alcance se ve siempre.
- **Unidad** (`unit_flow`). Cualquier toque empieza. Un cursor sigue al lápiz; si el lápiz sale del alcance, el cursor se queda en el borde. Isaac se gira hacia el lápiz. Cada tick se elige la unidad más cercana al cursor entre las que permite `targets` y cuya caja de toque contiene el cursor. Caja: en x, la x de la unidad ± (`touch_box_by_unit[slug][0]` + 4 px); en y, desde la y de la unidad − (`[1]` + 4 px) hasta la y + (`radius_px` de la unidad + 4 px). Se dibuja un marcador de tres líneas (`marker.lines_px`, px desde su punto) en rojo #ff5252 sobre la unidad elegida, también con hechizos para aliados, o en amarillo #ffff52 sobre el cursor si no hay ninguna. Al soltar se lanza sobre esa unidad. Si no había unidad, vuelve a esperar un toque y el botón del canto vuelve a cancelar. La sombra de fuera del alcance se ve siempre.
- `cross` (una cruz) se carga para todos los modos pero ninguno la muestra. `numbers` son cifras del mismo archivo que no se usan al apuntar: son las de los números de daño (ver "Números de daño").

## Estados alterados

`/data/statuses.json` tiene los 141 registros de estado; el `status.id` de un hechizo apunta a uno. `duration_ticks` es cuánto dura (0: actúa una vez) y `caused_by`, los hechizos que lo causan. La intensidad de los que hacen daño o curan es la `status.power` del hechizo. `key` dice qué hace:

| `key` | Efecto |
|---|---|
| `stats` | Aplica `changes` mientras dura |
| `virus` | `changes`; las unidades a `spread_px` de una víctima original se contagian por el tiempo que le quede |
| `berserk` | `changes`; ataca a su objetivo e ignora las órdenes. Cancela `charm` |
| `burn`, `strong_burn` | Daño de fuego igual a la intensidad cada `every_ticks`, el primero tras `every_ticks`. No funciona en vados; el daño de agua la apaga; quita `freeze` y `bubble`. `strong_burn` reemplaza a `burn` |
| `poison` | Daño de oscuridad cada `every_ticks`, solo mientras la unidad se mueve o ataca |
| `vile_poison` | Daño de oscuridad cada `every_ticks` |
| `regeneration` | Cura la intensidad cada `every_ticks`, la primera al instante |
| `pulse` | Daño mágico de `element` igual a la intensidad al instante y tras `every_ticks` |
| `wall_contact` | Al tocar un poste: daño de `element` (`heal`: cura) igual a la intensidad del muro, como mucho una vez cada 120 tics por poste |
| `freeze`, `long_freeze` | No ataca ni se mueve; el daño de fuego la termina |
| `sleep`, `deep_sleep` | No ataca ni se mueve; cualquier daño la despierta |
| `bubble` | No ataca ni se mueve hasta recibir 2 golpes |
| `charm` | Pierde sus órdenes y pelea contra su propio bando. Cancela `berserk` |
| `terror` | Huye de la unidad que la amenaza |
| `forget` | Se queda quieta |
| `no_attack`, `no_move`, `no_damage` | No ataca / no se mueve / no recibe daño |
| `invincible` | No recibe daño, no ataca, no se mueve y no se la puede empujar |
| `hidden` | Se desvanece en 127 tics |
| `recoil` | Cada golpe que da le cuesta `share` × el daño que hace |
| `weakness`, `halving` | ×2 / ×½ el daño mágico de `element`; una anula a la otra |
| `cure` | Quita `removes` (`all`: todos los estados y cambios de estadísticas; `stats`: cambios de estadísticas, maldiciones y auras) y aplica `changes` si los tiene |
| `terrain` | Marca el terreno con `change` |
| `random` | Aplica uno de `options` según su `chance` |
| `none` | Sin efecto |

Cada cambio de `changes` tiene `stat` (`attack`, `defense`, `speed`, `magic`, `magic_defense`, `interval` = tiempo entre ataques, `accuracy`, `evasion`) y `mode`: `percent` → `stat × (1 + amount)`; `flat` → `stat + amount`; `gap` → `stat + (100 − stat) × amount`.

- Los estados no se acumulan: aplicar uno otra vez reinicia su tiempo. Los cambios de estadísticas, las auras y las maldiciones sí, y se multiplican.
- Cada fotograma las estadísticas se recalculan desde las de la unidad aplicando sus estados, del más nuevo al más viejo, y se limitan: ninguna baja de 0; ataque, defensa, velocidad, magia e `interval` llegan como mucho al doble de las de la unidad (`interval` al menos 1); la defensa mágica, a 99; la precisión y la evasión, a 150 más que las de la unidad.
- Isaac y los jefes son inmunes a `berserk`, `charm`, `terror` y `forget`.

## Combate

La batalla corre a 60 tics por segundo. `/data/combat.json` tiene las constantes del código; las de cada unidad están en `/data/units/<slug>.json`.

**Ataque normal.** `gap` = distancia entre centros − `radius_px` de las dos unidades.
- Empieza a golpear con `gap < attack_reach_px` y sigue mientras `gap < attack_reach_px + attack.reach_pad_px`. Con `attack_reach_px` 0 no ataca nunca; al perseguir se detiene a `attack.stop_gap_px`.
- Ritmo: espera `attack_interval_ticks`, reproduce su animación `attack`; el golpe o el disparo sale a los `attack_strike_ticks` de empezarla; al terminar la animación vuelve a esperar.
- Acierto cuerpo a cuerpo: `azar(0..100) + precisión − evasión ≥ 100`. A distancia: `azar(0..99) < (precisión − evasión) mod 256` (una diferencia negativa acierta siempre).
- Daño: `d = floor(ataque / 2) − floor(defensa / 4)`; si `d ≤ 0`, nada. Con `attack_element`: ×½ contra el mismo elemento, ×1,5 contra el opuesto (`opposite`). Los disparos hacen ×1,5 a las unidades que vuelan. Al final `floor(d × (128 + r) / 128)`, con `r` entero al azar entre −10 y 10.

**Disparos** (`projectile_type` de la unidad, `projectiles` de `/data/combat.json`): salen de `shot_start_px` según hacia dónde mira, en línea recta hacia donde estaba el centro del objetivo, a `speed_px_per_tick`, sin perseguirlo. Recorren como mucho `attack_reach_px + reach_pad_px`. Impactan a la unidad enemiga más cercana que esté a `shot_hit_radius_px` + su radio; no atraviesan. Los obstáculos altos los detienen; el agua y los pozos no. `on_hit`: `stick` se queda `stick_ticks` sobre el objetivo, `vanish` desaparece. Si fallan, rebotan y se desvanecen en `shot_fade_ticks`.

**Visión.** Cada `search_every_ticks` la unidad busca enemigos a menos de `max(sight_px, search_px)` y persigue mientras `gap < sight_px`. Los guiones de misión cambian `sight_px` a menudo.

**Daño de los hechizos.** Con la potencia al lanzarlo P (ver «Hechizos y runas»), sobre cada objetivo:
1. Elemento: sin elemento, P; mismo elemento que el objetivo, `−floor(P / 8)`; opuesto, `floor(3P / 2)`.
2. Si es mayor que 0, `× (100 − defensa mágica) / 100` (defensa mágica ≤ 99); si es 0 o menos, se divide entre 2 (el mismo elemento cura alrededor de P/16).
3. `× (128 + r) / 128`, con `r` al azar entre −10 y 10.
4. Si el objetivo tiene más estados `weakness` que `halving` de ese elemento, ×2; si tiene más `halving`, ×½.
Los hechizos con `heal` suman P sin elemento, defensa ni azar.

**Elemento contra elemento** (`elements` de `/data/combat.json`). Cuenta el `element` de la unidad (`null`: sin elemento) contra el del golpe. `elements.spell_table` es el paso 1 de arriba, por elemento del hechizo (filas) y del objetivo (columnas): `-0.125` es el mismo elemento, que cura; `1.5`, el opuesto; `1`, el resto, y siempre que uno de los dos no tiene elemento. Un hechizo de fuego cura a una criatura de fuego en lugar de dañarla, sea cual sea su defensa mágica. Ninguna unidad es inmune al daño de un elemento.
- Ataque normal (`elements.attack`): ×½ contra el mismo elemento, ×1,5 contra el opuesto.
- Probabilidad del estado del hechizo (`elements.status_chance`): ×½ si el objetivo tiene el elemento del hechizo, ×1,5 si tiene el opuesto. Se compara el elemento del hechizo, no el del estado. Ningún estado tiene una excepción por elemento: una criatura de fuego se puede quemar y una de agua se puede congelar, con la mitad de probabilidad si el hechizo es de su elemento.
- Daño que hacen los estados (`elements.status_damage`): la quemadura es de fuego y el veneno de oscuridad, y pasan por los pasos 1 a 4. A una criatura de fuego, cada tic de quemadura la cura en vez de dañarla (con potencia 10, 1 o 2 de vida); a una de agua le hace ×1,5. El congelamiento lleva agua, pero no hace daño.

**Jefes.** Solo la unidad jefe de cada batalla lanza hechizos (`casts_spells` en su ficha), cuando su guion se lo ordena; no hay comprobación de alcance. Tiene su propio MP (su estadística `mp`), paga cada runa con rango 0 y su potencia es `power + magia`, sin calidad ni rango.

**Niveles de elemento de Isaac.** Empiezan en `growth.start`. Solo cambian en la pantalla de resultados de una batalla. Con `old` el nivel antes de la batalla, `c` los hechizos de ese elemento lanzados en ella (uno por hechizo, el elemento de su última runa; los cantos cancelados no cuentan) y `fav` el elemento lanzado estrictamente más veces (ninguno si hay empate):
- `old ≤ low_band_max`: +`low_band_many_gain` si `c ≥ many_casts`, +1 si `1 ≤ c < many_casts`; +1 más si es `fav`.
- `old ≤ mid_band_max`: +1 si `c ≥ many_casts`; +1 más si es `fav`.
- Si no: +1 solo si es `fav`, y entonces el elemento opuesto pierde 1 si su nivel era `decay_min` o más.
- Después, cada nivel se limita a `level_min`–`level_max`.
El nivel solo cambia el rango (`floor((nivel − 1) / 10)`), y el rango solo cambia el MP de las runas, la potencia (×(rango + 4) / 4) y la probabilidad de los estados (×(rango + 4) / 4).

## Barra de vida

`hp_bar` de `/data/combat.json` es la barra de vida que el juego dibuja sobre cada unidad en el campo, todo el tiempo. Son rectángulos de color sólido, sin imagen (`drawn_by_site: true`). Las medidas están en px desde el punto de la unidad (`x`, `y` de `/data/maps.json`) subido la altura de su cabeza, `head_px` de `/data/units/<slug>.json`: un rectángulo con `left_px` y `top_px` va de `(x + left_px, y − head_px + top_px)` a `width_px` × `height_px`.

1. `frame`: el marco, de color `frame.color`, debajo de todo. Lo que la barra no cubre se ve de ese color.
2. `bar`: la vida, de `floor(width_px × vida / vida máxima)` px de ancho, con la vida máxima efectiva (después de `change_max_hp`). Su color, `color_by_side`: `player` para el bando de Isaac, `enemy` para los enemigos, `ally` para los aliados y los personajes sin bando.
3. `change`: durante `change.ticks` tics después de cambiar la vida, otra barra de la misma posición y alto va entre el marco y `bar`. Solo cuenta el último cambio.
   - Tras un daño, `bar` muestra la vida nueva y la de abajo la de antes, en `damage_color.enemy` en los enemigos y `damage_color.other` en el resto.
   - Tras una curación, `bar` muestra la vida de antes y la de abajo la nueva, en `heal_color`.

La barra no sube con las unidades que se elevan del suelo. Una unidad vencida la conserva durante su animación `die` y la espera de 30 tics, y la pierde cuando empieza la nube con la que desaparece (`hidden_from: vanish`).

## Marca de enemigo

`/data/enemy-marker.json` es la letra pequeña que el juego dibuja justo a la izquierda de la barra de vida de cada enemigo. Marca el bando: solo la llevan los enemigos; Isaac, sus escuadrones y los aliados no llevan ninguna (`carried_by`).

- **Imagen.** `letters`: «E» con el juego en inglés, francés o español y «F» en alemán (`languages`; el juego toma el idioma de la consola y cualquier otro cuenta como inglés). Cada una mide 8 × 8 px (`size_px`), rosa y blanca, sin animación.
- **Posición.** Desde el punto de la unidad (`x`, `y` de `/data/maps.json`) subido la altura de su cabeza (`head_px`): el `pivot_px` de la imagen va en `(x + offset_px[0], y − head_px + offset_px[1])`, es decir `(x − 14, y − head_px − 6)`. Queda `gap_to_hp_bar_frame_px` (5 px) a la izquierda del marco de la barra de vida. Escala 1, opaca, delante de su unidad. Ejemplo (`example`): en un Hongo bruma (`head_px` 14) la imagen ocupa de `(x − 18, y − 24)` a `(x − 11, y − 17)`.
- **Cuándo se ve.** Mientras el enemigo esté en el campo y en pantalla, todo el tiempo; no depende de la selección ni del daño. Cada unidad tiene un contador de aparición de 0 a `fade_max` (127): entra al campo en 63 y sube 1 por tic hasta 127; mientras parpadea al aparecer, la letra se ve fija (`blinks: false`). Un enemigo con el estado `hidden` baja 1 por tic y la letra se va cuando llega a 0. Al morir, la letra sigue durante la nube de desaparición, aunque la barra ya no se dibuje, y se va cuando la unidad se quita.

## Cristales de maná

Los cristales de maná (pilares de MP) están en muchas batallas. Cada batalla de `/data/maps.json` trae `crystals`: la posición `x`, `y` en px del mapa y `owner`, quién lo tiene al empezar (`player`, `enemy` o `neutral`). Las reglas están en `mana_crystals` de `/data/combat.json`, en px y tics de 1/60 s:

- Es un círculo sólido de `radius_px` que detiene a las unidades que caminan, a las que vuelan y a los disparos. No tiene vida: solo cambia de dueño.
- **Captura.** Cada tic, cada unidad que puede capturar (`can_capture_crystals` en `/data/units/<slug>.json`), viva y a menos de `capture_radius_px`, suma `gauge_per_unit_per_tick` al medidor. Cuando nadie suma, el medidor baja `gauge_decay_per_tick`. Al pasar de `capture_gauge.neutral` (cristal de nadie) o de `capture_gauge.owned` (cristal con dueño), pasa al bando que lo toma, y el medidor vuelve a 0.
- **Bloqueo.** Nadie lo toma mientras una unidad de su dueño esté a menos de `presence_radius_px`. Un cristal de nadie tampoco avanza si hay unidades de dos bandos a esa distancia.
- **Bandos.** `sides_that_capture`: en la historia solo Isaac y sus escuadrones toman cristales; los enemigos nunca los toman ni los defienden.
- **Curación.** Las unidades de su dueño a menos de `heal.radius_px` recuperan `heal.hp` de vida cada `heal.every_ticks` mientras estén heridas.
- **MP.** Cada `mp_regen.every_ticks`, el MP de Isaac sube `mp_regen.base_mp`, más `mp_regen.mp_per_crystal` por cada cristal suyo, más 1 por cada aumento anterior seguido sin cantar, hasta `mp_regen.streak_max`. Sin cristales también recupera: la base y la racha. Después de un canto, el primer aumento llega a los `mp_regen.chant_wait_ticks`; con cristales, a los `chant_wait_ticks − chant_wait_cut_ticks_per_crystal × cristales`, con un mínimo de `chant_wait_min_ticks`.
- **Imagen.** `pictures` tiene una imagen por dueño, de `picture_size`. Su esquina superior izquierda va en el punto del cristal más `picture_offset`.
- **Barra de captura.** `capture_bar` describe la barra que el juego dibuja con rectángulos, sin imagen. `drawn_by_site` es `true` porque el sitio la redibuja a partir de estas reglas:
  - Mide `height_px` de alto y va a `above_px` sobre el punto del cristal.
  - El fondo, de color `colors.background`, empieza en `background.<neutral|owned>.left_px` y mide `width_px`.
  - Encima, desde el mismo borde y en `colors.remaining`, va lo que falta: `(capture_gauge − medidor) / remaining_divisor` px, con el medidor del último empuje.
  - Cada empuje la muestra durante `fade_ticks` ticks, con opacidad `ticks restantes / 2` sobre `alpha_max`.
- **Misiones.** El objetivo «¡Purifica los cristales!» se gana cuando todos son del jugador (`all_crystals` en `conditions`).

## Cámara de batalla

`/data/camera.json` tiene las constantes del código, en px del mapa y px por frame (60 por segundo). La vista mide `view_px` (256 × 192): es la pantalla de abajo, la táctil, donde el juego pone el motor gráfico principal (el 3D y los fondos del mapa). La de arriba la ocupa la sección «Pantalla de arriba» de abajo.

- **Posición.** Es la esquina superior izquierda de la vista. Va de 0 a `view_x_max_px` y `view_y_max_px` de cada mapa en `maps` (el tamaño del mapa menos la vista); cada movimiento se recorta a esos límites. Son los límites del mundo (`world_bounds.always`): fuera del mapa no hay nada, así que la cámara se detiene en cada eje al llegar al borde y sigue en el otro eje si todavía puede. El juego tiene una bandera para quitarlos, pero solo la escribe al iniciar la cámara, con 0; nada la activa. Las unidades y efectos se dibujan en `posición en el mapa − posición de la cámara`, en px enteros. Una unidad a más de 40 px fuera de la vista (`unit_draw_margin_px`) no se dibuja.
- **No sigue a nadie.** La cámara no se mueve sola cuando Isaac camina, ni sigue a los hechizos, a los objetivos ni a las criaturas capturadas. Solo la mueven los cuatro casos siguientes, sin suavizado ni zona muerta: velocidad constante y llegada exacta.
- **Al empezar la batalla** se centra en Isaac: lo deja en `battle_start_isaac_at_px` (128, 96) de la pantalla, recortado a los límites.
- **Cruceta.** Mientras se mantiene, la vista avanza `dpad.px_per_frame` (7) en esa dirección, o `dpad.diagonal_px_per_frame_per_axis` (4,9) por eje en diagonal. Con la opción de zurdo, A, Y, B y X hacen de derecha, izquierda, abajo y arriba. No hay retorno automático: la vista se queda donde el jugador la deja. No se mueve mientras se prepara o se canta un hechizo, ni cuando la misión desactiva la cruceta.
- **Canto.** Al mantener L (R con la opción de zurdo), si Isaac está fuera del rectángulo `chant_recentre.isaac_rect_px` de la pantalla (x 20–236, y 20–172), el canto espera y la vista va hacia él a `chant_recentre.px_per_frame` (10) en línea recta, hasta dejarlo en `target_px` (128, 96). Cuando falta menos de `jump_below_px` (10), salta al destino. Termina antes si Isaac entra en el rectángulo o si un borde del mapa detiene la vista. Durante el canto sigue corrigiendo igual.
- **Misiones.** El guion lee la posición de la vista y la mueve en línea recta a `(x, y)` a `velocidad × script_pan.px_per_frame_per_speed_unit` (velocidad / 4) px por frame; las misiones usan casi siempre 24, es decir, 6 px por frame. Al llegar devuelve que terminó. No vuelve sola a Isaac. Si el destino queda fuera de los límites, la vista se detiene en el borde.
- **Temblor.** Big Drop mueve la vista `shake.big_drop`: ±2 px en horizontal, cambiando de lado cada 2 frames, durante 30 frames, y luego la devuelve a 0. El efecto de terremoto (`earthquake_unused`, tres fases) existe en el código, pero ningún hechizo lo usa. El temblor no cambia la posición de la cámara, solo lo que se dibuja.
- **Pantalla de arriba** (`top_screen`). Depende del estado del canto. En los estados 0, 1 y 2 (sin canto) muestra el minimapa, que coloca cuatro marcas según la posición de la vista. Mientras se dibuja (estados 7 y 8) muestra la lista de runas: una cuadrícula de `rune_grid` 6 columnas, una por elemento, por 3 filas, una por nivel de runa, junto a una barra del MP de Isaac. Cuando el canto da un hechizo (estado 9) muestra ese hechizo: su número, su nombre y su descripción. Vuelve al minimapa en cuanto el estado vuelve a 0, 1 o 2. Que las runas no aprendidas salgan como «?» se vio en el juego, pero no se siguió en el código.
- **Fondos.** Cada capa del mapa recibe la posición de la vista. `BACK` (el mapa) y `ANIM` la usan tal cual. `NORM` la multiplica por `speed_factor` (`PARM` / 256): con modo 3 o 4 es un fondo detrás del mapa (cielo, mar, lava) que avanza a esa fracción; con modo 1 va delante. En un mapa con `ANIM`, la capa `NORM` no se muestra (`shown: false`). No se confirmó leyendo el código si el fondo se repite al llegar a su borde.

## Números de daño

`/data/damage-numbers.json` dice cómo se dibuja el número que aparece sobre una unidad cuando su vida cambia. Está leído del código (`evidence: static`); el color, la posición, la duración y el error de la centena se comprobaron además en el emulador con Fireball en la primera batalla. `confidence` dice qué tan seguro es cada punto. Posiciones en px de pantalla (la y crece hacia abajo) y tiempos en ticks de 1/60 s.

- **Cuándo** (`shown_when: hp_changes`). Cada vez que la vida de una unidad cambia, por un hechizo, un ataque normal, un disparo, un estado, el terreno o la curación de un cristal (`sources`), en cualquier bando (Isaac, sus escuadrones y los enemigos por igual). Se muestra el valor absoluto del cambio (`value.shown`), sin signo, hasta `value.max` (9999). Es el daño calculado, no lo que le quedaba de vida: un golpe de 347 a una unidad con 20 de vida muestra 347. Un cambio de 0 no muestra nada, y un fallo no cambia la vida, así que tampoco.
- **Un error del juego con 1000 o más** (`value.drops_zero_hundreds`). Las cifras se eligen así (`value.digits_drawn`): el millar si el valor pasa de 999; la centena solo si el resto (valor mod 1000) pasa de 99; la decena si el resto (mod 100) pasa de 9 o ya se dibujó una cifra; y la unidad. La centena no mira si ya hubo millar, así que un 0 en la centena se pierde: 1050 se ve «150», 1005 «105» y 2000 «200». Para dibujarlo como el DS hay que copiar ese error.
- **Cifras** (`font`). Solo los dígitos 0–9, en celdas de 8 × 8 px con su punto en (4, 4), el centro. Son las mismas en los cuatro idiomas. Las imágenes de `kinds.<tipo>.image` son tiras de 80 × 8 con el dígito d en x = 8 × d.
- **Color** (`kinds`). Depende solo del signo, no del bando ni del elemento: `damage` (vida perdida) es rosa/rojo `#de1852` con borde blanco; `healing` (vida ganada) es verde `#18bd73` con borde blanco. Daño a Isaac o a un aliado se ve igual que daño a un enemigo.
- **Dónde** (`place`). El centro del número va en el punto de la unidad (sus pies) más `radius_px` de esa unidad hacia abajo (`/data/units/<slug>.json`). Se calcula en cada tick, así que sigue a la unidad si se mueve. No se dibuja mientras la unidad no se dibuja (más de 40 px fuera de la vista).
- **Cómo se reparten las cifras** (`layout`). Con `n` cifras, el centro de la cifra `k` (desde 0, de izquierda a derecha) va en x = `−(3n − 3) + 6k` respecto al centro del número, y en y = 0. Están a 6 px (`advance_px`) aunque cada celda mida 8, así que los bordes blancos se pisan: cada cifra se dibuja encima de la anterior.
- **Movimiento** (`timing`). Ninguno: no sube, no rebota, no cambia de tamaño ni se desvanece. Aparece entero, se queda `lasts_ticks` (40, dos tercios de segundo) y desaparece de golpe. Si el golpe mata a la unidad (`timing.killing_blow`), la cuenta se detiene mientras muere: el número se queda hasta que la unidad desaparece en su nube (unos 190 ticks con el Misty Mush de la primera batalla).
- **Varios a la vez** (`stacking`). Cada unidad tiene un solo número. Si su vida vuelve a cambiar antes de que se borre, el nuevo valor lo reemplaza y los 40 ticks vuelven a empezar; no se suman ni se apilan. Los números de unidades distintas son independientes.
- **Capa** (`layer`). Se ordena con el resto del campo como si estuviera 20 px más abajo que la unidad: queda delante de ella y de lo que esté a menos de 20 px por debajo.
- **Sonido.** El daño (no la curación) suena con `damage_sound` (`SE_UNIT_DAMAGE`, efecto 50).
- **Ejemplo** (`example`). Fireball le quita 347 a un Misty Mush (`radius_px` 6): tres cifras rosas con sus centros en (−6, 6), (0, 6) y (6, 6) respecto a los pies del hongo, durante 40 ticks. `example.image` es ese número ya armado (20 × 8), con su centro en `point_px` (10, 4).

## Pantalla del canto

`/data/chant-screen.json` tiene los gráficos de la pantalla del canto, leídos del código (`evidence: static`), no medidos en un emulador. Las posiciones son px de la pantalla (256 × 192), los tiempos ticks de 1/60 s y los alfas van de 0 (transparente) a 1 (opaco; el DS usa 0 a 31).

- **Cuándo.** El círculo y la capa oscura aparecen al empezar a dibujar y desaparecen de golpe al terminar el dibujo, junto con los trazos. No siguen a la cámara: quedan fijos en la pantalla de abajo.
- **Capa oscura** (`dim_layer`). Un rectángulo de toda la pantalla, color `colour` (#525252), mezclado con alfa normal sobre el campo. Su alfa empieza en `alpha_from` (10/31) y sube 1/31 cada `step_every_ticks` (2) ticks hasta `alpha_to` (20/31): llega en 20 ticks. Es lo que tiñe el campo; el círculo no lo tiñe.
- **Círculo** (`circle`). La imagen `image` (256 × 192, paleta propia naranja) va con su punto `pivot_px` (128, 79) en `centre_px` (128, 96); el dibujo ocupa `art_box_px` de la imagen, así que su centro cae en (128, 103) de la pantalla. Se dibuja opaco (alfa 1) encima de la capa oscura. Al aparecer, durante `appear.ticks` (30) crece desde casi 0 a `scale_per_tick` (0,0332) por tick y gira `turns_per_tick` (1/30 de vuelta por tick): una vuelta completa. En el tick 31 queda en escala 1 y ángulo 0, y se queda quieto. El sentido del giro no se confirmó. `fade_unused` es una salida con giro y fundido que está en el código pero el juego no usa.
- **Trazo** (`stroke`). Cada trazo es una cinta de `width_px` (8) de ancho alrededor de los puntos del lápiz (uno por tick, hasta `max_points`), con remates redondos que pasan `cap_px` (4) del primer y del último punto. Se pinta con la textura `texture` (32 × 32: borde `rim` amarillo, centro `core` blanco) reducida al ancho de la cinta: la mitad izquierda de la textura es el remate del inicio, la derecha el del final, y las columnas del centro se estiran a lo largo. El color de la textura se multiplica por el del trazo (canal a canal, en 0..31: `(textura + 1) × (trazo + 1) / 32 − 1`) y se mezcla con alfa.
- **Color del trazo.** Mientras se dibuja es blanco y opaco: se ve la textura tal cual. Cuando un trazo se reconoce como runa, en `rune.ticks` (11) ticks cada canal baja `step31` por tick hasta el color de su elemento (`by_element`, en 0..31) y el alfa baja de 31 a 20 (`alpha_to`); se queda así. Un trazo que no es runa (`not_a_rune`) sigue blanco y baja 1/31 de alfa por tick hasta 1/31 en 30 ticks.
- **Lista de runas** (`rune_list`, pantalla de arriba, mientras se dibuja). Encima del fondo `background`, que ya trae un «?» en cada celda y el riel de la barra de MP, van los iconos de los seis elementos (`elements`) y una imagen de 32 × 32 por runa aprendida en su celda (`cells`: columna = elemento, fila = nivel). Una runa no aprendida no dibuja nada y se ve el «?». Una aprendida que Isaac no puede pagar con su MP actual muestra `no_mp` (una X) en lugar de la runa. `preview` es la lista con las 18.
- **Números.** Cada runa que se puede pagar lleva su costo en MP, `cost.digits` (2) cifras en `cost.offset_px` (18, 18) desde la esquina de su celda; el costo es el de `mp_per_rune` de `/data/runes.json` para la próxima runa del canto. El MP actual de Isaac va con 3 cifras en `mp.at_px` (217, 45). La fuente de esas cifras no está en estos datos.
- **Barras de MP** (`bars`). Dos imágenes de 64 × 64 (gris y amarilla) con su punto `pivot_px` (29, 64) en `at_px` (224, 155): crecen hacia arriba y, llenas, miden `full_height_px` (88) de alto. Las dos siguen el MP de Isaac; cuál queda delante no se confirmó.
- **Destello** (`sparkle`). Una tira de cuadros de 32 × 32 con su duración en `ticks`, que se pone sobre la celda de la runa que se acaba de dibujar.
## Fin de la batalla

`/data/battle-end.json` dice qué muestra el DS desde que una batalla se decide hasta que termina la pantalla de resultados. Está leído del código (`evidence: static`), no medido en un emulador; `confidence` dice qué tan seguro es cada punto. Px de pantalla (256 × 192 cada una) y ticks de 1/60 s.

- **Resultados** (`outcomes`). Un disparador de la misión termina la batalla con una de cuatro acciones (`action_id`, el número de acción de los scripts de misión). Cada una fija la palabra que sale en el campo (`word`), la canción (`song`) y si sigue la pantalla de resultados (`results_screen`). `GameClearStart` (3) es una victoria: WIN y canción 11. `GameOverStart` (2) es una derrota: LOSE, o TIME OVER (`word_when_time_limit`) si el disparador fue el límite de tiempo. `GameLostClearStart` (5) muestra LOSE pero cuenta como victoria. `GameEvenStart` (6) es un empate: DRAW. Todas menos la victoria ponen la canción 12.
- **La batalla se detiene** (`battle`). Desde ese tick ninguna unidad se mueve, piensa ni ataca y no corre ningún disparador; las unidades se siguen dibujando. No hay pose de victoria. Un canto en curso se cancela y su sonido (`stops_sound`, `SE_MAGIC_EISHO`) se corta. La batalla espera `settle_ticks` (30) y a que terminen los efectos en curso.
- **Cartel** (`banner`). Luego empieza la canción y corre el cartel, en la pantalla de abajo, fijo a la pantalla en `at_px` (128, 96); cada imagen de `words` (una por idioma, según el de la consola: francés, alemán, español; cualquier otro, inglés) va con su `pivot_px` sobre ese punto. En español las palabras son VICTORIA, DERROTA, EMPATE, FIN DEL TIEMPO e INICIO.
  1. Durante `ticks.circle_only` (31) solo se ven el círculo mágico y la capa oscura del canto, entrando como cuando Isaac canta (`/data/chant-screen.json`, `circle` y `dim_layer`). La palabra todavía no se dibuja.
  2. La palabra aparece, opaca, durante `ticks.shown` (31) y se queda `ticks.held` (61) más.
  3. Después la palabra se va de golpe (el código quiere desvanecerla, pero su alfa empieza en 0; visto en el emulador) y durante `ticks.circle_leaving` (31) el círculo se va (`circle.exit`): gira 24° por tick y su alfa baja 1/31 por tick.
  4. En `ticks.total` (154) el círculo y la capa oscura desaparecen a la vez.
- **Después.** Con la canción 12 la batalla además espera a que esa canción termine (`waits_while_song_plays`; suena una vez, 4,287 s), así que una derrota dura más que una victoria. Al final las dos pantallas funden a negro (`fade_out`, 30 ticks). Tras una victoria viene la pantalla de resultados; tras una derrota o un empate, Continuar (sección «Reglas del juego»).
- **INICIO** (`banner.start`). Antes de jugar la batalla muestra START igual, salvo que la misión llame antes al nativo 46. La primera batalla (`qc01_m001`) lo llama, así que empieza sin cartel; su versión de nueva partida+ sí lo muestra.

### Pantalla de resultados

`results`. Solo sigue a una victoria. La canción `results.song` (10, `SEQ_QUESTCL`) empieza con ella y suena hasta el final. Tiene hasta cuatro fases en el orden de `results.phases`; una fase sin nada que mostrar se salta. Cada fase entra desde negro y sale a negro (30 ticks). Tras la última viene el título del capítulo, si hay uno pendiente, o el mapa del mundo.

**Experiencia** (`results.exp`), siempre. `previews` trae las dos pantallas armadas con estos campos, en inglés y en español, sin el texto. La fuente de los menús del juego no está decodificada: los textos van con caracteres de ancho fijo de 7 px desde su punto, en una fuente a elección.
- Pantalla de arriba: `top.background` (ya trae el retrato de Isaac, el recuadro LV y tres filas oscuras). Isaac: la etiqueta LV en `isaac.lv_label_at_px` (la `_fr_es` en francés y español), su nivel como `%2d` en `level_text_at_px`, la barra de EXP en `exp_bar_at_px` (`bar_frame_64`, luego `bar_back_64`, luego `bar_fill` estirada a `exp_bar_fill_px` (62) × EXP / EXP para el siguiente nivel). Cada escuadrón del equipo, en el orden de la lista, 3 como mucho: el icono de su unidad en `squads.icon_at_px`, la placa, su nombre, su nivel y su barra a los desplazamientos de `squads` desde el icono (barra de 46 px, `bar_frame_48` y `bar_back_48`). Los niveles de elemento no salen en la pantalla de arriba.
- Pantalla de abajo, página 1 (`bottom.pages` 1): las cuatro `bars` en su `at_px` y los `texts`: `clear_time` el tiempo de batalla en segundos (como mucho su límite) como `hh:mm:ss`; `defeated` los derribados; `survival` los monstruos de escuadrón vivos × 10000 / los desplegados, como `%3d.%02d%%`, o `---.--%` sin escuadrones; `exp_pool` la EXP que falta repartir, que baja hasta 0.
- Pantalla de abajo, página 2 (`bottom.pages` 2, 192 px más abajo): el radar de elementos. Los seis iconos en `radar.icons` y dos hexágonos traslúcidos centrados en `radar.centre_px`, un vértice por elemento (arriba luz, arriba a la derecha fuego, abajo a la derecha tierra, abajo oscuridad, abajo a la izquierda agua, arriba a la izquierda viento). Rojo: radio `floor((nivel + 10) × 68 / 50)`; verde: `floor(rango × 10 × 68 / 50)`, rango `floor((nivel − 1) / 10) + 1`. El verde va delante del rojo (visto en el emulador); su alfa no está decodificado.
- Cronología (`waits_ticks` 30, 60, 30, 60):
  1. Espera 30 ticks.
  2. Conteo, cada tick: `paso = max(1, floor(bolsa / 80))`, bolsa − paso, e Isaac y cada escuadrón reciben `paso`. El sonido `count_step` (18, `SE_KASAN`) suena cada tick; cada nivel ganado suena `level_up` (19, `SE_LEVELUP`) y muestra `level_up` («UP!», «¡SUBE!» en español; sus `frames` lo mueven `dy_px`) en `level_up_at_px` de Isaac o sobre el icono del escuadrón. Barras, niveles y `exp_pool` cambian cada tick. En 0 aparece el botón Siguiente y la pantalla espera.
  3. Espera 60 ticks y la página 2 sube a la vista en 64 ticks (3 px por tick).
  4. Espera 30 ticks y los niveles de elemento pasan de los viejos a los nuevos (regla de crecimiento en «Reglas del juego») en 30 ticks, redibujando los hexágonos cada tick; `element_step` (20, `SE_ZOKUSEI`) suena en los ticks 0, 10 y 20. Aparece Siguiente y la pantalla espera.
  5. Espera 60 ticks y funde a negro.
- Controles. El botón Siguiente (`next_button`, por idioma) está en (232, 168), o en (24, 168) con la opción para zurdos; actúa al levantar el lápiz a menos de 20 px de ese punto. Pulsar A, B, X, Y o la cruceta también cuenta como Siguiente. Durante el conteo, cualquier toque o uno de esos botones da el resto de golpe (cada miembro recibe `ceil(bolsa / miembros)`, un defecto del juego) y se salta ese Siguiente. Un toque durante el desplazamiento salta a la página 2.

**Objetos y monstruos** (`results.getting`): una pantalla por objeto encontrado y luego una por monstruo capturado, en el orden en que la batalla los consiguió. Pantalla de abajo: el fondo (`item` o `monster`), el título (texto 70 «¡Encontraste un objeto!» o 75 «¡Has capturado un monstruo!», centrado en x 128, y 24), la fila del hallazgo en `found_row_at_px` (su fondo `button_images` 11 para objetos, 10 para monstruos; icono, nombre 16 px a la derecha, LV y nivel) y los dos botones de `buttons.options` (104 × 48, textos 71/72 o 76/77). «Añadir» lo agrega a la lista; con la lista llena el título pasa a 73/78 y Siguiente abre la lista (`item_list`/`monster_list`, 6 filas cada 18 px, `list_images`, flechas de página 30/31) para elegir cuál reemplazar; no se pueden elegir objetos equipados ni monstruos en un escuadrón. «Tirar»/«Soltar» pregunta con `buttons.confirm` (SÍ 8, NO 9). Solo táctil; un botón actúa al levantar el lápiz dentro de él. La pantalla de arriba muestra el nombre y la descripción del objeto (`item_top`, `item_top_window`).

**Hechizos nuevos** (`results.magic`): una pantalla por hechizo nuevo, primero los aprendidos y luego los solo vistos (sus runas salen como «?»). Abajo: `background` y una fila en `row_at_px` (32, 76): barra del color del elemento (`images` 9–14), número `%03d:`, nombre 32 px a la derecha y runas en 132, 152 y 172 px (`images` 15–32, «?» 34). Arriba se ve el hechizo como en la pantalla de arriba de la batalla. Siguiente o A/B/X/Y/cruceta avanza.

`/data/battle-end.md` trae los mismos datos en tablas.

## Pantalla de título

`/data/title-screen.json` es el menú que sigue a TOUCH TO START: el logo arriba y cinco botones abajo. Está leído del código y comprobado en un emulador (`evidence: static_and_emulator`); `confidence` dice qué tan seguro es cada punto. Px de pantalla (256 × 192 cada una, desde la esquina superior izquierda) y ticks de 1/60 s.

- **Fondos.** `top.background` ya trae el logo y «©TAITO CORP. 2005, 2006»; `bottom.background`, el cielo, Isaac en la colina, el bastón y los pájaros. Los dos van en (0, 0) y no se mueve nada: los pájaros están pintados.
- **Botones** (`buttons`, en orden: New Game, Load Game, Multiplayer, Wi-Fi Connection, Hand Settings). Cada uno es el panel `panel` (128 × 24) con su texto (`label`, en, es, fr, de) centrado a `label.offset_px` (64, 4) de su esquina. `images` trae el botón ya armado en cada idioma, capturado del emulador: la fuente de los menús no está decodificada. El juego elige el idioma por el de la consola (francés, alemán, español; cualquier otro, inglés).
- **Entrada** (`entry`). Al aparecer el menú, las dos pantallas se aclaran desde negro en 30 ticks (`fade_in`) y, a la vez, cada botón va en línea recta de `from_px` a `to_px` en 30 ticks: New Game y Multiplayer desde la izquierda, Load Game y Wi-Fi desde la derecha, Hand Settings desde abajo. El paso por tick es `step_px`, truncado como hace el juego (`entry.position`), por eso New Game termina en x 29 y Multiplayer en x 40, 1 px antes que `table_to_px`. Hasta que todos se detienen, los toques no cuentan.
- **Toque** (`press`). Solo pantalla táctil: la cruceta y los botones no hacen nada aquí. Un toque que empieza dentro de un botón (`hit_box`, sin contar el borde) lo marca; mientras el lápiz sigue dentro, el botón se dibuja 2 px a la derecha y 2 px abajo y vuelve 1 px por tick al soltarlo. Levantar el lápiz dentro lo elige: suena `press.sound` (3, `SE_KETTEI`) y los demás dejan de responder. No hay sonido de cursor ni de cancelar.
- **Al elegir** (`choose`). Los botones no salen: las dos pantallas se oscurecen a negro en 30 ticks y el juego pasa a lo que dice `does`. New Game empieza el prólogo. Load Game abre la pantalla de carga aunque no haya partida guardada: sus tres casillas dicen «No Data». El menú nunca desactiva un botón; un botón desactivado tendría el texto gris (`label.colour_bgr555.disabled`) y no respondería.
- **Música.** La canción `music` (21, `SEQ_DM_TITLE`) la empieza la demo del título, antes de TOUCH TO START, y el menú no pone otra. Tocar TOUCH TO START suena 2 (`SE_CURSOR`), oscurece en 30 ticks y queda en negro unos 57 mientras carga el menú.
- **Quieto** (`idle`). Sin tocar nada no cambia nada y no vuelve a la demo.
- **Hand Settings** (`hand_settings`). No sale del menú: los cinco botones vuelven a su `from_px` en 30 ticks, Hand Settings regresa a su lugar y entran la pregunta («Left/Right handed?», `question`) y las dos manos (`hands`, de `chr/select_hand.can`). Tocar una mano suena 3, guarda 1 para la de la izquierda y 0 para la de la derecha, y vuelve el menú. Qué valor es zurdo no está confirmado.
- En el DS esta pantalla no tiene Opciones ni Salir.

`/data/title-screen.md` trae los mismos datos en tablas.

## Personajes

`/data/characters.json` lista los 19 personajes que hablan en la historia, con su nombre (`name`), cuántas líneas tienen (`lines`) y `faces`: una imagen por expresión, en el orden del juego. Las escenas usan estos retratos a través de `/juego/data.json` (ver "Escenas").

## Mapas de batalla y batallas

`/data/maps.json` lista cada mapa de batalla: la imagen completa (`image`), el minimapa del juego (`minimap`) y sus batallas (`battles`). Una batalla tiene su número (`battle`), las misiones de la historia que la usan (`missions`) y las unidades que están en el campo al empezar (`units`):

- `uid` y `unit` (el slug en `/data/units.json`) dicen quién es.
- `x`, `y` es su posición en el mapa. Con el `origin` del sprite en ese punto, las unidades quedan sobre el suelo transitable del mapa.
- `level`, y `named`: `true` para los personajes de la historia (jefes y aliados), `false` para los monstruos comunes.
- `orders`: lo que hace por su cuenta desde que entra al campo (ver «Órdenes de las unidades en las batallas»).

Cada misión de `missions` trae además `objective` (el texto que muestra el juego), `conditions` (cómo termina: `result` `victory`, `defeat`, `defeat_passed` —una derrota que cuenta como superada— o `draw`, cuando se cumple `trigger`: `no_enemies`, `boss_dies`, `isaac_dies`, `time` (`value` en segundos), `all_crystals`, `no_crystals`, `crystals_held` (`value` cristales), `isaac_reaches_area`, `boss_reaches_area`, `spell_cast` (`value` = id del hechizo), `chest_opened`, `capture`, `draw`…), `time_limit_s` (sin límite propio, 3600) y `music` (el `n` de la canción en `/data/audio.json`). Si la misión tiene varias batallas, la lista vale para todas.

`chests` son los cofres de la batalla: posición `x`, `y` y `odds`, lo que puede dar (`item` de `/data/items.json`, `chance` y la probabilidad de cada nivel en `levels`).

`seals` son los sellos de aparición del suelo de la batalla: punto `x`, `y` y `units`, los índices en `units` de las unidades ocultas que están sobre él. Cómo dibujarlos está en «Sellos de aparición».

`animation` existe en los mapas que el juego anima: la nieve que cae (juego de teselas `ac03`, 8 cuadros de 11 ticks) y la arena que corre (`ac04`, 4 cuadros de 7 ticks). Cada cuadro dura un tick más que el valor del archivo, porque el reloj del juego cuenta hasta 0 y cambia de cuadro en el tick siguiente. `base` es el mapa sin esa capa; `frames` tiene los cuadros apilados de arriba abajo, cada uno del tamaño de `base` y transparente donde no hay capa. Se dibuja el cuadro `i` encima de `base` durante `ticks[i]` ticks de 1/60 s y se repite. `image` es `base` con el primer cuadro. En los demás mapas, `animation` es `null`.

`decorations` son los objetos del juego que van sobre el mapa: puentes, copas de árboles y otros. Cada uno trae su punto `x`, `y` en px del mapa, `alpha` (opacidad de 0 a 31) y `height`: el juego los dibuja en orden de `y + height`. `frames` es una tira de cuadros de `frame_size`, apilados de arriba abajo; `origin` es el punto dentro de cada cuadro. El cuadro `i` se muestra `ticks[i]` ticks y se repite; con un solo cuadro, la decoración está quieta. `sprite` agrupa las que comparten dibujo.

`lava` existe en los mapas de magma (025–028, 044, 305): la lava brilla porque el juego cambia los colores de una fila de su paleta. `mask` es una imagen del tamaño del mapa: su rojo es el color (1–15) de esa fila en cada píxel, y 0 fuera de la lava. El cuadro `i` pinta esos píxeles con `palettes[i]` (16 colores `#rrggbb`; el 0 no se usa) durante `ticks[i]` ticks, y se repite. El cuadro 0 es la imagen del mapa.

`terrain` es la colisión y el terreno del mapa, en px del mapa (las formas pueden salir del borde). No hay cuadrícula: las unidades se empujan fuera de las formas cada fotograma.

- `boxes` (centro `x`, `y` ± `half_width`, `half_height`) y `circles` (`x`, `y`, `radius`), cada una con su `kind`:
  - `high_obstacle`: detiene a las unidades que caminan, a las que vuelan y a los disparos. `low_obstacle`: solo a las que caminan.
  - Zonas, solo para unidades que caminan (las que vuelan nunca se ven afectadas): `magma` y `damage_floor` hacen 50 de daño cada 60 tics (fuego y sin elemento); `ground` igual, solo si un hechizo lo calentó; `ford` mitad de velocidad salvo congelado, apaga y bloquea las quemaduras; `ice` nada, mitad de velocidad si un hechizo lo derrite; `bog` mitad salvo seco; `sand` nada, mitad si se vuelve pantano; `quicksand` empuja 0,25 px por tic hacia `push`.
  - El daño de 50 se multiplica por (118 + azar(0..20)) / 128; una unidad del mismo elemento se cura 6 y una del opuesto recibe 75. Las de agua ignoran la lentitud del vado y del hielo derretido; las de tierra, la de ciénaga, arena y pantano y el empuje.
  - `event_area` (con su número `area`): las misiones preguntan si hay unidades dentro; no detiene a nadie.
- `squad_starts`: donde aparecen los escuadrones del jugador. Las unidades de un escuadrón se colocan en ese punto más `team.formation_px` de `/data/rules.json`.
- Los hechizos de terreno (`terrain` en `/data/spells.json`) cambian cómo actúan las zonas dentro de un círculo durante 20 s; no cambian las formas.

`terrain_effects` resume las zonas con efecto de cada mapa (`kind`, y en `quicksand` las direcciones `push`). Las reglas completas están en `terrain` de `/data/rules.json`:

- `effects` dice qué hace cada `kind` a una unidad que camina dentro: `push_px_per_tick`, `speed_multiplier`, o `damage` cada `every_ticks` con su `element`. `spared_elements` son los elementos que se libran de su freno o empuje, y `altered` es el efecto mientras un hechizo de terreno la cambia (`heat`, `harden`, `swamp`, `dry`, `freeze`).
- `damage`: la unidad del mismo elemento se cura `same_element_heals_fraction` del daño, la del opuesto recibe `opposite_element_multiplier`, y el resultado se multiplica por un valor al azar entre los dos de `random_multiplier`.
- `flying_units_affected` es `false`, y `sides_affected` dice que Isaac, sus escuadrones y los enemigos se tratan igual.
- Las capas animadas de los mapas solo dibujan: la arena que corre coincide con las zonas `quicksand`, la nieve no tiene zona propia y la lava es `magma`.
- Cada unidad trae `terrain_spared` en `/data/units/<slug>.json`: `all` si vuela, el `kind` cuyo freno o empuje no la afecta, `fire_heals` si el daño de fuego del terreno la cura, y `fire_x1.5` si le hace 1,5 veces.

## Sellos de aparición

Algunas batallas de la historia dibujan sellos hexagonales en el suelo, en los puntos donde aparecen enemigos (98 sellos en 34 batallas; la batalla 1 tiene seis, uno bajo cada Hongo bruma). `/data/appear-seals.json` trae las imágenes, las reglas y, en `battles`, los sellos de cada batalla con las unidades que están sobre ellos; `seals` en `/data/maps.json` trae los mismos puntos. Px del mapa, tics de 1/60 s y alfa sobre 31 (opacidad = alfa / 31). Sale del código del juego; los colores, la profundidad, el parpadeo y la columna de luz se comprobaron en un emulador en la batalla 1.

- **Cuándo.** Cada sello está en el suelo desde que empieza la batalla hasta que termina. No reacciona a las unidades: no crea ninguna y no cambia cuando una aparece sobre él.
- **Imagen.** `seal.image` es una tira de cuadros de 32 × 25, de izquierda a derecha `base`, `fire`, `wind`, `water`, `earth`, `light`. El `pivot` (16, 12) del cuadro va sobre el punto del sello. El sello se dibuja debajo de todas las unidades.
- **Base.** El cuadro `base` (gris), siempre opaco.
- **Capa de color.** Un cuadro de color sobre la base; empieza en `fire`. Cada `step_ticks` (4) sube un contador de pasos; el alfa de la capa es `alpha_by_step[paso − 1]` (de 1 hasta 30 y luego hasta 2). Tras `steps_per_color` (59) pasos, `color_ticks` (236), pasa al color siguiente de `colors`, y de `light` vuelve a `fire`. Así el sello brilla y se apaga despacio en fuego (amarillo anaranjado), viento (verde), agua (azul), tierra (naranja) y luz (amarillo).
- **Tamaño.** El sello no cambia de tamaño (`scale` 1).

**Una unidad que aparece.** Una unidad oculta sale cuando el guion de la misión la hace aparecer, en su propio punto (en la batalla 1 es un sello) o en otro. No suena nada. Pasan dos cosas a la vez, desde el tic 1:

- **Parpadeo.** Un contador va de `counter_from` (63) a `counter_to` (127), uno por tic. Mientras sube, la unidad solo se dibuja cuando `counter & 2` no es 0: 2 tics se ve y 2 no, durante 64 tics. Luego queda opaca.
- **Columna de luz.** `column.image` tiene dos cuadros de 32 × 40 con el pivote en (16, 32): `back`, detrás de la unidad, y `front`, delante. El pivote va en el punto de la unidad bajado su radio (`radius_px` en `/data/units/<slug>.json`), con alfa 16. Los dos se espejan en horizontal un tic sí y otro no (`mirror_frames`). En los tics 1 a 29 su alto es t / 30 del total y crece hacia arriba desde el pivote; desde el tic 30 tienen su tamaño y su alfa es 16 − floor(0,4 × (t − 30)); después del tic 70 desaparecen. La columna sigue a la unidad.

## Mapa del mundo

`/data/world-map.json` describe el mapa por el que viaja Isaac entre misiones. `picture` es `/juego/mundo.png`; las posiciones están en píxeles de esa imagen.

- `places`: los 44 lugares, con `id`, `x`, `y` y `neighbors`, los lugares unidos a él por un camino. `region` es su posición en `regions`: el nombre y la descripción que muestra el juego (`name`, `description`, en `en`, `es`, `fr` y `de`); varios lugares comparten región.
- `availability_group` (0 a 15) es el interruptor que habilita el lugar, y `unlock_flag` la bandera de `/juego/data.json` que lo enciende. Según el código, un lugar se habilita cuando su bandera está encendida, y un camino cuando lo están sus dos lugares.
- `paths`: los 50 caminos, como pares de `id`. Son rectos y se recorren en los dos sentidos. Isaac solo va a un lugar unido por un camino, y cada tramo dura `movement.leg_frames` (60) cuadros, sea largo o corto.
- `offers`: las misiones y pueblos que el mapa ofrece en cada lugar.
  - `chapter` es el capítulo de `/juego/data.json` (`qc01` a `qc06`; `sc01` a `sc03` para los secundarios) y `step`, el paso dentro del capítulo. `optional` es `true` si completarla no cierra el paso.
  - `objective` es el texto de sus batallas; vacío si solo tiene escenas. `scenes` son ids de `/juego/data.json` y `battles` trae `battle` y `map` de `/data/maps.json` (`null` si la batalla no tiene página de mapa).
  - Un mismo guion puede aparecer en dos lugares: el juego lo ofrece en uno u otro según una condición.
- `free_battles`: las batallas libres que el juego registra en cada lugar, con su capítulo.
- Al llegar a un lugar con algo, empieza, y el juego guarda un punto de control.
- `markers`: los tres marcadores del mapa. El rojo marca un lugar donde se ofrece una misión o un pueblo; el azul, otro tipo de evento sin identificar; el verde, no se sabe qué lo muestra.
- `confidence` dice cómo se sabe cada parte: `rom` sale de los datos del juego; `code`, de leer su código, sin comprobarlo en una partida.

## La historia: `/juego/data.json`

Este archivo es la lógica de la historia, leída de sus scripts: qué misiones están disponibles, qué ocurre en cada una, qué cambian las respuestas del jugador y a qué final llevan. Las rutas dentro de él son relativas a `/juego/`. Los textos están en `/juego/textos/<idioma>.json`, con los mismos ids.

### Estado de la partida

Una partida son cuatro cosas, todas vacías al empezar:

- **Banderas**: interruptores numerados, encendidos o apagados.
- **Contadores**: valores numerados de 0 a 127. El contador `rules.compassion` es el que decide entre dos finales.
- **Respuestas**: para cada escena, la opción elegida en cada punto de decisión.
- **Completadas**: las misiones terminadas.

### Condiciones

Muchas entradas llevan una lista de condiciones, y todas deben cumplirse. Cada una es `[negate, test]` (si `negate` es `true`, la prueba debe fallar):

| Prueba | Se cumple cuando |
|--------|------------------|
| `["flag", n]` | La bandera n está encendida |
| `["counter", n, op, k]` | El contador n comparado con k (`<`, `<=`, `>`, `>=`, `==`, `!=`) da verdadero |
| `["answer", scene, slot, option]` | En esa escena se eligió esa opción en ese punto de decisión |
| `["done", mission]` | Esa misión está completada |
| `["result", k]` | La última batalla de la misión terminó por la condición k: 2, no queda ningún enemigo vivo; 3, murió el jefe. Las dos son victorias |

### Capítulos y misiones

`chapters` lista los capítulos en orden: `qc01` a `qc06` para la historia principal y `sc01` a `sc03` para las misiones secundarias. Cada capítulo es una lista de **pasos** en el mapa del mundo; cada paso ofrece misiones como `[mission, offer, conditions]` y tiene `flags` que se encienden al terminarlo:

- Solo se ofrecen las misiones cuyas condiciones se cumplen.
- `offer` 159 marca una misión opcional. Un paso termina cuando se completa una de sus otras misiones (o cualquiera, si todas son opcionales); entonces se encienden sus `flags` y se abre el paso siguiente.
- Si no queda ningún paso abierto (falta encontrar algo), el último paso que ofreció misiones sigue abierto para que sus lugares se puedan volver a visitar.
- El capítulo actual es el primer `qc0N` cuya bandera en `rules.chapterFlags` (una por capítulo, del 1 al 4) está apagada. Con las cuatro encendidas, la historia sigue en `qc06` si la bandera `rules.route6Flag` está encendida, y en `qc05` si no. Los capítulos secundarios transcurren al mismo tiempo.

`missions` describe cada misión: `kind` (`m` batalla, `t` pueblo, `r` batalla libre) y una línea de tiempo (`timeline`) que se recorre en orden, saltando las entradas cuyas condiciones (el último elemento) no se cumplen:

| Entrada | Efecto |
|---------|--------|
| `["scene", id, how, conditions]` | Reproduce esa escena; `how` es 32 para una escena sola, y 29 o 31 para una que se muestra en la batalla siguiente |
| `["battle", id, conditions]` | Juega esa batalla |
| `["flag", n, value, conditions]` | Pone la bandera n en value (1 es encendida) |
| `["counter", n, delta, conditions]` | Suma delta al contador n, sin salir del rango de 0 a 127 |

Al final, la misión cuenta como completada. Los nombres de las misiones y de los capítulos están en los archivos de textos (`missions`, `chapters`).

### Escenas

`scenes[id].blocks` tiene los bloques de la escena; empieza en `main` y ejecuta sus elementos en orden:

| Elemento | Significado |
|----------|-------------|
| `["s", background, cameraX, cameraY, cast]` | Lo que muestra la pantalla de arriba desde ese momento |
| `["l", speaker, line]` | Una línea de diálogo: `names[speaker]` dice `scenes[id].lines[line]` |
| `["c", slot, [[line, block], …]]` | El jugador elige: el texto de cada opción es `scenes[id].lines[line]`. Guarda la posición de la opción como respuesta de ese punto de decisión y sigue en su bloque |
| `["n", block]` | Sigue en ese bloque |

La escena termina cuando un bloque se queda sin elementos. Las líneas conservan los saltos de línea del juego (`\n`). Isaac es el nombre por defecto del protagonista. `[L]`, `[B]` y `[+Control Pad]` (con las palabras de cada idioma) representan los botones de la consola.

Para dibujar la pantalla de arriba (256×192):

- **Fondo**: `bgs[background]` es `[picture, width, height]`. Muestra la parte que empieza en `x = clamp(-cameraX, 0, width - 256)`, `y = clamp(-cameraY, 0, height - 192)`.
- **Personajes**: cada `[actor, x, y, expression, mirror]` de `cast` es un retrato. `portraits["<actor>_<expression>"]` es `[picture, dx, dy, width, height]`: su esquina superior izquierda va en `(x + dx, 160 + y + dy)`, reflejada en horizontal cuando `mirror` es 1. `names[actor]` dice quién es.

### Finales

Cuando una misión enciende la bandera `endings.gameOverFlag`, el juego termina. La escena final es la que va con la primera bandera encendida de `endings.byFlag` (`[flag, scene]`), o `endings.default` si ninguna lo está. Sus títulos están en los archivos de textos (`endings`).

### Batallas en la historia

`battles[id]` es una batalla de la historia: `map` (su imagen), `size` y `units` como `[uid, x, y, level, character]` (`character` no es 0 en los personajes de la historia). Las mismas batallas, con nombres, están en `/data/maps.json`.

### Archivos de textos

`/juego/textos/<idioma>.json` tiene, para ese idioma: `names` (quién habla, por número), `scenes` (el `title` y las `lines` de cada escena), `missions`, `chapters`, `endings` y `units` (los nombres de las unidades por `uid`). Su `ui` tiene las etiquetas que usa la novela del sitio; puedes ignorarlo.

## Audio

`/data/audio.json` tiene `music` (`code` es el nombre de la canción en el juego) y `effects`, agrupados por `category`. Las canciones con bucle están grabadas tocando el bucle dos veces y desvaneciéndose al final. En el juego, una canción con `loop_end_s` vuelve a `loop_start_s` al llegar a `loop_end_s` y suena para siempre; una con `end_s` suena una vez. `missions` y `scenes` cuentan las misiones y escenas que la usan (la de cada misión está en `/data/maps.json`), y `screen` es la pantalla fija que la usa: `world_map`, `mission_complete`, `victory` y `defeat` (al terminar una batalla), `game_over`, `title` o `ending`.

`sound_events` dice qué hace sonar los efectos de los menús, el mapa del mundo, la pantalla de resultados, los cofres y los cristales de maná. Sale de leer el código del juego (`evidence: static`): cada lugar que reproduce un sonido y el número que carga; no se observó en un emulador. El juego reproduce el sonido número N como la entrada N − 1 del archivo de sonidos elegido. Normalmente es `SEQ_SE`, la categoría «Juego» de `effects`.
- `events`: `group` (`menu`, `world_map`, `results`, `chest`, `crystal`), `event`, `sounds` (`id`, `name`, `url`) y `calls`, los lugares del código que lo hacen sonar. En los menús, `cursor`, `confirm` y `cancel` están identificados; `other` junta el resto de los sonidos de menú, de los que no se trazó qué acción los dispara en cada pantalla. En el mapa: `move` (un tramo de camino), `enter_event` (llegar a una escena o un pueblo) y `enter_battle`. En resultados: `exp_count` (cada paso del conteo de experiencia) y `level_up`. Cofre: `open` e `item` (el objeto obtenido). Cristal: `captured` y `lost`.
- `download`: mientras está abierta la pantalla Download del modo duelo, el juego usa otro archivo, `SEQ_SE_CURSORS`. En él, los números 2, 3, 4 y 108 suenan con sus propias versiones (`replaces` es el sonido normal de ese número). `url` es `null` si el sitio no tiene ese clip.

## Otras imágenes

`/data/gallery.json` agrupa el resto del arte por sección: runas (la forma de cada una, como imagen), hechizos y efectos de batalla, fondos, el mapa del mundo y sus marcadores, la interfaz, y arte sin usar o de prueba. `/juego/mundo.png` es el mapa del mundo.

## Lo que no está en los datos

Tu versión tiene que definir esto:

- Qué órdenes producen los estados 6, 7 y 12 de la IA (`unresolved_states` de `/data/ai.json`) y cómo se ve la IA en una batalla: lo publicado sale del código, no se observó en un emulador.

Los gráficos, los textos y la música pertenecen a Taito y Ubisoft.
