diff --git a/docs/global-reference.md b/docs/global-reference.md index 3f2e211..03d7724 100644 --- a/docs/global-reference.md +++ b/docs/global-reference.md @@ -1,7 +1,7 @@ # Global Variable Reference (generated) -5038 globals (244 curated, 4794 auto shape-inferred). Source of truth: `vm-map/globals.toml`. +5038 globals (245 curated, 4793 auto shape-inferred). Source of truth: `vm-map/globals.toml`. ## choice-output @@ -38,6 +38,7 @@ | `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. 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. | +| `0x20543` | tile_faction_traversal_masks | high | investigation | Row-major tile traversal permissions. Movement checks the acting entity's faction bit before enqueuing a tile in movement-limited MVSEEK searches. RTN_M013 builds either one selected faction bit or all bits except the actor's, requires the current tile not to match that set, and approaches the nearest reachable tile whose mask does. | | `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. | @@ -164,8 +165,8 @@ | `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_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. | +| `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; RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero); RTN_M013 uses target_faction_filter; and RTN_M014 uses maximum_threat_route_steps. The one RTN_M001 and one RTN_M008 authored cells are never read by those providers. | +| `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. The lone RTN_M001-authored value is never read by that provider. | | `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. | @@ -3701,7 +3702,6 @@ | `0x3ebe` | — | med | auto-shape | record-table[stride 10] | | `0x6f70` | — | med | auto-shape | record-table[stride 14] | | `0x7e54` | — | med | auto-shape | record-table[stride 50] | -| `0x20543` | — | med | auto-shape | record-table[stride 27] | | `0x341ab` | — | med | auto-shape | record-table[stride 53] | | `0x4e3d7` | — | med | auto-shape | record-table[stride 14] | | `0x4e693` | — | med | auto-shape | record-table[stride 300] | diff --git a/docs/name-resolution.md b/docs/name-resolution.md index 51f065e..408814b 100644 --- a/docs/name-resolution.md +++ b/docs/name-resolution.md @@ -444,8 +444,9 @@ 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_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 +The provider-specific join now covers RTN_M001/004/005/006/007/008/010/011/012/013/014/015: 632 +movement steps, 974 semantically consumed populated parameters, thirteen explicit zero defaults, and +three authored-but-unread parameter cells. 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 @@ -487,6 +488,26 @@ medium, or large `魔力の柱`) whose runtime ownership state differs from the 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. +RTN_M008 is the unrestricted-radius form of that Magic Pillar search: it chooses and approaches the +nearest active foreign-controlled type-2/3/4 object without consulting a parameter bank. Its sole +authored bank-2 value is therefore retained under `ignored_movement_parameters`, not presented as a +behavior input. RTN_M001 likewise reads no parameter bank; it advances the current routine step's +per-entity progress counter and sets the execution state. Its two authored cells are preserved as unread +residue. These three cells are the complete authored-but-unread movement-parameter set. + +RTN_M013 approaches terrain permitted to a selected faction. Bank 2 is `target_faction_filter`: a +nonzero value selects that faction's bit, while zero/unwritten means any faction except the actor's own. +The provider requires the current tile not already to match that mask and selects the nearest reachable +tile whose `tile_faction_traversal_masks` cell overlaps it. Three of its four steps use the explicit +zero default. + +RTN_M014 retreats from nearby enemies. Bank 2 is an inclusive +`maximum_threat_route_steps`, authored as 3 or 6. It collects active foreign-faction entities within +that route radius, sums a proximity surface from all collected threats, removes occupied and +movement-unreachable tiles, and randomly chooses among the lowest-positive-score tiles. This makes the +destination the reachable tile farthest from the nearby threat set rather than merely farthest from one +enemy. + 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; @@ -496,13 +517,18 @@ 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`, +mapping, target-selection rules, zero/unwritten behavior, and authored fields proven unread by their +provider. All 977 populated movement-parameter cells are now accounted for: 974 semantic inputs and +three explicit residue cells. The seven used providers still without schemas +(M002/003/009/017/051/052/061) have no populated parameter cells, so their remaining work is behavior +decoding rather than column semantics. 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, `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. +index. The faction-specific terrain masks used by M013 are curated as +`tile_faction_traversal_masks`. ### The curated registry — `vm-map/globals.toml` (2026-07-07) @@ -552,9 +578,9 @@ 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, 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 + banks are structurally decoded with every populated movement-parameter cell classified. Next decode + the seven behavior-only RTN_M providers by evidence density, beginning with M003, M051, M017, and + M052; 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. diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index 881348e..822d73c 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -787,12 +787,15 @@ 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 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. +history. Provider-tagged decoding now covers movement providers 1, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, +and 15: progress-only steps, stage-object slots, coordinate destinations (including M012's +foreign-entity route mask), enemy/ally and Magic Pillar searches, resource-gated Healing Feather +selection, cyclic waypoints, faction-traversable terrain, and retreat from nearby enemies. This +classifies every one of the 977 authored movement-parameter cells: 974 are semantic inputs across 632 +steps, while the three cells attached to M001/M008 are retained as provider-unread residue; thirteen +unwritten zero defaults are projected separately. The seven remaining used providers +(M002/003/009/017/051/052/061) are behavior-only in the shipped table. Decode those next by use density, +starting with M003 and M051. 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 26f952d..929551f 100644 --- a/docs/script-inventory.md +++ b/docs/script-inventory.md @@ -99,11 +99,14 @@ 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_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. +banks visible. Selector-specific schemas now cover RTN_M001/004/005/006/007/008/010/011/012/013/014/015: +progress-only steps, stage-object slots, coordinate destinations, enemy/ally and Magic Pillar searches, +resource-threshold Healing Feather search, cyclic waypoints, faction-traversable terrain, and retreat +from nearby enemies. The generated join covers 632 steps and accounts for all 977 authored movement +parameters: 974 semantic inputs plus three M001/M008 cells proven unread by their providers. Thirteen +unwritten zero defaults are projected separately while the structurally named raw banks remain intact. +The remaining seven used providers have no populated parameter cells and therefore need behavior +decoding, not additional column semantics. ### 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 08628e7..312ba4e 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_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` | +| `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_M001/004/005/006/007/008/010/011/012/013/014/015, including progress-only, object-slot, coordinate, enemy/ally and Magic Pillar search, Healing Feather, waypoint, faction-terrain, and retreat behavior. Provider defaults such as M004's unwritten slot 0, M010's zero/HP resource index, and M013's any-foreign-faction filter are projected explicitly and counted separately from populated source cells. Authored cells proven unread by M001/M008 remain under per-step `ignored_movement_parameters` and contribute to top-level `ignored_movement_parameter_count`; they are not silently discarded or assigned invented meanings. Raw provider banks remain intact, and top-level `movement_provider_parameter_schemas` documents the projection. ITINIT 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 2cb9f62..7ed6e38 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_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.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_M001/004/005/006/007/008/010/011/012/013/014/015 add selector-specific progress, object-slot, coordinate, enemy/ally and object search, Healing Feather, waypoint, faction-terrain, and retreat semantics to 632 steps. All 977 populated movement-parameter cells are classified: 974 semantic inputs and three M001/M008 cells proven unread; thirteen unwritten defaults are projected separately while the generic raw banks remain intact. - [x] **2.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: 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`. +- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: with RTINIT's populated movement-parameter tail now closed, decode the behavior-only M002/003/009/017/051/052/061 providers (starting with high-use M003/M051), 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 a853115..e8434de 100644 --- a/tools/extract_init.py +++ b/tools/extract_init.py @@ -91,6 +91,24 @@ ROUTINE_BANK_ROLES = ( ) MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { + 1: { + "behavior": "advance_step_progress", + "parameter_fields": {}, + "ignored_parameter_fields": { + "movement_parameter_1": ( + "authored once (routine set 173, slot 2, value 10), but " + "RTN_M001 never reads movement parameter bank 2" + ), + "movement_parameter_2": ( + "authored once in the same step (value 711), but RTN_M001 " + "never reads movement parameter bank 3" + ), + }, + "completion": ( + "unconditionally advance the current step's progress counter and " + "produce movement result state 1 without selecting a destination" + ), + }, 4: { "behavior": "approach_stage_object_slot", "parameter_fields": { @@ -172,6 +190,26 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { "valid movement destination toward the selected ally" ), }, + 8: { + "behavior": "approach_nearest_foreign_magic_pillar", + "parameter_fields": {}, + "ignored_parameter_fields": { + "movement_parameter_1": ( + "authored once (routine set 112, slot 6, value 1), but " + "RTN_M008 never reads movement parameter bank 2" + ), + }, + "target_selection": ( + "nearest reachable 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; unlike RTN_M015, no configured " + "route-radius gate is applied" + ), + "completion": ( + "produce a movement result when a reachable foreign-controlled " + "Magic Pillar exists" + ), + }, 10: { "behavior": "approach_healing_feather", "parameter_fields": { @@ -241,6 +279,55 @@ MOVEMENT_PROVIDER_PARAMETER_SCHEMAS = { "destination tile (or its linked type-6 stage-object exit tile)" ), }, + 13: { + "behavior": "approach_faction_traversable_tile", + "parameter_fields": { + "movement_parameter_1": "target_faction_filter", + }, + "parameter_defaults": { + "movement_parameter_1": 0, + }, + "parameter_notes": { + "target_faction_filter": ( + "zero means any faction other than the acting entity's faction; " + "a nonzero value selects exactly that faction id. Only one " + "shipped step explicitly writes value 1; three use default zero" + ), + }, + "target_selection": ( + "when the current tile is not traversable by the selected faction " + "set, choose the nearest reachable tile whose " + "tile_faction_traversal_masks value includes that set, then " + "approach it" + ), + "completion": ( + "advance the current step's progress counter after producing a " + "valid movement destination into the selected faction's traversable " + "territory" + ), + }, + 14: { + "behavior": "retreat_from_nearby_enemies", + "parameter_fields": { + "movement_parameter_1": "maximum_threat_route_steps", + }, + "parameter_notes": { + "maximum_threat_route_steps": ( + "inclusive route-step radius used to collect active foreign-" + "faction threats; shipped values are 3 or 6" + ), + }, + "target_selection": ( + "sum route-proximity scores from every active foreign-faction " + "entity within the threat radius, exclude occupied tiles, and " + "choose a reachable tile with the lowest positive aggregate score " + "(farthest from the collected threats), randomizing ties" + ), + "completion": ( + "advance the current step's progress counter after producing a " + "valid retreat destination" + ), + }, 15: { "behavior": "approach_foreign_magic_pillar", "parameter_fields": { @@ -1074,12 +1161,12 @@ def _movement_provider_names(names: dict[int, str]) -> dict[int, str]: return providers -def _join_movement_provider_semantics(step: dict) -> tuple[int, int]: +def _join_movement_provider_semantics(step: dict) -> tuple[int, int, 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, 0 + return 0, 0, 0 step["provider_behavior"] = schema["behavior"] joined = 0 defaulted = 0 @@ -1091,7 +1178,14 @@ def _join_movement_provider_semantics(step: dict) -> tuple[int, int]: elif raw_field in defaults: step[semantic_field] = defaults[raw_field] defaulted += 1 - return joined, defaulted + ignored_fields = { + raw_field: step[raw_field] + for raw_field in schema.get("ignored_parameter_fields", {}) + if raw_field in step + } + if ignored_fields: + step["ignored_movement_parameters"] = ignored_fields + return joined, defaulted, len(ignored_fields) def extract_banked(scr): @@ -1112,6 +1206,7 @@ def extract_banked(scr): decoded_movement_step_count = 0 decoded_movement_parameter_count = 0 decoded_movement_defaulted_parameter_count = 0 + ignored_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 @@ -1162,6 +1257,7 @@ def extract_banked(scr): ( joined_parameter_count, defaulted_parameter_count, + ignored_parameter_count, ) = _join_movement_provider_semantics(step) if selector in MOVEMENT_PROVIDER_PARAMETER_SCHEMAS: decoded_movement_step_count += 1 @@ -1169,6 +1265,7 @@ def extract_banked(scr): decoded_movement_defaulted_parameter_count += ( defaulted_parameter_count ) + ignored_movement_parameter_count += ignored_parameter_count movement_steps.append(step) battle = { @@ -1274,6 +1371,7 @@ def extract_banked(scr): "decoded_movement_defaulted_parameter_count": ( decoded_movement_defaulted_parameter_count ), + "ignored_movement_parameter_count": ignored_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 2a97a4d..7e73f0d 100644 --- a/tools/init_table_profile.py +++ b/tools/init_table_profile.py @@ -248,6 +248,9 @@ def profile_banked(data: dict) -> dict: "decoded_movement_defaulted_parameter_count": data.get( "decoded_movement_defaulted_parameter_count", 0 ), + "ignored_movement_parameter_count": data.get( + "ignored_movement_parameter_count", 0 + ), "available_battle_provider_count": len( data.get("battle_provider_scripts", {}) ), @@ -408,7 +411,9 @@ def render_markdown(data: dict, rows: list[dict], limit: int) -> str: f"{banked_profile['decoded_movement_parameter_count']} populated " f"parameters + " f"{banked_profile['decoded_movement_defaulted_parameter_count']} " - f"explicit defaults " + f"explicit defaults + " + f"{banked_profile['ignored_movement_parameter_count']} " + f"authored-but-unread parameters " f"across {banked_profile['decoded_movement_provider_count']} providers", f"- joined battle steps/used providers: " f"{banked_profile['battle_step_count']}/" diff --git a/tools/test_extract_init.py b/tools/test_extract_init.py index 9b53ec1..4b27bab 100644 --- a/tools/test_extract_init.py +++ b/tools/test_extract_init.py @@ -371,10 +371,11 @@ 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"] == 8 - and meta["decoded_movement_step_count"] == 508 - and meta["decoded_movement_parameter_count"] == 971 - and meta["decoded_movement_defaulted_parameter_count"] == 10, + check(meta["decoded_movement_provider_count"] == 12 + and meta["decoded_movement_step_count"] == 632 + and meta["decoded_movement_parameter_count"] == 974 + and meta["decoded_movement_defaulted_parameter_count"] == 13 + and meta["ignored_movement_parameter_count"] == 3, "RTINIT reports selector-specific semantic coverage") check([ layout["bank_index"] @@ -454,8 +455,41 @@ def test_real_routine_banks() -> None: 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, + provider_1_residue = by_id[173]["movement_steps"][2] + check(provider_1_residue["movement_provider_selector"] == 1 + and provider_1_residue["provider_behavior"] == "advance_step_progress" + and provider_1_residue["ignored_movement_parameters"] == { + "movement_parameter_1": 10, + "movement_parameter_2": 711, + }, + "RTINIT marks RTN_M001's authored but unread parameter cells") + provider_8_residue = by_id[112]["movement_steps"][6] + check(provider_8_residue["movement_provider_selector"] == 8 + and provider_8_residue["provider_behavior"] + == "approach_nearest_foreign_magic_pillar" + and provider_8_residue["ignored_movement_parameters"] + == {"movement_parameter_1": 1}, + "RTINIT marks RTN_M008's authored but unread parameter cell") + provider_13 = by_id[104]["movement_steps"][3] + check(provider_13["movement_provider_selector"] == 13 + and provider_13["provider_behavior"] + == "approach_faction_traversable_tile" + and provider_13["target_faction_filter"] == 1, + "RTINIT joins RTN_M013's explicit faction filter") + provider_13_default = by_id[23]["movement_steps"][1] + check(provider_13_default["movement_provider_selector"] == 13 + and provider_13_default["target_faction_filter"] == 0 + and "movement_parameter_1" not in provider_13_default, + "RTINIT projects RTN_M013's any-foreign-faction default") + provider_14 = by_id[130]["movement_steps"][5] + check(provider_14["movement_provider_selector"] == 14 + and provider_14["provider_behavior"] == "retreat_from_nearby_enemies" + and provider_14["maximum_threat_route_steps"] == 6, + "RTINIT joins RTN_M014's threat-detection radius") + undecoded_movement = by_id[2]["movement_steps"][1] + check(undecoded_movement["movement_provider_selector"] == 2 + and "provider_behavior" not in undecoded_movement + and "destination_tile_x" not in undecoded_movement, "RTINIT does not leak provider-specific meanings onto undecoded selectors") check( meta["movement_provider_parameter_schemas"]["11"]["parameter_fields"] diff --git a/tools/test_globals.py b/tools/test_globals.py index bb0624b..6ec7841 100644 --- a/tools/test_globals.py +++ b/tools/test_globals.py @@ -36,6 +36,8 @@ def test_load_and_lint(): 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[0x20543]["name"] == "tile_faction_traversal_masks", + "faction-specific tile traversal masks are curated") check(entries[0xaba64]["name"] == "stage_object_runtime_flags", "stage-object targeting flags are curated") check(entries[0xb240e]["name"] == "pathfinding_movement_costs", diff --git a/tools/test_init_table_profile.py b/tools/test_init_table_profile.py index dadcca7..bdd5b6e 100644 --- a/tools/test_init_table_profile.py +++ b/tools/test_init_table_profile.py @@ -133,6 +133,7 @@ def main() -> int: "decoded_movement_step_count": 1, "decoded_movement_parameter_count": 2, "decoded_movement_defaulted_parameter_count": 1, + "ignored_movement_parameter_count": 1, "movement_provider_scripts": {"1": "RTN_M001.BIN"}, "battle_provider_scripts": {"1": "RTN_B001.BIN"}, "used_movement_provider_selectors": [1], @@ -165,6 +166,7 @@ def main() -> int: assert banked_summary["decoded_movement_step_count"] == 1 assert banked_summary["decoded_movement_parameter_count"] == 2 assert banked_summary["decoded_movement_defaulted_parameter_count"] == 1 + assert banked_summary["ignored_movement_parameter_count"] == 1 messages = profile.profile_messages(fixture) assert messages["population"] == 1 diff --git a/vm-map/globals.toml b/vm-map/globals.toml index c87918a..3631eae 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_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)." +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; RTN_M010 uses resource_index (0=HP, 1=SP, 2=FS; shipped cells are unwritten/default zero); RTN_M013 uses target_faction_filter; and RTN_M014 uses maximum_threat_route_steps. The one RTN_M001 and one RTN_M008 authored cells are never read by those providers." 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. 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." +usage = "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. The lone RTN_M001-authored value is never read by that provider." source = "investigation" confidence = "high" depends_on = ["0xeff78"] @@ -1286,6 +1286,17 @@ source = "investigation" confidence = "high" depends_on = ["0x4e085"] +[[global]] +address = "0x20543" +name = "tile_faction_traversal_masks" +category = "data-table" +type = "int[][27]" +value_domain = "faction bitmask indexed by map tile" +usage = "Row-major tile traversal permissions. Movement checks the acting entity's faction bit before enqueuing a tile in movement-limited MVSEEK searches. RTN_M013 builds either one selected faction bit or all bits except the actor's, requires the current tile not to match that set, and approaches the nearest reachable tile whose mask does." +source = "investigation" +confidence = "high" +depends_on = ["0x522ed", "0xaba96"] + [[global]] address = "0xaba96" name = "pathfinding_remaining_route_steps"