Files
OpenMaidEngine/docs/script-inventory.md
2026-07-23 17:39:08 -04:00

13 KiB
Raw Blame History

DATA1 Script Inventory — 481 .BIN files

All scripts share the magic header SYS4422 (8 bytes), confirming a uniform SYS4 bytecode format (engine version 4.4.2.2) across the entire set. Header is followed by what appear to be little-endian u32 fields (version/section table — to be mapped during disassembler work).

Source: extracted\DATA1\ (extracted from DATA1.ALF).

Patch overrides (runtime re-counted 2026-07-11): 49 loose .BIN scripts sit in the game root and shadow DATA1 counterparts at runtime (sizes differ slightly — e.g. FIELD.BIN root 200,536 vs archive 200,224). These v1.03 / append-patch versions are authoritative. Two additional engine BINs exist only in the root: SYS4INI.BIN (272 KB) and SYS4AB.BIN (1.08 MB), for 51 root BINs total. The earlier count of 52 shadowing scripts conflated this inventory and was not reproducible; VFS-A enumerates and byte-checks all 49 real catalog/name intersections.


Breakdown by series

Series Count Total size Role (inferred)
SC#### 136 15.6 MB Scenario/event scripts (numbered 00001690, step 10)
SP#### 163 10.6 MB Secondary scene series (00101369; incl. SP0051A/B split) — likely character/H-events
Named scripts 175 ~7.5 MB Engine subsystems, data tables, UI, battle logic
DEBUG* 5 1.4 MB Debug tools (DEBUGADV.BIN alone is 1.4 MB — a scene viewer/jump menu)
RTN_M### / RTN_B### 26 ~70 KB Small routine scripts (map routines M001M061, battle routines B001B004)

SC series notes

  • Numbered SC0000SC0880 (main chapters, largest files — up to 700 KB) and SC1000SC1690 (smaller; likely sub-events, endings, appendix content).
  • SCJUMP.BIN (778 KB) is almost certainly the master scene-dispatch/jump table.
  • SCINIT.BIN (88 KB) initializes the SCJUMP decision-to-scene-script registry and authored chapter tags.

Named scripts by subsystem (inferred from names)

Engine core / boot flow

SYSTEM4.BIN (config root), INIT.BIN (64 bytes — smallest script, ideal first disassembly target), INITCONFIG, LOADCONFIG, CONFIG, TUNE, LOGO, OP, ED, TITLE, GAMESTART, GAMECLEAR, STAGECLEAR

INIT data and rule scripts

Large, table-like scripts — prime candidates for data extraction:

  • STINIT (579 KB) — stages/scenario tables
  • EBINIT (338 KB) — enemy battle data
  • MPINIT (330 KB) — maps
  • SCINIT (88 KB), CGINIT (79 KB — CG gallery), ITINIT (70 KB — items), RTINIT (67 KB — movement/battle routine-step banks), CCINIT (41 KB — class-change rule program), SKINIT (37 KB — skills), CDINIT (31 KB), BTANINIT (105 KB — battle animations)
  • Smaller: AFINIT, ALINIT, CIINIT, CNINIT, CTINIT, CVINIT, ILINIT, LAINIT, MAINIT, OBINIT (46 object definitions), SPINIT, TRINIT, VIINIT

Extracted-table correction and semantic pilot (2026-07-22). Name-mode INIT tables use sparse, one-based runtime ids over fixed reserved array spans, not a new record whenever the string destination decreases. The corrected extractor finds SKINIT 131 skills (span 300), ITINIT 287 items (span 1000), and EBINIT 277 units (span 1000). In particular, the earlier ITINIT JSON's 189 records were invalid: it collapsed items 1101 into the first record and treated consecutive item names as descriptions. Generated table state and counts are indexed in build/data/README.md; field semantics are curated in vm-map/globals.toml and described by the workflow in docs/name-resolution.md.

The same audit found that linked row-major writes must not be normalized as independent parallel arrays. Corpus lookup-array-2d bases and strides assign every such ITINIT write unambiguously to six tables (44 populated columns), while SKINIT and EBINIT expose 18 and 82 linked columns respectively. The extractor also preserves the scripts' zero-minus-immediate negative writes, which carry item penalties, condition cures, and skill SP costs. Generated records keep linked cells under record_fields[base/stride/column]; fields contains only genuine parallel arrays.

STINIT uses the separate mixed shape: 74 sparse scjump_progress_a branches load one current-stage work buffer rather than parallel per-id arrays. Its generated records preserve four mission-condition strings, six scalars, cells in 29 preallocated buffers, and all 1,396 length-prefixed footer-array copies. Confirmed stage field meanings and the evidence workflow live in docs/name-resolution.md. The first semantic join now assembles the confirmed type/X/Y/difficulty and story-prerequisite buffers into 2,312 object placements across 66 stages. It also exposes 604 reinforcement schedules and consumer-proven tagged payload variants for initial factions, teleport destinations, treasure item/count pairs, card-generation lists, and 104 hazard/barrier non-triggering faction gates. Every placement joins its type name and, where populated, its effect description from OBINIT's 46 definitions. OBINIT's state-row mode resolves another 78 initial object states on types 11, 17, and 26. The last three populated tagged writes, all type 27, are preserved as ignored authoring data after FIELD's dedicated spawn path proves it never reads them. A second join assembles all 1,378 populated enemy slots across the same 66 stages, including unit/faction, placement, difficulty/story gates, level rules, weighted alternatives, difficulty-specific movement/battle routine sets, and 485 first-clear-only replay gates.

CCINIT is the important exception to the static-table heuristic. Its 71 source-ordered branches are class-change rules over 33 EBINIT unit ids, not address-indexed database rows. CALCCC evaluates CCINIT through a 32-script provider list and applies the selected rule's title, deployment-cost delta, fourteen stat bonuses, skill awards, and persistent applied-state slot; EVOLVE directly queries the two untitled Lily form rules for movement preview. extract_init.py emits this as rules mode with raw guards and output addresses joined to EBINIT unit names, SKINIT skill names, and the curated class-change ABI.

SCINIT is a sixth paired-dispatch shape. Its 2,179 alternating writes populate a sparse decision-indexed script-resource array at 0x87a57 and authored-chapter array at 0x8a167, leaving 1,209 final rows. All 135 packed resource ids resolve to numbered SC scripts. The generated view preserves 710 overwritten decision ids and joins every one of SCJUMP's 847 live decision ids; 844 final chapter tags agree, while three legacy/stale mismatches remain explicit.

RTINIT is a seventh banked shape. Its 3,336 writes populate twenty parallel 1000 × 20 banks: ten movement banks consumed by MVRTN/RTN_M providers and ten battle banks consumed by BTRTN/RTN_B providers. The generated 172 routine-set records assemble 1,043 movement steps and fourteen battle steps, join provider selectors to script names, retain all 29 overwritten cells, and keep the six reserved empty banks visible. Selector-specific schemas now cover all nineteen used providers, RTN_M001/002/003/004/005/006/007/008/009/010/011/012/013/014/015/017/051/052/061: progress-only steps, randomized roaming, stage-object slots, coordinate destinations, enemy/ally and Magic Pillar searches, resource-threshold Healing Feather search, collectible-treasure seeking, cyclic waypoints, faction-traversable terrain, retreat, reachable normal-attack routing, immediate element-effective target/action selection, and immediate allied healing. The generated join covers all 1,043 steps and accounts for all 977 authored movement parameters: 974 semantic inputs plus three M001/M008 cells proven unread by their providers. Thirteen unwritten zero defaults are projected separately while the structurally named raw banks remain intact. The three additionally dispatchable providers which are absent from the shipped table have no instantiated steps to join.

Message/string tables (*MES)

ITMES (64 KB — item text), VIMES (43 KB), EIMES (37 KB), SKMES (31 KB — skill text), CIMES (15 KB), MAMES, INFOMES, MES — where most translatable text outside scenes lives. The generated message join covers ITMES's 287 item records, SKMES's 131 skill records, VIMES's complete 65-topic VIINIT glossary, and EIMES's 192 sparse EBINIT enemy-commentary records. ITMES/SKMES use fallthrough message bodies; VIMES/EIMES place all guards first and branch to later bodies. VIMES records retain title plus multiline description, while EIMES's untitled two-line records expose summary and strategy. CIMES has the same branch-target dispatcher shape and is the next unjoined message table; the smaller MAMES/INFOMES/MES scripts need separate structural classification.

Battle system

BTL (61 KB — main battle loop), BTRTN, ROUND, AIM, ATSEEK, MVSEEK, MVRTN, MAGIC, USEMAGIC, SUMMON, EXILE, DISARM, RECOVER, COUNTUNIT, SETOCC, and the CALC* family: CALCBTPARAM, CALCDMG (16 KB — damage formula!), CALCSCOPE, CALCOCC, CALCREVISE, CALCCC, CALCILL, CALCARR

Dungeon/map engine

FIELD (200 KB — the core dungeon-crawl loop), DRAWMAP, RENDERMAP, DRAWMINIMAP, DRAWCH/DRAWCHP/DRAWENP/DRAWOBJ/DRAWTIP/DRAWVOL, SETCH/SETEN/SETLAND/SETOBJ/SETROUTE/SETMVWORK, DELCH/DELEN/DELENMASS/DELLAND, RESETLAND, WARPU/WARPD, LOOK, READICON

Unit/party management

ADDEXP, ADDSKILL, ADDEN, ADDITEM, ADDRANDOMITEM, LOSTRANDOMITEM, ADDILL/ADDILLSUB, EVOLVE, IMPROVE (37 KB), TRAIN, STUDY, UNITECH, REMOVECH, SHOWGROW, STATUS, USEITEM, SETCH

Base/facility gameplay

CAMP, ROOM, FORT, ALCHEMY (40 KB), SALLY (40 KB — sortie/deployment), READY (39 KB — pre-battle prep), SELSTAGE (34 KB), SELACT (30 KB)

Menus / UI / meta

MENU, CHMENU (69 KB — character menu), INFO/INFOAF/INFOCH/INFOEN/ INFOIT/INFOVO (info panels: characters, enemies, items, glossary/help topics), SAVE (40 KB), HISTORY, HIDEWIN, CLOSE, INPUTNAME (28 KB), CGMODE (24 KB — gallery), MMODE (music mode), HMODE (22 KB — scene replay)

Flow control / branching

BUNKI (分岐 = branch, 15 KB), SBUNKI, BUNKIMOVE, SBUNKIMOVE, SCJUMP

Callbacks (engine → script hooks)

CALLBACK_LOAD, CALLBACK_LOST, CALLBACK_SETTING, CALLBACK_WINDOW

Debug

DEBUG, DEBUGADV (1.4 MB), DEBUGANIME, DEBUGBTL, DEBUGMAP (+2 numbered)


Call graph — scripts are addressable by call-script <id> (2026-07-07)

call-script <id> (opcode 0x03) loads another script by a raw index into the SYS4INI file table (id = the entry's raw_index = its global position in SYS4INI). This is the resolved call-graph registry — there is no separate id→code table; SYS4INI is it. Mechanism: engine-re.md (op 0x03 section); the runtime source is Sys4AssetCatalog over SYS4INI, while the mechanically generated build/callscript-names.json feeds sys4load diagnostics. The regenerated build/disasm/*.asm corpus renders targets by name (call-script 0x1ab =ADDITEM.BIN). 297 distinct scripts are called across the corpus (3002 sites); the hottest are HISTORY (backlog), MENU, HIDEWIN, BUNKI (branch), MES (message), ADDITEM, ADDEN, LOOK, RENDERMAP. Scenes (SCxxxx.BIN) load through the same id-indexed loader.

Living-reference decision: no separate generated markdown call-script reference is kept. Unlike opcode-reference.md / global-reference.md (rendered from curated knowledge bases), the id→name mapping is purely mechanical (SYS4INI index → filename) with no semantics to curate — it already lives in the build artifact and in the named disasm corpus. Full call-graph edges (caller→callee counts) are derivable on demand from the corpus; materialize a doc only if a consumer needs it.


Implications for the port

  1. Much more game logic lives in bytecode than expected. Damage formulas (CALCDMG), the dungeon loop (FIELD), battle flow (BTL, ROUND), and unit progression (ADDEXP, EVOLVE) are all scripts — AGE.EXE is closer to a pure VM/renderer. This strengthens the case for re-implementing the AGE VM in Godot rather than transpiling every script by hand (the doc's open question #12).
  2. Disassembler bootstrapping order (small → large, system → scene): INIT.BIN (64 B) → ED.BIN/OP.BIN/LOGO.BIN (~230 B) → CALLBACK_LOST (176 B) → MENU.BIN (3 KB) → CALCDMG → a mid-size SC####.
  3. The *INIT giants are likely data tables, decodable early even with a partial opcode map — instant win for extracting item/skill/enemy/stage databases.
  4. Use root-directory overrides, not archive copies, for the 49 archive-backed patched scripts.