Saltar al contenido

llm.md

Guía para agentes de código

Este es el texto que lee un agente de código para hacer su propia versión del juego con los datos de este sitio, en cualquier plataforma. Cópialo en el agente o pásale el enlace del archivo Markdown.

Abrir llm.md

Contenido
  1. Archivos
  2. Convenciones
  3. Unidades y sus animaciones
  4. Movimiento e IA
  5. Duelo
  6. Reglas del juego
  7. Objetos
  8. Hechizos y runas
  9. Escenas de hechizos
  10. Apuntar un hechizo
  11. Estados alterados
  12. Combate
  13. Barra de vida
  14. Marca de enemigo
  15. Cristales de maná
  16. Cámara de batalla
  17. Números de daño
  18. Pantalla del canto
  19. Fin de la batalla
  20. Pantalla de título
  21. Personajes
  22. Mapas de batalla y batallas
  23. Sellos de aparición
  24. Mapa del mundo
  25. La historia: /juego/data.json
  26. Audio
  27. Otras imágenes
  28. Lo que no está en los datos

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.

{
  "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]
  1. 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.

1×