The user asked for an actively hunting pilot, since a player who kills nothing
cannot trigger an event-gated wave and both previous runs used the survival
pilot.
pilot.py gains SYLPH_HUNT=1. The substantive change is which contacts ENGAGE may
shoot: it previously skipped every "hard" target -- "turrets and hulls are not
the objective" -- and stood off 2500 units from turrets, on the assumption that
an e007 Turret is an AA mount on a capital ship. It is a craft, one of the main
enemy types of the first six missions, and at 100 HP the cheapest kill on the
field. Under SYLPH_HUNT it is a target and the keep-out drops to 600. The run
confirms the pilot engages: steady ENGAGE, fire=1, committed to an
e010_ADAN_Attacker_S at ~2.2 km, hull and escorted asset untouched over 160 s.
Withdrawn: "only 10 of 116 records ever changed a byte in 170 s". This run
measured 41-56 records changing in every 10 s tick. The old figure does not
reproduce.
I cannot say why, because I changed two variables at once -- the record bound
(fixed 0x200 to bounded-by-next-record) and the pilot (survival to hunting).
Either explains it. That is a design error, and the honest outcome is a
retraction without a replacement explanation rather than a story that fits.
The conclusion it had supported is unaffected: the roster identity now rests on
the exact 10-of-10 unit-composition match measured independently.
Still open, and explicitly not concluded: the pilot's own entity scan shows ADAN
drifting 147 -> 129 -> 142, and the late rise has the shape of an arrival, but
the sample-to-sample swing is +/-10, the same size as the effect. AGENT.md warns
that polling faster than the guest updates manufactures a curve out of noise, so
no wave conclusion is drawn. The run probably did not kill anything either
(fc=0, asset at 100%), so it does not test the event-gated model. A stable
per-record liveness field and a working kill counter are both needed first;
REMAINING OB at 0xbdb59668 still does not read as a counter.
Both defects from the previous iteration are fixed by measuring instead of
assuming, and the fix immediately promotes a 🟡 result to ✅.
Labelling: the previous probe assumed object+0x04 -> name_record+0x10 -> char*
and resolved 0 of 116. wave3_probe.py searches for the chain per record instead,
the way unit_discover.py does, and resolves 116 of 116 -- every one through the
pointer at +0x04 with the string at delta 0x00, not 0x10. The 0x10 belongs to
the definition object (vtable 0x820af844); the spawned-entity record
(0x820af030) uses 0x00. Carrying one over to the other cost the last run.
Stride: measured, not assumed. Gaps between consecutive records are min 32,
median 800, with common values 800, 640, 608, 576, 416 and 32. There is no fixed
record size, so the old RECLEN=0x200 window truncated large records and overran
small ones -- which is why its busiest fields were the last words of the window.
Future diffs must bound each record by the next record's address.
With labels available, the "116 records == 116 roster members" claim was tested
properly and is promoted from 🟡 to ✅. The multiset of unit types matches the
static roster exactly: Turret 21/21, e106 Destroyer 19/19, f106 Destroyer 14/14,
f105 Cruiser 11/11, ASFrigate 9/9, ISCMissile 9/9, Attacker_S 9/9, e105 Cruiser
7/7, DeltaSaber_T 7/7, ArrowHead 6/6 -- 10 of 10 exact. A coincidental total is
possible; a coincidental distribution over ten unit types is not. The game
allocates one record per roster member at mission load.
Not settled: REMAINING OB at 0xbdb59668 held 95748078 unchanged all run. That
address is known to be run-dependent, and this was one of the misses, so the run
cannot say whether the pilot killed anything. Re-hunting it is a precondition
for the kill-versus-no-kill test, not an optional extra.
The user proposed that a wave may be released by an event -- enemies destroyed,
an objective completed -- rather than at a fixed time, with the route's
first-keyframe time being a delay after the trigger. It fits the evidence better
than the clock model I had been assuming, and it explains a result I had left as
an open puzzle.
Both probe runs used the survival pilot, whose job is to stay alive rather than
shoot, and both saw no arrivals at all. A clock model says arrivals at
t = 90/120/170 happen regardless of what the player does. An event model says a
player who kills nothing triggers nothing, which is what was measured.
DisableInterval also gains a plausible referent: a pure timetable in the route
table would not need a per-squadron flag to switch a gating interval off.
Supporting evidence, cross-tabulated across all stages: no squadron with
DisableInterval=Yes has a delayed route (55 of 55 at t=0), while all 68 delayed
routes belong to squadrons with DisableInterval=No. If independent, about 3.7
such routes would be expected; P(zero) is roughly 0.02.
Kept at 🟡 and explicitly not promoted. A p ~ 0.02 association on a single
derived statistic is the same shape of near-fit as the "109 of 111" squadron
name base that already misled this corpus once. Name matching itself is sound --
for Stage 02, 108 of 111 squadrons have routes.
Records the test that separates the models: the same stage flown twice, killing
nothing versus killing aggressively, with REMAINING OB at 0xbdb59668 as the
cheap precondition check since it needs no new decoding. That test still depends
on the record-to-squadron labelling defect noted in the previous commit.
The user pointed out that the "Turret" enemy of the first few missions is its
own craft type and must not be confused with the AA turrets mounted on capital
ships. The tables agree, and I should have checked them before hedging:
UN_e007_ADAN_Turret AI_ADAN_CraftSquadron_Rookie x70, _Veteran x19
UN_e007_ADAN_Turret_EX4 AI_ADAN_CraftSquadron_Ace x4
No *_Turret unit ever uses AI_Structure, which is reserved for the things that
genuinely do not move -- asteroids, FloatingMine, SchlosBase, TransitPlatform,
SpaceFortress. AI_ADAN_CraftSquadron_* is Type=Squad in AIParams with the full
manoeuvre-weight block. The distribution matches "first few missions" too: 93
turret members across S01-S06, then nothing until S27.
This retracts the caveat on the 10-of-116 result. Removing it makes that
evidence stronger rather than weaker: the 106 inert records cannot be dismissed
as immobile scenery, because Stage 02's turrets are craft that should be
manoeuvring. The pre-allocated-roster reading survives a test it had been
allowed to dodge.
Lesson recorded in the doc: do not infer a unit's behaviour from its English
name -- check its AIID. The roster is self-describing and the answer was already
in the dump committed two iterations ago.
Since the record count is flat, an arrival would have to flip a field inside a
record. This diffed all 116 records every 5 s for 170 s of Stage 02 flight.
The prediction under test -- groups of 3, 3, 3, 2, 1 records changing state at
t = 90, 120, 170, 210, 240 s -- did not appear. Changes are spread evenly across
ticks with no cluster at any predicted time and no field that transitions once
for exactly three records. Three explanations survive and this run cannot
separate them: the timetable's t is not seconds (at 30 Hz the whole phase-1
schedule finishes inside 8 s, before the first sample); arrival is not marked in
these records; or the mission was not in phase 1.
Supporting, not conclusive: only 10 of 116 records ever changed a byte, and 106
never changed at all. Live entities would be moving, so that fits the
pre-allocated roster reading -- but Stage 02's roster is turret-heavy and a
turret does not move while alive, so "inert" and "not yet arrived" are not
distinguishable here.
Two defects in my own probe, recorded rather than quietly fixed:
- label() resolved to '?' for all 116 records, so nothing could be tied back
to a squadron. That association is what would have made the result
decisive -- "the three records that changed at t=90 are ADN110, ADN111,
ADN112" is evidence; "records 18, 32, 99" is not. unit_discover.py already
solves this and should be reused.
- RECLEN=0x200 was assumed, not measured. The busiest fields are the last
eight words of the window, which is what spilling into the next object
looks like.
Method error kept: the first attempt deferred all analysis to the end and the
turn timeout killed it with 240 s of data in memory and nothing written. The
probe now streams transitions to disk and prints a partial ranking every 60 s.
With a 219 s cold-boot title movie, an end-only report has ~300 s of budget and
one bad estimate loses the whole run.
The routes' first-keyframe time is the arrival schedule. It is not always zero,
and grouping Stage 02's 120 route records by phase and first-frame time gives a
timetable: phase 1 releases 25 routes at t=0 then 3, 3, 3, 2, 1 at t = 90, 120,
170, 210, 240. Phase 2 has every one of its 37 routes at t=0, which is what
pins the meaning: t is measured from the start of its phase, not of the mission.
Entering a phase releases that phase's t=0 group and the rest follow on the
offsets.
That completes the data side of the question this line of work started from --
the schedule is data, split across UnitGroup (who) and Route (when, and the path
flown in), with no fixed enemy count anywhere.
Refuted: counting spawned-entity records does not reveal arrivals. One Stage 02
flight, 210 s sampled every 15 s, counting aligned 0x820af030 in an 8.3 MB span:
flat at 116 throughout, no step at 90, 120, 170, 210 or anywhere.
The reason looks more useful than the refutation. UnitGroup_S02's Count fields
sum to exactly 116 members, and there are exactly 116 records from the first
sample on, so the game most likely allocates one record per roster member at
mission load and a route arrival activates an existing record rather than
creating one. Kept at 🟡, not promoted: n=1, and the obvious refutation -- check
another stage's record count against its member sum (S01=42, S16=2, S29=95) --
needs a save for another stage, and only slot 01 / Stage 02 exists. Noted as the
blocker rather than worked around.
Not settled: whether the timetable's t is frames or seconds (at 30 Hz t=240 is
8 s; as seconds it is 4 min), and where an arrival is observable in memory. The
live flag is presumably a field inside those 116 records, which is a well-scoped
next probe now that the record set is bounded and located.
Operational note recorded: cold boot spent 204 s in the title movie, so a 300 s
probe overran the turn and the first attempt died with its output still in the
pipe. Log to a file rather than piping to tail.