re: poking all three squadrons to state 4 does NOT end phase 1
Ran the direct test instead of a seventh attempt at winning. All three objective squadrons were live (state 2); the write to +16 sticks, and 60s later with all three reading 4 -- the value a naturally-destroyed squadron takes, measured on ADN111 -- [ScriptPhase+196] is still 0 and the ordinal still 1. So 'phase 1 clears when ADN110/111/112 are destroyed' is not confirmed and its simplest form is refuted. The bytecode reading (three unit_state polls then set_flag(8)) stands; what does not follow is that flipping the field equals the kill. The persistence is the clue: built-in 69 normalises +16 when it polls, so a running condition coroutine should have overwritten the poke within a frame. It did not, which points at the condition being evaluated only when a trigger fires. Also corrects the per-unit record layout: +4 is 26/27/28 for the three squadrons -- small consecutive integers, NOT the 'live object pointer' the built-in summary describes (an undeployed squadron has +4=0). +20 = 9 is exactly their member count n from the roster, so the record is per-squadron and carries its strength. My own probe printed 'obj=yes' by testing that word for non-zero rather than pointer-ness, which made an index look like an object.
This commit is contained in:
97
tools/re-capture/poke_squadron.py
Normal file
97
tools/re-capture/poke_squadron.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test the phase-1 clear condition by KILLING squadrons from outside the game.
|
||||
|
||||
Six flown attempts failed to reach a phase advance, because the autopilot cannot
|
||||
win Stage 02 (script-runtime-probe.md). The condition itself is cheap to test
|
||||
directly: the script polls `unit_state` on ADN110/111/112, so make those records
|
||||
read as gone and watch whether `[ScriptPhase+196]` goes to 1 and
|
||||
`[ScriptMission+40]` steps to 2.
|
||||
|
||||
Two candidate writes, and the FIRST THING THIS DOES IS CHECK WHICH ONE STICKS:
|
||||
|
||||
+16 state -- but built-in 69 "normalises" this field when it polls, so
|
||||
it may be a cache that the game rewrites within a frame.
|
||||
+4 live object -- built-ins 69/70/24 all test `[rec+4]` first and take an
|
||||
early exit when it is NULL, so NULLing it is the more
|
||||
faithful "this squadron is gone".
|
||||
|
||||
A write that reverts is itself a result: it means the field is derived, not
|
||||
authoritative, and the poke route is closed.
|
||||
|
||||
Usage: poke_squadron.py <Stage02.ssb> [--field 16|4] [--value N] [names...]
|
||||
"""
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
||||
import gmem
|
||||
import isl
|
||||
import squadron_state as S
|
||||
|
||||
def poke32(f, va, value):
|
||||
off = gmem.va_to_off(va)
|
||||
f.seek(off)
|
||||
f.write(struct.pack('>I', value & 0xFFFFFFFF))
|
||||
f.flush()
|
||||
|
||||
|
||||
def main():
|
||||
ssb = isl.load(sys.argv[1])
|
||||
sym2 = isl.symbols(ssb, 2)
|
||||
field = 16
|
||||
value = 4
|
||||
if '--field' in sys.argv:
|
||||
field = int(sys.argv[sys.argv.index('--field') + 1], 0)
|
||||
if '--value' in sys.argv:
|
||||
value = int(sys.argv[sys.argv.index('--value') + 1], 0)
|
||||
names = [a for a in sys.argv[2:] if not a.startswith('--') and not a.isdigit()][1:] \
|
||||
or ['ADN110', 'ADN111', 'ADN112']
|
||||
path = gmem.mem_path()
|
||||
size = os.path.getsize(path)
|
||||
idx = {n: i for i, (_t, n) in sym2.items()}
|
||||
with open(path, 'r+b', buffering=0) as f:
|
||||
m, fb = S.find_mission(f, size, ssb)
|
||||
if m is None:
|
||||
print('ScriptMission not located'); return 1
|
||||
ph = S.u32(f, m + 4)
|
||||
arr = S.u32(f, ph + 324)
|
||||
base = S.u32(f, arr + 4)
|
||||
print('ScriptMission 0x%08X ScriptPhase 0x%08X' % (m, ph))
|
||||
print('before: phase=%s finished=%s' % (S.u32(f, m + 40), S.u32(f, ph + 196)))
|
||||
|
||||
recs = {}
|
||||
for n in names:
|
||||
rec = S.u32(f, base + idx[n] * 4)
|
||||
recs[n] = rec
|
||||
print(' %-8s rec=0x%08X +4=0x%08X +16=%s' % (
|
||||
n, rec, S.u32(f, rec + 4), S.u32(f, rec + 16)))
|
||||
|
||||
# --- does the write even stick? ---
|
||||
probe = recs[names[0]]
|
||||
was = S.u32(f, probe + field)
|
||||
poke32(f, probe + field, value)
|
||||
imm = S.u32(f, probe + field)
|
||||
time.sleep(2.0)
|
||||
later = S.u32(f, probe + field)
|
||||
print('STICK TEST on %s +%d: was=%s wrote=%s immediately=%s after2s=%s -> %s' % (
|
||||
names[0], field, was, value, imm, later,
|
||||
'STICKS' if later == value else 'REVERTED (field is derived)'))
|
||||
if later != value:
|
||||
poke32(f, probe + field, was)
|
||||
return 2
|
||||
|
||||
for n in names[1:]:
|
||||
poke32(f, recs[n] + field, value)
|
||||
print('poked all %d' % len(names))
|
||||
for t in range(0, 60, 5):
|
||||
time.sleep(5)
|
||||
print(' [+%2ds] phase=%s finished=%s states=%s' % (
|
||||
t + 5, S.u32(f, m + 40), S.u32(f, ph + 196),
|
||||
{n: S.u32(f, recs[n] + 16) for n in names}), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user