LostMagic: game data for building your own version
LostMagic (Taito, 2006, Nintendo DS) is a tactics game. Isaac, the hero, casts spells by drawing runes on the touch screen and fights alongside the monsters he recruits, through a story whose choices lead to four endings.
These files hold what was extracted from the game's cartridge (US release): sprites and animations of every unit, character portraits, battle maps with where each enemy starts, the full story with its choices and the logic that orders the missions, texts in English, Spanish, French and German, music, sound effects and the rest of the art. Use them to build a version of the game on any platform.
Base URL: https://lostmagic-sitio.vercel.app/
Paths inside the JSON files start with / and are relative to the base URL.
To download everything at once: https://lostmagic-sitio.vercel.app/data/files.txt (one URL per line).
Files
| File | Contents |
|---|---|
/data/units.md, /data/units.json |
The 84 units (77 with a name, the rest unnamed appearances), each linking to its details |
/data/units/<slug>.md, /data/units/<slug>.json |
One unit: its stats, sprite sheet, the exact place of every frame, and its animations |
/data/levels.md, /data/levels.json |
Levels 1 to 50: experience, the growth curves of the stats and the Line Up capacity |
/data/rules.md, /data/rules.json |
The game's limits: team, monsters, items, levels and experience |
/data/ai.md, /data/ai.json |
Movement and AI: routes, target search and the units' orders |
/data/duel.md, /data/duel.json |
The duel mode: the ready-made teams and the Wi-Fi rating |
/data/items.md, /data/items.json |
The 8 items: what they raise, how much at each level and which chests hold them |
/data/spells.md, /data/spells.json |
The 396 spells: runes, MP, aiming, power, status, terrain and their numbers |
/data/spell-families.md, /data/spell-families.json |
The families of similar spells and what changes inside each one |
/data/spell-scenes.md, /data/spell-scenes.json |
Scenes: graphics, palettes, frames and sounds for 396 spells; 90 units and 141 statuses |
/data/runes.md, /data/runes.json |
The 18 runes: the strokes the game accepts, where they are learned and the MP of each |
/data/statuses.md, /data/statuses.json |
Status effects: what they do, how long they last and which spells cause them |
/data/combat.md, /data/combat.json |
Battle constants: opposite elements, normal attacks, shots and element-level growth |
/data/characters.md, /data/characters.json |
The 19 story characters and their portrait expressions |
/data/maps.md, /data/maps.json |
The 55 battle maps and their 176 battles: missions and starting units |
/data/world-map.md, /data/world-map.json |
The world map: 44 places, 50 paths and what each place offers in each chapter |
/juego/data.json |
The story and its logic: chapters, missions, scenes, battles, flags and endings |
/juego/textos/<en|es|fr|de>.json |
Every text of /juego/data.json in one language |
/data/audio.md, /data/audio.json |
24 songs and 147 sound effects |
/data/gallery.md, /data/gallery.json |
818 more pictures: runes, spells and effects, backgrounds, world map, interface, unused art |
Conventions
- Every file under
/data/comes in two formats with the same content:.md, as tables, easier to read and lighter in tokens, and.json, to load the data in a program. The fields described below are the JSON keys; the Markdown tables use the same names as column headers (name.en,name.esfor the two languages). - Each of the DS's two screens is 256×192 px. Story scenes are drawn for the top one.
- Coordinates are in pixels, from the top-left corner, with y growing downwards.
- Durations come in ticks of 1/60 s, the console's frame rate.
msfields are the same, rounded. - The art is pixel art: scale it by whole numbers with nearest-neighbour sampling to keep it sharp.
- Names come in English and Spanish as
{ "en": …, "es": … }. The story has four languages. - Pictures are PNG; animated GIFs are previews only. Audio is AAC in
.m4a: convert it if your platform does not play AAC.
Units and their animations
/data/units.json lists every unit: uid (the id battles and the story use; null for unnamed appearances), slug, name, element (fire, wind, water, earth, dark, light or null), a portrait picture, the sheet, and details, the URL of its full data.
Each unit has one sprite sheet: a PNG holding every frame the unit uses. sheet.cells gives the exact rectangle of each frame on it (x, y, w, h); the position in that list is the frame's number.
An animation is a loop of frames drawn on a fixed canvas of width × height:
- Each item of
framessays which sheet frame to show (cell), where its top-left corner goes on the canvas (x,y) and for how long (ticks). The same cell can appear several times. originis the unit's anchor point on the canvas. It stays fixed while the frames move around it, which is how jumps and attacks move the body. Put the canvas so thatoriginfalls on the unit's position.actionandfacingsay when the game plays it: the unit's action and which way it faces (downfront view,upback view,left,right; empty when the action has no facing).usedisfalsefor the ones that are drawn but never played.previewis the same animation as a GIF, to see what it shows. GIFs store times in 1/100 s and merge repeated frames, so useframesfor timing.
The actions:
action |
When |
|---|---|
idle |
Standing still. Usually a single frame, on the sheet but not as an animation |
walk |
While it moves |
attack |
Its normal attack, up close or from a distance |
chant |
While the runes are drawn (Isaac and bosses) |
cast |
When the spell is released |
knocked_back |
A spell that knocks units down hits it; get_up follows |
get_up |
Standing up again after the fall |
sleep |
A spell puts it to sleep |
dance |
Isaac, with the dancing spells |
trapped |
A capture trap holds it |
captured |
The capture works |
die |
Its HP reaches 0. Plays once; half a second later the unit fades out |
run, victory |
Drawn, never used (used: false) |
When a unit has no animation of its own for an action, it keeps its idle pose. Right-facing animations are separate drawings, not a mirror made at play time.
{
"uid": 1,
"slug": "isaac",
"name": {
"en": "Isaac",
"es": "Isaac"
},
"element": null,
"description": {
"en": "A 14 year old boy. His face plainly displays his emotions.",
"es": "Un joven. Su rostro muestra emociones."
},
"stats": {
"hp": [
300,
450
],
"mp": [
30,
130
],
"attack": [
80,
80
],
"defense": [
25,
38
],
"magic": [
30,
130
],
"magic_defense": [
87,
92
],
"speed": [
21,
23
],
"accuracy": [
100,
100
],
"evasion": [
0,
30
]
},
"growth": "linear",
"up_cost": 1,
"squad_size": 1,
"flying": false,
"ranged": false,
"attack_interval_ticks": 60,
"attack_reach_px": 0,
"radius_px": 8,
"speed_px_per_tick": [
0.65625,
0.71875
],
"attack_strike_ticks": 8,
"attack_element": null,
"projectile_type": null,
"shot_start_px": null,
"sight_px": 0,
"keep_distance_px": 0,
"portrait": {
"url": "/unidades/isaac/retrato.png",
"width": 125,
"height": 125
},
"sheet": {
"url": "/unidades/isaac/hoja.png",
"width": 1012,
"height": 156,
"cells": [
{
"x": 6,
"y": 10,
"w": 32,
"h": 32
},
"…"
]
},
"animations": [
{
"index": 1,
"action": "walk",
"facing": "down",
"used": true,
"preview": "/unidades/isaac/01.gif",
"width": 35,
"height": 34,
"origin": {
"x": 20,
"y": 23
},
"frames": [
{
"cell": 4,
"x": 3,
"y": 2,
"ticks": 15,
"ms": 250
},
"…"
]
},
"…"
],
"battles": [],
"casts_spells": []
}
battles lists the story battles the unit takes part in.
To draw frame i of an animation: take the rectangle sheet.cells[frames[i].cell] and paste it on the canvas at (frames[i].x, frames[i].y). Show it for frames[i].ticks / 60 seconds, then go to the next frame and back to the first at the end.
Stats
Each named unit's /data/units/<slug>.json also has its numbers from the game's tables:
stats:hp,mp,attack,defense,magic,magic_defense,speed,accuracyandevasion, each as[level 1, level 50]. At level L a stat islevel1 + floor((level50 − level1) × factor / 128), wherefactoris the value of the unit'sgrowthcurve (early,lateorlinear) for level L in/data/levels.json.speed_px_per_tick: how far it walks per tick at levels 1 and 50; it isspeed / 32. Flying units use the same speed.flying,ranged(its normal attack is a projectile),attack_interval_ticks(time between attacks),attack_reach_px(it attacks when the gap between the two units' edges is below this) andradius_px(its body is a circle of this radius).up_cost(what it takes from the Line Up's capacity) andsquad_size(how many units one squad brings).elementis the unit's element, anddescriptionthe game's text about it, in English and Spanish.
/data/levels.json has, per level: exp_to_next (the experience to reach the next one), the factors of the three curves (growth, out of 128) and lineup_up, how many UP of units the Line Up holds at that level of Isaac's.
Movement and AI
/data/ai.json describes how units move and what they do on their own. It is read from the game's code (evidence: static); it was not observed in an emulator.
movement: routes are straight (route: straight), with no path finding and up toroute_points_maxpoints; a unit reaches a point when it is less thanpoint_reached_distance_pxaway. A group sent to a point spreads around it: each unit goes to the point plus its direction from the group's centre × (its radius +group_spacing_extra_px). A unit walksspeed / 32px per tick, without acceleration; obstacles and other units push it out.Target: every
retarget_every_ticksticks, in thestates_with_target_searchstates, it looks for enemies less thansearch_pxaway and picks the nearest, the one with most HP or the one with least HP (these two prefer targets within its reach).orders: each order (order, with the game's classes inclasses) sets a decision state (state):waitwith mode 0 (state 0) attacks without chasing whoever comes within its reach +attack_in_place_extra_px; with another mode (state_other_mode, 1) it chases within its sight radius.movefollows the route without attacking;move_autoattacks on the way without leaving it;move_attackchases within its sight radius or follows the route;move_wait_autofollows the route, chases and, once stopped, waits.guard(states 9 and 10) watches over a point:guardsays when it walks back (distance > sight) and how far (distance <= sight / 2).escape(11) runs straight away while the distance between centres is under the unit'skeep_distance(/data/units.json).
The sight radius is each unit's
sight, and missions change it.unresolved_states: states that exist but whose producing order is not known yet.
Duel
/data/duel.json describes the duel mode, where two people play each other. It comes from the game's data and code (DB/chrSelScnDat.cssd, DB/eloRate.elo); it was not observed in an emulator.
presets: the 8 ready-made teams a player can pick. Each haslevel(the player's level, 40),element_levels(the six element levels, which the game clamps to 1–50) andsquads, three squads (unit_idof/data/units.json,name,slugandlevel). The cartridge does not name them: they are numbered 1 to 8.rating: the rating of Wi-Fi matches, betweenminandmax. Each of therowshas the bound on the difference (difference, own rating − the opponent's) and the points a win (win), a draw (draw) or a loss (loss) adds. A negative difference uses the first row whose bound is at least the difference; any other, the last row whose bound is at most it. Then the rating is kept betweenminandmax.elo_kis 50 because all 53 rows match, within one point, the Elo system with K = 50:K × (result − 1 / (1 + 10^(−difference/400))), with result 1, 0.5 or 0.
Game rules
/data/rules.json has the game's limits, read from its code:
team:squads_max, the squads that go with Isaac, never more than the map's squad start points;player_units_max;formation_px, where each unit of a squad starts from its start point (y grows downwards);capacity_up_by_isaac_level, the UP the team holds at each of Isaac's levels, from level 1.roster:monsters_max, the monsters that can be kept, andcaptures_per_battle_max.items:inventory_max,found_level_max(the highest level of a found item) andfound_per_battle_max.levels:level_maxof Isaac and the monsters,element_level_maxandenemy_level_max.experience:per_kill_per_level(each unit that dies gives that number × its level),battle_maxandlevel_1_to_max.battle:default_time_limit_s, the limit when the mission sets none.new_game_plus: the levels enemies gain in each chapter (1 to 5, capped atlevels.enemy_level_max) and the extra levels of chest items (capped atitems.found_level_max).checkpoints: how many map missions and scenes and how many free battles take a checkpoint when they start.
The rules that are not a number:
- Isaac is always in the team and carries no item. Each squad carries one item, which works on every unit in it, and has as many units as its species'
squad_size. - UP is only checked when the team is put together, not in battle.
- Only regular enemy units can be captured, not bosses or allies. They join after a win, at their battle level with 0 experience. Repeated species in the list and repeated items in the inventory are allowed; with the list full, the new one replaces one that is not in a squad, or is dropped.
- Every unit of the enemy side or allied unit of the map that dies gives experience, whoever kills it; captured ones give nothing. On a win, Isaac and every squad each get the whole amount; it is not split.
- A defeat or a draw discards everything gained in the battle. Continuing puts the story and the runes back to the checkpoint, taken when a mission, scene or free battle starts from the map; level, monsters and items are kept, and the mission replays from its start.
- Retiring from the pause menu discards the battle and goes to the title, with no continue.
- In New Game+ Isaac keeps his level, experience, element levels, runes, monsters, team and items; the story starts again with harder battles.
- The results screen, only after a win, shows in order: experience and element levels, items found, captured monsters and new spells. There are no ranks or grades.
- There is no money or shop, and no maximum of units on the field or of active spells. Isaac can cast as soon as the previous cast animation ends; MP is the limit.
Items
/data/items.json has the 8 items. Each squad carries one, applied to every unit in it when the battle starts; Isaac carries none.
icon is the 16 × 16 px icon the menus show. It depends on the stat the item raises, so several items share one. Battles draw no item icon: chests always look the same.
stat: the stat it changes (hp,speed,attack,interval,accuracy,evasion,defenseormagic_defense).bonus_by_level: the value v at levels 1 to 10, out of 128. Chests only give levels 1 toitems.found_level_maxof/data/rules.json.effect, how v applies to the unit's stat x:multiply:x + floor(x × v / 128). Withhp, the unit also starts with its HP full.reduce:x − floor(x × v / 128), at least 1.double:x × 2, at any level.gap_to_100:x + floor((100 − x) × v / 128).
chests: the story chests that can give the item:battle(the same number as in/data/maps.json),map, the positionx,yin map pixels,chance(0 to 1) andlevels, the chance of each level.
When the battle loads, the game draws each chest's item and level. The item goes to the player's unit that opens the chest and is received on a win. In New Game+ the level rises by 2, with the same cap.
Spells and runes
Runes
/data/runes.json has the 18 runes: id (1 to 18), element, level (1 to 3), its picture, spell (the spell it casts alone) and learned (the missions where it is learned). Isaac casts a spell by drawing 1 to 3 runes in order while holding L (R with the left-handed option); releasing the button ends the chant. Each rune is a single stroke: lifting the stylus closes it.
templates are the strokes the game accepts for each rune: points on a 0–63 grid (y down), in drawing order. from_dot marks the ones that start at the dot of the rune's picture; the rest are the same drawing from another start or in the other direction.
To recognize a stroke:
- Keep one point per frame while the screen is touched. After the first, keep only the points that move at least 1 px in x from the last one kept: the game ignores movement in y. A stroke of fewer than 4 points cancels the chant.
- Take a square centred on the stroke, with side
max(width, height), so the proportions are kept. Simplify it: keep both ends and, piece by piece, the point farthest from the chord if it ismax(1, 2 × side / 64)px away or more, up to 40 points. Map it to the 0–63 grid with(p − origin) × 64 / (side + 1)and drop repeated consecutive points. - For each point k ≥ 1 compute
angle[k], the direction of the segment that reaches it, andratio[k], the share of the total length that segment is. Point 0 takes the direction of the first segment. - Compare the stroke (I, with nI points) with each template (P, with nP points) by walking both lines together along their length:
i = j = 1; remP = ratioP[1]; remI = ratioI[1]; score = 0
repeat nP + nI − 3 times:
A = 1 − |angleP[j] − angleI[i]| / 180° # angle difference wrapped to 0–180°
if remP <= remI:
remI −= remP
q = I[i] if remI < ratioI[i] / 2 else I[i − 1]
score += remP × A × (1 − manhattan(q, P[j]) / 128)
j += 1; remP = ratioP[j]
else:
remP −= remI
q = P[j] if remP < ratioP[j] / 2 else P[j − 1]
score += remI × A × (1 − manhattan(I[i], q) / 128)
i += 1; remI = ratioI[i]
- The template with the highest score (0 to 1) wins; on a tie, the first one. If it reaches
accept(0.70), the stroke is its rune. Rotation is not forgiven: about 30° off is already at the limit.
A recognized rune must also be learned, the runes drawn so far must begin some spell, the runes-per-spell limit cannot be passed (runes_per_spell: the missions that allow 2 and 3) and there must be enough MP for that rune. If anything fails, the chant is cancelled.
A spell's quality is the average score of its runes. If it reaches great (0.85), 0.5 is added: the factor goes from 0.70–0.85 to 1.35–1.50.
Spells
/data/spells.json has the 396 spells:
name: the cartridge's name in English and Spanish. The Spanish ones are often abbreviated (none is longer than 12 characters, for example «Torbell. f.»); the site keeps them as they are, without expanding or translating them, and shows the English one beside them.runes: its runes, in order.element: its element, which is its last rune's: 294 of the 396 mix runes of different elements.kind: the family (cloud,wall,bullet,blast,falling_objects,aura,zone,unit_effect,object,dragon).variant: the exact behaviour within the family.mp_by_rank: the MP cost with the element at rank 0 to 4. Rank isfloor((element level − 1) / 10). The cost is the sum of its runes:mp_per_rune[position][rank of that rune's element][rune level − 1]from/data/runes.json. Each rune is paid as it is drawn.targeting: how it is aimed (circle: centre and radius;line: start and end;fixed_circleandpoint: a point;direction: it leaves Isaac towards where the stylus is released;unit: a unit;instant: no aiming).targets:enemies,alliesorall;nullfor the three that touch no unit (Transport and Flight move whoever casts them, Restore only changes the ground).numbers.cast_range_pxis the farthest distance from Isaac.effect_group: only onunit_effect(nullfor the other kinds), what it does to units.healrestores HP or removes statuses: thehealflag, a status of groupcureorregeneration, or the variants that move HP to the caster's side (life_thread,life_shine,light_transfer).boostputs a status on allies andweakenon enemies: every status the game casts on allies helps them and every one it casts on enemies hinders them.otheris the rest: no status and no healing, or a status on both sides. It is the spells page's filter, not a field of the cartridge.power: withpower_scaled, the power at cast isfloor(floor((power + caster's magic) × quality factor) × (rank + 4) / 4); without it,poweris used as it is. With thehealflag, it heals.status: the status it can cause: its record (id),group,chance(at rank 0 and factor 1; multiply by the factor and by(rank + 4) / 4),duration_ticksandpower.terrain: how it changes the ground (heatturns it into magma,hardenhardens magma,freezefreezes water,drydries swamp into sand,swampturns quicksand into swamp,restorerestores it).flags:piercing,homing,rapid(bursts),spread(fan),lock_on(one shot per target),flying_onlyandwalking_onlylimit who it affects,knockback,knockdown.numbers: its family's numbers, in ticks of 1/60 s and px.
Spell families
/data/spell-families.json groups the 396 spells by the game code that runs them: the effect class and subkind in their data. The generic spell on units (direct) is split by role (heal, damage or status-only) and status group, and Prank, Joke and Trick go together as tub. Each family has spells (the slugs of /data/spells.json), same (the fields equal in all, with their value) and differs (the ones that change). The fields are those of /data/spells.json: numbers.<name> and status.<field> are flattened, and runes is how many it has. power is only compared within a family, because each one uses it differently. note (EN/ES), when present, says when one pays off over another, only where the research supports it; it comes from reading the code, not from playing. Families of one spell have empty same and differs.
Spells with rules of their own, by variant:
variant |
Rule |
|---|---|
bomb_man |
Puts a bomb on each target unit, of both sides. After 180 ticks each living bomb hits every unit within 15 px with the power (fire), once per unit, its carrier included |
dance |
Isaac walks to the target at 2 px/tick and takes no damage (gives up after 180 ticks). Within 12 px he dances 300 ticks: hits on ticks 9, 19 and 30 of each 43-tick cycle, with his whole attack minus a quarter of the defense. It ends if the target dies or gets more than 34 px away |
light_transfer |
Dark damage; Isaac recovers twice the damage dealt, up to 32,767 |
life_thread / life_shine |
Heals what the target lacks, taken in equal shares from the other allies / the enemies, never below 1 HP |
holy_pulse |
600 ticks: the caster's side gains 25/128 attack and the other side loses 25/128 defense inside the circle. Casting it again restarts the time |
transport |
The caster jumps to the point if it fits (otherwise it fails); no receiver, no impact. Departure flash on casting, arrival flash at 20 ticks and position change at 30; until then it can be hit where it stood. Flight is the same jump with a longer range (250 px instead of 100) |
self_cannon |
Isaac flies at 2 px/tick for up to 240 ticks; within 12 px, 620 wind magic damage to the target (not scaled) and 255 to Isaac |
free_lift |
Lifts the target (not Isaac or bosses) for 64 ticks; when it drops, the power (wind) and a strong burn to enemies within 40 px |
switch_life |
Swaps Isaac's current HP and the target's, each capped at the receiver's max HP |
gather |
Brings each ally next to Isaac, one every 6 ticks, at a random angle, both radii plus 0–31 px away |
death_gamble |
Picks a random living unit of either side after 282 ticks; it takes 2 × the power (fire) and others within 50 px the power |
prank, trick |
One tub per target; after 20 ticks it takes exactly 1 HP, which counts as damage |
mine |
Each mine lasts 920 ticks and goes off when a walking enemy comes within 14 px: the power (earth) and a knock-down to enemies within 14 px. A new mine disarms the side's earlier ones |
capture_trap |
Catches the first regular enemy within trigger_radius_px. Under capture_hp_below the capture always succeeds; intended_chance_* are not used. A new trap cancels the side's earlier one |
elemental_wall |
Posts have no HP; each post hurts a unit touching it at most once every 120 ticks |
Spell scenes
/data/spell-scenes.json (also .md) maps every spell to graphics, sounds and actual bosses, with 90 unit appearances and 141 statuses. Each spell page offers caster, receiver, four facings, distance, stroke quality, selected outcome, replay and sound. Receiver affiliation follows targets; example outcomes/status application are user choices, not battle rolls. Transport and Flight have no receiver or outcome: their scene moves the caster to the destination.
assets[id]: PNG, CAN/NCER/NANR and exact palette;cellsholds [x,y,w,h] sheet rectangles andoriginstheir anchor-relative positions. Empty frames still consume their time.sequences[].frames: [cell,ticks,dx,dy,rotation degrees,scaleX,scaleY]. Single-frame animations are retained.mode: 1 once,2 loop,3 ping-pong once,4 ping-pong loop;loop_startis the loop's starting frame.ticksandsecondsretain original durations.units[].actions[action][facing]: sequence,ownandfallback. Monsters without chant/cast actions use idle.launch_pxpositions the shot;boss_spellsidentifies actual battle use.spells[].graphics: role, atlas, sequences, palette, evidence and confidence.times_secondsconverts spell time fields; distances remain px. Projectile travel is distance/speed, rounded up to a tick.sounds: audio, anchor (chant,stroke,rune,cast,release,shot,hit,status,death,vanish,capture,capture_close), offset seconds/ticks, condition and confidence.timing_confidenceseparates sound dispatch from example phase placement. Rune/stroke repeat per rune; hit per shot. Quality <0.85 uses KAKUNIN/INVOKE; ≥0.85 uses MENU_MAGIC/INVOKE2. Sound requires interaction.statuses[].overlay,popup,soundsandduration_secondsdescribe each status./data/statuses.jsonalso includes this asvisual.- Knockdown requires flag 0x800: 40 ticks grounded and 10 getting up. Push requires 0x400 and decelerates over 30 ticks. Defeat plays die, waits 30 ticks, uses puff 272 for monsters, then removes after 80. Resolved capture uses
capture_timing: success closes at tick 2 and removes the unit at tick 33; failure closes/releases at tick 60. Book sequences 199/200 determine the end, independently of armed trap lifetime.
Walls (class 1) create ground structures, without a receiver or hit outcome. structure_timing preserves the 5 ticks between posts; targeting distinguishes line and circle. Elemental statuses apply on contact, not placement. The viewer allows adjusting line length.
Projectile orientation is in graphics[].rotation: the original artwork points upward and the ROM adds 90° to its travel angle. Trails inherit that angle; dark/light spin 16.875°/tick. The preview fixes the random initial phase to 0. The manifest has a revision to prevent browser reuse of earlier exports.
Spatial composition is illustrative, not a complete battle simulation. preview declares drawing speed (0.8 s/rune) and conventions; confidence/evidence distinguish decoded behavior from probable assignments. Follow-up extraccion/investigacion/visuals/SCENE_DISPATCH.md confirms chant/drawing sound dispatch, byte-7 status icons and Fallthing sound tables in code. Zero entries skip that sound. Other families have independent calls; fall and object_hit repeat per object, using a 20-tick descent as a preview convention.
visual_behavior distinguishes shared feedback (Holy Pulse: icon 11; Life Thread/Life Shine: icon 7), actor motion (Self Cannon: unit_motion, 2 px/tick, 12 px collision gap) and conditional statuses (Betrayal/Mutiny/Uprising: choices). caster_teleport (Transport and Flight) marks the caster's jump: departure flash on casting, arrival flash at arrival_effect_ticks (20) and position change at move_ticks (30), with no receiver or impact. Their probabilities are conditional on success; the viewer selects a branch without simulating a roll. own_graphic_confirmed describes dispatch in the original report, not whether shared feedback exists.
Blast/dragon composition and knockdown still require in-game verification. Phase synchronization, Missile's absolute phase placement, life donors and Self Cannon's self-damage are not fully reconstructed. Follow-up extraccion/investigacion/visuals/SCENE_RENDERING.md adds dragon_assembly (14 indexed parts, emergence 23+index and scales), explosion_assembly (main, ring and inward charging particles) and lock_timing (ticks 8/16/24). Dragon routing and preliminary Explosion positions remain illustrative. sounds[].duration_ticks stops chanting at cast and drawing at each stroke end; *-sustain clips use ROM samples/instruments with a 3 s preview hold. Resume continues held notes with their current offset and remaining duration. None of these findings is emulator-validated.
Status effects
/data/statuses.json has the 141 status records; a spell's status.id points to one. duration_ticks is how long it lasts (0: it acts once) and caused_by, the spells that cause it. The strength of the ones that hurt or heal is the spell's status.power. key says what it does:
key |
Effect |
|---|---|
stats |
Applies changes while it lasts |
virus |
changes; units within spread_px of a first victim catch it for the time it has left |
berserk |
changes; attacks its target and ignores orders. Cancels charm |
burn, strong_burn |
Fire damage equal to the strength every every_ticks, the first after every_ticks. It does not work in fords; water damage puts it out; it removes freeze and bubble. strong_burn replaces burn |
poison |
Dark damage every every_ticks, only while the unit moves or attacks |
vile_poison |
Dark damage every every_ticks |
regeneration |
Heals the strength every every_ticks, the first at once |
pulse |
element magic damage equal to the strength at once and after every_ticks |
wall_contact |
On touching a post: element damage (heal: healing) equal to the wall's strength, at most once every 120 ticks per post |
freeze, long_freeze |
Cannot attack or move; fire damage ends it |
sleep, deep_sleep |
Cannot attack or move; any damage wakes it |
bubble |
Cannot attack or move until it is hit twice |
charm |
Drops its orders and fights its own side. Cancels berserk |
terror |
Runs away from the unit that threatens it |
forget |
Stands still |
no_attack, no_move, no_damage |
Cannot attack / cannot move / takes no damage |
invincible |
Takes no damage, cannot attack or move, and cannot be pushed |
hidden |
Fades out over 127 ticks |
recoil |
Each hit it lands costs it share × the damage it deals |
weakness, halving |
×2 / ×½ element magic damage; one cancels the other |
cure |
Removes removes (all: every status and stat change; stats: stat changes, curses and auras) and applies changes if it has them |
terrain |
Marks the ground with change |
random |
Applies one of options by its chance |
none |
No effect |
Each item of changes has a stat (attack, defense, speed, magic, magic_defense, interval = time between attacks, accuracy, evasion) and a mode: percent → stat × (1 + amount); flat → stat + amount; gap → stat + (100 − stat) × amount.
- Statuses do not stack: applying one again restarts its time. Stat changes, auras and curses do stack, and they multiply.
- Every frame stats are rebuilt from the unit's own, applying its statuses from newest to oldest, then clamped: none below 0; attack, defense, speed, magic and
intervalat most twice the unit's own (intervalat least 1); magic defense at most 99; accuracy and evasion at most 150 over the unit's own. - Isaac and bosses are immune to
berserk,charm,terrorandforget.
Combat
Battle logic runs at 60 ticks per second. /data/combat.json has the constants from the code; each unit's own are in /data/units/<slug>.json.
Normal attack. gap = distance between centres − both units' radius_px.
- It starts a swing at
gap < attack_reach_pxand keeps it whilegap < attack_reach_px + attack.reach_pad_px. Withattack_reach_px0 it never attacks; when chasing it stops atattack.stop_gap_px. - Rhythm: wait
attack_interval_ticks, play theattackanimation; the hit or the shot comes outattack_strike_ticksafter it starts; when the animation ends, wait again. - Melee hit:
random(0..100) + accuracy − evasion ≥ 100. Ranged:random(0..99) < (accuracy − evasion) mod 256(a negative difference always hits). - Damage:
d = floor(attack / 2) − floor(defense / 4); ifd ≤ 0, nothing. With anattack_element: ×½ against the same element, ×1.5 against the opposite one (opposite). Shots do ×1.5 to flying units. Finallyfloor(d × (128 + r) / 128), withra random integer from −10 to 10.
Shots (the unit's projectile_type, projectiles in /data/combat.json): they leave from shot_start_px for the facing, straight towards where the target's centre was, at speed_px_per_tick, without following it. They travel at most attack_reach_px + reach_pad_px. They hit the nearest opposing unit within shot_hit_radius_px + its radius and do not go through. High obstacles stop them; water and pits do not. on_hit: stick stays stick_ticks on the target, vanish disappears. On a miss they bounce back and fade in shot_fade_ticks.
Sight. Every search_every_ticks a unit looks for enemies within max(sight_px, search_px) and chases while gap < sight_px. Mission scripts often change sight_px.
Spell damage. With the power at cast P (see "Spells and runes"), on each target:
- Element: no element, P; same element as the target,
−floor(P / 8); opposite,floor(3P / 2). - If above 0,
× (100 − magic defense) / 100(magic defense ≤ 99); if 0 or less, halve it (the same element heals about P/16). × (128 + r) / 128, withrrandom from −10 to 10.- If the target has more
weaknessthanhalvingstatuses for that element, ×2; with morehalving, ×½. Spells withhealadd P with no element, defense or randomness.
Bosses. Only each battle's boss unit casts spells (casts_spells in its file), when its script tells it to; there is no range check. It has its own MP (its mp stat), pays each rune at rank 0, and its power is power + magic, with no quality or rank.
Isaac's element levels. They start at growth.start. They only change on a battle's results screen. With old the level before the battle, c the spells of that element cast in it (one per spell, its last rune's element; cancelled chants don't count) and fav the element cast strictly the most (none on a tie):
old ≤ low_band_max: +low_band_many_gainifc ≥ many_casts, +1 if1 ≤ c < many_casts; +1 more if it isfav.old ≤ mid_band_max: +1 ifc ≥ many_casts; +1 more if it isfav.- Otherwise: +1 only if it is
fav, and then the opposite element loses 1 if its level wasdecay_minor more. - Then each level is clamped to
level_min–level_max. The level only changes the rank (floor((level − 1) / 10)), and the rank only changes the runes' MP, the power (×(rank + 4) / 4) and the status chance (×(rank + 4) / 4).
Characters
/data/characters.json lists the 19 characters who speak in the story, with their name, how many lines they have (lines) and faces: one picture per expression, in the game's order. The story scenes use these portraits through /juego/data.json (see "Scenes").
Battle maps and battles
/data/maps.json lists each battle map: the full image, the game's minimap, and its battles. A battle has its number (battle), the story missions that use it, and the units on the field when it starts:
uidandunit(the slug in/data/units.json) say who it is.x,yis its position on the map. With the sprite'soriginon that point, units stand on the walkable ground of the map.level, andnamed:truefor story characters (bosses and allies),falsefor regular monsters.
Each mission in missions also has objective (the text the game shows), conditions (how it ends: result victory, defeat, defeat_passed —a defeat that counts as passed— or draw, when trigger is met: no_enemies, boss_dies, isaac_dies, time (value in seconds), all_crystals, no_crystals, crystals_held (value crystals), isaac_reaches_area, boss_reaches_area, spell_cast (value = spell id), chest_opened, capture, draw…), time_limit_s (3600 when it sets none) and music (the song's n in /data/audio.json). When a mission has several battles, the list covers them all.
chests are the battle's chests: position x, y and odds, what it can give (item from /data/items.json, chance and the chance of each level in levels).
terrain is the map's collision and terrain, in map px (shapes may sit off the edge). There is no grid: units are pushed out of shapes every frame.
boxes(centrex,y±half_width,half_height) andcircles(x,y,radius), each with itskind:high_obstacle: stops walking and flying units and shots.low_obstacle: walking units only.- Zones, for walking units only (flying units are never affected):
magmaanddamage_floordeal 50 damage every 60 ticks (fire and no element);groundthe same, only when a spell heated it;fordhalf speed unless frozen, puts out and blocks burns;icenothing, half speed when a spell melts it;boghalf unless dried;sandnothing, half when turned to swamp;quicksandpushes 0.25 px per tick towardspush. - The 50 damage is multiplied by (118 + random(0..20)) / 128; a unit of the same element heals 6 and one of the opposite element takes 75. Water units ignore the ford and melted-ice slowing; earth units ignore bog, sand and swamp slowing and the push.
event_area(with its numberarea): missions ask whether units are inside; it blocks nothing.
squad_starts: where the player's squads appear. A squad's units are placed at that point plusteam.formation_pxfrom/data/rules.json.- Terrain spells (
terrainin/data/spells.json) change how zones behave inside a circle for 20 s; they do not change the shapes.
World map
/data/world-map.json describes the map Isaac travels between missions. picture is /juego/mundo.png; positions are pixels of that picture.
places: the 44 places, withid,x,yandneighbors, the places a path joins to it.regionis its position inregions: the name and description the game shows (name,description, inen,es,frandde); several places share a region.availability_group(0 to 15) is the switch that enables the place, andunlock_flagthe flag of/juego/data.jsonthat turns it on. According to the code, a place is enabled when its flag is on, and a path when both its places are.paths: the 50 paths, as pairs ofid. They are straight and go both ways. Isaac only goes to a place joined by a path, and each leg takesmovement.leg_frames(60) frames, short or long.offers: the missions and towns the map offers on each place.chapteris the chapter of/juego/data.json(qc01toqc06;sc01tosc03for the side chapters) andstep, the step inside the chapter.optionalistruewhen completing it does not close the step.objectiveis the text of its battles, empty when it only has scenes.scenesare ids of/juego/data.jsonandbattlescarriesbattleandmapof/data/maps.json(nullwhen the battle has no map page).- The same script can appear on two places: the game offers it on one or the other depending on a condition.
free_battles: the free battles the game registers on each place, with their chapter.- Arriving at a place that holds something starts it, and the game takes a checkpoint.
markers: the map's three markers. Red marks a place where a mission or a town is offered; blue, another kind of event, not identified; for green, what shows it is not known.confidencesays how each part is known:romcomes from the game's data;code, from reading its code, not checked in a play session.
The story: /juego/data.json
This file is the game's story logic, read from its scripts: which missions are open, what each one plays, what the player's answers change and which ending they reach. Paths inside it are relative to /juego/. Texts are in /juego/textos/<language>.json, under the same ids.
Game state
A game is four things, all starting empty:
- flags: numbered switches, on or off.
- counters: numbered values from 0 to 127. Counter
rules.compassionis the one that decides between two endings. - answers: for each scene, the option chosen in each choice slot.
- done: the missions completed.
Conditions
Many entries carry a list of conditions; all must hold. Each is [negate, test] (when negate is true, the test must fail):
| Test | Holds when |
|---|---|
["flag", n] |
Flag n is on |
["counter", n, op, k] |
Counter n compared with k (<, <=, >, >=, ==, !=) is true |
["answer", scene, slot, option] |
In that scene, that option was chosen in that slot |
["done", mission] |
That mission was completed |
["result", k] |
The mission's last battle ended by condition k: 2, no enemy is left alive; 3, the boss died. Both are wins |
Chapters and missions
chapters lists the chapters in order: qc01 to qc06 for the main story, sc01 to sc03 for side missions. Each chapter is a list of steps on the world map; each step offers missions as [mission, offer, conditions] and has flags to turn on when it ends:
- Only missions whose conditions hold are offered.
offer159 marks an optional mission. A step ends when one of its other missions is done (or any mission, if all are optional); then itsflagsturn on and the next step opens.- If no step is open (something must still be found), the last step that offered missions stays open so its places can be visited again.
- The current chapter is the first
qc0Nwhose flag inrules.chapterFlags(one per chapter, 1 to 4) is off. With all four on, the story continues inqc06if flagrules.route6Flagis on, and inqc05otherwise. Side chapters run at the same time.
missions describes each mission: kind (m battle, t town, r free battle) and a timeline played in order, skipping entries whose conditions (the last element) fail:
| Entry | Effect |
|---|---|
["scene", id, how, conditions] |
Play that scene; how is 32 for a scene on its own, 29 or 31 for one shown at the battle that follows |
["battle", id, conditions] |
Fight that battle |
["flag", n, value, conditions] |
Set flag n to value (1 is on) |
["counter", n, delta, conditions] |
Add delta to counter n, kept between 0 and 127 |
At the end the mission counts as done. Mission and chapter names are in the text files (missions, chapters).
Scenes
scenes[id].blocks holds the scene's blocks; it starts in main and runs its items in order:
| Item | Meaning |
|---|---|
["s", background, cameraX, cameraY, cast] |
What the top screen shows from now on |
["l", speaker, line] |
A line of dialogue: names[speaker] says scenes[id].lines[line] |
["c", slot, [[line, block], …]] |
The player chooses: each option's text is scenes[id].lines[line]. Record the option's position as the answer for that slot, then continue in its block |
["n", block] |
Continue in that block |
The scene ends when a block runs out of items. Lines keep the game's own line breaks (\n). Isaac is the hero's default name. [L], [B] and [+Control Pad] (in each language's words) stand for the console's buttons.
To draw the top screen (256×192):
- Background:
bgs[background]is[picture, width, height]. Show the part that starts atx = clamp(-cameraX, 0, width - 256),y = clamp(-cameraY, 0, height - 192). - Characters: each
[actor, x, y, expression, mirror]ofcastis a portrait.portraits["<actor>_<expression>"]is[picture, dx, dy, width, height]: its top-left corner goes at(x + dx, 160 + y + dy), mirrored horizontally whenmirroris 1.names[actor]is who it is.
Endings
When a mission turns on flag endings.gameOverFlag, the game ends: the ending scene is the one paired with the first flag that is on in endings.byFlag ([flag, scene]), or endings.default. Their titles are in the text files (endings).
Battles in the story
battles[id] is a battle of the story: map (its picture), size, and units as [uid, x, y, level, character] (character is not 0 for story characters). The same battles, with names, are in /data/maps.json.
Text files
/juego/textos/<language>.json has, for that language: names (speakers, by number), scenes (each scene's title and lines), missions, chapters, endings and units (unit names by uid). Its ui holds the labels the site's novel uses; you can ignore it.
Audio
/data/audio.json has music (code is the song's name in the game) and effects grouped by category. Songs that loop are recorded playing the loop twice and fading out. In the game, a song with loop_end_s jumps back to loop_start_s when it reaches loop_end_s and plays forever; one with end_s plays once. missions and scenes count the missions and scenes that use it (each mission's is in /data/maps.json), and screen is the fixed screen that uses it: world_map, mission_complete, victory and defeat (when a battle ends), game_over, title or ending.
sound_events says what plays the sound effects of the menus, the world map, the results screen, treasure chests and mana crystals. It is read from the game's code (evidence: static): each place that plays a sound and the number it loads; it was not observed in an emulator. The game plays sound number N as entry N − 1 of the selected sound archive. That is normally SEQ_SE, the "Game" category of effects.
events:group(menu,world_map,results,chest,crystal),event,sounds(id,name,url) andcalls, the places in the code that play it. In menus,cursor,confirmandcancelare identified;othergathers the remaining menu sounds, whose triggering action on each screen was not traced. On the map:move(one leg of a path),enter_event(arriving at a scene or a town) andenter_battle. Results:exp_count(each step of the experience count) andlevel_up. Chest:openanditem(the item obtained). Crystal:capturedandlost.download: while the duel mode's Download screen is open, the game uses another archive,SEQ_SE_CURSORS. In it, numbers 2, 3, 4 and 108 play their own versions (replacesis the normal sound of that number).urlisnullwhen the site has no clip of it.
Other pictures
/data/gallery.json groups the rest of the art by section: runes (the shape of each one, as a picture), spells and battle effects, backgrounds, world map and its markers, interface, and unused or test art. /juego/mundo.png is the world map.
Not in the data
Your version has to define these:
- Which orders produce the AI's states 6, 7, 8 and 12 (
unresolved_statesin/data/ai.json) and how the AI looks in a battle: what is published comes from the code, not from an emulator.
The graphics, texts and music belong to Taito and Ubisoft.