Decode AFINIT and CTINIT data tables

This commit is contained in:
gamer147
2026-07-23 19:42:32 -04:00
parent 825b1b08fd
commit 7bec889eed
9 changed files with 771 additions and 29 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)
5048 globals (307 curated, 4741 auto shape-inferred). Source of truth: `vm-map/globals.toml`. 5049 globals (316 curated, 4733 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
## choice-output ## choice-output
@@ -20,8 +20,12 @@
| address | name | conf | source | usage | | address | name | conf | source | usage |
|---|---|---|---|---| |---|---|---|---|---|
| `0x671b` | shared_spendable_points | high | investigation | Shared spendable point balance used by the alchemy, study, evolution, and summoning systems. ALCHEMY requires and deducts each recipe's alchemy_recipe_point_costs value; STAGECLEAR awards points to this balance and caps it at 999. The exact player-facing Japanese resource label remains unresolved. | | `0x671b` | shared_spendable_points | high | investigation | Shared spendable point balance used by the alchemy, study, evolution, and summoning systems. ALCHEMY requires and deducts each recipe's alchemy_recipe_point_costs value; STAGECLEAR awards points to this balance and caps it at 999. The exact player-facing Japanese resource label remains unresolved. |
| `0x671c` | item_tuning_facility_level | high | investigation | Current equipment-tuning facility level. IMPROVE indexes facility_level_progress_thresholds row 0 with this value and raises it, up to 6, as item_tuning_facility_progress accumulates. |
| `0x671d` | alchemy_level | high | investigation | Current alchemy level. ALCHEMY hides recipes whose alchemy_recipe_minimum_levels value exceeds this level and raises it, up to 6, when alchemy_level_progress reaches the current threshold. | | `0x671d` | alchemy_level | high | investigation | Current alchemy level. ALCHEMY hides recipes whose alchemy_recipe_minimum_levels value exceeds this level and raises it, up to 6, when alchemy_level_progress reaches the current threshold. |
| `0x671e` | magic_facility_level | high | investigation | Current magic/research facility level. MAGIC and USEMAGIC index facility_level_progress_thresholds row 2 with this level; USEMAGIC raises it up to 6 when magic_facility_progress reaches the current threshold. |
| `0x671f` | item_tuning_facility_progress | high | investigation | Progress toward the next equipment-tuning facility level. IMPROVE adds the selected item's tuning-level increases, applies row 0 of facility_level_progress_thresholds, and carries or clamps progress when the level rises. |
| `0x6720` | alchemy_level_progress | high | investigation | Progress toward the next alchemy level. Each successful ALCHEMY synthesis increments it; reaching the current threshold raises alchemy_level and resets this counter. | | `0x6720` | alchemy_level_progress | high | investigation | Progress toward the next alchemy level. Each successful ALCHEMY synthesis increments it; reaching the current threshold raises alchemy_level and resets this counter. |
| `0x6721` | magic_facility_progress | high | investigation | Progress toward the next magic/research facility level. USEMAGIC adds the selected action's progress award and applies row 2 of facility_level_progress_thresholds; MAGIC renders the same current/threshold pair. |
| `0x2052e` | scjump_progress_b | med | inference | Second SCJUMP switch input (1223 comparison reads) — progression counter/position. INFERENCE from SCJUMP usage. | | `0x2052e` | scjump_progress_b | med | inference | Second SCJUMP switch input (1223 comparison reads) — progression counter/position. INFERENCE from SCJUMP usage. |
| `0x4dfbc` | scjump_progress_a | med | inference | Dominant SCJUMP switch input (1609 comparison reads) — a per-chapter story-progress counter/position the progression machine branches on. INFERENCE from SCJUMP usage; confirm exact meaning via a listing/playthrough. | | `0x4dfbc` | scjump_progress_a | med | inference | Dominant SCJUMP switch input (1609 comparison reads) — a per-chapter story-progress counter/position the progression machine branches on. INFERENCE from SCJUMP usage; confirm exact meaning via a listing/playthrough. |
| `0x665d6` | modal_message_line_count | high | investigation | Number of populated strings in modal_message_lines. Dozens of menu/gameplay producers append at lines[count] and increment this value; MES renders the resulting non-selecting modal and clears the count, while SBUNKI consumes the same buffer as selectable options. | | `0x665d6` | modal_message_line_count | high | investigation | Number of populated strings in modal_message_lines. Dozens of menu/gameplay producers append at lines[count] and increment this value; MES renders the resulting non-selecting modal and clears the count, while SBUNKI consumes the same buffer as selectable options. |
@@ -161,7 +165,10 @@
| `0xaacf0` | condition_duration_turns_by_level | high | investigation | ILINIT's five-column duration row for each condition. ADDILLSUB indexes it by the resulting condition level minus one and stores the value in entity_condition_remaining_turns. Columns: 0=level_1_turns, 1=level_2_turns, 2=level_3_turns, 3=level_4_turns, 4=level_5_turns. | | `0xaacf0` | condition_duration_turns_by_level | high | investigation | ILINIT's five-column duration row for each condition. ADDILLSUB indexes it by the resulting condition level minus one and stores the value in entity_condition_remaining_turns. Columns: 0=level_1_turns, 1=level_2_turns, 2=level_3_turns, 3=level_4_turns, 4=level_5_turns. |
| `0xaad86` | condition_stat_deltas | high | investigation | ILINIT's five-level by eleven-stat matrix for each condition. CALCREVISE indexes (condition level - 1) * 11 + stat column and adds the result to entity_effective_stats columns 0..10. Shipped rows populate curse, confusion, paralysis, and exaltation. Columns: 0=level_1_accuracy, 1=level_1_evasion, 2=level_1_physical_attack, 3=level_1_physical_defense, 4=level_1_magic_attack, 5=level_1_magic_defense, 6=level_1_speed, 7=level_1_luck, 8=level_1_critical_chance, 9=level_1_capture_power, 10=level_1_movement, 11=level_2_accuracy, 12=level_2_evasion, 13=level_2_physical_attack, 14=level_2_physical_defense, 15=level_2_magic_attack, 16=level_2_magic_defense, 17=level_2_speed, 18=level_2_luck, 19=level_2_critical_chance, 20=level_2_capture_power, 21=level_2_movement, 22=level_3_accuracy, 23=level_3_evasion, 24=level_3_physical_attack, 25=level_3_physical_defense, 26=level_3_magic_attack, 27=level_3_magic_defense, 28=level_3_speed, 29=level_3_luck, 30=level_3_critical_chance, 31=level_3_capture_power, 32=level_3_movement, 33=level_4_accuracy, 34=level_4_evasion, 35=level_4_physical_attack, 36=level_4_physical_defense, 37=level_4_magic_attack, 38=level_4_magic_defense, 39=level_4_speed, 40=level_4_luck, 41=level_4_critical_chance, 42=level_4_capture_power, 43=level_4_movement, 44=level_5_accuracy, 45=level_5_evasion, 46=level_5_physical_attack, 47=level_5_physical_defense, 48=level_5_magic_attack, 49=level_5_magic_defense, 50=level_5_speed, 51=level_5_luck, 52=level_5_critical_chance, 53=level_5_capture_power, 54=level_5_movement. | | `0xaad86` | condition_stat_deltas | high | investigation | ILINIT's five-level by eleven-stat matrix for each condition. CALCREVISE indexes (condition level - 1) * 11 + stat column and adds the result to entity_effective_stats columns 0..10. Shipped rows populate curse, confusion, paralysis, and exaltation. Columns: 0=level_1_accuracy, 1=level_1_evasion, 2=level_1_physical_attack, 3=level_1_physical_defense, 4=level_1_magic_attack, 5=level_1_magic_defense, 6=level_1_speed, 7=level_1_luck, 8=level_1_critical_chance, 9=level_1_capture_power, 10=level_1_movement, 11=level_2_accuracy, 12=level_2_evasion, 13=level_2_physical_attack, 14=level_2_physical_defense, 15=level_2_magic_attack, 16=level_2_magic_defense, 17=level_2_speed, 18=level_2_luck, 19=level_2_critical_chance, 20=level_2_capture_power, 21=level_2_movement, 22=level_3_accuracy, 23=level_3_evasion, 24=level_3_physical_attack, 25=level_3_physical_defense, 26=level_3_magic_attack, 27=level_3_magic_defense, 28=level_3_speed, 29=level_3_luck, 30=level_3_critical_chance, 31=level_3_capture_power, 32=level_3_movement, 33=level_4_accuracy, 34=level_4_evasion, 35=level_4_physical_attack, 36=level_4_physical_defense, 37=level_4_magic_attack, 38=level_4_magic_defense, 39=level_4_speed, 40=level_4_luck, 41=level_4_critical_chance, 42=level_4_capture_power, 43=level_4_movement, 44=level_5_accuracy, 45=level_5_evasion, 46=level_5_physical_attack, 47=level_5_physical_defense, 48=level_5_magic_attack, 49=level_5_magic_defense, 50=level_5_speed, 51=level_5_luck, 52=level_5_critical_chance, 53=level_5_capture_power, 54=level_5_movement. |
| `0xab3f8` | condition_resource_deltas | high | investigation | ILINIT's five-level by three-resource matrix for each condition. FIELD applies the active row each turn; CALCDMG also consumes the HP/SP/FS drain condition rows during battle. Positive values restore/drain to the acting side as defined by the caller, while negative values are periodic damage or loss. Columns: 0=level_1_hp, 1=level_1_sp, 2=level_1_fs, 3=level_2_hp, 4=level_2_sp, 5=level_2_fs, 6=level_3_hp, 7=level_3_sp, 8=level_3_fs, 9=level_4_hp, 10=level_4_sp, 11=level_4_fs, 12=level_5_hp, 13=level_5_sp, 14=level_5_fs. | | `0xab3f8` | condition_resource_deltas | high | investigation | ILINIT's five-level by three-resource matrix for each condition. FIELD applies the active row each turn; CALCDMG also consumes the HP/SP/FS drain condition rows during battle. Positive values restore/drain to the acting side as defined by the caller, while negative values are periodic damage or loss. Columns: 0=level_1_hp, 1=level_1_sp, 2=level_1_fs, 3=level_2_hp, 4=level_2_sp, 5=level_2_fs, 6=level_3_hp, 7=level_3_sp, 8=level_3_fs, 9=level_4_hp, 10=level_4_sp, 11=level_4_fs, 12=level_5_hp, 13=level_5_sp, 14=level_5_fs. |
| `0xab5ba` | attack_element_effectiveness_percent | high | investigation | AFINIT-authored element matchup table. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases. | | `0xab5ba` | attack_element_effectiveness_percent | high | investigation | AFINIT-authored element matchup table with thirteen authored defense rows and eighteen authored cells per row inside the reserved 20-by-20 layout. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases. INFOAF displays eight attack columns for its selected defense rows. |
| `0xab6fa` | item_tuning_stat_bonus_curves | high | investigation | Eighteen usable equipment-growth curves selected by item_tuning_curve_ids, plus an explicitly zeroed reserved row 19. TUNE and IMPROVE use the nonzero prefix as the available tuning-level range; CALCREVISE and DRAWTIP add the selected zero-based tuning level's bonus to the corresponding stat. Columns: 0=tuning_level_1, 1=tuning_level_2, 2=tuning_level_3, 3=tuning_level_4, 4=tuning_level_5, 5=tuning_level_6, 6=tuning_level_7, 7=tuning_level_8, 8=tuning_level_9, 9=tuning_level_10, 10=reserved. |
| `0xab7d6` | item_tuning_point_cost_curves | high | investigation | Point-cost curves paired by curve id and tuning level with item_tuning_stat_bonus_curves. IMPROVE sums costs for newly selected levels, subtracts refunds for removed levels, checks the resulting total against the tuning-point balance, and deducts it on confirmation. Columns: 0=tuning_level_1, 1=tuning_level_2, 2=tuning_level_3, 3=tuning_level_4, 4=tuning_level_5, 5=tuning_level_6, 6=tuning_level_7, 7=tuning_level_8, 8=tuning_level_9, 9=tuning_level_10, 10=reserved. |
| `0xab8b2` | facility_level_progress_thresholds | high | investigation | AFINIT's three facility-progression rows. IMPROVE uses row 0 for equipment tuning, ALCHEMY uses row 1, and MAGIC/USEMAGIC use row 2; each indexes the row by the current level and caps advancement at level 6. Columns: 0=level_0_to_1, 1=level_1_to_2, 2=level_2_to_3, 3=level_3_to_4, 4=level_4_to_5, 5=level_5_to_6, 6=level_6_cap. |
| `0xab8c7` | class_change_rule_script_ids | high | investigation | CALCCC iterates these 32 cells and call-scripts every positive entry to evaluate class-change providers. CCINIT is the shipped rule program decoded into build/data/CCINIT.json. | | `0xab8c7` | class_change_rule_script_ids | high | investigation | CALCCC iterates these 32 cells and call-scripts every positive entry to evaluate class-change providers. CCINIT is the shipped rule program decoded into build/data/CCINIT.json. |
| `0xaba64` | stage_object_runtime_flags | high | investigation | Per-current-stage object flags. RTN_M010 and RTN_M015 require bit 1 before considering Healing Feathers or Magic Pillars as movement targets; FIELD and object rendering maintain the broader type-dependent bitfield. | | `0xaba64` | stage_object_runtime_flags | high | investigation | Per-current-stage object flags. RTN_M010 and RTN_M015 require bit 1 before considering Healing Feathers or Magic Pillars as movement targets; FIELD and object rendering maintain the broader type-dependent bitfield. |
| `0xaba96` | pathfinding_remaining_route_steps | high | investigation | MVSEEK's row-major reachability grid. Mode 0 searches from the current entity with movement+1 at the origin; modes 1/2 search from the caller-supplied coordinate with 9999 at the origin. Each traversed edge decrements the value, so a larger positive value is nearer to the origin. RTN_M006/007/015 use origin-minus-target values as route-step radii; RTN_M010 ranks Healing Feathers by the negated value. | | `0xaba96` | pathfinding_remaining_route_steps | high | investigation | MVSEEK's row-major reachability grid. Mode 0 searches from the current entity with movement+1 at the origin; modes 1/2 search from the caller-supplied coordinate with 9999 at the origin. Each traversed edge decrements the value, so a larger positive value is nearer to the origin. RTN_M006/007/015 use origin-minus-target values as route-step radii; RTN_M010 ranks Healing Feathers by the negated value. |
@@ -244,8 +251,6 @@
| `0x69e` | — | low | auto-shape | array | | `0x69e` | — | low | auto-shape | array |
| `0x6fe` | — | low | auto-shape | array | | `0x6fe` | — | low | auto-shape | array |
| `0x712` | — | low | auto-shape | array | | `0x712` | — | low | auto-shape | array |
| `0x2690` | — | low | auto-shape | array |
| `0x26a4` | — | low | auto-shape | array |
| `0x26b5` | — | low | auto-shape | array | | `0x26b5` | — | low | auto-shape | array |
| `0x26d3` | — | low | auto-shape | array | | `0x26d3` | — | low | auto-shape | array |
| `0x27bd` | — | low | auto-shape | array | | `0x27bd` | — | low | auto-shape | array |
@@ -260,8 +265,6 @@
| `0x3ad6` | — | low | auto-shape | array | | `0x3ad6` | — | low | auto-shape | array |
| `0x4315` | — | low | auto-shape | array | | `0x4315` | — | low | auto-shape | array |
| `0x4379` | — | low | auto-shape | array | | `0x4379` | — | low | auto-shape | array |
| `0x671c` | — | low | auto-shape | array |
| `0x671f` | — | low | auto-shape | array |
| `0x6727` | — | low | auto-shape | array | | `0x6727` | — | low | auto-shape | array |
| `0x6804` | — | low | auto-shape | array | | `0x6804` | — | low | auto-shape | array |
| `0x6868` | — | low | auto-shape | array | | `0x6868` | — | low | auto-shape | array |
@@ -438,7 +441,6 @@
| `0x3231` | game_mode | med | inference | Game-mode/phase selector in the 0x3231-0x3234 progression-state cluster (chapter_mode is 0x3234). Enum 1..9, written by the gameplay scripts (AIM/ALCHEMY/BTL/BUNKI), branch-read in 136 scenes. Distinct from chapter; likely current sub-mode/screen. INFERENCE — confirm with a listing/sweep before relying on the exact meaning. | | `0x3231` | game_mode | med | inference | Game-mode/phase selector in the 0x3231-0x3234 progression-state cluster (chapter_mode is 0x3234). Enum 1..9, written by the gameplay scripts (AIM/ALCHEMY/BTL/BUNKI), branch-read in 136 scenes. Distinct from chapter; likely current sub-mode/screen. INFERENCE — confirm with a listing/sweep before relying on the exact meaning. |
| `0x3234` | chapter_mode | high | investigation | Progression chapter/mode selector. SCJUMP's top-level switch keys on it; branch-read by progression scripts (FIELD etc.), not directly by SC/SP scenes. | | `0x3234` | chapter_mode | high | investigation | Progression chapter/mode selector. SCJUMP's top-level switch keys on it; branch-read by progression scripts (FIELD etc.), not directly by SC/SP scenes. |
| `0x3275` | — | med | auto-shape | TODO: confirm. Branch-read in 136 scenes / 143 scripts; compared against [0]; writers=['CAMP.BIN', 'DEBUGADV.BIN', 'DEBUGADV2.BIN', 'FIELD.BIN']. | | `0x3275` | — | med | auto-shape | TODO: confirm. Branch-read in 136 scenes / 143 scripts; compared against [0]; writers=['CAMP.BIN', 'DEBUGADV.BIN', 'DEBUGADV2.BIN', 'FIELD.BIN']. |
| `0x671e` | — | med | auto-shape | TODO: confirm. Branch-read in 0 scenes / 3 scripts; compared against [5, 6]; writers=['USEMAGIC.BIN']. |
| `0x6722` | — | med | auto-shape | TODO: confirm. Branch-read in 13 scenes / 18 scripts; compared against [0, 1, 5, 99]; writers=['GAMESTART.BIN', 'SALLY.BIN', 'SC0010.BIN', 'SC0140.BIN']. | | `0x6722` | — | med | auto-shape | TODO: confirm. Branch-read in 13 scenes / 18 scripts; compared against [0, 1, 5, 99]; writers=['GAMESTART.BIN', 'SALLY.BIN', 'SC0010.BIN', 'SC0140.BIN']. |
| `0x6724` | — | med | auto-shape | TODO: confirm. Branch-read in 4 scenes / 10 scripts; compared against [0, 25, 50, 99]; writers=['DEBUGADV2.BIN', 'DEBUGMAP.BIN', 'DEBUGMAP2.BIN', 'DEBUGMAP3.BIN']. | | `0x6724` | — | med | auto-shape | TODO: confirm. Branch-read in 4 scenes / 10 scripts; compared against [0, 25, 50, 99]; writers=['DEBUGADV2.BIN', 'DEBUGMAP.BIN', 'DEBUGMAP2.BIN', 'DEBUGMAP3.BIN']. |
| `0x7679` | — | med | auto-shape | TODO: confirm. Branch-read in 0 scenes / 4 scripts; compared against [0, 1, 4, 8, 16, 256, 512, 1024]; writers=['FIELD.BIN']. | | `0x7679` | — | med | auto-shape | TODO: confirm. Branch-read in 0 scenes / 4 scripts; compared against [0, 1, 4, 8, 16, 256, 512, 1024]; writers=['FIELD.BIN']. |
@@ -475,6 +477,8 @@
| `0x7db` | modal_message_lines | high | investigation | Shared ten-string modal buffer. Producers append messages or menu options at modal_message_line_count; MES measures and draws each string, and SBUNKI reuses the same entries for an interactive selection list. | | `0x7db` | modal_message_lines | high | investigation | Shared ten-string modal buffer. Producers append messages or menu options at modal_message_line_count; MES measures and draws each string, and SBUNKI reuses the same entries for an interactive selection list. |
| `0x7e5` | modal_annotation_texts | high | investigation | Optional small-font annotations rendered by MES and SBUNKI after the primary modal lines. No direct shipped producer was found; the paired count and placement arrays expose a reserved/extensible annotation ABI. | | `0x7e5` | modal_annotation_texts | high | investigation | Optional small-font annotations rendered by MES and SBUNKI after the primary modal lines. No direct shipped producer was found; the paired count and placement arrays expose a reserved/extensible annotation ABI. |
| `0x25fa` | condition_level_names | high | investigation | ILINIT's row-major five-name matrix. Runtime condition UI selects the row by condition id and the column by current level minus one; id 1 has only the unnumbered instant-death label, id 12 and ids 15..29 are reserved. Columns: 0=level_1, 1=level_2, 2=level_3, 3=level_4, 4=level_5. | | `0x25fa` | condition_level_names | high | investigation | ILINIT's row-major five-name matrix. Runtime condition UI selects the row by condition id and the column by current level minus one; id 1 has only the unnumbered instant-death label, id 12 and ids 15..29 are reserved. Columns: 0=level_1, 1=level_2, 2=level_3, 3=level_4, 4=level_5. |
| `0x2690` | attack_element_names | high | investigation | AFINIT attack-side affinity vocabulary. DRAWTIP indexes ids 1..8 from ITINIT, while INFOAF displays those same eight ordinary attack elements above attack_element_effectiveness_percent. Columns: 1=physical, 2=universal, 3=fire, 4=ice, 5=lightning, 6=earth, 7=holy, 8=dark, 11=resistance_1, 12=resistance_2, 13=resistance_3, 14=resistance_4, 15=resistance_5, 16=resistance_6, 17=resistance_7. |
| `0x26a4` | defense_element_names | high | investigation | AFINIT defense-side affinity vocabulary. DRAWENP and DRAWTIP index it with unit/item defense element ids; the same id selects a row of attack_element_effectiveness_percent. Columns: 1=physical, 2=universal, 3=fire, 4=ice, 5=lightning, 6=earth, 7=holy, 8=dark, 9=divinity, 10=demon, 11=spirit, 12=undead. |
| `0x26b4` | class_change_title_output | high | investigation | CCINIT writes the title selected by each eligible class-change rule. CALCCC copies it to unit_class_titles for a successful promotion, and ADDEXP includes the same string in the level-up notification. | | `0x26b4` | class_change_title_output | high | investigation | CCINIT writes the title selected by each eligible class-change rule. CALCCC copies it to unit_class_titles for a successful promotion, and ADDEXP includes the same string in the level-up notification. |
| `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. | | `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. | | `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. |
@@ -482,6 +486,7 @@
| `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. | | `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. | | `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. |
| `0x27bc` | stage_defeat_condition_2 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders nonempty values after stage_defeat_condition_1, and FIELD copies the slot into the current mission-condition display. | | `0x27bc` | stage_defeat_condition_2 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders nonempty values after stage_defeat_condition_1, and FIELD copies the slot into the current mission-condition display. |
| `0x43dd` | name_entry_character_palette | high | investigation | INPUTNAME's five 70-cell character pages: row 0 hiragana, row 1 katakana, row 2 full-width Latin letters, row 3 Arabic/Kanji/Roman/circled numerals, and row 4 symbols. Cursor slots 70..74 select the page; selecting a populated cell copies it into the seven-character name buffer. |
| `0x45b9` | magic_action_names | high | investigation | MAINIT's one-based action-name array: eleven shipped records occupy ids 1..11 in a reserved 30-cell span. MAGIC, STUDY, and EVOLVE render the selected entry. | | `0x45b9` | magic_action_names | high | investigation | MAINIT's one-based action-name array: eleven shipped records occupy ids 1..11 in a reserved 30-cell span. MAGIC, STUDY, and EVOLVE render the selected entry. |
| `0x45d7` | character_profile_names | high | investigation | CIINIT writes the character-information screen's 24 display names. INFOCH lists them by profile id, masks unrevealed rows, and uses the same id to select the backing unit metadata and CIMES biography. | | `0x45d7` | character_profile_names | high | investigation | CIINIT writes the character-information screen's 24 display names. INFOCH lists them by profile id, masks unrevealed rows, and uses the same id to select the backing unit metadata and CIMES biography. |
| `0x463b` | glossary_topic_titles | high | investigation | VIINIT writes the sparse glossary title table. INFOVO renders the selected title, and extract_init joins all 65 populated ids to VIMES's full help text. | | `0x463b` | glossary_topic_titles | high | investigation | VIINIT writes the sparse glossary title table. INFOVO renders the selected title, and extract_init joins all 65 populated ids to VIMES's full help text. |
@@ -3228,7 +3233,6 @@
| `0x43c8` | — | med | auto-shape | string-table (written by CDINIT2) | | `0x43c8` | — | med | auto-shape | string-table (written by CDINIT2) |
| `0x43c9` | — | med | auto-shape | string-table (written by CDINIT2) | | `0x43c9` | — | med | auto-shape | string-table (written by CDINIT2) |
| `0x43ca` | — | med | auto-shape | string-table (written by CDINIT2) | | `0x43ca` | — | med | auto-shape | string-table (written by CDINIT2) |
| `0x43dd` | — | med | auto-shape | string-table (written by CTINIT) |
| `0x43de` | — | med | auto-shape | string-table (written by CTINIT) | | `0x43de` | — | med | auto-shape | string-table (written by CTINIT) |
| `0x43df` | — | med | auto-shape | string-table (written by CTINIT) | | `0x43df` | — | med | auto-shape | string-table (written by CTINIT) |
| `0x43e0` | — | med | auto-shape | string-table (written by CTINIT) | | `0x43e0` | — | med | auto-shape | string-table (written by CTINIT) |
@@ -5041,9 +5045,6 @@
| `0x6638b` | — | med | auto-shape | record-table[stride 15] | | `0x6638b` | — | med | auto-shape | record-table[stride 15] |
| `0x81c96` | — | med | auto-shape | record-table[stride 3] | | `0x81c96` | — | med | auto-shape | record-table[stride 3] |
| `0x8284e` | — | med | auto-shape | record-table[stride 3] | | `0x8284e` | — | med | auto-shape | record-table[stride 3] |
| `0xab6fa` | — | med | auto-shape | record-table[stride 11] |
| `0xab7d6` | — | med | auto-shape | record-table[stride 11] |
| `0xab8b2` | — | med | auto-shape | record-table[stride 7] |
| `0xc6077` | — | med | auto-shape | record-table[stride 27] | | `0xc6077` | — | med | auto-shape | record-table[stride 27] |
| `0xcc9f0` | — | low | auto-shape | index/counter? | | `0xcc9f0` | — | low | auto-shape | index/counter? |
| `0xccbdc` | — | med | auto-shape | record-table[stride 14] | | `0xccbdc` | — | med | auto-shape | record-table[stride 14] |

View File

@@ -669,15 +669,16 @@ 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, and ALINIT now have 1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, EBINIT, STINIT, CCINIT, SCINIT, RTINIT, MAINIT, ILINIT, CGINIT, ALINIT, AFINIT, and CTINIT 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, and 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. With RTINIT's used-provider surface closed and ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and joins, AFINIT closes the signed affinity/tuning/facility tables, and CTINIT closes INPUTNAME's
INFOMES/MES classified, audit the malformed AFINIT/CTINIT outputs next; five-page character palette. With RTINIT's used-provider surface closed and
ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and INFOMES/MES classified, audit CVINIT 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

@@ -2900,5 +2900,31 @@ now names the shared spendable-point balance, alchemy level, and alchemy-level p
protect the full write accounting, sparse layout, item joins, story gates, paired ingredient cells, and protect the full write accounting, sparse layout, item joins, story gates, paired ingredient cells, and
raw-to-semantic projection. raw-to-semantic projection.
**Next:** audit the remaining small malformed INIT outputs, especially AFINIT and CTINIT, before ## Data-semantics sidebar: AFINIT affinity/tuning tables and CTINIT name palette (2026-07-23)
spending a larger slice on structurally regular but semantically shallow MPINIT.
AFINIT and CTINIT were not malformed record tables; both were vocabulary/table initializers that the
generic name heuristic could not segment. AFINIT contains 27 Japanese element labels and 54
length-prefixed integer rows. Its affinity section is a reserved `20 × 20` defense-by-attack matrix
with thirteen authored defense rows and eighteen authored columns. The dedicated extractor converts
the footer's unsigned representations back to signed values, preserving the `-100` immunity cases,
and joins rows 1..12 to the physical/universal/elemental/divinity/demon/spirit/undead defense names.
AFINIT's remaining rows are two paired `20 × 11` item-tuning tables and one `3 × 7` facility
progression table. Curve ids 1..18 provide ten tuning-level stat bonuses and matching point costs;
row 19 is explicitly zero/reserved and the eleventh column remains reserved. TUNE, IMPROVE, DRAWTIP,
and CALCREVISE establish the bonus/cost contract. The three facility rows belong to equipment tuning,
alchemy, and magic/research respectively, each with six thresholds leading to the level-6 cap. The
registry now also names the equipment-tuning and magic facility level/progress counters.
CTINIT is INPUTNAME's complete `5 × 70` character palette. Its rows are hiragana, katakana, full-width
Latin letters, four numeral styles, and symbols; their populated counts are 56, 56, 52, 40, and 69,
for 273 authored cells total. INPUTNAME uses cursor slots 70..74 to select a page, rejects an empty
cell, and copies a selected character into its seven-character name buffer. The generated JSON keeps
all 350 positions so layout gaps remain explicit rather than collapsing into a flat character list.
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.
**Next:** audit CVINIT. Its generic output is already small (37 writes, thirteen rows, ten apparent
fields), so direct consumer tracing should quickly distinguish a valid sparse registry from the last
remaining ownership artifact before moving to MPINIT.

View File

@@ -108,6 +108,13 @@ layout, backed by output item, minimum level, required/forbidden story flags, po
ingredient-id/quantity slots. The dedicated extraction classifies every write, joins all 107 output ingredient-id/quantity slots. The dedicated extraction classifies every write, joins all 107 output
items and 286 ingredient references to ITINIT, and retains the seven raw table coordinates. items and 286 ingredient references to ITINIT, and retains the seven raw table coordinates.
AFINIT is an affinity and progression initializer, not a name-record table. Its 27 strings form sparse
attack- and defense-element vocabularies; its 54 footer copies form a signed `20 × 20` effectiveness
matrix (thirteen by eighteen cells authored), eighteen usable item-tuning bonus/cost curves plus a
reserved zero row, and equipment-tuning/alchemy/magic facility progression thresholds. CTINIT is
INPUTNAME's separate `5 × 70` character palette: 273 authored hiragana, katakana, Latin, numeral, and
symbol cells with all reserved gaps retained.
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, and ALINIT alchemy-recipe 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, 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 | — |
| `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,
@@ -86,6 +86,12 @@ required and forbidden story flags, point cost, and four paired ingredient-id/qu
and ingredient ids must resolve through ITINIT. The generated record adds a nested `ingredients` view and ingredient ids must resolve through ITINIT. The generated record adds a nested `ingredients` view
while retaining every raw scalar and `base/stride/column` coordinate. while retaining every raw scalar and `base/stride/column` coordinate.
Name-mode AFINIT and CTINIT bypass the generic name-record heuristic because their strings are
vocabularies rather than record boundaries. AFINIT classifies its 27 element labels and 54 footer
copies into the signed affinity matrix, paired item-tuning bonus/cost curves, and three facility
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.
## VM ## VM
| Tool | Purpose | Run | Reads → Writes | | Tool | Purpose | Run | Reads → Writes |

View File

@@ -53,14 +53,15 @@
- [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], **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], **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.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 and ALINIT recipe 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 the malformed small AFINIT/CTINIT outputs by their 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. - [ ] **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.
## 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

@@ -599,6 +599,33 @@ ALCHEMY_RECIPE_RECORD_SPAN = 1000
ALCHEMY_RECIPE_STORY_FLAG_STRIDE = 2 ALCHEMY_RECIPE_STORY_FLAG_STRIDE = 2
ALCHEMY_RECIPE_INGREDIENT_STRIDE = 4 ALCHEMY_RECIPE_INGREDIENT_STRIDE = 4
AFFINITY_ATTACK_ELEMENT_NAME_BASE = 0x2690
AFFINITY_DEFENSE_ELEMENT_NAME_BASE = 0x26A4
AFFINITY_ELEMENT_NAME_SPAN = 20
AFFINITY_EFFECTIVENESS_BASE = 0xAB5BA
AFFINITY_EFFECTIVENESS_STRIDE = 20
AFFINITY_EFFECTIVENESS_ROW_COUNT = 13
AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS = 18
ITEM_TUNING_BONUS_CURVE_BASE = 0xAB6FA
ITEM_TUNING_COST_CURVE_BASE = 0xAB7D6
ITEM_TUNING_CURVE_STRIDE = 11
ITEM_TUNING_CURVE_COUNT = 19
ITEM_TUNING_AUTHORED_LEVELS = 10
FACILITY_LEVEL_THRESHOLD_BASE = 0xAB8B2
FACILITY_LEVEL_THRESHOLD_STRIDE = 7
FACILITY_LEVEL_THRESHOLD_ROW_COUNT = 3
FACILITY_LEVEL_THRESHOLD_AUTHORED_LEVELS = 6
NAME_ENTRY_CHARACTER_PALETTE_BASE = 0x43DD
NAME_ENTRY_CHARACTER_PALETTE_STRIDE = 70
NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES = (
"hiragana",
"katakana",
"latin",
"numerals",
"symbols",
)
RECOVER_CURRENT_ENTITY = 0x152616 RECOVER_CURRENT_ENTITY = 0x152616
RECOVER_EFFECTIVE_STATS = 0x4E11B RECOVER_EFFECTIVE_STATS = 0x4E11B
RECOVER_CURRENT_RESOURCES = 0x4E085 RECOVER_CURRENT_RESOURCES = 0x4E085
@@ -2257,6 +2284,424 @@ def extract_alchemy_recipes(scr):
} }
def extract_affinity_definitions(scr):
"""Extract AFINIT's element, tuning-curve, and facility-threshold tables."""
attack_names = {}
defense_names = {}
effectiveness_rows = {}
tuning_bonus_rows = {}
tuning_cost_rows = {}
facility_threshold_rows = {}
string_write_count = 0
footer_array_count = 0
exit_count = 0
def signed_values(values):
return [
value - 0x100000000 if value >= 0x80000000 else value
for value in values
]
for ins in scr.instructions:
if (
ins.opcode == SET_STRING
and len(ins.args) >= 2
and ins.args[0][0] == T_GLOBAL_STRING
):
destination = ins.args[0][1]
text = scr.strings.get(ins.args[1][1], (None,))[0]
for base, target in (
(AFFINITY_ATTACK_ELEMENT_NAME_BASE, attack_names),
(AFFINITY_DEFENSE_ELEMENT_NAME_BASE, defense_names),
):
element_id = destination - base
if 0 <= element_id < AFFINITY_ELEMENT_NAME_SPAN:
_store_unique(target, element_id, text, element_id)
string_write_count += 1
break
else:
raise ValueError(
f"{scr.path.name}: unexpected string destination "
f"0x{destination:x}"
)
continue
if (
ins.opcode == COPY_LOCAL_ARRAY
and len(ins.args) >= 2
and ins.args[0][0] == T_GLOBAL_INT
and ins.args[1][0] == T_IMM
):
destination = ins.args[0][1]
footer_off = ins.args[1][1]
values = read_footer_array(scr, footer_off)
if values is None:
raise ValueError(
f"{scr.path.name}: invalid footer array 0x{footer_off:x}"
)
values = signed_values(values)
classified = False
relative = destination - AFFINITY_EFFECTIVENESS_BASE
if (
relative % AFFINITY_EFFECTIVENESS_STRIDE == 0
and 0 <= relative
< AFFINITY_EFFECTIVENESS_ROW_COUNT
* AFFINITY_EFFECTIVENESS_STRIDE
):
row = relative // AFFINITY_EFFECTIVENESS_STRIDE
if len(values) != AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS:
raise ValueError(
f"{scr.path.name}: effectiveness row {row} has "
f"{len(values)} values"
)
_store_unique(
effectiveness_rows, row, (footer_off, values), row
)
classified = True
if not classified:
for base, target in (
(ITEM_TUNING_BONUS_CURVE_BASE, tuning_bonus_rows),
(ITEM_TUNING_COST_CURVE_BASE, tuning_cost_rows),
):
relative = destination - base
if (
relative % ITEM_TUNING_CURVE_STRIDE == 0
and ITEM_TUNING_CURVE_STRIDE
<= relative
<= ITEM_TUNING_CURVE_COUNT
* ITEM_TUNING_CURVE_STRIDE
):
curve_id = relative // ITEM_TUNING_CURVE_STRIDE
if len(values) != ITEM_TUNING_AUTHORED_LEVELS:
raise ValueError(
f"{scr.path.name}: tuning curve {curve_id} has "
f"{len(values)} values"
)
_store_unique(
target, curve_id, (footer_off, values), curve_id
)
classified = True
break
if not classified:
relative = destination - FACILITY_LEVEL_THRESHOLD_BASE
if (
relative % FACILITY_LEVEL_THRESHOLD_STRIDE == 0
and 0 <= relative
< FACILITY_LEVEL_THRESHOLD_ROW_COUNT
* FACILITY_LEVEL_THRESHOLD_STRIDE
):
row = relative // FACILITY_LEVEL_THRESHOLD_STRIDE
if len(values) != FACILITY_LEVEL_THRESHOLD_AUTHORED_LEVELS:
raise ValueError(
f"{scr.path.name}: facility row {row} has "
f"{len(values)} values"
)
_store_unique(
facility_threshold_rows, row, (footer_off, values), row
)
classified = True
if not classified:
raise ValueError(
f"{scr.path.name}: unexpected footer destination "
f"0x{destination:x}"
)
footer_array_count += 1
continue
if sys4load.display_label(ins.opcode) == "exit":
exit_count += 1
else:
raise ValueError(
f"{scr.path.name}: unexpected opcode "
f"{sys4load.display_label(ins.opcode)} at 0x{ins.offset:x}"
)
expected_effectiveness_rows = set(range(AFFINITY_EFFECTIVENESS_ROW_COUNT))
expected_tuning_curves = set(range(1, ITEM_TUNING_CURVE_COUNT + 1))
expected_facility_rows = set(range(FACILITY_LEVEL_THRESHOLD_ROW_COUNT))
if set(effectiveness_rows) != expected_effectiveness_rows:
raise ValueError(f"{scr.path.name}: incomplete effectiveness matrix")
if (
set(tuning_bonus_rows) != expected_tuning_curves
or set(tuning_cost_rows) != expected_tuning_curves
):
raise ValueError(f"{scr.path.name}: incomplete tuning curves")
if set(facility_threshold_rows) != expected_facility_rows:
raise ValueError(f"{scr.path.name}: incomplete facility thresholds")
if exit_count != 1:
raise ValueError(f"{scr.path.name}: expected one exit, got {exit_count}")
records = []
for defense_element_id in sorted(effectiveness_rows):
footer_off, values = effectiveness_rows[defense_element_id]
record = {
"id": defense_element_id,
"name": defense_names.get(defense_element_id, ""),
"defense_element_id": defense_element_id,
"footer_arrays": {
(
f"0x{AFFINITY_EFFECTIVENESS_BASE:x}/"
f"{defense_element_id * AFFINITY_EFFECTIVENESS_STRIDE}"
): {
"footer_off": f"0x{footer_off:x}",
"values": values,
}
},
"attack_effectiveness": [
{
"attack_element_id": attack_element_id,
"attack_element_name": attack_names.get(
attack_element_id, ""
),
"percent": percent,
}
for attack_element_id, percent in enumerate(values)
],
}
if record["name"]:
record["string_fields"] = {
f"0x{AFFINITY_DEFENSE_ELEMENT_NAME_BASE:x}": record["name"]
}
records.append(record)
tuning_curves = []
for curve_id in sorted(tuning_bonus_rows):
bonus_footer_off, bonuses = tuning_bonus_rows[curve_id]
cost_footer_off, costs = tuning_cost_rows[curve_id]
tuning_curves.append({
"curve_id": curve_id,
"level_bonuses": bonuses,
"level_costs": costs,
"bonus_raw_key": (
f"0x{ITEM_TUNING_BONUS_CURVE_BASE:x}/"
f"{curve_id * ITEM_TUNING_CURVE_STRIDE}"
),
"bonus_footer_off": f"0x{bonus_footer_off:x}",
"cost_raw_key": (
f"0x{ITEM_TUNING_COST_CURVE_BASE:x}/"
f"{curve_id * ITEM_TUNING_CURVE_STRIDE}"
),
"cost_footer_off": f"0x{cost_footer_off:x}",
})
facility_names = ("item_tuning", "alchemy", "magic")
facility_thresholds = []
for row in sorted(facility_threshold_rows):
footer_off, thresholds = facility_threshold_rows[row]
facility_thresholds.append({
"system_id": row,
"system": facility_names[row],
"level_progress_thresholds": thresholds,
"raw_key": (
f"0x{FACILITY_LEVEL_THRESHOLD_BASE:x}/"
f"{row * FACILITY_LEVEL_THRESHOLD_STRIDE}"
),
"footer_off": f"0x{footer_off:x}",
})
return records, {
"schema": "affinity-and-progression-tables",
"attack_element_names": [
{"id": element_id, "name": name}
for element_id, name in sorted(attack_names.items())
],
"defense_element_names": [
{"id": element_id, "name": name}
for element_id, name in sorted(defense_names.items())
],
"effectiveness_matrix": {
"base": f"0x{AFFINITY_EFFECTIVENESS_BASE:x}",
"reserved_shape": [
AFFINITY_EFFECTIVENESS_STRIDE,
AFFINITY_EFFECTIVENESS_STRIDE,
],
"authored_rows": AFFINITY_EFFECTIVENESS_ROW_COUNT,
"authored_columns": AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS,
},
"item_tuning_curves": tuning_curves,
"usable_item_tuning_curve_ids": [
curve["curve_id"]
for curve in tuning_curves
if any(curve["level_bonuses"])
],
"reserved_item_tuning_curve_ids": [
curve["curve_id"]
for curve in tuning_curves
if not any(curve["level_bonuses"])
and not any(curve["level_costs"])
],
"facility_level_thresholds": facility_thresholds,
"string_write_count": string_write_count,
"footer_array_count": footer_array_count,
"exit_count": exit_count,
"classified_instruction_count": (
string_write_count + footer_array_count + exit_count
),
"array_layouts": {
f"0x{AFFINITY_EFFECTIVENESS_BASE:x}": {
"length": (
AFFINITY_EFFECTIVENESS_STRIDE
* AFFINITY_EFFECTIVENESS_STRIDE
),
"stride": AFFINITY_EFFECTIVENESS_STRIDE,
"rows": AFFINITY_EFFECTIVENESS_STRIDE,
},
f"0x{ITEM_TUNING_BONUS_CURVE_BASE:x}": {
"length": (
(ITEM_TUNING_CURVE_COUNT + 1)
* ITEM_TUNING_CURVE_STRIDE
),
"stride": ITEM_TUNING_CURVE_STRIDE,
"rows": ITEM_TUNING_CURVE_COUNT + 1,
},
f"0x{ITEM_TUNING_COST_CURVE_BASE:x}": {
"length": (
(ITEM_TUNING_CURVE_COUNT + 1)
* ITEM_TUNING_CURVE_STRIDE
),
"stride": ITEM_TUNING_CURVE_STRIDE,
"rows": ITEM_TUNING_CURVE_COUNT + 1,
},
f"0x{FACILITY_LEVEL_THRESHOLD_BASE:x}": {
"length": (
FACILITY_LEVEL_THRESHOLD_ROW_COUNT
* FACILITY_LEVEL_THRESHOLD_STRIDE
),
"stride": FACILITY_LEVEL_THRESHOLD_STRIDE,
"rows": FACILITY_LEVEL_THRESHOLD_ROW_COUNT,
},
},
"consumer_contract": {
"affinity": (
"CALCBTPARAM and AI providers index the effectiveness matrix "
"by defense element then attack element; INFOAF displays the "
"consumer-selected rows and the eight shipped attack elements."
),
"item_tuning": (
"TUNE, IMPROVE, DRAWTIP, and CALCREVISE combine each ITINIT "
"curve id with a zero-based tuning level to obtain the stat "
"bonus and point cost."
),
"facility_progression": (
"IMPROVE, ALCHEMY, and MAGIC/USEMAGIC index rows 0, 1, and 2 "
"respectively by current facility level."
),
},
}
def extract_name_entry_palette(scr):
"""Extract CTINIT's five-page, 70-cell name-entry character palette."""
rows = [
[None] * NAME_ENTRY_CHARACTER_PALETTE_STRIDE
for _ in NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES
]
string_write_count = 0
exit_count = 0
for ins in scr.instructions:
if (
ins.opcode == SET_STRING
and len(ins.args) >= 2
and ins.args[0][0] == T_GLOBAL_STRING
):
destination = ins.args[0][1]
relative = destination - NAME_ENTRY_CHARACTER_PALETTE_BASE
if not (
0 <= relative
< len(rows) * NAME_ENTRY_CHARACTER_PALETTE_STRIDE
):
raise ValueError(
f"{scr.path.name}: unexpected character destination "
f"0x{destination:x}"
)
row, column = divmod(
relative, NAME_ENTRY_CHARACTER_PALETTE_STRIDE
)
if rows[row][column] is not None:
raise ValueError(
f"{scr.path.name}: duplicate character cell {row}/{column}"
)
rows[row][column] = scr.strings.get(
ins.args[1][1], (None,)
)[0]
string_write_count += 1
continue
if sys4load.display_label(ins.opcode) == "exit":
exit_count += 1
else:
raise ValueError(
f"{scr.path.name}: unexpected opcode "
f"{sys4load.display_label(ins.opcode)} at 0x{ins.offset:x}"
)
if exit_count != 1:
raise ValueError(f"{scr.path.name}: expected one exit, got {exit_count}")
records = []
for row_id, (name, characters) in enumerate(zip(
NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES, rows
)):
populated = [
{"slot": slot, "character": character}
for slot, character in enumerate(characters)
if character is not None
]
records.append({
"id": row_id,
"name": name,
"characters": characters,
"populated_characters": populated,
"string_fields": {
(
f"0x{NAME_ENTRY_CHARACTER_PALETTE_BASE:x}/"
f"{NAME_ENTRY_CHARACTER_PALETTE_STRIDE}/{entry['slot']}"
): entry["character"]
for entry in populated
},
})
return records, {
"schema": "name-entry-character-palette",
"palette_base": f"0x{NAME_ENTRY_CHARACTER_PALETTE_BASE:x}",
"reserved_shape": [
len(NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES),
NAME_ENTRY_CHARACTER_PALETTE_STRIDE,
],
"row_names": list(NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES),
"string_write_count": string_write_count,
"exit_count": exit_count,
"classified_instruction_count": string_write_count + exit_count,
"populated_cells_per_row": [
sum(character is not None for character in row) for row in rows
],
"empty_slots_per_row": [
[
slot
for slot, character in enumerate(row)
if character is None
]
for row in rows
],
"consumer_contract": {
"script": "INPUTNAME.BIN",
"lookup": (
"INPUTNAME selects one of five palette pages, indexes its "
"70-cell row by cursor slot, rejects empty cells, and copies "
"a selected character into the seven-character name buffer."
),
"page_selection": (
"Cursor slots 70..74 select palette rows 0..4."
),
},
}
@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."""
@@ -2990,6 +3435,14 @@ def write_data_index(data_dir: Path) -> None:
"point cost, required/forbidden story flags, and four fixed ingredient slots retain", "point cost, required/forbidden story flags, and four fixed ingredient slots retain",
"their raw parallel-array and row-table coordinates.", "their raw parallel-array and row-table coordinates.",
"", "",
"AFINIT's dedicated affinity/progression schema exposes its attack and defense",
"element vocabularies, signed effectiveness matrix, eighteen usable item-tuning",
"bonus/cost curves plus a reserved zero row, and three facility progression rows.",
"",
"CTINIT's dedicated name-entry schema exposes INPUTNAME's five 70-cell palette",
"pages (hiragana, katakana, Latin, numerals, and symbols), preserving all reserved",
"empty slots beside the 273 authored characters.",
"",
"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`",
@@ -3065,6 +3518,10 @@ def main() -> int:
extractor = extract_magic_actions extractor = extract_magic_actions
elif mode == "name" and name == "ILINIT": elif mode == "name" and name == "ILINIT":
extractor = extract_condition_definitions extractor = extract_condition_definitions
elif mode == "name" and name == "AFINIT":
extractor = extract_affinity_definitions
elif mode == "name" and name == "CTINIT":
extractor = extract_name_entry_palette
elif mode == "numeric" and name == "CGINIT": elif mode == "numeric" and name == "CGINIT":
extractor = extract_gallery_definitions extractor = extract_gallery_definitions
elif mode == "numeric" and name == "ALINIT": elif mode == "numeric" and name == "ALINIT":

View File

@@ -1134,6 +1134,144 @@ def test_alchemy_recipes() -> None:
) )
def test_affinity_definitions() -> None:
scripts = paths.scripts()
script = sys4load.load(scripts["AFINIT.BIN"])
check(
extract_init.detect_mode(script) == "name",
"AFINIT remains compatible with name-mode auto-detection",
)
records, meta = extract_init.extract_affinity_definitions(script)
by_id = {record["id"]: record for record in records}
check(
len(records) == 13
and meta["string_write_count"] == 27
and meta["footer_array_count"] == 54
and meta["classified_instruction_count"] == 82,
"AFINIT classifies every vocabulary, footer-array, and exit instruction",
)
check(
[entry["id"] for entry in meta["attack_element_names"]]
== [*range(1, 9), *range(11, 18)]
and [entry["id"] for entry in meta["defense_element_names"]]
== list(range(1, 13)),
"AFINIT preserves its sparse attack and defense element vocabularies",
)
check(
by_id[3]["name"] == "火炎"
and by_id[3]["attack_effectiveness"][3]["percent"] == -100
and by_id[3]["attack_effectiveness"][4]["percent"] == 150
and by_id[11]["attack_effectiveness"][1]["percent"] == 1
and by_id[11]["attack_effectiveness"][7]["percent"] == 200,
"AFINIT exposes signed elemental immunities, weaknesses, and resistances",
)
tuning = {
curve["curve_id"]: curve for curve in meta["item_tuning_curves"]
}
check(
len(tuning) == 19
and tuning[1]["level_bonuses"] == [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
and tuning[9]["level_bonuses"] == list(range(1, 11))
and tuning[18]["level_bonuses"] == list(range(3, 31, 3))
and tuning[18]["level_costs"]
== [10, 25, 45, 70, 100, 140, 190, 250, 320, 400]
and tuning[19]["level_bonuses"] == [0] * 10
and tuning[19]["level_costs"] == [0] * 10
and meta["usable_item_tuning_curve_ids"] == list(range(1, 19))
and meta["reserved_item_tuning_curve_ids"] == [19],
"AFINIT pairs all nineteen item-tuning bonus and point-cost curves",
)
check(
[
row["level_progress_thresholds"]
for row in meta["facility_level_thresholds"]
] == [
[40, 80, 120, 160, 200, 300],
[20, 40, 60, 90, 120, 200],
[20, 50, 100, 150, 200, 400],
],
"AFINIT exposes the tuning, alchemy, and magic progression rows",
)
semantics = extract_init.field_semantics(
records, meta["array_layouts"]
)
check(
len(semantics) == 14
and semantics["0x26a4"] == "defense_element_names"
and semantics["0xab5ba/0"]
== "attack_element_effectiveness_percent.row_0"
and semantics["0xab5ba/240"]
== "attack_element_effectiveness_percent.row_12",
"AFINIT raw vocabulary and matrix rows join to canonical semantics",
)
extract_init.attach_semantic_fields(records, semantics)
check(
by_id[3]["semantic_fields"]["defense_element_names"] == "火炎"
and by_id[3]["semantic_fields"][
"attack_element_effectiveness_percent.row_3"
][3] == -100,
"AFINIT retains raw footer provenance beside signed semantic rows",
)
def test_name_entry_palette() -> None:
scripts = paths.scripts()
script = sys4load.load(scripts["CTINIT.BIN"])
check(
extract_init.detect_mode(script) == "name",
"CTINIT remains compatible with name-mode auto-detection",
)
records, meta = extract_init.extract_name_entry_palette(script)
by_id = {record["id"]: record for record in records}
check(
len(records) == 5
and meta["reserved_shape"] == [5, 70]
and meta["string_write_count"] == 273
and meta["classified_instruction_count"] == 274,
"CTINIT classifies all five reserved palette pages and every instruction",
)
check(
meta["row_names"]
== ["hiragana", "katakana", "latin", "numerals", "symbols"]
and meta["populated_cells_per_row"] == [56, 56, 52, 40, 69],
"CTINIT names each page and preserves its authored cell population",
)
check(
by_id[0]["characters"][0] == ""
and by_id[0]["characters"][17] is None
and by_id[1]["characters"][50] == ""
and by_id[2]["characters"][0] == ""
and by_id[2]["characters"][30] == ""
and by_id[3]["characters"][20] == ""
and by_id[3]["characters"][30] == ""
and by_id[4]["characters"][68] == "ω"
and by_id[4]["characters"][69] is None,
"CTINIT retains representative characters and intentional empty slots",
)
semantics = extract_init.field_semantics(records)
check(
len(semantics) == 69
and semantics["0x43dd/70/0"]
== "name_entry_character_palette.column_0"
and semantics["0x43dd/70/68"]
== "name_entry_character_palette.column_68",
"CTINIT raw palette slots join to one canonical table name",
)
extract_init.attach_semantic_fields(records, semantics)
check(
by_id[0]["semantic_fields"][
"name_entry_character_palette.column_0"
] == ""
and by_id[4]["semantic_fields"][
"name_entry_character_palette.column_68"
] == "ω",
"CTINIT retains raw cells beside the semantic palette 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"])
@@ -1345,6 +1483,8 @@ if __name__ == "__main__":
test_character_names() test_character_names()
test_gallery_definitions() test_gallery_definitions()
test_alchemy_recipes() test_alchemy_recipes()
test_affinity_definitions()
test_name_entry_palette()
test_condition_definitions() test_condition_definitions()
test_field_semantics() test_field_semantics()
if FAILS: if FAILS:

View File

@@ -1798,10 +1798,10 @@ name = "attack_element_effectiveness_percent"
category = "data-table" category = "data-table"
type = "int[20][20]" type = "int[20][20]"
value_domain = "signed effectiveness percentage indexed by defense element then attack element" value_domain = "signed effectiveness percentage indexed by defense element then attack element"
usage = "AFINIT-authored element matchup table. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases." usage = "AFINIT-authored element matchup table with thirteen authored defense rows and eighteen authored cells per row inside the reserved 20-by-20 layout. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases. INFOAF displays eight attack columns for its selected defense rows."
source = "investigation" source = "investigation"
confidence = "high" confidence = "high"
depends_on = ["0x77c6e", "0x8e3d1", "0x8dfe9", "0xa7436"] depends_on = ["0x2690", "0x26a4", "0x77c6e", "0x8e3d1", "0x8dfe9", "0xa7436"]
[[global]] [[global]]
address = "0xcc9f4" address = "0xcc9f4"
@@ -2194,14 +2194,47 @@ depends_on = ["0x671d"]
[[global]] [[global]]
address = "0x671e" address = "0x671e"
name = "" name = "magic_facility_level"
category = "story-flag" category = "counter"
type = "int" type = "int"
value_domain = "one of {5, 6}" value_domain = "0..6"
usage = "TODO: confirm. Branch-read in 0 scenes / 3 scripts; compared against [5, 6]; writers=['USEMAGIC.BIN']." usage = "Current magic/research facility level. MAGIC and USEMAGIC index facility_level_progress_thresholds row 2 with this level; USEMAGIC raises it up to 6 when magic_facility_progress reaches the current threshold."
source = "auto-shape" source = "investigation"
confidence = "med" confidence = "high"
depends_on = [] depends_on = ["0xab8b2", "0x6721"]
[[global]]
address = "0x671c"
name = "item_tuning_facility_level"
category = "counter"
type = "int"
value_domain = "0..6"
usage = "Current equipment-tuning facility level. IMPROVE indexes facility_level_progress_thresholds row 0 with this value and raises it, up to 6, as item_tuning_facility_progress accumulates."
source = "investigation"
confidence = "high"
depends_on = ["0xab8b2", "0x671f"]
[[global]]
address = "0x671f"
name = "item_tuning_facility_progress"
category = "counter"
type = "int"
value_domain = "non-negative tuning progress"
usage = "Progress toward the next equipment-tuning facility level. IMPROVE adds the selected item's tuning-level increases, applies row 0 of facility_level_progress_thresholds, and carries or clamps progress when the level rises."
source = "investigation"
confidence = "high"
depends_on = ["0x671c", "0xab8b2"]
[[global]]
address = "0x6721"
name = "magic_facility_progress"
category = "counter"
type = "int"
value_domain = "non-negative magic/research progress"
usage = "Progress toward the next magic/research facility level. USEMAGIC adds the selected action's progress award and applies row 2 of facility_level_progress_thresholds; MAGIC renders the same current/threshold pair."
source = "investigation"
confidence = "high"
depends_on = ["0x671e", "0xab8b2"]
[[global]] [[global]]
address = "0x6722" address = "0x6722"
@@ -3348,3 +3381,73 @@ usage = "Required ingredient quantities paired cell-for-cell with alchemy_recipe
source = "investigation" source = "investigation"
confidence = "high" confidence = "high"
depends_on = ["0x158154"] depends_on = ["0x158154"]
[[global]]
address = "0x2690"
name = "attack_element_names"
category = "string-table"
type = "string[20]"
columns = { "1" = "physical", "2" = "universal", "3" = "fire", "4" = "ice", "5" = "lightning", "6" = "earth", "7" = "holy", "8" = "dark", "11" = "resistance_1", "12" = "resistance_2", "13" = "resistance_3", "14" = "resistance_4", "15" = "resistance_5", "16" = "resistance_6", "17" = "resistance_7" }
value_domain = "Japanese element/attribute display strings; unused cells are empty"
usage = "AFINIT attack-side affinity vocabulary. DRAWTIP indexes ids 1..8 from ITINIT, while INFOAF displays those same eight ordinary attack elements above attack_element_effectiveness_percent."
source = "investigation"
confidence = "high"
depends_on = ["0x8dfe9", "0xa7436", "0xab5ba"]
[[global]]
address = "0x26a4"
name = "defense_element_names"
category = "string-table"
type = "string[20]"
columns = { "1" = "physical", "2" = "universal", "3" = "fire", "4" = "ice", "5" = "lightning", "6" = "earth", "7" = "holy", "8" = "dark", "9" = "divinity", "10" = "demon", "11" = "spirit", "12" = "undead" }
value_domain = "Japanese defense-affinity display strings; unused cells are empty"
usage = "AFINIT defense-side affinity vocabulary. DRAWENP and DRAWTIP index it with unit/item defense element ids; the same id selects a row of attack_element_effectiveness_percent."
source = "investigation"
confidence = "high"
depends_on = ["0x77c6e", "0x8e3d1", "0xab5ba"]
[[global]]
address = "0x43dd"
name = "name_entry_character_palette"
category = "string-table"
type = "string[5][70]"
value_domain = "one authored input character or empty reserved cell"
usage = "INPUTNAME's five 70-cell character pages: row 0 hiragana, row 1 katakana, row 2 full-width Latin letters, row 3 Arabic/Kanji/Roman/circled numerals, and row 4 symbols. Cursor slots 70..74 select the page; selecting a populated cell copies it into the seven-character name buffer."
source = "investigation"
confidence = "high"
[[global]]
address = "0xab6fa"
name = "item_tuning_stat_bonus_curves"
category = "data-table"
type = "int[20][11]"
columns = { "0" = "tuning_level_1", "1" = "tuning_level_2", "2" = "tuning_level_3", "3" = "tuning_level_4", "4" = "tuning_level_5", "5" = "tuning_level_6", "6" = "tuning_level_7", "7" = "tuning_level_8", "8" = "tuning_level_9", "9" = "tuning_level_10", "10" = "reserved" }
value_domain = "non-negative stat bonus; zero means that tuning level is unavailable"
usage = "Eighteen usable equipment-growth curves selected by item_tuning_curve_ids, plus an explicitly zeroed reserved row 19. TUNE and IMPROVE use the nonzero prefix as the available tuning-level range; CALCREVISE and DRAWTIP add the selected zero-based tuning level's bonus to the corresponding stat."
source = "investigation"
confidence = "high"
depends_on = ["0xa2bf1"]
[[global]]
address = "0xab7d6"
name = "item_tuning_point_cost_curves"
category = "data-table"
type = "int[20][11]"
columns = { "0" = "tuning_level_1", "1" = "tuning_level_2", "2" = "tuning_level_3", "3" = "tuning_level_4", "4" = "tuning_level_5", "5" = "tuning_level_6", "6" = "tuning_level_7", "7" = "tuning_level_8", "8" = "tuning_level_9", "9" = "tuning_level_10", "10" = "reserved" }
value_domain = "non-negative point cost; shipped nonzero values are 10..400"
usage = "Point-cost curves paired by curve id and tuning level with item_tuning_stat_bonus_curves. IMPROVE sums costs for newly selected levels, subtracts refunds for removed levels, checks the resulting total against the tuning-point balance, and deducts it on confirmation."
source = "investigation"
confidence = "high"
depends_on = ["0xab6fa", "0xa2bf1"]
[[global]]
address = "0xab8b2"
name = "facility_level_progress_thresholds"
category = "data-table"
type = "int[3][7]"
columns = { "0" = "level_0_to_1", "1" = "level_1_to_2", "2" = "level_2_to_3", "3" = "level_3_to_4", "4" = "level_4_to_5", "5" = "level_5_to_6", "6" = "level_6_cap" }
value_domain = "required progress; final cap column is zero/unwritten"
usage = "AFINIT's three facility-progression rows. IMPROVE uses row 0 for equipment tuning, ALCHEMY uses row 1, and MAGIC/USEMAGIC use row 2; each indexes the row by the current level and caps advancement at level 6."
source = "investigation"
confidence = "high"
depends_on = ["0x671c", "0x671d", "0x671e", "0x671f", "0x6720", "0x6721"]