From 770ef2ea2bffb998cbe10cddfe684bb7bff84f5d Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 25 Aug 2026 20:55:49 +0000 Subject: [PATCH] re: CONFIRMED -- the poke produced 'absent', not 'destroyed' Settled from the disassembly, no run needed. Built-in 69's tail maps the lifecycle lookup into [phase+164]: handle == 0 takes the early exit at 0x8226AF44 and returns 0, while the destroyed states return 2, 3 or 4 (and two of those also normalise the record's +16). Those are different values. So poking rec+4 = 0 made the predicate report ABSENT -- the same answer an undeployed unit gives -- and never the answer the script branches on. The condition was polling at 5 Hz throughout and correctly saw 'not here'. Both null results are now fully explained, and neither was evidence about the condition: the first poke wrote a field nobody reads, the second wrote the wrong value into the right field. Simulating a kill needs the handle to stay valid while the lifecycle lookup returns 3/4/5, i.e. the write belongs in whatever sub_82301240 reads, not in the script's own record. Also names built-ins 46/47/48 as squadron_trace / squadron_attack / squadron_escort in isl.py. --- docs/re/script-runtime-probe.md | 28 ++++++++++++++++++++++++++++ tools/re-capture/isl.py | 3 ++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index 99d65270..28550800 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -518,3 +518,31 @@ remaining reason is that **neither poke produced the state the predicate tests** 69 returns for a genuinely destroyed squadron versus a zeroed handle — the run that caught `ADN111` dying naturally at 433 s is the reference, and its record is the thing to compare against. + +## ✅ CONFIRMED from the disassembly: the poke produced "absent", not "destroyed" + +The hypothesis from the previous entry is settled without another run. Built-in +69's tail (`0x8226AE8C`–`0x8226AF48`) maps the lifecycle lookup's result into +`[phase+164]`: + +| lookup result | `[phase+164]` | note | +|---|---|---| +| **handle `== 0`** (the early exit at `0x8226AF44`) | **0** | | +| 5 | 4 | | +| 4 | 3 | also normalises the record's `+16` to 3 | +| 3 | 2 | also normalises `+16` to 4 | +| 2 (and record state 2) | 1 | the healthy/active case | + +**A zeroed handle returns `0`, and every destroyed state returns 2, 3 or 4.** +Those are different values, so the poke `rec+4 = 0` made the predicate report +*absent* — the same answer an **undeployed** unit gives — and never the answer +the script branches on. The condition was polling at 5 Hz the whole time and +correctly saw "not here". + +So the two null results are fully explained, and neither was evidence about the +condition: **the first poke wrote a field nobody reads, and the second wrote the +wrong value into the right field.** + +**To actually simulate a kill**, the handle must stay valid and the *lifecycle +lookup* must return 3, 4 or 5 — i.e. the poke belongs in whatever +`sub_82301240` reads, not in the script's own record. diff --git a/tools/re-capture/isl.py b/tools/re-capture/isl.py index 1fed8c8f..d6c8a156 100755 --- a/tools/re-capture/isl.py +++ b/tools/re-capture/isl.py @@ -84,7 +84,8 @@ BUILTIN = { 26: 'damage_unit', 30: 'objective_marker', 31: 'objective_marker_at_route', 33: 'global_counter0', 34: 'global_counter1', 36: 'screen_fade', 39: 'MARK_LAST_PHASE', 40: 'mark_not_last', 43: 'play_voice', - 45: 'play_voice_vol', 52: 'play_stream', 53: 'sound_busy', 54: 'stop_sound', + 45: 'play_voice_vol', 46: 'squadron_trace', 47: 'squadron_attack', + 48: 'squadron_escort', 52: 'play_stream', 53: 'sound_busy', 54: 'stop_sound', 56: 'unit_relation', 59: 'fade_sound', 62: 'FORCE_END_PHASE', 69: 'unit_state', 70: 'unit_alive', 72: 'group_ratio_pct', 73: 'timer_start', 74: 'timer_limit', 88: 'camera_at', 90: 'camera_at_route',