From 0bb7dc7e12e471769904c063882d349eaeb4f50d Mon Sep 17 00:00:00 2001 From: gamer147 Date: Fri, 24 Jul 2026 08:58:32 -0400 Subject: [PATCH] Decode battle passive activation state --- docs/global-reference.md | 5 +- docs/name-resolution.md | 6 +- docs/phase-a-slice-plan.md | 31 +++++++++++ docs/vm-mapping-plan.md | 3 +- tools/test_globals.py | 110 +++++++++++++++++++++++++++++++++++++ vm-map/globals.toml | 22 ++++++++ 6 files changed, 172 insertions(+), 5 deletions(-) diff --git a/docs/global-reference.md b/docs/global-reference.md index db9c326..4664d01 100644 --- a/docs/global-reference.md +++ b/docs/global-reference.md @@ -1,7 +1,7 @@ # Global Variable Reference (generated) -4276 globals (428 curated, 3848 auto shape-inferred). Source of truth: `vm-map/globals.toml`. +4277 globals (430 curated, 3847 auto shape-inferred). Source of truth: `vm-map/globals.toml`. ## choice-output @@ -288,6 +288,7 @@ | `0x152421` | card_definition_visual_asset_ids | high | investigation | Card-result visual selected by FIELD before applying and presenting the effect. All 81 authored rows resolve to MVS*.AGF assets, with related card families sharing artwork. | | `0x152486` | card_generation_weight_schedules | high | investigation | CDINIT's selector-specific candidate weights, parallel to card_generation_card_ids. FIELD computes base_weight + floor(current_stage_turn / growth_interval_turns) * growth_weight, with a zero interval selecting the fixed base path. The shipped lists populate 11..75 one-based slots. Columns: 0=base_weight, 1=growth_interval_turns, 2=growth_weight. | | `0x1525b2` | card_generation_card_ids | high | investigation | Card ids parallel to card_generation_weight_schedules. FIELD scans slots 0..99, filters each nonzero id through the CDINIT2 story-flag rows, and performs cumulative weighted random selection. CDINIT clears only the first 50 slots even though its largest authored list reaches slot 75. | +| `0x15261f` | battle_triggered_passive_skill_flags | high | investigation | Per-exchange passive-skill activation matrix. CALCDMG clears both 300-cell side rows, considers the four equipped skills on each battle entity, retains category-4 passives allowed for that actor/target role, rolls skill_proc_chance_percent, and applies species, boss, movement-skill, action-category, and mutual-exclusion filters. BTL consumes the surviving flags to draw triggered-passive icons, play each passive's battle animation, and execute Roar, Counter, Reflect, Absorb, Pierce, Shield, Parry, and Revive behavior. | | `0x152877` | battle_entity_indices | high | investigation | The two runtime entity rows participating in BTL. battle_actor_side_index and battle_target_side_index select these cells before BTL and CALCDMG access HP, unit definitions, skills, animation state, and voice banks. Columns: 0=side_0, 1=side_1. | | `0x152879` | battle_selected_skill_ids | high | investigation | Selected skill for each battle side. BTL displays a positive entry through the skill-name table and selects skill-use voice columns; CALCDMG uses the same id for skill parameters. A zero entry follows the equipped/default ordinary-attack path. Columns: 0=side_0, 1=side_1. | | `0x15288e` | battle_animation_effect_ids | high | investigation | BTANINIT2's sparse battle-animation composition table. BTANINIT expands the selected row into six runtime effect work slots. BTL anchors requested effects in slots 0 and 1 to the actor and slots 2 through 5 to the target; shipped data never populates slot 4. Columns: 0=effect_slot_0, 1=effect_slot_1, 2=effect_slot_2, 3=effect_slot_3, 4=reserved_effect_slot_4, 5=effect_slot_5. | @@ -3747,6 +3748,7 @@ | `0xab8fb` | class_change_state_work | high | investigation | Ten-cell working copy of the current unit's unit_class_change_state row. CCINIT sets the slot belonging to an awarded rule; CALCCC manages the block copy between this buffer and persistent per-unit state. Columns: 0=promotion_slot_1, 1=promotion_slot_2, 2=promotion_slot_3, 3=promotion_slot_4, 4=promotion_slot_5, 5=promotion_slot_6, 6=promotion_slot_7, 7=promotion_slot_8, 8=promotion_slot_9, 9=promotion_slot_10. | | `0x15261c` | battle_hit_result | high | investigation | CALCDMG initializes this to miss, changes it to hit after the accuracy gate, and then to critical after the critical-chance gate. BTL uses the same result to choose presentation, cut-ins, and critical voice columns; -1 is written only by a synthetic pre-battle path. | | `0x15261d` | battle_hp_delta | high | investigation | CALCDMG's signed HP result. BTL subtracts it from the target's current HP, so positive values deal damage and negative values heal; zero and result state gate damage reactions and defeat handling. | +| `0x15261e` | battle_actor_hp_recovery | high | investigation | CALCDMG's actor-side HP recovery output. Absorb (SKINIT skill 40) contributes half of positive battle_hp_delta with a minimum of one, and the target's HP-absorption condition can add its level-scaled amount capped at the damage dealt. BTL adds the result to the actor's current HP, renders the green recovery number, and clamps the resource through the ordinary post-battle path. | | `0x84a` | — | high | auto-shape | unit-name-table | | `0xc32` | — | high | auto-shape | unit-desc-table | | `0x101a` | — | high | auto-shape | unit-desc1-table | @@ -3919,7 +3921,6 @@ | `0x15235a` | — | low | auto-shape | cdinit2-field? | | `0x1523be` | — | low | auto-shape | cdinit2-field? | | `0x152422` | — | med | auto-shape | cdinit2-field | -| `0x15261f` | — | med | auto-shape | record-table[stride 300] | | `0x152878` | — | low | auto-shape | index/counter? | | `0x15288b` | — | low | auto-shape | index/counter? | | `0x152894` | — | low | auto-shape | btaninit2-field? | diff --git a/docs/name-resolution.md b/docs/name-resolution.md index bdb34c9..0c86591 100644 --- a/docs/name-resolution.md +++ b/docs/name-resolution.md @@ -709,8 +709,10 @@ The v1 map labels *shapes and tables*; the next increments add *meaning*, cheape story gates, map/minimap geometry, rewards, and 174 resolved entry/clear/failure transitions. With RTINIT's used-provider surface closed, ITMES/SKMES/VIMES/EIMES/CIMES/MAMES joined, INFOMES/MES classified, STINIT2's engine-dead authoring tier retained at medium confidence, and the - 309-script ADV primary/alternate/transition surface-slot registry named, continue with - consumer-led tables that still have shipped readers; + 309-script ADV primary/alternate/transition surface-slot registry named, the selected + movement-route grid reconstructed, and CALCDMG/BTL's two-side-by-300 triggered-passive + matrix plus actor HP-recovery output closed, continue with consumer-led tables that still + have shipped readers; never assign one universal meaning to a parameter bank whose meaning varies by provider selector. 2. **Label remaining 2D record tables by their readers** — cross-reference which scripts read each `rec[sN]` table and infer purpose from context, preserving reserved rows and sparse cells. RECOVER's diff --git a/docs/phase-a-slice-plan.md b/docs/phase-a-slice-plan.md index 060c992..780ccd4 100644 --- a/docs/phase-a-slice-plan.md +++ b/docs/phase-a-slice-plan.md @@ -3267,3 +3267,34 @@ Evidence regressions protect the exact corpus xrefs and clear geometry, all seve readers, both SETROUTE flood-score copies, the INIT2 cardinal vectors, and every canonical name. **Next:** investigate the remaining unnamed stride-300 table at `G[0x15261f]`. + +## Data-semantics sidebar: battle triggered-passive matrix (2026-07-24) + +The former stride-300 auto table at `G[0x15261f]` is the reserved `2 × 300` +`battle_triggered_passive_skill_flags` work matrix. Its first dimension is BTL's two battle sides, +and its second dimension is the SKINIT skill id. The complete corpus surface is compact and exact: +BTL and CALCDMG are its only readers/writers, with 37 two-dimensional lookups and three full +600-cell clears. + +CALCDMG rebuilds the matrix for each exchange. A local eligibility surface first assigns the +actor/target roles of shipped passive ids 28..50 (with Seal/id 44 absent from this activation path). +The script then scans the four equipped skills on both battle entities, accepts category-4 passives, +applies the configured `skill_proc_chance_percent`, and filters surviving cells through the concrete +combat contract: species and boss slayers, anti-air/anti-sub movement skills, ordinary-versus-special +attack categories, role-specific defensive/offensive passives, and mutually exclusive reactions. +The result is an activation record for this exchange rather than the entity's persistent skill +ownership; `entity_skill_flags` remains the latter source. + +BTL proves the output side. It indexes each participant's four equipped skills through the matrix +to draw the triggered-passive icons, and repeats the dynamic join for actor- and target-side passive +battle animations. Direct column tests then implement Roar, Counter, Reflect, Absorb, Pierce, Shield, +Parry, and Revive behavior. The adjacent `G[0x15261e]` is now +`battle_actor_hp_recovery`: CALCDMG adds Absorb's half-damage return and any level-scaled HP-absorption +condition amount, while BTL adds the result to the actor's HP and renders the green recovery number. + +Evidence regressions protect the exact two-script/40-reference surface, all three `2 × 300` clears, +the passive-id eligibility range, category gate and dynamic filters, BTL's three skill-indexed +presentation consumers, and the actor-recovery producer/consumer pair. + +**Next:** investigate the highest-use remaining auto-shaped row table, the stride-14 table at +`G[0x6f70]` (27 references across ADDEXP, EVOLVE, GAMECLEAR, GAMESTART, TRAIN, and UNITECH). diff --git a/docs/vm-mapping-plan.md b/docs/vm-mapping-plan.md index 67112c3..bdd1af1 100644 --- a/docs/vm-mapping-plan.md +++ b/docs/vm-mapping-plan.md @@ -71,8 +71,9 @@ - [x] **2.3n — Extract `STINIT2`'s stage-definition registry.** Name/string-matrix specialization corrects the generic 321-record view to 74 sparse stages plus 296 strings in six pre/post-clear description slots, classifying all 1,634 instructions. FORT, FIELD, SELSTAGE, and STAGECLEAR prove main/EX flags, seven-column story gates, unlock groups, numbered/event presentation, map/minimap geometry, base point and three-coin rewards, and the loader contract. All 174 entry/clear/failure decisions resolve through SCINIT and all 74 loader ids resolve to STINIT. The remaining `0xedc4d` array has no shipped reader and is retained at medium confidence as the authoring-only stage difficulty tier: values 1..8 correlate with challenge/reward progression but have no runtime effect. - [x] **2.3o — Name the ADV layer surface-slot registry.** `G[0x3239]` is an eight-by-three primary/alternate/transition surface bank initialized by 143 scripts and read across 309. The common CG loader proves double-buffer selection and transition staging; INIT2 supplies the paired retained-object handles, and the companion resource, layer-index, live-slot, and handle globals are curated. - [x] **2.3p — Name the selected movement-route grid.** `G[0xc6077]` is a reserved 1000-by-27 work grid carrying the one-cell-wide route copied from MVSEEK's remaining-step scores. SETROUTE proves the cardinal backtrace, FIELD proves route rendering and execution, SELACT proves move-before-action use, and RTN_M002..018 prove candidate-endpoint testing. The shared map target coordinates and INIT2 cardinal vectors are curated with it. +- [x] **2.3q — Name the triggered passive-skill matrix.** `G[0x15261f]` is a two-side-by-300 SKINIT-indexed activation matrix rebuilt for each CALCDMG exchange. CALCDMG gates owned category-4 passives by actor/target role, proc chance, species/boss/movement/action conditions, and pairwise exclusions; BTL uses the surviving flags for passive icons, reaction animations, and passive combat behavior. The adjacent actor HP-recovery output is curated with it. - [x] **2.4 — Partial global-var map BUILT + wired into the disassembler.** `tools/global_map.py` → `build/global-var-map.{json,md}` (16,354/49,435 globals labelled: string tables, `*INIT` field arrays, 122 record tables w/ strides, current-entity index pointers). `sys4load` renders the labels inline (`=rec[s30]`, `=current-entity-index?`). See `docs/name-resolution.md`. -- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, CTINIT name palette, CVINIT character-voice registry, LAINIT terrain definitions, MPINIT stage-terrain atlas, SPINIT H-scene gallery, TRINIT training actions, CDINIT/CDINIT2 card selection/effects, BTANINIT/BTANINIT2 battle animations, STINIT2's complete stage surface, the 309-script ADV layer surface-slot registry, and the selected movement-route grid are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, continue remaining consumer-led 2D table naming. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved. +- [ ] **2.5 — Grow the global-var map (future, incremental).** Static first: RTINIT is closed at 1,043/1,043 movement steps; the RECOVER/ILINIT condition ABI, ALINIT recipes, AFINIT affinity/progression tables, CTINIT name palette, CVINIT character-voice registry, LAINIT terrain definitions, MPINIT stage-terrain atlas, SPINIT H-scene gallery, TRINIT training actions, CDINIT/CDINIT2 card selection/effects, BTANINIT/BTANINIT2 battle animations, STINIT2's complete stage surface, the 309-script ADV layer surface-slot registry, the selected movement-route grid, and the battle triggered-passive matrix are closed; ITMES/SKMES/VIMES/EIMES/CIMES/MAMES are joined to their definitions; and the non-table INFOMES/MES ABIs are classified. Next, continue remaining consumer-led 2D table naming. Full detail: `docs/name-resolution.md` → "Future step — growing the map". Packed `call-script` ids, SCJUMP decision-to-scene dispatch, the shipped RTINIT movement-provider join, and the six completed message joins are resolved. ## Phase 3 — Name the unnamed opcodes *(top ~20 BEFORE Phase 4; the rest on demand)* diff --git a/tools/test_globals.py b/tools/test_globals.py index f6e0a0b..e2d37d9 100644 --- a/tools/test_globals.py +++ b/tools/test_globals.py @@ -170,6 +170,11 @@ def test_load_and_lint(): and entries[0xccc0a]["name"] == "map_target_tile_x" and entries[0xccc0b]["name"] == "map_target_tile_y", "selected movement route grid and coordinate helpers are curated") + check(entries[0x15261e]["name"] == "battle_actor_hp_recovery" + and entries[0x15261f]["name"] + == "battle_triggered_passive_skill_flags" + and entries[0x15261f]["type"] == "int[2][300]", + "battle recovery output and triggered-passive matrix are curated") # lint clean against a permissive address universe (curated addrs are self-consistent) errors, warnings = G.lint(entries, set(entries)) check(errors == [], f"globals.toml lints clean (errors={errors})") @@ -308,6 +313,110 @@ def test_selected_movement_route_grid_evidence(): 0xccbd4: [0, 1, 0, -1, 0], }, "INIT2 defines the no-move plus four-cardinal-neighbor vectors") +def test_battle_triggered_passive_skill_flags_evidence(): + target = 0x15261f + recovery = 0x15261e + refs = [] + recovery_refs = [] + loaded = {} + for name, path in paths.scripts().items(): + if path.read_bytes()[:8] != b"SYS4422 ": + continue + script = sys4load.load(path) + loaded[name] = script + for index, instruction in enumerate(script.instructions): + if (3, target) in instruction.args: + refs.append((name, script, index, instruction)) + if (3, recovery) in instruction.args: + recovery_refs.append((name, instruction)) + + check({name for name, _, _, _ in refs} + == {"BTL.BIN", "CALCDMG.BIN"} + and len(refs) == 40 + and sum(instruction.opcode == 0x12c + for _, _, _, instruction in refs) == 37 + and sum(instruction.opcode == 0x6c + for _, _, _, instruction in refs) == 3, + "triggered-passive matrix has exactly 37 lookups and three clears in BTL/CALCDMG") + + clears_cover_both_rows = True + for _, script, index, instruction in refs: + if instruction.opcode != 0x6c: + continue + previous = script.instructions[index - 1] + clears_cover_both_rows &= ( + previous.label == "mul" + and previous.args[1:] == [(0, 2), (0, 300)] + and instruction.args[1] == previous.args[0] + ) + check(clears_cover_both_rows, + "every triggered-passive clear covers both reserved 300-skill side rows") + + calcdmg = loaded["CALCDMG.BIN"].instructions + eligibility_columns = { + instruction.args[-1][1] + for index, instruction in enumerate(calcdmg[:-1]) + if 0x1f1 <= instruction.offset <= 0x3b9 + and instruction.opcode == 0x12c + and instruction.args[1] == (9, 0xf) + and instruction.args[3] == (0, 300) + and calcdmg[index + 1].label == "mov" + and calcdmg[index + 1].args[1] == (0, 1) + } + check(eligibility_columns == set(range(28, 51)) - {44}, + "CALCDMG seeds the actor/target eligibility surface for shipped passive skill ids") + + category_gate = [ + instruction for instruction in calcdmg + if instruction.offset == 0x4d6 + ] + dynamic_writes = { + instruction.offset + for instruction in calcdmg + if instruction.opcode == 0x12c + and instruction.args[1] == (3, target) + and instruction.args[3] == (0, 300) + and instruction.args[-1] == (9, 8) + } + check(len(category_gate) == 1 + and category_gate[0].args == [ + (12, 0), (3, 0xa6f86), (9, 8) + ] + and dynamic_writes == { + 0x52e, 0x57c, 0x5d3, 0x612, 0x62a, 0x65b, + 0x688, 0x6bd, 0x725, 0x799, 0x7b5, 0x7fb, + 0x817, 0x85d, + }, + "CALCDMG gates category-4 skills then filters the side/skill activation cells") + + btl_dynamic_consumers = { + instruction.offset + for instruction in loaded["BTL.BIN"].instructions + if instruction.opcode == 0x12c + and instruction.args[1] == (3, target) + and instruction.args[3] == (0, 300) + and instruction.args[-1] == (9, 0x12) + } + check(btl_dynamic_consumers == {0x1b8c, 0x2270, 0x2319}, + "BTL indexes triggered flags by equipped skill for icons and both-side animations") + + check({name for name, _ in recovery_refs} + == {"BTL.BIN", "CALCDMG.BIN"} + and len(recovery_refs) == 20 + and any(instruction.offset == 0xa3f + and instruction.label == "div" + and instruction.args == [ + (3, recovery), (3, 0x15261d), (0, 2) + ] + for name, instruction in recovery_refs + if name == "CALCDMG.BIN") + and any(instruction.offset == 0x943 + and instruction.label == "add" + and instruction.args[-1] == (3, recovery) + for name, instruction in recovery_refs + if name == "BTL.BIN"), + "CALCDMG computes actor recovery and BTL adds it to the acting entity's HP") + def test_merge_precedence(): curated, _ = G.load_toml(paths.VM_MAP / "globals.toml") auto = G.load_auto(paths.BUILD / "global-var-map.json") @@ -372,6 +481,7 @@ if __name__ == "__main__": test_lint_catches_bad_vocab() test_adv_layer_surface_slot_evidence() test_selected_movement_route_grid_evidence() + test_battle_triggered_passive_skill_flags_evidence() test_merge_precedence() test_sys4load_labels_from_registry() test_miner_finds_known_flags() diff --git a/vm-map/globals.toml b/vm-map/globals.toml index 2887dfc..4b81408 100644 --- a/vm-map/globals.toml +++ b/vm-map/globals.toml @@ -2036,6 +2036,28 @@ usage = "CALCDMG's signed HP result. BTL subtracts it from the target's current source = "investigation" confidence = "high" +[[global]] +address = "0x15261e" +name = "battle_actor_hp_recovery" +category = "unknown" +type = "int" +value_domain = "nonnegative HP restored to the acting side" +usage = "CALCDMG's actor-side HP recovery output. Absorb (SKINIT skill 40) contributes half of positive battle_hp_delta with a minimum of one, and the target's HP-absorption condition can add its level-scaled amount capped at the damage dealt. BTL adds the result to the actor's current HP, renders the green recovery number, and clamps the resource through the ordinary post-battle path." +source = "investigation" +confidence = "high" +depends_on = ["0x4e085", "0x52383", "0x15261d"] + +[[global]] +address = "0x15261f" +name = "battle_triggered_passive_skill_flags" +category = "data-table" +type = "int[2][300]" +value_domain = "{0,1}; rows are battle side, columns are SKINIT skill ids" +usage = "Per-exchange passive-skill activation matrix. CALCDMG clears both 300-cell side rows, considers the four equipped skills on each battle entity, retains category-4 passives allowed for that actor/target role, rolls skill_proc_chance_percent, and applies species, boss, movement-skill, action-category, and mutual-exclusion filters. BTL consumes the surviving flags to draw triggered-passive icons, play each passive's battle animation, and execute Roar, Counter, Reflect, Absorb, Pierce, Shield, Parry, and Revive behavior." +source = "investigation" +confidence = "high" +depends_on = ["0x4e693", "0x5212b", "0xa6f86", "0xaa8f2", "0xaaa1e", "0x15261a", "0x15261b", "0x152877"] + [[global]] address = "0x152877" name = "battle_entity_indices"