Decode CVINIT voice configuration

This commit is contained in:
gamer147
2026-07-23 20:01:53 -04:00
parent 7bec889eed
commit 3a46b2236a
9 changed files with 380 additions and 18 deletions

View File

@@ -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)
5049 globals (316 curated, 4733 auto shape-inferred). Source of truth: `vm-map/globals.toml`. 5049 globals (319 curated, 4730 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
## choice-output ## choice-output
@@ -69,6 +69,7 @@
| `0x53643` | entity_movement_routine_progress | high | investigation | Per-runtime-entity movement-routine progress counters. FIELD clears all fifty rows, movement providers increment the current step, and MVRTN compares it with movement_routine_minimum_progress_counts before enabling a step. | | `0x53643` | entity_movement_routine_progress | high | investigation | Per-runtime-entity movement-routine progress counters. FIELD clears all fifty rows, movement providers increment the current step, and MVRTN compares it with movement_routine_minimum_progress_counts before enabling a step. |
| `0x53e13` | entity_carried_item_ids | high | investigation | Two carried/drop-item slots per runtime entity. SETEN initializes slot 0 from the unit's starting equipment item, FIELD fills an empty or matching slot when treasure is collected, and RTN_M009 requires an empty slot or an item-id match before approaching a chest/treasure object. Columns: 0=slot_0, 1=slot_1. | | `0x53e13` | entity_carried_item_ids | high | investigation | Two carried/drop-item slots per runtime entity. SETEN initializes slot 0 from the unit's starting equipment item, FIELD fills an empty or matching slot when treasure is collected, and RTN_M009 requires an empty slot or an item-id match before approaching a chest/treasure object. Columns: 0=slot_0, 1=slot_1. |
| `0x53e77` | entity_carried_item_counts | high | investigation | Quantities for the two per-entity carried/drop-item slots. SETEN seeds a starting item with quantity one; FIELD adds treasure quantities to the matching slot and caps the result at 999. Columns: 0=slot_0, 1=slot_1. | | `0x53e77` | entity_carried_item_counts | high | investigation | Quantities for the two per-entity carried/drop-item slots. SETEN seeds a starting item with quantity one; FIELD adds treasure quantities to the matching slot and caps the result at 999. Columns: 0=slot_0, 1=slot_1. |
| `0x56223` | unit_story_speaker_seen_flags | high | investigation | Scene scripts set and persist a unit's cell when drawing that speaker's name. CONFIG reloads the twelve CVINIT-mapped unit cells and enables each named character-voice control only after its speaker has been encountered. |
| `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. |
| `0x56738` | entity_battle_routine_random_rolls | high | investigation | Per-runtime-entity battle-step random rolls. FIELD and BTL fill every active entity's twenty cells with random-modulo-100; BTRTN executes a step when the matching roll is below battle_routine_activation_percents. | | `0x56738` | entity_battle_routine_random_rolls | high | investigation | Per-runtime-entity battle-step random rolls. FIELD and BTL fill every active entity's twenty cells with random-modulo-100; BTRTN executes a step when the matching roll is below battle_routine_activation_percents. |
| `0x56b20` | entity_patrol_waypoint_indices | high | investigation | RTN_M011 compares each step's one-based waypoint_ordinal minus one with the current entity's value. Reaching the selected destination advances this value modulo the largest RTN_M011 waypoint ordinal in the routine set. | | `0x56b20` | entity_patrol_waypoint_indices | high | investigation | RTN_M011 compares each step's one-based waypoint_ordinal minus one with the current entity's value. Reaching the selected destination advances this value modulo the largest RTN_M011 waypoint ordinal in the routine set. |
@@ -77,7 +78,9 @@
| `0x573bb` | unit_class_change_state | high | investigation | Persistent ten-slot class-change state for each playable unit. CALCCC copies the current unit's row to class_change_state_work before invoking the rule scripts and persists the updated row; CCINIT requires the rule's slot to be zero so each promotion is applied once. Columns: 0=promotion_slot_1, 1=promotion_slot_2, 2=promotion_slot_3, 3=promotion_slot_4, 4=promotion_slot_5, 5=promotion_slot_6, 6=promotion_slot_7, 7=promotion_slot_8, 8=promotion_slot_9, 9=promotion_slot_10. | | `0x573bb` | unit_class_change_state | high | investigation | Persistent ten-slot class-change state for each playable unit. CALCCC copies the current unit's row to class_change_state_work before invoking the rule scripts and persists the updated row; CCINIT requires the rule's slot to be zero so each promotion is applied once. Columns: 0=promotion_slot_1, 1=promotion_slot_2, 2=promotion_slot_3, 3=promotion_slot_4, 4=promotion_slot_5, 5=promotion_slot_6, 6=promotion_slot_7, 7=promotion_slot_8, 8=promotion_slot_9, 9=promotion_slot_10. |
| `0x5f0ed` | scene_decision_seen_flags | high | investigation | Persistent seen-state for progression decisions. Numbered scene scripts set the current scjump_decision_out cell after playback and synchronize it through the persistence opcode; INFOVO uses VIINIT's one-based prerequisites to unlock glossary topics. | | `0x5f0ed` | scene_decision_seen_flags | high | investigation | Persistent seen-state for progression decisions. Numbered scene scripts set the current scjump_decision_out cell after playback and synchronize it through the persistence opcode; INFOVO uses VIINIT's one-based prerequisites to unlock glossary topics. |
| `0x624bf` | unit_voice_family_unit_ids | high | investigation | CNINIT's sparse unit-id keyed voice-family normalization map. Story, history, field, and battle voice paths map the current unit variant through this table before selecting the family-level voice-suppression state; 175 of 277 authored rows alias a variant to another representative unit id. | | `0x624bf` | unit_voice_family_unit_ids | high | investigation | CNINIT's sparse unit-id keyed voice-family normalization map. Story, history, field, and battle voice paths map the current unit variant through this table before selecting the family-level voice-suppression state; 175 of 277 authored rows alias a variant to another representative unit id. |
| `0x628a7` | unit_voice_suppression_flag_ids | high | investigation | Second stage of the shared voice-eligibility chain. Story, history, field, and battle paths normalize the current unit through unit_voice_family_unit_ids, index this table by that representative unit id, and suppress voice playback when the selected character_voice_suppressed cell is nonzero. | | `0x628a7` | unit_voice_suppression_flag_ids | high | investigation | CVINIT's unit-to-setting inverse map for the twelve named character voice controls. Story, history, field, and battle paths normalize the current unit through unit_voice_family_unit_ids, index this table by that representative unit id, and suppress voice playback when the selected character_voice_suppressed cell is nonzero. |
| `0x62c8f` | character_voice_setting_unit_ids | high | investigation | CVINIT's CONFIG-row join. CONFIG iterates setting slots 1..12, resolves each unit id through unit_story_display_names for its label, and tests the same unit's unit_story_speaker_seen_flags cell before exposing the row. CVINIT also writes the exact inverse mapping to unit_voice_suppression_flag_ids. Columns: 0=system_or_reserved, 1=lily, 2=sylphine, 3=sasune, 4=vidal, 5=estelle, 6=nelly, 7=tiofania, 8=colette, 9=bridget, 10=octavia, 11=fam, 12=deirdre. |
| `0x62cad` | character_voice_preview_asset_ids | high | investigation | CVINIT's thirteen voice-setting preview clips. CONFIG indexes this array by setting slot and plays the selected clip; slot 0 is the non-unit system voice, while slots 1..12 join to character_voice_setting_unit_ids. Columns: 0=system_voice, 1=lily, 2=sylphine, 3=sasune, 4=vidal, 5=estelle, 6=nelly, 7=tiofania, 8=colette, 9=bridget, 10=octavia, 11=fam, 12=deirdre. |
| `0x62cd1` | gallery_image_assets | high | investigation | CGINIT's sparse gallery asset rows. CGMODE tests and displays column 0 as the full-size gallery image. SAVE and SELSTAGE scan column 0 for the current image and, when column 1 is populated, load that 112x84 preview instead of capturing the current screen. Columns: 0=gallery_image_asset_id, 1=save_stage_preview_asset_id. | | `0x62cd1` | gallery_image_assets | high | investigation | CGINIT's sparse gallery asset rows. CGMODE tests and displays column 0 as the full-size gallery image. SAVE and SELSTAGE scan column 0 for the current image and, when column 1 is populated, load that 112x84 preview instead of capturing the current screen. Columns: 0=gallery_image_asset_id, 1=save_stage_preview_asset_id. |
| `0x63c71` | gallery_thumbnail_sheet_ids | high | investigation | CGINIT's gallery thumbnail-atlas selector. CGMODE subtracts one, remaps the configured sheet through gallery_thumbnail_sheet_asset_ids, and groups every populated gallery record under that sheet. | | `0x63c71` | gallery_thumbnail_sheet_ids | high | investigation | CGINIT's gallery thumbnail-atlas selector. CGMODE subtracts one, remaps the configured sheet through gallery_thumbnail_sheet_asset_ids, and groups every populated gallery record under that sheet. |
| `0x64441` | gallery_thumbnail_slot_ids | high | investigation | CGINIT's slot within the selected 6x5 thumbnail atlas. CGMODE subtracts one, draws that one of thirty 126x95 cells, and reports the unlocked/total variant counts associated with the slot. | | `0x64441` | gallery_thumbnail_slot_ids | high | investigation | CGINIT's slot within the selected 6x5 thumbnail atlas. CGMODE subtracts one, draws that one of thirty 126x95 cells, and reports the unlocked/total variant counts associated with the slot. |
@@ -285,7 +288,6 @@
| `0x522bb` | — | low | auto-shape | array | | `0x522bb` | — | low | auto-shape | array |
| `0x53ede` | — | low | auto-shape | array | | `0x53ede` | — | low | auto-shape | array |
| `0x55e3b` | — | low | auto-shape | array | | `0x55e3b` | — | low | auto-shape | array |
| `0x56223` | — | low | auto-shape | array |
| `0x56b52` | — | low | auto-shape | array | | `0x56b52` | — | low | auto-shape | array |
| `0x56f6d` | — | low | auto-shape | array | | `0x56f6d` | — | low | auto-shape | array |
| `0x577a3` | — | low | auto-shape | array | | `0x577a3` | — | low | auto-shape | array |
@@ -300,8 +302,6 @@
| `0x62469` | — | low | auto-shape | array | | `0x62469` | — | low | auto-shape | array |
| `0x6247d` | — | low | auto-shape | array | | `0x6247d` | — | low | auto-shape | array |
| `0x624a3` | — | low | auto-shape | array | | `0x624a3` | — | low | auto-shape | array |
| `0x62c8f` | — | low | auto-shape | array |
| `0x62cad` | — | low | auto-shape | array |
| `0x653e1` | — | low | auto-shape | array | | `0x653e1` | — | low | auto-shape | array |
| `0x65bb1` | — | low | auto-shape | array | | `0x65bb1` | — | low | auto-shape | array |
| `0x66421` | — | low | auto-shape | array | | `0x66421` | — | low | auto-shape | array |

View File

@@ -669,16 +669,17 @@ are *not* story flags — the miner over-tags them; they are recategorized `unkn
The v1 map labels *shapes and tables*; the next increments add *meaning*, cheapest first: The v1 map labels *shapes and tables*; the next increments add *meaning*, cheapest first:
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, EBINIT, STINIT, CCINIT, SCINIT, RTINIT, MAINIT, ILINIT, CGINIT, ALINIT, AFINIT, and CTINIT now have 1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, EBINIT, STINIT, CCINIT, SCINIT, RTINIT, MAINIT, ILINIT, CGINIT, ALINIT, AFINIT, CTINIT, and CVINIT now have
machine-readable investigation surfaces and semantic joins; EBINIT's populated schema is fully named, machine-readable investigation surfaces and semantic joins; EBINIT's populated schema is fully named,
STINIT's joined object/enemy payloads are decoded, and CCINIT's 71 class-change rules expose predicates STINIT's joined object/enemy payloads are decoded, and CCINIT's 71 class-change rules expose predicates
and effects. SCINIT closes the progression decision-to-scene join, and RTINIT's twenty movement/battle and effects. SCINIT closes the progression decision-to-scene join, and RTINIT's twenty movement/battle
banks are structurally decoded with every populated movement-parameter cell classified and all 1,043 banks are structurally decoded with every populated movement-parameter cell classified and all 1,043
shipped movement steps joined to provider behavior. ILINIT/RECOVER closes the condition ABI, CGINIT shipped movement steps joined to provider behavior. ILINIT/RECOVER closes the condition ABI, CGINIT
closes the gallery registry, ALINIT closes the 107-recipe alchemy registry with complete ITINIT closes the gallery registry, ALINIT closes the 107-recipe alchemy registry with complete ITINIT
joins, AFINIT closes the signed affinity/tuning/facility tables, and CTINIT closes INPUTNAME's joins, AFINIT closes the signed affinity/tuning/facility tables, CTINIT closes INPUTNAME's
five-page character palette. With RTINIT's used-provider surface closed and five-page character palette, and CVINIT closes CONFIG's thirteen preview clips plus the
ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and INFOMES/MES classified, audit CVINIT next; bidirectional character-to-suppression-setting join. With RTINIT's used-provider surface closed and
ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and INFOMES/MES classified, audit MPINIT next;
never assign one universal meaning to a parameter bank whose meaning varies by provider selector. never assign one universal meaning to a parameter bank whose meaning varies by provider selector.
2. **Label remaining 2D record tables by their readers** — cross-reference which scripts read each 2. **Label remaining 2D record tables by their readers** — cross-reference which scripts read each
`rec[sN]` table and infer purpose from context, preserving reserved rows and sparse cells. RECOVER's `rec[sN]` table and infer purpose from context, preserving reserved rows and sparse cells. RECOVER's

View File

@@ -2925,6 +2925,30 @@ all 350 positions so layout gaps remain explicit rather than collapsing into a f
Regressions account for every instruction in both scripts, signed affinity values, all tuning and Regressions account for every instruction in both scripts, signed affinity values, all tuning and
facility curves, representative characters and gaps, and raw-to-semantic projection. facility curves, representative characters and gaps, and raw-to-semantic projection.
**Next:** audit CVINIT. Its generic output is already small (37 writes, thirteen rows, ten apparent ## Data-semantics sidebar: CVINIT character-voice configuration (2026-07-23)
fields), so direct consumer tracing should quickly distinguish a valid sparse registry from the last
remaining ownership artifact before moving to MPINIT. CVINIT's generic thirteen-row shape was close but incomplete: it grouped 35 of the script's 37 writes
and presented a twelve-entry inverse map as eight unrelated address-derived fields. The dedicated
schema accounts for all 38 instructions (37 static writes plus `exit`) as three coordinated arrays:
thirteen CONFIG preview-voice assets at `0x62cad`, twelve setting-slot-to-EBINIT-unit ids at
`0x62c8f + slot`, and twelve inverse unit-to-setting ids at `0x628a7 + unit`.
CONFIG proves the presentation contract. Slot 0 is a non-unit system voice preview; slots 1..12 map
to Lily, Sylphine, Sasune, Vidal, Estelle, Nelly, Tiofania, Colette, Bridget, Octavia, Fam, and
Deirdre. CONFIG resolves those unit ids through the shared story-display-name table, reloads each
unit's persisted speaker-seen flag to decide whether the row is available, plays the slot's preview
OGG, and edits the matching cell in the thirteen-entry `character_voice_suppressed` settings array.
All thirteen preview ids resolve to shipped audio assets.
The inverse map closes the runtime voice-filter chain rather than duplicating the CONFIG row map.
Story, history, field, and battle paths first normalize the active EBINIT unit through CNINIT's
voice-family unit table, then map that representative unit through CVINIT's inverse table and test
the selected suppression cell. The twelve forward and inverse entries round-trip exactly. Scene
scripts independently set and persist the unit-keyed speaker-seen flags when each speaker first
appears, explaining CONFIG's unlock gate.
Regressions protect complete instruction accounting, all asset and EBINIT joins, the system slot,
all twelve round trips, speaker-seen addresses, and raw-to-semantic projection.
**Next:** audit MPINIT's 1,472 structurally regular footer-array records by their direct map readers,
grouping rows into consumer-proven families before naming any shallow address-only fields.

View File

@@ -115,6 +115,12 @@ reserved zero row, and equipment-tuning/alchemy/magic facility progression thres
INPUTNAME's separate `5 × 70` character palette: 273 authored hiragana, katakana, Latin, numeral, and INPUTNAME's separate `5 × 70` character palette: 273 authored hiragana, katakana, Latin, numeral, and
symbol cells with all reserved gaps retained. symbol cells with all reserved gaps retained.
CVINIT is CONFIG's character-voice control registry. Its 37 writes form thirteen preview-voice asset
slots, a twelve-entry setting-slot-to-EBINIT-unit map, and the exact inverse unit-to-setting map used
after CNINIT's voice-family normalization. Slot 0 is the non-unit system voice; slots 1..12 join to
Lily and eleven other named characters. CONFIG uses persisted per-unit speaker-seen flags to reveal those
twelve rows, plays the corresponding preview clip, and writes the selected suppression setting.
RTINIT is a seventh banked shape. Its 3,336 writes populate twenty parallel `1000 × 20` banks: ten 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. 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 The generated 172 routine-set records assemble 1,043 movement steps and fourteen battle steps, join

View File

@@ -57,7 +57,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
| `extract_message_table.py` | Discover repeated global-id dispatch chains in either fallthrough-body (ITMES/SKMES/MAMES) or branch-target-body (VIMES/EIMES/CIMES) form, reconstruct player-facing text and furigana, preserve guard/body offsets, and emit an ID-keyed message table. Title-bearing tables expose `title`/`description`; EIMES exposes its untitled lines as `summary`/`strategy`; CIMES exposes one untitled multiline `biography`; MAMES exposes untitled `description` text. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN``build/data/<OUTNAME>.json` | | `extract_message_table.py` | Discover repeated global-id dispatch chains in either fallthrough-body (ITMES/SKMES/MAMES) or branch-target-body (VIMES/EIMES/CIMES) form, reconstruct player-facing text and furigana, preserve guard/body offsets, and emit an ID-keyed message table. Title-bearing tables expose `title`/`description`; EIMES exposes its untitled lines as `summary`/`strategy`; CIMES exposes one untitled multiline `biography`; MAMES exposes untitled `description` text. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN``build/data/<OUTNAME>.json` |
| `extract_init.py` | Parse a `*INIT` data source (auto-detects name / numeric / footer / mixed / rules / dispatch / banked 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`; when flat global ranges overlap, a parallel base established by other records wins over a coincidental row-table cell calculation. Dedicated name-mode schemas recover VIINIT's pre-title glossary prerequisites and CIINIT's profile name, backing-unit, optional portrait, and reserved placement columns. 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. Rules mode decodes CCINIT's source-ordered class-change predicates and output accumulators, joining EBINIT unit names, SKINIT skill names, titles, named stat bonuses, cost deltas, and applied-state slots. Dispatch mode decodes SCINIT's paired decision-to-packed-scene and authored-chapter arrays, preserving source-order overwrites, resolving SYS4INI script names, and cross-checking chapter tags against decoded SCJUMP paths. Banked mode decodes RTINIT's twenty parallel 1000-by-20 movement/battle routine banks, retains all assignments and overwrites, assembles final per-slot steps, resolves provider selectors to RTN_M/RTN_B scripts, and applies selector-scoped schemas where proven. Current schemas 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, including progress-only, randomized roaming, object-slot, coordinate, enemy/ally, treasure and Magic Pillar search, Healing Feather, waypoint, faction-terrain, retreat, reachable normal-attack routing, immediate offensive target/action selection, and immediate allied healing. Parameterless behavior schemas publish an empty `parameter_fields` mapping while still joining `provider_behavior`, target selection, action selection, and completion rules. Provider defaults such as M004's unwritten slot 0, M010's zero/HP resource index, and M013's any-foreign-faction filter are projected explicitly and counted separately from populated source cells. Authored cells proven unread by M001/M008 remain under per-step `ignored_movement_parameters` and contribute to top-level `ignored_movement_parameter_count`; they are not silently discarded or assigned invented meanings. Raw provider banks remain intact, and top-level `movement_provider_parameter_schemas` documents the projection. ITINIT, SKINIT, VIINIT, EBINIT, and CIINIT join their matching MES text. 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 EBINIT/SKINIT for CCINIT, OBINIT for STINIT, `build/callscript-names.json` for RTINIT/SCINIT, `build/scjump-decisions.json` for SCINIT, matching `<MES>.BIN` when supported, and `build/globals.json``build/data/<OUTNAME>.json`, `build/data/README.md` | | `extract_init.py` | Parse a `*INIT` data source (auto-detects name / numeric / footer / mixed / rules / dispatch / banked 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`; when flat global ranges overlap, a parallel base established by other records wins over a coincidental row-table cell calculation. Dedicated name-mode schemas recover VIINIT's pre-title glossary prerequisites and CIINIT's profile name, backing-unit, optional portrait, and reserved placement columns. 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. Rules mode decodes CCINIT's source-ordered class-change predicates and output accumulators, joining EBINIT unit names, SKINIT skill names, titles, named stat bonuses, cost deltas, and applied-state slots. Dispatch mode decodes SCINIT's paired decision-to-packed-scene and authored-chapter arrays, preserving source-order overwrites, resolving SYS4INI script names, and cross-checking chapter tags against decoded SCJUMP paths. Banked mode decodes RTINIT's twenty parallel 1000-by-20 movement/battle routine banks, retains all assignments and overwrites, assembles final per-slot steps, resolves provider selectors to RTN_M/RTN_B scripts, and applies selector-scoped schemas where proven. Current schemas 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, including progress-only, randomized roaming, object-slot, coordinate, enemy/ally, treasure and Magic Pillar search, Healing Feather, waypoint, faction-terrain, retreat, reachable normal-attack routing, immediate offensive target/action selection, and immediate allied healing. Parameterless behavior schemas publish an empty `parameter_fields` mapping while still joining `provider_behavior`, target selection, action selection, and completion rules. Provider defaults such as M004's unwritten slot 0, M010's zero/HP resource index, and M013's any-foreign-faction filter are projected explicitly and counted separately from populated source cells. Authored cells proven unread by M001/M008 remain under per-step `ignored_movement_parameters` and contribute to top-level `ignored_movement_parameter_count`; they are not silently discarded or assigned invented meanings. Raw provider banks remain intact, and top-level `movement_provider_parameter_schemas` documents the projection. ITINIT, SKINIT, VIINIT, EBINIT, and CIINIT join their matching MES text. 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 EBINIT/SKINIT for CCINIT, OBINIT for STINIT, `build/callscript-names.json` for RTINIT/SCINIT, `build/scjump-decisions.json` for SCINIT, 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/rules/dispatch/banked table: per-scalar/string/array-cell/footer-array population and value distributions, representative records, and direct opcode/script consumers. Name tables report message coverage for title/description, summary/strategy, biography, and description-only layouts; rule tables summarize covered units, titled and level-independent rules, threshold/slot distributions, and skill awards; dispatch tables summarize assignments, overwrites, script-name resolution, and SCJUMP chapter agreement; banked tables summarize populated/reserved banks, movement/battle steps and providers, selector-specific semantic coverage, and overwrite conflicts. `--message-query REGEX` searches every supported message field 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/rules/dispatch/banked table: per-scalar/string/array-cell/footer-array population and value distributions, representative records, and direct opcode/script consumers. Name tables report message coverage for title/description, summary/strategy, biography, and description-only layouts; rule tables summarize covered units, titled and level-independent rules, threshold/slot distributions, and skill awards; dispatch tables summarize assignments, overwrites, script-name resolution, and SCJUMP chapter agreement; banked tables summarize populated/reserved banks, movement/battle steps and providers, selector-specific semantic coverage, and overwrite conflicts. `--message-query REGEX` searches every supported message field 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, mixed selector-dispatched, CCINIT rule, SCINIT dispatch, RTINIT banked, ILINIT condition-matrix, CNINIT unit-name/voice-family, CGINIT gallery, ALINIT alchemy-recipe, AFINIT affinity/progression, and CTINIT name-entry-palette extraction; RECOVER ABI validation; MES reconstruction/joins; footer-array accounting and semantic projection; EBINIT/SKINIT/OBINIT definition joins; STINIT object/enemy joins and tagged-payload behavior; overwrite/provider joins; and field/message/rule/dispatch/banked profiling. | run each directly | — | | `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based, mixed selector-dispatched, CCINIT rule, SCINIT dispatch, RTINIT banked, ILINIT condition-matrix, CNINIT unit-name/voice-family, CGINIT gallery, ALINIT alchemy-recipe, AFINIT affinity/progression, CTINIT name-entry-palette, and CVINIT character-voice extraction; RECOVER ABI validation; MES reconstruction/joins; footer-array accounting and semantic projection; EBINIT/SKINIT/OBINIT definition joins; STINIT object/enemy joins and tagged-payload behavior; overwrite/provider joins; and field/message/rule/dispatch/banked 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}` |
`extract_init.py` also has name-mode specializations for VIINIT's sparse 200-row glossary table, `extract_init.py` also has name-mode specializations for VIINIT's sparse 200-row glossary table,
@@ -92,6 +92,12 @@ copies into the signed affinity matrix, paired item-tuning bonus/cost curves, an
progression rows. CTINIT emits five reserved 70-cell INPUTNAME pages with 273 authored characters and progression rows. CTINIT emits five reserved 70-cell INPUTNAME pages with 273 authored characters and
every empty slot preserved. Both retain raw global/stride/footer provenance beside their semantic views. every empty slot preserved. Both retain raw global/stride/footer provenance beside their semantic views.
Numeric-mode CVINIT classifies all 37 writes into CONFIG's thirteen preview-voice assets, twelve
setting-slot-to-EBINIT-unit joins, and the exact inverse unit-to-suppression-setting map. Preview ids
join to shipped OGG filenames, character slots join to EBINIT names, and the schema records the
per-unit persisted speaker-seen cells that gate CONFIG row availability. Every raw coordinate remains
beside the joined setting view.
## VM ## VM
| Tool | Purpose | Run | Reads → Writes | | Tool | Purpose | Run | Reads → Writes |

View File

@@ -53,15 +53,16 @@
- [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 and rule sources → JSON.** `tools/extract_init.py` auto-detects shape (`name`/`numeric`/`footer`/`mixed`/`rules`/`dispatch`/`banked`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions), CIINIT (24 character-information profiles)** [name: sparse one-based name/description/fields, with CIINIT's dedicated profile registry], **CGINIT (851 sparse gallery images)** [numeric: full image, optional save/stage preview, thumbnail sheet/slot, and variant ordinal in a reserved 2,000-row layout], **ALINIT (107 sparse alchemy recipes)** [numeric: output, level/story gates, point cost, and four paired ingredient slots in a reserved 1,000-row layout], **AFINIT (13 authored affinity rows plus tuning/facility curves)** [name/footer specialization], **CTINIT (five 70-cell name-entry pages)** [string-matrix specialization], **MPINIT (1472 map records)** [footer: length-prefixed arrays], **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays], **CCINIT (71 class-change rules over 33 units)** [rules: unit/level/state predicates plus title/cost/stat/skill effects], **SCINIT (1,209 final decision rows)** [dispatch: scene resource plus authored chapter metadata and overwrite history], and **RTINIT (172 routine sets)** [banked: twenty movement/battle step banks, provider joins, and overwrite history]. Validated; see `build/data/README.md`. Raw addresses remain bytecode provenance; confirmed semantics come from `vm-map/globals.toml`. - [x] **2.2 — `*INIT` data and rule sources → JSON.** `tools/extract_init.py` auto-detects shape (`name`/`numeric`/`footer`/`mixed`/`rules`/`dispatch`/`banked`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions), CIINIT (24 character-information profiles)** [name: sparse one-based name/description/fields, with CIINIT's dedicated profile registry], **CGINIT (851 sparse gallery images)** [numeric: full image, optional save/stage preview, thumbnail sheet/slot, and variant ordinal in a reserved 2,000-row layout], **ALINIT (107 sparse alchemy recipes)** [numeric: output, level/story gates, point cost, and four paired ingredient slots in a reserved 1,000-row layout], **AFINIT (13 authored affinity rows plus tuning/facility curves)** [name/footer specialization], **CTINIT (five 70-cell name-entry pages)** [string-matrix specialization], **CVINIT (13 character-voice settings)** [numeric: preview assets, slot-to-unit joins, and inverse suppression-setting map], **MPINIT (1472 map records)** [footer: length-prefixed arrays], **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays], **CCINIT (71 class-change rules over 33 units)** [rules: unit/level/state predicates plus title/cost/stat/skill effects], **SCINIT (1,209 final decision rows)** [dispatch: scene resource plus authored chapter metadata and overwrite history], and **RTINIT (172 routine sets)** [banked: twenty movement/battle step banks, provider joins, and overwrite history]. 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; 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.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.3a — Extract `CCINIT`'s 71 class-change rules.** Rules mode preserves source-order guards and detects the unit id, minimum level, clear applied-state slot, selected title/level, deployment-cost delta, named fourteen-stat bonuses, awarded SKINIT skills, and state slot set by each rule. EBINIT/SKINIT definition joins and the common global semantic projection coexist with raw addresses. CALCCC/ADDEXP establish the apply/report ABI; EVOLVE explains the two level-independent empty-title Lily rules. The generated profile covers 33 units, 69 titled rules, 30 skill awards, three used promotion slots, and 19 populated output fields. - [x] **2.3a — Extract `CCINIT`'s 71 class-change rules.** Rules mode preserves source-order guards and detects the unit id, minimum level, clear applied-state slot, selected title/level, deployment-cost delta, named fourteen-stat bonuses, awarded SKINIT skills, and state slot set by each rule. EBINIT/SKINIT definition joins and the common global semantic projection coexist with raw addresses. CALCCC/ADDEXP establish the apply/report ABI; EVOLVE explains the two level-independent empty-title Lily rules. The generated profile covers 33 units, 69 titled rules, 30 skill awards, three used promotion slots, and 19 populated output fields.
- [x] **2.3b — Extract `SCINIT`'s scene-dispatch registry.** Dispatch mode recognizes 2,179 alternating writes to two 10,000-cell arrays, preserves all 710 overwritten decision ids, and emits 1,209 final rows. The primary column maps decisions to 135 packed SYS4INI ids, all resolved to numbered SC scripts; the parallel column is authored chapter metadata. All 847 live SCJUMP decisions join to rows and 844 final chapter tags agree with the independently decoded paths; three legacy/stale mismatches remain explicit. - [x] **2.3b — Extract `SCINIT`'s scene-dispatch registry.** Dispatch mode recognizes 2,179 alternating writes to two 10,000-cell arrays, preserves all 710 overwritten decision ids, and emits 1,209 final rows. The primary column maps decisions to 135 packed SYS4INI ids, all resolved to numbered SC scripts; the parallel column is authored chapter metadata. All 847 live SCJUMP decisions join to rows and 844 final chapter tags agree with the independently decoded paths; three legacy/stale mismatches remain explicit.
- [x] **2.3c — Extract `RTINIT`'s routine-step banks.** Banked mode recognizes twenty parallel 1000-by-20 tables and emits 172 sparse routine-set rows with all 3,336 source assignments and 3,307 final cells. Movement banks join all 1,043 steps to all 19 used RTN_M providers (22 dispatchable); battle banks join fourteen steps to all four RTN_B providers. Activation percentages, progress gates, and required/forbidden story flags are consumer-proven; six empty banks remain explicit. RTN_M001/002/003/004/005/006/007/008/009/010/011/012/013/014/015/017/051/052/061 add selector-specific progress, randomized roaming, object-slot, coordinate, enemy/ally, treasure and object search, Healing Feather, waypoint, faction-terrain, retreat, normal-attack routing, offensive target/action selection, and immediate allied-healing semantics. All 977 populated movement-parameter cells are classified: 974 semantic inputs and three M001/M008 cells proven unread; thirteen unwritten defaults are projected separately while the generic raw banks remain intact. - [x] **2.3c — Extract `RTINIT`'s routine-step banks.** Banked mode recognizes twenty parallel 1000-by-20 tables and emits 172 sparse routine-set rows with all 3,336 source assignments and 3,307 final cells. Movement banks join all 1,043 steps to all 19 used RTN_M providers (22 dispatchable); battle banks join fourteen steps to all four RTN_B providers. Activation percentages, progress gates, and required/forbidden story flags are consumer-proven; six empty banks remain explicit. RTN_M001/002/003/004/005/006/007/008/009/010/011/012/013/014/015/017/051/052/061 add selector-specific progress, randomized roaming, object-slot, coordinate, enemy/ally, treasure and object search, Healing Feather, waypoint, faction-terrain, retreat, normal-attack routing, offensive target/action selection, and immediate allied-healing semantics. All 977 populated movement-parameter cells are classified: 974 semantic inputs and three M001/M008 cells proven unread; thirteen unwritten defaults are projected separately while the generic raw banks remain intact.
- [x] **2.3d — Extract `ALINIT`'s alchemy recipes.** Numeric specialization recognizes seven recipe-indexed structures and emits 107 sparse recipes from all 914 static writes. ALCHEMY proves the output, minimum level, required/forbidden story gates, point cost, and four paired ingredient-id/quantity slots. Every output and all 286 ingredient references join to ITINIT; raw coordinates remain beside the semantic and nested ingredient views. - [x] **2.3d — Extract `ALINIT`'s alchemy recipes.** Numeric specialization recognizes seven recipe-indexed structures and emits 107 sparse recipes from all 914 static writes. ALCHEMY proves the output, minimum level, required/forbidden story gates, point cost, and four paired ingredient-id/quantity slots. Every output and all 286 ingredient references join to ITINIT; raw coordinates remain beside the semantic and nested ingredient views.
- [x] **2.3e — Extract `AFINIT` affinity/progression data and `CTINIT` name-entry characters.** AFINIT classifies 27 element strings and 54 footer arrays into thirteen signed effectiveness rows, paired tuning bonus/cost curves, and three facility-progress rows. CTINIT preserves 273 authored characters and 77 empty cells across INPUTNAME's five reserved 70-cell pages. Both schemas account for every instruction and retain raw coordinates. - [x] **2.3e — Extract `AFINIT` affinity/progression data and `CTINIT` name-entry characters.** AFINIT classifies 27 element strings and 54 footer arrays into thirteen signed effectiveness rows, paired tuning bonus/cost curves, and three facility-progress rows. CTINIT preserves 273 authored characters and 77 empty cells across INPUTNAME's five reserved 70-cell pages. Both schemas account for every instruction and retain raw coordinates.
- [x] **2.3f — Extract `CVINIT` character-voice settings.** Numeric specialization accounts for all 37 static writes as thirteen preview OGG assets, twelve CONFIG-setting-slot-to-EBINIT-unit joins, and the exact inverse unit-to-suppression-setting map. CONFIG proves preview, row-label, persisted speaker-seen unlock, and setting-edit behavior; the shared CNINIT/CVINIT voice chain proves the inverse map.
- [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: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, and CTINIT name palette are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, audit CVINIT's small thirteen-row output, then use Frida only for semantics static consumers cannot settle. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved. - [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, CTINIT name palette, and CVINIT character-voice registry are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, audit MPINIT's structurally regular footer rows by their direct readers, then use Frida only for semantics static consumers cannot settle. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved.
## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)* ## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)*

View File

@@ -22,6 +22,9 @@
ILINIT is a special name-mode matrix: 30 reserved condition ids by five authored ILINIT is a special name-mode matrix: 30 reserved condition ids by five authored
levels, joined to the runtime condition-state ABI and RECOVER policy. levels, joined to the runtime condition-state ABI and RECOVER policy.
CVINIT is a special numeric-mode registry: thirteen voice-configuration preview
slots, twelve slot-to-unit joins, and the matching unit-to-setting inverse map.
Records are {id, name?, desc?, fields:{"0x<col_base>": value}} or, for footer tables, Records are {id, name?, desc?, fields:{"0x<col_base>": value}} or, for footer tables,
{id, global_addr, footer_off, values:[...]}. Column addresses are raw engine globals; {id, global_addr, footer_off, values:[...]}. Column addresses are raw engine globals;
confirmed names come from the generated engine global registry while raw keys remain provenance. confirmed names come from the generated engine global registry while raw keys remain provenance.
@@ -626,6 +629,13 @@ NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES = (
"symbols", "symbols",
) )
VOICE_CONFIG_PREVIEW_ASSET_ARRAY_BASE = 0x62CAD
VOICE_CONFIG_SLOT_UNIT_ARRAY_BASE = 0x62C8F
VOICE_CONFIG_UNIT_SETTING_ARRAY_BASE = 0x628A7
VOICE_CONFIG_SLOT_COUNT = 13
VOICE_CONFIG_NAMED_SLOT_COUNT = 12
VOICE_CONFIG_SPEAKER_SEEN_ARRAY_BASE = 0x56223
RECOVER_CURRENT_ENTITY = 0x152616 RECOVER_CURRENT_ENTITY = 0x152616
RECOVER_EFFECTIVE_STATS = 0x4E11B RECOVER_EFFECTIVE_STATS = 0x4E11B
RECOVER_CURRENT_RESOURCES = 0x4E085 RECOVER_CURRENT_RESOURCES = 0x4E085
@@ -2702,6 +2712,191 @@ def extract_name_entry_palette(scr):
} }
def extract_voice_configuration(scr):
"""Extract CVINIT's preview-voice and character-setting registry."""
preview_by_slot: dict[int, int] = {}
unit_by_slot: dict[int, int] = {}
setting_by_unit: dict[int, int] = {}
static_write_count = 0
classified_write_count = 0
exit_count = 0
for ins in scr.instructions:
write = _static_global_write(ins)
if write is None:
if sys4load.display_label(ins.opcode) == "exit":
exit_count += 1
continue
raise ValueError(
f"{scr.path.name}: unclassified instruction at 0x{ins.offset:x}"
)
static_write_count += 1
destination, value = write
if not isinstance(value, int):
raise ValueError(
f"{scr.path.name}: non-static voice-config value "
f"at 0x{ins.offset:x}"
)
preview_slot = destination - VOICE_CONFIG_PREVIEW_ASSET_ARRAY_BASE
if 0 <= preview_slot < VOICE_CONFIG_SLOT_COUNT:
_store_unique(preview_by_slot, preview_slot, value, preview_slot)
classified_write_count += 1
continue
unit_slot = destination - VOICE_CONFIG_SLOT_UNIT_ARRAY_BASE
if 1 <= unit_slot <= VOICE_CONFIG_NAMED_SLOT_COUNT:
_store_unique(unit_by_slot, unit_slot, value, unit_slot)
classified_write_count += 1
continue
unit_id = destination - VOICE_CONFIG_UNIT_SETTING_ARRAY_BASE
if (
0 <= unit_id < CHARACTER_NAME_RECORD_SPAN
and 1 <= value <= VOICE_CONFIG_NAMED_SLOT_COUNT
):
_store_unique(setting_by_unit, unit_id, value, unit_id)
classified_write_count += 1
continue
raise ValueError(
f"{scr.path.name}: unclassified voice-config write "
f"0x{destination:x} at 0x{ins.offset:x}"
)
expected_preview_slots = set(range(VOICE_CONFIG_SLOT_COUNT))
expected_named_slots = set(range(1, VOICE_CONFIG_NAMED_SLOT_COUNT + 1))
if set(preview_by_slot) != expected_preview_slots:
raise ValueError(
f"{scr.path.name}: preview slots are "
f"{sorted(preview_by_slot)}, expected 0..{VOICE_CONFIG_SLOT_COUNT - 1}"
)
if set(unit_by_slot) != expected_named_slots:
raise ValueError(
f"{scr.path.name}: named slots are {sorted(unit_by_slot)}, "
f"expected 1..{VOICE_CONFIG_NAMED_SLOT_COUNT}"
)
if set(setting_by_unit.values()) != expected_named_slots:
raise ValueError(
f"{scr.path.name}: inverse setting ids are "
f"{sorted(setting_by_unit.values())}, expected "
f"1..{VOICE_CONFIG_NAMED_SLOT_COUNT}"
)
if len(setting_by_unit) != VOICE_CONFIG_NAMED_SLOT_COUNT:
raise ValueError(
f"{scr.path.name}: expected {VOICE_CONFIG_NAMED_SLOT_COUNT} "
f"unit-to-setting writes, found {len(setting_by_unit)}"
)
for slot, unit_id in unit_by_slot.items():
if setting_by_unit.get(unit_id) != slot:
raise ValueError(
f"{scr.path.name}: slot {slot} -> unit {unit_id} does not "
f"round-trip through the inverse map"
)
if exit_count != 1:
raise ValueError(
f"{scr.path.name}: expected one exit, found {exit_count}"
)
unit_records, _ = extract_name(sys4load.load(resolve("EBINIT")))
unit_names = {record["id"]: record["name"] for record in unit_records}
missing_unit_ids = sorted(set(unit_by_slot.values()) - set(unit_names))
if missing_unit_ids:
raise ValueError(
f"{scr.path.name}: unknown EBINIT unit ids {missing_unit_ids}"
)
asset_names = callscript_names()
preview_key = f"0x{VOICE_CONFIG_PREVIEW_ASSET_ARRAY_BASE:x}"
unit_key = f"0x{VOICE_CONFIG_SLOT_UNIT_ARRAY_BASE:x}"
inverse_base = f"0x{VOICE_CONFIG_UNIT_SETTING_ARRAY_BASE:x}"
records = []
for slot in range(VOICE_CONFIG_SLOT_COUNT):
preview_asset_id = preview_by_slot[slot]
record = {
"id": slot,
"name": (
"system_voice" if slot == 0 else unit_names[unit_by_slot[slot]]
),
"slot_kind": "system" if slot == 0 else "character",
"preview_voice_asset_id": preview_asset_id,
"preview_voice_asset_name": asset_names.get(preview_asset_id, ""),
"fields": {preview_key: preview_asset_id},
"array_fields": {},
}
if slot:
unit_id = unit_by_slot[slot]
inverse_key = f"{inverse_base}/{unit_id}"
record["fields"][unit_key] = unit_id
record["array_fields"][inverse_key] = slot
record.update({
"unit_id": unit_id,
"unit_name": unit_names[unit_id],
"voice_suppression_setting_id": slot,
"speaker_seen_flag_address": (
f"0x{VOICE_CONFIG_SPEAKER_SEEN_ARRAY_BASE + unit_id:x}"
),
})
records.append(record)
return records, {
"schema": "character-voice-configuration",
"slot_count": VOICE_CONFIG_SLOT_COUNT,
"system_slot": 0,
"named_character_slots": list(
range(1, VOICE_CONFIG_NAMED_SLOT_COUNT + 1)
),
"preview_asset_array_base": (
f"0x{VOICE_CONFIG_PREVIEW_ASSET_ARRAY_BASE:x}"
),
"slot_unit_array_base": f"0x{VOICE_CONFIG_SLOT_UNIT_ARRAY_BASE:x}",
"unit_setting_array_base": (
f"0x{VOICE_CONFIG_UNIT_SETTING_ARRAY_BASE:x}"
),
"speaker_seen_array_base": (
f"0x{VOICE_CONFIG_SPEAKER_SEEN_ARRAY_BASE:x}"
),
"array_layouts": {
inverse_base: {"length": CHARACTER_NAME_RECORD_SPAN},
},
"array_field_columns": [
f"{inverse_base}/{unit_id}" for unit_id in sorted(setting_by_unit)
],
"static_write_count": static_write_count,
"classified_static_write_count": classified_write_count,
"exit_count": exit_count,
"classified_instruction_count": classified_write_count + exit_count,
"preview_asset_join_count": sum(
bool(record["preview_voice_asset_name"]) for record in records
),
"unit_join_count": len(unit_by_slot),
"round_trip_mapping_count": sum(
setting_by_unit[unit_id] == slot
for slot, unit_id in unit_by_slot.items()
),
"consumer_contract": {
"script": "CONFIG.BIN",
"preview": (
"CONFIG indexes the thirteen preview assets by voice-setting "
"slot and plays the selected clip before changing that slot's "
"suppression flag."
),
"character_rows": (
"CONFIG lists slots 1..12 by resolving their unit ids through "
"the shared unit display-name table. A persisted per-unit "
"speaker-seen flag controls whether each row is available."
),
"runtime_voice_filter": (
"Story, history, field, and battle paths normalize a unit to "
"its voice family, map that representative unit through the "
"CVINIT inverse table, and test the selected one of thirteen "
"character_voice_suppressed settings."
),
},
}
@cache @cache
def gallery_thumbnail_sheet_assets() -> dict[int, int]: def gallery_thumbnail_sheet_assets() -> dict[int, int]:
"""Read CGMODE's enabled thumbnail-sheet assets from INIT2.""" """Read CGMODE's enabled thumbnail-sheet assets from INIT2."""
@@ -3443,6 +3638,10 @@ def write_data_index(data_dir: Path) -> None:
"pages (hiragana, katakana, Latin, numerals, and symbols), preserving all reserved", "pages (hiragana, katakana, Latin, numerals, and symbols), preserving all reserved",
"empty slots beside the 273 authored characters.", "empty slots beside the 273 authored characters.",
"", "",
"CVINIT's dedicated character-voice schema exposes CONFIG's thirteen preview",
"clips, twelve slot-to-unit joins, and the matching unit-to-suppression-setting",
"inverse map used by story, history, field, and battle voice filters.",
"",
"Mixed-mode tables preserve the sparse selector id, branch offset, condition strings,", "Mixed-mode tables preserve the sparse selector id, branch offset, condition strings,",
"scalar fields, cells within preallocated buffers, and length-prefixed footer arrays.", "scalar fields, cells within preallocated buffers, and length-prefixed footer arrays.",
"STINIT additionally joins confirmed parallel buffers into per-slot `object_placements`", "STINIT additionally joins confirmed parallel buffers into per-slot `object_placements`",
@@ -3526,6 +3725,8 @@ def main() -> int:
extractor = extract_gallery_definitions extractor = extract_gallery_definitions
elif mode == "numeric" and name == "ALINIT": elif mode == "numeric" and name == "ALINIT":
extractor = extract_alchemy_recipes extractor = extract_alchemy_recipes
elif mode == "numeric" and name == "CVINIT":
extractor = extract_voice_configuration
recs, meta = extractor(scr) recs, meta = extractor(scr)
if mode == "name" and name in MESSAGE_TABLES: if mode == "name" and name in MESSAGE_TABLES:
message_name = MESSAGE_TABLES[name] message_name = MESSAGE_TABLES[name]

View File

@@ -1272,6 +1272,93 @@ def test_name_entry_palette() -> None:
) )
def test_voice_configuration() -> None:
scripts = paths.scripts()
script = sys4load.load(scripts["CVINIT.BIN"])
check(
extract_init.detect_mode(script) == "numeric",
"CVINIT remains compatible with numeric-mode auto-detection",
)
records, meta = extract_init.extract_voice_configuration(script)
by_id = {record["id"]: record for record in records}
check(
len(records) == 13
and meta["static_write_count"] == 37
and meta["classified_static_write_count"] == 37
and meta["classified_instruction_count"] == 38,
"CVINIT classifies every preview, forward-map, inverse-map, and exit instruction",
)
check(
[
record["preview_voice_asset_name"]
for record in records
] == [
"EUA0065.OGG",
"LILA1424.OGG",
"SYL0675.OGG",
"SAS0328.OGG",
"VID0345.OGG",
"EST0562.OGG",
"NEL0192.OGG",
"TIO0209.OGG",
"COL0836.OGG",
"BRI0687.OGG",
"OKT0357.OGG",
"FEM0328.OGG",
"DEI0501.OGG",
],
"CVINIT joins all thirteen preview clips to shipped OGG assets",
)
check(
[by_id[slot]["unit_id"] for slot in range(1, 13)]
== [2, 5, 6, 7, 12, 14, 13, 10, 8, 9, 15, 11]
and meta["unit_join_count"] == 12,
"CVINIT joins its twelve named configuration slots to EBINIT units",
)
check(
all(
by_id[slot]["array_fields"][
f"0x628a7/{by_id[slot]['unit_id']}"
] == slot
for slot in range(1, 13)
)
and meta["round_trip_mapping_count"] == 12,
"CVINIT's slot-to-unit and unit-to-setting maps round-trip exactly",
)
check(
by_id[0]["slot_kind"] == "system"
and "unit_id" not in by_id[0]
and by_id[1]["speaker_seen_flag_address"] == "0x56225"
and by_id[12]["speaker_seen_flag_address"] == "0x5622e",
"CVINIT preserves the non-unit system slot and speaker-seen joins",
)
semantics = extract_init.field_semantics(
records, meta["array_layouts"]
)
check(
len(semantics) == 14
and semantics["0x62cad"] == "character_voice_preview_asset_ids"
and semantics["0x62c8f"] == "character_voice_setting_unit_ids"
and semantics["0x628a7/11"]
== "unit_voice_suppression_flag_ids.index_11",
"CVINIT raw arrays join to their canonical voice-setting semantics",
)
extract_init.attach_semantic_fields(records, semantics)
check(
by_id[12]["semantic_fields"][
"character_voice_preview_asset_ids"
] == 11412
and by_id[12]["semantic_fields"][
"character_voice_setting_unit_ids"
] == 11
and by_id[12]["semantic_fields"][
"unit_voice_suppression_flag_ids.index_11"
] == 12,
"CVINIT retains raw coordinates beside its joined semantic view",
)
def test_condition_definitions() -> None: def test_condition_definitions() -> None:
scripts = paths.scripts() scripts = paths.scripts()
script = sys4load.load(scripts["ILINIT.BIN"]) script = sys4load.load(scripts["ILINIT.BIN"])
@@ -1485,6 +1572,7 @@ if __name__ == "__main__":
test_alchemy_recipes() test_alchemy_recipes()
test_affinity_definitions() test_affinity_definitions()
test_name_entry_palette() test_name_entry_palette()
test_voice_configuration()
test_condition_definitions() test_condition_definitions()
test_field_semantics() test_field_semantics()
if FAILS: if FAILS:

View File

@@ -1403,11 +1403,46 @@ address = "0x628a7"
name = "unit_voice_suppression_flag_ids" name = "unit_voice_suppression_flag_ids"
category = "data-table" category = "data-table"
type = "int[1000]" type = "int[1000]"
value_domain = "index into character_voice_suppressed" value_domain = "0 for no mapped setting; 1..12 index character_voice_suppressed"
usage = "Second stage of the shared voice-eligibility chain. Story, history, field, and battle paths normalize the current unit through unit_voice_family_unit_ids, index this table by that representative unit id, and suppress voice playback when the selected character_voice_suppressed cell is nonzero." usage = "CVINIT's unit-to-setting inverse map for the twelve named character voice controls. Story, history, field, and battle paths normalize the current unit through unit_voice_family_unit_ids, index this table by that representative unit id, and suppress voice playback when the selected character_voice_suppressed cell is nonzero."
source = "investigation" source = "investigation"
confidence = "high" confidence = "high"
depends_on = ["0x2e49"] depends_on = ["0x2e49", "0x62c8f"]
[[global]]
address = "0x56223"
name = "unit_story_speaker_seen_flags"
category = "data-table"
type = "int[1000]"
value_domain = "per-EBINIT-unit {0,1}; 1 after that unit has appeared as a story speaker"
usage = "Scene scripts set and persist a unit's cell when drawing that speaker's name. CONFIG reloads the twelve CVINIT-mapped unit cells and enables each named character-voice control only after its speaker has been encountered."
source = "investigation"
confidence = "high"
depends_on = ["0x315", "0x62c8f"]
[[global]]
address = "0x62c8f"
name = "character_voice_setting_unit_ids"
category = "data-table"
type = "int[13]"
columns = { "0" = "system_or_reserved", "1" = "lily", "2" = "sylphine", "3" = "sasune", "4" = "vidal", "5" = "estelle", "6" = "nelly", "7" = "tiofania", "8" = "colette", "9" = "bridget", "10" = "octavia", "11" = "fam", "12" = "deirdre" }
value_domain = "slot 0 unwritten; slots 1..12 contain EBINIT unit ids"
usage = "CVINIT's CONFIG-row join. CONFIG iterates setting slots 1..12, resolves each unit id through unit_story_display_names for its label, and tests the same unit's unit_story_speaker_seen_flags cell before exposing the row. CVINIT also writes the exact inverse mapping to unit_voice_suppression_flag_ids."
source = "investigation"
confidence = "high"
depends_on = ["0x315", "0x56223", "0x628a7"]
[[global]]
address = "0x62cad"
name = "character_voice_preview_asset_ids"
category = "data-table"
type = "int[13]"
columns = { "0" = "system_voice", "1" = "lily", "2" = "sylphine", "3" = "sasune", "4" = "vidal", "5" = "estelle", "6" = "nelly", "7" = "tiofania", "8" = "colette", "9" = "bridget", "10" = "octavia", "11" = "fam", "12" = "deirdre" }
value_domain = "packed OGG asset resource id"
usage = "CVINIT's thirteen voice-setting preview clips. CONFIG indexes this array by setting slot and plays the selected clip; slot 0 is the non-unit system voice, while slots 1..12 join to character_voice_setting_unit_ids."
source = "investigation"
confidence = "high"
depends_on = ["0x2e49", "0x62c8f"]
[[global]] [[global]]
address = "0x62cd1" address = "0x62cd1"