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.espara 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
msson 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
framesdice 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. origines 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 queorigincaiga en la posición de la unidad.actionyfacingdicen cuándo la reproduce el juego: la acción de la unidad y hacia dónde mira (downde frente,upde espaldas,left,right; vacío si la acción no tiene dirección).usedesfalseen las que están dibujadas pero el juego nunca reproduce.previewes 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 usaframespara 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,accuracyyevasion, cada una como[nivel 1, nivel 50]. En el nivel L valenivel1 + floor((nivel50 − nivel1) × factor / 128), dondefactores el valor de la curvagrowthde la unidad (early,lateolinear) para el nivel L en/data/levels.json.speed_px_per_tick: cuánto camina por tick en los niveles 1 y 50; esspeed / 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) yradius_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) ysquad_size(cuántas unidades trae un escuadrón).attack_used_by: para qué sirve su estadísticaattack.normal_attack, su ataque normal. Las que nunca atacan (attack_reach_px0) traendance_spells(Isaac: solo lo usan los hechizos de baile, que golpean con su ataque completo menos un cuarto de la defensa del objetivo) onothing(los magos, los jefes de la historia y el Aldeano: no se usa nunca).attack_notelo explica en inglés y en español, y dice cómo hace daño la unidad./data/units.jsontrae los dos campos de cada unidad.elementes el elemento de la unidad, ydescriptionel 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 hastaroute_points_maxpuntos; una unidad llega a un punto cuando está a menos depoint_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 caminavelocidad / 32px por tic, sin aceleración; los obstáculos y las demás unidades la empujan hacia afuera.Objetivo: cada
retarget_every_tickstics, en los estadosstates_with_target_search, busca enemigos a menos desearch_pxy 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 enclasses) fija un estado de decisión (state):waitcon 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.movesigue la ruta sin atacar;move_autoataca de paso sin salirse;move_attackpersigue dentro de su radio de visión o sigue la ruta;move_wait_autosigue la ruta, persigue y, detenida, espera.guard(estados 9 y 10) vigila un punto:guarddice 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 lakeep_distancede la unidad (/data/units.json).take_crystal(8) va al cristal de maná de otro bando más cercano a menos deorder_list.take_crystal_radius_pxy se queda hasta que es de su bando.
El radio de visión es el
sightde 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,yes un punto del mapa en px;dx,dyse 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: falsees el estado 0 (ataca sin moverse),trueel 1 (persigue dentro de su visión).back: vuelvestepsó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 asight_px.change_max_hp: su vida máxima pasa afloor(máxima × percent / 100);set_max_hp: ahp. Su vida actual pasa a ese mismo valor, sin superar la máxima que tenía.guardno termina nunca: la lista se queda ahí.
Cómo corre la lista (order_list), un paso por tic (steps_per_tick):
- Empieza en la primera orden. El tic en que una orden empieza no cuenta para terminarla.
ends:at_oncetermina el mismo tic en que empieza;route, el primer tic después de que la unidad llega al punto (al llegar queda en el estadoroute_end_state);ticks, cuando llevatickstics 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;jumpesback;removed, comoat_once, y además la orden sale de la lista.- La orden siguiente empieza en el tic siguiente. Con
waitdeticksN, la siguiente empieza N + 2 tics después de que empezó la espera. backcuenta solo las órdenes que siguen en la lista:removed_when_doneya no están.- 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 tienelevel(el nivel del jugador, 40),element_levels(los seis niveles de elemento, que el juego limita a 1–50) ysquads, tres escuadrones (unit_idde/data/units.json,name,slugylevel). El cartucho no les da nombre: se numeran del 1 al 8.rating: la puntuación de las partidas por Wi-Fi, entreminymax.rowstiene, 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 entreminymax.elo_kvale 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, ycaptures_per_battle_max.items:inventory_max,found_level_max(el nivel más alto de un objeto encontrado) yfound_per_battle_max.levels:level_maxde Isaac y los monstruos,element_level_maxyenemy_level_max.experience:per_kill_per_level(cada unidad que muere da ese número × su nivel),battle_maxylevel_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 delevels.enemy_level_max) y los niveles extra de los objetos de los cofres (con el tope deitems.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_sizede 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,defenseomagic_defense).bonus_by_level: el valor v en los niveles 1 a 10, sobre 128. Los cofres solo dan niveles 1 aitems.found_level_maxde/data/rules.json.effect, cómo se aplica v a la estadística x de la unidad:multiply:x + floor(x × v / 128). Conhp, 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.;nullen los demás): qué cambia el objeto en la batalla, enenyes:what(qué es la estadística),examples(ejemplos con unidades de nivel 1),good,bad,caution,level(cuando el nivel del objeto no importa) yanchor(la sección de/logic/combat/con la regla).examplestrae los mismos ejemplos como números:accuracy,evasionyhit_percentcomo [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ónx,yen píxeles del mapa,chance(0 a 1),levels, la probabilidad de cada nivel, ynew_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:
closedes la imagen cerrada que dibujan los mapas, puesta en el punto del cofre más (dx,dy).sheettienecellsceldas iguales decell.width×cell.heightpx encolumnscolumnas. El punto del cofre está encell.originde cada celda.animations:idle0(cerrado),open(se abre),loop(abierto con el premio),suka(humo) yemptybox(vacío). Cadaframeses una lista decellyticksde 1/60 s.loopdice si se repite, ypreviewes un GIF o PNG para verla.flow: con premio, el juego pasa deidle0aopeny queda enloop; sin premio, deidle0aemptybox.sukatieneused_by_gameenfalse: 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:
- 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.
- 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á amax(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. - Para cada punto k ≥ 1 calcula
angle[k], la dirección del segmento que llega a él, yratio[k], qué parte del largo total es ese segmento. El punto 0 toma la dirección del primer segmento. - 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]
- 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 esfloor((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_circleypoint: un punto;direction: sale de Isaac hacia donde se suelta;unit: una unidad;instant: sin apuntar).targets:enemies,alliesoall; esnullen los tres que no tocan ninguna unidad (Transport y Flight mueven a quien los lanza, Restore solo cambia el suelo).numbers.cast_range_pxes la distancia máxima desde Isaac.effect_group: solo enunit_effect(nullen las demás familias), qué hace sobre las unidades.healrecupera vida o quita estados: la banderaheal, un estado de grupocureoregeneration, o las variantes que pasan vida al propio bando (life_thread,life_shine,light_transfer).boostpone un estado a aliados yweakena enemigos: todo estado que el juego lanza a aliados los ayuda y todo el que lanza a enemigos los perjudica.otheres 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: conpower_scaled, la potencia al lanzarlo esfloor(floor((power + magia de quien lo lanza) × factor de calidad) × (rango + 4) / 4); sinpower_scaled, se usapowertal cual. Con la banderaheal, 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) / 4y por el factor de elemento deelements.status_chancede/data/combat.json),duration_ticksypower.terrain: cómo cambia el terreno (heatvuelve magma el suelo,hardenendurece el magma,freezecongela el agua,dryseca el pantano en arena,swampvuelve pantano la arena movediza,restorelo 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 dearea.per_object_radius_pxpor cada objeto que cae. Se pintan las celdas de 8 × 8 px cuyo centro cae dentro del círculo.paintses la capa que pone y sobre qué superficie (on);imageson sus 32 teselas animadas, cada cuadro deframe_ticks(null: fija).clearsson 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, yrestoreborra 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 deframe_ticksson un bucle de toda la capa. Es solo el dibujo: el efecto sobre las unidades lo dan los estados del terreno.on_any_mapdice si esa superficie existe en algún mapa.drypintamf04sobre pantano, pero ningún mapa tiene pantano (on_any_map: false): en el juego solo borra el pantano que dejaswamp.flags:piercingatraviesa,homingpersigue,rapidráfaga,spreadabanico,lock_onfija cada objetivo,flying_onlyywalking_onlylimitan a quién afecta,knockbackempuja,knockdownderriba.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 yeffect_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; sigains_onlyno 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; pormp_per_kill= MP × lanzamientos para quitarle la vida al enemigo débil, después por trazo y después por alcance) yboss(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;cellsda rectángulos [x,y,w,h] de la hoja yoriginssu 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_startes el cuadro inicial del bucle.ticksysecondsconservan la duración original.units[].actions[acción][dirección]: secuencia,ownyfallback. Los monstruos sin conjuro o lanzamiento usan reposo.launch_pxcoloca el disparo.boss_spellsidentifica los hechizos que el jefe usa en batalla.spells[].graphics: función, hoja, secuencias, paleta, evidencia y confianza.times_secondsconvierte 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_confidencesepara 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,soundsyduration_secondsdescriben el estado./data/statuses.jsontambién incluye este objeto comovisual.- 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:
mapyorigindicen en qué mapa del juego y en qué punto se pone la escena, para que se vea el terreno sobre el que actúa;zonedice cuándo nace la zona: al impactar (hit), en cada objeto que cae (object_hits) o al aparecer el área (release);radius_pxylasts_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). Cadatargetingtiene su icono (icon) y su forma de confirmar (confirm).arrowdice si usa la flecha;range_graphics_3d, si dibuja algo en el suelo (círculos o sombra).directionno dibuja nada en el suelo: solo la flecha y el icono. - Flecha (
arrow, solodirection: Fireball y los demás disparos). La imagenimage(32 × 56) apunta hacia arriba. Su puntopivot_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 quedatip_px(36) px delante de Isaac y el mangotail_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_steps4096 por vuelta), sin pasos de 4 u 8 direcciones. No se estira ni cambia de tamaño con la distancia (scale1) y es opaca (alpha1). 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 sushot_range_pxde/data/spells.json. Antes de tocar, pulsar el botón del canto (L, o R con la opción para zurdos) cancela: suenaSE_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 enat_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 demove_when_arrow_deg: 90–180 para diestros, 0–90 para zurdos) salta amoved_at_px(236, 20 o 20, 20) para no quedar bajo la mano. - Círculo (
circle_flow,targeting: circle). Fuera del alcance (cast_range_pxdesde Isaac) el campo se sombrea conshade_outside_range(#525252, alfa 20/31). Un toque fuera del alcance no cuenta; uno dentro fija el centro. Arrastrando se elige el radio, desderadius.min_px(5) hasta elradius_pxdel hechizo; al soltar se lanza con ese centro y ese radio. El área se colorea segúntargets(area_colour_by_targets: rojo enemigos, azul aliados, amarillo todos;area_colour_otherpara los hechizos que lista) y conarea_colour_at_max_radiusal 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áximomax_length_pxdel 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.pointdibuja 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_circledibuja en el punto un disco de 24 lados del radio del hechizo (radius_px,trigger_radius_pxoring_radius_pxsegú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 permitetargetsy 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_pxde 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.numbersson 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
intervalllegan como mucho al doble de las de la unidad (intervalal 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,terroryforget.
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_pxy sigue mientrasgap < attack_reach_px + attack.reach_pad_px. Conattack_reach_px0 no ataca nunca; al perseguir se detiene aattack.stop_gap_px. - Ritmo: espera
attack_interval_ticks, reproduce su animaciónattack; el golpe o el disparo sale a losattack_strike_ticksde 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); sid ≤ 0, nada. Conattack_element: ×½ contra el mismo elemento, ×1,5 contra el opuesto (opposite). Los disparos hacen ×1,5 a las unidades que vuelan. Al finalfloor(d × (128 + r) / 128), conrentero 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:
- Elemento: sin elemento, P; mismo elemento que el objetivo,
−floor(P / 8); opuesto,floor(3P / 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). × (128 + r) / 128, conral azar entre −10 y 10.- Si el objetivo tiene más estados
weaknessquehalvingde ese elemento, ×2; si tiene máshalving, ×½. Los hechizos conhealsuman 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_gainsic ≥ many_casts, +1 si1 ≤ c < many_casts; +1 más si esfav.old ≤ mid_band_max: +1 sic ≥ many_casts; +1 más si esfav.- Si no: +1 solo si es
fav, y entonces el elemento opuesto pierde 1 si su nivel eradecay_mino 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.
frame: el marco, de colorframe.color, debajo de todo. Lo que la barra no cubre se ve de ese color.bar: la vida, defloor(width_px × vida / vida máxima)px de ancho, con la vida máxima efectiva (después dechange_max_hp). Su color,color_by_side:playerpara el bando de Isaac,enemypara los enemigos,allypara los aliados y los personajes sin bando.change: durantechange.tickstics después de cambiar la vida, otra barra de la misma posición y alto va entre el marco ybar. Solo cuenta el último cambio.- Tras un daño,
barmuestra la vida nueva y la de abajo la de antes, endamage_color.enemyen los enemigos ydamage_color.otheren el resto. - Tras una curación,
barmuestra la vida de antes y la de abajo la nueva, enheal_color.
- Tras un daño,
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,yde/data/maps.json) subido la altura de su cabeza (head_px): elpivot_pxde la imagen va en(x + offset_px[0], y − head_px + offset_px[1]), es decir(x − 14, y − head_px − 6). Quedagap_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_px14) 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 estadohiddenbaja 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_pxque 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_crystalsen/data/units/<slug>.json), viva y a menos decapture_radius_px, sumagauge_per_unit_per_tickal medidor. Cuando nadie suma, el medidor bajagauge_decay_per_tick. Al pasar decapture_gauge.neutral(cristal de nadie) o decapture_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_pxrecuperanheal.hpde vida cadaheal.every_ticksmientras estén heridas. - MP. Cada
mp_regen.every_ticks, el MP de Isaac subemp_regen.base_mp, másmp_regen.mp_per_crystalpor cada cristal suyo, más 1 por cada aumento anterior seguido sin cantar, hastamp_regen.streak_max. Sin cristales también recupera: la base y la racha. Después de un canto, el primer aumento llega a losmp_regen.chant_wait_ticks; con cristales, a loschant_wait_ticks − chant_wait_cut_ticks_per_crystal × cristales, con un mínimo dechant_wait_min_ticks. - Imagen.
picturestiene una imagen por dueño, depicture_size. Su esquina superior izquierda va en el punto del cristal máspicture_offset. - Barra de captura.
capture_bardescribe la barra que el juego dibuja con rectángulos, sin imagen.drawn_by_siteestrueporque el sitio la redibuja a partir de estas reglas:- Mide
height_pxde alto y va aabove_pxsobre el punto del cristal. - El fondo, de color
colors.background, empieza enbackground.<neutral|owned>.left_pxy midewidth_px. - Encima, desde el mismo borde y en
colors.remaining, va lo que falta:(capture_gauge − medidor) / remaining_divisorpx, con el medidor del último empuje. - Cada empuje la muestra durante
fade_ticksticks, con opacidadticks restantes / 2sobrealpha_max.
- Mide
- Misiones. El objetivo «¡Purifica los cristales!» se gana cuando todos son del jugador (
all_crystalsenconditions).
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_pxyview_y_max_pxde cada mapa enmaps(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 enposició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, odpad.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_pxde la pantalla (x 20–236, y 20–172), el canto espera y la vista va hacia él achant_recentre.px_per_frame(10) en línea recta, hasta dejarlo entarget_px(128, 96). Cuando falta menos dejump_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)avelocidad × 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 derune_grid6 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) yANIMla usan tal cual.NORMla multiplica porspeed_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 conANIM, la capaNORMno 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, hastavalue.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 dekinds.<tipo>.imageson 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#de1852con borde blanco;healing(vida ganada) es verde#18bd73con 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ásradius_pxde 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). Conncifras, el centro de la cifrak(desde 0, de izquierda a derecha) va en x =−(3n − 3) + 6krespecto 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 quedalasts_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_px6): tres cifras rosas con sus centros en (−6, 6), (0, 6) y (6, 6) respecto a los pies del hongo, durante 40 ticks.example.imagees ese número ya armado (20 × 8), con su centro enpoint_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, colorcolour(#525252), mezclado con alfa normal sobre el campo. Su alfa empieza enalpha_from(10/31) y sube 1/31 cadastep_every_ticks(2) ticks hastaalpha_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 imagenimage(256 × 192, paleta propia naranja) va con su puntopivot_px(128, 79) encentre_px(128, 96); el dibujo ocupaart_box_pxde 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, duranteappear.ticks(30) crece desde casi 0 ascale_per_tick(0,0332) por tick y giraturns_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_unusedes una salida con giro y fundido que está en el código pero el juego no usa. - Trazo (
stroke). Cada trazo es una cinta dewidth_px(8) de ancho alrededor de los puntos del lápiz (uno por tick, hastamax_points), con remates redondos que pasancap_px(4) del primer y del último punto. Se pinta con la texturatexture(32 × 32: borderimamarillo, centrocoreblanco) 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 bajastep31por 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 fondobackground, 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 muestrano_mp(una X) en lugar de la runa.previewes la lista con las 18. - Números. Cada runa que se puede pagar lleva su costo en MP,
cost.digits(2) cifras encost.offset_px(18, 18) desde la esquina de su celda; el costo es el demp_per_runede/data/runes.jsonpara la próxima runa del canto. El MP actual de Isaac va con 3 cifras enmp.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 puntopivot_px(29, 64) enat_px(224, 155): crecen hacia arriba y, llenas, midenfull_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 enticks, 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 esperasettle_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 enat_px(128, 96); cada imagen dewords(una por idioma, según el de la consola: francés, alemán, español; cualquier otro, inglés) va con supivot_pxsobre ese punto. En español las palabras son VICTORIA, DERROTA, EMPATE, FIN DEL TIEMPO e INICIO.- 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,circleydim_layer). La palabra todavía no se dibuja. - La palabra aparece, opaca, durante
ticks.shown(31) y se quedaticks.held(61) más. - 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. - En
ticks.total(154) el círculo y la capa oscura desaparecen a la vez.
- Durante
- 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 enisaac.lv_label_at_px(la_fr_esen francés y español), su nivel como%2denlevel_text_at_px, la barra de EXP enexp_bar_at_px(bar_frame_64, luegobar_back_64, luegobar_fillestirada aexp_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 ensquads.icon_at_px, la placa, su nombre, su nivel y su barra a los desplazamientos desquadsdesde el icono (barra de 46 px,bar_frame_48ybar_back_48). Los niveles de elemento no salen en la pantalla de arriba. - Pantalla de abajo, página 1 (
bottom.pages1): las cuatrobarsen suat_pxy lostexts:clear_timeel tiempo de batalla en segundos (como mucho su límite) comohh:mm:ss;defeatedlos derribados;survivallos monstruos de escuadrón vivos × 10000 / los desplegados, como%3d.%02d%%, o---.--%sin escuadrones;exp_poolla EXP que falta repartir, que baja hasta 0. - Pantalla de abajo, página 2 (
bottom.pages2, 192 px más abajo): el radar de elementos. Los seis iconos enradar.iconsy dos hexágonos traslúcidos centrados enradar.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: radiofloor((nivel + 10) × 68 / 50); verde:floor(rango × 10 × 68 / 50), rangofloor((nivel − 1) / 10) + 1. El verde va delante del rojo (visto en el emulador); su alfa no está decodificado. - Cronología (
waits_ticks30, 60, 30, 60):- Espera 30 ticks.
- Conteo, cada tick:
paso = max(1, floor(bolsa / 80)), bolsa − paso, e Isaac y cada escuadrón recibenpaso. El sonidocount_step(18,SE_KASAN) suena cada tick; cada nivel ganado suenalevel_up(19,SE_LEVELUP) y muestralevel_up(«UP!», «¡SUBE!» en español; susframeslo muevendy_px) enlevel_up_at_pxde Isaac o sobre el icono del escuadrón. Barras, niveles yexp_poolcambian cada tick. En 0 aparece el botón Siguiente y la pantalla espera. - Espera 60 ticks y la página 2 sube a la vista en 64 ticks (3 px por tick).
- 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. - 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 recibeceil(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.backgroundya 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 panelpanel(128 × 24) con su texto (label, en, es, fr, de) centrado alabel.offset_px(64, 4) de su esquina.imagestrae 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 defrom_pxato_pxen 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 esstep_px, truncado como hace el juego (entry.position), por eso New Game termina en x 29 y Multiplayer en x 40, 1 px antes quetable_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: suenapress.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 dicedoes. 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 sufrom_pxen 30 ticks, Hand Settings regresa a su lugar y entran la pregunta («Left/Right handed?»,question) y las dos manos (hands, dechr/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):
uidyunit(el slug en/data/units.json) dicen quién es.x,yes su posición en el mapa. Con elorigindel sprite en ese punto, las unidades quedan sobre el suelo transitable del mapa.level, ynamed:truepara los personajes de la historia (jefes y aliados),falsepara 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(centrox,y±half_width,half_height) ycircles(x,y,radius), cada una con sukind: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):
magmaydamage_floorhacen 50 de daño cada 60 tics (fuego y sin elemento);groundigual, solo si un hechizo lo calentó;fordmitad de velocidad salvo congelado, apaga y bloquea las quemaduras;icenada, mitad de velocidad si un hechizo lo derrite;bogmitad salvo seco;sandnada, mitad si se vuelve pantano;quicksandempuja 0,25 px por tic haciapush. - 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úmeroarea): 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ásteam.formation_pxde/data/rules.json.- Los hechizos de terreno (
terrainen/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:
effectsdice qué hace cadakinda una unidad que camina dentro:push_px_per_tick,speed_multiplier, odamagecadaevery_tickscon suelement.spared_elementsson los elementos que se libran de su freno o empuje, yalteredes el efecto mientras un hechizo de terreno la cambia (heat,harden,swamp,dry,freeze).damage: la unidad del mismo elemento se curasame_element_heals_fractiondel daño, la del opuesto recibeopposite_element_multiplier, y el resultado se multiplica por un valor al azar entre los dos derandom_multiplier.flying_units_affectedesfalse, ysides_affecteddice 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 esmagma. - Cada unidad trae
terrain_spareden/data/units/<slug>.json:allsi vuela, elkindcuyo freno o empuje no la afecta,fire_healssi el daño de fuego del terreno la cura, yfire_x1.5si 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.imagees una tira de cuadros de 32 × 25, de izquierda a derechabase,fire,wind,water,earth,light. Elpivot(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. Cadastep_ticks(4) sube un contador de pasos; el alfa de la capa esalpha_by_step[paso − 1](de 1 hasta 30 y luego hasta 2). Trassteps_per_color(59) pasos,color_ticks(236), pasa al color siguiente decolors, y delightvuelve afire. 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 (
scale1).
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) acounter_to(127), uno por tic. Mientras sube, la unidad solo se dibuja cuandocounter & 2no es 0: 2 tics se ve y 2 no, durante 64 tics. Luego queda opaca. - Columna de luz.
column.imagetiene dos cuadros de 32 × 40 con el pivote en (16, 32):back, detrás de la unidad, yfront, delante. El pivote va en el punto de la unidad bajado su radio (radius_pxen/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, conid,x,yyneighbors, los lugares unidos a él por un camino.regiones su posición enregions: el nombre y la descripción que muestra el juego (name,description, enen,es,fryde); varios lugares comparten región.availability_group(0 a 15) es el interruptor que habilita el lugar, yunlock_flagla bandera de/juego/data.jsonque 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 deid. Son rectos y se recorren en los dos sentidos. Isaac solo va a un lugar unido por un camino, y cada tramo duramovement.leg_frames(60) cuadros, sea largo o corto.offers: las misiones y pueblos que el mapa ofrece en cada lugar.chapteres el capítulo de/juego/data.json(qc01aqc06;sc01asc03para los secundarios) ystep, el paso dentro del capítulo.optionalestruesi completarla no cierra el paso.objectivees el texto de sus batallas; vacío si solo tiene escenas.scenesson ids de/juego/data.jsonybattlestraebattleymapde/data/maps.json(nullsi 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.confidencedice cómo se sabe cada parte:romsale 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.compassiones 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.
offer159 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 susflagsy 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
qc0Ncuya bandera enrules.chapterFlags(una por capítulo, del 1 al 4) está apagada. Con las cuatro encendidas, la historia sigue enqc06si la banderarules.route6Flagestá encendida, y enqc05si 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 enx = clamp(-cameraX, 0, width - 256),y = clamp(-cameraY, 0, height - 192). - Personajes: cada
[actor, x, y, expression, mirror]decastes 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 cuandomirrores 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) ycalls, los lugares del código que lo hacen sonar. En los menús,cursor,confirmycancelestán identificados;otherjunta 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) yenter_battle. En resultados:exp_count(cada paso del conteo de experiencia) ylevel_up. Cofre:openeitem(el objeto obtenido). Cristal:capturedylost.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 (replaceses el sonido normal de ese número).urlesnullsi 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_statesde/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.