Battle: capture notes
The save-state diffs and watchpoint captures behind the battle subsystem's claims. These are the measurements, kept for anyone re-deriving a claim; the conclusions live on the main page and the struct tables on battle internals. Falsified readings live on do-not-re-walk.
Encounter trigger - runtime memory layout
A pre/post encounter save pair (one frame walking the map01 field scene; the next frame with battle just initiated, same map01 scene) pins the runtime memory layout of an encounter trigger. The mednafen-state diff over 0x801C0000..0x80200000 surfaces:
| Range | Bytes changed | What it is |
|---|---|---|
0x801CE808..0x801F3818 | ~133 KB | Battle overlay loaded into RAM (single contiguous region) |
0x801C9370..0x801C9900 | ~200–500 B | 8-slot battle actor pointer table; stride 0x60 per slot |
0x80083000..0x80084000 | ~600 B | Scene-bundle / sound-pool: encounter formation + BGM resolution |
The active scene-name table at 0x80084540 is identical between the pre-encounter and post-encounter saves - the battle is layered on top of the field scene rather than swapping it out.
Battle scene-init residency window
A separate map01 save pair (one frame with the encounter armed but battle not yet entered, the next frame with battle just initiated) pins the post-load residency window of the battle scene-init pipeline. Distinct from the encounter-trigger overlay swap above; this pair brackets the loader function with concrete RAM-resident artefacts the loader writes into.
| Range | Bytes changed | What it is |
|---|---|---|
0x80124690..0x801503C4 | ~168 KB | Battle-bundle residency window. Pre-battle holds field-scene payload; post-battle holds battle-bundle data. |
0x801CE808..0x801D3018 | ~16 KB | Battle-overlay scratch slice. Wholesale reset on entry. |
0x800836C8 | 4 B | Per-frame actor-tick fn-pointer slot. Pre-battle = 0x80024C50; post-battle = FUN_80021DF4. |
0x801FFCA0..0x801FFFFE | ~600 B | CD I/O state slice; rewires while the battle bundle is paged in. |
The pair is post-load by design - both save frames resolve to a state where the loader function has already returned. The loader itself (which reads PROT entry 0x05C4 + sibling Seru blobs and populates the battle bundle) lives in an overlay slice not visible in either snapshot; only a mid-execution capture between the field→battle mode flip and this residency state could pin it directly.
Codified as engine_core::capture_observations::battle_init_overlay; disc-gated test battle_init_overlay_pair_pins_battle_bundle_window_and_actor_tick_wiring.
Item-use battle-event residency
A mid-battle save pair (battle just initiated; party member about to use a Healing Leaf) pins the item-use sub-mode residency:
| Address | Pre → Post | Notes |
|---|---|---|
_DAT_8007B8D0 | 0x8014BD30 → 0x800ABA4C | Field-pack base pointer flips. The item-use sub-mode reseats the active scene asset buffer. |
0x801BA7DC..0x801BADEC | ~660 B shift | Script-VM context block. The menu / item / target / commit pipeline rewrites the entire ctx region. |
| Actor pool slots 0..4 | per-frame motion deltas | 3 party + 2 monsters (count-2 formation). Slots 5..7 stay zero across the pair. |
The captured pair uses a Healing Leaf (consumable HP-restore), not a spell-learn item, so it pins the residency window of the item-use battle-event handler but says nothing about the spell-learn writer to the displayed-skills array at +0x185 - that writer needs its own capture pair.
Codified as engine_core::capture_observations::item_use_battle_event; disc-gated test item_use_pair_pins_field_pack_base_flip_and_script_vm_ctx_shift.
Captured stat-growth observations
The mednafen-state diff toolkit over a magic-rank-up + character-level-up save triplet pins the per-byte footprint for Vahn (party slot 0). The deltas inside Vahn's character record at 0x80084708 (stride 0x414):
| Event | Offset | Before → After | Interpretation |
|---|---|---|---|
| Magic-rank up (pre → post) | +0x08 | 0x30 → 0x3C | flag word low byte (+12) |
| Magic-rank up | +0x9C | 0x09 → 0x0A | magic-rank counter (+1) |
| Magic-rank up | +0x10A | 0x1B → 0x11 | low byte of mp_cur (cast cost spent) |
| Magic-rank up | +0x161 | 0x02 → 0x03 | spell-level array (spell_levels[0] +1) |
| Level-up, 4-level jump (pre → post) | +0x00 | 0x4F → 0x73 | unconfirmed (jump +0x24 doesn't match single-level granularity) |
| Level-up | +0x04..+0x06 | 0x016D → 0x02DA | u16 LE XP delta (+365) |
| Level-up | +0x10E | 0x3A → 0x42 | low byte of ap_cur (AP / arts gauge refill, +8) |
| Level-up | +0x11C..+0x12C | six per-byte +1..+4 | per-stat increments at byte stride 2 |
| Level-up | +0x130 | 0x02 → 0x03 | displayed character level (+1 per level-up event; see level-up) |
The retail per-level growth source is in SCUS_942.54: the per-stat 98-entry curves at DAT_800769CC (stride 0x62) + the parameter block at DAT_80076918 that selects each stat's curve row, read and applied by the overlay level-up function FUN_801E9504. The engine applies them: legaia_asset::level_up_tables::growth_tables_from_scus parses the tables and LevelUpTracker::with_growth_tables installs per-character StatGrowthCurve::PerLevel (all 8 stats) at boot, byte-validated against the captured Noa L2→L3 single-level deltas - see level-up.