Decode ILINIT condition and RECOVER schemas
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
||||||
# Global Variable Reference (generated)
|
# Global Variable Reference (generated)
|
||||||
|
|
||||||
5045 globals (277 curated, 4768 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
5046 globals (290 curated, 4756 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||||
|
|
||||||
## choice-output
|
## choice-output
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
| `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. |
|
||||||
| `0x665e2` | modal_annotation_count | high | investigation | Number of populated modal_annotation_texts entries. MES and SBUNKI iterate this many annotations and clear it on dismissal; no direct shipped producer was found. |
|
| `0x665e2` | modal_annotation_count | high | investigation | Number of populated modal_annotation_texts entries. MES and SBUNKI iterate this many annotations and clear it on dismissal; no direct shipped producer was found. |
|
||||||
|
| `0xaac77` | current_condition_delta_levels | high | investigation | Shared signed level delta passed to ADDILL/ADDILLSUB for current_condition_id. Positive values apply or strengthen a condition; negative values weaken/remove it subject to the equipment/passive baseline floor. |
|
||||||
| `0xcc9f2` | usable_action_min_range | high | investigation | CALCSCOPE initializes this from the equipped/default attack's minimum range and widens it for usable offensive and healing skills. It is the lower bound shared by the offensive and healing action-scope tables. |
|
| `0xcc9f2` | usable_action_min_range | high | investigation | CALCSCOPE initializes this from the equipped/default attack's minimum range and widens it for usable offensive and healing skills. It is the lower bound shared by the offensive and healing action-scope tables. |
|
||||||
| `0xcc9f3` | usable_action_max_range | high | investigation | CALCSCOPE initializes this from the equipped/default attack's maximum range and widens it for usable offensive and healing skills. ATSEEK bounds its action-range flood fill with this value; RTN_M051/052 and RTN_M061 use it as their upper scan bound. |
|
| `0xcc9f3` | usable_action_max_range | high | investigation | CALCSCOPE initializes this from the equipped/default attack's maximum range and widens it for usable offensive and healing skills. ATSEEK bounds its action-range flood fill with this value; RTN_M051/052 and RTN_M061 use it as their upper scan bound. |
|
||||||
| `0xe6c5d` | scjump_progress_d | med | inference | SCJUMP switch input (168 comparison reads) — progression counter/position. INFERENCE from SCJUMP usage. |
|
| `0xe6c5d` | scjump_progress_d | med | inference | SCJUMP switch input (168 comparison reads) — progression counter/position. INFERENCE from SCJUMP usage. |
|
||||||
@@ -53,6 +54,9 @@
|
|||||||
| `0x522ed` | entity_faction_ids | high | investigation | Per-runtime-entity faction id. RTN_M007 restricts its injured-unit target to the acting entity's faction; MVSEEK mode 2 compares this value while masking active foreign-entity cells, and ordinary movement checks the acting faction's bit in tile passability masks. |
|
| `0x522ed` | entity_faction_ids | high | investigation | Per-runtime-entity faction id. RTN_M007 restricts its injured-unit target to the acting entity's faction; MVSEEK mode 2 compares this value while masking active foreign-entity cells, and ordinary movement checks the acting faction's bit in tile passability masks. |
|
||||||
| `0x5231f` | entity_tile_x | high | investigation | Per-runtime-entity map X coordinate. FIELD movement and occupancy logic maintain it; movement providers pair it with entity_tile_y and compare or route from the current tile. |
|
| `0x5231f` | entity_tile_x | high | investigation | Per-runtime-entity map X coordinate. FIELD movement and occupancy logic maintain it; movement providers pair it with entity_tile_y and compare or route from the current tile. |
|
||||||
| `0x52351` | entity_tile_y | high | investigation | Per-runtime-entity map Y coordinate paired with entity_tile_x. Values use the same stage map-space cell keys as STINIT object and enemy coordinates. |
|
| `0x52351` | entity_tile_y | high | investigation | Per-runtime-entity map Y coordinate paired with entity_tile_x. Values use the same stage map-space cell keys as STINIT object and enemy coordinates. |
|
||||||
|
| `0x52383` | entity_condition_levels | high | investigation | Per-runtime-entity current condition levels. ADDILLSUB applies signed level deltas, clamps the minimum to entity_condition_baseline_levels and the maximum to 5, and assigns condition duration. CALCREVISE, FIELD, combat, and UI consumers index the same ILINIT-defined 30-column condition id space. Columns: 1=instant_death, 2=hp_drain, 3=sp_drain, 4=fs_drain, 5=curse, 6=charm, 7=confusion, 8=paralysis, 9=poison, 10=water_flow, 11=fear, 12=reserved, 13=regeneration, 14=exaltation. |
|
||||||
|
| `0x5295f` | entity_condition_remaining_turns | high | investigation | Per-runtime-entity duration counters paired with entity_condition_levels. FIELD decrements positive counters and removes the condition when a counter reaches zero; DRAWENP displays negative values as infinity. RECOVER writes -1 when a recoverable condition retains an equipment/passive baseline and zero when it clears completely. Columns: 1=instant_death, 2=hp_drain, 3=sp_drain, 4=fs_drain, 5=curse, 6=charm, 7=confusion, 8=paralysis, 9=poison, 10=water_flow, 11=fear, 12=reserved, 13=regeneration, 14=exaltation. |
|
||||||
|
| `0x52f3b` | entity_condition_baseline_levels | high | investigation | Per-runtime-entity minimum condition levels supplied by equipment and passive effects. SETCH/SETEN populate the row from item_equipped_status_levels, ADDILLSUB cannot reduce current levels below it, CALCREVISE separates transient current levels from this baseline, and RECOVER restores eligible conditions to it. Columns: 1=instant_death, 2=hp_drain, 3=sp_drain, 4=fs_drain, 5=curse, 6=charm, 7=confusion, 8=paralysis, 9=poison, 10=water_flow, 11=fear, 12=reserved, 13=regeneration, 14=exaltation. |
|
||||||
| `0x53517` | entity_movement_routine_set_ids | high | investigation | Per-runtime-entity movement routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; MVRTN selects the current difficulty column and stores it in current_routine_set_id. |
|
| `0x53517` | entity_movement_routine_set_ids | high | investigation | Per-runtime-entity movement routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; MVRTN selects the current difficulty column and stores it in current_routine_set_id. |
|
||||||
| `0x535ad` | entity_battle_routine_set_ids | high | investigation | Per-runtime-entity battle routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; BTRTN selects the current difficulty column and stores it in current_routine_set_id. |
|
| `0x535ad` | entity_battle_routine_set_ids | high | investigation | Per-runtime-entity battle routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; BTRTN selects the current difficulty column and stores it in current_routine_set_id. |
|
||||||
| `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. |
|
||||||
@@ -140,6 +144,13 @@
|
|||||||
| `0xaa8f2` | skill_proc_chance_percent | high | investigation | Probability for 14 passive skills. CALCDMG compares random-modulo 100 against this value; examples include Re-action 20, Double Action 100, Counter 10, and Resurrection 50. |
|
| `0xaa8f2` | skill_proc_chance_percent | high | investigation | Probability for 14 passive skills. CALCDMG compares random-modulo 100 against this value; examples include Re-action 20, Double Action 100, Counter 10, and Resurrection 50. |
|
||||||
| `0xaaa1e` | skill_battle_animation_id | high | investigation | Populated for 101 combat skills. BTL and CALCDMG place this value in the battle-animation selector before calling BTANINIT; most skills reuse their own id, while related skills deliberately share an animation and passive reactions use ids 801..808. |
|
| `0xaaa1e` | skill_battle_animation_id | high | investigation | Populated for 101 combat skills. BTL and CALCDMG place this value in the battle-animation selector before calling BTANINIT; most skills reuse their own id, while related skills deliberately share an animation and passive reactions use ids 801..808. |
|
||||||
| `0xaab4a` | skill_handler_script_id | high | investigation | SKINIT field for all 131 skills. CHMENU and INFOIT look it up and pass it directly to call-script; packed id 0x31ca resolves to SKMES.BIN, the shared per-skill text/behavior dispatcher. |
|
| `0xaab4a` | skill_handler_script_id | high | investigation | SKINIT field for all 131 skills. CHMENU and INFOIT look it up and pass it directly to call-script; packed id 0x31ca resolves to SKMES.BIN, the shared per-skill text/behavior dispatcher. |
|
||||||
|
| `0xaac78` | condition_effectiveness_element_ids | high | investigation | ILINIT condition metadata. CALCILL uses the selected condition's value as the element-table column for its application-chance effectiveness calculation. |
|
||||||
|
| `0xaac96` | condition_can_affect_bosses | high | investigation | ILINIT policy vector. CALCILL rejects a condition against a boss-class target when this cell is zero; nonzero cells bypass that boss immunity gate. |
|
||||||
|
| `0xaacb4` | condition_cleared_by_recover | high | investigation | ILINIT recovery-policy vector. RECOVER and DISARM use it to identify charm, confusion, paralysis, poison, water-flow, and fear as removable conditions; RECOVER resets each eligible current level to its equipment/passive baseline. |
|
||||||
|
| `0xaacd2` | condition_icon_ids | high | investigation | ILINIT display metadata. DRAWENP and FIELD use these ids to deduplicate active-condition indicators and select the corresponding status-icon atlas entry. |
|
||||||
|
| `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. |
|
||||||
|
| `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. 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. |
|
||||||
| `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. |
|
||||||
@@ -287,10 +298,6 @@
|
|||||||
| `0x665d8` | — | low | auto-shape | array |
|
| `0x665d8` | — | low | auto-shape | array |
|
||||||
| `0x8fb41` | — | low | auto-shape | array |
|
| `0x8fb41` | — | low | auto-shape | array |
|
||||||
| `0xa5eb9` | — | low | auto-shape | array |
|
| `0xa5eb9` | — | low | auto-shape | array |
|
||||||
| `0xaac78` | — | low | auto-shape | array |
|
|
||||||
| `0xaac96` | — | low | auto-shape | array |
|
|
||||||
| `0xaacb4` | — | low | auto-shape | array |
|
|
||||||
| `0xaacd2` | — | low | auto-shape | array |
|
|
||||||
| `0xaba3b` | — | low | auto-shape | array |
|
| `0xaba3b` | — | low | auto-shape | array |
|
||||||
| `0xaba43` | — | low | auto-shape | array |
|
| `0xaba43` | — | low | auto-shape | array |
|
||||||
| `0xaba4b` | — | low | auto-shape | array |
|
| `0xaba4b` | — | low | auto-shape | array |
|
||||||
@@ -399,6 +406,7 @@
|
|||||||
| `0x66715` | current_unit_id | high | investigation | Shared current-unit selector used by character growth and setup scripts. CCINIT keys every class-change rule on this value; CALCCC, ADDEXP, SETEN, SALLY, and related scripts use it to index unit definitions and persistent per-unit state. |
|
| `0x66715` | current_unit_id | high | investigation | Shared current-unit selector used by character growth and setup scripts. CCINIT keys every class-change rule on this value; CALCCC, ADDEXP, SETEN, SALLY, and related scripts use it to index unit definitions and persistent per-unit state. |
|
||||||
| `0x8c877` | current_item_id | high | investigation | Shared item-id argument/selection slot. Item menus and gameplay scripts write a chosen item id, use it to index ITINIT arrays, and dispatch through item_handler_script_id; ITMES compares it against all 287 item ids to select the matching player-facing title and description. |
|
| `0x8c877` | current_item_id | high | investigation | Shared item-id argument/selection slot. Item menus and gameplay scripts write a chosen item id, use it to index ITINIT arrays, and dispatch through item_handler_script_id; ITMES compares it against all 287 item ids to select the matching player-facing title and description. |
|
||||||
| `0xa6e59` | current_skill_id | high | investigation | Shared skill-id argument/selection slot. Skill menus and combat scripts write the chosen skill id and use it to index SKINIT arrays; SKMES compares it against all 131 skill ids to select the matching player-facing title and description. |
|
| `0xa6e59` | current_skill_id | high | investigation | Shared skill-id argument/selection slot. Skill menus and combat scripts write the chosen skill id and use it to index SKINIT arrays; SKMES compares it against all 131 skill ids to select the matching player-facing title and description. |
|
||||||
|
| `0xaac76` | current_condition_id | high | investigation | Shared condition selector consumed by CALCILL, ADDILL, ADDILLSUB, DISARM, FIELD, and related condition handlers. It indexes the 30-column runtime condition rows and ILINIT definition arrays. |
|
||||||
| `0xeff75` | current_routine_set_id | high | investigation | Shared RTINIT row selector. MVRTN loads the current entity's difficulty-selected movement routine set; BTRTN loads its battle routine set. Both then iterate routine_step_index across the selected twenty-slot row. |
|
| `0xeff75` | current_routine_set_id | high | investigation | Shared RTINIT row selector. MVRTN loads the current entity's difficulty-selected movement routine set; BTRTN loads its battle routine set. Both then iterate routine_step_index across the selected twenty-slot row. |
|
||||||
| `0xeff76` | routine_step_index | high | investigation | Shared RTINIT step selector. MVRTN and BTRTN iterate it from zero through nineteen and use it as the column index in every routine bank and matching per-entity runtime row. |
|
| `0xeff76` | routine_step_index | high | investigation | Shared RTINIT step selector. MVRTN and BTRTN iterate it from zero through nineteen and use it as the column index in every routine bank and matching per-entity runtime row. |
|
||||||
| `0x152616` | current_entity_index | med | investigation | Primary current-entity row index (RECOVER-confirmed; purity 0.51, 363 row-index uses). |
|
| `0x152616` | current_entity_index | med | investigation | Primary current-entity row index (RECOVER-confirmed; purity 0.51, 363 row-index uses). |
|
||||||
@@ -459,6 +467,7 @@
|
|||||||
| `0x27e` | unit_class_titles | high | investigation | Persistent per-unit class/title string table. CALCCC writes the selected class_change_title_output into the current unit's cell; character and status presentation scripts read the resulting title. |
|
| `0x27e` | unit_class_titles | high | investigation | Persistent per-unit class/title string table. CALCCC writes the selected class_change_title_output into the current unit's cell; character and status presentation scripts read the resulting title. |
|
||||||
| `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. |
|
||||||
| `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. |
|
||||||
@@ -3702,7 +3711,6 @@
|
|||||||
| `0x1fba` | — | high | auto-shape | item-desc-table |
|
| `0x1fba` | — | high | auto-shape | item-desc-table |
|
||||||
| `0x23a2` | — | high | auto-shape | skill-name-table |
|
| `0x23a2` | — | high | auto-shape | skill-name-table |
|
||||||
| `0x24ce` | — | high | auto-shape | skill-desc-table |
|
| `0x24ce` | — | high | auto-shape | skill-desc-table |
|
||||||
| `0x25fa` | — | med | auto-shape | record-table[stride 5] |
|
|
||||||
| `0x2ba5` | — | med | auto-shape | record-table[stride 6] |
|
| `0x2ba5` | — | med | auto-shape | record-table[stride 6] |
|
||||||
| `0x3239` | — | med | auto-shape | record-table[stride 3] |
|
| `0x3239` | — | med | auto-shape | record-table[stride 3] |
|
||||||
| `0x32f0` | — | med | auto-shape | current-entity-index? |
|
| `0x32f0` | — | med | auto-shape | current-entity-index? |
|
||||||
@@ -3711,9 +3719,6 @@
|
|||||||
| `0x7e54` | — | med | auto-shape | record-table[stride 50] |
|
| `0x7e54` | — | med | auto-shape | record-table[stride 50] |
|
||||||
| `0x341ab` | — | med | auto-shape | record-table[stride 53] |
|
| `0x341ab` | — | med | auto-shape | record-table[stride 53] |
|
||||||
| `0x4e3d7` | — | med | auto-shape | record-table[stride 14] |
|
| `0x4e3d7` | — | med | auto-shape | record-table[stride 14] |
|
||||||
| `0x52383` | — | med | auto-shape | record-table[stride 30] |
|
|
||||||
| `0x5295f` | — | med | auto-shape | record-table[stride 30] |
|
|
||||||
| `0x52f3b` | — | med | auto-shape | record-table[stride 30] |
|
|
||||||
| `0x53a2b` | — | med | auto-shape | record-table[stride 20] |
|
| `0x53a2b` | — | med | auto-shape | record-table[stride 20] |
|
||||||
| `0x53edd` | — | low | auto-shape | index/counter? |
|
| `0x53edd` | — | low | auto-shape | index/counter? |
|
||||||
| `0x53ef5` | — | low | auto-shape | index/counter? |
|
| `0x53ef5` | — | low | auto-shape | index/counter? |
|
||||||
@@ -5030,10 +5035,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] |
|
||||||
| `0xaac76` | — | low | auto-shape | index/counter? |
|
|
||||||
| `0xaacf0` | — | med | auto-shape | record-table[stride 5] |
|
|
||||||
| `0xaad86` | — | med | auto-shape | record-table[stride 55] |
|
|
||||||
| `0xab3f8` | — | med | auto-shape | record-table[stride 15] |
|
|
||||||
| `0xab6fa` | — | med | auto-shape | record-table[stride 11] |
|
| `0xab6fa` | — | med | auto-shape | record-table[stride 11] |
|
||||||
| `0xab7d6` | — | med | auto-shape | record-table[stride 11] |
|
| `0xab7d6` | — | med | auto-shape | record-table[stride 11] |
|
||||||
| `0xab8b2` | — | med | auto-shape | record-table[stride 7] |
|
| `0xab8b2` | — | med | auto-shape | record-table[stride 7] |
|
||||||
|
|||||||
@@ -2806,3 +2806,32 @@ line and optional annotation buffers. Their stable globals are curated in `vm-ma
|
|||||||
structural regressions protect the registry initialization, indirect dispatch, buffer reads, and cleanup.
|
structural regressions protect the registry initialization, indirect dispatch, buffer reads, and cleanup.
|
||||||
|
|
||||||
**Next:** return to reader-proven 2D tables, beginning with RECOVER's 30-wide status/recovery structures.
|
**Next:** return to reader-proven 2D tables, beginning with RECOVER's 30-wide status/recovery structures.
|
||||||
|
|
||||||
|
## Data-semantics sidebar: ILINIT condition matrix and RECOVER ABI (2026-07-23)
|
||||||
|
|
||||||
|
ILINIT is now a dedicated thirteen-record condition-definition schema inside the engine's reserved
|
||||||
|
30-condition by five-level layout. The generated JSON preserves every one of its 228 integer writes and
|
||||||
|
61 authored level-name strings, while adding a nested level view over duration, eleven-stat deltas, and
|
||||||
|
HP/SP/FS deltas. Condition ids 1..11, 13, and 14 are defined; id 12 and ids 15..29 remain explicit reserved
|
||||||
|
slots. Scalar metadata now identifies the effectiveness element, boss-immunity override, RECOVER policy,
|
||||||
|
and display icon for each condition. All raw address/stride/column keys remain beside their canonical
|
||||||
|
semantic joins.
|
||||||
|
|
||||||
|
The runtime side is one coherent four-table ABI. `entity_condition_levels[50][30]` holds current levels,
|
||||||
|
`entity_condition_baseline_levels[50][30]` is the equipment/passive minimum, and
|
||||||
|
`entity_condition_remaining_turns[50][30]` uses -1 for permanent/baseline, zero for inactive, and positive
|
||||||
|
turns for expiring effects. RECOVER first copies max HP/SP/FS from effective-stat columns 11..13 to the
|
||||||
|
three current resources. It then scans all 30 condition ids; for the six ILINIT-policy conditions
|
||||||
|
(charm, confusion, paralysis, poison, water-flow, and fear), it restores the current level to the passive
|
||||||
|
baseline and writes -1 when that baseline remains or zero when fully cleared. The extractor validates
|
||||||
|
those table bases, strides, loop bounds, and post-recovery CALCREVISE/DRAWCHP calls before publishing the
|
||||||
|
join.
|
||||||
|
|
||||||
|
Validation covers every ILINIT write, representative stat/resource matrices, the complete reserved id
|
||||||
|
space, level-aware semantic projection, the resource restore mapping, and the four-table reset protocol.
|
||||||
|
The global registry and generated reference now carry the condition id columns and all definition/runtime
|
||||||
|
table meanings.
|
||||||
|
|
||||||
|
**Next:** profile CNINIT as the next unresolved high-density name-mode table. Its current generic output
|
||||||
|
has 32 records but 242 address-derived fields, making it the clearest candidate for another dedicated
|
||||||
|
schema driven by reader-proven row/column ownership.
|
||||||
|
|||||||
@@ -57,12 +57,16 @@ 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, and RTINIT banked extraction; 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, and ILINIT condition-matrix 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,
|
||||||
CIINIT's 24 populated character-information profiles within four reserved 100-cell columns, and MAINIT's
|
CIINIT's 24 populated character-information profiles within four reserved 100-cell columns, MAINIT's
|
||||||
eleven populated magic-action rows within a reserved 30-cell layout. Matching message joins are
|
eleven populated magic-action rows within a reserved 30-cell layout, and ILINIT's thirteen populated
|
||||||
|
condition ids within a reserved 30-by-5 level layout. ILINIT classifies every authored write, adds nested
|
||||||
|
names/durations/stat/resource deltas, and validates/joins RECOVER's current-level, passive-baseline,
|
||||||
|
remaining-turn, recovery-policy, and full-resource-restore protocol. The profiler accepts the same
|
||||||
|
`base/stride/column` provenance for row-major string fields as for integer record columns. Matching message joins are
|
||||||
ITINIT→ITMES, SKINIT→SKMES, VIINIT→VIMES, EBINIT→EIMES, CIINIT→CIMES, and MAINIT→MAMES. EBINIT is
|
ITINIT→ITMES, SKINIT→SKMES, VIINIT→VIMES, EBINIT→EIMES, CIINIT→CIMES, and MAINIT→MAMES. EBINIT is
|
||||||
intentionally sparse across unrelated unit definitions; MAINIT intentionally lacks MAMES bodies for growth
|
intentionally sparse across unrelated unit definitions; MAINIT intentionally lacks MAMES bodies for growth
|
||||||
ritual ids 10 and 11.
|
ritual ids 10 and 11.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Extract a *INIT data table to JSON. Auto-detects the table's shape.
|
"""Extract a *INIT data table to JSON. Auto-detects the table's shape.
|
||||||
|
|
||||||
*INIT scripts populate global arrays and work buffers with static game data. Seven shapes seen:
|
*INIT scripts populate global arrays and work buffers with static game data. Eight shapes seen:
|
||||||
|
|
||||||
name — records keyed by a name string. Each record: set-string(name), static field writes,
|
name — records keyed by a name string. Each record: set-string(name), static field writes,
|
||||||
set-string(desc). Arrays indexed by record id in lockstep (+1/record).
|
set-string(desc). Arrays indexed by record id in lockstep (+1/record).
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
banked —twenty parallel 1000-by-20 banks define sparse movement and battle routine
|
banked —twenty parallel 1000-by-20 banks define sparse movement and battle routine
|
||||||
step records, including provider joins and source overwrites. (RTINIT routines)
|
step records, including provider joins and source overwrites. (RTINIT routines)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
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.
|
||||||
@@ -529,6 +532,57 @@ VOCABULARY_RECORD_TABLE_BASE = 0x15A2A9
|
|||||||
VOCABULARY_RECORD_STRIDE = 3
|
VOCABULARY_RECORD_STRIDE = 3
|
||||||
VOCABULARY_RECORD_SPAN = 200
|
VOCABULARY_RECORD_SPAN = 200
|
||||||
|
|
||||||
|
CONDITION_RECORD_SPAN = 30
|
||||||
|
CONDITION_LEVEL_COUNT = 5
|
||||||
|
CONDITION_LEVEL_NAME_BASE = 0x25FA
|
||||||
|
CONDITION_COLUMNS = {
|
||||||
|
1: "instant_death",
|
||||||
|
2: "hp_drain",
|
||||||
|
3: "sp_drain",
|
||||||
|
4: "fs_drain",
|
||||||
|
5: "curse",
|
||||||
|
6: "charm",
|
||||||
|
7: "confusion",
|
||||||
|
8: "paralysis",
|
||||||
|
9: "poison",
|
||||||
|
10: "water_flow",
|
||||||
|
11: "fear",
|
||||||
|
12: "reserved",
|
||||||
|
13: "regeneration",
|
||||||
|
14: "exaltation",
|
||||||
|
}
|
||||||
|
CONDITION_SCALAR_ARRAYS = {
|
||||||
|
0xAAC78: "effectiveness_element_id",
|
||||||
|
0xAAC96: "can_affect_bosses",
|
||||||
|
0xAACB4: "cleared_by_recover",
|
||||||
|
0xAACD2: "icon_id",
|
||||||
|
}
|
||||||
|
CONDITION_DURATION_BASE = 0xAACF0
|
||||||
|
CONDITION_STAT_DELTA_BASE = 0xAAD86
|
||||||
|
CONDITION_RESOURCE_DELTA_BASE = 0xAB3F8
|
||||||
|
CONDITION_STAT_COLUMNS = (
|
||||||
|
"accuracy",
|
||||||
|
"evasion",
|
||||||
|
"physical_attack",
|
||||||
|
"physical_defense",
|
||||||
|
"magic_attack",
|
||||||
|
"magic_defense",
|
||||||
|
"speed",
|
||||||
|
"luck",
|
||||||
|
"critical_chance",
|
||||||
|
"capture_power",
|
||||||
|
"movement",
|
||||||
|
)
|
||||||
|
CONDITION_RESOURCE_COLUMNS = ("hp", "sp", "fs")
|
||||||
|
|
||||||
|
RECOVER_CURRENT_ENTITY = 0x152616
|
||||||
|
RECOVER_EFFECTIVE_STATS = 0x4E11B
|
||||||
|
RECOVER_CURRENT_RESOURCES = 0x4E085
|
||||||
|
RECOVER_CURRENT_LEVELS = 0x52383
|
||||||
|
RECOVER_REMAINING_TURNS = 0x5295F
|
||||||
|
RECOVER_BASELINE_LEVELS = 0x52F3B
|
||||||
|
RECOVER_POLICY = 0xAACB4
|
||||||
|
|
||||||
|
|
||||||
def resolve(name: str) -> Path:
|
def resolve(name: str) -> Path:
|
||||||
for cand in (paths.GAME_DIR / f"{name}.BIN", paths.DATA1 / f"{name}.BIN"):
|
for cand in (paths.GAME_DIR / f"{name}.BIN", paths.DATA1 / f"{name}.BIN"):
|
||||||
@@ -1199,6 +1253,280 @@ def extract_vocabulary(scr):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_recovery_protocol(scr) -> dict:
|
||||||
|
"""Validate and describe RECOVER's resource/condition reset ABI."""
|
||||||
|
lookups_2d = {
|
||||||
|
(ins.args[1][1], ins.args[3][1])
|
||||||
|
for ins in scr.instructions
|
||||||
|
if (
|
||||||
|
sys4load.display_label(ins.opcode) == "lookup-array-2d"
|
||||||
|
and len(ins.args) >= 5
|
||||||
|
and ins.args[1][0] == T_GLOBAL_INT
|
||||||
|
and ins.args[3][0] == T_IMM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
lookups_1d = {
|
||||||
|
ins.args[1][1]
|
||||||
|
for ins in scr.instructions
|
||||||
|
if (
|
||||||
|
sys4load.display_label(ins.opcode) == "lookup-array"
|
||||||
|
and len(ins.args) >= 3
|
||||||
|
and ins.args[1][0] == T_GLOBAL_INT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loop_bounds = {
|
||||||
|
ins.args[2][1]
|
||||||
|
for ins in scr.instructions
|
||||||
|
if (
|
||||||
|
sys4load.display_label(ins.opcode) == "lt"
|
||||||
|
and len(ins.args) >= 3
|
||||||
|
and ins.args[2][0] == T_IMM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
calls = {
|
||||||
|
ins.args[0][1]
|
||||||
|
for ins in scr.instructions
|
||||||
|
if sys4load.display_label(ins.opcode) == "call-script" and ins.args
|
||||||
|
}
|
||||||
|
required_2d = {
|
||||||
|
(RECOVER_EFFECTIVE_STATS, 14),
|
||||||
|
(RECOVER_CURRENT_RESOURCES, 3),
|
||||||
|
(RECOVER_CURRENT_LEVELS, CONDITION_RECORD_SPAN),
|
||||||
|
(RECOVER_REMAINING_TURNS, CONDITION_RECORD_SPAN),
|
||||||
|
(RECOVER_BASELINE_LEVELS, CONDITION_RECORD_SPAN),
|
||||||
|
}
|
||||||
|
failures = []
|
||||||
|
if not required_2d <= lookups_2d:
|
||||||
|
failures.append(f"missing 2d lookups {sorted(required_2d - lookups_2d)}")
|
||||||
|
if RECOVER_POLICY not in lookups_1d:
|
||||||
|
failures.append("missing recovery-policy lookup")
|
||||||
|
if not {3, CONDITION_RECORD_SPAN} <= loop_bounds:
|
||||||
|
failures.append("missing resource or condition loop bound")
|
||||||
|
if not {0x329D, 0x2ADE} <= calls:
|
||||||
|
failures.append("missing CALCREVISE or DRAWCHP post-call")
|
||||||
|
if failures:
|
||||||
|
raise ValueError(f"{scr.path.name}: " + "; ".join(failures))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"source": scr.path.name,
|
||||||
|
"current_entity_selector": f"0x{RECOVER_CURRENT_ENTITY:x}",
|
||||||
|
"resource_restore": {
|
||||||
|
"source_table": f"0x{RECOVER_EFFECTIVE_STATS:x}",
|
||||||
|
"source_columns": ["max_hp", "max_sp", "max_fs"],
|
||||||
|
"destination_table": f"0x{RECOVER_CURRENT_RESOURCES:x}",
|
||||||
|
"destination_columns": ["current_hp", "current_sp", "current_fs"],
|
||||||
|
},
|
||||||
|
"condition_reset": {
|
||||||
|
"column_count": CONDITION_RECORD_SPAN,
|
||||||
|
"current_level_table": f"0x{RECOVER_CURRENT_LEVELS:x}",
|
||||||
|
"baseline_level_table": f"0x{RECOVER_BASELINE_LEVELS:x}",
|
||||||
|
"remaining_turns_table": f"0x{RECOVER_REMAINING_TURNS:x}",
|
||||||
|
"recovery_policy_table": f"0x{RECOVER_POLICY:x}",
|
||||||
|
"policy": (
|
||||||
|
"For each active condition whose policy cell is nonzero, copy "
|
||||||
|
"the equipment/passive baseline into the current level and set "
|
||||||
|
"remaining turns to -1 when that baseline is nonzero, otherwise 0."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"post_recovery_scripts": ["CALCREVISE.BIN", "DRAWCHP.BIN"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _condition_family_name(level_names: dict[int, str]) -> str | None:
|
||||||
|
"""Collapse authored `name1`..`name5` strings to their shared family."""
|
||||||
|
if not level_names:
|
||||||
|
return None
|
||||||
|
ordered = [level_names[level] for level in sorted(level_names)]
|
||||||
|
prefixes = [
|
||||||
|
text[:-1]
|
||||||
|
for level, text in sorted(level_names.items())
|
||||||
|
if text.endswith(str(level))
|
||||||
|
]
|
||||||
|
if len(prefixes) == len(ordered) and len(set(prefixes)) == 1:
|
||||||
|
return prefixes[0]
|
||||||
|
return ordered[0]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_condition_definitions(scr):
|
||||||
|
"""Extract ILINIT's sparse 30-condition, five-level definition matrix."""
|
||||||
|
level_names: dict[int, dict[int, str]] = collections.defaultdict(dict)
|
||||||
|
records_by_id: dict[int, dict] = {}
|
||||||
|
classified_writes = 0
|
||||||
|
static_write_count = 0
|
||||||
|
|
||||||
|
def record_for(condition_id: int) -> dict:
|
||||||
|
if not (1 <= condition_id < CONDITION_RECORD_SPAN):
|
||||||
|
raise ValueError(
|
||||||
|
f"{scr.path.name}: condition id outside reserved span: {condition_id}"
|
||||||
|
)
|
||||||
|
return records_by_id.setdefault(condition_id, {
|
||||||
|
"id": condition_id,
|
||||||
|
"condition": CONDITION_COLUMNS.get(condition_id, f"reserved_{condition_id}"),
|
||||||
|
"string_fields": {},
|
||||||
|
"fields": {},
|
||||||
|
"record_fields": {},
|
||||||
|
})
|
||||||
|
|
||||||
|
for ins in scr.instructions:
|
||||||
|
if (
|
||||||
|
ins.opcode == SET_STRING
|
||||||
|
and len(ins.args) >= 2
|
||||||
|
and ins.args[0][0] == T_GLOBAL_STRING
|
||||||
|
):
|
||||||
|
relative = ins.args[0][1] - CONDITION_LEVEL_NAME_BASE
|
||||||
|
condition_id, level_index = divmod(relative, CONDITION_LEVEL_COUNT)
|
||||||
|
if not (
|
||||||
|
1 <= condition_id < CONDITION_RECORD_SPAN
|
||||||
|
and 0 <= level_index < CONDITION_LEVEL_COUNT
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"{scr.path.name}: unexpected condition name destination "
|
||||||
|
f"0x{ins.args[0][1]:x}"
|
||||||
|
)
|
||||||
|
text = scr.strings.get(ins.args[1][1], (None,))[0]
|
||||||
|
level_names[condition_id][level_index + 1] = text
|
||||||
|
_store_unique(
|
||||||
|
record_for(condition_id)["string_fields"],
|
||||||
|
(
|
||||||
|
f"0x{CONDITION_LEVEL_NAME_BASE:x}/"
|
||||||
|
f"{CONDITION_LEVEL_COUNT}/{level_index}"
|
||||||
|
),
|
||||||
|
text,
|
||||||
|
condition_id,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
write = _static_global_write(ins)
|
||||||
|
if write is None:
|
||||||
|
continue
|
||||||
|
static_write_count += 1
|
||||||
|
destination, value = write
|
||||||
|
matched = False
|
||||||
|
for base in CONDITION_SCALAR_ARRAYS:
|
||||||
|
condition_id = destination - base
|
||||||
|
if 1 <= condition_id < CONDITION_RECORD_SPAN:
|
||||||
|
_store_unique(
|
||||||
|
record_for(condition_id)["fields"],
|
||||||
|
f"0x{base:x}",
|
||||||
|
value,
|
||||||
|
condition_id,
|
||||||
|
)
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
if not matched:
|
||||||
|
layouts = (
|
||||||
|
(CONDITION_DURATION_BASE, CONDITION_LEVEL_COUNT),
|
||||||
|
(
|
||||||
|
CONDITION_STAT_DELTA_BASE,
|
||||||
|
CONDITION_LEVEL_COUNT * len(CONDITION_STAT_COLUMNS),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
CONDITION_RESOURCE_DELTA_BASE,
|
||||||
|
CONDITION_LEVEL_COUNT * len(CONDITION_RESOURCE_COLUMNS),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for base, stride in layouts:
|
||||||
|
relative = destination - base
|
||||||
|
condition_id, column = divmod(relative, stride)
|
||||||
|
if 1 <= condition_id < CONDITION_RECORD_SPAN:
|
||||||
|
_store_unique(
|
||||||
|
record_for(condition_id)["record_fields"],
|
||||||
|
f"0x{base:x}/{stride}/{column}",
|
||||||
|
value,
|
||||||
|
condition_id,
|
||||||
|
)
|
||||||
|
matched = True
|
||||||
|
break
|
||||||
|
if not matched:
|
||||||
|
raise ValueError(
|
||||||
|
f"{scr.path.name}: unclassified static write 0x{destination:x}"
|
||||||
|
)
|
||||||
|
classified_writes += 1
|
||||||
|
|
||||||
|
records = []
|
||||||
|
for condition_id in sorted(records_by_id):
|
||||||
|
record = records_by_id[condition_id]
|
||||||
|
names = level_names.get(condition_id, {})
|
||||||
|
record["name"] = _condition_family_name(names)
|
||||||
|
record["level_names"] = [
|
||||||
|
names.get(level) for level in range(1, CONDITION_LEVEL_COUNT + 1)
|
||||||
|
]
|
||||||
|
levels = []
|
||||||
|
for level in range(1, CONDITION_LEVEL_COUNT + 1):
|
||||||
|
level_record = {"level": level}
|
||||||
|
if level in names:
|
||||||
|
level_record["name"] = names[level]
|
||||||
|
|
||||||
|
duration_key = (
|
||||||
|
f"0x{CONDITION_DURATION_BASE:x}/{CONDITION_LEVEL_COUNT}/{level - 1}"
|
||||||
|
)
|
||||||
|
if duration_key in record["record_fields"]:
|
||||||
|
level_record["duration_turns"] = record["record_fields"][duration_key]
|
||||||
|
|
||||||
|
stat_deltas = {}
|
||||||
|
for stat_index, stat_name in enumerate(CONDITION_STAT_COLUMNS):
|
||||||
|
column = (level - 1) * len(CONDITION_STAT_COLUMNS) + stat_index
|
||||||
|
key = (
|
||||||
|
f"0x{CONDITION_STAT_DELTA_BASE:x}/"
|
||||||
|
f"{CONDITION_LEVEL_COUNT * len(CONDITION_STAT_COLUMNS)}/{column}"
|
||||||
|
)
|
||||||
|
if key in record["record_fields"]:
|
||||||
|
stat_deltas[stat_name] = record["record_fields"][key]
|
||||||
|
if stat_deltas:
|
||||||
|
level_record["stat_deltas"] = stat_deltas
|
||||||
|
|
||||||
|
resource_deltas = {}
|
||||||
|
for resource_index, resource_name in enumerate(CONDITION_RESOURCE_COLUMNS):
|
||||||
|
column = (level - 1) * len(CONDITION_RESOURCE_COLUMNS) + resource_index
|
||||||
|
key = (
|
||||||
|
f"0x{CONDITION_RESOURCE_DELTA_BASE:x}/"
|
||||||
|
f"{CONDITION_LEVEL_COUNT * len(CONDITION_RESOURCE_COLUMNS)}/{column}"
|
||||||
|
)
|
||||||
|
if key in record["record_fields"]:
|
||||||
|
resource_deltas[resource_name] = record["record_fields"][key]
|
||||||
|
if resource_deltas:
|
||||||
|
level_record["resource_deltas"] = resource_deltas
|
||||||
|
|
||||||
|
if len(level_record) > 1:
|
||||||
|
levels.append(level_record)
|
||||||
|
record["levels"] = levels
|
||||||
|
records.append(record)
|
||||||
|
|
||||||
|
defined_ids = [record["id"] for record in records]
|
||||||
|
record_columns = sorted(
|
||||||
|
{
|
||||||
|
key
|
||||||
|
for record in records
|
||||||
|
for key in record.get("record_fields", {})
|
||||||
|
},
|
||||||
|
key=lambda key: tuple(int(part, 0) for part in key.split("/")),
|
||||||
|
)
|
||||||
|
return records, {
|
||||||
|
"schema": "condition-definitions",
|
||||||
|
"record_span": CONDITION_RECORD_SPAN,
|
||||||
|
"level_count": CONDITION_LEVEL_COUNT,
|
||||||
|
"condition_columns": {
|
||||||
|
str(index): name for index, name in CONDITION_COLUMNS.items()
|
||||||
|
},
|
||||||
|
"defined_condition_ids": defined_ids,
|
||||||
|
"reserved_condition_ids": [
|
||||||
|
condition_id
|
||||||
|
for condition_id in range(1, CONDITION_RECORD_SPAN)
|
||||||
|
if condition_id not in defined_ids
|
||||||
|
],
|
||||||
|
"level_name_table": {
|
||||||
|
"base": f"0x{CONDITION_LEVEL_NAME_BASE:x}",
|
||||||
|
"stride": CONDITION_LEVEL_COUNT,
|
||||||
|
},
|
||||||
|
"record_field_columns": record_columns,
|
||||||
|
"static_write_count": static_write_count,
|
||||||
|
"classified_static_write_count": classified_writes,
|
||||||
|
"recovery_protocol": extract_recovery_protocol(
|
||||||
|
sys4load.load(resolve("RECOVER"))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def extract_character_profiles(scr):
|
def extract_character_profiles(scr):
|
||||||
"""Extract CIINIT's profile-id keyed character-information registry."""
|
"""Extract CIINIT's profile-id keyed character-information registry."""
|
||||||
records = []
|
records = []
|
||||||
@@ -2094,6 +2422,12 @@ def write_data_index(data_dir: Path) -> None:
|
|||||||
"name-keyed convenience view. Complete footer copies expose their row values there while",
|
"name-keyed convenience view. Complete footer copies expose their row values there while",
|
||||||
"raw keys and footer metadata remain intact as bytecode provenance.",
|
"raw keys and footer metadata remain intact as bytecode provenance.",
|
||||||
"",
|
"",
|
||||||
|
"ILINIT's dedicated condition schema exposes thirteen authored condition ids in the",
|
||||||
|
"reserved 30-by-5 layout. Each record keeps its raw scalar and row-table cells while",
|
||||||
|
"joining level names, durations, eleven-stat deltas, three-resource deltas, boss/recovery",
|
||||||
|
"policies, and icon ids. Top-level `recovery_protocol` validates RECOVER.BIN and links",
|
||||||
|
"current levels, equipment/passive baselines, remaining turns, and full resource restore.",
|
||||||
|
"",
|
||||||
"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`",
|
||||||
@@ -2165,6 +2499,8 @@ def main() -> int:
|
|||||||
extractor = extract_character_profiles
|
extractor = extract_character_profiles
|
||||||
elif mode == "name" and name == "MAINIT":
|
elif mode == "name" and name == "MAINIT":
|
||||||
extractor = extract_magic_actions
|
extractor = extract_magic_actions
|
||||||
|
elif mode == "name" and name == "ILINIT":
|
||||||
|
extractor = extract_condition_definitions
|
||||||
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]
|
||||||
|
|||||||
@@ -107,11 +107,22 @@ def profile_columns(data: dict) -> list[dict]:
|
|||||||
"semantic_name": field_semantics.get(key),
|
"semantic_name": field_semantics.get(key),
|
||||||
}, value, record)
|
}, value, record)
|
||||||
for address, value in record.get("string_fields", {}).items():
|
for address, value in record.get("string_fields", {}).items():
|
||||||
base = int(address, 16)
|
parts = address.split("/")
|
||||||
|
base = int(parts[0], 16)
|
||||||
|
if len(parts) == 1:
|
||||||
|
stride = column = None
|
||||||
key = f"0x{base:x}"
|
key = f"0x{base:x}"
|
||||||
|
kind = "string-field"
|
||||||
|
elif len(parts) == 3:
|
||||||
|
stride = int(parts[1])
|
||||||
|
column = int(parts[2])
|
||||||
|
key = f"0x{base:x}/{stride}/{column}"
|
||||||
|
kind = "string-record-column"
|
||||||
|
else:
|
||||||
|
raise ValueError(f"bad string-field key: {address}")
|
||||||
add(key, {
|
add(key, {
|
||||||
"key": key, "kind": "string-field", "base": key,
|
"key": key, "kind": kind, "base": f"0x{base:x}",
|
||||||
"stride": None, "column": None,
|
"stride": stride, "column": column,
|
||||||
"semantic_name": field_semantics.get(key),
|
"semantic_name": field_semantics.get(key),
|
||||||
}, value, record)
|
}, value, record)
|
||||||
for key, value in record.get("record_fields", {}).items():
|
for key, value in record.get("record_fields", {}).items():
|
||||||
|
|||||||
@@ -911,6 +911,97 @@ def test_message_join() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_condition_definitions() -> None:
|
||||||
|
scripts = paths.scripts()
|
||||||
|
script = sys4load.load(scripts["ILINIT.BIN"])
|
||||||
|
check(
|
||||||
|
extract_init.detect_mode(script) == "name",
|
||||||
|
"ILINIT remains compatible with name-mode auto-detection",
|
||||||
|
)
|
||||||
|
records, meta = extract_init.extract_condition_definitions(script)
|
||||||
|
by_id = {record["id"]: record for record in records}
|
||||||
|
check(
|
||||||
|
list(by_id) == [*range(1, 12), 13, 14],
|
||||||
|
"ILINIT extracts all thirteen authored condition ids",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
meta["record_span"] == 30
|
||||||
|
and meta["level_count"] == 5
|
||||||
|
and meta["reserved_condition_ids"] == [12, *range(15, 30)],
|
||||||
|
"ILINIT exposes its complete 30-by-5 reserved layout",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
meta["static_write_count"] == 228
|
||||||
|
and meta["classified_static_write_count"] == 228,
|
||||||
|
"ILINIT classifies every static integer write",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
by_id[1]["name"] == "即死"
|
||||||
|
and by_id[2]["name"] == "HP吸"
|
||||||
|
and by_id[14]["condition"] == "exaltation",
|
||||||
|
"condition records preserve authored names and canonical ids",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
by_id[2]["string_fields"]["0x25fa/5/0"] == "HP吸1"
|
||||||
|
and by_id[2]["string_fields"]["0x25fa/5/4"] == "HP吸5",
|
||||||
|
"condition level names retain raw base/stride/column provenance",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
[level["duration_turns"] for level in by_id[6]["levels"]]
|
||||||
|
== [2, 3, 4, 5, 6]
|
||||||
|
and by_id[6]["fields"]["0xaacb4"] == 1
|
||||||
|
and by_id[6]["fields"]["0xaacd2"] == 10,
|
||||||
|
"charm exposes level durations, RECOVER policy, and icon id",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
by_id[5]["levels"][4]["stat_deltas"]["accuracy"] == -25
|
||||||
|
and by_id[14]["levels"][4]["stat_deltas"]["physical_attack"] == 10
|
||||||
|
and by_id[14]["levels"][4]["stat_deltas"]["physical_defense"] == -10,
|
||||||
|
"curse and exaltation expose their five-level stat matrices",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
by_id[9]["levels"][4]["resource_deltas"]["hp"] == -5
|
||||||
|
and by_id[13]["levels"][4]["resource_deltas"]["hp"] == 5,
|
||||||
|
"poison and regeneration expose opposing periodic HP deltas",
|
||||||
|
)
|
||||||
|
|
||||||
|
recovery = meta["recovery_protocol"]
|
||||||
|
check(
|
||||||
|
recovery["resource_restore"]["source_columns"]
|
||||||
|
== ["max_hp", "max_sp", "max_fs"]
|
||||||
|
and recovery["resource_restore"]["destination_columns"]
|
||||||
|
== ["current_hp", "current_sp", "current_fs"],
|
||||||
|
"RECOVER joins max-stat columns to current HP/SP/FS",
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
recovery["condition_reset"]["current_level_table"] == "0x52383"
|
||||||
|
and recovery["condition_reset"]["baseline_level_table"] == "0x52f3b"
|
||||||
|
and recovery["condition_reset"]["remaining_turns_table"] == "0x5295f"
|
||||||
|
and recovery["condition_reset"]["recovery_policy_table"] == "0xaacb4",
|
||||||
|
"RECOVER publishes the four-table condition reset protocol",
|
||||||
|
)
|
||||||
|
|
||||||
|
semantics = extract_init.field_semantics(records)
|
||||||
|
check(
|
||||||
|
semantics["0xaacf0/5/4"]
|
||||||
|
== "condition_duration_turns_by_level.level_5_turns"
|
||||||
|
and semantics["0x25fa/5/4"] == "condition_level_names.level_5"
|
||||||
|
and semantics["0xaad86/55/46"]
|
||||||
|
== "condition_stat_deltas.level_5_physical_attack"
|
||||||
|
and semantics["0xab3f8/15/12"]
|
||||||
|
== "condition_resource_deltas.level_5_hp",
|
||||||
|
"ILINIT raw cells join to level-aware condition semantics",
|
||||||
|
)
|
||||||
|
extract_init.attach_semantic_fields(records, semantics)
|
||||||
|
check(
|
||||||
|
by_id[6]["semantic_fields"]["condition_cleared_by_recover"] == 1
|
||||||
|
and by_id[14]["semantic_fields"][
|
||||||
|
"condition_stat_deltas.level_5_physical_attack"
|
||||||
|
] == 10,
|
||||||
|
"condition definitions retain raw addresses beside the semantic view",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_field_semantics() -> None:
|
def test_field_semantics() -> None:
|
||||||
scripts = paths.scripts()
|
scripts = paths.scripts()
|
||||||
items, _ = extract_init.extract_name(sys4load.load(scripts["ITINIT.BIN"]))
|
items, _ = extract_init.extract_name(sys4load.load(scripts["ITINIT.BIN"]))
|
||||||
@@ -1028,6 +1119,7 @@ if __name__ == "__main__":
|
|||||||
test_real_message_tables()
|
test_real_message_tables()
|
||||||
test_message_infrastructure()
|
test_message_infrastructure()
|
||||||
test_message_join()
|
test_message_join()
|
||||||
|
test_condition_definitions()
|
||||||
test_field_semantics()
|
test_field_semantics()
|
||||||
if FAILS:
|
if FAILS:
|
||||||
raise SystemExit(f"{len(FAILS)} failed checks")
|
raise SystemExit(f"{len(FAILS)} failed checks")
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ def main() -> int:
|
|||||||
"field_semantics": {
|
"field_semantics": {
|
||||||
"0x10": "test_parallel",
|
"0x10": "test_parallel",
|
||||||
"0x30/3/0": "test_record.zero",
|
"0x30/3/0": "test_record.zero",
|
||||||
|
"0x40/5/4": "test_names.level_5",
|
||||||
},
|
},
|
||||||
"records": [
|
"records": [
|
||||||
{"id": 1, "name": "one", "fields": {"0x10": 2, "0x20": 0},
|
{"id": 1, "name": "one", "fields": {"0x10": 2, "0x20": 0},
|
||||||
|
"string_fields": {"0x40/5/4": "One5"},
|
||||||
"record_fields": {"0x30/3/0": 9},
|
"record_fields": {"0x30/3/0": 9},
|
||||||
"message": {"title": "One", "description": "First",
|
"message": {"title": "One", "description": "First",
|
||||||
"furigana": [{"line": 0, "text": "One", "reading": "one"}]}},
|
"furigana": [{"line": 0, "text": "One", "reading": "one"}]}},
|
||||||
@@ -41,6 +43,10 @@ def main() -> int:
|
|||||||
assert rows["0x30/3/0"]["stride"] == 3
|
assert rows["0x30/3/0"]["stride"] == 3
|
||||||
assert rows["0x30/3/0"]["semantic_name"] == "test_record.zero"
|
assert rows["0x30/3/0"]["semantic_name"] == "test_record.zero"
|
||||||
assert rows["0x30/3/2"]["column"] == 2
|
assert rows["0x30/3/2"]["column"] == 2
|
||||||
|
assert rows["0x40/5/4"]["kind"] == "string-record-column"
|
||||||
|
assert rows["0x40/5/4"]["stride"] == 5
|
||||||
|
assert rows["0x40/5/4"]["column"] == 4
|
||||||
|
assert rows["0x40/5/4"]["semantic_name"] == "test_names.level_5"
|
||||||
|
|
||||||
mixed_fixture = {
|
mixed_fixture = {
|
||||||
"table": "MIXED",
|
"table": "MIXED",
|
||||||
|
|||||||
@@ -1364,6 +1364,156 @@ source = "investigation"
|
|||||||
confidence = "high"
|
confidence = "high"
|
||||||
depends_on = ["0x5231f"]
|
depends_on = ["0x5231f"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0x25fa"
|
||||||
|
name = "condition_level_names"
|
||||||
|
category = "string-table"
|
||||||
|
type = "string[30][5]"
|
||||||
|
columns = { "0" = "level_1", "1" = "level_2", "2" = "level_3", "3" = "level_4", "4" = "level_5" }
|
||||||
|
value_domain = "ILINIT-authored display strings for condition ids 1..11, 13, and 14"
|
||||||
|
usage = "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."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0x52383"
|
||||||
|
name = "entity_condition_levels"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[50][30]"
|
||||||
|
columns = { "1" = "instant_death", "2" = "hp_drain", "3" = "sp_drain", "4" = "fs_drain", "5" = "curse", "6" = "charm", "7" = "confusion", "8" = "paralysis", "9" = "poison", "10" = "water_flow", "11" = "fear", "12" = "reserved", "13" = "regeneration", "14" = "exaltation" }
|
||||||
|
value_domain = "condition level 0..5; zero means inactive"
|
||||||
|
usage = "Per-runtime-entity current condition levels. ADDILLSUB applies signed level deltas, clamps the minimum to entity_condition_baseline_levels and the maximum to 5, and assigns condition duration. CALCREVISE, FIELD, combat, and UI consumers index the same ILINIT-defined 30-column condition id space."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52f3b", "0x5295f", "0xaacf0"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0x5295f"
|
||||||
|
name = "entity_condition_remaining_turns"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[50][30]"
|
||||||
|
columns = { "1" = "instant_death", "2" = "hp_drain", "3" = "sp_drain", "4" = "fs_drain", "5" = "curse", "6" = "charm", "7" = "confusion", "8" = "paralysis", "9" = "poison", "10" = "water_flow", "11" = "fear", "12" = "reserved", "13" = "regeneration", "14" = "exaltation" }
|
||||||
|
value_domain = "-1 permanent/baseline, 0 inactive, positive remaining turns"
|
||||||
|
usage = "Per-runtime-entity duration counters paired with entity_condition_levels. FIELD decrements positive counters and removes the condition when a counter reaches zero; DRAWENP displays negative values as infinity. RECOVER writes -1 when a recoverable condition retains an equipment/passive baseline and zero when it clears completely."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383", "0x52f3b"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0x52f3b"
|
||||||
|
name = "entity_condition_baseline_levels"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[50][30]"
|
||||||
|
columns = { "1" = "instant_death", "2" = "hp_drain", "3" = "sp_drain", "4" = "fs_drain", "5" = "curse", "6" = "charm", "7" = "confusion", "8" = "paralysis", "9" = "poison", "10" = "water_flow", "11" = "fear", "12" = "reserved", "13" = "regeneration", "14" = "exaltation" }
|
||||||
|
value_domain = "equipment/passive condition level 0..5"
|
||||||
|
usage = "Per-runtime-entity minimum condition levels supplied by equipment and passive effects. SETCH/SETEN populate the row from item_equipped_status_levels, ADDILLSUB cannot reduce current levels below it, CALCREVISE separates transient current levels from this baseline, and RECOVER restores eligible conditions to it."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383", "0x97c29"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaac76"
|
||||||
|
name = "current_condition_id"
|
||||||
|
category = "index-pointer"
|
||||||
|
type = "int"
|
||||||
|
value_domain = "condition id 1..14 in shipped scripts"
|
||||||
|
usage = "Shared condition selector consumed by CALCILL, ADDILL, ADDILLSUB, DISARM, FIELD, and related condition handlers. It indexes the 30-column runtime condition rows and ILINIT definition arrays."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaac77"
|
||||||
|
name = "current_condition_delta_levels"
|
||||||
|
category = "counter"
|
||||||
|
type = "int"
|
||||||
|
value_domain = "signed condition-level adjustment, commonly -5..5"
|
||||||
|
usage = "Shared signed level delta passed to ADDILL/ADDILLSUB for current_condition_id. Positive values apply or strengthen a condition; negative values weaken/remove it subject to the equipment/passive baseline floor."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0xaac76", "0x52383", "0x52f3b"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaac78"
|
||||||
|
name = "condition_effectiveness_element_ids"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30]"
|
||||||
|
value_domain = "element id 11..17 for shipped condition definitions"
|
||||||
|
usage = "ILINIT condition metadata. CALCILL uses the selected condition's value as the element-table column for its application-chance effectiveness calculation."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0xaac76"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaac96"
|
||||||
|
name = "condition_can_affect_bosses"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30]"
|
||||||
|
value_domain = "boolean; nonzero for HP/SP/FS drain, regeneration, and exaltation"
|
||||||
|
usage = "ILINIT policy vector. CALCILL rejects a condition against a boss-class target when this cell is zero; nonzero cells bypass that boss immunity gate."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0xaac76"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaacb4"
|
||||||
|
name = "condition_cleared_by_recover"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30]"
|
||||||
|
value_domain = "boolean; shipped nonzero ids are 6..11"
|
||||||
|
usage = "ILINIT recovery-policy vector. RECOVER and DISARM use it to identify charm, confusion, paralysis, poison, water-flow, and fear as removable conditions; RECOVER resets each eligible current level to its equipment/passive baseline."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383", "0x52f3b", "0x5295f"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaacd2"
|
||||||
|
name = "condition_icon_ids"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30]"
|
||||||
|
value_domain = "condition icon id 1..10; zero means no icon"
|
||||||
|
usage = "ILINIT display metadata. DRAWENP and FIELD use these ids to deduplicate active-condition indicators and select the corresponding status-icon atlas entry."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaacf0"
|
||||||
|
name = "condition_duration_turns_by_level"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30][5]"
|
||||||
|
columns = { "0" = "level_1_turns", "1" = "level_2_turns", "2" = "level_3_turns", "3" = "level_4_turns", "4" = "level_5_turns" }
|
||||||
|
value_domain = "-1 permanent or a positive turn count"
|
||||||
|
usage = "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."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x5295f", "0x52383"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xaad86"
|
||||||
|
name = "condition_stat_deltas"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30][55]"
|
||||||
|
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" }
|
||||||
|
value_domain = "signed additive deltas"
|
||||||
|
usage = "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."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383", "0x4e11b"]
|
||||||
|
|
||||||
|
[[global]]
|
||||||
|
address = "0xab3f8"
|
||||||
|
name = "condition_resource_deltas"
|
||||||
|
category = "data-table"
|
||||||
|
type = "int[30][15]"
|
||||||
|
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" }
|
||||||
|
value_domain = "signed per-turn HP/SP/FS deltas"
|
||||||
|
usage = "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."
|
||||||
|
source = "investigation"
|
||||||
|
confidence = "high"
|
||||||
|
depends_on = ["0x52383", "0x4e085"]
|
||||||
|
|
||||||
[[global]]
|
[[global]]
|
||||||
address = "0x53e13"
|
address = "0x53e13"
|
||||||
name = "entity_carried_item_ids"
|
name = "entity_carried_item_ids"
|
||||||
|
|||||||
Reference in New Issue
Block a user