Decode AFINIT and CTINIT data tables
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)
|
||||
|
||||
5048 globals (307 curated, 4741 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
5049 globals (316 curated, 4733 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
|
||||
|
||||
## choice-output
|
||||
|
||||
@@ -20,8 +20,12 @@
|
||||
| address | name | conf | source | usage |
|
||||
|---|---|---|---|---|
|
||||
| `0x671b` | shared_spendable_points | high | investigation | Shared spendable point balance used by the alchemy, study, evolution, and summoning systems. ALCHEMY requires and deducts each recipe's alchemy_recipe_point_costs value; STAGECLEAR awards points to this balance and caps it at 999. The exact player-facing Japanese resource label remains unresolved. |
|
||||
| `0x671c` | item_tuning_facility_level | high | investigation | Current equipment-tuning facility level. IMPROVE indexes facility_level_progress_thresholds row 0 with this value and raises it, up to 6, as item_tuning_facility_progress accumulates. |
|
||||
| `0x671d` | alchemy_level | high | investigation | Current alchemy level. ALCHEMY hides recipes whose alchemy_recipe_minimum_levels value exceeds this level and raises it, up to 6, when alchemy_level_progress reaches the current threshold. |
|
||||
| `0x671e` | magic_facility_level | high | investigation | Current magic/research facility level. MAGIC and USEMAGIC index facility_level_progress_thresholds row 2 with this level; USEMAGIC raises it up to 6 when magic_facility_progress reaches the current threshold. |
|
||||
| `0x671f` | item_tuning_facility_progress | high | investigation | Progress toward the next equipment-tuning facility level. IMPROVE adds the selected item's tuning-level increases, applies row 0 of facility_level_progress_thresholds, and carries or clamps progress when the level rises. |
|
||||
| `0x6720` | alchemy_level_progress | high | investigation | Progress toward the next alchemy level. Each successful ALCHEMY synthesis increments it; reaching the current threshold raises alchemy_level and resets this counter. |
|
||||
| `0x6721` | magic_facility_progress | high | investigation | Progress toward the next magic/research facility level. USEMAGIC adds the selected action's progress award and applies row 2 of facility_level_progress_thresholds; MAGIC renders the same current/threshold pair. |
|
||||
| `0x2052e` | scjump_progress_b | med | inference | Second SCJUMP switch input (1223 comparison reads) — progression counter/position. INFERENCE from SCJUMP usage. |
|
||||
| `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. |
|
||||
@@ -161,7 +165,10 @@
|
||||
| `0xaacf0` | condition_duration_turns_by_level | high | investigation | ILINIT's five-column duration row for each condition. ADDILLSUB indexes it by the resulting condition level minus one and stores the value in entity_condition_remaining_turns. Columns: 0=level_1_turns, 1=level_2_turns, 2=level_3_turns, 3=level_4_turns, 4=level_5_turns. |
|
||||
| `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 with thirteen authored defense rows and eighteen authored cells per row inside the reserved 20-by-20 layout. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases. INFOAF displays eight attack columns for its selected defense rows. |
|
||||
| `0xab6fa` | item_tuning_stat_bonus_curves | high | investigation | Eighteen usable equipment-growth curves selected by item_tuning_curve_ids, plus an explicitly zeroed reserved row 19. TUNE and IMPROVE use the nonzero prefix as the available tuning-level range; CALCREVISE and DRAWTIP add the selected zero-based tuning level's bonus to the corresponding stat. Columns: 0=tuning_level_1, 1=tuning_level_2, 2=tuning_level_3, 3=tuning_level_4, 4=tuning_level_5, 5=tuning_level_6, 6=tuning_level_7, 7=tuning_level_8, 8=tuning_level_9, 9=tuning_level_10, 10=reserved. |
|
||||
| `0xab7d6` | item_tuning_point_cost_curves | high | investigation | Point-cost curves paired by curve id and tuning level with item_tuning_stat_bonus_curves. IMPROVE sums costs for newly selected levels, subtracts refunds for removed levels, checks the resulting total against the tuning-point balance, and deducts it on confirmation. Columns: 0=tuning_level_1, 1=tuning_level_2, 2=tuning_level_3, 3=tuning_level_4, 4=tuning_level_5, 5=tuning_level_6, 6=tuning_level_7, 7=tuning_level_8, 8=tuning_level_9, 9=tuning_level_10, 10=reserved. |
|
||||
| `0xab8b2` | facility_level_progress_thresholds | high | investigation | AFINIT's three facility-progression rows. IMPROVE uses row 0 for equipment tuning, ALCHEMY uses row 1, and MAGIC/USEMAGIC use row 2; each indexes the row by the current level and caps advancement at level 6. Columns: 0=level_0_to_1, 1=level_1_to_2, 2=level_2_to_3, 3=level_3_to_4, 4=level_4_to_5, 5=level_5_to_6, 6=level_6_cap. |
|
||||
| `0xab8c7` | class_change_rule_script_ids | high | investigation | CALCCC iterates these 32 cells and call-scripts every positive entry to evaluate class-change providers. CCINIT is the shipped rule program decoded into build/data/CCINIT.json. |
|
||||
| `0xaba64` | stage_object_runtime_flags | high | investigation | Per-current-stage object flags. RTN_M010 and RTN_M015 require bit 1 before considering Healing Feathers or Magic Pillars as movement targets; FIELD and object rendering maintain the broader type-dependent bitfield. |
|
||||
| `0xaba96` | pathfinding_remaining_route_steps | high | investigation | MVSEEK's row-major reachability grid. Mode 0 searches from the current entity with movement+1 at the origin; modes 1/2 search from the caller-supplied coordinate with 9999 at the origin. Each traversed edge decrements the value, so a larger positive value is nearer to the origin. RTN_M006/007/015 use origin-minus-target values as route-step radii; RTN_M010 ranks Healing Feathers by the negated value. |
|
||||
@@ -244,8 +251,6 @@
|
||||
| `0x69e` | — | low | auto-shape | array |
|
||||
| `0x6fe` | — | low | auto-shape | array |
|
||||
| `0x712` | — | low | auto-shape | array |
|
||||
| `0x2690` | — | low | auto-shape | array |
|
||||
| `0x26a4` | — | low | auto-shape | array |
|
||||
| `0x26b5` | — | low | auto-shape | array |
|
||||
| `0x26d3` | — | low | auto-shape | array |
|
||||
| `0x27bd` | — | low | auto-shape | array |
|
||||
@@ -260,8 +265,6 @@
|
||||
| `0x3ad6` | — | low | auto-shape | array |
|
||||
| `0x4315` | — | low | auto-shape | array |
|
||||
| `0x4379` | — | low | auto-shape | array |
|
||||
| `0x671c` | — | low | auto-shape | array |
|
||||
| `0x671f` | — | low | auto-shape | array |
|
||||
| `0x6727` | — | low | auto-shape | array |
|
||||
| `0x6804` | — | low | auto-shape | array |
|
||||
| `0x6868` | — | low | auto-shape | array |
|
||||
@@ -438,7 +441,6 @@
|
||||
| `0x3231` | game_mode | med | inference | Game-mode/phase selector in the 0x3231-0x3234 progression-state cluster (chapter_mode is 0x3234). Enum 1..9, written by the gameplay scripts (AIM/ALCHEMY/BTL/BUNKI), branch-read in 136 scenes. Distinct from chapter; likely current sub-mode/screen. INFERENCE — confirm with a listing/sweep before relying on the exact meaning. |
|
||||
| `0x3234` | chapter_mode | high | investigation | Progression chapter/mode selector. SCJUMP's top-level switch keys on it; branch-read by progression scripts (FIELD etc.), not directly by SC/SP scenes. |
|
||||
| `0x3275` | — | med | auto-shape | TODO: confirm. Branch-read in 136 scenes / 143 scripts; compared against [0]; writers=['CAMP.BIN', 'DEBUGADV.BIN', 'DEBUGADV2.BIN', 'FIELD.BIN']. |
|
||||
| `0x671e` | — | med | auto-shape | TODO: confirm. Branch-read in 0 scenes / 3 scripts; compared against [5, 6]; writers=['USEMAGIC.BIN']. |
|
||||
| `0x6722` | — | med | auto-shape | TODO: confirm. Branch-read in 13 scenes / 18 scripts; compared against [0, 1, 5, 99]; writers=['GAMESTART.BIN', 'SALLY.BIN', 'SC0010.BIN', 'SC0140.BIN']. |
|
||||
| `0x6724` | — | med | auto-shape | TODO: confirm. Branch-read in 4 scenes / 10 scripts; compared against [0, 25, 50, 99]; writers=['DEBUGADV2.BIN', 'DEBUGMAP.BIN', 'DEBUGMAP2.BIN', 'DEBUGMAP3.BIN']. |
|
||||
| `0x7679` | — | med | auto-shape | TODO: confirm. Branch-read in 0 scenes / 4 scripts; compared against [0, 1, 4, 8, 16, 256, 512, 1024]; writers=['FIELD.BIN']. |
|
||||
@@ -475,6 +477,8 @@
|
||||
| `0x7db` | modal_message_lines | high | investigation | Shared ten-string modal buffer. Producers append messages or menu options at modal_message_line_count; MES measures and draws each string, and SBUNKI reuses the same entries for an interactive selection list. |
|
||||
| `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. |
|
||||
| `0x2690` | attack_element_names | high | investigation | AFINIT attack-side affinity vocabulary. DRAWTIP indexes ids 1..8 from ITINIT, while INFOAF displays those same eight ordinary attack elements above attack_element_effectiveness_percent. Columns: 1=physical, 2=universal, 3=fire, 4=ice, 5=lightning, 6=earth, 7=holy, 8=dark, 11=resistance_1, 12=resistance_2, 13=resistance_3, 14=resistance_4, 15=resistance_5, 16=resistance_6, 17=resistance_7. |
|
||||
| `0x26a4` | defense_element_names | high | investigation | AFINIT defense-side affinity vocabulary. DRAWENP and DRAWTIP index it with unit/item defense element ids; the same id selects a row of attack_element_effectiveness_percent. Columns: 1=physical, 2=universal, 3=fire, 4=ice, 5=lightning, 6=earth, 7=holy, 8=dark, 9=divinity, 10=demon, 11=spirit, 12=undead. |
|
||||
| `0x26b4` | class_change_title_output | high | investigation | CCINIT writes the title selected by each eligible class-change rule. CALCCC copies it to unit_class_titles for a successful promotion, and ADDEXP includes the same string in the level-up notification. |
|
||||
| `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. |
|
||||
@@ -482,6 +486,7 @@
|
||||
| `0x27ba` | stage_victory_condition_2 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders nonempty values after stage_victory_condition_1, and FIELD copies the slot into the current mission-condition display. |
|
||||
| `0x27bb` | stage_defeat_condition_1 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders this line first in the defeat-condition section, and FIELD copies it into the current mission-condition display. |
|
||||
| `0x27bc` | stage_defeat_condition_2 | high | investigation | STINIT writes one value for each of its 74 stage records. AIM renders nonempty values after stage_defeat_condition_1, and FIELD copies the slot into the current mission-condition display. |
|
||||
| `0x43dd` | name_entry_character_palette | high | investigation | INPUTNAME's five 70-cell character pages: row 0 hiragana, row 1 katakana, row 2 full-width Latin letters, row 3 Arabic/Kanji/Roman/circled numerals, and row 4 symbols. Cursor slots 70..74 select the page; selecting a populated cell copies it into the seven-character name buffer. |
|
||||
| `0x45b9` | magic_action_names | high | investigation | MAINIT's one-based action-name array: eleven shipped records occupy ids 1..11 in a reserved 30-cell span. MAGIC, STUDY, and EVOLVE render the selected entry. |
|
||||
| `0x45d7` | character_profile_names | high | investigation | CIINIT writes the character-information screen's 24 display names. INFOCH lists them by profile id, masks unrevealed rows, and uses the same id to select the backing unit metadata and CIMES biography. |
|
||||
| `0x463b` | glossary_topic_titles | high | investigation | VIINIT writes the sparse glossary title table. INFOVO renders the selected title, and extract_init joins all 65 populated ids to VIMES's full help text. |
|
||||
@@ -3228,7 +3233,6 @@
|
||||
| `0x43c8` | — | med | auto-shape | string-table (written by CDINIT2) |
|
||||
| `0x43c9` | — | med | auto-shape | string-table (written by CDINIT2) |
|
||||
| `0x43ca` | — | med | auto-shape | string-table (written by CDINIT2) |
|
||||
| `0x43dd` | — | med | auto-shape | string-table (written by CTINIT) |
|
||||
| `0x43de` | — | med | auto-shape | string-table (written by CTINIT) |
|
||||
| `0x43df` | — | med | auto-shape | string-table (written by CTINIT) |
|
||||
| `0x43e0` | — | med | auto-shape | string-table (written by CTINIT) |
|
||||
@@ -5041,9 +5045,6 @@
|
||||
| `0x6638b` | — | med | auto-shape | record-table[stride 15] |
|
||||
| `0x81c96` | — | med | auto-shape | record-table[stride 3] |
|
||||
| `0x8284e` | — | med | auto-shape | record-table[stride 3] |
|
||||
| `0xab6fa` | — | med | auto-shape | record-table[stride 11] |
|
||||
| `0xab7d6` | — | med | auto-shape | record-table[stride 11] |
|
||||
| `0xab8b2` | — | med | auto-shape | record-table[stride 7] |
|
||||
| `0xc6077` | — | med | auto-shape | record-table[stride 27] |
|
||||
| `0xcc9f0` | — | low | auto-shape | index/counter? |
|
||||
| `0xccbdc` | — | med | auto-shape | record-table[stride 14] |
|
||||
|
||||
@@ -669,15 +669,16 @@ are *not* story flags — the miner over-tags them; they are recategorized `unkn
|
||||
|
||||
The v1 map labels *shapes and tables*; the next increments add *meaning*, cheapest first:
|
||||
|
||||
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, EBINIT, STINIT, CCINIT, SCINIT, RTINIT, MAINIT, ILINIT, CGINIT, and ALINIT now have
|
||||
1. **Continue INIT semantics by evidence density.** ITINIT/SKINIT, EBINIT, STINIT, CCINIT, SCINIT, RTINIT, MAINIT, ILINIT, CGINIT, ALINIT, AFINIT, and CTINIT now have
|
||||
machine-readable investigation surfaces and semantic joins; EBINIT's populated schema is fully named,
|
||||
STINIT's joined object/enemy payloads are decoded, and CCINIT's 71 class-change rules expose predicates
|
||||
and effects. SCINIT closes the progression decision-to-scene join, and RTINIT's twenty movement/battle
|
||||
banks are structurally decoded with every populated movement-parameter cell classified and all 1,043
|
||||
shipped movement steps joined to provider behavior. ILINIT/RECOVER closes the condition ABI, CGINIT
|
||||
closes the gallery registry, and ALINIT closes the 107-recipe alchemy registry with complete ITINIT
|
||||
joins. With RTINIT's used-provider surface closed and ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and
|
||||
INFOMES/MES classified, audit the malformed AFINIT/CTINIT outputs next;
|
||||
closes the gallery registry, ALINIT closes the 107-recipe alchemy registry with complete ITINIT
|
||||
joins, AFINIT closes the signed affinity/tuning/facility tables, and CTINIT closes INPUTNAME's
|
||||
five-page character palette. With RTINIT's used-provider surface closed and
|
||||
ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined and INFOMES/MES classified, audit CVINIT next;
|
||||
never assign one universal meaning to a parameter bank whose meaning varies by provider selector.
|
||||
2. **Label remaining 2D record tables by their readers** — cross-reference which scripts read each
|
||||
`rec[sN]` table and infer purpose from context, preserving reserved rows and sparse cells. RECOVER's
|
||||
|
||||
@@ -2900,5 +2900,31 @@ now names the shared spendable-point balance, alchemy level, and alchemy-level p
|
||||
protect the full write accounting, sparse layout, item joins, story gates, paired ingredient cells, and
|
||||
raw-to-semantic projection.
|
||||
|
||||
**Next:** audit the remaining small malformed INIT outputs, especially AFINIT and CTINIT, before
|
||||
spending a larger slice on structurally regular but semantically shallow MPINIT.
|
||||
## Data-semantics sidebar: AFINIT affinity/tuning tables and CTINIT name palette (2026-07-23)
|
||||
|
||||
AFINIT and CTINIT were not malformed record tables; both were vocabulary/table initializers that the
|
||||
generic name heuristic could not segment. AFINIT contains 27 Japanese element labels and 54
|
||||
length-prefixed integer rows. Its affinity section is a reserved `20 × 20` defense-by-attack matrix
|
||||
with thirteen authored defense rows and eighteen authored columns. The dedicated extractor converts
|
||||
the footer's unsigned representations back to signed values, preserving the `-100` immunity cases,
|
||||
and joins rows 1..12 to the physical/universal/elemental/divinity/demon/spirit/undead defense names.
|
||||
|
||||
AFINIT's remaining rows are two paired `20 × 11` item-tuning tables and one `3 × 7` facility
|
||||
progression table. Curve ids 1..18 provide ten tuning-level stat bonuses and matching point costs;
|
||||
row 19 is explicitly zero/reserved and the eleventh column remains reserved. TUNE, IMPROVE, DRAWTIP,
|
||||
and CALCREVISE establish the bonus/cost contract. The three facility rows belong to equipment tuning,
|
||||
alchemy, and magic/research respectively, each with six thresholds leading to the level-6 cap. The
|
||||
registry now also names the equipment-tuning and magic facility level/progress counters.
|
||||
|
||||
CTINIT is INPUTNAME's complete `5 × 70` character palette. Its rows are hiragana, katakana, full-width
|
||||
Latin letters, four numeral styles, and symbols; their populated counts are 56, 56, 52, 40, and 69,
|
||||
for 273 authored cells total. INPUTNAME uses cursor slots 70..74 to select a page, rejects an empty
|
||||
cell, and copies a selected character into its seven-character name buffer. The generated JSON keeps
|
||||
all 350 positions so layout gaps remain explicit rather than collapsing into a flat character list.
|
||||
|
||||
Regressions account for every instruction in both scripts, signed affinity values, all tuning and
|
||||
facility curves, representative characters and gaps, and raw-to-semantic projection.
|
||||
|
||||
**Next:** audit CVINIT. Its generic output is already small (37 writes, thirteen rows, ten apparent
|
||||
fields), so direct consumer tracing should quickly distinguish a valid sparse registry from the last
|
||||
remaining ownership artifact before moving to MPINIT.
|
||||
|
||||
@@ -108,6 +108,13 @@ layout, backed by output item, minimum level, required/forbidden story flags, po
|
||||
ingredient-id/quantity slots. The dedicated extraction classifies every write, joins all 107 output
|
||||
items and 286 ingredient references to ITINIT, and retains the seven raw table coordinates.
|
||||
|
||||
AFINIT is an affinity and progression initializer, not a name-record table. Its 27 strings form sparse
|
||||
attack- and defense-element vocabularies; its 54 footer copies form a signed `20 × 20` effectiveness
|
||||
matrix (thirteen by eighteen cells authored), eighteen usable item-tuning bonus/cost curves plus a
|
||||
reserved zero row, and equipment-tuning/alchemy/magic facility progression thresholds. CTINIT is
|
||||
INPUTNAME's separate `5 × 70` character palette: 273 authored hiragana, katakana, Latin, numeral, and
|
||||
symbol cells with all reserved gaps retained.
|
||||
|
||||
RTINIT is a seventh banked shape. Its 3,336 writes populate twenty parallel `1000 × 20` banks: ten
|
||||
movement banks consumed by MVRTN/RTN_M providers and ten battle banks consumed by BTRTN/RTN_B providers.
|
||||
The generated 172 routine-set records assemble 1,043 movement steps and fourteen battle steps, join
|
||||
|
||||
@@ -57,7 +57,7 @@ All opcode knowledge (ABI, semantics, provenance, `depends_on`) is hand-edited *
|
||||
| `extract_message_table.py` | Discover repeated global-id dispatch chains in either fallthrough-body (ITMES/SKMES/MAMES) or branch-target-body (VIMES/EIMES/CIMES) form, reconstruct player-facing text and furigana, preserve guard/body offsets, and emit an ID-keyed message table. Title-bearing tables expose `title`/`description`; EIMES exposes its untitled lines as `summary`/`strategy`; CIMES exposes one untitled multiline `biography`; MAMES exposes untitled `description` text. | `extract_message_table.py <MES> [OUTNAME]` | `<MES>.BIN` → `build/data/<OUTNAME>.json` |
|
||||
| `extract_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}` |
|
||||
| `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based, mixed selector-dispatched, CCINIT rule, SCINIT dispatch, RTINIT banked, ILINIT condition-matrix, CNINIT unit-name/voice-family, CGINIT gallery, and ALINIT alchemy-recipe extraction; RECOVER ABI validation; MES reconstruction/joins; footer-array accounting and semantic projection; EBINIT/SKINIT/OBINIT definition joins; STINIT object/enemy joins and tagged-payload behavior; overwrite/provider joins; and field/message/rule/dispatch/banked profiling. | run each directly | — |
|
||||
| `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based, mixed selector-dispatched, CCINIT rule, SCINIT dispatch, RTINIT banked, ILINIT condition-matrix, CNINIT unit-name/voice-family, CGINIT gallery, ALINIT alchemy-recipe, AFINIT affinity/progression, and CTINIT name-entry-palette extraction; RECOVER ABI validation; MES reconstruction/joins; footer-array accounting and semantic projection; EBINIT/SKINIT/OBINIT definition joins; STINIT object/enemy joins and tagged-payload behavior; overwrite/provider joins; and field/message/rule/dispatch/banked profiling. | run each directly | — |
|
||||
| `global_map.py` | Build the partial global-variable name map from static evidence. | `global_map.py` | corpus + `build/data/` → `build/global-var-map.{json,md}` |
|
||||
|
||||
`extract_init.py` also has name-mode specializations for VIINIT's sparse 200-row glossary table,
|
||||
@@ -86,6 +86,12 @@ required and forbidden story flags, point cost, and four paired ingredient-id/qu
|
||||
and ingredient ids must resolve through ITINIT. The generated record adds a nested `ingredients` view
|
||||
while retaining every raw scalar and `base/stride/column` coordinate.
|
||||
|
||||
Name-mode AFINIT and CTINIT bypass the generic name-record heuristic because their strings are
|
||||
vocabularies rather than record boundaries. AFINIT classifies its 27 element labels and 54 footer
|
||||
copies into the signed affinity matrix, paired item-tuning bonus/cost curves, and three facility
|
||||
progression rows. CTINIT emits five reserved 70-cell INPUTNAME pages with 273 authored characters and
|
||||
every empty slot preserved. Both retain raw global/stride/footer provenance beside their semantic views.
|
||||
|
||||
## VM
|
||||
|
||||
| Tool | Purpose | Run | Reads → Writes |
|
||||
|
||||
@@ -53,14 +53,15 @@
|
||||
|
||||
- [x] **2.0 — Project structure.** Established `docs/`, `build/{disasm,text,data,scripts-json}/`, `godot/`; game install stays read-only in place. Also relaxed the loader magic check to the `SYS4` family (`SYS4424` patch scripts now parse — was silently skipping 5 scripts).
|
||||
- [x] **2.1 — Text corpora.** `tools/extract_phase2.py` → 481/481 scripts: full disassembly (`build/disasm/*.asm`), per-script strings, `build/text/dialogue.jsonl` (**30,057 show-text lines** — the translation corpus), `build/text/strings.jsonl` (38,449 strings tagged by source opcode), `build/manifest.json`.
|
||||
- [x] **2.2 — `*INIT` data and rule sources → JSON.** `tools/extract_init.py` auto-detects shape (`name`/`numeric`/`footer`/`mixed`/`rules`/`dispatch`/`banked`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions), CIINIT (24 character-information profiles)** [name: sparse one-based name/description/fields, with CIINIT's dedicated profile registry], **CGINIT (851 sparse gallery images)** [numeric: full image, optional save/stage preview, thumbnail sheet/slot, and variant ordinal in a reserved 2,000-row layout], **ALINIT (107 sparse alchemy recipes)** [numeric: output, level/story gates, point cost, and four paired ingredient slots in a reserved 1,000-row layout], **MPINIT (1472 map records)** [footer: length-prefixed arrays], **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays], **CCINIT (71 class-change rules over 33 units)** [rules: unit/level/state predicates plus title/cost/stat/skill effects], **SCINIT (1,209 final decision rows)** [dispatch: scene resource plus authored chapter metadata and overwrite history], and **RTINIT (172 routine sets)** [banked: twenty movement/battle step banks, provider joins, and overwrite history]. Validated; see `build/data/README.md`. Raw addresses remain bytecode provenance; confirmed semantics come from `vm-map/globals.toml`.
|
||||
- [x] **2.2 — `*INIT` data and rule sources → JSON.** `tools/extract_init.py` auto-detects shape (`name`/`numeric`/`footer`/`mixed`/`rules`/`dispatch`/`banked`) → **SKINIT (131 skills), ITINIT (287 items), EBINIT (277 units), OBINIT (46 object definitions), CIINIT (24 character-information profiles)** [name: sparse one-based name/description/fields, with CIINIT's dedicated profile registry], **CGINIT (851 sparse gallery images)** [numeric: full image, optional save/stage preview, thumbnail sheet/slot, and variant ordinal in a reserved 2,000-row layout], **ALINIT (107 sparse alchemy recipes)** [numeric: output, level/story gates, point cost, and four paired ingredient slots in a reserved 1,000-row layout], **AFINIT (13 authored affinity rows plus tuning/facility curves)** [name/footer specialization], **CTINIT (five 70-cell name-entry pages)** [string-matrix specialization], **MPINIT (1472 map records)** [footer: length-prefixed arrays], **STINIT (74 stages)** [mixed: selector-dispatched strings/scalars/buffer cells/footer arrays], **CCINIT (71 class-change rules over 33 units)** [rules: unit/level/state predicates plus title/cost/stat/skill effects], **SCINIT (1,209 final decision rows)** [dispatch: scene resource plus authored chapter metadata and overwrite history], and **RTINIT (172 routine sets)** [banked: twenty movement/battle step banks, provider joins, and overwrite history]. Validated; see `build/data/README.md`. Raw addresses remain bytecode provenance; confirmed semantics come from `vm-map/globals.toml`.
|
||||
- [x] **2.3 — Extract `STINIT`'s 74 sparse stage records.** The mixed mode identifies the dominant `scjump_progress_a` dispatch, recovers 29 preallocated buffer layouts (including six consumer-confirmed row strides), and keeps four condition strings, six scalars, fixed-buffer writes, and all 1,396 footer-array copies separated by stage id. Profiles supply population/value and direct-consumer evidence. Header/map semantics cover conditions, BGM, turn/replay/clear-reward settings, and map texture overrides. The object join assembles 2,312 placements with type/position/gates, OBINIT names and available descriptions, 604 reinforcement schedules, and typed initial-faction, teleport, treasure, card-list, non-triggering-faction, and initial-object-state payloads; FIELD's dedicated special-spawn path proves the final three populated type-27 tagged writes are engine-dead, so they remain visible as ignored provenance rather than unresolved semantics. The 30-cell enemy family contributes 1,378 joined spawns with unit/faction, placement, difficulty/story gates, level scaling, weighted selection, difficulty-specific movement/battle routine sets, and 485 first-clear-only gates. Raw address views remain alongside generated `semantic_fields`.
|
||||
- [x] **2.3a — Extract `CCINIT`'s 71 class-change rules.** Rules mode preserves source-order guards and detects the unit id, minimum level, clear applied-state slot, selected title/level, deployment-cost delta, named fourteen-stat bonuses, awarded SKINIT skills, and state slot set by each rule. EBINIT/SKINIT definition joins and the common global semantic projection coexist with raw addresses. CALCCC/ADDEXP establish the apply/report ABI; EVOLVE explains the two level-independent empty-title Lily rules. The generated profile covers 33 units, 69 titled rules, 30 skill awards, three used promotion slots, and 19 populated output fields.
|
||||
- [x] **2.3b — Extract `SCINIT`'s scene-dispatch registry.** Dispatch mode recognizes 2,179 alternating writes to two 10,000-cell arrays, preserves all 710 overwritten decision ids, and emits 1,209 final rows. The primary column maps decisions to 135 packed SYS4INI ids, all resolved to numbered SC scripts; the parallel column is authored chapter metadata. All 847 live SCJUMP decisions join to rows and 844 final chapter tags agree with the independently decoded paths; three legacy/stale mismatches remain explicit.
|
||||
- [x] **2.3c — Extract `RTINIT`'s routine-step banks.** Banked mode recognizes twenty parallel 1000-by-20 tables and emits 172 sparse routine-set rows with all 3,336 source assignments and 3,307 final cells. Movement banks join all 1,043 steps to all 19 used RTN_M providers (22 dispatchable); battle banks join fourteen steps to all four RTN_B providers. Activation percentages, progress gates, and required/forbidden story flags are consumer-proven; six empty banks remain explicit. RTN_M001/002/003/004/005/006/007/008/009/010/011/012/013/014/015/017/051/052/061 add selector-specific progress, randomized roaming, object-slot, coordinate, enemy/ally, treasure and object search, Healing Feather, waypoint, faction-terrain, retreat, normal-attack routing, offensive target/action selection, and immediate allied-healing semantics. All 977 populated movement-parameter cells are classified: 974 semantic inputs and three M001/M008 cells proven unread; thirteen unwritten defaults are projected separately while the generic raw banks remain intact.
|
||||
- [x] **2.3d — Extract `ALINIT`'s alchemy recipes.** Numeric specialization recognizes seven recipe-indexed structures and emits 107 sparse recipes from all 914 static writes. ALCHEMY proves the output, minimum level, required/forbidden story gates, point cost, and four paired ingredient-id/quantity slots. Every output and all 286 ingredient references join to ITINIT; raw coordinates remain beside the semantic and nested ingredient views.
|
||||
- [x] **2.3e — Extract `AFINIT` affinity/progression data and `CTINIT` name-entry characters.** AFINIT classifies 27 element strings and 54 footer arrays into thirteen signed effectiveness rows, paired tuning bonus/cost curves, and three facility-progress rows. CTINIT preserves 273 authored characters and 77 empty cells across INPUTNAME's five reserved 70-cell pages. Both schemas account for every instruction and retain raw coordinates.
|
||||
- [x] **2.4 — Partial global-var map BUILT + wired into the disassembler.** `tools/global_map.py` → `build/global-var-map.{json,md}` (16,354/49,435 globals labelled: string tables, `*INIT` field arrays, 122 record tables w/ strides, current-entity index pointers). `sys4load` renders the labels inline (`=rec[s30]`, `=current-entity-index?`). See `docs/name-resolution.md`.
|
||||
- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI and ALINIT recipe registry are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, audit the malformed small AFINIT/CTINIT outputs by their readers, then use Frida only for semantics static consumers cannot settle. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved.
|
||||
- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, and CTINIT name palette are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, audit CVINIT's small thirteen-row output, then use Frida only for semantics static consumers cannot settle. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved.
|
||||
|
||||
## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)*
|
||||
|
||||
|
||||
@@ -599,6 +599,33 @@ ALCHEMY_RECIPE_RECORD_SPAN = 1000
|
||||
ALCHEMY_RECIPE_STORY_FLAG_STRIDE = 2
|
||||
ALCHEMY_RECIPE_INGREDIENT_STRIDE = 4
|
||||
|
||||
AFFINITY_ATTACK_ELEMENT_NAME_BASE = 0x2690
|
||||
AFFINITY_DEFENSE_ELEMENT_NAME_BASE = 0x26A4
|
||||
AFFINITY_ELEMENT_NAME_SPAN = 20
|
||||
AFFINITY_EFFECTIVENESS_BASE = 0xAB5BA
|
||||
AFFINITY_EFFECTIVENESS_STRIDE = 20
|
||||
AFFINITY_EFFECTIVENESS_ROW_COUNT = 13
|
||||
AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS = 18
|
||||
ITEM_TUNING_BONUS_CURVE_BASE = 0xAB6FA
|
||||
ITEM_TUNING_COST_CURVE_BASE = 0xAB7D6
|
||||
ITEM_TUNING_CURVE_STRIDE = 11
|
||||
ITEM_TUNING_CURVE_COUNT = 19
|
||||
ITEM_TUNING_AUTHORED_LEVELS = 10
|
||||
FACILITY_LEVEL_THRESHOLD_BASE = 0xAB8B2
|
||||
FACILITY_LEVEL_THRESHOLD_STRIDE = 7
|
||||
FACILITY_LEVEL_THRESHOLD_ROW_COUNT = 3
|
||||
FACILITY_LEVEL_THRESHOLD_AUTHORED_LEVELS = 6
|
||||
|
||||
NAME_ENTRY_CHARACTER_PALETTE_BASE = 0x43DD
|
||||
NAME_ENTRY_CHARACTER_PALETTE_STRIDE = 70
|
||||
NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES = (
|
||||
"hiragana",
|
||||
"katakana",
|
||||
"latin",
|
||||
"numerals",
|
||||
"symbols",
|
||||
)
|
||||
|
||||
RECOVER_CURRENT_ENTITY = 0x152616
|
||||
RECOVER_EFFECTIVE_STATS = 0x4E11B
|
||||
RECOVER_CURRENT_RESOURCES = 0x4E085
|
||||
@@ -2257,6 +2284,424 @@ def extract_alchemy_recipes(scr):
|
||||
}
|
||||
|
||||
|
||||
def extract_affinity_definitions(scr):
|
||||
"""Extract AFINIT's element, tuning-curve, and facility-threshold tables."""
|
||||
attack_names = {}
|
||||
defense_names = {}
|
||||
effectiveness_rows = {}
|
||||
tuning_bonus_rows = {}
|
||||
tuning_cost_rows = {}
|
||||
facility_threshold_rows = {}
|
||||
string_write_count = 0
|
||||
footer_array_count = 0
|
||||
exit_count = 0
|
||||
|
||||
def signed_values(values):
|
||||
return [
|
||||
value - 0x100000000 if value >= 0x80000000 else value
|
||||
for value in values
|
||||
]
|
||||
|
||||
for ins in scr.instructions:
|
||||
if (
|
||||
ins.opcode == SET_STRING
|
||||
and len(ins.args) >= 2
|
||||
and ins.args[0][0] == T_GLOBAL_STRING
|
||||
):
|
||||
destination = ins.args[0][1]
|
||||
text = scr.strings.get(ins.args[1][1], (None,))[0]
|
||||
for base, target in (
|
||||
(AFFINITY_ATTACK_ELEMENT_NAME_BASE, attack_names),
|
||||
(AFFINITY_DEFENSE_ELEMENT_NAME_BASE, defense_names),
|
||||
):
|
||||
element_id = destination - base
|
||||
if 0 <= element_id < AFFINITY_ELEMENT_NAME_SPAN:
|
||||
_store_unique(target, element_id, text, element_id)
|
||||
string_write_count += 1
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: unexpected string destination "
|
||||
f"0x{destination:x}"
|
||||
)
|
||||
continue
|
||||
|
||||
if (
|
||||
ins.opcode == COPY_LOCAL_ARRAY
|
||||
and len(ins.args) >= 2
|
||||
and ins.args[0][0] == T_GLOBAL_INT
|
||||
and ins.args[1][0] == T_IMM
|
||||
):
|
||||
destination = ins.args[0][1]
|
||||
footer_off = ins.args[1][1]
|
||||
values = read_footer_array(scr, footer_off)
|
||||
if values is None:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: invalid footer array 0x{footer_off:x}"
|
||||
)
|
||||
values = signed_values(values)
|
||||
classified = False
|
||||
|
||||
relative = destination - AFFINITY_EFFECTIVENESS_BASE
|
||||
if (
|
||||
relative % AFFINITY_EFFECTIVENESS_STRIDE == 0
|
||||
and 0 <= relative
|
||||
< AFFINITY_EFFECTIVENESS_ROW_COUNT
|
||||
* AFFINITY_EFFECTIVENESS_STRIDE
|
||||
):
|
||||
row = relative // AFFINITY_EFFECTIVENESS_STRIDE
|
||||
if len(values) != AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: effectiveness row {row} has "
|
||||
f"{len(values)} values"
|
||||
)
|
||||
_store_unique(
|
||||
effectiveness_rows, row, (footer_off, values), row
|
||||
)
|
||||
classified = True
|
||||
|
||||
if not classified:
|
||||
for base, target in (
|
||||
(ITEM_TUNING_BONUS_CURVE_BASE, tuning_bonus_rows),
|
||||
(ITEM_TUNING_COST_CURVE_BASE, tuning_cost_rows),
|
||||
):
|
||||
relative = destination - base
|
||||
if (
|
||||
relative % ITEM_TUNING_CURVE_STRIDE == 0
|
||||
and ITEM_TUNING_CURVE_STRIDE
|
||||
<= relative
|
||||
<= ITEM_TUNING_CURVE_COUNT
|
||||
* ITEM_TUNING_CURVE_STRIDE
|
||||
):
|
||||
curve_id = relative // ITEM_TUNING_CURVE_STRIDE
|
||||
if len(values) != ITEM_TUNING_AUTHORED_LEVELS:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: tuning curve {curve_id} has "
|
||||
f"{len(values)} values"
|
||||
)
|
||||
_store_unique(
|
||||
target, curve_id, (footer_off, values), curve_id
|
||||
)
|
||||
classified = True
|
||||
break
|
||||
|
||||
if not classified:
|
||||
relative = destination - FACILITY_LEVEL_THRESHOLD_BASE
|
||||
if (
|
||||
relative % FACILITY_LEVEL_THRESHOLD_STRIDE == 0
|
||||
and 0 <= relative
|
||||
< FACILITY_LEVEL_THRESHOLD_ROW_COUNT
|
||||
* FACILITY_LEVEL_THRESHOLD_STRIDE
|
||||
):
|
||||
row = relative // FACILITY_LEVEL_THRESHOLD_STRIDE
|
||||
if len(values) != FACILITY_LEVEL_THRESHOLD_AUTHORED_LEVELS:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: facility row {row} has "
|
||||
f"{len(values)} values"
|
||||
)
|
||||
_store_unique(
|
||||
facility_threshold_rows, row, (footer_off, values), row
|
||||
)
|
||||
classified = True
|
||||
|
||||
if not classified:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: unexpected footer destination "
|
||||
f"0x{destination:x}"
|
||||
)
|
||||
footer_array_count += 1
|
||||
continue
|
||||
|
||||
if sys4load.display_label(ins.opcode) == "exit":
|
||||
exit_count += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: unexpected opcode "
|
||||
f"{sys4load.display_label(ins.opcode)} at 0x{ins.offset:x}"
|
||||
)
|
||||
|
||||
expected_effectiveness_rows = set(range(AFFINITY_EFFECTIVENESS_ROW_COUNT))
|
||||
expected_tuning_curves = set(range(1, ITEM_TUNING_CURVE_COUNT + 1))
|
||||
expected_facility_rows = set(range(FACILITY_LEVEL_THRESHOLD_ROW_COUNT))
|
||||
if set(effectiveness_rows) != expected_effectiveness_rows:
|
||||
raise ValueError(f"{scr.path.name}: incomplete effectiveness matrix")
|
||||
if (
|
||||
set(tuning_bonus_rows) != expected_tuning_curves
|
||||
or set(tuning_cost_rows) != expected_tuning_curves
|
||||
):
|
||||
raise ValueError(f"{scr.path.name}: incomplete tuning curves")
|
||||
if set(facility_threshold_rows) != expected_facility_rows:
|
||||
raise ValueError(f"{scr.path.name}: incomplete facility thresholds")
|
||||
if exit_count != 1:
|
||||
raise ValueError(f"{scr.path.name}: expected one exit, got {exit_count}")
|
||||
|
||||
records = []
|
||||
for defense_element_id in sorted(effectiveness_rows):
|
||||
footer_off, values = effectiveness_rows[defense_element_id]
|
||||
record = {
|
||||
"id": defense_element_id,
|
||||
"name": defense_names.get(defense_element_id, ""),
|
||||
"defense_element_id": defense_element_id,
|
||||
"footer_arrays": {
|
||||
(
|
||||
f"0x{AFFINITY_EFFECTIVENESS_BASE:x}/"
|
||||
f"{defense_element_id * AFFINITY_EFFECTIVENESS_STRIDE}"
|
||||
): {
|
||||
"footer_off": f"0x{footer_off:x}",
|
||||
"values": values,
|
||||
}
|
||||
},
|
||||
"attack_effectiveness": [
|
||||
{
|
||||
"attack_element_id": attack_element_id,
|
||||
"attack_element_name": attack_names.get(
|
||||
attack_element_id, ""
|
||||
),
|
||||
"percent": percent,
|
||||
}
|
||||
for attack_element_id, percent in enumerate(values)
|
||||
],
|
||||
}
|
||||
if record["name"]:
|
||||
record["string_fields"] = {
|
||||
f"0x{AFFINITY_DEFENSE_ELEMENT_NAME_BASE:x}": record["name"]
|
||||
}
|
||||
records.append(record)
|
||||
|
||||
tuning_curves = []
|
||||
for curve_id in sorted(tuning_bonus_rows):
|
||||
bonus_footer_off, bonuses = tuning_bonus_rows[curve_id]
|
||||
cost_footer_off, costs = tuning_cost_rows[curve_id]
|
||||
tuning_curves.append({
|
||||
"curve_id": curve_id,
|
||||
"level_bonuses": bonuses,
|
||||
"level_costs": costs,
|
||||
"bonus_raw_key": (
|
||||
f"0x{ITEM_TUNING_BONUS_CURVE_BASE:x}/"
|
||||
f"{curve_id * ITEM_TUNING_CURVE_STRIDE}"
|
||||
),
|
||||
"bonus_footer_off": f"0x{bonus_footer_off:x}",
|
||||
"cost_raw_key": (
|
||||
f"0x{ITEM_TUNING_COST_CURVE_BASE:x}/"
|
||||
f"{curve_id * ITEM_TUNING_CURVE_STRIDE}"
|
||||
),
|
||||
"cost_footer_off": f"0x{cost_footer_off:x}",
|
||||
})
|
||||
|
||||
facility_names = ("item_tuning", "alchemy", "magic")
|
||||
facility_thresholds = []
|
||||
for row in sorted(facility_threshold_rows):
|
||||
footer_off, thresholds = facility_threshold_rows[row]
|
||||
facility_thresholds.append({
|
||||
"system_id": row,
|
||||
"system": facility_names[row],
|
||||
"level_progress_thresholds": thresholds,
|
||||
"raw_key": (
|
||||
f"0x{FACILITY_LEVEL_THRESHOLD_BASE:x}/"
|
||||
f"{row * FACILITY_LEVEL_THRESHOLD_STRIDE}"
|
||||
),
|
||||
"footer_off": f"0x{footer_off:x}",
|
||||
})
|
||||
|
||||
return records, {
|
||||
"schema": "affinity-and-progression-tables",
|
||||
"attack_element_names": [
|
||||
{"id": element_id, "name": name}
|
||||
for element_id, name in sorted(attack_names.items())
|
||||
],
|
||||
"defense_element_names": [
|
||||
{"id": element_id, "name": name}
|
||||
for element_id, name in sorted(defense_names.items())
|
||||
],
|
||||
"effectiveness_matrix": {
|
||||
"base": f"0x{AFFINITY_EFFECTIVENESS_BASE:x}",
|
||||
"reserved_shape": [
|
||||
AFFINITY_EFFECTIVENESS_STRIDE,
|
||||
AFFINITY_EFFECTIVENESS_STRIDE,
|
||||
],
|
||||
"authored_rows": AFFINITY_EFFECTIVENESS_ROW_COUNT,
|
||||
"authored_columns": AFFINITY_EFFECTIVENESS_AUTHORED_COLUMNS,
|
||||
},
|
||||
"item_tuning_curves": tuning_curves,
|
||||
"usable_item_tuning_curve_ids": [
|
||||
curve["curve_id"]
|
||||
for curve in tuning_curves
|
||||
if any(curve["level_bonuses"])
|
||||
],
|
||||
"reserved_item_tuning_curve_ids": [
|
||||
curve["curve_id"]
|
||||
for curve in tuning_curves
|
||||
if not any(curve["level_bonuses"])
|
||||
and not any(curve["level_costs"])
|
||||
],
|
||||
"facility_level_thresholds": facility_thresholds,
|
||||
"string_write_count": string_write_count,
|
||||
"footer_array_count": footer_array_count,
|
||||
"exit_count": exit_count,
|
||||
"classified_instruction_count": (
|
||||
string_write_count + footer_array_count + exit_count
|
||||
),
|
||||
"array_layouts": {
|
||||
f"0x{AFFINITY_EFFECTIVENESS_BASE:x}": {
|
||||
"length": (
|
||||
AFFINITY_EFFECTIVENESS_STRIDE
|
||||
* AFFINITY_EFFECTIVENESS_STRIDE
|
||||
),
|
||||
"stride": AFFINITY_EFFECTIVENESS_STRIDE,
|
||||
"rows": AFFINITY_EFFECTIVENESS_STRIDE,
|
||||
},
|
||||
f"0x{ITEM_TUNING_BONUS_CURVE_BASE:x}": {
|
||||
"length": (
|
||||
(ITEM_TUNING_CURVE_COUNT + 1)
|
||||
* ITEM_TUNING_CURVE_STRIDE
|
||||
),
|
||||
"stride": ITEM_TUNING_CURVE_STRIDE,
|
||||
"rows": ITEM_TUNING_CURVE_COUNT + 1,
|
||||
},
|
||||
f"0x{ITEM_TUNING_COST_CURVE_BASE:x}": {
|
||||
"length": (
|
||||
(ITEM_TUNING_CURVE_COUNT + 1)
|
||||
* ITEM_TUNING_CURVE_STRIDE
|
||||
),
|
||||
"stride": ITEM_TUNING_CURVE_STRIDE,
|
||||
"rows": ITEM_TUNING_CURVE_COUNT + 1,
|
||||
},
|
||||
f"0x{FACILITY_LEVEL_THRESHOLD_BASE:x}": {
|
||||
"length": (
|
||||
FACILITY_LEVEL_THRESHOLD_ROW_COUNT
|
||||
* FACILITY_LEVEL_THRESHOLD_STRIDE
|
||||
),
|
||||
"stride": FACILITY_LEVEL_THRESHOLD_STRIDE,
|
||||
"rows": FACILITY_LEVEL_THRESHOLD_ROW_COUNT,
|
||||
},
|
||||
},
|
||||
"consumer_contract": {
|
||||
"affinity": (
|
||||
"CALCBTPARAM and AI providers index the effectiveness matrix "
|
||||
"by defense element then attack element; INFOAF displays the "
|
||||
"consumer-selected rows and the eight shipped attack elements."
|
||||
),
|
||||
"item_tuning": (
|
||||
"TUNE, IMPROVE, DRAWTIP, and CALCREVISE combine each ITINIT "
|
||||
"curve id with a zero-based tuning level to obtain the stat "
|
||||
"bonus and point cost."
|
||||
),
|
||||
"facility_progression": (
|
||||
"IMPROVE, ALCHEMY, and MAGIC/USEMAGIC index rows 0, 1, and 2 "
|
||||
"respectively by current facility level."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def extract_name_entry_palette(scr):
|
||||
"""Extract CTINIT's five-page, 70-cell name-entry character palette."""
|
||||
rows = [
|
||||
[None] * NAME_ENTRY_CHARACTER_PALETTE_STRIDE
|
||||
for _ in NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES
|
||||
]
|
||||
string_write_count = 0
|
||||
exit_count = 0
|
||||
|
||||
for ins in scr.instructions:
|
||||
if (
|
||||
ins.opcode == SET_STRING
|
||||
and len(ins.args) >= 2
|
||||
and ins.args[0][0] == T_GLOBAL_STRING
|
||||
):
|
||||
destination = ins.args[0][1]
|
||||
relative = destination - NAME_ENTRY_CHARACTER_PALETTE_BASE
|
||||
if not (
|
||||
0 <= relative
|
||||
< len(rows) * NAME_ENTRY_CHARACTER_PALETTE_STRIDE
|
||||
):
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: unexpected character destination "
|
||||
f"0x{destination:x}"
|
||||
)
|
||||
row, column = divmod(
|
||||
relative, NAME_ENTRY_CHARACTER_PALETTE_STRIDE
|
||||
)
|
||||
if rows[row][column] is not None:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: duplicate character cell {row}/{column}"
|
||||
)
|
||||
rows[row][column] = scr.strings.get(
|
||||
ins.args[1][1], (None,)
|
||||
)[0]
|
||||
string_write_count += 1
|
||||
continue
|
||||
|
||||
if sys4load.display_label(ins.opcode) == "exit":
|
||||
exit_count += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"{scr.path.name}: unexpected opcode "
|
||||
f"{sys4load.display_label(ins.opcode)} at 0x{ins.offset:x}"
|
||||
)
|
||||
|
||||
if exit_count != 1:
|
||||
raise ValueError(f"{scr.path.name}: expected one exit, got {exit_count}")
|
||||
|
||||
records = []
|
||||
for row_id, (name, characters) in enumerate(zip(
|
||||
NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES, rows
|
||||
)):
|
||||
populated = [
|
||||
{"slot": slot, "character": character}
|
||||
for slot, character in enumerate(characters)
|
||||
if character is not None
|
||||
]
|
||||
records.append({
|
||||
"id": row_id,
|
||||
"name": name,
|
||||
"characters": characters,
|
||||
"populated_characters": populated,
|
||||
"string_fields": {
|
||||
(
|
||||
f"0x{NAME_ENTRY_CHARACTER_PALETTE_BASE:x}/"
|
||||
f"{NAME_ENTRY_CHARACTER_PALETTE_STRIDE}/{entry['slot']}"
|
||||
): entry["character"]
|
||||
for entry in populated
|
||||
},
|
||||
})
|
||||
|
||||
return records, {
|
||||
"schema": "name-entry-character-palette",
|
||||
"palette_base": f"0x{NAME_ENTRY_CHARACTER_PALETTE_BASE:x}",
|
||||
"reserved_shape": [
|
||||
len(NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES),
|
||||
NAME_ENTRY_CHARACTER_PALETTE_STRIDE,
|
||||
],
|
||||
"row_names": list(NAME_ENTRY_CHARACTER_PALETTE_ROW_NAMES),
|
||||
"string_write_count": string_write_count,
|
||||
"exit_count": exit_count,
|
||||
"classified_instruction_count": string_write_count + exit_count,
|
||||
"populated_cells_per_row": [
|
||||
sum(character is not None for character in row) for row in rows
|
||||
],
|
||||
"empty_slots_per_row": [
|
||||
[
|
||||
slot
|
||||
for slot, character in enumerate(row)
|
||||
if character is None
|
||||
]
|
||||
for row in rows
|
||||
],
|
||||
"consumer_contract": {
|
||||
"script": "INPUTNAME.BIN",
|
||||
"lookup": (
|
||||
"INPUTNAME selects one of five palette pages, indexes its "
|
||||
"70-cell row by cursor slot, rejects empty cells, and copies "
|
||||
"a selected character into the seven-character name buffer."
|
||||
),
|
||||
"page_selection": (
|
||||
"Cursor slots 70..74 select palette rows 0..4."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@cache
|
||||
def gallery_thumbnail_sheet_assets() -> dict[int, int]:
|
||||
"""Read CGMODE's enabled thumbnail-sheet assets from INIT2."""
|
||||
@@ -2990,6 +3435,14 @@ def write_data_index(data_dir: Path) -> None:
|
||||
"point cost, required/forbidden story flags, and four fixed ingredient slots retain",
|
||||
"their raw parallel-array and row-table coordinates.",
|
||||
"",
|
||||
"AFINIT's dedicated affinity/progression schema exposes its attack and defense",
|
||||
"element vocabularies, signed effectiveness matrix, eighteen usable item-tuning",
|
||||
"bonus/cost curves plus a reserved zero row, and three facility progression rows.",
|
||||
"",
|
||||
"CTINIT's dedicated name-entry schema exposes INPUTNAME's five 70-cell palette",
|
||||
"pages (hiragana, katakana, Latin, numerals, and symbols), preserving all reserved",
|
||||
"empty slots beside the 273 authored characters.",
|
||||
"",
|
||||
"Mixed-mode tables preserve the sparse selector id, branch offset, condition strings,",
|
||||
"scalar fields, cells within preallocated buffers, and length-prefixed footer arrays.",
|
||||
"STINIT additionally joins confirmed parallel buffers into per-slot `object_placements`",
|
||||
@@ -3065,6 +3518,10 @@ def main() -> int:
|
||||
extractor = extract_magic_actions
|
||||
elif mode == "name" and name == "ILINIT":
|
||||
extractor = extract_condition_definitions
|
||||
elif mode == "name" and name == "AFINIT":
|
||||
extractor = extract_affinity_definitions
|
||||
elif mode == "name" and name == "CTINIT":
|
||||
extractor = extract_name_entry_palette
|
||||
elif mode == "numeric" and name == "CGINIT":
|
||||
extractor = extract_gallery_definitions
|
||||
elif mode == "numeric" and name == "ALINIT":
|
||||
|
||||
@@ -1134,6 +1134,144 @@ def test_alchemy_recipes() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_affinity_definitions() -> None:
|
||||
scripts = paths.scripts()
|
||||
script = sys4load.load(scripts["AFINIT.BIN"])
|
||||
check(
|
||||
extract_init.detect_mode(script) == "name",
|
||||
"AFINIT remains compatible with name-mode auto-detection",
|
||||
)
|
||||
records, meta = extract_init.extract_affinity_definitions(script)
|
||||
by_id = {record["id"]: record for record in records}
|
||||
check(
|
||||
len(records) == 13
|
||||
and meta["string_write_count"] == 27
|
||||
and meta["footer_array_count"] == 54
|
||||
and meta["classified_instruction_count"] == 82,
|
||||
"AFINIT classifies every vocabulary, footer-array, and exit instruction",
|
||||
)
|
||||
check(
|
||||
[entry["id"] for entry in meta["attack_element_names"]]
|
||||
== [*range(1, 9), *range(11, 18)]
|
||||
and [entry["id"] for entry in meta["defense_element_names"]]
|
||||
== list(range(1, 13)),
|
||||
"AFINIT preserves its sparse attack and defense element vocabularies",
|
||||
)
|
||||
check(
|
||||
by_id[3]["name"] == "火炎"
|
||||
and by_id[3]["attack_effectiveness"][3]["percent"] == -100
|
||||
and by_id[3]["attack_effectiveness"][4]["percent"] == 150
|
||||
and by_id[11]["attack_effectiveness"][1]["percent"] == 1
|
||||
and by_id[11]["attack_effectiveness"][7]["percent"] == 200,
|
||||
"AFINIT exposes signed elemental immunities, weaknesses, and resistances",
|
||||
)
|
||||
|
||||
tuning = {
|
||||
curve["curve_id"]: curve for curve in meta["item_tuning_curves"]
|
||||
}
|
||||
check(
|
||||
len(tuning) == 19
|
||||
and tuning[1]["level_bonuses"] == [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
and tuning[9]["level_bonuses"] == list(range(1, 11))
|
||||
and tuning[18]["level_bonuses"] == list(range(3, 31, 3))
|
||||
and tuning[18]["level_costs"]
|
||||
== [10, 25, 45, 70, 100, 140, 190, 250, 320, 400]
|
||||
and tuning[19]["level_bonuses"] == [0] * 10
|
||||
and tuning[19]["level_costs"] == [0] * 10
|
||||
and meta["usable_item_tuning_curve_ids"] == list(range(1, 19))
|
||||
and meta["reserved_item_tuning_curve_ids"] == [19],
|
||||
"AFINIT pairs all nineteen item-tuning bonus and point-cost curves",
|
||||
)
|
||||
check(
|
||||
[
|
||||
row["level_progress_thresholds"]
|
||||
for row in meta["facility_level_thresholds"]
|
||||
] == [
|
||||
[40, 80, 120, 160, 200, 300],
|
||||
[20, 40, 60, 90, 120, 200],
|
||||
[20, 50, 100, 150, 200, 400],
|
||||
],
|
||||
"AFINIT exposes the tuning, alchemy, and magic progression rows",
|
||||
)
|
||||
|
||||
semantics = extract_init.field_semantics(
|
||||
records, meta["array_layouts"]
|
||||
)
|
||||
check(
|
||||
len(semantics) == 14
|
||||
and semantics["0x26a4"] == "defense_element_names"
|
||||
and semantics["0xab5ba/0"]
|
||||
== "attack_element_effectiveness_percent.row_0"
|
||||
and semantics["0xab5ba/240"]
|
||||
== "attack_element_effectiveness_percent.row_12",
|
||||
"AFINIT raw vocabulary and matrix rows join to canonical semantics",
|
||||
)
|
||||
extract_init.attach_semantic_fields(records, semantics)
|
||||
check(
|
||||
by_id[3]["semantic_fields"]["defense_element_names"] == "火炎"
|
||||
and by_id[3]["semantic_fields"][
|
||||
"attack_element_effectiveness_percent.row_3"
|
||||
][3] == -100,
|
||||
"AFINIT retains raw footer provenance beside signed semantic rows",
|
||||
)
|
||||
|
||||
|
||||
def test_name_entry_palette() -> None:
|
||||
scripts = paths.scripts()
|
||||
script = sys4load.load(scripts["CTINIT.BIN"])
|
||||
check(
|
||||
extract_init.detect_mode(script) == "name",
|
||||
"CTINIT remains compatible with name-mode auto-detection",
|
||||
)
|
||||
records, meta = extract_init.extract_name_entry_palette(script)
|
||||
by_id = {record["id"]: record for record in records}
|
||||
check(
|
||||
len(records) == 5
|
||||
and meta["reserved_shape"] == [5, 70]
|
||||
and meta["string_write_count"] == 273
|
||||
and meta["classified_instruction_count"] == 274,
|
||||
"CTINIT classifies all five reserved palette pages and every instruction",
|
||||
)
|
||||
check(
|
||||
meta["row_names"]
|
||||
== ["hiragana", "katakana", "latin", "numerals", "symbols"]
|
||||
and meta["populated_cells_per_row"] == [56, 56, 52, 40, 69],
|
||||
"CTINIT names each page and preserves its authored cell population",
|
||||
)
|
||||
check(
|
||||
by_id[0]["characters"][0] == "あ"
|
||||
and by_id[0]["characters"][17] is None
|
||||
and by_id[1]["characters"][50] == "ァ"
|
||||
and by_id[2]["characters"][0] == "A"
|
||||
and by_id[2]["characters"][30] == "a"
|
||||
and by_id[3]["characters"][20] == "Ⅰ"
|
||||
and by_id[3]["characters"][30] == "①"
|
||||
and by_id[4]["characters"][68] == "ω"
|
||||
and by_id[4]["characters"][69] is None,
|
||||
"CTINIT retains representative characters and intentional empty slots",
|
||||
)
|
||||
|
||||
semantics = extract_init.field_semantics(records)
|
||||
check(
|
||||
len(semantics) == 69
|
||||
and semantics["0x43dd/70/0"]
|
||||
== "name_entry_character_palette.column_0"
|
||||
and semantics["0x43dd/70/68"]
|
||||
== "name_entry_character_palette.column_68",
|
||||
"CTINIT raw palette slots join to one canonical table name",
|
||||
)
|
||||
extract_init.attach_semantic_fields(records, semantics)
|
||||
check(
|
||||
by_id[0]["semantic_fields"][
|
||||
"name_entry_character_palette.column_0"
|
||||
] == "あ"
|
||||
and by_id[4]["semantic_fields"][
|
||||
"name_entry_character_palette.column_68"
|
||||
] == "ω",
|
||||
"CTINIT retains raw cells beside the semantic palette view",
|
||||
)
|
||||
|
||||
|
||||
def test_condition_definitions() -> None:
|
||||
scripts = paths.scripts()
|
||||
script = sys4load.load(scripts["ILINIT.BIN"])
|
||||
@@ -1345,6 +1483,8 @@ if __name__ == "__main__":
|
||||
test_character_names()
|
||||
test_gallery_definitions()
|
||||
test_alchemy_recipes()
|
||||
test_affinity_definitions()
|
||||
test_name_entry_palette()
|
||||
test_condition_definitions()
|
||||
test_field_semantics()
|
||||
if FAILS:
|
||||
|
||||
@@ -1798,10 +1798,10 @@ name = "attack_element_effectiveness_percent"
|
||||
category = "data-table"
|
||||
type = "int[20][20]"
|
||||
value_domain = "signed effectiveness percentage indexed by defense element then attack element"
|
||||
usage = "AFINIT-authored element matchup table. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases."
|
||||
usage = "AFINIT-authored element matchup table with thirteen authored defense rows and eighteen authored cells per row inside the reserved 20-by-20 layout. Positive values make an action eligible in SETMVWORK and RTN_M051/052; CALCBTPARAM multiplies battle parameters by the selected percentage and handles negative values as special/immunity cases. INFOAF displays eight attack columns for its selected defense rows."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x77c6e", "0x8e3d1", "0x8dfe9", "0xa7436"]
|
||||
depends_on = ["0x2690", "0x26a4", "0x77c6e", "0x8e3d1", "0x8dfe9", "0xa7436"]
|
||||
|
||||
[[global]]
|
||||
address = "0xcc9f4"
|
||||
@@ -2194,14 +2194,47 @@ depends_on = ["0x671d"]
|
||||
|
||||
[[global]]
|
||||
address = "0x671e"
|
||||
name = ""
|
||||
category = "story-flag"
|
||||
name = "magic_facility_level"
|
||||
category = "counter"
|
||||
type = "int"
|
||||
value_domain = "one of {5, 6}"
|
||||
usage = "TODO: confirm. Branch-read in 0 scenes / 3 scripts; compared against [5, 6]; writers=['USEMAGIC.BIN']."
|
||||
source = "auto-shape"
|
||||
confidence = "med"
|
||||
depends_on = []
|
||||
value_domain = "0..6"
|
||||
usage = "Current magic/research facility level. MAGIC and USEMAGIC index facility_level_progress_thresholds row 2 with this level; USEMAGIC raises it up to 6 when magic_facility_progress reaches the current threshold."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0xab8b2", "0x6721"]
|
||||
|
||||
[[global]]
|
||||
address = "0x671c"
|
||||
name = "item_tuning_facility_level"
|
||||
category = "counter"
|
||||
type = "int"
|
||||
value_domain = "0..6"
|
||||
usage = "Current equipment-tuning facility level. IMPROVE indexes facility_level_progress_thresholds row 0 with this value and raises it, up to 6, as item_tuning_facility_progress accumulates."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0xab8b2", "0x671f"]
|
||||
|
||||
[[global]]
|
||||
address = "0x671f"
|
||||
name = "item_tuning_facility_progress"
|
||||
category = "counter"
|
||||
type = "int"
|
||||
value_domain = "non-negative tuning progress"
|
||||
usage = "Progress toward the next equipment-tuning facility level. IMPROVE adds the selected item's tuning-level increases, applies row 0 of facility_level_progress_thresholds, and carries or clamps progress when the level rises."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x671c", "0xab8b2"]
|
||||
|
||||
[[global]]
|
||||
address = "0x6721"
|
||||
name = "magic_facility_progress"
|
||||
category = "counter"
|
||||
type = "int"
|
||||
value_domain = "non-negative magic/research progress"
|
||||
usage = "Progress toward the next magic/research facility level. USEMAGIC adds the selected action's progress award and applies row 2 of facility_level_progress_thresholds; MAGIC renders the same current/threshold pair."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x671e", "0xab8b2"]
|
||||
|
||||
[[global]]
|
||||
address = "0x6722"
|
||||
@@ -3348,3 +3381,73 @@ usage = "Required ingredient quantities paired cell-for-cell with alchemy_recipe
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x158154"]
|
||||
|
||||
[[global]]
|
||||
address = "0x2690"
|
||||
name = "attack_element_names"
|
||||
category = "string-table"
|
||||
type = "string[20]"
|
||||
columns = { "1" = "physical", "2" = "universal", "3" = "fire", "4" = "ice", "5" = "lightning", "6" = "earth", "7" = "holy", "8" = "dark", "11" = "resistance_1", "12" = "resistance_2", "13" = "resistance_3", "14" = "resistance_4", "15" = "resistance_5", "16" = "resistance_6", "17" = "resistance_7" }
|
||||
value_domain = "Japanese element/attribute display strings; unused cells are empty"
|
||||
usage = "AFINIT attack-side affinity vocabulary. DRAWTIP indexes ids 1..8 from ITINIT, while INFOAF displays those same eight ordinary attack elements above attack_element_effectiveness_percent."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x8dfe9", "0xa7436", "0xab5ba"]
|
||||
|
||||
[[global]]
|
||||
address = "0x26a4"
|
||||
name = "defense_element_names"
|
||||
category = "string-table"
|
||||
type = "string[20]"
|
||||
columns = { "1" = "physical", "2" = "universal", "3" = "fire", "4" = "ice", "5" = "lightning", "6" = "earth", "7" = "holy", "8" = "dark", "9" = "divinity", "10" = "demon", "11" = "spirit", "12" = "undead" }
|
||||
value_domain = "Japanese defense-affinity display strings; unused cells are empty"
|
||||
usage = "AFINIT defense-side affinity vocabulary. DRAWENP and DRAWTIP index it with unit/item defense element ids; the same id selects a row of attack_element_effectiveness_percent."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x77c6e", "0x8e3d1", "0xab5ba"]
|
||||
|
||||
[[global]]
|
||||
address = "0x43dd"
|
||||
name = "name_entry_character_palette"
|
||||
category = "string-table"
|
||||
type = "string[5][70]"
|
||||
value_domain = "one authored input character or empty reserved cell"
|
||||
usage = "INPUTNAME's five 70-cell character pages: row 0 hiragana, row 1 katakana, row 2 full-width Latin letters, row 3 Arabic/Kanji/Roman/circled numerals, and row 4 symbols. Cursor slots 70..74 select the page; selecting a populated cell copies it into the seven-character name buffer."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
|
||||
[[global]]
|
||||
address = "0xab6fa"
|
||||
name = "item_tuning_stat_bonus_curves"
|
||||
category = "data-table"
|
||||
type = "int[20][11]"
|
||||
columns = { "0" = "tuning_level_1", "1" = "tuning_level_2", "2" = "tuning_level_3", "3" = "tuning_level_4", "4" = "tuning_level_5", "5" = "tuning_level_6", "6" = "tuning_level_7", "7" = "tuning_level_8", "8" = "tuning_level_9", "9" = "tuning_level_10", "10" = "reserved" }
|
||||
value_domain = "non-negative stat bonus; zero means that tuning level is unavailable"
|
||||
usage = "Eighteen usable equipment-growth curves selected by item_tuning_curve_ids, plus an explicitly zeroed reserved row 19. TUNE and IMPROVE use the nonzero prefix as the available tuning-level range; CALCREVISE and DRAWTIP add the selected zero-based tuning level's bonus to the corresponding stat."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0xa2bf1"]
|
||||
|
||||
[[global]]
|
||||
address = "0xab7d6"
|
||||
name = "item_tuning_point_cost_curves"
|
||||
category = "data-table"
|
||||
type = "int[20][11]"
|
||||
columns = { "0" = "tuning_level_1", "1" = "tuning_level_2", "2" = "tuning_level_3", "3" = "tuning_level_4", "4" = "tuning_level_5", "5" = "tuning_level_6", "6" = "tuning_level_7", "7" = "tuning_level_8", "8" = "tuning_level_9", "9" = "tuning_level_10", "10" = "reserved" }
|
||||
value_domain = "non-negative point cost; shipped nonzero values are 10..400"
|
||||
usage = "Point-cost curves paired by curve id and tuning level with item_tuning_stat_bonus_curves. IMPROVE sums costs for newly selected levels, subtracts refunds for removed levels, checks the resulting total against the tuning-point balance, and deducts it on confirmation."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0xab6fa", "0xa2bf1"]
|
||||
|
||||
[[global]]
|
||||
address = "0xab8b2"
|
||||
name = "facility_level_progress_thresholds"
|
||||
category = "data-table"
|
||||
type = "int[3][7]"
|
||||
columns = { "0" = "level_0_to_1", "1" = "level_1_to_2", "2" = "level_2_to_3", "3" = "level_3_to_4", "4" = "level_4_to_5", "5" = "level_5_to_6", "6" = "level_6_cap" }
|
||||
value_domain = "required progress; final cap column is zero/unwritten"
|
||||
usage = "AFINIT's three facility-progression rows. IMPROVE uses row 0 for equipment tuning, ALCHEMY uses row 1, and MAGIC/USEMAGIC use row 2; each indexes the row by the current level and caps advancement at level 6."
|
||||
source = "investigation"
|
||||
confidence = "high"
|
||||
depends_on = ["0x671c", "0x671d", "0x671e", "0x671f", "0x6720", "0x6721"]
|
||||
|
||||
Reference in New Issue
Block a user