Randomizer internals
The engineering companion to the randomizer: how an edit travels from a PROT entry to a rewritten disc sector, the six techniques every feature is built from, and - for the features that inject hand-assembled MIPS code - where that code is allowed to live, why the room is so scarce, and the two traps that bite anyone who adds a hook. Read the main page first; this one assumes its vocabulary.
At a glance
- Crate
crates/patcher(legaia_patcher); disc bridgedisc::DiscPatcher, orchestrationapply, patch outputppf- Three capabilities
- LZS encoder (
legaia_lzs::compress), Mode 2/2352 sector write-back with EDC/ECC (legaia_iso::write), PROT-entry → LBA addressing (DiscPatcher) - Editing rule
- Same-size in place; only doors and place names resize a record, through MAN relocation
- Code hosts
- SCUS rodata gap
0x8007AB38(1028 B), the SCUS arena (652 B in four regions), reference-free dead runs inside overlays 0898 / 0899 / 0958, theDMY.DATannex - Detour shape
- Two words at the hook site become
j routine+nop; the routine replays the two displaced instructions and jumps back - Guards
- Every hook word must match the US build; every landing zone must be all-zero, outside every known table, and read-watch-verified unreferenced
- Confidence
- Confirmed: disc oracles pin every byte; the MIPS hooks are additionally verified by emulator playtest (the from-scratch engine cannot execute them)
How a patch reaches the disc
A PlayStation disc is not a folder of files. Almost every value the randomizer changes sits inside a compressed block, on a sector whose checksums must be recomputed, at an address nothing else may move. Three capabilities the preservation track never needed make that editable.
| Capability | Module | Why it is needed |
|---|---|---|
| LZS encoder | legaia_lzs::compress | Retail ships only a decoder. Monster records, scene MANs and art records are LZS streams, so editing one means producing a stream the retail decoder accepts - and packing it no larger than the original. LZS → |
| Sector write-back | legaia_iso::write | Overwriting a sector's 2048-byte payload also means recomputing its 4-byte EDC and 276-byte P/Q ECC, or the drive reads it as corrupt. Disc geometry → |
| Disc bridge | legaia_patcher::disc::DiscPatcher | Turns "byte K of PROT entry N" into physical sector writes through the PROT.DAT table of contents. |
DiscPatcher::patch_prot_entry. patch_named_file is the sibling for edits straight into SCUS_942.54; patch_monster_slot wraps the battle_data archive.The editing rule
- Same size, in place. No sector address, PROT entry or ISO directory record ever moves, so a patch is a pure byte overwrite plus checksum recompute. The disc image's size never changes.
- Fixed slots pad; tight streams re-pack. A monster record is re-emitted zero-padded back to its
0x14000-byte slot in the battle_data archive (PROT 867). A scene MAN has no compressed slack, so the encoder's lazy matching has to fit the exact original span; the rare stream that overflows is skipped and reported. - Budget from the boundary. A MAN's re-pack budget is its span up to the next descriptor, not its current compressed length - otherwise a second pass over the same scene (encounters, then chests, then shops) would shrink the budget each time.
- Doors and place names are the exception. A destination carries its scene name inline and a banner name is stored unpadded, so re-pointing or renaming changes a record's length. The MAN relocation engine fixes every offset the resize disturbs and rewrites the descriptor's size word.
The error-correction math is the generic CD-ROM scheme (ECMA-130): EDC is a CRC over reversed polynomial 0xD8018001, ECC is Reed-Solomon P/Q over GF(28) with generator 0x11D, and the header is treated as zero so parity is independent of the sector's address. It embeds no game bytes; the disc-gated ecc_real test reproduces real PROT.DAT sectors bit-for-bit.
The technique ladder
Every feature uses one of six techniques, from "overwrite a byte in a table" up to "host new machine code in unused overlay space". The disc-patching write-ups walk each tier with a worked example.
| Tier | Technique | What changes | Features |
|---|---|---|---|
| A | Static table | Bytes in a SCUS_942.54 data table, same size | Steals, spell costs, equip bonuses and masks, casino prizes, item prices, Seru Bell name |
| B | LZS slot | Decompress, mutate, recompress, pad back into a fixed slot | Drops, monster stats, arts power and combos, difficulty scale, custom monster models |
| C | Script operand | An operand byte inside a scene MAN's field-VM script, same size, MAN re-packed | Chests, shops, encounters, house doors, inn costs, Earth Egg threshold |
| D | MAN relocation | A record grows or shrinks; every dependent offset is fixed | Doors, place names, starting-bag overflow, Delilas Challenge script, custom-item narration |
| E | Code in the rodata gap | A hand-assembled routine in preserved SCUS padding, reached by a two-word detour | Equipment drops, run-away EXP, enemy ally, Seru Bell string |
| F | Overlay dead region / arena | Code in an overlay's reference-free zero run, or in the verified-dead SCUS arena | Shiny Seru, Seru trading, Super Arts list, Super Arts Pack, arts AP override, Delilas Challenge, Delilas party swap, custom items |
Code hooks: where injected code lives
A detour overwrites two instructions at a hook site with j routine + nop. The routine does its work, replays the two displaced instructions, and jumps back to the word after the site. On the PSX all resident RAM is executable, so a routine written into a dead data region runs when jumped to. Every feature below guards on the hook words matching the US build and refuses a differently-laid-out image rather than corrupting it.
| Feature | Hook site(s) | Host image | Code lives at | What the routine does |
|---|---|---|---|---|
| Equipment drops | 0x8004F610 in the reward routine, right after the normal drop grant | SCUS | rodata gap 0x8007AB80 + id table | Rolls rand() % 100 < chance, picks a random id from an embedded equipment table, calls the inventory-add helper once per battle |
| Run-away EXP | 0x801E5A10, the successful-escape teardown (state 0x66) | PROT 0898 → SCUS | rodata gap 0x8007AD00 | Sums the formation's EXP, scales to the percentage, banks it into every member's cumulative-XP cell (cap 9,999,999) without calling the level processor |
| Enemy ally | 0x80051990 after battle setup's monster loop; one word at 0x801E6638 | SCUS + PROT 0898 | rodata gap 0x8007ACA0 | Rolls the chance and ORs the AI-delegated flag 0x380 into the frontmost enemy; the overlay word widens the victory mask so the ally counts as down |
| Shiny Seru | Nine detours: setup, capture, grant, grant-shift, damage, menu, battle-menu, summon-fade, caption | SCUS + 0898 + 0899 | the four arena regions | Boosts a capturable enemy ×135/100, marks it, carries the mark through capture into a per-slot shiny byte at record +0x788, and applies +35% on every cast |
| Seru trading | Two picker edits in the shop menu | PROT 0899 | 0899's own dead run (~3.8 KB), from 0x801E7FB0 | Adds a Trade row and hosts the whole trade screen - render, slide, cursor, confirm, swap - plus the seed-derived 64-bucket offer table. Touches no SCUS bytes |
| Show Super Arts (battle list) | Count read 0x800343C4, id read 0x80034450, applier arm 0x801EFBCC, pager 0x801D3748 | SCUS + PROT 0898 | all four arena regions (613 B) | Synthesises a 20-byte arts-table record in dead space and enters the list renderer's hit arm with it, so retail draws the row; the pager is replaced in place |
| Show Super Arts (Moves page) | Count reads 0x801D4454 / 0x801D4440, scan entry 0x801D4480, cursor bound 0x801DA64C | PROT 0899 | dead runs 0x801E7FB0.. and 0x801E65F4.. | The same merge for the pause menu's panel renderer; composes with everything because it uses no SCUS bytes |
| Super Arts Pack | Six applier retargets, three jumps, battle-load stub at 0x80055DBC | SCUS + PROT 0898 | 56 B at arena 1 head; the 4 KB block in the DMY.DAT annex, streamed to 0x801FD000 at battle load | Doubles the applier's per-character stride so its find/replace tables point at the pack's wider tables; the stub reads the block with the game's own synchronous reader and flushes the I-cache |
| Arts AP override | Three detours in the party arts queue-builder | PROT 0898 → SCUS | arena regions + a 4×32 i8 config table | Replaces the computed per-command cost with a per-(character, row) value, or turns it into a grant; rewrites the menu AP byte alongside |
| Delilas Challenge (dome) | Seed detour 0x801CEBCC, payout 0x801D1118, display 0x801D125C, settle 0x801D114C | PROT 0977 → SCUS | arena 1 (seed routine + relocated hub template); grant cave 0x800352EC; display override over FUN_800260DC | Adds course 3 to the arena's descriptor table, pays 5000 for it, and grants the reward item(s) on a cleared settle |
| Delilas party swap (cast route) | Queue hook in the arts matcher; strike-loop fetch 0x801E374C; label gate 0x801E43D0; thirteen module damage retargets | SCUS + 0898 + 0958/0959/0960 | rodata gap tail, arena 1 / arena 2 / slot 6, PROT 958's dead party-wipe body | Converts a matched signature combo into a capture-class cast, deferring when the starter is not at the queue base; halves the Hyper admission tier |
| Jewel fix | Thirteen jal words across PROT 944 / 952 / 953 / 958 / 959 / 960 | cast modules | none (word edits) | Retargets the resist-bypassing damage wrapper to the guard-respecting one, so boss signature casts respect Jewels and guards |
| Approach-softlock fix | Nine words at 0x801E3568 in the state-0x19 range poll | PROT 0898 | none (in-place rewrite) | Replaces a redundant facing recompute with a guard that bounces a monster whose Move clip died back to state 0x14, which re-stages the approach |
| Starting bag / level | 40 bytes at 0x80034B04 in the new-game data-init | SCUS | none (the seed code itself) | Rewrites the code that builds the opening inventory (one packed halfword store per slot, cap 7) and stamps the party level; overflow items are spliced into town01's entry script as silent give-item ops |
| Seru Bell name | Name pointer of item 0xFD | SCUS | rodata gap 0x8007AB40 | Writes the string into loader-preserved padding and repoints one name pointer |
| Spirit / enemy-damage AP | The accrual tails in 0898 | PROT 0898 | none (same-size word rewrites) | Changes the AP constants; a negative value also installs a signed floor - see the signed tail |
The dead-space map
"Dead" is a runtime property, not a file-byte one: a region qualifies only when it is all-zero in the clean image, constant across states, outside every known table, and read-watch-verified unreferenced on a live PCSX-Redux session. These are the regions that pass.
| Region | Span | Size | Nature | Tenants |
|---|---|---|---|---|
| Rodata gap | 0x8007AB38..0x8007AF3C | 1028 B | SCUS rodata padding the loader preserves (flanking constants survive file → RAM) | Seru Bell string +0x08; equipment-drop routine + table 0x8007AB80..0x8007ACA0; enemy-ally 0x8007ACA0; flee-EXP 0x8007AD00. All coexist |
SCUS_GAP | 0x80077728..0x80077828 | 256 B | Arena; bounded above by the steal table | Arena claimants (exclusive) |
ARENA1 | 0x8007AE00..0x8007AF00 | 256 B | Arena; the SsAPI I/O table begins exactly at 0x8007AF00 | Arena claimants (exclusive) |
ARENA2 | 0x8007AFF8..0x8007B040 | 72 B | Arena; a pocket between two SsAPI tables | Arena claimants (exclusive) |
SLOT6 | 0x80078A88..0x80078ACC | 68 B | Arena; padding between the 0x80078xxx tables | Arena claimants (exclusive) |
| 0899 dead runs | 0x801E7FB0.., 0x801E65F4.. | ~3.8 KB | Reference-free zero runs inside the menu overlay image; read/write-watched across a full pause-menu tour | Seru trading; Super Arts Moves page (past trading's highest blob) |
| 0958 wipe body | inside PROT 0958 | a few words | The module's dead party-wipe body | Party-swap cast stubs |
| Free RAM | 0x801FD000..0x801FE000 | 4 KB | Not on the disc: all-zero in every state, no overlay covers it, deepest stack use 1388 B above it | Super Arts Pack block, streamed from the DMY.DAT annex at battle load |
The injected-code arena budget
Every hand-assembled SCUS-resident feature that needs more than the rodata gap offers lands in the same four verified-dead regions, and they add up to 652 bytes. The limit is arithmetic, not policy: a sweep of every zero run in the executable outside the known live tables finds only 53 further candidate bytes.
| Region | Size | Shiny Seru uses | Show Super Arts uses |
|---|---|---|---|
SCUS_GAP | 256 B | 246 B | 253 B (routine B, scratch record, glyph buffer) |
ARENA1 | 256 B | 252 B | 256 B (routine F, packed arrows) |
ARENA2 | 72 B | 64 B | 44 B (routine A) |
SLOT6 | 68 B | 56 B | 60 B (fifteen Super Art records) |
| Total | 652 B | 618 B | 613 B |
Shiny Seru leaves 34 bytes in fragments of 4, 5, 8 and 12; nothing else fits beside it. So the arena claimants are mutually exclusive, enforced by the CLI and the browser patcher:
| Claimant | Needs | Conflict rule |
|---|---|---|
--shiny-seru | 618 B across all four | Yields to the Delilas Challenge with a note; refused alongside the others |
--show-super-arts | 613 B across all four | Refused alongside any other claimant (it also detours the same applier the Pack retargets) |
--super-arts-pack | 56 B at the head of ARENA1 | Refused alongside any other claimant |
--arts-ap-grant / --arts-ap-cost | Routines + config table in the arena | Hard conflict with everything, including the Challenge |
--delilas-challenge | ARENA1 (seed routine + relocated hub template) | Takes precedence over shiny Seru - the presets that enable both install the Challenge |
| Delilas party swap (cast route) | Rodata-gap tail + ARENA1 / ARENA2 / SLOT6 | Resolves silently: when another claimant owns the gap the swap still applies, but that slot keeps the art-side signature; the summary says so |
The Super Arts list payload cannot move into the battle overlay: the arts list is a shared window widget whose renderer has one reference on the disc, from a dispatcher the field and menu overlays call 64 times between them, so it runs when PROT 0898 is not resident. A Super Art damage row claims no arena and composes with everything. Growing PROT 0898 to cover the Pack's block was rejected because it would zero 0x801F7018..0x801FD000 - persistent effect and world state the field overlay leaves there - on every battle load.
Three traps in the MIPS injection features
Each has bitten a shipped hook at least once; each is guarded now.
| Trap | What goes wrong | The rule |
|---|---|---|
| R3000 load-delay slot | A just-loaded register is not ready for the very next instruction. A routine that reads one in the instruction after its load reads the stale value; the shiny-Seru boost loop cascades into garbage without it | A filler instruction (or a reorder) between every load and its first use; never split a mult/mflo. The in-crate mips_sim interpreter is load-delay-accurate and runs the stubs in unit tests |
| "Zero is not dead" | A run of zero bytes is reusable only if no code reads it. Live indexed tables are full of zero padding the game still indexes | Regions must be all-zero in the clean image, constant across states, outside SCUS_TABLE_RANGES / OVERLAY_TABLE_RANGES (the structural assert_not_in_tables guard), and read-watched on a live battle that uses an item, plays a victory pose and casts a summon |
| Unaligned entry | The j encoding drops the target's low two bits, and zero-run scans return run starts that are frequently unaligned - so a routine placed at one jumps 2-3 bytes into garbage | Every routine VA is rounded up to a word boundary; place refuses an unaligned routine. Only byte-addressed data may sit unaligned |
The three times "zero is not dead" bit
| Region that looked dead | Reader | Symptom |
|---|---|---|
Victory mouth-override table 0x80077E80 (rows 0x800781B0..) | FUN_8004C7B4, the victory face animator | Routine bytes read as facial keyframes: a corrupted mouth on the victory pose |
Move-power table 0x801F4F5C, records 4..8 | The move-power lookup | Six move ids (0x07, 0x12..0x15, 0x19) read routine bytes as power records: garbage damage |
SsAPI sound tables 0x800794F0 (and the former arenas at 0x8007075C / 0x80079340) | FUN_8005D0B8, the item-use sound engine | Using a Healing Leaf indexed straight into the bitmap; the sound-synced item banner never dismissed - the Tetsu-tutorial freeze |
The same lesson shaped two data choices: the shiny flag lives in a parallel per-slot byte array at record +0x1C0 rather than the spell-level byte's free 0x80 bit (a shared level-up/display function reads that byte unmasked and indexed a table out of bounds), and the Seru Bell string sits in rodata padding rather than the data segment's trailing zero-fill (that fill is .sbss scratch the game overwrites every frame).
The signed accrual tail
The Spirit and enemy-damage AP sliders accept negative values, which invert the knob: Spirit then spends AP, or being hit drains it. The gauge is a u16 and every retail site that grows it clamps only against the 100 ceiling, so a negative constant alone would read back as +200-odd and pin the gauge at 100. The consumer has to honour the sign as well.
- Damage site: the ceiling words are dead once the site only subtracts, so a
bgezfloor fits in them. - Spirit site: its add/clamp tail is shared with the +8 every non-Spirit action grants, so the ceiling must survive beside the new floor. They fit because a negative setting makes the AP-Boost-1 accessory arm dead, and the head of that arm hosts the relocated over-100 clamp. Consequence: while a slider is negative, the "spirit gain up" accessory is inert.
- No arena involved: every word is a same-size rewrite inside PROT 0898; the two sliders compose with each other and with everything else, and the retail value restores the stock bytes exactly.
The rewritten Spirit tail
801e5e78 bgez v1,0x801e5e84
801e5e7c _slti v0,v1,0x65 ; delay slot: ceiling test
801e5e80 move v1,zero ; floor at 0
801e5e84 beq v0,zero,0x801e5e40 ; over 100 -> the relocated clamp
801e5e88 _sh v1,0x170(s3) ; delay slot: the store
Both boost arms read the staged accrual unsigned, which is what makes the AP-Boost-1 arm dead under a negative setting. The negative modes are statically verified only: the disc oracles prove which words land where and that the hand-assembled branches resolve to these instructions; a live battle playtest has not been run.
Crate modules
One machinery, three families on top: the randomizer, the translation packs and the manual edits. Each module is one row.
Module map
| Module | Role |
|---|---|
rng | Version-stable SplitMix64; the first output for seed 0 is pinned by a test. |
items / equipment | Valid item-id pool from the SCUS item-name table; equipment ids recovered by matching curated names against the disc's own table (~150 ids, no Sony bytes). |
drops / monster | Drop-table planner (shuffle preserves the multiset); slot re-pack decompress → mutate → recompress → pad to 0x14000. |
encounter / kingdom | Formation shuffle per scene, kingdom or world; the kingdom partition of the scene namespace comes from CDNAME anchored on the overworld maps. |
chest / shop / door / house_door | Opcode-aware MAN walks for give-item (0x39), vendor (0x49), scene-change (0x3F) and cross-context move sites; doors go through man_edit. |
casino / steal / spell_cost / equip_* | Same-size static-table edits in the menu overlay (casino, raw file offset 0x15D00) and SCUS_942.54. |
monster_stats / move_power / element_affinity | Battle tuning: per-stat column redistribution over PROT 867 with tutorial enemies and bosses pinned; move-power halfwords and the 8×8 affinity matrix in PROT 0898. |
starting_items / starting_bag / starting_level | Rewrite the new-game seed code; splice overflow gives into town01; stamp level and recompute growth templates for Vahn / Noa / Gala. |
bonus_drop / flee_exp / enemy_ally / shiny_seru | The SCUS-resident MIPS hooks of the table above. |
seru_overlay / super_art_list / super_art_menu / super_arts_pack / arts_ap_grant | The overlay- and arena-resident hooks. |
delilas_dome / delilas_challenge / delilas_cast / custom_items / nivora_field | The Delilas features: dome course injection, koin1 script edit, cast route, custom item set, field-form mesh swap. |
jewel_fix / approach_fix | The two seedless retail-bug fixes. |
unused / item_name / texture / model | Cut-content ids, the Seru Bell name, PNG → TIM texture swaps, custom monster models. |
mips_sim | In-crate MIPS interpreter (load-delay-accurate) the hook stubs execute under in unit tests. |
disc / apply / ppf | The disc bridge; the orchestration every CLI verb drives, each returning an apply report; PPF 3.0 diff / write / apply. |
Tests
Three layers. Disc-gated tests read LEGAIA_DISC_BIN; with it unset they skip and pass.
| Layer | Where | What it proves |
|---|---|---|
| Disc-free unit tests | crates/lzs, crates/iso, crates/patcher | The encoder round-trips; sector encoding is idempotent and address-independent; the planner is seed-deterministic; hook stubs behave under mips_sim; a synthetic disc round-trips through the disc → ISO → PROT chain. |
| Disc round-trip oracles | crates/patcher/tests/*_real.rs | Patch a feature onto a scratch copy of a real disc, re-decode off the patched image, assert the multiset or invariant is preserved, every touched sector is EDC/ECC-valid, neighbours are untouched, and a fixed seed is byte-deterministic. For code hooks: the stock words match, the detour and routine decode as the hand-assembled bytes, and the build guard refuses an unknown layout. |
| Runtime oracles | crates/engine-core/tests/*_randomizer_runtime_e2e.rs | Drive the from-scratch engine's own grant / spawn / warp kernel over the patched bytes - chest, drop, encounter, steal, door - so a patch a savestate would mask (the scene or archive already resident in RAM) is observed. The engine cannot execute injected MIPS, so hook features are covered by the disc oracle plus an emulator playtest instead. |
Per-feature oracle inventory
| Oracle | Asserts |
|---|---|
ecc_real, lzs_compress_roundtrip_real | Real PROT.DAT sectors re-encode bit-for-bit; real monster records and container sections round-trip and compress. |
disc_patch_real, rando_cli_real | One drop patch re-decodes with neighbours untouched; a full-archive shuffle diffs into a PPF that reproduces the patched image. |
encounter_patch_real | Counts and id multiset preserved per scene, ids in-pool; every scripted or boss formation (Tetsu 0x4F among them) byte-identical. |
chest_patch_real, shop_*, casino_* | Give-item site offsets unchanged, item multisets preserved; a targeted keikoku chest carries the new id in both the give operand and every announcement token. |
steal_patch_real | Steal-item multiset preserved, every chance byte untouched. |
door_*, house_door_* | Destination multisets preserved, names valid, sectors valid, image size unchanged; a census test pins per-scene entry/exit counts against a live-captured anchor. |
equipment_drops_real, flee_exp_real, enemy_ally_real, shiny_seru_real | Each hook site holds its stock words; detour + routine + tables decode as planned; edits are surgical and compose in the shared gap; every landing region is dead and outside every live table. |
seru_trade_real | Every piece lands in 0899, the schedule round-trips to the kernel's offers, the SCUS gap is untouched. |
super_art_list_real, super_art_power_real, super_arts_pack_real, arts_ap_grant_real | Arena accounting intact; the Pack's runtime oracle executes the patched retail applier over a live queue and checks all fifteen added chains fire. |
jewel_fix_real, approach_fix_real | Baseline words stock, exactly the planned words change, module windows contain no other module's bytes, re-application is refused or a no-op. |
delilas_*_real, nivora_field_real, party_swap_real | The dome, cast, mirror and field-form halves of the Delilas features. |
unused_content_real, monster_stats_*, move_power_*, element_affinity_*, spell_cost_*, equip_*, weapon_specialty_* | The remaining table and slot edits, each with a baseline pass so the oracle is non-vacuous. |
No-Sony-bytes hygiene
- The crate never embeds, commits or redistributes game bytes. Injected routines are the project's own code; item and equipment ids come from the user's disc at patch time.
- A patched
.bincontains Sony data and is never committed. The distribution form is a patcher plus seed, or the PPF the CLI emits - a PPF carries only the deltas between the user's original disc and the patched one and is meaningless without the original. - Disc-gated tests skip without
LEGAIA_DISC_BIN; CI runs without disc data.
How we know
| Function | Address | What it proves | Dump |
|---|---|---|---|
| LZS decoder | FUN_8001A55C | The stream format the encoder must produce; 4 KB ring initialised to zeros | ghidra/scripts/funcs/8001a55c.txt |
| PROT entry size | FUN_8003E68C | An entry's size is the sector gap to the next entry - the re-pack budget | funcs/8003e68c.txt |
| Battle reward routine | FUN_8004E568, join 0x8004F610 | Spoils tally once per battle; the equipment-drop hook site | funcs/8004e568.txt |
| Battle setup | FUN_800513F0, 0x80051990 / 0x80051A20 | Actor table populated after the monster loop; the enemy-ally and shiny-Seru setup sites | funcs/800513f0.txt |
| Battle-action SM | FUN_801E295C, state 0x66 at 0x801E5A10 | Successful-escape teardown runs once; the flee-EXP hook site | funcs/overlay_battle_action_801e295c.txt |
| Arts list renderer | FUN_80034358, one caller 0x8003238C | Draws rows from a 20-byte record found by a linear scan - the record the Super Arts list synthesises | funcs/80034358.txt |
| Super-Art applier | FUN_801EF9E4 | Find/replace tables addressed by a per-character stride in t5 - the Pack's retarget | funcs/overlay_battle_action_801ef9e4.txt |
| Party arts queue-builder | FUN_801EED1C | AP cost is computed (multiplier × commands), not stored - why the AP override needs a hook | funcs/overlay_battle_action_801eed1c.txt |
| New-game data-init | FUN_80034A6C, 0x80034B04 | The opening bag is code-built; both callers memset the block, so the zero-loop is reclaimable | funcs/80034a6c.txt |
| Synchronous CD reader | FUN_8005E4D4 | The read primitive the Pack's battle-load stub calls | funcs/8005e4d4.txt |
| Damage wrappers | FUN_801DD6B4 / FUN_801DD4B0 | The resist-bypassing and guard-respecting wrappers the jewel fix swaps | funcs/overlay_battle_action_801dd6b4.txt |
| Read-watch sessions | PCSX-Redux Lua probes | Every arena region unreferenced across item use, victory pose and a summon cast; the 0899 dead runs untouched across a pause-menu tour | PCSX-Redux automation |