Join STINIT object definitions
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
||||
# Global Variable Reference (generated)
|
||||
|
||||
5030 globals (185 curated, 4845 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
5030 globals (187 curated, 4843 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
|
||||
## choice-output
|
||||
|
||||
@@ -153,8 +153,6 @@
|
||||
| `0x26a4` | — | low | auto-shape | array |
|
||||
| `0x26b5` | — | low | auto-shape | array |
|
||||
| `0x26d3` | — | low | auto-shape | array |
|
||||
| `0x26f1` | — | low | auto-shape | array |
|
||||
| `0x2755` | — | low | auto-shape | array |
|
||||
| `0x27bd` | — | low | auto-shape | array |
|
||||
| `0x3276` | — | low | auto-shape | array |
|
||||
| `0x328a` | — | low | auto-shape | array |
|
||||
@@ -425,6 +423,8 @@
|
||||
|
||||
| address | name | conf | source | usage |
|
||||
|---|---|---|---|---|
|
||||
| `0x26f1` | object_type_names | high | investigation | OBINIT writes the authoritative object names. FIELD, SETOBJ, and DRAWOBJ use STINIT's object type id to select these definitions; extract_init joins the names to stage object placements. |
|
||||
| `0x2755` | object_type_descriptions | high | investigation | OBINIT writes the short object descriptions displayed by the field object-information path. extract_init joins populated descriptions to STINIT object placements by type id. |
|
||||
| `0x27b9` | stage_victory_condition_1 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders this line first in the victory-condition section, and FIELD copies it into the current mission-condition display. |
|
||||
| `0x27ba` | stage_victory_condition_2 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders nonempty values after stage_victory_condition_1, and FIELD copies the slot into the current mission-condition display. |
|
||||
| `0x27bb` | stage_defeat_condition_1 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders this line first in the defeat-condition section, and FIELD copies it into the current mission-condition display. |
|
||||
|
||||
@@ -280,10 +280,21 @@ variants:
|
||||
- types 6 and 36: teleport `destination_tile_x` / `destination_tile_y`;
|
||||
- types 7 and 8: treasure `item_id` / `item_quantity`, passed to ADDITEM;
|
||||
- type 28: `card_generation_list_id`, passed to CDINIT.
|
||||
- types 18--25: `non_triggering_faction_id` in the first cell when populated. FIELD suppresses the
|
||||
hazard/barrier interaction when the entering unit's faction equals this value.
|
||||
|
||||
This accounts for 220 initial-owner values, 229 teleport destinations, 626 treasure pairs, and 246 card
|
||||
list ids. Another 185 placements across object types 11, 17--21, 25--27 retain one raw payload cell because
|
||||
their type-specific meaning is not yet separable from the consumer behavior.
|
||||
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
|
||||
rather than borrowing the neighboring hazard meaning. In total, 81 placements across object types 11,
|
||||
17, 26, and 27 still retain one raw payload cell because their type-specific meaning is not yet separable
|
||||
from the consumer behavior.
|
||||
|
||||
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
|
||||
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
|
||||
from payload decoding: a known display label does not by itself establish the meaning of a tagged cell.
|
||||
|
||||
The enemy pass follows the separate 30-cell family through FIELD, SETEN, ADDEN, MVRTN, and BTRTN. Slot
|
||||
zero is reserved for ADDEN's synthesized special-unit path; the stage table populates slots 1 through 29.
|
||||
@@ -301,9 +312,10 @@ set and the spawn's cell equals 2. The joined view exposes all 485 populated cas
|
||||
Generated INIT records now retain their raw `fields`/`record_fields`/buffer keys and additionally expose a
|
||||
flat `semantic_fields` projection joined through the top-level `field_semantics` map. For STINIT, the four
|
||||
confirmed parallel buffers plus both prerequisite tables are also assembled into 2,312
|
||||
`object_placements` across 66 stages. Each placement contains its slot, type, tile coordinates, difficulty
|
||||
mask, populated positive/negative story prerequisites, optional reinforcement schedule, and the decoded
|
||||
type-tagged payload variants above. The remaining 185 undecoded payload cells stay attached under
|
||||
`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 81 undecoded payload cells stay attached 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
|
||||
@@ -357,8 +369,9 @@ 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
|
||||
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
|
||||
variants, and first-clear enemy gate are decoded; next trace the 185 remaining raw object payloads only
|
||||
where their type-specific runtime branches distinguish them. Isolate EBINIT's remaining voice/action
|
||||
families, faction-gated hazards/barriers, OBINIT definition join, and first-clear enemy gate are decoded;
|
||||
next trace the 81 remaining raw object payloads on types 11, 17, 26, and 27 only where their
|
||||
type-specific runtime branches distinguish them. 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.
|
||||
|
||||
@@ -753,10 +753,11 @@ and turn-limit outcome. Further passes establish target clear turns, performance
|
||||
the 20-slot map-texture override list, object type/tile/difficulty fields, and seven required plus five
|
||||
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
|
||||
type-tagged initial-faction, teleport, treasure, and card-list payloads; 185 payload cells on other object
|
||||
types remain raw. 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
|
||||
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,
|
||||
17, 26, and 27 remain raw. 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 after the stage's first clear.
|
||||
|
||||
Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks:
|
||||
@@ -767,7 +768,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.
|
||||
- Remaining STINIT tagged payloads on object types 11, 17--21, and 25--27, plus other data schemas when
|
||||
- Remaining STINIT tagged payloads on object types 11, 17, 26, and 27, plus 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
|
||||
|
||||
@@ -50,7 +50,7 @@ Large, table-like scripts — prime candidates for data extraction:
|
||||
`RTINIT` (67 KB), `CCINIT` (41 KB), `SKINIT` (37 KB — skills), `CDINIT` (31 KB),
|
||||
`BTANINIT` (105 KB — battle animations)
|
||||
- Smaller: `AFINIT`, `ALINIT`, `CIINIT`, `CNINIT`, `CTINIT`, `CVINIT`, `ILINIT`,
|
||||
`LAINIT`, `MAINIT`, `OBINIT`, `SPINIT`, `TRINIT`, `VIINIT`
|
||||
`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
|
||||
@@ -73,8 +73,10 @@ six scalars, cells in 29 preallocated buffers, and all 1,396 length-prefixed foo
|
||||
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, and card-generation lists; 185
|
||||
other type-specific payload cells remain raw. A second join assembles all 1,378 populated enemy slots
|
||||
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; 81 type-specific payload cells on types 11, 17, 26, and
|
||||
27 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
|
||||
alternatives, difficulty-specific movement/battle routine sets, and 485 first-clear-only replay gates.
|
||||
|
||||
|
||||
@@ -55,9 +55,9 @@ 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`, including type-tagged teleport/treasure/card/initial-faction payloads, 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 matching `<MES>.BIN` when supported + `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, 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` |
|
||||
| `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, STINIT object/enemy joins, 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}` |
|
||||
|
||||
## VM
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
|
||||
- [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)** [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, 604 reinforcement schedules, and typed initial-faction, teleport, treasure, and card-list payloads while preserving 185 unresolved tagged 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.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.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).
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
name — records keyed by a name string. Each record: set-string(name), static field writes,
|
||||
set-string(desc). Arrays indexed by record id in lockstep (+1/record).
|
||||
(SKINIT skills, ITINIT items, EBINIT units)
|
||||
(SKINIT skills, ITINIT items, EBINIT units, OBINIT object definitions)
|
||||
numeric— column table with NO names: mov/copy-to-global into parallel int arrays, keyed
|
||||
by an incrementing index column. (CGINIT gallery)
|
||||
footer — copy-local-array (op 0x64) bulk-loads length-prefixed arrays from the file
|
||||
@@ -394,6 +394,19 @@ def extract_name(scr):
|
||||
"desc_array_bases": {k: f"0x{v:x}" for k, v in sorted(desc_bases.items())}}
|
||||
|
||||
|
||||
@cache
|
||||
def object_type_definitions() -> dict[int, dict]:
|
||||
"""Load OBINIT's authoritative names and descriptions keyed by object type id."""
|
||||
records, _ = extract_name(sys4load.load(resolve("OBINIT")))
|
||||
return {
|
||||
record["id"]: {
|
||||
"name": record["name"],
|
||||
**({"description": record["desc"]} if record.get("desc") else {}),
|
||||
}
|
||||
for record in records
|
||||
}
|
||||
|
||||
|
||||
def _int_writes(scr):
|
||||
"""Ordered (addr, value_arg) for global-int mov / copy-to-global."""
|
||||
out = []
|
||||
@@ -557,8 +570,12 @@ def attach_semantic_fields(records: list[dict], semantics: dict[str, str]) -> No
|
||||
record.pop("semantic_fields", None)
|
||||
|
||||
|
||||
def attach_stage_object_placements(records: list[dict]) -> None:
|
||||
def attach_stage_object_placements(
|
||||
records: list[dict], definitions: dict[int, dict] | None = None
|
||||
) -> None:
|
||||
"""Assemble STINIT's parallel object buffers into modder-facing slot records."""
|
||||
if definitions is None:
|
||||
definitions = object_type_definitions()
|
||||
known_fields = {
|
||||
"type_id": "0xe7389",
|
||||
"tile_x": "0xe7325",
|
||||
@@ -574,8 +591,15 @@ def attach_stage_object_placements(records: list[dict]) -> None:
|
||||
type_key = f"{known_fields['type_id']}/{slot}"
|
||||
if type_key not in fields:
|
||||
continue
|
||||
obj = {"slot": slot}
|
||||
type_id = fields[type_key]
|
||||
obj = {"slot": slot, "type_id": type_id}
|
||||
if definition := definitions.get(type_id):
|
||||
obj["type_name"] = definition["name"]
|
||||
if description := definition.get("description"):
|
||||
obj["type_description"] = description
|
||||
for semantic_name, base in known_fields.items():
|
||||
if semantic_name == "type_id":
|
||||
continue
|
||||
if (key := f"{base}/{slot}") in fields:
|
||||
obj[semantic_name] = fields[key]
|
||||
required = [
|
||||
@@ -599,7 +623,6 @@ def attach_stage_object_placements(records: list[dict]) -> None:
|
||||
for base in ("0xe73bb", "0xe73ed")
|
||||
if (key := f"{base}/{slot}") in fields
|
||||
}
|
||||
type_id = obj["type_id"]
|
||||
if type_id in (1, 2, 3, 4) and "0xe73bb" in payload:
|
||||
obj["initial_faction_id"] = payload.pop("0xe73bb")
|
||||
elif type_id in (6, 36):
|
||||
@@ -614,6 +637,8 @@ def attach_stage_object_placements(records: list[dict]) -> None:
|
||||
obj["item_quantity"] = payload.pop("0xe73ed")
|
||||
elif type_id == 28 and "0xe73bb" in payload:
|
||||
obj["card_generation_list_id"] = payload.pop("0xe73bb")
|
||||
elif 18 <= type_id <= 25 and "0xe73bb" in payload:
|
||||
obj["non_triggering_faction_id"] = payload.pop("0xe73bb")
|
||||
unknown = payload
|
||||
if unknown:
|
||||
obj["unknown_fields"] = unknown
|
||||
@@ -747,7 +772,9 @@ def write_data_index(data_dir: Path) -> None:
|
||||
"Mixed-mode tables preserve the sparse selector id, branch offset, condition strings,",
|
||||
"scalar fields, cells within preallocated buffers, and length-prefixed footer arrays.",
|
||||
"STINIT additionally joins confirmed parallel buffers into per-slot `object_placements`",
|
||||
"and `enemy_spawns`; unresolved type-specific/mode parameters remain in `unknown_fields`.",
|
||||
"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`.",
|
||||
"Use `tools/init_table_profile.py <TABLE> --build` to generate value/population and",
|
||||
"direct-consumer evidence.",
|
||||
"",
|
||||
@@ -798,6 +825,7 @@ def main() -> int:
|
||||
semantics = field_semantics(recs, meta.get("array_layouts"))
|
||||
attach_semantic_fields(recs, semantics)
|
||||
if mode == "mixed" and name == "STINIT":
|
||||
meta["object_definition_table"] = "OBINIT"
|
||||
attach_stage_object_placements(recs)
|
||||
attach_stage_enemy_spawns(recs)
|
||||
out = {"table": name, "source": scr.path.name, "magic": scr.magic, "mode": mode,
|
||||
|
||||
@@ -29,6 +29,7 @@ def test_real_name_tables() -> None:
|
||||
"SKINIT.BIN": (300, 131),
|
||||
"ITINIT.BIN": (1000, 287),
|
||||
"EBINIT.BIN": (1000, 277),
|
||||
"OBINIT.BIN": (100, 46),
|
||||
}
|
||||
scripts = paths.scripts()
|
||||
for name, (span, count) in expected.items():
|
||||
@@ -55,6 +56,14 @@ def test_real_name_tables() -> None:
|
||||
check(by_id[108]["record_fields"]["0x906f9/30/8"] == -5,
|
||||
"ITINIT item 108 preserves its paralysis-removal delta")
|
||||
|
||||
objects, _ = extract_init.extract_name(sys4load.load(scripts["OBINIT.BIN"]))
|
||||
object_by_id = {record["id"]: record for record in objects}
|
||||
check(object_by_id[17]["name"] == "針"
|
||||
and object_by_id[17]["desc"] == "HP-2",
|
||||
"OBINIT object 17 preserves its name and effect description")
|
||||
check(object_by_id[28]["name"] == "カード取得",
|
||||
"OBINIT object ids provide authoritative STINIT type labels")
|
||||
|
||||
|
||||
def test_static_negative_write() -> None:
|
||||
class Instruction:
|
||||
@@ -110,6 +119,8 @@ def test_real_mixed_table() -> None:
|
||||
check(first_object == {
|
||||
"slot": 1,
|
||||
"type_id": 1,
|
||||
"type_name": "拠点",
|
||||
"type_description": "▲命中・回避・防御",
|
||||
"tile_x": 13,
|
||||
"tile_y": 1,
|
||||
"difficulty_mask": 7,
|
||||
@@ -132,6 +143,8 @@ def test_real_mixed_table() -> None:
|
||||
)
|
||||
check(stage1_slot8["card_generation_list_id"] == 1,
|
||||
"STINIT card objects expose their generation-list id")
|
||||
check(stage1_slot8["type_name"] == "カード取得",
|
||||
"STINIT object placements join OBINIT type names")
|
||||
stage1_slot9 = next(
|
||||
obj for obj in records[0]["object_placements"] if obj["slot"] == 9
|
||||
)
|
||||
@@ -146,7 +159,20 @@ def test_real_mixed_table() -> None:
|
||||
all_objects = [
|
||||
obj for record in records for obj in record["object_placements"]
|
||||
]
|
||||
check(sum("unknown_fields" in obj for obj in all_objects) == 185,
|
||||
check(sum("non_triggering_faction_id" in obj for obj in all_objects) == 104,
|
||||
"STINIT hazard/barrier payloads expose their FIELD-proven faction gate")
|
||||
check({
|
||||
obj["non_triggering_faction_id"]
|
||||
for obj in all_objects
|
||||
if "non_triggering_faction_id" in obj
|
||||
} == {1, 2, 3}, "STINIT faction gates retain all observed faction ids")
|
||||
spike = next(
|
||||
obj for obj in all_objects
|
||||
if obj["type_id"] == 17 and "unknown_fields" in obj
|
||||
)
|
||||
check(spike["unknown_fields"] == {"0xe73bb": 2},
|
||||
"STINIT spike payload remains raw because FIELD excludes type 17 from the gate")
|
||||
check(sum("unknown_fields" in obj for obj in all_objects) == 81,
|
||||
"STINIT preserves unresolved tagged object payloads as raw evidence")
|
||||
extract_init.attach_stage_enemy_spawns(records)
|
||||
first_spawn = records[0]["enemy_spawns"][0]
|
||||
|
||||
@@ -1602,6 +1602,26 @@ source = "inference"
|
||||
confidence = "med"
|
||||
depends_on = []
|
||||
|
||||
[[global]]
|
||||
address = "0x26f1"
|
||||
name = "object_type_names"
|
||||
category = "string-table"
|
||||
type = "string[100]"
|
||||
value_domain = "OBINIT object definition names for ids 1..46"
|
||||
usage = "OBINIT writes the authoritative object names. FIELD, SETOBJ, and DRAWOBJ use STINIT's object type id to select these definitions; extract_init joins the names to stage object placements."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0x2755"
|
||||
name = "object_type_descriptions"
|
||||
category = "string-table"
|
||||
type = "string[100]"
|
||||
value_domain = "optional OBINIT player-facing object behavior/effect descriptions for ids 1..46"
|
||||
usage = "OBINIT writes the short object descriptions displayed by the field object-information path. extract_init joins populated descriptions to STINIT object placements by type id."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0x27b9"
|
||||
name = "stage_victory_condition_1"
|
||||
|
||||
Reference in New Issue
Block a user