From 46d4d1e88f0f87d316786c08a0883a8bf73fa238 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 23 Jul 2026 14:25:47 -0400 Subject: [PATCH] Decode RTINIT movement providers 5 and 11 --- docs/global-reference.md | 22 ++++----- docs/name-resolution.md | 26 +++++++++-- docs/phase-b-framework.md | 6 ++- docs/script-inventory.md | 6 ++- docs/tools-reference.md | 4 +- docs/vm-mapping-plan.md | 4 +- tools/extract_init.py | 77 +++++++++++++++++++++++++++++++- tools/init_table_profile.py | 13 ++++++ tools/test_extract_init.py | 33 ++++++++++++++ tools/test_globals.py | 13 ++++++ tools/test_init_table_profile.py | 6 +++ vm-map/globals.toml | 76 +++++++++++++++++++++++++++++-- 12 files changed, 257 insertions(+), 29 deletions(-) diff --git a/docs/global-reference.md b/docs/global-reference.md index 733e681..29bf84f 100644 --- a/docs/global-reference.md +++ b/docs/global-reference.md @@ -1,7 +1,7 @@ # Global Variable Reference (generated) -5037 globals (231 curated, 4806 auto shape-inferred). Source of truth: `vm-map/globals.toml`. +5037 globals (237 curated, 4800 auto shape-inferred). Source of truth: `vm-map/globals.toml`. ## choice-output @@ -37,11 +37,16 @@ | `0x74e8` | unit_skill_ids | high | investigation | Persistent four-skill row for each playable unit. CALCCC copies positive class_change_skill_awards into the first three slots after a promotion; ADDEXP compares the before/after row to report learned or replaced skills. Columns: 0=skill_slot_1, 1=skill_slot_2, 2=skill_slot_3, 3=skill_slot_4. | | `0x7684` | stage_clear_state | high | investigation | Persistent per-stage completion state indexed by stage id. STAGECLEAR sets the current stage cell to 1, while FORT, SELSTAGE, and FIELD use zero versus one to distinguish an uncleared mission from a cleared replay. | | `0x141a4` | stage_object_runtime_state | high | investigation | Per-stage, per-object-slot runtime state. FIELD initializes mode-1 objects from STINIT's first tagged payload on a fresh stage and updates capturable-object ownership; DRAWOBJ uses it as the vertical sprite-row index. Other consumers interpret it by object type. | +| `0x4e085` | entity_current_resources | high | investigation | Per-runtime-entity current resources. DRAWCHP/DRAWENP render columns 0..2 against max-stat columns 11..13, skill/item resource deltas modify the same three columns, and movement search compares path cost with current_fs. Columns: 0=current_hp, 1=current_sp, 2=current_fs. | +| `0x4e11b` | entity_effective_stats | high | investigation | Per-runtime-entity effective fourteen-stat row after CALCREVISE applies base data, equipment, skills, and conditions. Combat/UI consumers use columns 0..10 directly; columns 11..13 are the maxima paired with entity_current_resources HP/SP/FS. 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. | +| `0x5231f` | entity_tile_x | high | investigation | Per-runtime-entity map X coordinate. FIELD movement and occupancy logic maintain it; movement providers pair it with entity_tile_y and compare or route from the current tile. | +| `0x52351` | entity_tile_y | high | investigation | Per-runtime-entity map Y coordinate paired with entity_tile_x. Values use the same stage map-space cell keys as STINIT object and enemy coordinates. | | `0x53517` | entity_movement_routine_set_ids | high | investigation | Per-runtime-entity movement routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; MVRTN selects the current difficulty column and stores it in current_routine_set_id. | | `0x535ad` | entity_battle_routine_set_ids | high | investigation | Per-runtime-entity battle routine set for easy/normal/hard. SETEN copies a stage-specific override or the EBINIT default into this row; BTRTN selects the current difficulty column and stores it in current_routine_set_id. | | `0x53643` | entity_movement_routine_progress | high | investigation | Per-runtime-entity movement-routine progress counters. FIELD clears all fifty rows, movement providers increment the current step, and MVRTN compares it with movement_routine_minimum_progress_counts before enabling a step. | | `0x5660b` | skill_info_revealed_flags | high | investigation | Persistent skill-information visibility flags. ADDSKILL sets the selected skill, BTL marks every equipped skill when it is observed in combat, and INFOIT suppresses a skill's icon/handler-driven details until this flag is nonzero. This is broader than skill_acquired_flags. | | `0x56738` | entity_battle_routine_random_rolls | high | investigation | Per-runtime-entity battle-step random rolls. FIELD and BTL fill every active entity's twenty cells with random-modulo-100; BTRTN executes a step when the matching roll is below battle_routine_activation_percents. | +| `0x56b20` | entity_patrol_waypoint_indices | high | investigation | RTN_M011 compares each step's one-based waypoint_ordinal minus one with the current entity's value. Reaching the selected destination advances this value modulo the largest RTN_M011 waypoint ordinal in the routine set. | | `0x57357` | unit_deployment_cost_adjustments | high | investigation | Persistent per-unit delta added to unit_deployment_cost_base. CALCCC increments it by class_change_deployment_cost_delta, while SETCH, SETEN, ADDEXP, and deployment scripts include it when constructing or updating runtime command cost. | | `0x573bb` | unit_class_change_state | high | investigation | Persistent ten-slot class-change state for each playable unit. CALCCC copies the current unit's row to class_change_state_work before invoking the rule scripts and persists the updated row; CCINIT requires the rule's slot to be zero so each promotion is applied once. Columns: 0=promotion_slot_1, 1=promotion_slot_2, 2=promotion_slot_3, 3=promotion_slot_4, 4=promotion_slot_5, 5=promotion_slot_6, 6=promotion_slot_7, 7=promotion_slot_8, 8=promotion_slot_9, 9=promotion_slot_10. | | `0x66716` | unit_voice_asset_ids | high | investigation | EBINIT per-unit voice bank for 116 voiced characters and variants. FIELD directly selects column 0 before WARPD, column 1 when an acting unit takes chest/treasure contents, and column 3 after occupying, losing, or sealing a stage objective. BTL selects the acting unit's columns 7..9 for ordinary attacks, 10 for an ordinary critical, odd columns 11/13/15 for skill uses, and even columns 12/14/16 for critical skills; the three variants follow its six-step exchange selector. If the target survives positive damage, BTL selects target columns 19/20/21 with 60/30/10 percent weights; column 22 belongs to a target reduced to zero HP, while actor column 23 is selected for a finishing blow. SHOWGROW selects column 24 after level gain. Populated columns 4..6 and 17..18 have no reachable selector in the shipped script corpus and are retained as explicit unused authoring slots; columns 17 and 18 duplicate columns 15 and 16 in all 116 populated rows. Columns: 0=warp, 1=treasure_capture, 3=objective_interaction, 4=unused_slot_4, 5=unused_slot_5, 6=unused_slot_6, 7=normal_attack_1, 8=normal_attack_2, 9=normal_attack_3, 10=critical_normal_attack, 11=skill_use_1, 12=critical_skill_1, 13=skill_use_2, 14=critical_skill_2, 15=skill_use_3, 16=critical_skill_3, 17=unused_slot_17, 18=unused_slot_18, 19=damage_reaction_1, 20=damage_reaction_2, 21=damage_reaction_3, 22=defeated, 23=finishing_blow, 24=level_up. | @@ -118,6 +123,7 @@ | `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. | | `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. | +| `0xb240e` | pathfinding_movement_costs | high | investigation | MVSEEK's row-major movement-cost work grid, indexed as [tile_y][tile_x] with stride 27. FIELD and RTN_M providers accept candidate destinations only when this cost is within the acting entity's current FS or a provider-specific override. | | `0xe6dee` | object_sprite_state_row_mode | high | investigation | OBINIT metadata indexed by object type. FIELD copies the type-tagged initial payload into stage_object_runtime_state only for mode 1; DRAWOBJ likewise multiplies that runtime state by the object sprite height to select source Y only for mode 1. | | `0xe7302` | stage_bgm_id | high | investigation | STINIT's per-stage scalar loaded for all 74 records. FIELD passes the value directly to play-bgm when starting the stage. | | `0xe7303` | stage_target_clear_turns | high | investigation | STINIT's target/par turn count. STAGECLEAR divides elapsed turns by this value to derive a performance multiplier; when replaying an already-cleared ordinary stage, FIELD also uses it as the forced-retreat turn limit. | @@ -151,10 +157,10 @@ | `0xe7a0f` | stage_enemy_forbidden_story_flags | high | investigation | Five negative prerequisites per stage enemy. FIELD subtracts one from every populated id and suppresses the spawn when the corresponding story_event_flags cell equals 1. Columns: 0=forbidden_flag_1, 1=forbidden_flag_2, 2=forbidden_flag_3, 3=forbidden_flag_4, 4=forbidden_flag_5. | | `0xeff78` | movement_routine_provider_selectors | high | investigation | RTINIT movement bank 0. MVRTN indexes it by current_routine_set_id and routine_step_index, resolves the selector through its RTN_M001..018/051..053/061 provider table, and call-scripts the selected movement routine. | | `0xf4d98` | movement_routine_activation_percents | high | investigation | RTINIT movement bank 1. After all other step gates pass, MVRTN executes the provider only when random-modulo-100 is below this value. | -| `0xf9bb8` | movement_routine_parameter_1 | high | investigation | RTINIT movement bank 2. RTN_M004..015 providers consume the current row/step value; its exact meaning is tagged by movement_routine_provider_selectors and remains provider-specific. | -| `0xfe9d8` | movement_routine_parameter_2 | high | investigation | RTINIT movement bank 3. RTN_M005/007/010/011/012 consume it as a provider-specific parameter. | -| `0x1037f8` | movement_routine_parameter_3 | high | investigation | RTINIT movement bank 4. RTN_M011 consumes it as a provider-specific parameter. | -| `0x108618` | movement_routine_parameter_4 | high | investigation | RTINIT movement bank 5. RTN_M011 consumes it as a provider-specific parameter. | +| `0xf9bb8` | movement_routine_parameter_1 | high | investigation | RTINIT movement bank 2. Its meaning is tagged by movement_routine_provider_selectors: RTN_M005 and RTN_M011 use it as destination_tile_x; other consuming providers remain provider-specific. | +| `0xfe9d8` | movement_routine_parameter_2 | high | investigation | RTINIT movement bank 3. RTN_M005 and RTN_M011 use it as destination_tile_y; RTN_M007/010/012 meanings remain provider-specific. | +| `0x1037f8` | movement_routine_parameter_3 | high | investigation | RTINIT movement bank 4. RTN_M011 uses it as a one-based waypoint_ordinal, executing only the step whose ordinal matches the entity's current zero-based waypoint index. | +| `0x108618` | movement_routine_parameter_4 | high | investigation | RTINIT movement bank 5. RTN_M011 uses a nonzero value as path_cost_limit_override; zero or an unwritten cell falls back to the entity's current FS. | | `0x112258` | movement_routine_minimum_progress_counts | high | investigation | RTINIT movement bank 7. MVRTN requires the current entity's matching movement-step progress counter to reach this value; movement providers increment those counters as their steps execute. | | `0x117078` | movement_routine_required_story_flag_ids | high | investigation | RTINIT movement bank 8. MVRTN subtracts one and rejects the step when the referenced story flag is not set. | | `0x11be98` | movement_routine_forbidden_story_flag_ids | high | investigation | RTINIT movement bank 9. MVRTN subtracts one and rejects the step when the referenced story flag is set. | @@ -221,12 +227,9 @@ | `0x52289` | — | low | auto-shape | array | | `0x522bb` | — | low | auto-shape | array | | `0x522ed` | — | low | auto-shape | array | -| `0x5231f` | — | low | auto-shape | array | -| `0x52351` | — | low | auto-shape | array | | `0x53ede` | — | low | auto-shape | array | | `0x55e3b` | — | low | auto-shape | array | | `0x56223` | — | low | auto-shape | array | -| `0x56b20` | — | low | auto-shape | array | | `0x56b52` | — | low | auto-shape | array | | `0x56b85` | — | low | auto-shape | array | | `0x56f6d` | — | low | auto-shape | array | @@ -3697,8 +3700,6 @@ | `0x7e54` | — | med | auto-shape | record-table[stride 50] | | `0x20543` | — | med | auto-shape | record-table[stride 27] | | `0x341ab` | — | med | auto-shape | record-table[stride 53] | -| `0x4e085` | — | med | auto-shape | record-table[stride 3] | -| `0x4e11b` | — | med | auto-shape | record-table[stride 14] | | `0x4e3d7` | — | med | auto-shape | record-table[stride 14] | | `0x4e693` | — | med | auto-shape | record-table[stride 300] | | `0x5212b` | — | med | auto-shape | record-table[stride 4] | @@ -5033,7 +5034,6 @@ | `0xab7d6` | — | med | auto-shape | record-table[stride 11] | | `0xab8b2` | — | med | auto-shape | record-table[stride 7] | | `0xaba96` | — | med | auto-shape | record-table[stride 27] | -| `0xb240e` | — | med | auto-shape | record-table[stride 27] | | `0xb8d86` | — | med | auto-shape | record-table[stride 27] | | `0xbf6fe` | — | med | auto-shape | record-table[stride 27] | | `0xc6077` | — | med | auto-shape | record-table[stride 27] | diff --git a/docs/name-resolution.md b/docs/name-resolution.md index a46ad30..bc4ce62 100644 --- a/docs/name-resolution.md +++ b/docs/name-resolution.md @@ -442,7 +442,24 @@ are reserved/empty. Only fourteen shipped battle steps are populated. keeps its raw `base/20/slot` fields and complete offset-tagged assignment history while adding joined `movement_steps`/`battle_steps` with provider script names. The bank layout explicitly includes all six empty reserved banks. Structural and consumer-proven meanings live in `vm-map/globals.toml`; the generic -parameter names deliberately remain until each RTN_M/RTN_B provider proves its tagged schema. +parameter names remain as raw provenance while each RTN_M/RTN_B consumer proves its tagged schema. + +The first provider-specific join covers RTN_M005 and RTN_M011, 252 movement steps and 685 populated +parameters in total. Both providers read banks 2/3 as `destination_tile_x` / `destination_tile_y` and +approach that exact map tile, incrementing the current step's progress counter after arrival. Their +alternate completion test recognizes a type-6 stage object at the authored destination and also accepts +the object's linked exit tile. + +RTN_M011 is the cyclic-waypoint form. Bank 4 is a one-based `waypoint_ordinal`; only the step whose +ordinal minus one matches `entity_patrol_waypoint_indices[current_entity]` executes. Arrival advances +that runtime index modulo the largest RTN_M011 ordinal in the selected routine set. Bank 5 is an optional +`path_cost_limit_override`; zero or an unwritten cell falls back to the entity's current FS. +`movement_steps` now carry these selector-scoped semantic fields beside the original +`movement_parameter_1..4`, and top-level `movement_provider_parameter_schemas` records the reusable +mapping and zero/unwritten behavior. No meaning is projected onto other providers. The supporting +runtime joins are now curated too: `entity_tile_x`/`entity_tile_y`, the fourteen-column +`entity_effective_stats`, current HP/SP/FS, `pathfinding_movement_costs`, and the per-entity patrol +waypoint index. ### The curated registry — `vm-map/globals.toml` (2026-07-07) @@ -492,9 +509,10 @@ The v1 map labels *shapes and tables*; the next increments add *meaning*, cheape 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. Next decode provider-tagged RTINIT parameters by their RTN_M consumers, - starting with high-density providers 5 and 11; never assign one universal meaning to a parameter bank - whose meaning varies by provider selector. + banks are structurally decoded, and high-density providers 5 and 11 now expose destination/waypoint + semantics without changing their shared raw banks. Next continue through the remaining parameterized + RTN_M consumers by evidence density; never assign one universal meaning to a parameter bank whose + meaning varies by provider selector. 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 diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index a981289..aa5e5f9 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -787,8 +787,10 @@ the shared `semantic_fields` join. SCINIT is now the sixth shape: 2,179 paired a resolved and the full overwrite history retained. RTINIT is now the seventh shape: its twenty parallel 1000-by-20 banks produce 172 routine sets, 1,043 movement steps, and fourteen battle steps with RTN_M/RTN_B provider joins, activation/progress/story gates, six explicit reserved banks, and complete overwrite -history. The next data-semantics slice is provider-tagged parameter decoding, beginning with high-density -movement providers 5 and 11 rather than assigning one meaning to their shared raw parameter banks. +history. Provider-tagged decoding now covers the two high-density movement providers 5 and 11: both join +destination X/Y, while provider 11 adds a cyclic waypoint ordinal and optional path-cost limit. This +projects 685 populated parameters across 252 steps without assigning those meanings to other providers +that share the same raw banks. Continue the remaining parameterized RTN_M consumers by evidence density. Once the natural spine and first gameplay loop are trustworthy, broaden in independent tracks: diff --git a/docs/script-inventory.md b/docs/script-inventory.md index 787f1b7..daee631 100644 --- a/docs/script-inventory.md +++ b/docs/script-inventory.md @@ -99,8 +99,10 @@ RTINIT is a seventh banked shape. Its 3,336 writes populate twenty parallel `100 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 provider selectors to script names, retain all 29 overwritten cells, and keep the six reserved empty -banks visible. Provider-specific parameter banks remain structurally named until their tagged consumers -prove more specific meanings. +banks visible. RTN_M005 and RTN_M011 now provide the first selector-specific parameter schemas: both +decode destination X/Y, while RTN_M011 adds a one-based cyclic waypoint ordinal and optional path-cost +limit. The generated join covers 252 steps and 685 populated parameters while retaining the structurally +named raw banks for every provider. ### Message/string tables (`*MES`) `ITMES` (64 KB — item text), `VIMES` (43 KB), `EIMES` (37 KB), `SKMES` (31 KB — skill diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 5011e1a..4e42349 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.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 [OUTNAME]` | `.BIN` → `build/data/.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. 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, and resolves provider selectors to RTN_M/RTN_B scripts while keeping tagged provider parameters generic. ITINIT and SKINIT join ITMES/SKMES messages. 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 [OUTNAME] [--mode …]` | `
.BIN` plus EBINIT/SKINIT for CCINIT, OBINIT for STINIT, `build/callscript-names.json` for RTINIT/SCINIT, `build/scjump-decisions.json` for SCINIT, matching `.BIN` when supported, and `build/globals.json` → `build/data/.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 also report message coverage; 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, and overwrite conflicts. `--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
[--build] [--limit N] [--message-query REGEX]` | `build/data/
.json` + corpus → stdout; with `--build`, `build/data/
-field-profile.{json,md}` | +| `extract_init.py` | Parse a `*INIT` data source (auto-detects name / numeric / footer / mixed / rules / dispatch / banked shape). Name tables infer their reserved record span, preserve sparse one-based runtime ids, distinguish lookup bases from first written cells, statically evaluate direct and negative-value writes, and separate parallel `fields` from linked row-major `record_fields`; when flat global ranges overlap, a parallel base established by other records wins over a coincidental row-table cell calculation. 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 parameter schemas where proven (currently RTN_M005/011 destination and cyclic-waypoint fields). Raw provider banks remain intact, and top-level `movement_provider_parameter_schemas` documents the projection. ITINIT and SKINIT join ITMES/SKMES messages. 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
[OUTNAME] [--mode …]` | `
.BIN` plus EBINIT/SKINIT for CCINIT, OBINIT for STINIT, `build/callscript-names.json` for RTINIT/SCINIT, `build/scjump-decisions.json` for SCINIT, matching `.BIN` when supported, and `build/globals.json` → `build/data/.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 also report message coverage; 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` 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
[--build] [--limit N] [--message-query REGEX]` | `build/data/
.json` + corpus → stdout; with `--build`, `build/data/
-field-profile.{json,md}` | | `test_extract_init.py`, `test_init_table_profile.py` | Regression checks for sparse one-based, mixed selector-dispatched, CCINIT rule, SCINIT dispatch, and RTINIT banked extraction; MES reconstruction/joins; footer-array accounting and semantic projection; EBINIT/SKINIT/OBINIT definition joins; STINIT object/enemy joins and tagged-payload behavior; overwrite/provider joins; and field/message/rule/dispatch/banked profiling. | run each directly | — | | `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}` | diff --git a/docs/vm-mapping-plan.md b/docs/vm-mapping-plan.md index 096abb1..6257390 100644 --- a/docs/vm-mapping-plan.md +++ b/docs/vm-mapping-plan.md @@ -57,9 +57,9 @@ - [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 1,043 steps to 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 and provider-tagged parameters stay generic. +- [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 1,043 steps to 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_M005/011 add selector-specific destination/waypoint semantics to 252 steps and 685 populated parameters while retaining the generic raw banks. - [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: decode RTINIT's provider-tagged parameters through their RTN_M/RTN_B readers, fold in remaining `*MES` writers, and label other 2D record tables by their reader scripts. Then Frida to name *which stat* each unresolved field is. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids and SCJUMP decision-to-scene dispatch are resolved; see `docs/name-resolution.md` #1 and `docs/scjump-progression.md`. +- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: continue RTINIT's provider-tagged parameters after the completed RTN_M005/011 slice, fold in remaining `*MES` writers, and label other 2D record tables by their reader scripts. Then Frida to name *which stat* each unresolved field is. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids and SCJUMP decision-to-scene dispatch are resolved; see `docs/name-resolution.md` #1 and `docs/scjump-progression.md`. ## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)* diff --git a/tools/extract_init.py b/tools/extract_init.py index d20ee1e..7ebc373 100644 --- a/tools/extract_init.py +++ b/tools/extract_init.py @@ -90,6 +90,43 @@ ROUTINE_BANK_ROLES = ( "battle_forbidden_story_flag_id", ) +MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { + 5: { + "behavior": "approach_destination_tile", + "parameter_fields": { + "movement_parameter_1": "destination_tile_x", + "movement_parameter_2": "destination_tile_y", + }, + "completion": ( + "advance the current step's progress counter after reaching the " + "destination tile (or its linked type-6 stage-object exit tile)" + ), + }, + 11: { + "behavior": "cycle_destination_waypoints", + "parameter_fields": { + "movement_parameter_1": "destination_tile_x", + "movement_parameter_2": "destination_tile_y", + "movement_parameter_3": "waypoint_ordinal", + "movement_parameter_4": "path_cost_limit_override", + }, + "parameter_notes": { + "waypoint_ordinal": ( + "one-based; only the ordinal matching the entity's current " + "zero-based waypoint index executes" + ), + "path_cost_limit_override": ( + "optional; zero/absent falls back to the entity's current FS" + ), + }, + "completion": ( + "advance the entity's waypoint index modulo the largest authored " + "waypoint ordinal after reaching the destination tile (or its " + "linked type-6 stage-object exit tile)" + ), + }, +} + UNIT_STAT_COLUMNS = ( "accuracy", "evasion", "physical_attack", "physical_defense", "magic_attack", "magic_defense", "speed", "luck", "critical_chance", @@ -900,6 +937,21 @@ def _movement_provider_names(names: dict[int, str]) -> dict[int, str]: return providers +def _join_movement_provider_semantics(step: dict) -> int: + """Add selector-specific RTN_M semantics while retaining every raw bank.""" + selector = step.get("movement_provider_selector") + schema = MOVEMENT_PROVIDER_PARAMETER_SCHEMAS.get(selector) + if schema is None: + return 0 + step["provider_behavior"] = schema["behavior"] + joined = 0 + for raw_field, semantic_field in schema["parameter_fields"].items(): + if raw_field in step: + step[semantic_field] = step[raw_field] + joined += 1 + return joined + + def extract_banked(scr): """Extract RTINIT's sparse routine sets across twenty parallel step banks.""" writes = _routine_bank_writes(scr) @@ -915,6 +967,8 @@ def extract_banked(scr): records_by_id: dict[int, dict] = {} cell_assignments: dict[tuple[int, int, int], list[int]] = collections.defaultdict(list) bank_cells: dict[int, set[tuple[int, int]]] = collections.defaultdict(set) + decoded_movement_step_count = 0 + decoded_movement_parameter_count = 0 for offset, destination, value, bank_index, record_id, slot in writes: bank_base = ROUTINE_BANK_ROOT + bank_index * ROUTINE_BANK_SPAN @@ -954,14 +1008,19 @@ def extract_banked(scr): } if movement: selector = movement.get("movement_provider_selector") - movement_steps.append({ + step = { "slot": slot, **movement, **( {"provider_script": movement_providers.get(selector, "")} if selector is not None else {} ), - }) + } + joined_parameter_count = _join_movement_provider_semantics(step) + if selector in MOVEMENT_PROVIDER_PARAMETER_SCHEMAS: + decoded_movement_step_count += 1 + decoded_movement_parameter_count += joined_parameter_count + movement_steps.append(step) battle = { ROUTINE_BANK_ROLES[bank]: final_by_bank_slot[(bank, slot)] @@ -1049,6 +1108,20 @@ def extract_banked(scr): str(selector): name for selector, name in sorted(movement_providers.items()) }, + "movement_provider_parameter_schemas": { + str(selector): { + "provider_script": movement_providers.get(selector, ""), + **schema, + } + for selector, schema in sorted( + MOVEMENT_PROVIDER_PARAMETER_SCHEMAS.items() + ) + }, + "decoded_movement_provider_count": len( + MOVEMENT_PROVIDER_PARAMETER_SCHEMAS + ), + "decoded_movement_step_count": decoded_movement_step_count, + "decoded_movement_parameter_count": decoded_movement_parameter_count, "battle_provider_scripts": { str(selector): name for selector, name in sorted(battle_providers.items()) diff --git a/tools/init_table_profile.py b/tools/init_table_profile.py index e4c3bef..36ecddf 100644 --- a/tools/init_table_profile.py +++ b/tools/init_table_profile.py @@ -236,6 +236,15 @@ def profile_banked(data: dict) -> dict: "available_movement_provider_count": len( data.get("movement_provider_scripts", {}) ), + "decoded_movement_provider_count": data.get( + "decoded_movement_provider_count", 0 + ), + "decoded_movement_step_count": data.get( + "decoded_movement_step_count", 0 + ), + "decoded_movement_parameter_count": data.get( + "decoded_movement_parameter_count", 0 + ), "available_battle_provider_count": len( data.get("battle_provider_scripts", {}) ), @@ -391,6 +400,10 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str: f"{banked_profile['movement_step_count']}/" f"{banked_profile['movement_provider_count']} " f"({banked_profile['available_movement_provider_count']} dispatchable)", + f"- selector-specific movement semantics: " + f"{banked_profile['decoded_movement_step_count']} steps, " + f"{banked_profile['decoded_movement_parameter_count']} parameters " + f"across {banked_profile['decoded_movement_provider_count']} providers", f"- joined battle steps/used providers: " f"{banked_profile['battle_step_count']}/" f"{banked_profile['battle_provider_count']} " diff --git a/tools/test_extract_init.py b/tools/test_extract_init.py index 98f5b81..ad152db 100644 --- a/tools/test_extract_init.py +++ b/tools/test_extract_init.py @@ -371,6 +371,10 @@ def test_real_routine_banks() -> None: and len(meta["used_battle_provider_selectors"]) == 4 and len(meta["record_field_columns"]) == 117, "RTINIT assembles every populated movement and battle step") + check(meta["decoded_movement_provider_count"] == 2 + and meta["decoded_movement_step_count"] == 252 + and meta["decoded_movement_parameter_count"] == 685, + "RTINIT reports selector-specific semantic coverage") check([ layout["bank_index"] for layout in meta["bank_layouts"].values() @@ -393,6 +397,35 @@ def test_real_routine_banks() -> None: check(by_id[173]["movement_steps"][0]["movement_parameter_1"] == 2 and by_id[173]["movement_steps"][0]["movement_parameter_2"] == 158, "RTINIT final rows reflect source-ordered conflicting overwrites") + provider_5 = by_id[5]["movement_steps"][2] + check(provider_5["movement_provider_selector"] == 5 + and provider_5["provider_behavior"] == "approach_destination_tile" + and provider_5["destination_tile_x"] == 24 + and provider_5["destination_tile_y"] == 123 + and provider_5["movement_parameter_1"] == 24 + and provider_5["movement_parameter_2"] == 123, + "RTINIT joins RTN_M005 destination semantics without replacing raw banks") + provider_11 = by_id[87]["movement_steps"][2] + check(provider_11["movement_provider_selector"] == 11 + and provider_11["provider_behavior"] == "cycle_destination_waypoints" + and provider_11["destination_tile_x"] == 3 + and provider_11["destination_tile_y"] == 499 + and provider_11["waypoint_ordinal"] == 1 + and provider_11["path_cost_limit_override"] == 2, + "RTINIT joins all four RTN_M011 waypoint parameters") + check("provider_behavior" not in movement + and "destination_tile_x" not in movement, + "RTINIT does not leak provider-specific meanings onto undecoded selectors") + check( + meta["movement_provider_parameter_schemas"]["11"]["parameter_fields"] + == { + "movement_parameter_1": "destination_tile_x", + "movement_parameter_2": "destination_tile_y", + "movement_parameter_3": "waypoint_ordinal", + "movement_parameter_4": "path_cost_limit_override", + }, + "RTINIT publishes the reusable RTN_M011 parameter schema", + ) semantics = extract_init.field_semantics(records) check( semantics["0xeff78/20/0"] diff --git a/tools/test_globals.py b/tools/test_globals.py index ae40af5..3e378d2 100644 --- a/tools/test_globals.py +++ b/tools/test_globals.py @@ -16,6 +16,19 @@ def test_load_and_lint(): check(entries[0xa57]["category"] == "story-flag", "0xa57 is category story-flag") check(entries[0x3234]["category"] == "story-flag", "0x3234 is category story-flag") check({0xa57, 0xa58, 0xa59} <= set(entries), "Lily form flags A/B/C all present") + check(entries[0x4e085]["columns"] == { + "0": "current_hp", "1": "current_sp", "2": "current_fs" + }, "entity current-resource columns are curated") + check(entries[0x4e11b]["columns"]["10"] == "movement" + and entries[0x4e11b]["columns"]["13"] == "max_fs", + "entity effective-stat columns are curated") + check(entries[0x5231f]["name"] == "entity_tile_x" + and entries[0x52351]["name"] == "entity_tile_y", + "entity map-coordinate arrays are curated") + check(entries[0x56b20]["name"] == "entity_patrol_waypoint_indices", + "RTN_M011 waypoint state is curated") + check(entries[0xb240e]["name"] == "pathfinding_movement_costs", + "movement-cost work grid is curated") # lint clean against a permissive address universe (curated addrs are self-consistent) errors, warnings = G.lint(entries, set(entries)) check(errors == [], f"globals.toml lints clean (errors={errors})") diff --git a/tools/test_init_table_profile.py b/tools/test_init_table_profile.py index a2bfa4f..63a2538 100644 --- a/tools/test_init_table_profile.py +++ b/tools/test_init_table_profile.py @@ -129,6 +129,9 @@ def main() -> int: "conflicting_overwrite_count": 1, "movement_step_count": 2, "battle_step_count": 1, + "decoded_movement_provider_count": 1, + "decoded_movement_step_count": 1, + "decoded_movement_parameter_count": 2, "movement_provider_scripts": {"1": "RTN_M001.BIN"}, "battle_provider_scripts": {"1": "RTN_B001.BIN"}, "used_movement_provider_selectors": [1], @@ -157,6 +160,9 @@ def main() -> int: assert banked_summary["reserved_bank_count"] == 1 assert banked_summary["movement_step_count"] == 2 assert banked_summary["battle_provider_count"] == 1 + assert banked_summary["decoded_movement_provider_count"] == 1 + assert banked_summary["decoded_movement_step_count"] == 1 + assert banked_summary["decoded_movement_parameter_count"] == 2 messages = profile.profile_messages(fixture) assert messages["population"] == 1 diff --git a/vm-map/globals.toml b/vm-map/globals.toml index b23a7e5..566cf61 100644 --- a/vm-map/globals.toml +++ b/vm-map/globals.toml @@ -850,7 +850,7 @@ name = "movement_routine_parameter_1" category = "data-table" type = "int[1000][20]" value_domain = "provider-specific integer" -usage = "RTINIT movement bank 2. RTN_M004..015 providers consume the current row/step value; its exact meaning is tagged by movement_routine_provider_selectors and remains provider-specific." +usage = "RTINIT movement bank 2. Its meaning is tagged by movement_routine_provider_selectors: RTN_M005 and RTN_M011 use it as destination_tile_x; other consuming providers remain provider-specific." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -861,7 +861,7 @@ name = "movement_routine_parameter_2" category = "data-table" type = "int[1000][20]" value_domain = "provider-specific integer" -usage = "RTINIT movement bank 3. RTN_M005/007/010/011/012 consume it as a provider-specific parameter." +usage = "RTINIT movement bank 3. RTN_M005 and RTN_M011 use it as destination_tile_y; RTN_M007/010/012 meanings remain provider-specific." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -872,7 +872,7 @@ name = "movement_routine_parameter_3" category = "data-table" type = "int[1000][20]" value_domain = "provider-specific integer" -usage = "RTINIT movement bank 4. RTN_M011 consumes it as a provider-specific parameter." +usage = "RTINIT movement bank 4. RTN_M011 uses it as a one-based waypoint_ordinal, executing only the step whose ordinal matches the entity's current zero-based waypoint index." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -883,7 +883,7 @@ name = "movement_routine_parameter_4" category = "data-table" type = "int[1000][20]" value_domain = "{1,2}" -usage = "RTINIT movement bank 5. RTN_M011 consumes it as a provider-specific parameter." +usage = "RTINIT movement bank 5. RTN_M011 uses a nonzero value as path_cost_limit_override; zero or an unwritten cell falls back to the entity's current FS." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -1152,6 +1152,52 @@ source = "investigation" confidence = "high" depends_on = ["0xeff75", "0xeff76"] +[[global]] +address = "0x4e11b" +name = "entity_effective_stats" +category = "data-table" +type = "int[50][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 = "calculated runtime stat values" +usage = "Per-runtime-entity effective fourteen-stat row after CALCREVISE applies base data, equipment, skills, and conditions. Combat/UI consumers use columns 0..10 directly; columns 11..13 are the maxima paired with entity_current_resources HP/SP/FS." +source = "investigation" +confidence = "high" +depends_on = ["0x4e085"] + +[[global]] +address = "0x4e085" +name = "entity_current_resources" +category = "data-table" +type = "int[50][3]" +columns = { "0" = "current_hp", "1" = "current_sp", "2" = "current_fs" } +value_domain = "nonnegative current HP/SP/FS values" +usage = "Per-runtime-entity current resources. DRAWCHP/DRAWENP render columns 0..2 against max-stat columns 11..13, skill/item resource deltas modify the same three columns, and movement search compares path cost with current_fs." +source = "investigation" +confidence = "high" +depends_on = ["0x4e11b"] + +[[global]] +address = "0x5231f" +name = "entity_tile_x" +category = "data-table" +type = "int[50]" +value_domain = "current map X coordinate" +usage = "Per-runtime-entity map X coordinate. FIELD movement and occupancy logic maintain it; movement providers pair it with entity_tile_y and compare or route from the current tile." +source = "investigation" +confidence = "high" +depends_on = ["0x52351"] + +[[global]] +address = "0x52351" +name = "entity_tile_y" +category = "data-table" +type = "int[50]" +value_domain = "current map Y cell key" +usage = "Per-runtime-entity map Y coordinate paired with entity_tile_x. Values use the same stage map-space cell keys as STINIT object and enemy coordinates." +source = "investigation" +confidence = "high" +depends_on = ["0x5231f"] + [[global]] address = "0x53517" name = "entity_movement_routine_set_ids" @@ -1196,6 +1242,28 @@ source = "investigation" confidence = "high" depends_on = ["0x125ad8", "0xeff76"] +[[global]] +address = "0x56b20" +name = "entity_patrol_waypoint_indices" +category = "data-table" +type = "int[50]" +value_domain = "zero-based waypoint index; observed RTINIT waypoint ordinals imply 0..7" +usage = "RTN_M011 compares each step's one-based waypoint_ordinal minus one with the current entity's value. Reaching the selected destination advances this value modulo the largest RTN_M011 waypoint ordinal in the routine set." +source = "investigation" +confidence = "high" +depends_on = ["0x1037f8", "0xeff78"] + +[[global]] +address = "0xb240e" +name = "pathfinding_movement_costs" +category = "data-table" +type = "int[][27]" +value_domain = "nonnegative accumulated movement cost for reachable tiles" +usage = "MVSEEK's row-major movement-cost work grid, indexed as [tile_y][tile_x] with stride 27. FIELD and RTN_M providers accept candidate destinations only when this cost is within the acting entity's current FS or a provider-specific override." +source = "investigation" +confidence = "high" +depends_on = ["0x4e085"] + [[global]] address = "0x152616" name = "current_entity_index"