I wrote that no mission counts kills. Too strong. What holds is narrower: no mission reads the two GLOBAL counters (33/34, verified 0 sites). The game does track kills and missions test them through attribution: built-in 105 killed_by_player(unit) -- reads [squadron+600], which the unit-death handler sets from the damage message's _BY_PLAYER flag -- 117 sites across 14 stages (S02: 38, S01: 32), the 17th-most-used built-in in Stage 02. built-in 72 -- numerator is a per-(killer, victim) squadron KILL COUNTER, incremented in the unit-death handler; 13 sites, S01 only. So 'did the player get that kill' is a common predicate; the game just never asks 'how many kills in total'. Evidence for 105 is the game's own strings: the death handler branches into KILLED_BY_PLAYER vs KILLED, beside LOST_TURRET_BY_PLAYER, SHIELD_DOWNED_BY_PLAYER and the rest. Also withdraws the 'current / initial members' gloss on built-in 72: it is 100 x (units of B killed by A) / (declared members of B), attributed kills rather than survivors, and reads 0 unless the script registered the (A,B) pair. Built-in 55 resolved and shown to be effectively dead (0 sites corpus-wide); built-in 75 confirmed as five HUD text lines via a [phase+376] dictionary.
117 lines
6.2 KiB
Markdown
117 lines
6.2 KiB
Markdown
# What the missions actually test — all 28 scripts surveyed
|
||
|
||
Status: ✅ corpus-wide counts, verified independently; ✅ two idioms identified
|
||
that change how the numbers read; 🟡 one built-in's semantics only partly read.
|
||
|
||
All 28 `Stage\StageNN.ssb` scripts decoded with `tools/re-capture/isl.py`:
|
||
**2,085,628 bytes, 25,705 built-in call sites, 108 of the 147 built-ins ever
|
||
used.** Nothing failed to parse. Re-checked here against the survey: `33=0`,
|
||
`34=0`, `108` distinct, `hp_pct_test` 1955, `unit_state` 1271 — all exact.
|
||
|
||
## ✅ The kill-counter primitives are DEAD, game-wide
|
||
|
||
`global_counter0` / `global_counter1` (built-ins 33/34) have **zero call sites in
|
||
all 28 stages**. Their handlers exist and are wired — they read
|
||
`[0x828F35C4]+8` and `+12` into the int result register — and **no mission ever
|
||
calls them**.
|
||
|
||
39 built-ins are never called at all, including `timer_start`,
|
||
`deploy_and_wait`, `camera_at`, `play_voice` and `screen_fade`.
|
||
|
||
### 🔴 CORRECTION: "no mission counts kills" was too strong
|
||
|
||
I wrote that here, and it is wrong as stated. What is true is narrower: **no
|
||
mission reads the two GLOBAL counters.** The game does track kills, and missions
|
||
do test them — through *attribution*, per unit and per squadron pair:
|
||
|
||
| built-in | what it is | sites |
|
||
|---|---|---|
|
||
| **105 `killed_by_player(unit)`** | reads `[squadron+600]`, which the unit-death handler sets from the damage message's `_BY_PLAYER` flag — "was the most recent death in this squadron the player's doing" | **117 across 14 stages** (S02: 38, S01: 32) |
|
||
| **72** | numerator is a per-`(killer, victim)` **squadron kill counter** incremented in the unit-death handler | 13, **S01 only** |
|
||
|
||
`killed_by_player` is the 17th-most-used built-in in Stage 02. So "did *the
|
||
player* get that kill" is a common mission predicate — the game simply never asks
|
||
"how many kills in total".
|
||
|
||
The evidence for 105 is the game's own strings: the death handler branches on
|
||
that flag into `"KILLED_BY_PLAYER"` versus `"KILLED"`, alongside
|
||
`LOST_TURRET_BY_PLAYER`, `SHIELD_DOWNED_BY_PLAYER`,
|
||
`DAMAGED_BY_PLAYER_GREATER_50` and the rest of the family.
|
||
|
||
## ✅ `hp_pct_test(unit, 0.0)` *is* a destruction test
|
||
|
||
**1786 of 1955 `hp_pct_test` calls (91 %) pass threshold `0.0`** — 107 of
|
||
Stage 02's 109 resolvable sites. Built-in 20's handler (`0x8226AB78`) explains
|
||
it: above zero it returns `pct ≤ threshold`, but at exactly `0.0` it takes a
|
||
separate path that *also* requires the unit's state field `[unit+16] == 4`.
|
||
|
||
So **`unit_state` and `hp_pct_test(…, 0)` are interchangeable "is it destroyed"
|
||
tests**, and stages simply pick an idiom. Real damage thresholds are the
|
||
minority: 50 (57×), 30 (19×), 70 (18×), 40 (14×), then a thin tail.
|
||
|
||
This matters for reading the counts: a stage with no `unit_state` is not
|
||
necessarily testing something different — S14 has none, and 25 of its 34
|
||
`hp_pct_test` calls pass `0.0`.
|
||
|
||
## ✅ `squad_survival_pct` is a boolean, not a percentage
|
||
|
||
29 sites, in S06/S07/S08/S09/S15, and **every one passes a friendly TCAF
|
||
squadron and the threshold 99.9**. The handler divides current by initial member
|
||
count with an **integer `divwu`** before multiplying by 100.0, so the value can
|
||
only be **100 or 0**.
|
||
|
||
At a 99.9 threshold that makes every site the predicate *"has this friendly
|
||
squadron lost anybody at all?"* — an escort-casualty test. **The game has no
|
||
"destroy N% of a squadron" objective.**
|
||
|
||
## ✅ The condition vocabulary, and the outliers
|
||
|
||
| pattern | stages |
|
||
|---|---|
|
||
| `unit_state` + `hp_pct_test` + `dist_lt` + `unit_relation` | the story stages |
|
||
| `squad_survival_pct` (escort intact?) | S06, S07, S08, S09, S15 |
|
||
| `group_ratio_pct` | **S01 only**, 13 sites, always `(TCN001, <ADAN squadron>)` |
|
||
| player gauges 132/133/134 | **S16 only** — 11 + 2 + 2 sites |
|
||
| `is_engaged` | **S05 only**, 3 sites, all on `TCN004` |
|
||
|
||
**S18–S23, the tutorials, are the outliers by a distance.** No flag/trigger
|
||
machinery at all — zero `read_freg`, `clear_flag`, `push_trigger`,
|
||
`named_event`, `MARK_LAST_PHASE`, `FORCE_END_PHASE`, and none of the timer
|
||
family. One `set_flag` and one `END_PHASE` each. They are linear scripted
|
||
lessons, not missions.
|
||
|
||
**S16 has no unit predicates whatsoever** — its symbol table 2 holds three
|
||
entries, and its conditions are a descending player-gauge ladder
|
||
(75/50/25/10/0). A player-state set piece.
|
||
|
||
## 🟡 Not settled
|
||
|
||
* ✅ **`group_ratio_pct` (72) — read to the bottom, and the old label was wrong
|
||
in the numerator.** It is
|
||
`100 × (units of squadron B killed by squadron A) ÷ (declared member count of
|
||
B)`, with a real `fdiv`. The numerator is `e[+12]` of a 16-byte entry in the
|
||
table at `0x828F363C`, created by an interpreter command (max 32 entries) and
|
||
incremented inside the unit-death handler as
|
||
`(killer squadron id, victim squadron id)`. So it is **attributed kills, not
|
||
survivors** — and it reads 0 unless the script first registered the `(A, B)`
|
||
pair in `[phase+328]`. The name `group_ratio_pct` is retained but the
|
||
"current ÷ initial members" gloss is **withdrawn**.
|
||
* ✅ **Built-in 55 is effectively dead**: **0 call sites corpus-wide**. It
|
||
computes a component-count ratio by category (Bridge / Hatch / Turret /
|
||
ShieldGenerator / Thruster / Versatile — names taken from the unit-`.tbl`
|
||
loader's own `TurretCount`/`ShieldGeneratorCount` strings), but its numerator
|
||
is a spawn-time snapshot nothing decrements, its category pairing is offset by
|
||
one (slot 0's denominator absorbed `Hatch`), its k=5 denominator is never
|
||
written, and it truncates with an integer `divw` before the ×100. Whether the
|
||
misalignment is a shipped bug is **unknown**.
|
||
* ✅ **Built-in 75 is text** — five symbol-table-1 names resolved through a
|
||
dictionary at `[phase+376]` into five 1024-wchar slots, which
|
||
`sub_821AA1B0` copies into five 256-wchar HUD fields when `[phase+336] == 2`.
|
||
Mechanism confirmed; **which** text (objectives, briefing, subtitles) is not.
|
||
39 sites, 3 in Stage 02.
|
||
* `isl.py`'s `UNIT_ARG` omitted 71 and 72, so their second unit operand printed
|
||
as a bare integer. Noted in the tool; 56 may be the same shape, unverified.
|
||
* The timer names in `isl.BUILTIN` look mismatched: 73 `timer_start` has **zero**
|
||
sites while 123/124 carry the traffic, and their handlers are the run/stop
|
||
pair. Flagged, not renamed.
|