From c55c5ada4c2d9c6f9edccb9da00bfd98cee79d92 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 23 Jul 2026 15:43:27 -0400 Subject: [PATCH] Decode RTINIT movement providers 4 6 and 15 --- docs/global-reference.md | 16 +++++----- docs/name-resolution.md | 33 ++++++++++++++++--- docs/phase-b-framework.md | 12 +++---- docs/script-inventory.md | 9 +++--- docs/tools-reference.md | 2 +- docs/vm-mapping-plan.md | 4 +-- tools/extract_init.py | 65 ++++++++++++++++++++++++++++++++++++++ tools/test_extract_init.py | 28 +++++++++++++--- tools/test_globals.py | 5 +++ vm-map/globals.toml | 42 +++++++++++++++++++++--- 10 files changed, 183 insertions(+), 33 deletions(-) diff --git a/docs/global-reference.md b/docs/global-reference.md index ebc885a..3f2e211 100644 --- a/docs/global-reference.md +++ b/docs/global-reference.md @@ -1,7 +1,7 @@ # Global Variable Reference (generated) -5038 globals (241 curated, 4797 auto shape-inferred). Source of truth: `vm-map/globals.toml`. +5038 globals (244 curated, 4794 auto shape-inferred). Source of truth: `vm-map/globals.toml`. ## choice-output @@ -12,7 +12,7 @@ | `0xa68` | — | med | auto-shape | TODO: confirm. Branch-read in 11 scenes / 12 scripts; compared against [0, 1]; writers=['SC0740.BIN', 'SC1580.BIN', 'SC1590.BIN']. | | `0x62ccc` | scjump_decision_out2 | low | inference | Adjacent to scjump_decision_out (0x62ccf) in the 0x62ccc-0x62ccf progression decision-output cluster; same 136-scene reach, written by CAMP/CLOSE/DEBUGADV. INFERENCE from adjacency — confirm meaning before relying on it. | | `0x62ccf` | scjump_decision_out | high | investigation | SCJUMP's selected progression decision id. Its chapter/flag decision tree writes one of 847 distinct values across 1,755 sites; SYSTEM4, FIELD, SALLY, and TRAIN use it to index scjump_scene_script_resource_ids and dispatch the corresponding SCxxxx scene script. | -| `0xcc9f1` | movement_search_mode | high | investigation | Transient MVSEEK mode. Zero keeps the caller-supplied origin and broad search allowance; nonzero modes replace them with the current entity tile and movement+1. Mode 2 additionally masks the doubled-coordinate terrain cells occupied by active entities of another faction before the flood fill. MVSEEK resets the mode to zero on exit. | +| `0xcc9f1` | movement_search_mode | high | investigation | Transient MVSEEK mode. Zero replaces the caller-supplied origin with the current entity tile and uses movement+1 as the search allowance; nonzero modes retain the caller coordinate and use a broad allowance of 9999. Mode 2 additionally masks the doubled-coordinate terrain cells occupied by active entities of another faction before the flood fill. MVSEEK resets the mode to zero on exit. | ## counter @@ -37,7 +37,7 @@ | `0x69f8` | unit_current_stats | high | investigation | Persistent fourteen-stat row for each playable unit, using the same column order as unit_base_stats. CALCCC adds class_change_stat_bonuses to the current unit's row and clamps each result to the shared stat caps. 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. | | `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. | +| `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. RTN_M015 treats the state of Magic Pillar types 2..4 as their controlling faction and targets pillars whose value differs from the acting entity's faction. | | `0x4e021` | entity_runtime_flags | high | investigation | Per-runtime-entity state flags. RTN_M007 requires bit 0 when selecting an injured ally, and MVSEEK mode 2 requires it before masking a foreign entity's occupied terrain cell. | | `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. | @@ -126,8 +126,11 @@ | `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. | -| `0xaba96` | pathfinding_remaining_route_steps | high | investigation | MVSEEK's row-major reachability grid. The origin receives movement+1 and each traversed edge decrements the value, so a larger positive value is nearer to the origin. RTN_M007 uses the difference from the origin value as its ally-search radius; RTN_M010 ranks Healing Feathers by the negated value to choose the nearest reachable object. | +| `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. | | `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. | +| `0xbf6fe` | pathfinding_filtered_route_scores | high | investigation | SETMVWORK begins by copying pathfinding_remaining_route_steps, then filters occupied entity cells according to faction and the acting entity's usable offensive-action mask/effectiveness. RTN_M providers combine this target-proximity score with a movement-limited MVSEEK grid to select a reachable tile nearest an entity or stage object. | +| `0xcc9f4` | offensive_action_scope_masks | high | investigation | CALCSCOPE clears ten encoded-range cells, sets bit 0 over the equipped/default attack's range, and sets bits 1..4 over each usable offensive skill's range. SETMVWORK uses the range-0 mask and paired attack elements when filtering target cells; RTN_M003/006/016/017/018 require its normal-attack bit. Columns: 0=range_0, 1=range_1, 2=range_2, 3=range_3, 4=range_4, 5=range_5, 6=range_6, 7=range_7, 8=range_8, 9=range_9. | | `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. | @@ -161,7 +164,7 @@ | `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. Its meaning is tagged by movement_routine_provider_selectors: RTN_M005/012 use destination_tile_x, RTN_M007 uses maximum_target_route_steps, RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero), and RTN_M011 uses destination_tile_x. | +| `0xf9bb8` | movement_routine_parameter_1 | high | investigation | RTINIT movement bank 2. Its meaning is tagged by movement_routine_provider_selectors: RTN_M004 uses a stage_object_slot_index; RTN_M005/011/012 use destination_tile_x; RTN_M006/007/015 use maximum_target_route_steps; and RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero). | | `0xfe9d8` | movement_routine_parameter_2 | high | investigation | RTINIT movement bank 3. Its meaning is tagged by movement_routine_provider_selectors: RTN_M005/011/012 use destination_tile_y, RTN_M007 uses maximum_target_hp_percent, and RTN_M010 uses maximum_resource_percent. | | `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. | @@ -276,8 +279,6 @@ | `0xaba4b` | — | low | auto-shape | array | | `0xaba53` | — | low | auto-shape | array | | `0xaba5f` | — | low | auto-shape | array | -| `0xaba64` | — | low | auto-shape | array | -| `0xcc9f4` | — | low | auto-shape | array | | `0xcc9fe` | — | low | auto-shape | array | | `0xcca3a` | — | low | auto-shape | array | | `0xccb66` | — | low | auto-shape | array | @@ -5036,7 +5037,6 @@ | `0xab7d6` | — | med | auto-shape | record-table[stride 11] | | `0xab8b2` | — | med | auto-shape | record-table[stride 7] | | `0xb8d86` | — | med | auto-shape | record-table[stride 27] | -| `0xbf6fe` | — | med | auto-shape | record-table[stride 27] | | `0xc6077` | — | med | auto-shape | record-table[stride 27] | | `0xcc9f0` | — | low | auto-shape | index/counter? | | `0xcca08` | — | med | auto-shape | record-table[stride 5] | diff --git a/docs/name-resolution.md b/docs/name-resolution.md index 129a0d2..51f065e 100644 --- a/docs/name-resolution.md +++ b/docs/name-resolution.md @@ -444,8 +444,8 @@ keeps its raw `base/20/slot` fields and complete offset-tagged assignment histor empty reserved banks. Structural and consumer-proven meanings live in `vm-map/globals.toml`; the generic parameter names remain as raw provenance while each RTN_M/RTN_B consumer proves its tagged schema. -The provider-specific join now covers RTN_M005/007/010/011/012: 292 movement steps, 758 populated -parameters, and seven explicit zero defaults. RTN_M005/011/012 read banks 2/3 as +The provider-specific join now covers RTN_M004/005/006/007/010/011/012/015: 508 movement steps, 971 +populated parameters, and ten explicit zero defaults. RTN_M005/011/012 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_M012 is byte-for-byte @@ -453,6 +453,17 @@ equivalent to M005 except for its MVSEEK mode: mode 2 masks the doubled-coordina active foreign-faction entity before the flood fill, so the generated behavior distinguishes this foreign-entity-avoiding route from ordinary M005. +RTN_M004's bank-2 value is a zero-based `stage_object_slot_index`. It approaches that current-stage +object and uses the same exact-tile/type-6-linked-exit completion contract as the coordinate providers. +Three of its 70 steps leave the cell unwritten and therefore receive an explicit semantic slot-0 default; +the generic raw bank remains absent in those rows. + +RTN_M006 is the nearest-enemy form. Bank 2 is an inclusive `maximum_target_route_steps`, authored from +1 through 7 plus 10 and 20. It requires the normal-attack bit at encoded range 0, selects the nearest +active entity of another faction which remains eligible after SETMVWORK's offensive-action filtering, +randomizes equal-distance ties, and approaches a reachable tile nearest that enemy. Producing a valid +destination increments the step's progress counter. + 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 @@ -471,13 +482,27 @@ zero/HP default without fabricating a raw assignment. Bank 3 is an inclusive provider chooses the nearest active OBINIT type 15 or 16 object (`治癒の羽`, full status recovery, or its single-use red variant) and approaches a reachable tile nearest it. +RTN_M015 is the Magic Pillar form. It chooses the nearest active OBINIT type 2/3/4 object (small, +medium, or large `魔力の柱`) whose runtime ownership state differs from the acting entity's faction. +Bank 2 is an inclusive `maximum_target_route_steps`, authored from 2 through 6; the provider produces +movement only when the chosen foreign-controlled pillar is within that radius. + +MVSEEK's mode contract is also now explicit. Mode 0 replaces the input coordinate with the current +entity tile and seeds the origin with movement+1, producing the movement-limited reachability grid. +Modes 1/2 retain the caller coordinate and seed it with 9999, producing a broad target-distance grid; +mode 2 additionally applies the foreign-entity terrain mask described above. Every traversed edge +decrements `pathfinding_remaining_route_steps`, so origin minus target is the route-step distance used +by M006/007/015. + `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, target-selection rules, and zero/unwritten behavior. No meaning is projected onto other providers. The supporting runtime joins are now curated too: `entity_runtime_flags`, `entity_faction_ids`, `entity_tile_x`/`entity_tile_y`, the fourteen-column `entity_effective_stats`, -current HP/SP/FS, `movement_search_mode`, `pathfinding_remaining_route_steps`, -`pathfinding_movement_costs`, and the per-entity patrol waypoint index. +current HP/SP/FS, `stage_object_runtime_flags`, `movement_search_mode`, +`offensive_action_scope_masks`, `pathfinding_remaining_route_steps`, +`pathfinding_filtered_route_scores`, `pathfinding_movement_costs`, and the per-entity patrol waypoint +index. ### The curated registry — `vm-map/globals.toml` (2026-07-07) diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index b8f2837..881348e 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -787,12 +787,12 @@ 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. Provider-tagged decoding now covers movement providers 5, 7, 10, 11, and 12: coordinate -destinations (including M012's foreign-entity route mask), injured-ally selection by route radius and HP -cutoff, Healing Feather selection gated by current resource percentage, and cyclic waypoints with an -optional path-cost limit. This projects 758 populated parameters plus seven explicit zero/HP defaults -across 292 steps without assigning those meanings to other providers that share the same raw banks. -Continue the remaining parameterized RTN_M consumers by evidence density. +history. Provider-tagged decoding now covers movement providers 4, 5, 6, 7, 10, 11, 12, and 15: +stage-object slots, coordinate destinations (including M012's foreign-entity route mask), nearest-enemy +and injured-ally searches, resource-gated Healing Feather selection, cyclic waypoints, and +foreign-controlled Magic Pillars. This projects 971 populated parameters plus ten explicit defaults +across 508 steps without assigning those meanings to other providers that share the same raw banks. +Continue with the sparse parameter tail in RTN_M001/008/013/014. 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 1d91eba..26f952d 100644 --- a/docs/script-inventory.md +++ b/docs/script-inventory.md @@ -99,10 +99,11 @@ 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. Selector-specific parameter schemas now cover RTN_M005/012 coordinate destinations, -RTN_M007 injured-ally search, RTN_M010 resource-threshold Healing Feather search, and RTN_M011 cyclic -waypoints with an optional path-cost limit. The generated join covers 292 steps, 758 populated parameters, -and seven explicit zero/HP defaults while retaining the structurally named raw banks for every provider. +banks visible. Selector-specific parameter schemas now cover RTN_M004 stage-object slots, +RTN_M005/012 coordinate destinations, RTN_M006 nearest-enemy search, RTN_M007 injured-ally search, +RTN_M010 resource-threshold Healing Feather search, RTN_M011 cyclic waypoints, and RTN_M015 +foreign-controlled Magic Pillars. The generated join covers 508 steps, 971 populated parameters, and ten +explicit defaults 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 7e8160b..08628e7 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -55,7 +55,7 @@ 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, resolves provider selectors to RTN_M/RTN_B scripts, and applies selector-scoped parameter schemas where proven. Current schemas cover RTN_M005/012 coordinate destinations, RTN_M007 injured-ally search, RTN_M010 resource-threshold Healing Feather search, and RTN_M011 cyclic waypoints; provider defaults such as M010's unwritten zero/HP resource index are projected explicitly and counted separately from populated source cells. 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` | +| `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. Current schemas cover RTN_M004 stage-object slots, RTN_M005/012 coordinate destinations, RTN_M006/007 enemy/ally searches, RTN_M010 resource-threshold Healing Feather search, RTN_M011 cyclic waypoints, and RTN_M015 foreign-controlled Magic Pillars; provider defaults such as M004's unwritten slot 0 and M010's unwritten zero/HP resource index are projected explicitly and counted separately from populated source cells. 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 7dbb241..2cb9f62 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. RTN_M005/007/010/011/012 add selector-specific coordinate, injured-ally, Healing Feather, and waypoint semantics to 292 steps, 758 populated parameters, and seven explicit defaults while retaining the generic raw banks. +- [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_M004/005/006/007/010/011/012/015 add selector-specific object-slot, coordinate, enemy/ally search, Healing Feather, waypoint, and Magic Pillar semantics to 508 steps, 971 populated parameters, and ten explicit defaults 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: continue RTINIT's provider-tagged parameters after the completed RTN_M005/007/010/011/012 slices, 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: close RTINIT's sparse parameter tail in RTN_M001/008/013/014 after the completed M004/005/006/007/010/011/012/015 slices, 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 49fdfad..a853115 100644 --- a/tools/extract_init.py +++ b/tools/extract_init.py @@ -91,6 +91,27 @@ ROUTINE_BANK_ROLES = ( ) MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { + 4: { + "behavior": "approach_stage_object_slot", + "parameter_fields": { + "movement_parameter_1": "stage_object_slot_index", + }, + "parameter_defaults": { + "movement_parameter_1": 0, + }, + "parameter_notes": { + "stage_object_slot_index": ( + "zero-based index into the current stage's object arrays; " + "shipped explicit values are 1 or 2, with three unwritten " + "cells using the zero/slot-0 default" + ), + }, + "completion": ( + "advance the current step's progress counter after reaching the " + "selected object's tile (or, for a type-6 stage object, its linked " + "exit tile)" + ), + }, 5: { "behavior": "approach_destination_tile", "parameter_fields": { @@ -102,6 +123,29 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { "destination tile (or its linked type-6 stage-object exit tile)" ), }, + 6: { + "behavior": "approach_nearest_enemy", + "parameter_fields": { + "movement_parameter_1": "maximum_target_route_steps", + }, + "parameter_notes": { + "maximum_target_route_steps": ( + "inclusive route-step radius from the acting entity after " + "SETMVWORK applies offensive-action eligibility; shipped " + "values are 1..7, 10, or 20" + ), + }, + "target_selection": ( + "nearest active entity of another faction within the route-step " + "radius; choose randomly among ties, then approach a reachable " + "tile nearest that enemy; execution also requires the normal-" + "attack bit in offensive_action_scope_masks[0]" + ), + "completion": ( + "advance the current step's progress counter after producing a " + "valid movement destination toward the selected enemy" + ), + }, 7: { "behavior": "approach_injured_ally", "parameter_fields": { @@ -197,6 +241,27 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { "destination tile (or its linked type-6 stage-object exit tile)" ), }, + 15: { + "behavior": "approach_foreign_magic_pillar", + "parameter_fields": { + "movement_parameter_1": "maximum_target_route_steps", + }, + "parameter_notes": { + "maximum_target_route_steps": ( + "inclusive route-step radius; shipped values are 2..6" + ), + }, + "target_selection": ( + "nearest active stage object of OBINIT type 2, 3, or 4 (small, " + "medium, or large Magic Pillar) whose runtime state/faction differs " + "from the acting entity; require it to be within the route-step " + "radius, then approach a reachable tile nearest that object" + ), + "completion": ( + "produce a movement result only when a foreign-controlled Magic " + "Pillar exists within the configured route-step radius" + ), + }, } UNIT_STAT_COLUMNS = ( diff --git a/tools/test_extract_init.py b/tools/test_extract_init.py index 6eeedd1..9b53ec1 100644 --- a/tools/test_extract_init.py +++ b/tools/test_extract_init.py @@ -371,10 +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"] == 5 - and meta["decoded_movement_step_count"] == 292 - and meta["decoded_movement_parameter_count"] == 758 - and meta["decoded_movement_defaulted_parameter_count"] == 7, + check(meta["decoded_movement_provider_count"] == 8 + and meta["decoded_movement_step_count"] == 508 + and meta["decoded_movement_parameter_count"] == 971 + and meta["decoded_movement_defaulted_parameter_count"] == 10, "RTINIT reports selector-specific semantic coverage") check([ layout["bank_index"] @@ -434,6 +434,26 @@ def test_real_routine_banks() -> None: and provider_12["destination_tile_x"] == 11 and provider_12["destination_tile_y"] == 68, "RTINIT joins RTN_M012's route-mode destination semantics") + provider_4 = by_id[5]["movement_steps"][0] + check(provider_4["movement_provider_selector"] == 4 + and provider_4["provider_behavior"] == "approach_stage_object_slot" + and provider_4["stage_object_slot_index"] == 1, + "RTINIT joins RTN_M004's stage-object slot semantics") + provider_4_default = by_id[17]["movement_steps"][1] + check(provider_4_default["movement_provider_selector"] == 4 + and provider_4_default["stage_object_slot_index"] == 0 + and "movement_parameter_1" not in provider_4_default, + "RTINIT projects RTN_M004's implicit stage-object slot zero") + provider_6 = by_id[6]["movement_steps"][0] + check(provider_6["movement_provider_selector"] == 6 + and provider_6["provider_behavior"] == "approach_nearest_enemy" + and provider_6["maximum_target_route_steps"] == 5, + "RTINIT joins RTN_M006's enemy-search radius") + provider_15 = by_id[2]["movement_steps"][0] + check(provider_15["movement_provider_selector"] == 15 + and provider_15["provider_behavior"] == "approach_foreign_magic_pillar" + and provider_15["maximum_target_route_steps"] == 3, + "RTINIT joins RTN_M015's foreign Magic Pillar search radius") check("provider_behavior" not in movement and "destination_tile_x" not in movement, "RTINIT does not leak provider-specific meanings onto undecoded selectors") diff --git a/tools/test_globals.py b/tools/test_globals.py index 3907d78..bb0624b 100644 --- a/tools/test_globals.py +++ b/tools/test_globals.py @@ -33,6 +33,11 @@ def test_load_and_lint(): check(entries[0xaba96]["name"] == "pathfinding_remaining_route_steps" and entries[0xcc9f1]["name"] == "movement_search_mode", "movement-search reachability state is curated") + check(entries[0xbf6fe]["name"] == "pathfinding_filtered_route_scores" + and entries[0xcc9f4]["name"] == "offensive_action_scope_masks", + "movement target filtering and offensive scope are curated") + check(entries[0xaba64]["name"] == "stage_object_runtime_flags", + "stage-object targeting flags are 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) diff --git a/vm-map/globals.toml b/vm-map/globals.toml index 67cc100..c87918a 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. Its meaning is tagged by movement_routine_provider_selectors: RTN_M005/012 use destination_tile_x, RTN_M007 uses maximum_target_route_steps, RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero), and RTN_M011 uses destination_tile_x." +usage = "RTINIT movement bank 2. Its meaning is tagged by movement_routine_provider_selectors: RTN_M004 uses a stage_object_slot_index; RTN_M005/011/012 use destination_tile_x; RTN_M006/007/015 use maximum_target_route_steps; and RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero)." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -1292,22 +1292,45 @@ name = "pathfinding_remaining_route_steps" category = "data-table" type = "int[][27]" value_domain = "0 for unreachable; positive remaining flood-fill steps" -usage = "MVSEEK's row-major reachability grid. The origin receives movement+1 and each traversed edge decrements the value, so a larger positive value is nearer to the origin. RTN_M007 uses the difference from the origin value as its ally-search radius; RTN_M010 ranks Healing Feathers by the negated value to choose the nearest reachable object." +usage = "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." source = "investigation" confidence = "high" depends_on = ["0x4e11b", "0x5231f", "0x52351", "0xcc9f1"] +[[global]] +address = "0xbf6fe" +name = "pathfinding_filtered_route_scores" +category = "data-table" +type = "int[][27]" +value_domain = "0 for excluded/unreachable; positive remaining route score" +usage = "SETMVWORK begins by copying pathfinding_remaining_route_steps, then filters occupied entity cells according to faction and the acting entity's usable offensive-action mask/effectiveness. RTN_M providers combine this target-proximity score with a movement-limited MVSEEK grid to select a reachable tile nearest an entity or stage object." +source = "investigation" +confidence = "high" +depends_on = ["0x4e021", "0x522ed", "0xaba96", "0xcc9f4"] + [[global]] address = "0xcc9f1" name = "movement_search_mode" category = "choice-output" type = "int" value_domain = "0..2" -usage = "Transient MVSEEK mode. Zero keeps the caller-supplied origin and broad search allowance; nonzero modes replace them with the current entity tile and movement+1. Mode 2 additionally masks the doubled-coordinate terrain cells occupied by active entities of another faction before the flood fill. MVSEEK resets the mode to zero on exit." +usage = "Transient MVSEEK mode. Zero replaces the caller-supplied origin with the current entity tile and uses movement+1 as the search allowance; nonzero modes retain the caller coordinate and use a broad allowance of 9999. Mode 2 additionally masks the doubled-coordinate terrain cells occupied by active entities of another faction before the flood fill. MVSEEK resets the mode to zero on exit." source = "investigation" confidence = "high" depends_on = ["0x4e021", "0x4e11b", "0x522ed", "0x5231f", "0x52351"] +[[global]] +address = "0xcc9f4" +name = "offensive_action_scope_masks" +category = "data-table" +type = "int[10]" +columns = { "0" = "range_0", "1" = "range_1", "2" = "range_2", "3" = "range_3", "4" = "range_4", "5" = "range_5", "6" = "range_6", "7" = "range_7", "8" = "range_8", "9" = "range_9" } +value_domain = "action-availability bitmask: bit 0 normal attack, bits 1..4 equipped active skills" +usage = "CALCSCOPE clears ten encoded-range cells, sets bit 0 over the equipped/default attack's range, and sets bits 1..4 over each usable offensive skill's range. SETMVWORK uses the range-0 mask and paired attack elements when filtering target cells; RTN_M003/006/016/017/018 require its normal-attack bit." +source = "investigation" +confidence = "high" +depends_on = ["0x8d431", "0x8d819", "0xa71de", "0xa730a"] + [[global]] address = "0x152616" name = "current_entity_index" @@ -2213,10 +2236,21 @@ name = "stage_object_runtime_state" category = "data-table" type = "int[][50]" value_domain = "type-dependent small integer state; initial values observed 1..3" -usage = "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." +usage = "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. RTN_M015 treats the state of Magic Pillar types 2..4 as their controlling faction and targets pillars whose value differs from the acting entity's faction." source = "investigation" confidence = "high" +[[global]] +address = "0xaba64" +name = "stage_object_runtime_flags" +category = "data-table" +type = "int[50]" +value_domain = "runtime stage-object bitfield; bit 1 marks a targetable active slot" +usage = "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." +source = "investigation" +confidence = "high" +depends_on = ["0xe7389"] + [[global]] address = "0x27b9" name = "stage_victory_condition_1"