Expose semantic INIT column names
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
|
||||
# Global Variable Reference (generated)
|
||||
|
||||
5023 globals (145 curated, 4878 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
5023 globals (148 curated, 4875 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
|
||||
## choice-output
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
| address | name | conf | source | usage |
|
||||
|---|---|---|---|---|
|
||||
| `0x2e49` | character_voice_suppressed | high | investigation | Base of the per-character voice enable/suppress settings. INITCONFIG zero-fills all 13 cells and registers each with the shared profile service; LOADCONFIG restores them. CONFIG indexes the table to preview a character voice and write 0/1. ROOM reads cell 0 before assigning its selected greeter's greeting/farewell voice ids, so the port's former scalar interpretation of zero-int-range (writing 13 into the base cell) suppressed those voices on every natural boot. This names the script-visible setting array without choosing a persistence backend for op 0x1a2/0x1a3. |
|
||||
| `0x65ce` | skill_acquired_flags | high | investigation | Persistent acquired-skill flags. ADDSKILL sets the selected skill after resolving the unit's equipped-skill slots; FORT checks the flag before granting a skill; CHMENU combines it with skill_change_catalog_eligible to build the available skill-change catalog. |
|
||||
| `0x673c` | party_slot_flags | high | investigation | Per-party-slot state flags for slots 0..99. UNITECH creates the initial unit by setting slot 2 to 0x13; CALCARR counts slots whose flags intersect 0x6, and CHMENU includes slots with bit 1 set. Exact meanings of the remaining bits are not yet classified. |
|
||||
| `0x67a0` | party_slot_character_id | high | investigation | Character/unit definition id stored for each party slot. UNITECH writes character id 2 into initial slot 2 on a natural New Game; CHMENU reads this table for every active party_slot_flags entry when constructing its roster. |
|
||||
| `0x5660b` | skill_info_revealed_flags | high | investigation | Persistent skill-information visibility flags. ADDSKILL sets the selected skill, BTL marks every equipped skill when it is observed in combat, and INFOIT suppresses a skill's icon/handler-driven details until this flag is nonzero. This is broader than skill_acquired_flags. |
|
||||
| `0x66716` | unit_voice_asset_ids | high | investigation | EBINIT per-unit voice bank for 116 voiced characters and variants. Resolving the values through SYS4INI yields character OGG clips (for example Lily's row is LILA1381..1406); BTL, FIELD, and SHOWGROW select columns for battle, map, and level-up reactions. |
|
||||
| `0x6dc46` | unit_status_art_asset_ids | high | investigation | Three-variant status/menu illustration table for 25 principal characters. The ids resolve to 456x420 CS character art; DRAWCHP selects the requested variant, loads it into the status-panel texture slot, and draws it at the lower right. |
|
||||
| `0x6e7fe` | unit_map_sprite_asset_ids | high | investigation | Five-context unit sprite-sheet table for 251 units. The ids resolve to CP sprite atlases (compact AA and full directional AB sheets); FIELD and DRAWCH use them on the map, while INFOCH/INFOEN and DRAWENP reuse the same animated unit representation. |
|
||||
@@ -75,26 +77,27 @@
|
||||
| `0x8dc01` | item_icon_id | high | investigation | ITINIT field for all 287 items. READICON sorts and indexes this array to select icon atlas entries; ALCHEMY, CHMENU, IMPROVE, and INFOIT use the same value for item-row presentation. |
|
||||
| `0x8dfe9` | item_attack_element | high | investigation | Populated for 107 weapons/attacks. Values exactly match the AFINIT attack-attribute string table at GStr[0x2690 + value] and the Japanese item descriptions; combat scope/parameter code consumes the same array. |
|
||||
| `0x8e3d1` | item_defense_element | high | investigation | Populated for the 13 shields. Values match descriptions such as physical, universal, holy, dark, spirit, and divinity defense; DRAWTIP renders them through AFINIT's defense string table at GStr[0x26a4 + value]. |
|
||||
| `0x8e7b9` | item_character_whitelist | high | investigation | Sparse ITINIT row-major table with stride 5. CHMENU rejects an item when column 0 is populated and the selected party slot's character id is absent from the row. Unique accessories 471/472 allow one character each, while crossover accessories 480..485 allow character ids 2, 3, and 4. Unused trailing columns remain zero. |
|
||||
| `0x8ff29` | item_sex_restriction_mask | med | investigation | Sparse ITINIT equipment restriction. CHMENU tests this mask against EBINIT field 0x71eae, whose values partition male, female, and sexless units; both populated items carry bit 2 and are therefore female-only. |
|
||||
| `0x906f9` | item_status_delta_levels | high | investigation | Sparse ITINIT row-major table consumed by USEITEM and CALCILL when applying an item's effects. Positive values inflict or drain; -5 removes a condition (for example paralysis-removal item 108 stores -5 in column 8). Confirmed columns are 2 HP drain, 3 SP drain, 4 FS drain, 5 curse, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear. |
|
||||
| `0x97c29` | item_equipped_status_levels | high | investigation | Sparse ITINIT row-major table added to a unit's 30-column condition state by CALCREVISE. Item descriptions identify populated columns 9 poison, 11 fear, 13 regeneration, and 14 exaltation; these are passive equipped effects, distinct from item_status_delta_levels. |
|
||||
| `0x8e7b9` | item_character_whitelist | high | investigation | Sparse ITINIT row-major table with stride 5. CHMENU rejects an item when column 0 is populated and the selected party slot's character id is absent from the row. Unique accessories 471/472 allow one character each, while crossover accessories 480..485 allow character ids 2, 3, and 4. Unused trailing columns remain zero. Columns: 0=allowed_character_id_1, 1=allowed_character_id_2, 2=allowed_character_id_3, 3=allowed_character_id_4, 4=allowed_character_id_5. |
|
||||
| `0x8ff29` | item_sex_restriction_mask | high | investigation | Sparse ITINIT equipment restriction. CHMENU uses unit_sex_category as a bit index and rejects an item when that bit is absent from this mask. Both populated items carry only bit 2; EBINIT value 2 is female, and ITMES explicitly describes item 419 as female-only. |
|
||||
| `0x906f9` | item_status_delta_levels | high | investigation | Sparse ITINIT row-major table consumed by USEITEM and CALCILL when applying an item's effects. Positive values inflict or drain; -5 removes a condition (for example paralysis-removal item 108 stores -5 in column 8). Confirmed columns are 2 HP drain, 3 SP drain, 4 FS drain, 5 curse, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear. Columns: 2=hp_drain, 3=sp_drain, 4=fs_drain, 5=curse, 6=charm, 7=confusion, 8=paralysis, 9=poison, 10=water_flow, 11=fear. |
|
||||
| `0x97c29` | item_equipped_status_levels | high | investigation | Sparse ITINIT row-major table added to a unit's 30-column condition state by CALCREVISE. Item descriptions identify populated columns 9 poison, 11 fear, 13 regeneration, and 14 exaltation; these are passive equipped effects, distinct from item_status_delta_levels. Columns: 9=poison, 11=fear, 13=regeneration, 14=exaltation. |
|
||||
| `0x9f159` | item_granted_skill_id | high | investigation | Populated for 85 equipment items. Values cross-resolve to SKINIT (for example flying bracelet=1 Flying, transfer bracelet=21 Transfer, thief key=22 Lockpick, ropes=156 Capture Attack); CALCREVISE applies the linked skill and UI scripts display it. |
|
||||
| `0x9f541` | item_stat_modifiers | high | investigation | ITINIT row-major equipment modifiers added directly to the unit's 14-column stat record by CALCREVISE. Descriptions and consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, 9 capture power, 10 movement, 11 max HP, 12 max SP, and 13 max FS. CALCBTPARAM adds columns 7 and 8 into the action's critical percentage, and CALCDMG compares the clamped result with a random-modulo-100 roll. |
|
||||
| `0xa2bf1` | item_tuning_curve_ids | high | investigation | ITINIT row-major table selecting an equipment-growth curve for each of the ten tunable fields. TUNE, IMPROVE, DRAWTIP, and CALCREVISE combine each nonzero curve id with the item's corresponding tuning level and index the shared curve-value table at 0xab6fa. Columns align with item_stat_modifiers columns 0..9. |
|
||||
| `0xa5301` | item_resource_recovery_amounts | high | investigation | Sparse ITINIT row-major consumable table. USEITEM applies columns 0, 1, and 2 to the matching three-column unit resource record; descriptions prove these are HP, SP, and FS respectively (for example item 101 stores HP 30, and item 107 stores 999/99/99 for full recovery). |
|
||||
| `0x9f541` | item_stat_modifiers | high | investigation | ITINIT row-major equipment modifiers added directly to the unit's 14-column stat record by CALCREVISE. Descriptions and consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, 9 capture power, 10 movement, 11 max HP, 12 max SP, and 13 max FS. CALCBTPARAM adds columns 7 and 8 into the action's critical percentage, and CALCDMG compares the clamped result with a random-modulo-100 roll. Columns: 0=accuracy, 1=evasion, 2=physical_attack, 3=physical_defense, 4=magic_attack, 5=magic_defense, 6=speed, 7=luck, 8=critical_chance, 9=capture_power, 10=movement, 11=max_hp, 12=max_sp, 13=max_fs. |
|
||||
| `0xa2bf1` | item_tuning_curve_ids | high | investigation | ITINIT row-major table selecting an equipment-growth curve for each of the ten tunable fields. TUNE, IMPROVE, DRAWTIP, and CALCREVISE combine each nonzero curve id with the item's corresponding tuning level and index the shared curve-value table at 0xab6fa. Columns align with item_stat_modifiers columns 0..9. Columns: 0=accuracy, 1=evasion, 2=physical_attack, 3=physical_defense, 4=magic_attack, 5=magic_defense, 6=speed, 7=luck, 8=critical_chance, 9=capture_power. |
|
||||
| `0xa5301` | item_resource_recovery_amounts | high | investigation | Sparse ITINIT row-major consumable table. USEITEM applies columns 0, 1, and 2 to the matching three-column unit resource record; descriptions prove these are HP, SP, and FS respectively (for example item 101 stores HP 30, and item 107 stores 999/99/99 for full recovery). Columns: 0=hp, 1=sp, 2=fs. |
|
||||
| `0xa62a1` | item_essence_recovery_amount | high | investigation | Sparse ITINIT consumable field. Item 106, Blood Price Healing Hand, describes essence recovery 50 and stores 50 here; USEITEM follows the dedicated essence-recovery path and scales the value before updating the selected unit. |
|
||||
| `0xa6689` | item_weapon_class | high | investigation | Populated for 104 weapons/innate attacks. Stable values identify weapon families (1 unarmed, 2 staff, 3 claw, 4 dagger, 5 sword, 6 chain blade, 7 spear, 8 axe, 9 bow, 11 blade boots; later values are monster/natural attack classes). CALCDMG consumes it. |
|
||||
| `0xa6a71` | item_handler_script_id | high | investigation | ITINIT field for all 287 items. Item menus look up this value and feed it directly to call-script; the packed id 0x319b resolves to ITMES.BIN, the shared per-item behavior/description dispatcher. |
|
||||
| `0xa6e5a` | skill_sort_key | high | investigation | SKINIT field populated for 129 of 131 skills. ADDEXP, ADDSKILL, CHMENU, INFOIT, and SETCH combine it with skill_category to construct deterministic skill-list ordering keys; higher-level sort operations consume the resulting indices. |
|
||||
| `0xa6f86` | skill_category | high | investigation | SKINIT category for all 131 skills. The records establish 1 movement/exploration, 2 defense/special attack, 3 utility/passive, 4 combat passive, 5 physical special, 6 offensive magic, and 7 healing magic. Combat and menu scripts dispatch directly on these values. |
|
||||
| `0xa70b2` | skill_change_catalog_eligible | high | investigation | SKINIT inclusion flag for CHMENU's fourth (skill-change) catalog. CHMENU scans all 300 ids, keeps only nonzero rows, sorts acquired rows ahead of unavailable rows using skill_category and skill_sort_key, and copies the acquired prefix into catalog row 3. Eligible ids are 1, 2, 112, 130..133, 210, 228, 230, and 231. |
|
||||
| `0xa71de` | skill_min_range_encoded | high | investigation | Populated for the 94 active skills. CALCSCOPE and CALCREVISE subtract one before using it as the lower range bound; SELACT and DRAWTIP use the same encoded bound. |
|
||||
| `0xa730a` | skill_max_range_encoded | high | investigation | Populated for the same 94 active skills as skill_min_range_encoded. CALCSCOPE subtracts one to obtain the upper bound, exactly matching descriptions such as range 3 -> stored 4 and range 9 -> stored 10. |
|
||||
| `0xa7436` | skill_attack_element | high | investigation | Populated for 60 elemental attacks and spells. Values match the Japanese descriptions and the same eight-value attack-element enum used by item_attack_element; CALCSCOPE, CALCBTPARAM, and SELACT consume it. |
|
||||
| `0xa7562` | skill_status_levels | high | investigation | Sparse SKINIT row-major condition table consumed by CALCILL when applying a skill's effects to the target unit. Skill names and descriptions identify columns 1 instant death, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear; the column layout matches item_status_delta_levels. |
|
||||
| `0xa7562` | skill_status_levels | high | investigation | Sparse SKINIT row-major condition table consumed by CALCILL when applying a skill's effects to the target unit. Skill names and descriptions identify columns 1 instant death, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear; the column layout matches item_status_delta_levels. Columns: 1=instant_death, 6=charm, 7=confusion, 8=paralysis, 9=poison, 10=water_flow, 11=fear. |
|
||||
| `0xa988a` | skill_icon_id | high | investigation | SKINIT icon id for all 131 skills. CHMENU and INFOIT translate it into the shared icon atlas; the six ids group movement, defense, utility, passive, physical-special, and magic icons. |
|
||||
| `0xa99b6` | skill_combat_stat_deltas | high | investigation | Sparse SKINIT row-major table applied by CALCBTPARAM for the selected action. Descriptions and combat consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, and 9 capture power. Column 7 is unpopulated in shipped SKINIT; CALCBTPARAM adds any luck and critical modifiers into the critical percentage that CALCDMG rolls after the hit check. |
|
||||
| `0xaa56e` | skill_resource_deltas | high | investigation | Sparse SKINIT resource table used throughout skill selection and resolution. Column 0 is HP recovery for the three healing spells; column 1 is the negative SP cost for all 95 active skills, exactly matching each description. Column 2 is unpopulated in the shipped table. |
|
||||
| `0xa99b6` | skill_combat_stat_deltas | high | investigation | Sparse SKINIT row-major table applied by CALCBTPARAM for the selected action. Descriptions and combat consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, and 9 capture power. Column 7 is unpopulated in shipped SKINIT; CALCBTPARAM adds any luck and critical modifiers into the critical percentage that CALCDMG rolls after the hit check. Columns: 0=accuracy, 1=evasion, 2=physical_attack, 3=physical_defense, 4=magic_attack, 5=magic_defense, 6=speed, 7=luck, 8=critical_chance, 9=capture_power. |
|
||||
| `0xaa56e` | skill_resource_deltas | high | investigation | Sparse SKINIT resource table used throughout skill selection and resolution. Column 0 is HP recovery for the three healing spells; column 1 is the negative SP cost for all 95 active skills, exactly matching each description. Column 2 is unpopulated in the shipped table. Columns: 0=hp_recovery, 1=sp_cost. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
@@ -135,7 +138,6 @@
|
||||
| `0x4379` | — | low | auto-shape | array |
|
||||
| `0x45d7` | — | low | auto-shape | array |
|
||||
| `0x463b` | — | low | auto-shape | array |
|
||||
| `0x65ce` | — | low | auto-shape | array |
|
||||
| `0x671c` | — | low | auto-shape | array |
|
||||
| `0x671f` | — | low | auto-shape | array |
|
||||
| `0x6727` | — | low | auto-shape | array |
|
||||
@@ -166,7 +168,6 @@
|
||||
| `0x53ede` | — | low | auto-shape | array |
|
||||
| `0x55e3b` | — | low | auto-shape | array |
|
||||
| `0x56223` | — | low | auto-shape | array |
|
||||
| `0x5660b` | — | low | auto-shape | array |
|
||||
| `0x56b20` | — | low | auto-shape | array |
|
||||
| `0x56b52` | — | low | auto-shape | array |
|
||||
| `0x56b85` | — | low | auto-shape | array |
|
||||
@@ -4987,7 +4988,6 @@
|
||||
| `0x7843e` | — | med | auto-shape | unit-field |
|
||||
| `0x81c96` | — | med | auto-shape | record-table[stride 3] |
|
||||
| `0x8284e` | — | med | auto-shape | record-table[stride 3] |
|
||||
| `0xa70b2` | — | low | auto-shape | skill-field? |
|
||||
| `0xaac76` | — | low | auto-shape | index/counter? |
|
||||
| `0xaacf0` | — | med | auto-shape | record-table[stride 5] |
|
||||
| `0xaad86` | — | med | auto-shape | record-table[stride 55] |
|
||||
|
||||
@@ -215,6 +215,20 @@ after the hit check and selects the critical-result state on success. Column 8 i
|
||||
for both `item_stat_modifiers` and `skill_combat_stat_deltas`; the skill descriptions and matching item
|
||||
columns also confirm evasion, magic defense, and speed.
|
||||
|
||||
ITMES and SKMES are now joined back to their INIT records by a reusable id-dispatch extractor: all 287 item
|
||||
ids and all 131 skill ids match exactly. `init_table_profile.py --message-query REGEX` puts the complete
|
||||
player-facing description beside every populated field, which confirms the item/skill condition, resource,
|
||||
range, combat-stat, and restriction mappings without relying on column position. The same CHMENU trace
|
||||
identifies SKINIT `0xa70b2` as `skill_change_catalog_eligible`, distinguishes persistent
|
||||
`skill_acquired_flags` from broader `skill_info_revealed_flags`, and the explicit ITMES “female-only” record
|
||||
raises `item_sex_restriction_mask` to high confidence.
|
||||
|
||||
Confirmed row-column meanings are no longer prose-only. The relevant `globals.toml` entries carry a
|
||||
machine-readable `columns` map; `globals_build.py` preserves it in `build/globals.json`, and
|
||||
`extract_init.py` emits a top-level `field_semantics` mapping while retaining raw address/stride/column keys
|
||||
as provenance. Generated profiles therefore render names such as
|
||||
`item_stat_modifiers.critical_chance` and `skill_status_levels.paralysis` directly.
|
||||
|
||||
### The curated registry — `vm-map/globals.toml` (2026-07-07)
|
||||
|
||||
The v1 auto map (`build/global-var-map.json`) infers *shapes* but cannot recover branch-flag
|
||||
@@ -223,7 +237,7 @@ The v1 auto map (`build/global-var-map.json`) infers *shapes* but cannot recover
|
||||
|
||||
- **`vm-map/globals.toml`** — the only hand-edited source. One `[[global]]` per known address:
|
||||
`name`, `category` (`story-flag`/`index-pointer`/`data-table`/`string-table`/`ui-toggle`/
|
||||
`choice-output`/`counter`/`unknown`), `type`, `value_domain`, `usage`, and provenance
|
||||
`choice-output`/`counter`/`unknown`), `type`, optional row-table `columns`, `value_domain`, `usage`, and provenance
|
||||
(`source`/`confidence`/`depends_on`).
|
||||
- **`tools/globals_build.py --build`** merges curated entries *over* the auto map →
|
||||
`build/globals.json` (machine) + `docs/global-reference.md` (generated human view). `--lint`
|
||||
@@ -259,14 +273,13 @@ 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:
|
||||
|
||||
1. **Continue INIT semantics by evidence density.** Resolve ITINIT/SKINIT's remaining stat and condition
|
||||
columns, then work through EBINIT's AI, route/evolution, and remaining sparse-flag tables by consumer
|
||||
strength. Preserve explicit item → skill and unit → attack/skill/equipment/drop joins. Do not infer
|
||||
meaning from column position alone.
|
||||
2. **Fold in the `*MES` message-table writers** (`ITMES`, `SKMES`, `VIMES`, …) and any other
|
||||
`set-string`/`copy-to-global` writers not covered by the `*INIT` set — pure static win,
|
||||
extends the string/data labels. (Also: most name-table bases are *read* rarely — reads
|
||||
likely go through `*MES`/an indirection; tracing that would connect names to their readers.)
|
||||
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT's populated row columns now have
|
||||
machine-readable meanings; extend the same structured `columns` metadata through the confirmed EBINIT
|
||||
tables, then investigate its unread enum and boss-class sign only when consumer evidence appears.
|
||||
Preserve explicit item → skill and unit → attack/skill/equipment/drop joins. Do not infer meaning from
|
||||
column position alone.
|
||||
2. **Extend message-table joins beyond the completed ITMES/SKMES pair** (`VIMES`, other id dispatchers, …)
|
||||
and fold in other `set-string`/`copy-to-global` writers not covered by the `*INIT` set.
|
||||
3. **Label 2D record tables by their readers** — cross-reference which scripts read each
|
||||
`rec[sN]` table and infer purpose from context (e.g. RECOVER's 30-wide tables ↔ a
|
||||
status/recovery system). Static, medium effort.
|
||||
|
||||
@@ -736,6 +736,15 @@ The dispatch keys also establish `current_item_id` and `current_skill_id` as hig
|
||||
slots. This makes message/field correlation the next evidence source for the remaining sparse item and skill
|
||||
columns.
|
||||
|
||||
The follow-up correlation pass makes that evidence directly queryable with
|
||||
`init_table_profile.py --message-query REGEX` and moves confirmed item/skill row-column meanings into
|
||||
structured `globals.toml` metadata. Extracted JSON and profiles now resolve raw keys to names such as
|
||||
`item_stat_modifiers.critical_chance` while preserving the original address key. The message audit confirms
|
||||
all populated condition columns and the female-only item mask. CHMENU also resolves the last anonymous
|
||||
SKINIT parallel field as the skill-change catalog inclusion flag and separates acquired-skill state from
|
||||
skill-information visibility. The remaining item/skill work is refinement rather than an unnamed populated
|
||||
schema.
|
||||
|
||||
The remaining EBINIT unknowns are now the unread `0x7843e` enum and the signed meaning within boss classes;
|
||||
enemy AI appears to live outside the static EBINIT schema. STINIT's bespoke parser remains a separate
|
||||
extraction task.
|
||||
|
||||
@@ -43,7 +43,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
||||
| `validate_opcode_table.py` | Definitive decode-coverage validator (replicates Kelebek's `data_array_end` code/data split). | `validate_opcode_table.py` | corpus → stdout |
|
||||
| `validate_opcode_table_naive.py` | Naïve variant of the above (baseline comparison). | `validate_opcode_table_naive.py` | corpus → stdout |
|
||||
| `age_opcodes_himegari.py` | ⚙ Inferred Himegari opcode semantics — **generated; do not hand-edit.** | *Imported by `sys4load.py`.* | — |
|
||||
| `globals_build.py` | Merge curated `globals.toml` over the auto shape map; generate the global registry + linter. | `--build` · `--lint` | `vm-map/globals.toml`, `build/global-var-map.json` → ⚙ `build/globals.json`, ⚙ `docs/global-reference.md` |
|
||||
| `globals_build.py` | Merge curated `globals.toml` over the auto shape map, preserve optional machine-readable row-table `columns`, and generate the global registry + linter. | `--build` · `--lint` | `vm-map/globals.toml`, `build/global-var-map.json` → ⚙ `build/globals.json`, ⚙ `docs/global-reference.md` |
|
||||
| `story_flags.py` | Static story-flag miner (branch-condition mining) + `--bootstrap` skeleton seeding. | `story_flags.py` · `--bootstrap` | corpus, `build/global-var-map.json` → ⚙ `build/story-flags-candidates.json`, appends `vm-map/globals.toml` |
|
||||
| `test_globals.py` | Unit tests for the globals registry + story-flag miner. | `test_globals.py` | — |
|
||||
| `scjump_decode.py` | Decode SCJUMP's progression logic → decision table; `--verify` VM cross-check. | `scjump_decode.py` · `--verify` | SCJUMP.BIN, `build/globals.json` → ⚙ `build/scjump-decisions.{json,md}` |
|
||||
@@ -55,8 +55,8 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
||||
|---|---|---|---|
|
||||
| `extract_phase2.py` | Batch: disassembly + text corpora for every script. | `extract_phase2.py` | corpus → `build/disasm/*.asm`, `build/text/{dialogue.jsonl,strings.jsonl,*.strings.txt}`, `build/manifest.json` |
|
||||
| `extract_message_table.py` | Discover a repeated global-id dispatch chain such as ITMES/SKMES, reconstruct player-facing title/description lines (including furigana surface text and readings), and emit an ID-keyed message table with bytecode provenance. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN` → `build/data/<OUTNAME>.json` |
|
||||
| `extract_init.py` | Parse a `*INIT` data table (auto-detects name / numeric / footer 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`. ITINIT and SKINIT also join their matching ITMES/SKMES player-facing messages by runtime id. Refreshes the generated data index. | `extract_init.py <TABLE> [OUTNAME] [--mode …]` | `<TABLE>.BIN` plus matching `<MES>.BIN` when supported → `build/data/<OUTNAME>.json`, `build/data/README.md` |
|
||||
| `init_table_profile.py` | Build the static investigation surface for an extracted name/numeric table: message coverage, per-array and per-record-column population/value distributions, representative records, and direct opcode/script consumers. Findings are evidence only; confirmed meanings go in `vm-map/globals.toml`. | `init_table_profile.py <TABLE> [--build] [--limit N]` | `build/data/<TABLE>.json` + corpus → stdout; with `--build`, `build/data/<TABLE>-field-profile.{json,md}` |
|
||||
| `extract_init.py` | Parse a `*INIT` data table (auto-detects name / numeric / footer 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`. ITINIT and SKINIT join ITMES/SKMES messages; top-level `field_semantics` maps raw keys to canonical global/column names. Refreshes the generated data index. | `extract_init.py <TABLE> [OUTNAME] [--mode …]` | `<TABLE>.BIN` plus matching `<MES>.BIN` when supported + `build/globals.json` → `build/data/<OUTNAME>.json`, `build/data/README.md` |
|
||||
| `init_table_profile.py` | Build the static investigation surface for an extracted name/numeric table: message coverage, per-array and per-record-column population/value distributions, representative records with their player-facing descriptions, and direct opcode/script consumers. `--message-query REGEX` shows every matching name/message beside all populated fields for semantic correlation. Findings are evidence only; confirmed meanings go in `vm-map/globals.toml`. | `init_table_profile.py <TABLE> [--build] [--limit N] [--message-query REGEX]` | `build/data/<TABLE>.json` + corpus → stdout; with `--build`, `build/data/<TABLE>-field-profile.{json,md}` |
|
||||
| `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based INIT extraction, MES dispatch reconstruction/joins, and field/message profiling. | run each directly | — |
|
||||
| `global_map.py` | Build the partial global-variable name map from static evidence. | `global_map.py` | corpus + `build/data/` → `build/global-var-map.{json,md}` |
|
||||
|
||||
|
||||
@@ -280,6 +280,40 @@ def join_messages(records: list[dict], message_scr) -> dict:
|
||||
}
|
||||
|
||||
|
||||
@cache
|
||||
def _global_registry() -> dict:
|
||||
path = paths.BUILD / "globals.json"
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf8")).get("globals", {})
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def field_semantics(records: list[dict]) -> dict[str, str]:
|
||||
"""Map raw extracted field keys to canonical semantic names when available."""
|
||||
keys = {
|
||||
key
|
||||
for record in records
|
||||
for key in (*record.get("fields", {}), *record.get("record_fields", {}))
|
||||
}
|
||||
registry = _global_registry()
|
||||
semantics = {}
|
||||
for key in sorted(keys, key=lambda value: tuple(
|
||||
int(part, 0) for part in value.split("/")
|
||||
)):
|
||||
parts = key.split("/")
|
||||
entry = registry.get(f"0x{int(parts[0], 16):x}", {})
|
||||
name = entry.get("name")
|
||||
if not name:
|
||||
continue
|
||||
if len(parts) == 3:
|
||||
column = parts[2]
|
||||
column_name = entry.get("columns", {}).get(column, f"column_{column}")
|
||||
name = f"{name}.{column_name}"
|
||||
semantics[key] = name
|
||||
return semantics
|
||||
|
||||
|
||||
def write_data_index(data_dir: Path) -> None:
|
||||
"""Regenerate the disposable build/data index from current table JSONs."""
|
||||
tables = []
|
||||
@@ -323,6 +357,8 @@ def write_data_index(data_dir: Path) -> None:
|
||||
"Where a matching `*MES` dispatcher exists, `message` preserves its player-facing",
|
||||
"title, description, furigana, and bytecode dispatch offset separately from the",
|
||||
"short description stored by the INIT script.",
|
||||
"Top-level `field_semantics` maps raw array/row-column keys to canonical machine-readable",
|
||||
"names from `vm-map/globals.toml`; raw keys remain intact as bytecode provenance.",
|
||||
"",
|
||||
"Use `tools/init_table_profile.py <TABLE> --build` to generate value/population and",
|
||||
"direct-consumer evidence. `STINIT` still requires a bespoke mixed numeric/string parser.",
|
||||
@@ -350,9 +386,11 @@ def main() -> int:
|
||||
)
|
||||
|
||||
cols = sorted({c for r in recs for c in r.get("fields", {})}, key=lambda h: int(h, 16))
|
||||
semantics = field_semantics(recs)
|
||||
out = {"table": name, "source": scr.path.name, "magic": scr.magic, "mode": mode,
|
||||
"record_count": len(recs), **meta,
|
||||
"field_columns": cols if mode != "footer" else None, "records": recs}
|
||||
"field_columns": cols if mode != "footer" else None,
|
||||
"field_semantics": semantics, "records": recs}
|
||||
outpath = paths.BUILD / "data" / f"{outname}.json"
|
||||
outpath.parent.mkdir(parents=True, exist_ok=True)
|
||||
outpath.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
@@ -49,6 +49,20 @@ def lint(entries: dict[int, dict], all_addrs: set[int]) -> tuple[list[str], list
|
||||
errors.append(f"{tag}: bad confidence {conf!r}")
|
||||
if src == "auto-shape" and conf == "high":
|
||||
errors.append(f"{tag}: auto-shape source may not claim high confidence")
|
||||
columns = e.get("columns", {})
|
||||
if not isinstance(columns, dict):
|
||||
errors.append(f"{tag}: columns must be a table")
|
||||
else:
|
||||
for column, name in columns.items():
|
||||
try:
|
||||
column_index = int(column)
|
||||
except (TypeError, ValueError):
|
||||
errors.append(f"{tag}: bad column index {column!r}")
|
||||
continue
|
||||
if column_index < 0:
|
||||
errors.append(f"{tag}: negative column index {column_index}")
|
||||
if not isinstance(name, str) or not name:
|
||||
errors.append(f"{tag}: column {column_index} has no semantic name")
|
||||
for dep in e.get("depends_on", []):
|
||||
if _parse_addr(dep) not in all_addrs:
|
||||
errors.append(f"{tag}: depends_on missing address {dep}")
|
||||
@@ -96,6 +110,10 @@ def merge(curated: dict[int, dict], auto: dict) -> dict[int, dict]:
|
||||
"source": e.get("source", "inference"), "confidence": e.get("confidence", "low"),
|
||||
"depends_on": [f"0x{_parse_addr(d):x}" for d in e.get("depends_on", [])],
|
||||
"provenance": "curated"}
|
||||
if e.get("columns"):
|
||||
out[addr]["columns"] = {
|
||||
str(column): name for column, name in e["columns"].items()
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
@@ -121,6 +139,14 @@ def emit_reference_md(merged: dict[int, dict]) -> str:
|
||||
e = merged[addr]
|
||||
name = e["name"] or "—"
|
||||
usage = (e["usage"] or "").replace("|", "\\|").replace("\n", " ")
|
||||
if columns := e.get("columns"):
|
||||
mapping = ", ".join(
|
||||
f"{column}={column_name}"
|
||||
for column, column_name in sorted(
|
||||
columns.items(), key=lambda item: int(item[0])
|
||||
)
|
||||
)
|
||||
usage += f" Columns: {mapping}."
|
||||
L.append(f"| `{e['address']}` | {name} | {e['confidence']} | {e['source']} | {usage} |")
|
||||
L.append("")
|
||||
return "\n".join(L) + "\n"
|
||||
|
||||
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import collections
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -49,6 +50,7 @@ def value_key(value) -> str:
|
||||
|
||||
def profile_columns(data: dict) -> list[dict]:
|
||||
records = data["records"]
|
||||
field_semantics = data.get("field_semantics", {})
|
||||
values: dict[str, list] = collections.defaultdict(list)
|
||||
examples: dict[str, list[dict]] = collections.defaultdict(list)
|
||||
identities: dict[str, dict] = {}
|
||||
@@ -59,14 +61,18 @@ def profile_columns(data: dict) -> list[dict]:
|
||||
identities[key] = {
|
||||
"key": key, "kind": "parallel-array", "base": key,
|
||||
"stride": None, "column": None,
|
||||
"semantic_name": field_semantics.get(key),
|
||||
}
|
||||
values[key].append(value)
|
||||
if len(examples[key]) < 5:
|
||||
examples[key].append({
|
||||
example = {
|
||||
"id": record["id"],
|
||||
"name": record.get("name", ""),
|
||||
"value": value,
|
||||
})
|
||||
}
|
||||
if message := record.get("message"):
|
||||
example["message_description"] = message.get("description", "")
|
||||
examples[key].append(example)
|
||||
for key, value in record.get("record_fields", {}).items():
|
||||
base_text, stride_text, column_text = key.split("/")
|
||||
base = int(base_text, 16)
|
||||
@@ -79,14 +85,18 @@ def profile_columns(data: dict) -> list[dict]:
|
||||
"base": f"0x{base:x}",
|
||||
"stride": stride,
|
||||
"column": column,
|
||||
"semantic_name": field_semantics.get(normalized_key),
|
||||
}
|
||||
values[normalized_key].append(value)
|
||||
if len(examples[normalized_key]) < 5:
|
||||
examples[normalized_key].append({
|
||||
example = {
|
||||
"id": record["id"],
|
||||
"name": record.get("name", ""),
|
||||
"value": value,
|
||||
})
|
||||
}
|
||||
if message := record.get("message"):
|
||||
example["message_description"] = message.get("description", "")
|
||||
examples[normalized_key].append(example)
|
||||
|
||||
rows = []
|
||||
for key, vals in values.items():
|
||||
@@ -131,6 +141,48 @@ def profile_messages(data: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def find_message_matches(data: dict, pattern: str) -> list[dict]:
|
||||
"""Return records whose name/title/description matches a regular expression."""
|
||||
regex = re.compile(pattern, re.IGNORECASE)
|
||||
return [
|
||||
record
|
||||
for record in data["records"]
|
||||
if regex.search("\n".join([
|
||||
record.get("name", ""),
|
||||
record.get("message", {}).get("title", ""),
|
||||
record.get("message", {}).get("description", ""),
|
||||
]))
|
||||
]
|
||||
|
||||
|
||||
def render_message_matches(data: dict, pattern: str) -> str:
|
||||
"""Render message hits beside every populated INIT field for correlation."""
|
||||
matches = find_message_matches(data, pattern)
|
||||
escaped_pattern = pattern.replace("`", "\\`")
|
||||
lines = [
|
||||
f"# {data['table']} message matches",
|
||||
"",
|
||||
f"- query: `{escaped_pattern}`",
|
||||
f"- matches: {len(matches)}",
|
||||
"",
|
||||
"| id | name | player-facing description | populated fields |",
|
||||
"|---:|---|---|---|",
|
||||
]
|
||||
for record in matches:
|
||||
fields = {**record.get("fields", {}), **record.get("record_fields", {})}
|
||||
rendered_fields = ", ".join(
|
||||
f"`{data.get('field_semantics', {}).get(key, key)}` (`{key}`)={value}"
|
||||
for key, value in sorted(fields.items())
|
||||
)
|
||||
name = record.get("name", "").replace("|", "\\|")
|
||||
description = record.get("message", {}).get("description", "").replace("|", "\\|")
|
||||
lines.append(
|
||||
f"| {record['id']} | {name} | {description} | {rendered_fields} |"
|
||||
)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def add_direct_references(rows: list[dict], source_name: str) -> None:
|
||||
by_base: dict[int, list[dict]] = collections.defaultdict(list)
|
||||
for row in rows:
|
||||
@@ -185,8 +237,8 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str:
|
||||
f"- messages with furigana spans: {message_profile['furigana_records']}",
|
||||
f"- rows shown: {len(shown)} (ranked by record coverage, then consumer references)",
|
||||
"",
|
||||
"| field | populated | distinct | range | direct refs | readers | common values | examples |",
|
||||
"|---|---:|---:|---|---:|---|---|---|",
|
||||
"| field | meaning | populated | distinct | range | direct refs | readers | common values | examples |",
|
||||
"|---|---|---:|---:|---|---:|---|---|---|",
|
||||
]
|
||||
for row in shown:
|
||||
value_range = "—" if row["min"] is None else f"{row['min']}..{row['max']}"
|
||||
@@ -198,7 +250,8 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str:
|
||||
for entry in row["examples"][:3]
|
||||
).replace("|", "\\|")
|
||||
lines.append(
|
||||
f"| `{row['key']}` | {row['population']}/{data['record_count']} "
|
||||
f"| `{row['key']}` | {row.get('semantic_name') or '—'} | "
|
||||
f"{row['population']}/{data['record_count']} "
|
||||
f"({row['coverage']:.0%}) | {row['distinct_values']} | {value_range} | "
|
||||
f"{row['references']} | {readers} | {common} | {examples} |"
|
||||
)
|
||||
@@ -211,6 +264,11 @@ def main() -> int:
|
||||
parser.add_argument("table", help="extracted table name, e.g. ITINIT")
|
||||
parser.add_argument("--build", action="store_true", help="write JSON and Markdown profiles")
|
||||
parser.add_argument("--limit", type=int, default=40, help="Markdown/console row limit")
|
||||
parser.add_argument(
|
||||
"--message-query",
|
||||
metavar="REGEX",
|
||||
help="show matching names/player-facing messages beside all populated fields",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
name = args.table.upper().removesuffix(".JSON").removesuffix(".BIN")
|
||||
@@ -231,6 +289,8 @@ def main() -> int:
|
||||
)),
|
||||
}
|
||||
markdown = render_markdown(data, rows, args.limit)
|
||||
if args.message_query:
|
||||
print(render_message_matches(data, args.message_query))
|
||||
print(markdown)
|
||||
if args.build:
|
||||
stem = paths.BUILD / "data" / f"{name}-field-profile"
|
||||
|
||||
@@ -130,11 +130,22 @@ def test_message_join() -> None:
|
||||
"INIT/MES join uses the shared runtime id")
|
||||
|
||||
|
||||
def test_field_semantics() -> None:
|
||||
scripts = paths.scripts()
|
||||
items, _ = extract_init.extract_name(sys4load.load(scripts["ITINIT.BIN"]))
|
||||
semantics = extract_init.field_semantics(items)
|
||||
check(semantics["0x8c879"] == "item_sort_key",
|
||||
"parallel INIT fields expose canonical semantic names")
|
||||
check(semantics["0x9f541/14/8"] == "item_stat_modifiers.critical_chance",
|
||||
"row-table columns expose canonical semantic names")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_real_name_tables()
|
||||
test_static_negative_write()
|
||||
test_real_message_tables()
|
||||
test_message_join()
|
||||
test_field_semantics()
|
||||
if FAILS:
|
||||
raise SystemExit(f"{len(FAILS)} failed checks")
|
||||
print("all extract_init checks passed")
|
||||
|
||||
@@ -22,10 +22,13 @@ def test_load_and_lint():
|
||||
|
||||
def test_lint_catches_bad_vocab():
|
||||
bad = {0x1: {"_addr": 0x1, "name": "x", "category": "bogus",
|
||||
"source": "auto-shape", "confidence": "high"}}
|
||||
"source": "auto-shape", "confidence": "high",
|
||||
"columns": {"not-an-index": "x", "-1": ""}}}
|
||||
errors, _ = G.lint(bad, {0x1})
|
||||
check(any("category" in e for e in errors), "lint flags bad category")
|
||||
check(any("confidence" in e for e in errors), "lint flags auto-shape claiming high confidence")
|
||||
check(any("column index" in e for e in errors), "lint flags nonnumeric column indices")
|
||||
check(any("negative column" in e for e in errors), "lint flags negative column indices")
|
||||
|
||||
def test_merge_precedence():
|
||||
curated, _ = G.load_toml(paths.VM_MAP / "globals.toml")
|
||||
@@ -34,6 +37,8 @@ def test_merge_precedence():
|
||||
check(merged[0xa57]["name"] == "lily_form_a", "curated 0xa57 name wins over auto label")
|
||||
check(merged[0xa57]["category"] == "story-flag", "curated 0xa57 category overrides auto string-table")
|
||||
check(merged[0xa57]["provenance"] == "curated", "0xa57 marked curated")
|
||||
check(merged[0x9f541]["columns"]["8"] == "critical_chance",
|
||||
"curated row-table column semantics survive the merge")
|
||||
# an address only in the auto map falls through as provenance=auto
|
||||
auto_only = next((a for a in auto.get("globals", {})
|
||||
if int(a, 16) not in curated and auto["globals"][a].get("label")), None)
|
||||
|
||||
@@ -11,6 +11,11 @@ import init_table_profile as profile
|
||||
|
||||
def main() -> int:
|
||||
fixture = {
|
||||
"table": "TEST",
|
||||
"field_semantics": {
|
||||
"0x10": "test_parallel",
|
||||
"0x30/3/0": "test_record.zero",
|
||||
},
|
||||
"records": [
|
||||
{"id": 1, "name": "one", "fields": {"0x10": 2, "0x20": 0},
|
||||
"record_fields": {"0x30/3/0": 9},
|
||||
@@ -27,17 +32,25 @@ def main() -> int:
|
||||
assert rows["0x10"]["distinct_values"] == 2
|
||||
assert rows["0x10"]["min"] == 2 and rows["0x10"]["max"] == 5
|
||||
assert rows["0x10"]["common"][0] == {"value": "2", "count": 2}
|
||||
assert rows["0x10"]["semantic_name"] == "test_parallel"
|
||||
assert rows["0x10"]["examples"][0]["message_description"] == "First"
|
||||
assert rows["0x20"]["population"] == 1
|
||||
assert rows["0x20"]["examples"][0]["name"] == "one"
|
||||
assert rows["0x30/3/0"]["kind"] == "record-column"
|
||||
assert rows["0x30/3/0"]["base"] == "0x30"
|
||||
assert rows["0x30/3/0"]["stride"] == 3
|
||||
assert rows["0x30/3/0"]["semantic_name"] == "test_record.zero"
|
||||
assert rows["0x30/3/2"]["column"] == 2
|
||||
messages = profile.profile_messages(fixture)
|
||||
assert messages["population"] == 1
|
||||
assert messages["coverage"] == 1 / 3
|
||||
assert messages["furigana_records"] == 1
|
||||
assert messages["examples"][0]["description"] == "First"
|
||||
matches = profile.find_message_matches(fixture, "first|three")
|
||||
assert [record["id"] for record in matches] == [1, 3]
|
||||
rendered = profile.render_message_matches(fixture, "First")
|
||||
assert "| 1 | one | First |" in rendered
|
||||
assert "`test_record.zero` (`0x30/3/0`)=9" in rendered
|
||||
print("all init_table_profile checks passed")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ address = "0x8e7b9"
|
||||
name = "item_character_whitelist"
|
||||
category = "data-table"
|
||||
type = "int[1000][5]"
|
||||
columns = { "0" = "allowed_character_id_1", "1" = "allowed_character_id_2", "2" = "allowed_character_id_3", "3" = "allowed_character_id_4", "4" = "allowed_character_id_5" }
|
||||
value_domain = "EBINIT character ids; up to five per item"
|
||||
usage = "Sparse ITINIT row-major table with stride 5. CHMENU rejects an item when column 0 is populated and the selected party slot's character id is absent from the row. Unique accessories 471/472 allow one character each, while crossover accessories 480..485 allow character ids 2, 3, and 4. Unused trailing columns remain zero."
|
||||
source = "investigation"
|
||||
@@ -142,15 +143,16 @@ name = "item_sex_restriction_mask"
|
||||
category = "data-table"
|
||||
type = "int[1000]"
|
||||
value_domain = "bit mask; shipped ITINIT populates value 4 on two items"
|
||||
usage = "Sparse ITINIT equipment restriction. CHMENU tests this mask against EBINIT field 0x71eae, whose values partition male, female, and sexless units; both populated items carry bit 2 and are therefore female-only."
|
||||
usage = "Sparse ITINIT equipment restriction. CHMENU uses unit_sex_category as a bit index and rejects an item when that bit is absent from this mask. Both populated items carry only bit 2; EBINIT value 2 is female, and ITMES explicitly describes item 419 as female-only."
|
||||
source = "investigation"
|
||||
confidence = "med"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0x906f9"
|
||||
name = "item_status_delta_levels"
|
||||
category = "data-table"
|
||||
type = "int[1000][30]"
|
||||
columns = { "2" = "hp_drain", "3" = "sp_drain", "4" = "fs_drain", "5" = "curse", "6" = "charm", "7" = "confusion", "8" = "paralysis", "9" = "poison", "10" = "water_flow", "11" = "fear" }
|
||||
value_domain = "condition delta -5..5; zero means absent"
|
||||
usage = "Sparse ITINIT row-major table consumed by USEITEM and CALCILL when applying an item's effects. Positive values inflict or drain; -5 removes a condition (for example paralysis-removal item 108 stores -5 in column 8). Confirmed columns are 2 HP drain, 3 SP drain, 4 FS drain, 5 curse, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear."
|
||||
source = "investigation"
|
||||
@@ -161,6 +163,7 @@ address = "0x97c29"
|
||||
name = "item_equipped_status_levels"
|
||||
category = "data-table"
|
||||
type = "int[1000][30]"
|
||||
columns = { "9" = "poison", "11" = "fear", "13" = "regeneration", "14" = "exaltation" }
|
||||
value_domain = "condition strength 1..5; zero means absent"
|
||||
usage = "Sparse ITINIT row-major table added to a unit's 30-column condition state by CALCREVISE. Item descriptions identify populated columns 9 poison, 11 fear, 13 regeneration, and 14 exaltation; these are passive equipped effects, distinct from item_status_delta_levels."
|
||||
source = "investigation"
|
||||
@@ -171,6 +174,7 @@ address = "0x9f541"
|
||||
name = "item_stat_modifiers"
|
||||
category = "data-table"
|
||||
type = "int[1000][14]"
|
||||
columns = { "0" = "accuracy", "1" = "evasion", "2" = "physical_attack", "3" = "physical_defense", "4" = "magic_attack", "5" = "magic_defense", "6" = "speed", "7" = "luck", "8" = "critical_chance", "9" = "capture_power", "10" = "movement", "11" = "max_hp", "12" = "max_sp", "13" = "max_fs" }
|
||||
value_domain = "signed additive stat values; observed -15..200"
|
||||
usage = "ITINIT row-major equipment modifiers added directly to the unit's 14-column stat record by CALCREVISE. Descriptions and consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, 9 capture power, 10 movement, 11 max HP, 12 max SP, and 13 max FS. CALCBTPARAM adds columns 7 and 8 into the action's critical percentage, and CALCDMG compares the clamped result with a random-modulo-100 roll."
|
||||
source = "investigation"
|
||||
@@ -181,6 +185,7 @@ address = "0xa2bf1"
|
||||
name = "item_tuning_curve_ids"
|
||||
category = "data-table"
|
||||
type = "int[1000][10]"
|
||||
columns = { "0" = "accuracy", "1" = "evasion", "2" = "physical_attack", "3" = "physical_defense", "4" = "magic_attack", "5" = "magic_defense", "6" = "speed", "7" = "luck", "8" = "critical_chance", "9" = "capture_power" }
|
||||
value_domain = "curve ids 1..18; zero means the field cannot be tuned"
|
||||
usage = "ITINIT row-major table selecting an equipment-growth curve for each of the ten tunable fields. TUNE, IMPROVE, DRAWTIP, and CALCREVISE combine each nonzero curve id with the item's corresponding tuning level and index the shared curve-value table at 0xab6fa. Columns align with item_stat_modifiers columns 0..9."
|
||||
source = "investigation"
|
||||
@@ -191,6 +196,7 @@ address = "0xa5301"
|
||||
name = "item_resource_recovery_amounts"
|
||||
category = "data-table"
|
||||
type = "int[1000][3]"
|
||||
columns = { "0" = "hp", "1" = "sp", "2" = "fs" }
|
||||
value_domain = "recovery amount; zero means absent"
|
||||
usage = "Sparse ITINIT row-major consumable table. USEITEM applies columns 0, 1, and 2 to the matching three-column unit resource record; descriptions prove these are HP, SP, and FS respectively (for example item 101 stores HP 30, and item 107 stores 999/99/99 for full recovery)."
|
||||
source = "investigation"
|
||||
@@ -206,6 +212,26 @@ usage = "Sparse ITINIT consumable field. Item 106, Blood Price Healing Hand, des
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0x65ce"
|
||||
name = "skill_acquired_flags"
|
||||
category = "data-table"
|
||||
type = "int[300]"
|
||||
value_domain = "0/1 by SKINIT skill id"
|
||||
usage = "Persistent acquired-skill flags. ADDSKILL sets the selected skill after resolving the unit's equipped-skill slots; FORT checks the flag before granting a skill; CHMENU combines it with skill_change_catalog_eligible to build the available skill-change catalog."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0x5660b"
|
||||
name = "skill_info_revealed_flags"
|
||||
category = "data-table"
|
||||
type = "int[300]"
|
||||
value_domain = "0/1 by SKINIT skill id"
|
||||
usage = "Persistent skill-information visibility flags. ADDSKILL sets the selected skill, BTL marks every equipped skill when it is observed in combat, and INFOIT suppresses a skill's icon/handler-driven details until this flag is nonzero. This is broader than skill_acquired_flags."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0xa6e59"
|
||||
name = "current_skill_id"
|
||||
@@ -236,6 +262,16 @@ usage = "SKINIT category for all 131 skills. The records establish 1 movement/ex
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0xa70b2"
|
||||
name = "skill_change_catalog_eligible"
|
||||
category = "data-table"
|
||||
type = "int[300]"
|
||||
value_domain = "0/1; 11 eligible skills in shipped SKINIT"
|
||||
usage = "SKINIT inclusion flag for CHMENU's fourth (skill-change) catalog. CHMENU scans all 300 ids, keeps only nonzero rows, sorts acquired rows ahead of unavailable rows using skill_category and skill_sort_key, and copies the acquired prefix into catalog row 3. Eligible ids are 1, 2, 112, 130..133, 210, 228, 230, and 231."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0xa71de"
|
||||
name = "skill_min_range_encoded"
|
||||
@@ -271,6 +307,7 @@ address = "0xa7562"
|
||||
name = "skill_status_levels"
|
||||
category = "data-table"
|
||||
type = "int[300][30]"
|
||||
columns = { "1" = "instant_death", "6" = "charm", "7" = "confusion", "8" = "paralysis", "9" = "poison", "10" = "water_flow", "11" = "fear" }
|
||||
value_domain = "condition strength 1..4; zero means absent"
|
||||
usage = "Sparse SKINIT row-major condition table consumed by CALCILL when applying a skill's effects to the target unit. Skill names and descriptions identify columns 1 instant death, 6 charm, 7 confusion, 8 paralysis, 9 poison, 10 water-flow, and 11 fear; the column layout matches item_status_delta_levels."
|
||||
source = "investigation"
|
||||
@@ -291,6 +328,7 @@ address = "0xa99b6"
|
||||
name = "skill_combat_stat_deltas"
|
||||
category = "data-table"
|
||||
type = "int[300][10]"
|
||||
columns = { "0" = "accuracy", "1" = "evasion", "2" = "physical_attack", "3" = "physical_defense", "4" = "magic_attack", "5" = "magic_defense", "6" = "speed", "7" = "luck", "8" = "critical_chance", "9" = "capture_power" }
|
||||
value_domain = "signed combat parameter deltas; observed -99..999"
|
||||
usage = "Sparse SKINIT row-major table applied by CALCBTPARAM for the selected action. Descriptions and combat consumers establish columns 0 accuracy, 1 evasion, 2 physical attack, 3 physical defense, 4 magic attack, 5 magic defense, 6 speed, 7 luck, 8 critical chance, and 9 capture power. Column 7 is unpopulated in shipped SKINIT; CALCBTPARAM adds any luck and critical modifiers into the critical percentage that CALCDMG rolls after the hit check."
|
||||
source = "investigation"
|
||||
@@ -301,6 +339,7 @@ address = "0xaa56e"
|
||||
name = "skill_resource_deltas"
|
||||
category = "data-table"
|
||||
type = "int[300][3]"
|
||||
columns = { "0" = "hp_recovery", "1" = "sp_cost" }
|
||||
value_domain = "column 0 recovery 20..50; column 1 cost -20..-2"
|
||||
usage = "Sparse SKINIT resource table used throughout skill selection and resolution. Column 0 is HP recovery for the three healing spells; column 1 is the negative SP cost for all 95 active skills, exactly matching each description. Column 2 is unpopulated in the shipped table."
|
||||
source = "investigation"
|
||||
|
||||
Reference in New Issue
Block a user