The technique in one box
Walk each script record instruction by instruction from its true entry point, skipping inline dialogue, until the target opcode is reached; overwrite its operand at the same size; recompress the whole script and confirm it still fits the space it came from. The trap is mistaking a dialogue byte for an instruction - a raw byte scan will.

At a glance

Target
An opcode's inline operand in a scene's MAN (the per-scene script-and-data bundle, LZS-compressed)
Find
Opcode-aware, dialogue-skipping walk with the field-VM disassembler (legaia_asset::field_disasm)
Write
Same-size operand overwrite → recompress → must fit the footprint budget
Budget
Distance from the MAN's data_offset to the next descriptor's - read from the boundary, never from a rewritten stream
Mods
--chests · --shops · --encounters · --house-doors
Confidence
Confirmed - opcode encodings from the dispatcher; oracles re-decode every patched scene
Oracles
chest_patch_real.rs · shop_patch_real.rs · encounter_patch_real.rs · house_door_patch_real.rs

Finding the operand

Field-VM bytecode is variable-width and interleaved with dialogue: runs of glyph segments introduced by 0x1F that are not opcodes. A chest's give almost always sits after the dialogue that announces it. The finder therefore:

  • starts each record at its true entry point, from the MAN's record-offset table;
  • decodes opcode by opcode with the disassembler;
  • treats a decode error at a 0x1F as a dialogue segment - skips to its terminating 0x00 and resumes;
  • stops on any other decode error, and never runs past the next record's start.

A chest carries two bytes that must move together: the GIVE_ITEM operand that grants the item, and a separate 0xC2 id dialogue token that renders the item's name in the announcement. Patch only the give and the text still names the old item.

OpcodeMeaningWhat the mod rewrites
0x39 idgive itemthe id, plus the paired 0xC2 id display token
0x49 sub-op 0shop stock listthe inline item ids (the unsellable template tail is not stock)
encounter recordformation monster idsthe id bytes; count and reserved bytes preserved
0xA3 0xF8intra-town warpplayer-warp operands, class-preserving across IN/OUT records

The footprint budget

After the edit the MAN is recompressed, and the new stream must still fit where the old one lived. The budget is read from the descriptor boundary, not from the just-written stream. Several passes (encounter, chest, shop) re-pack the same MAN; the project's packer is sometimes a touch tighter than the previous pass, so a budget read back from a shorter stream would shrink each time and a later pass would overflow a scene it should have edited.

MAN recompression must fit within the descriptor-boundary footprint original MAN LZS stream headroom re-packed edited MAN, recompressed headroom data_offset next descriptor budget = boundary − data_offset (constant across passes)
Both streams must end left of the next descriptor. A scene whose re-pack would overflow is skipped rather than corrupted.

Composition

Tier-C edits are same-size at the operand level, but several target the same scene. They compose under one rule: every pass reads the footprint budget from the descriptor boundary. The variable-length cousin - an edit that must change a byte count - is tier D.

How we know

ClaimEvidenceReference
Operand encodingsField-VM dispatcher FUN_801DE840 (43 opcodes), overlay-residentscript-vm
Shop stock is a script operand, not a tableOp 0x49 sub-op 0 carries the ids; prices come from the SCUS item recordshop
Encounter record shapeReader at FUN_801DA51C: [3 reserved][count][ids]encounter
Chest give follows the dialogueDisassembly of every chest site; the display token and the give are distinct byteschest_patch_real.rs
History: the walk that stopped at the first dialogue

A finder that halts at the first 0x1F misses the give at most chest sites, silently and without error - the give sits after the announcement. Resuming after each dialogue segment keeps the inter-segment control bytes in sync and reaches the real give. A byte scan for 0x39 fails the other way: it matches glyphs and operands.

Details: adding a tier-C mod
  1. Find sites with an opcode-aware walk bounded to each record - never a raw byte scan.
  2. Patch any paired display token alongside the operand.
  3. Recompress, reading the budget from the descriptor boundary; skip scenes that overflow.
  4. Add a disc-gated oracle that re-decodes the scene.

See also