diff --git a/docs/re/data/isl-stage02.txt b/docs/re/data/isl-stage02.txt index f31566c..38a362a 100644 --- a/docs/re/data/isl-stage02.txt +++ b/docs/re/data/isl-stage02.txt @@ -93,6 +93,7 @@ Generated by `tools/re-capture/isl_report.py calls`. 47 squadron_attack 137 15 set_group_speed 127 18 dist_lt 92 + 12 activate_unit 86 5 kill_coroutine 83 70 unit_alive 71 56 unit_relation 52 diff --git a/docs/re/structures/isl-builtins.md b/docs/re/structures/isl-builtins.md index a3073e4..0e920f8 100644 --- a/docs/re/structures/isl-builtins.md +++ b/docs/re/structures/isl-builtins.md @@ -140,6 +140,47 @@ and spread checks but its maximum *exceeds* the table — flag indices run 0..31 against symbol tables as small as 40 — so it is excluded, and the disassembler now declines to resolve it rather than printing an invented name. +### ✅ Built-in 12 is `activate_unit` — 1197 sites, all 28 stages + +The highest-traffic unnamed built-in. `sub_822659F0`, read directly: + +* indexes `[phase+324]`'s record array by the slot-4 symbol; +* **returns 0 immediately when the live object `[record+4]` is NULL** — so it + registers an object that already exists; it does **not** spawn one; +* sets **`[record+16] = 2`**, the documented *active* state that every unit + predicate tests; +* stores `sub_82301118`'s packed result into `[record+20]` (low 16 bits) and + `[record+24]` (high 16) — member counts; +* posts interpreter opcode **513** (`0xAB0201BA`) either way; the slot-8 mode + (1 in 999 sites, 0 in 198) only decides whether `cmd+20` is also set to 1. + +#### ✅ The ordering test — 517 / 517 + +If this activates a unit for the script, no predicate should ever test a unit +before it. Over all 28 stages, for every (stage, unit) pair having both an +`activate_unit` call and a predicate (`unit_state`, `hp_pct_test`, `unit_alive`, +`unit_hp_pct`, `dist_lt`, `unit_relation`) on that same unit: + +| | count | +|---|---| +| `activate_unit` comes first | **517** | +| a predicate comes first | **0** | + +🟡 That is *file* order, not proven execution order — coroutines mean the two can +in principle interleave. But 517 with zero exceptions across 28 stages is not a +coincidence. + +344 units are tested without ever being activated (they are live from mission +start) and 203 are activated without being tested. + +#### ❌ It is NOT the survival-percentage baseline + +The tempting closure: `squad_survival_pct` (id 24) reports current ÷ initial, and +`activate_unit` snapshots counts, so the snapshot must be the baseline. **It is +not.** Built-in 24 reads `[record+16]` for the state and then calls +`sub_823011B0` and `sub_82301118` on the **live object** — it never touches +`+20`/`+24`. ❔ What reads those two fields is unidentified. + ### ✅ Built-in 15 is `set_group_speed` — the group's commanded speed `15` is the biggest unnamed built-in: **1360 sites across 27 of 28 stages**. Its diff --git a/tools/re-capture/isl.py b/tools/re-capture/isl.py index 4a177f0..eec1518 100755 --- a/tools/re-capture/isl.py +++ b/tools/re-capture/isl.py @@ -79,7 +79,8 @@ KIND = {0: 'global', 1: 'imm', 2: 'special', 3: 'local'} BUILTIN = { 1: 'start_coroutine', 2: 'deploy_squadron', 3: 'move_order', 4: 'wait_s', 5: 'kill_coroutine', 6: 'END_PHASE', 8: 'set_flag', 9: 'read_freg', - 10: 'random', 11: 'end_coroutine', 13: 'play_se', 14: 'play_bgm', + 10: 'random', 11: 'end_coroutine', 12: 'activate_unit', + 13: 'play_se', 14: 'play_bgm', 15: 'set_group_speed', 17: 'wait_frames', 18: 'dist_lt', 20: 'hp_pct_test', 24: 'squad_survival_pct', 26: 'damage_unit', 30: 'objective_marker', 31: 'objective_marker_at_route',