Decode STINIT initial object states
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
||||||
# Global Variable Reference (generated)
|
# Global Variable Reference (generated)
|
||||||
|
|
||||||
5030 globals (187 curated, 4843 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
5030 globals (189 curated, 4841 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||||
|
|
||||||
## choice-output
|
## choice-output
|
||||||
|
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
| `0x673c` | party_slot_flags | high | investigation | Per-party-slot state flags for slots 0..99. UNITECH creates the initial unit by setting slot 2 to 0x13; CALCARR counts slots whose flags intersect 0x6, and CHMENU includes slots with bit 1 set. Exact meanings of the remaining bits are not yet classified. |
|
| `0x673c` | party_slot_flags | high | investigation | Per-party-slot state flags for slots 0..99. UNITECH creates the initial unit by setting slot 2 to 0x13; CALCARR counts slots whose flags intersect 0x6, and CHMENU includes slots with bit 1 set. Exact meanings of the remaining bits are not yet classified. |
|
||||||
| `0x67a0` | party_slot_character_id | high | investigation | Character/unit definition id stored for each party slot. UNITECH writes character id 2 into initial slot 2 on a natural New Game; CHMENU reads this table for every active party_slot_flags entry when constructing its roster. |
|
| `0x67a0` | party_slot_character_id | high | investigation | Character/unit definition id stored for each party slot. UNITECH writes character id 2 into initial slot 2 on a natural New Game; CHMENU reads this table for every active party_slot_flags entry when constructing its roster. |
|
||||||
| `0x7684` | stage_clear_state | high | investigation | Persistent per-stage completion state indexed by stage id. STAGECLEAR sets the current stage cell to 1, while FORT, SELSTAGE, and FIELD use zero versus one to distinguish an uncleared mission from a cleared replay. |
|
| `0x7684` | stage_clear_state | high | investigation | Persistent per-stage completion state indexed by stage id. STAGECLEAR sets the current stage cell to 1, while FORT, SELSTAGE, and FIELD use zero versus one to distinguish an uncleared mission from a cleared replay. |
|
||||||
|
| `0x141a4` | stage_object_runtime_state | high | investigation | Per-stage, per-object-slot runtime state. FIELD initializes mode-1 objects from STINIT's first tagged payload on a fresh stage and updates capturable-object ownership; DRAWOBJ uses it as the vertical sprite-row index. Other consumers interpret it by object type. |
|
||||||
| `0x5660b` | skill_info_revealed_flags | high | investigation | Persistent skill-information visibility flags. ADDSKILL sets the selected skill, BTL marks every equipped skill when it is observed in combat, and INFOIT suppresses a skill's icon/handler-driven details until this flag is nonzero. This is broader than skill_acquired_flags. |
|
| `0x5660b` | skill_info_revealed_flags | high | investigation | Persistent skill-information visibility flags. ADDSKILL sets the selected skill, BTL marks every equipped skill when it is observed in combat, and INFOIT suppresses a skill's icon/handler-driven details until this flag is nonzero. This is broader than skill_acquired_flags. |
|
||||||
| `0x66716` | unit_voice_asset_ids | high | investigation | EBINIT per-unit voice bank for 116 voiced characters and variants. Resolving the values through SYS4INI yields character OGG clips (for example Lily's row is LILA1381..1406); BTL and FIELD select mostly unresolved battle/map reaction slots, while SHOWGROW proves column 24 is the level-up reaction. Columns: 24=level_up. |
|
| `0x66716` | unit_voice_asset_ids | high | investigation | EBINIT per-unit voice bank for 116 voiced characters and variants. Resolving the values through SYS4INI yields character OGG clips (for example Lily's row is LILA1381..1406); BTL and FIELD select mostly unresolved battle/map reaction slots, while SHOWGROW proves column 24 is the level-up reaction. Columns: 24=level_up. |
|
||||||
| `0x6dc46` | unit_status_art_asset_ids | high | investigation | Three-variant status/menu illustration table for 25 principal characters. The ids resolve to 456x420 CS character art; DRAWCHP selects column 0 above 50% HP, column 1 at 26..50%, and column 2 at 25% or below, then loads the art into the status-panel texture slot. Columns: 0=healthy, 1=wounded, 2=critical. |
|
| `0x6dc46` | unit_status_art_asset_ids | high | investigation | Three-variant status/menu illustration table for 25 principal characters. The ids resolve to 456x420 CS character art; DRAWCHP selects column 0 above 50% HP, column 1 at 26..50%, and column 2 at 25% or below, then loads the art into the status-panel texture slot. Columns: 0=healthy, 1=wounded, 2=critical. |
|
||||||
@@ -103,6 +104,7 @@
|
|||||||
| `0xaa8f2` | skill_proc_chance_percent | high | investigation | Probability for 14 passive skills. CALCDMG compares random-modulo 100 against this value; examples include Re-action 20, Double Action 100, Counter 10, and Resurrection 50. |
|
| `0xaa8f2` | skill_proc_chance_percent | high | investigation | Probability for 14 passive skills. CALCDMG compares random-modulo 100 against this value; examples include Re-action 20, Double Action 100, Counter 10, and Resurrection 50. |
|
||||||
| `0xaaa1e` | skill_battle_animation_id | high | investigation | Populated for 101 combat skills. BTL and CALCDMG place this value in the battle-animation selector before calling BTANINIT; most skills reuse their own id, while related skills deliberately share an animation and passive reactions use ids 801..808. |
|
| `0xaaa1e` | skill_battle_animation_id | high | investigation | Populated for 101 combat skills. BTL and CALCDMG place this value in the battle-animation selector before calling BTANINIT; most skills reuse their own id, while related skills deliberately share an animation and passive reactions use ids 801..808. |
|
||||||
| `0xaab4a` | skill_handler_script_id | high | investigation | SKINIT field for all 131 skills. CHMENU and INFOIT look it up and pass it directly to call-script; packed id 0x31ca resolves to SKMES.BIN, the shared per-skill text/behavior dispatcher. |
|
| `0xaab4a` | skill_handler_script_id | high | investigation | SKINIT field for all 131 skills. CHMENU and INFOIT look it up and pass it directly to call-script; packed id 0x31ca resolves to SKMES.BIN, the shared per-skill text/behavior dispatcher. |
|
||||||
|
| `0xe6dee` | object_sprite_state_row_mode | high | investigation | OBINIT metadata indexed by object type. FIELD copies the type-tagged initial payload into stage_object_runtime_state only for mode 1; DRAWOBJ likewise multiplies that runtime state by the object sprite height to select source Y only for mode 1. |
|
||||||
| `0xe7302` | stage_bgm_id | high | investigation | STINIT's per-stage scalar loaded for all 74 records. FIELD passes the value directly to play-bgm when starting the stage. |
|
| `0xe7302` | stage_bgm_id | high | investigation | STINIT's per-stage scalar loaded for all 74 records. FIELD passes the value directly to play-bgm when starting the stage. |
|
||||||
| `0xe7303` | stage_target_clear_turns | high | investigation | STINIT's target/par turn count. STAGECLEAR divides elapsed turns by this value to derive a performance multiplier; when replaying an already-cleared ordinary stage, FIELD also uses it as the forced-retreat turn limit. |
|
| `0xe7303` | stage_target_clear_turns | high | investigation | STINIT's target/par turn count. STAGECLEAR divides elapsed turns by this value to derive a performance multiplier; when replaying an already-cleared ordinary stage, FIELD also uses it as the forced-retreat turn limit. |
|
||||||
| `0xe7304` | stage_clear_performance_bonus | med | investigation | Base stage-clear reward increment. STAGECLEAR multiplies it by the turn-performance percentage derived from stage_target_clear_turns, divides by 100, and adds the result to the capped persistent reward counter at 0x6719. The counter's player-facing resource name remains unresolved. |
|
| `0xe7304` | stage_clear_performance_bonus | med | investigation | Base stage-clear reward increment. STAGECLEAR multiplies it by the turn-performance percentage derived from stage_target_clear_turns, divides by 100, and adds the result to the capped persistent reward counter at 0x6719. The counter's player-facing resource name remains unresolved. |
|
||||||
@@ -266,7 +268,6 @@
|
|||||||
| `0xe6cc2` | — | low | auto-shape | array |
|
| `0xe6cc2` | — | low | auto-shape | array |
|
||||||
| `0xe6d26` | — | low | auto-shape | array |
|
| `0xe6d26` | — | low | auto-shape | array |
|
||||||
| `0xe6d8a` | — | low | auto-shape | array |
|
| `0xe6d8a` | — | low | auto-shape | array |
|
||||||
| `0xe6dee` | — | low | auto-shape | array |
|
|
||||||
| `0xe6e52` | — | low | auto-shape | array |
|
| `0xe6e52` | — | low | auto-shape | array |
|
||||||
| `0xe6eb6` | — | low | auto-shape | array |
|
| `0xe6eb6` | — | low | auto-shape | array |
|
||||||
| `0xe6f1a` | — | low | auto-shape | array |
|
| `0xe6f1a` | — | low | auto-shape | array |
|
||||||
@@ -3664,7 +3665,6 @@
|
|||||||
| `0x6f70` | — | med | auto-shape | record-table[stride 14] |
|
| `0x6f70` | — | med | auto-shape | record-table[stride 14] |
|
||||||
| `0x74e8` | — | med | auto-shape | record-table[stride 4] |
|
| `0x74e8` | — | med | auto-shape | record-table[stride 4] |
|
||||||
| `0x7e54` | — | med | auto-shape | record-table[stride 50] |
|
| `0x7e54` | — | med | auto-shape | record-table[stride 50] |
|
||||||
| `0x141a4` | — | med | auto-shape | record-table[stride 50] |
|
|
||||||
| `0x20543` | — | med | auto-shape | record-table[stride 27] |
|
| `0x20543` | — | med | auto-shape | record-table[stride 27] |
|
||||||
| `0x341ab` | — | med | auto-shape | record-table[stride 53] |
|
| `0x341ab` | — | med | auto-shape | record-table[stride 53] |
|
||||||
| `0x4e085` | — | med | auto-shape | record-table[stride 3] |
|
| `0x4e085` | — | med | auto-shape | record-table[stride 3] |
|
||||||
|
|||||||
@@ -285,14 +285,22 @@ variants:
|
|||||||
|
|
||||||
This accounts for 220 initial-owner values, 229 teleport destinations, 626 treasure pairs, and 246 card
|
This accounts for 220 initial-owner values, 229 teleport destinations, 626 treasure pairs, and 246 card
|
||||||
list ids. The faction-gate branch resolves another 104 cells on populated types 18--21 and 25. Type 17
|
list ids. The faction-gate branch resolves another 104 cells on populated types 18--21 and 25. Type 17
|
||||||
(`針`, spikes) is explicitly outside FIELD's faction comparison, so its 16 populated values remain raw
|
(`針`, spikes) is explicitly outside FIELD's faction comparison, so it does not borrow the neighboring
|
||||||
rather than borrowing the neighboring hazard meaning. In total, 81 placements across object types 11,
|
hazard meaning.
|
||||||
17, 26, and 27 still retain one raw payload cell because their type-specific meaning is not yet separable
|
|
||||||
from the consumer behavior.
|
A separate initialization/render path resolves the remaining state-row payloads. On a fresh stage, FIELD
|
||||||
|
copies the first tagged payload into `stage_object_runtime_state[stage][slot]` only when the object's
|
||||||
|
OBINIT `object_sprite_state_row_mode` equals 1. DRAWOBJ applies the same mode check and multiplies that
|
||||||
|
runtime state by the object's sprite height to select its vertical source row. The join therefore exposes
|
||||||
|
78 cells as `initial_object_state_id`: one door (type 11), 16 spikes (type 17), and 61 deployment flags
|
||||||
|
(type 26, `出撃の旗`). All deployment-flag values are 2, and all 63 enemies linked to those flags are also
|
||||||
|
faction 2, consistent with OBINIT's `敵の増援地点` description; because the spawn branch accepts type 26
|
||||||
|
without comparing those values, the field remains the directly proven object state rather than a guessed
|
||||||
|
faction id. Only three type-27 (`異界の門`) first-payload cells remain raw.
|
||||||
|
|
||||||
OBINIT is the authoritative object-definition table: 46 one-based records provide the type names, and 34
|
OBINIT is the authoritative object-definition table: 46 one-based records provide the type names, and 34
|
||||||
provide short player-facing effect descriptions used by the field object-information path. The STINIT join now
|
provide short player-facing effect descriptions used by the field object-information path. The STINIT join
|
||||||
adds `type_name` to every placement and `type_description` when populated while retaining `type_id`;
|
now adds `type_name` to every placement and `type_description` when populated while retaining `type_id`;
|
||||||
top-level `object_definition_table: "OBINIT"` records the join provenance. This is intentionally separate
|
top-level `object_definition_table: "OBINIT"` records the join provenance. This is intentionally separate
|
||||||
from payload decoding: a known display label does not by itself establish the meaning of a tagged cell.
|
from payload decoding: a known display label does not by itself establish the meaning of a tagged cell.
|
||||||
|
|
||||||
@@ -315,7 +323,7 @@ confirmed parallel buffers plus both prerequisite tables are also assembled into
|
|||||||
`object_placements` across 66 stages. Each placement contains its slot, numeric type plus OBINIT
|
`object_placements` across 66 stages. Each placement contains its slot, numeric type plus OBINIT
|
||||||
name/available description, tile coordinates, difficulty mask, populated positive/negative story
|
name/available description, tile coordinates, difficulty mask, populated positive/negative story
|
||||||
prerequisites, optional reinforcement schedule, and the decoded type-tagged payload variants above. The
|
prerequisites, optional reinforcement schedule, and the decoded type-tagged payload variants above. The
|
||||||
remaining 81 undecoded payload cells stay attached under
|
remaining three undecoded type-27 payload cells stay attached under
|
||||||
`unknown_fields`, so this convenience view loses no evidence or invents names.
|
`unknown_fields`, so this convenience view loses no evidence or invents names.
|
||||||
The same records now contain 1,378 joined `enemy_spawns` across 66 stages, with unit/faction, direct or
|
The same records now contain 1,378 joined `enemy_spawns` across 66 stages, with unit/faction, direct or
|
||||||
object-linked placement data when present, difficulty and story gates, level rules, random-selection
|
object-linked placement data when present, difficulty and story gates, level rules, random-selection
|
||||||
@@ -368,10 +376,10 @@ The v1 map labels *shapes and tables*; the next increments add *meaning*, cheape
|
|||||||
|
|
||||||
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, the confirmed EBINIT row layouts, and
|
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, the confirmed EBINIT row layouts, and
|
||||||
STINIT's mixed stage records now have machine-readable investigation surfaces, including joined object
|
STINIT's mixed stage records now have machine-readable investigation surfaces, including joined object
|
||||||
placements and enemy spawns. STINIT's universal object schedule, four consumer-proven tagged payload
|
placements and enemy spawns. STINIT's universal object schedule, consumer-proven tagged payload
|
||||||
families, faction-gated hazards/barriers, OBINIT definition join, and first-clear enemy gate are decoded;
|
families, faction-gated hazards/barriers, initialized object states, OBINIT definition join, and
|
||||||
next trace the 81 remaining raw object payloads on types 11, 17, 26, and 27 only where their
|
first-clear enemy gate are decoded; next trace the three remaining raw type-27 payloads only where its
|
||||||
type-specific runtime branches distinguish them. Isolate EBINIT's remaining voice/action
|
special-spawn path distinguishes them. Isolate EBINIT's remaining voice/action
|
||||||
slots under the same rule. Preserve explicit joins and do not infer meaning from column position alone.
|
slots under the same rule. Preserve explicit joins and do not infer meaning from column position alone.
|
||||||
2. **Extend message-table joins beyond the completed ITMES/SKMES pair** (`VIMES`, other id dispatchers, …)
|
2. **Extend message-table joins beyond the completed ITMES/SKMES pair** (`VIMES`, other id dispatchers, …)
|
||||||
and fold in other `set-string`/`copy-to-global` writers not covered by the `*INIT` set.
|
and fold in other `set-string`/`copy-to-global` writers not covered by the `*INIT` set.
|
||||||
|
|||||||
@@ -754,11 +754,13 @@ the 20-slot map-texture override list, object type/tile/difficulty fields, and s
|
|||||||
forbidden story prerequisites. Generated records now carry a joined `semantic_fields` view and assemble
|
forbidden story prerequisites. Generated records now carry a joined `semantic_fields` view and assemble
|
||||||
2,312 object placements across 66 stages. FIELD resolves the universal reinforcement interval/limit and
|
2,312 object placements across 66 stages. FIELD resolves the universal reinforcement interval/limit and
|
||||||
type-tagged initial-faction, teleport, treasure, card-list, and non-triggering-faction payloads. OBINIT
|
type-tagged initial-faction, teleport, treasure, card-list, and non-triggering-faction payloads. OBINIT
|
||||||
supplies authoritative names for all 46 object types and descriptions for 34; 81 payload cells on types 11,
|
supplies authoritative names for all 46 object types and descriptions for 34. Its state-row mode and
|
||||||
17, 26, and 27 remain raw. The separate 30-cell enemy family is also decoded: FIELD/SETEN/ADDEN establish
|
FIELD/DRAWOBJ initialization path resolve 78 initial object states on types 11, 17, and 26; only three
|
||||||
unit/faction, direct or object-linked placement, difficulty/story gates, level floor/cap/scaling, weighted
|
type-27 payload cells remain raw. The separate 30-cell enemy family is also decoded:
|
||||||
selection, and three difficulty-specific movement/battle routine-set ids. The generated view assembles all 1,378
|
FIELD/SETEN/ADDEN establish unit/faction, direct or object-linked placement, difficulty/story gates, level
|
||||||
populated enemy slots across 66 stages, including 485 enemies suppressed after the stage's first clear.
|
floor/cap/scaling, weighted selection, and three difficulty-specific movement/battle routine-set ids. The
|
||||||
|
generated view assembles all 1,378 populated enemy slots across 66 stages, including 485 enemies suppressed
|
||||||
|
after the stage's first clear.
|
||||||
|
|
||||||
Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks:
|
Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks:
|
||||||
|
|
||||||
@@ -768,7 +770,7 @@ Once the natural spine and first gameplay loop are trustworthy, broaden in indep
|
|||||||
- Unit statistics, equipment, inventory, skills, magic, heroine forms, and progression.
|
- Unit statistics, equipment, inventory, skills, magic, heroine forms, and progression.
|
||||||
- Combat resolution presentation, enemy turns/AI services, and win/loss transitions.
|
- Combat resolution presentation, enemy turns/AI services, and win/loss transitions.
|
||||||
- A full chapter of ADV and the scene types encountered between gameplay segments.
|
- A full chapter of ADV and the scene types encountered between gameplay segments.
|
||||||
- Remaining STINIT tagged payloads on object types 11, 17, 26, and 27, plus other data schemas when
|
- The three remaining STINIT tagged payloads on object type 27, plus other data schemas when
|
||||||
their runtime consumers make them necessary.
|
their runtime consumers make them necessary.
|
||||||
|
|
||||||
The high-level Phase B direction remains canonical in `docs/remake-architecture-and-roadmap.md`; this file
|
The high-level Phase B direction remains canonical in `docs/remake-architecture-and-roadmap.md`; this file
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ now assembles the confirmed type/X/Y/difficulty and story-prerequisite buffers i
|
|||||||
across 66 stages. It also exposes 604 reinforcement schedules and consumer-proven tagged payload variants
|
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
|
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
|
hazard/barrier non-triggering faction gates. Every placement joins its type name and, where populated, its
|
||||||
effect description from OBINIT's 46 definitions; 81 type-specific payload cells on types 11, 17, 26, and
|
effect description from OBINIT's 46 definitions. OBINIT's state-row mode resolves another 78 initial
|
||||||
27 remain raw. A second join assembles all 1,378 populated enemy slots
|
object states on types 11, 17, and 26; only three type-27 payload cells remain raw. 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
|
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.
|
alternatives, difficulty-specific movement/battle routine sets, and 485 first-clear-only replay gates.
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `extract_phase2.py` | Batch: disassembly + text corpora for every script. | `extract_phase2.py` | corpus → `build/disasm/*.asm`, `build/text/{dialogue.jsonl,strings.jsonl,*.strings.txt}`, `build/manifest.json` |
|
| `extract_phase2.py` | Batch: disassembly + text corpora for every script. | `extract_phase2.py` | corpus → `build/disasm/*.asm`, `build/text/{dialogue.jsonl,strings.jsonl,*.strings.txt}`, `build/manifest.json` |
|
||||||
| `extract_message_table.py` | Discover a repeated global-id dispatch chain such as ITMES/SKMES, reconstruct player-facing title/description lines (including furigana surface text and readings), and emit an ID-keyed message table with bytecode provenance. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN` → `build/data/<OUTNAME>.json` |
|
| `extract_message_table.py` | Discover a repeated global-id dispatch chain such as ITMES/SKMES, reconstruct player-facing title/description lines (including furigana surface text and readings), and emit an ID-keyed message table with bytecode provenance. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN` → `build/data/<OUTNAME>.json` |
|
||||||
| `extract_init.py` | Parse a `*INIT` data table (auto-detects name / numeric / footer / mixed shape). Name tables infer their reserved record span, preserve sparse one-based runtime ids, distinguish lookup bases from first written cells, statically evaluate direct and negative-value writes, and separate parallel `fields` from linked row-major `record_fields`. Mixed tables recover selector-dispatched records, condition strings, scalars, preallocated buffer cells, consumer-confirmed strides, and length-prefixed footer arrays; STINIT additionally assembles confirmed buffers into `object_placements` and `enemy_spawns`, joins object type names and available descriptions from OBINIT, and exposes consumer-proven tagged payloads (teleport, treasure, card, initial faction, and non-triggering hazard/barrier faction), reinforcement schedules, and first-clear-only enemy gates. ITINIT and SKINIT join ITMES/SKMES messages. Top-level `field_semantics` maps raw keys to canonical global/column names, while each record's `semantic_fields` is the generated name-keyed join; complete footer copies map to a row and expose its values without the raw provenance wrapper. Raw keys and unresolved tagged payload cells remain provenance. OUTNAME accepts a stem or one `.json` suffix and rejects paths. Refreshes the generated data index. | `extract_init.py <TABLE> [OUTNAME] [--mode …]` | `<TABLE>.BIN` plus OBINIT for STINIT, matching `<MES>.BIN` when supported, and `build/globals.json` → `build/data/<OUTNAME>.json`, `build/data/README.md` |
|
| `extract_init.py` | Parse a `*INIT` data table (auto-detects name / numeric / footer / mixed shape). Name tables infer their reserved record span, preserve sparse one-based runtime ids, distinguish lookup bases from first written cells, statically evaluate direct and negative-value writes, and separate parallel `fields` from linked row-major `record_fields`. Mixed tables recover selector-dispatched records, condition strings, scalars, preallocated buffer cells, consumer-confirmed strides, and length-prefixed footer arrays; STINIT additionally assembles confirmed buffers into `object_placements` and `enemy_spawns`, joins object type names and available descriptions from OBINIT, and exposes consumer-proven tagged payloads (teleport, treasure, card, initial faction, non-triggering hazard/barrier faction, and OBINIT-gated initial object state), reinforcement schedules, and first-clear-only enemy gates. ITINIT and SKINIT join ITMES/SKMES messages. Top-level `field_semantics` maps raw keys to canonical global/column names, while each record's `semantic_fields` is the generated name-keyed join; complete footer copies map to a row and expose its values without the raw provenance wrapper. Raw keys and unresolved tagged payload cells remain provenance. OUTNAME accepts a stem or one `.json` suffix and rejects paths. Refreshes the generated data index. | `extract_init.py <TABLE> [OUTNAME] [--mode …]` | `<TABLE>.BIN` plus OBINIT for STINIT, matching `<MES>.BIN` when supported, and `build/globals.json` → `build/data/<OUTNAME>.json`, `build/data/README.md` |
|
||||||
| `init_table_profile.py` | Build the static investigation surface for an extracted name/numeric/mixed table: message coverage, per-scalar/string/array-cell/footer-array population and value distributions, representative records, and direct opcode/script consumers. `--message-query REGEX` shows every matching name/message beside all populated fields for semantic correlation. Findings are evidence only; confirmed meanings go in `vm-map/globals.toml`. | `init_table_profile.py <TABLE> [--build] [--limit N] [--message-query REGEX]` | `build/data/<TABLE>.json` + corpus → stdout; with `--build`, `build/data/<TABLE>-field-profile.{json,md}` |
|
| `init_table_profile.py` | Build the static investigation surface for an extracted name/numeric/mixed table: message coverage, per-scalar/string/array-cell/footer-array population and value distributions, representative records, and direct opcode/script consumers. `--message-query REGEX` shows every matching name/message beside all populated fields for semantic correlation. Findings are evidence only; confirmed meanings go in `vm-map/globals.toml`. | `init_table_profile.py <TABLE> [--build] [--limit N] [--message-query REGEX]` | `build/data/<TABLE>.json` + corpus → stdout; with `--build`, `build/data/<TABLE>-field-profile.{json,md}` |
|
||||||
| `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based and mixed selector-dispatched INIT extraction, MES reconstruction/joins, footer-array accounting and semantic projection, OBINIT object-definition extraction, STINIT object/enemy joins and tagged-payload behavior, and field/message profiling. | run each directly | — |
|
| `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based and mixed selector-dispatched INIT extraction, MES reconstruction/joins, footer-array accounting and semantic projection, OBINIT object-definition extraction, STINIT object/enemy joins and tagged-payload behavior, and field/message profiling. | run each directly | — |
|
||||||
| `global_map.py` | Build the partial global-variable name map from static evidence. | `global_map.py` | corpus + `build/data/` → `build/global-var-map.{json,md}` |
|
| `global_map.py` | Build the partial global-variable name map from static evidence. | `global_map.py` | corpus + `build/data/` → `build/global-var-map.{json,md}` |
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
- [x] **2.0 — Project structure.** Established `docs/`, `build/{disasm,text,data,scripts-json}/`, `godot/`; game install stays read-only in place. Also relaxed the loader magic check to the `SYS4` family (`SYS4424` patch scripts now parse — was silently skipping 5 scripts).
|
- [x] **2.0 — Project structure.** Established `docs/`, `build/{disasm,text,data,scripts-json}/`, `godot/`; game install stays read-only in place. Also relaxed the loader magic check to the `SYS4` family (`SYS4424` patch scripts now parse — was silently skipping 5 scripts).
|
||||||
- [x] **2.1 — Text corpora.** `tools/extract_phase2.py` → 481/481 scripts: full disassembly (`build/disasm/*.asm`), per-script strings, `build/text/dialogue.jsonl` (**30,057 show-text lines** — the translation corpus), `build/text/strings.jsonl` (38,449 strings tagged by source opcode), `build/manifest.json`.
|
- [x] **2.1 — Text corpora.** `tools/extract_phase2.py` → 481/481 scripts: full disassembly (`build/disasm/*.asm`), per-script strings, `build/text/dialogue.jsonl` (**30,057 show-text lines** — the translation corpus), `build/text/strings.jsonl` (38,449 strings tagged by source opcode), `build/manifest.json`.
|
||||||
- [x] **2.2 — `*INIT` data tables → JSON.** `tools/extract_init.py` auto-detects table shape (`name`/`numeric`/`footer`/`mixed`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions)** [name: sparse one-based name/description/fields], **CGINIT (379 CG entries)** [numeric: index-keyed columns], **MPINIT (1472 map records)** [footer: length-prefixed arrays], and **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays]. Validated; see `build/data/README.md`. Raw addresses remain bytecode provenance; confirmed semantics come from `vm-map/globals.toml`.
|
- [x] **2.2 — `*INIT` data tables → JSON.** `tools/extract_init.py` auto-detects table shape (`name`/`numeric`/`footer`/`mixed`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions)** [name: sparse one-based name/description/fields], **CGINIT (379 CG entries)** [numeric: index-keyed columns], **MPINIT (1472 map records)** [footer: length-prefixed arrays], and **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays]. Validated; see `build/data/README.md`. Raw addresses remain bytecode provenance; confirmed semantics come from `vm-map/globals.toml`.
|
||||||
- [x] **2.3 — Extract `STINIT`'s 74 sparse stage records.** The mixed mode identifies the dominant `scjump_progress_a` dispatch, recovers 29 preallocated buffer layouts (including six consumer-confirmed row strides), and keeps four condition strings, six scalars, fixed-buffer writes, and all 1,396 footer-array copies separated by stage id. Profiles supply population/value and direct-consumer evidence. Header/map semantics cover conditions, BGM, turn/replay/clear-reward settings, and map texture overrides. The object join assembles 2,312 placements with type/position/gates, OBINIT names and available descriptions, 604 reinforcement schedules, and typed initial-faction, teleport, treasure, card-list, and non-triggering-faction payloads while preserving 81 unresolved tagged cells on types 11, 17, 26, and 27. The 30-cell enemy family contributes 1,378 joined spawns with unit/faction, placement, difficulty/story gates, level scaling, weighted selection, difficulty-specific movement/battle routine sets, and 485 first-clear-only gates. Raw address views remain alongside generated `semantic_fields`.
|
- [x] **2.3 — Extract `STINIT`'s 74 sparse stage records.** The mixed mode identifies the dominant `scjump_progress_a` dispatch, recovers 29 preallocated buffer layouts (including six consumer-confirmed row strides), and keeps four condition strings, six scalars, fixed-buffer writes, and all 1,396 footer-array copies separated by stage id. Profiles supply population/value and direct-consumer evidence. Header/map semantics cover conditions, BGM, turn/replay/clear-reward settings, and map texture overrides. The object join assembles 2,312 placements with type/position/gates, OBINIT names and available descriptions, 604 reinforcement schedules, and typed initial-faction, teleport, treasure, card-list, non-triggering-faction, and initial-object-state payloads while preserving only three unresolved type-27 cells. The 30-cell enemy family contributes 1,378 joined spawns with unit/faction, placement, difficulty/story gates, level scaling, weighted selection, difficulty-specific movement/battle routine sets, and 485 first-clear-only gates. Raw address views remain alongside generated `semantic_fields`.
|
||||||
- [x] **2.4 — Partial global-var map BUILT + wired into the disassembler.** `tools/global_map.py` → `build/global-var-map.{json,md}` (16,354/49,435 globals labelled: string tables, `*INIT` field arrays, 122 record tables w/ strides, current-entity index pointers). `sys4load` renders the labels inline (`=rec[s30]`, `=current-entity-index?`). See `docs/name-resolution.md`.
|
- [x] **2.4 — Partial global-var map BUILT + wired into the disassembler.** `tools/global_map.py` → `build/global-var-map.{json,md}` (16,354/49,435 globals labelled: string tables, `*INIT` field arrays, 122 record tables w/ strides, current-entity index pointers). `sys4load` renders the labels inline (`=rec[s30]`, `=current-entity-index?`). See `docs/name-resolution.md`.
|
||||||
- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: fold in `*MES` writers; label 2D record tables by their reader scripts. Then Frida to name *which stat* each field is. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Also deferred: `call-script` id→name resolution (engine-level — SCJUMP.BIN decode or Frida; see `docs/name-resolution.md` #1).
|
- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: fold in `*MES` writers; label 2D record tables by their reader scripts. Then Frida to name *which stat* each field is. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Also deferred: `call-script` id→name resolution (engine-level — SCJUMP.BIN decode or Frida; see `docs/name-resolution.md` #1).
|
||||||
|
|
||||||
|
|||||||
@@ -396,12 +396,15 @@ def extract_name(scr):
|
|||||||
|
|
||||||
@cache
|
@cache
|
||||||
def object_type_definitions() -> dict[int, dict]:
|
def object_type_definitions() -> dict[int, dict]:
|
||||||
"""Load OBINIT's authoritative names and descriptions keyed by object type id."""
|
"""Load OBINIT's authoritative display and state-row metadata by object type id."""
|
||||||
records, _ = extract_name(sys4load.load(resolve("OBINIT")))
|
records, _ = extract_name(sys4load.load(resolve("OBINIT")))
|
||||||
return {
|
return {
|
||||||
record["id"]: {
|
record["id"]: {
|
||||||
"name": record["name"],
|
"name": record["name"],
|
||||||
**({"description": record["desc"]} if record.get("desc") else {}),
|
**({"description": record["desc"]} if record.get("desc") else {}),
|
||||||
|
"uses_runtime_state_sprite_row": (
|
||||||
|
record.get("fields", {}).get("0xe6dee") == 1
|
||||||
|
),
|
||||||
}
|
}
|
||||||
for record in records
|
for record in records
|
||||||
}
|
}
|
||||||
@@ -639,6 +642,10 @@ def attach_stage_object_placements(
|
|||||||
obj["card_generation_list_id"] = payload.pop("0xe73bb")
|
obj["card_generation_list_id"] = payload.pop("0xe73bb")
|
||||||
elif 18 <= type_id <= 25 and "0xe73bb" in payload:
|
elif 18 <= type_id <= 25 and "0xe73bb" in payload:
|
||||||
obj["non_triggering_faction_id"] = payload.pop("0xe73bb")
|
obj["non_triggering_faction_id"] = payload.pop("0xe73bb")
|
||||||
|
elif (definition
|
||||||
|
and definition["uses_runtime_state_sprite_row"]
|
||||||
|
and "0xe73bb" in payload):
|
||||||
|
obj["initial_object_state_id"] = payload.pop("0xe73bb")
|
||||||
unknown = payload
|
unknown = payload
|
||||||
if unknown:
|
if unknown:
|
||||||
obj["unknown_fields"] = unknown
|
obj["unknown_fields"] = unknown
|
||||||
|
|||||||
@@ -166,13 +166,29 @@ def test_real_mixed_table() -> None:
|
|||||||
for obj in all_objects
|
for obj in all_objects
|
||||||
if "non_triggering_faction_id" in obj
|
if "non_triggering_faction_id" in obj
|
||||||
} == {1, 2, 3}, "STINIT faction gates retain all observed faction ids")
|
} == {1, 2, 3}, "STINIT faction gates retain all observed faction ids")
|
||||||
|
state_objects = [
|
||||||
|
obj for obj in all_objects if "initial_object_state_id" in obj
|
||||||
|
]
|
||||||
|
check(len(state_objects) == 78,
|
||||||
|
"STINIT state-row metadata decodes every remaining initialized object state")
|
||||||
|
check({
|
||||||
|
obj["type_id"] for obj in state_objects
|
||||||
|
} == {11, 17, 26}, "STINIT state-row payloads remain scoped to proven object types")
|
||||||
|
deployment_flags = [
|
||||||
|
obj for obj in state_objects if obj["type_id"] == 26
|
||||||
|
]
|
||||||
|
check(len(deployment_flags) == 61
|
||||||
|
and {obj["initial_object_state_id"] for obj in deployment_flags} == {2},
|
||||||
|
"STINIT deployment flags expose their initial object state")
|
||||||
spike = next(
|
spike = next(
|
||||||
obj for obj in all_objects
|
obj for obj in state_objects if obj["type_id"] == 17
|
||||||
if obj["type_id"] == 17 and "unknown_fields" in obj
|
|
||||||
)
|
)
|
||||||
check(spike["unknown_fields"] == {"0xe73bb": 2},
|
check(spike["initial_object_state_id"] == 2
|
||||||
"STINIT spike payload remains raw because FIELD excludes type 17 from the gate")
|
and "non_triggering_faction_id" not in spike,
|
||||||
check(sum("unknown_fields" in obj for obj in all_objects) == 81,
|
"STINIT spikes expose state without inventing the excluded faction gate")
|
||||||
|
unknown_objects = [obj for obj in all_objects if "unknown_fields" in obj]
|
||||||
|
check(len(unknown_objects) == 3
|
||||||
|
and {obj["type_id"] for obj in unknown_objects} == {27},
|
||||||
"STINIT preserves unresolved tagged object payloads as raw evidence")
|
"STINIT preserves unresolved tagged object payloads as raw evidence")
|
||||||
extract_init.attach_stage_enemy_spawns(records)
|
extract_init.attach_stage_enemy_spawns(records)
|
||||||
first_spawn = records[0]["enemy_spawns"][0]
|
first_spawn = records[0]["enemy_spawns"][0]
|
||||||
@@ -195,6 +211,20 @@ def test_real_mixed_table() -> None:
|
|||||||
"STINIT preserves weighted object-linked enemy alternatives")
|
"STINIT preserves weighted object-linked enemy alternatives")
|
||||||
check(sum(len(record["enemy_spawns"]) for record in records) == 1378,
|
check(sum(len(record["enemy_spawns"]) for record in records) == 1378,
|
||||||
"STINIT assembles every populated enemy spawn slot")
|
"STINIT assembles every populated enemy spawn slot")
|
||||||
|
linked_deployment_spawns = []
|
||||||
|
for record in records:
|
||||||
|
objects_by_slot = {
|
||||||
|
obj["slot"]: obj for obj in record["object_placements"]
|
||||||
|
}
|
||||||
|
linked_deployment_spawns.extend(
|
||||||
|
spawn
|
||||||
|
for spawn in record["enemy_spawns"]
|
||||||
|
if (object_slot := spawn.get("object_slot")) in objects_by_slot
|
||||||
|
and objects_by_slot[object_slot]["type_id"] == 26
|
||||||
|
)
|
||||||
|
check(len(linked_deployment_spawns) == 63
|
||||||
|
and {spawn["faction_id"] for spawn in linked_deployment_spawns} == {2},
|
||||||
|
"STINIT deployment flags correlate with all linked enemy-faction spawns")
|
||||||
check(sum(
|
check(sum(
|
||||||
spawn.get("first_clear_only", False)
|
spawn.get("first_clear_only", False)
|
||||||
for record in records
|
for record in records
|
||||||
|
|||||||
@@ -1622,6 +1622,26 @@ usage = "OBINIT writes the short object descriptions displayed by the field obje
|
|||||||
source = "investigation"
|
source = "investigation"
|
||||||
confidence = "high"
|
confidence = "high"
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xe6dee"
|
||||||
|
name = "object_sprite_state_row_mode"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[100]"
|
||||||
|
value_domain = "{1,2}; 1 selects a vertical sprite row from runtime object state, 2 fixes source Y at row 0"
|
||||||
|
usage = "OBINIT metadata indexed by object type. FIELD copies the type-tagged initial payload into stage_object_runtime_state only for mode 1; DRAWOBJ likewise multiplies that runtime state by the object sprite height to select source Y only for mode 1."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0x141a4"
|
||||||
|
name = "stage_object_runtime_state"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[][50]"
|
||||||
|
value_domain = "type-dependent small integer state; initial values observed 1..3"
|
||||||
|
usage = "Per-stage, per-object-slot runtime state. FIELD initializes mode-1 objects from STINIT's first tagged payload on a fresh stage and updates capturable-object ownership; DRAWOBJ uses it as the vertical sprite-row index. Other consumers interpret it by object type."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
|
||||||
[[global]]
|
[[global]]
|
||||||
address = "0x27b9"
|
address = "0x27b9"
|
||||||
name = "stage_victory_condition_1"
|
name = "stage_victory_condition_1"
|
||||||
|
|||||||
Reference in New Issue
Block a user