Resolve final STINIT object payloads
This commit is contained in:
@@ -296,7 +296,17 @@ runtime state by the object's sprite height to select its vertical source row. T
|
||||
(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.
|
||||
faction id.
|
||||
|
||||
The final three populated tagged cells are understood as engine-dead authoring data rather than a hidden
|
||||
type-27 (`異界の門`) parameter. STINIT explicitly writes `2` to the first payload cell for object slot 22
|
||||
in stages 52--54. Type 27 has no OBINIT state-row mode, so FIELD's fresh-stage initializer does not copy
|
||||
that value to runtime state; the generic interaction dispatch also has no type-27 branch. Its dedicated
|
||||
turn path reads only the object's active flag, type, reinforcement interval/limit, and coordinates, then
|
||||
forces ADDEN enemy slot 0. ADDEN hardcodes EBINIT unit 465 (`漂着した異界の姫/BOSS`) into runtime entity
|
||||
slot 49; on success FIELD changes to BGM 10 and reports `異界の姫が漂着した!`. Neither `0xe73bb` nor
|
||||
`0xe73ed` participates. The join therefore preserves the three explicit writes under
|
||||
`ignored_payload_fields` instead of inventing a semantic name or leaving them unresolved.
|
||||
|
||||
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
|
||||
@@ -323,8 +333,9 @@ 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
|
||||
name/available description, tile coordinates, difficulty mask, populated positive/negative story
|
||||
prerequisites, optional reinforcement schedule, and the decoded type-tagged payload variants above. The
|
||||
remaining three undecoded type-27 payload cells stay attached under
|
||||
`unknown_fields`, so this convenience view loses no evidence or invents names.
|
||||
three engine-dead type-27 payload writes stay attached under `ignored_payload_fields`; there are no
|
||||
remaining populated object payloads under `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
|
||||
object-linked placement data when present, difficulty and story gates, level rules, random-selection
|
||||
weight, movement/battle routine rows, and `first_clear_only` replay gating. Raw footer metadata stays in
|
||||
@@ -378,8 +389,8 @@ The v1 map labels *shapes and tables*; the next increments add *meaning*, cheape
|
||||
STINIT's mixed stage records now have machine-readable investigation surfaces, including joined object
|
||||
placements and enemy spawns. STINIT's universal object schedule, consumer-proven tagged payload
|
||||
families, faction-gated hazards/barriers, initialized object states, OBINIT definition join, and
|
||||
first-clear enemy gate are decoded; next trace the three remaining raw type-27 payloads only where its
|
||||
special-spawn path distinguishes them. Isolate EBINIT's remaining voice/action
|
||||
first-clear enemy gate are decoded, and its last three populated tagged cells are proven ignored by the
|
||||
type-27 special-spawn path. Isolate EBINIT's remaining voice/action
|
||||
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, …)
|
||||
and fold in other `set-string`/`copy-to-global` writers not covered by the `*INIT` set.
|
||||
|
||||
@@ -755,8 +755,9 @@ forbidden story prerequisites. Generated records now carry a joined `semantic_fi
|
||||
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
|
||||
supplies authoritative names for all 46 object types and descriptions for 34. Its state-row mode and
|
||||
FIELD/DRAWOBJ initialization path resolve 78 initial object states on types 11, 17, and 26; only three
|
||||
type-27 payload cells remain raw. The separate 30-cell enemy family is also decoded:
|
||||
FIELD/DRAWOBJ initialization path resolve 78 initial object states on types 11, 17, and 26. FIELD's
|
||||
dedicated special-spawn path proves the last three populated type-27 tagged writes are ignored by the
|
||||
engine; they remain preserved as provenance. The separate 30-cell enemy family is also decoded:
|
||||
FIELD/SETEN/ADDEN establish unit/faction, direct or object-linked placement, difficulty/story gates, level
|
||||
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
|
||||
@@ -770,8 +771,7 @@ Once the natural spine and first gameplay loop are trustworthy, broaden in indep
|
||||
- Unit statistics, equipment, inventory, skills, magic, heroine forms, and progression.
|
||||
- Combat resolution presentation, enemy turns/AI services, and win/loss transitions.
|
||||
- A full chapter of ADV and the scene types encountered between gameplay segments.
|
||||
- The three remaining STINIT tagged payloads on object type 27, plus other data schemas when
|
||||
their runtime consumers make them necessary.
|
||||
- Other data schemas when their runtime consumers make them necessary.
|
||||
|
||||
The high-level Phase B direction remains canonical in `docs/remake-architecture-and-roadmap.md`; this file
|
||||
only provides the execution framework.
|
||||
|
||||
@@ -76,8 +76,9 @@ across 66 stages. It also exposes 604 reinforcement schedules and consumer-prove
|
||||
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; only three type-27 payload cells remain raw. A second join assembles
|
||||
all 1,378 populated enemy slots
|
||||
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.
|
||||
|
||||
|
||||
@@ -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_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, 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` |
|
||||
| `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), preserves engine-dead type-27 writes under `ignored_payload_fields`, and exposes 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}` |
|
||||
| `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}` |
|
||||
|
||||
@@ -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.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.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.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; FIELD's dedicated special-spawn path proves the final three populated type-27 tagged writes are engine-dead, so they remain visible as ignored provenance rather than unresolved semantics. 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`.
|
||||
- [ ] **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).
|
||||
|
||||
|
||||
@@ -646,6 +646,12 @@ def attach_stage_object_placements(
|
||||
and definition["uses_runtime_state_sprite_row"]
|
||||
and "0xe73bb" in payload):
|
||||
obj["initial_object_state_id"] = payload.pop("0xe73bb")
|
||||
elif type_id == 27 and payload:
|
||||
# FIELD's dedicated otherworld-gate branch consumes the common
|
||||
# schedule and coordinates, then calls ADDEN's hard-coded slot-0
|
||||
# special-unit path. It never reads either tagged payload cell.
|
||||
obj["ignored_payload_fields"] = payload
|
||||
payload = {}
|
||||
unknown = payload
|
||||
if unknown:
|
||||
obj["unknown_fields"] = unknown
|
||||
@@ -781,7 +787,8 @@ def write_data_index(data_dir: Path) -> None:
|
||||
"STINIT additionally joins confirmed parallel buffers into per-slot `object_placements`",
|
||||
"and `enemy_spawns`. Its object type ids join to OBINIT's authoritative names and",
|
||||
"available descriptions; consumer-proven tagged payload variants receive semantic names while",
|
||||
"unresolved type-specific/mode parameters remain in `unknown_fields`.",
|
||||
"engine-dead tagged writes remain in `ignored_payload_fields` and unresolved",
|
||||
"type-specific/mode parameters remain in `unknown_fields`.",
|
||||
"Use `tools/init_table_profile.py <TABLE> --build` to generate value/population and",
|
||||
"direct-consumer evidence.",
|
||||
"",
|
||||
|
||||
@@ -186,10 +186,17 @@ def test_real_mixed_table() -> None:
|
||||
check(spike["initial_object_state_id"] == 2
|
||||
and "non_triggering_faction_id" not in spike,
|
||||
"STINIT spikes expose state without inventing the excluded faction gate")
|
||||
ignored_objects = [
|
||||
obj for obj in all_objects if "ignored_payload_fields" in obj
|
||||
]
|
||||
check(len(ignored_objects) == 3
|
||||
and {obj["type_id"] for obj in ignored_objects} == {27}
|
||||
and {tuple(obj["ignored_payload_fields"].items()) for obj in ignored_objects}
|
||||
== {(("0xe73bb", 2),)},
|
||||
"STINIT preserves explicitly written but engine-ignored type-27 payloads")
|
||||
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")
|
||||
check(not unknown_objects,
|
||||
"STINIT has no unresolved populated tagged object payloads")
|
||||
extract_init.attach_stage_enemy_spawns(records)
|
||||
first_spawn = records[0]["enemy_spawns"][0]
|
||||
check(first_spawn == {
|
||||
|
||||
Reference in New Issue
Block a user