45f31d69bdf8858629547f4781f7152d09bcda7f
465 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
45f31d69bd |
re: OB correlation method proven; turret tracking refuted
With the value filter in place, an attach watching turret losses -- frequent, unlike marked-fighter kills -- narrowed from 374 candidates to 2 to 0 across three events. That is exactly how a correlation search should behave, and it ends in a refutation rather than fizzling out: no plain u32 in the entity heap decrements consistently with turret kills. Witness checked first, per the rule adopted last iteration: 12 of 25 samples stalled, but all four kill events fall in the early non-stalled stretch between t=45 and t=177, so the events themselves are sound. The negative fits the objective text rather than contradicting it. Stage 02 phase 1 asks to shoot down all invading enemy fighters, and turrets are not fighters, so a counter of remaining marked targets should not move when a turret dies. It also rules out the cheaper reading that REMAINING OB is a general kill or enemy-remaining tally: it ignored ten turret deaths. The method is proven and the search region is right, since 0xbdb59668 where the counter was originally seen lies inside the scanned range. What is missing is unchanged -- two or three e010 kill events in non-stalled samples -- against the same two obstacles, the freeze rate and a pilot managing about two marked-fighter kills per five minutes. One clean 220 s run this iteration produced zero. |
||
|
|
3be3628468 |
re: OB hunt second attempt — saving verified, filter fixed, attach was frozen
The incremental-save fix is verified. A fresh mission caught one e010 event at t=241 s and wrote 1187 candidates to disk immediately; the turn timeout then fired exactly as before, but this time the data survived. The session also clears the candidate file at launch, since candidate offsets are only meaningful within one emulator instance and resuming across launches would intersect unrelated addresses. The correlation itself was wrong though. It matched on the delta alone, so any two float bit patterns whose integer representations differ by the loss count qualified, and in a heap full of positions and velocities that is thousands of words. The 1187 survivors were things like 1044450858, about 0.1f, and 3212461993, a negative float. Candidates must now also look like a counter -- a small non-negative integer in both samples -- which removes the noise by construction instead of hoping the intersection washes it out. The follow-up attach logged zero events across 520 s, which reads like the combat-effectiveness limit again. It was not: 25 of its 26 samples were flagged GUEST STALLED, so the guest was frozen for essentially the whole window. The witness added last iteration did its job, and the lesson is about reading it -- the run summary quoted "0 events" first and the stall count only surfaced on a deliberate check. A run's witness result should be the first thing looked at, before any interpretation of what the run showed. Still unfinished, with no address identified. What is needed is unchanged, two or three e010 kill events in non-stalled samples, and the two obstacles are now clearly separate: the freeze rate, and a pilot that manages about two marked-fighter kills per five minutes. |
||
|
|
cd473268c9 |
re: record the REMAINING OB hunt in the backlog index
The previous commit landed the doc and the probes but its BACKLOG edit asserted on text that had since moved, so the index did not mention the hunt at all. That is the third time an assert-then-replace patch has silently failed this way. The pattern is the problem: the assertion aborts the edit but the commit that follows still succeeds, so a missing index entry looks like a clean run. Locating the anchor first and verifying the insert afterwards, as done here, is the habit to keep. |
||
|
|
348b3833f2 |
re: REMAINING OB by correlation — method works, run unfinished, two repeat defects
The objective text settles what the counter is, so the hunt can be a correlation rather than a value scan: keep every word in the 32 MB heap that fell by the same amount, in the same interval, as a named e010 loss. One event cut roughly eight million words to 1056, so two or three more should leave a handful. The run did not get them. The turn's timeout fired at t=219 s and the probe saved its candidate set only at the end, so the 1056 were discarded and the follow-up attach started from nothing. That is the same mistake already recorded in guest-stalls.md, where an earlier probe deferred all analysis to the end and a timeout killed it with 240 s of data in memory and nothing written. The lesson was written down and then repeated in a new script four iterations later. The attach had a second gap: 535 s with zero losses of any kind, which is indistinguishable from a freeze, and ob_probe2 carried no stall witness so the run cannot say which it was. Both are fixed. Candidates are written after every event and SYLPH_OB_RESUME=1 reloads them so a chained attach keeps intersecting on the same mission, and the witness from wave7_probe is carried here. The underlying pattern is worth naming: each new probe starts from scratch and re-earns the same lessons about saving incrementally and validating liveness. A shared probe harness would stick where written-down lessons have not. The hunt itself is unfinished. No address is identified, and finishing needs a run that catches two or three marked-fighter kills, which is the same combat-effectiveness limit already recorded -- about two per five minutes against a dozen turrets. |
||
|
|
ef17c624ce |
re: the per-phase objectives, from the game's own text
TextIndex in sylpheed-formats::localization already indexes per-phase objective text under S<NN>_P<n>_Objective_<i>, with hints and lose conditions alongside. Querying it settles what the wave work spent many iterations circling, and a disc test now pins it. Stage 02 phase 1 is "Shoot down all invading enemy fighters while watching out for attacks on the ACROPOLIS", losing if the ACROPOLIS is sunk. Phase 2 is "Protect the Admiral's ship the CALIBAN until it has entered the safe zone", losing if the CALIBAN is sunk. Phase 3 is "Destroy the interstellar cruise missiles headed for Schlos Base", matching phase 3's roster of exactly nine ISCMissile and SUBOBJ_013. That explains the phase advances and shows they are not all the same kind. Phase 1 is a kill-all-marked-fighters gate -- fighters, not the turrets every run killed almost exclusively. Phase 2 is positional and ends when the CALIBAN reaches a safe zone, so "what advances a phase" never had a single answer. It also closes a loop to the first session. The hints say red mission markers [OB] indicate your targets, so REMAINING OB -- the HUD counter hunted in the earliest iterations and located at 0xbdb59668 -- is the count of remaining marked objective targets, which is phase-1 progress itself. The old reframing that the counter is not a roster was right, and this names what it is. It is also the correct signal to watch for an advance: not deployed, which only changes once the next phase deploys, but REMAINING OB reaching zero, which is the cause. Method lesson recorded because the reflex will recur: localization.rs has had objectives, hints and lose_conditions for some time, and several iterations were spent reconstructing the same information by hand from SUBObjective strings and the guide script. The reconstruction was not wasted -- it produced the IXUD UTF-16BE and language\ prefix findings independently -- but the corpus should have been searched before the disc was, and grep -rl TextIndex crates/ would have saved the detour. Minor discrepancy noted, not investigated: ixud.rs documents the string pool as UTF-16BE and localization.rs as UTF-16LE. |
||
|
|
054fd7586a |
re: target preference works; the blocker is now combat effectiveness, not RE
pilot.py gained SYLPH_PREFER, a unit-name substring whose matches get their target score multiplied by 0.05 while everything else is multiplied by 4.0. With SYLPH_PREFER=e010 a clean 320 s run, zero stalls by the witness, killed eight turrets and two Attacker_S. The preference is real -- e010 kills went from roughly one across all previous runs to two in a single run -- but it is weak. Turrets still outnumber attackers four to one in the kill log, because target commitment and simple proximity keep pulling the nose back to them, and phase 1 fields 108 turret craft against 16 attackers. Deployed stayed at 41 throughout, so no phase advance. That quantifies the blocker. Clearing the marked attackers means destroying 16 craft, and at two per 320 s that is about 2560 seconds, roughly 43 minutes of continuous verified-live flight across many chained attaches, against a freeze rate of about two runs in five. This is no longer a reverse-engineering problem. Everything needed to observe the phase advance is built and validated -- the roster-to-craft link, the liveness read, the stall witness, chained attaches and the discard rule. What is missing is a pilot good enough to complete the mission objective, which is game-playing work with an uncertain payoff. The choice is recorded rather than made, because it is about how much effort one confirmation is worth rather than a technical unknown: invest in the pilot, accept the static answer where only the trigger is inferred rather than observed, or attempt one very long chained run betting against the freeze rate. |
||
|
|
d1b03e5f8c |
re: the mission script is readable — phase 1 never advanced because we killed the wrong things
The localised string tables are decoded. language\*_local_string.tbl uses a
third archive prefix, language\, and is not IDXD: the magic is IXUD and the
strings are UTF-16 big-endian, which is why earlier ASCII-oriented dumps of
these files produced garbage.
That makes Stage 02's nine sub-objectives readable in words -- rescue the ally
destroyer, destroy all enemy fighters, sink all enemy warships, rescue the
Amalthea, keep the Caliban undamaged, sink the destroyer targeting the
Acropolis, destroy all enemy cruise missiles. Ninety-one objectives are defined
across the game.
The guide script then confirms the phase structure twice over. It reads as three
acts -- marked attackers and protecting the Acropolis, then a warship whose
engine, weapons and shield projector must be destroyed, then cruise missiles
taken down one by one -- and each act matches the per-phase roster composition
found independently. Phase 3 fields exactly nine UN_e201_ADAN_ISCMissile against
SUBOBJ_013's "You destroyed all enemy cruise missiles", an exact match between
two files decoded on different days by different routes.
And it gives a mundane explanation for every null result in the arrival work.
The phase-1 objective is stated outright in the script: the attackers with the
orange markers are the targets, meaning UN_e010_ADAN_Attacker_S, of which phase
1 fields four squadrons. Every loss line in every run log reads
UN_e007_ADAN_Turret, with e010 appearing twice in total, because SYLPH_HUNT was
built to make turrets targets and SYLPH_KEEPOUT was tuned for them. The 42 kills
were overwhelmingly the wrong 42.
Kept at 🟡 because the objective text is certain but the inference that this
particular objective gates the phase advance comes from the script's structure
rather than from measurement. The test is a pilot that prioritises e010 over
e007, which is a target-preference knob rather than new decoding, with the
existing probe watching deployed jump from 41 toward the phase-2 roster.
|
||
|
|
115e874986 |
re: message tables searched — phases are pervasive, the trigger is not in data
StageMessageSet_S02.tbl is resolved. stage-mission-tables.md had recorded it as absent from the pak; it is present, and the archive prefix is message\ rather than stage\ like its siblings. Sweeping prefix candidates over the full 16630 entry TOC found it immediately. It holds a voice mapping, a unit message set pointer, and a list of 31 message tables including ScriptMessage_S02_msg.tbl and the per-character PresetMessage sets. message\UnitMessageSet_S02.tbl then gives an independent confirmation of the phase structure. Its 26 records have exactly four named fields: CrewCount and PresetMessage_Phase1, Phase2 and Phase3. Every message set carries a different preset table per phase, so phases now appear in three unrelated table families -- the stage record, the route names, and the message sets. They are not a quirk of one file. The promising lead did not survive. ScriptMessage_S02_msg.tbl has 149 records whose third positional field takes values like Killed and None, which looked like a trigger. Enumerated across S01 and S02 it is a small closed vocabulary of None, Emergency, Killed and Noise -- delivery categories for radio chatter, meaning how a line is played rather than mission control flow -- and the fourth field is 1 in every record, so it is not an argument either. Rejected. That leaves no table anywhere carrying a phase-advance condition. Everything found is a consequence of the phase and never its cause, which points at the advance logic living in code. The only place left is the executable, and default.xex is encrypted on disc, so the decrypted image exists only in guest RAM and reaching it needs a run plus function-level work. Recorded as the honest end of the static search rather than continuing to guess at table names. |
||
|
|
5a6c4f5adc |
re: enemies come into play per phase, deployed at phase start
Cross-referencing Route_S02 with UnitGroup_S02 gives per-phase membership: phase 1 has 37 squadrons and 42 members, phase 2 has 34 and 36, phase 3 has 44 and 49, with squadrons appearing in more than one phase so the totals overlap. The measured deployed count, stable across many runs and from the first observable instant, is 41 of 116 records. 41 against 42. The deployed set is the phase-1 roster, to within one record. That makes every previously puzzling observation coherent. All 116 records exist at mission load, confirmed on two stages, but only the current phase's squadrons hold craft. Deployed is 41 before the ready room ends and never moves because phase 1 deploys as a unit at load rather than on a schedule. No arrival was ever observed in up to 435 s of verified-live flight with 42 kills because phase 1 never completed in any run, and phases 2 and 3 hold the other 85 members. And the route times are within-phase fly-in timings rather than release times, which fits the frames reading where Stage 01 phase 2's 1500, 1800 and 2100 are 50, 60 and 70 seconds at 30 Hz. So enemies come into play per phase, deployed together at phase start, with each squadron's route giving its entrance path and the moment it flies in. The off-by-one is not explained and is recorded rather than rounded away. The player's record may be counted differently, one squadron may hold craft in a way the liveness read misses, or one phase-1 member may genuinely not deploy. A one-record gap is exactly the size of the errors that have misled this corpus before. What advances a phase is now the single remaining question. It is directly observable, since deployed would jump toward the phase-2 roster. It did not happen in 435 s of verified-live flight with 42 craft destroyed, so it is neither a short timer nor a small kill count. SUBObjectiveSettings names nine objectives but carries only rewards and HUD strings, no trigger. Not yet searched: StageMessageSet_S02.tbl, which has never resolved in the pak, and the executable, whose decrypted image exists only in guest RAM. |
||
|
|
496745ef26 |
re: deployment is resolved at mission load; withdraw the take-off hypothesis
The run meant to catch a deployment step at take-off removed the hypothesis
instead. One continuous series of 64 samples over about 380 s, started when the
roster first appeared and running through the ready room, the transition to
flight at +47 s, and several minutes of flight, shows deployed at 41 and craft
at 292 with not one change in either number.
So the previous iteration's suggestion that deployment is a single step at
take-off is withdrawn. It rested on ready room 39/276 against flight 41/300,
which came from different runs and was flagged 🟡 for that reason. Measured
within one run the values are identical before and after take-off, so that gap
was cross-run variance.
What this does establish is stronger. The roster is already deployed when the
roster first exists: 41 of 116 records hold craft before the ready room ends and
nothing changes afterwards. Together with the roster identity confirmed on two
stages and the earlier piloted runs, Stage 02 phase 1 has no observable
in-mission arrival at all -- not in the ready room, not at take-off, and not in
up to 435 s of verified-live flight during which the player destroyed 42 craft.
The route times still mean something, since 0/90/120/170/210/240 and Stage 01's
1500/1800/2100 are structured rather than noise. The frames reading remains the
best fit, and under it they are most likely fly-in animation timings applied at
load rather than release times.
Two things stay open and are recorded as such. Phases 2 and 3 have never been
tested: every run has stayed in phase 1 and phase advance was never located, so
a phase transition is now the most likely place an arrival could exist. And this
particular run had no pilot, so it adds nothing on event-gating; the piloted
435 s run already covers that ground.
|
||
|
|
cb6e0fe7f9 |
re: sample from the first moment — still flat, and the wait variant caught the ready room
early_probe defers everything expensive: no witness calibration, no per-record
labelling, and the heap scan uses bytes.find. Setup now completes in 0.5 to 0.8
seconds instead of about 25, so the first sample lands essentially at flight
detection.
It is still flat. Deployed reads 41 at flight+0.8 s and every sample after, with
the only change in 252 s being 41 to 40 when one squadron was wiped out. No
climb at any point. Under the frames reading that is expected, since the whole
phase-1 schedule would be finished within eight seconds of mission start.
The attempt to get ahead of flight detection did not work, and the reason is
worth recording. A second run started the probe before the launch and waited for
the roster to appear. It appeared with 116 records, but the numbers were
deployed 39 and craft 276, flat for the whole window. That is the ready room:
the roster is built before take-off, so waiting for it catches the pre-flight
scene, and the probe's window expired around the time flight actually began.
Waiting for the roster is not the same as catching mission start, and the test
as designed does not do what it claims.
The two runs together do suggest something, held at 🟡 because they are
different runs. The ready room shows 39 deployed and 276 craft while flight
shows 41 and 300, so two records and twenty-four craft appear between them. That
points at deployment being a single step at take-off rather than a schedule
unfolding during the mission.
Next is one run of about 400 s with the probe waiting for the roster, long
enough to span ready room, take-off and flight in a single continuous series,
which would show the 39 to 41 step directly or refute it.
|
||
|
|
920938ffce |
re: the 42 anomaly is Stage 01, and the route times are probably frames
The rescan-until-baseline retry settled the anomaly: five rescans over 50 s
stuck at 42, so not a load race. Inspecting the live mission rather than
guessing, the records include UN_S01_Asteroid_cmesh_* and UnitGroup_S01.tbl is
resident while UnitGroup_S02.tbl is not. The launch sometimes loads Stage 01
instead of Stage 02, and the discard rule caught every such run, which is why
none of them contaminated a result.
That accident supplies a test previously recorded as blocked. The roster
identity needed checking against a different stage, and no second save exists.
Stage 01 provides one: static 42 members and 13 distinct units against live 42
roster records and 13 definitions, with composition matching 20/20, 6/6, 5/5 and
2/2. One record per roster member now holds on two independent stages with
completely different rosters.
Stage 01's timetable also bears on the unit question, which has been open since
the schedule was found. Its phase 2 has entries at t=1500, 1800 and 2100. As
seconds that is 25 to 35 minutes into a single phase, with three arrivals spread
across the last ten minutes of it, which is not a plausible mission. At 30 Hz
they are 50, 60 and 70 seconds, which is exactly the shape of a paced phase.
If the unit is frames, Stage 02's phase-1 entries at 90, 120, 170, 210 and 240
are 3 to 8 seconds, every one of them before the probe's first sample, which
lands about 25 s after flight is detected. That would explain every null result
in this line of work at a stroke, and it fits what the probe has always seen:
deployed already at 41 by t=0 and never changing.
Kept at 🟡 -- it is an inference from the implausibility of one reading rather
than a measurement -- but it is now the leading explanation, ahead of
event-gating. The test is to sample at flight+2 s and compare with flight+30 s,
which needs the probe's enumeration and calibration reordered after a first
cheap sample rather than any new decoding.
|
||
|
|
26c062a0d1 |
re: blocker removed — chained attaches extend the observation window
The previous iteration declared route entries t=210 and t=240 unreachable because a shell call is capped at 595 s. That was wrong, and it rested on an assumption I never checked: launch_mission.sh leaves the emulator running and it survives between Bash calls within a turn. Verified directly -- 611 s elapsed and still running after the launching call had returned. attach_session.sh attaches a pilot and probe to an already-running mission, and attaches chain, so the window is bounded by the turn rather than by one call. On one continuous mission: 300 s of clean observation followed by an attach that ran 135 s before freezing, giving 435 s of cumulative verified-live wall-clock. The craft population fell from 300 to 258, so 42 were destroyed, and deployed fell 41 to 38. Zero confirmed arrivals throughout -- the longest verified-live observation so far. That covers the first four phase-1 route entries, t=90, 120, 170 and 210; t=240 needs 436 s and was missed by a second. The coverage claim is qualified rather than asserted. The wall-to-game conversion uses 55 %, from a 16.5/s frame rate against an assumed 30 Hz tick, but the witness has measured 8, 11, 11, 21 and 24 per second across runs. At the low end the factor is 0.27 and the same 435 s covers only about 117 game-seconds, reaching t=90 alone. So what is established is that nothing arrived in 435 s of verified-live phase-1 flight with 42 kills; how many scheduled arrival times that spans depends on a tick rate that is not yet pinned, and pinning it is what would make the result sharp. Also refuted: SYLPH_HZ=3, a lower pilot poll rate, produced the lowest calibrated frame rate of any run at 8/s while landing the most kills at 26. The pilot's polling is not the throttle and lowering it does not buy game time. |
||
|
|
324cb7f85a |
re: blocker — the schedule's later entries cannot be reached in one turn
Computed the observation ceiling, which should have been done several iterations ago instead of being approached one run at a time. A turn's shell call is capped at 595 s; boot costs about 220 s, of which roughly 190 s is a title movie the launch script waits out because tapping breaks the title; probe startup is about 25 s. That leaves roughly 350 s of observation, and at the measured 55 % of real-time that is about 193 game-seconds. Against the phase-1 route schedule: t=90 needs 164 s of wall-clock and t=120 needs 218 s, both already observed with no arrival. t=170 needs 309 s and is reachable but only on a run that does not freeze. t=210 needs 382 s and t=240 needs 436 s, and neither fits in a turn at all. So the tail of Stage 02's phase-1 schedule cannot be tested under this harness however many runs are attempted. Recorded as a blocker rather than improvised around. Two things would unblock it and neither is mine to decide: a safe way to skip the title movie, which is more than half the budget and would roughly double the window to about 297 game-seconds covering every entry, or a longer shell timeout if the harness permits one. This run is discarded: frozen at t=90 with 15 stalled samples and 3 losses, correctly flagged and not interpreted. The freeze tally is now three clean of five, lower than the three-of-four quoted last iteration. What stands is unchanged: zero confirmed arrivals over 300 s of verified-live flight, about 165 game-seconds, covering the t=90 and t=120 entries. That is the strongest statement this harness can currently support. |
||
|
|
2e5bcf628e |
re: freezes are stochastic, not eliminated; calibration vectorised
Correcting the previous iteration's wording. It claimed on n=1 that disabling the periodic rescan "removes the freeze". With more runs that is too strong: the configuration is now clean at 210, 240 and 300 seconds and frozen at 60 on a fourth run. The tally across configurations: the heavy probe froze at 27, 45, 83, 183 and 255 seconds; the cheap probe with a 90 s rescan froze at 183; the cheap probe with no rescan is clean in three runs of four past 200 s. That is a real and large improvement in survival, but it is a change in probability rather than a fix, and the documentation now says so. The witness handled the frozen run correctly -- losses stop at t=45 and stalls are flagged from t=60 onward, with no contradiction between them. Separately, the candidate search was the last Python loop over eight million words and most of the remaining startup cost. Replaced with two numpy vector operations; 7250 candidates found and startup is no longer the bottleneck. Boot now dominates: about 190 s of title movie plus 35 s to flight against a 595 s turn cap leaves roughly 350 s of observation, so boot is the only remaining lever if longer windows are needed. Practical rule recorded: do not treat a single frozen run as evidence. Run, check the witness, discard the frozen ones and keep the clean ones -- about three in four are usable now, which is workable where it previously was not. |
||
|
|
d194aef52d |
re: clean run reproduces; startup was eating a third of the window
Second consecutive run with no stall flag on any sample, which confirms that disabling the periodic rescan is what fixed the freezes. Nine losses, four flicker increases all correctly rejected, and zero confirmed arrivals. The trustworthy negative now extends to 240 s of verified-live flight, roughly 132 game-seconds, so nothing arrives past the route table's t=90 or t=120 entries either. The flicker rate is worth noting: about one spurious increase per minute. That is the rate at which the old rule would have been manufacturing arrivals. The arithmetic of the run exposed a waste. Boot finished at 249 s, the probe ran 240 s, and the turn's 595 s cap fired, leaving about 100 s unaccounted for between them -- the witness calibration and the initial craft enumeration. enumerate_craft was iterating every 4-byte word of 32 MB in Python, eight million steps, to find fourteen fixed needles. Replaced with bytes.find() per definition VA, which is the same search at C speed and is what the vtable scan already did. Not yet run. If that recovers most of the hundred seconds the observation window grows from about 240 s to about 340 s, roughly 187 game-seconds, which would finally reach the t=170 route entry -- the first of the schedule's later arrivals that no run has yet been able to observe. |
||
|
|
cc21e58432 |
re: the periodic rescan was the freeze driver; first trustworthy negative
Two changes this iteration, one failed and reverted, one that worked. Trimming the witness calibration to eight spread 512 KB windows instead of the full region found 17 candidates, none of them frame counters, and the witness then reported 0 of 17 stalled on every sample of a run that recorded 13 losses. That is a total contradiction, caught by the same internal check that exposed the previous three witness failures. The frame-rate cluster is sparse and spread sampling misses it. Reverted: two 32 MB reads once at startup is simply the price of a witness that works. The recurring cost was the periodic rescan, a 32 MB read every 90 to 180 seconds, and it exists only to catch craft appearing from nowhere -- which the roster work already established does not happen, since every participant is allocated at mission load and an arrival is a state change rather than an allocation. Disabled. The result is the first fully clean probed run: 3875 candidates, 32 witnesses at 11/s, no stall flag on any sample from t=0 to t=210, and eight losses spread across it. Previous probed runs froze at 27, 45, 83, 183 and 255 seconds. This one ended on the turn timeout. One run, so not proven, but together with the clean no-probe control it points at recurring heavy reads rather than at memory reading as such. That also produces the first arrival result that means what it says. Every earlier one carried a caveat -- a stalled guest, an unvalidated witness, a probe degrading what it measured. This one has a validated witness reporting no stalls, a demonstrably live guest, and a clean end: zero confirmed arrivals over 210 s of verified-live Stage 02 flight, roughly 115 game-seconds, while the player destroyed eight craft. It does not settle the question. The route table's t = 170, 210 and 240 entries remain out of reach in a single turn. But it does establish that nothing arrives in the first ~115 game-seconds of phase 1 under those conditions, which none of the previous fifteen runs could honestly claim. |
||
|
|
2866f96bd6 |
re: the stall witness is validated; the freeze is now the blocker
Fourth attempt at the witness, and it passes the check the previous three failed. Searching the whole 32 MB region rather than a 4 MB window gives 6500 candidates and 32 witnesses in a clean 21/s cluster matching the measured frame rate, and the flags have zero contradictions with the loss data: the single loss falls in an unflagged sample and every flagged sample has none. The guest genuinely froze at about 45 s and the instrument says so. That leaves the freeze itself as the dominant problem. Onset across runs is 27, 45, 83, 183 and 255 seconds, median around 83, so the usable window is one to four minutes and unpredictable. This run produced exactly one loss and is useless for the arrival question even though the instrument finally works. Fifteen runs have now gone into that question without an answer, largely for this reason. Worth being honest about the probe: "cheap" was never that cheap and the witness made it worse. It still reads 32 MB to enumerate at startup and another 32 MB every 90 s to rescan, and the full-region calibration added two more 32 MB reads. The 45 s freeze came immediately after that calibration. Calibration now samples eight spread windows of 512 KB -- 4 MB total, still covering the whole span -- instead of reading everything twice, and the rescan interval is doubled to 180 s. Not yet run. Also this iteration: the 42-record anomaly recurred, the discard rule correctly refused to interpret the run, and a rescan-until-baseline retry is now in place so the next occurrence shows whether the count climbs toward 116 or sits at 42. |
||
|
|
03f58d2f54 |
re: fix the bind failure; witness search window was too narrow
Two harness problems, one fixed and verified, one diagnosed. Bind: three consecutive attempts failed and the session aborted rather than flying an unattended craft, which is last iteration's guard working as intended. The cause is that entities2 self finds the player by motion between two samples, so a craft sitting still at mission start is invisible. The session now holds the throttle for three seconds before each attempt. Verified: the next run bound immediately and produced 11 losses with zero confirmed arrivals, making fourteen runs without one. One increase, 10 to 11, was surfaced and correctly not counted. Witness: the frame-rate-band selection is still unreliable. It found only five candidates in band and reported 0 of 5 moved in samples where craft were being destroyed, and a counter genuinely running at 24/s cannot fail to advance across a 15 s sample -- it would gain about 360. They are bursty counters that moved during the 3 s calibration and then stopped. The cause is mine. When the witness was bolted into wave7_probe the candidate search was narrowed to a 4 MB window, while timer_probe searched the whole 32 MB region and found 286 candidates with a clean cluster near 17/s. The narrowing was never justified, it was just cheaper to write. Now searches the full region once at startup, which is a one-off cost rather than per sample, and warns explicitly when fewer than eight witnesses are available so a weak vote is visible instead of silently trusted. Not yet run. Worth recording: this is the third attempt at this witness, and all three failures share one pattern -- a shortcut in selecting the witness rather than in the voting logic. Each was caught only because the flagged samples contradicted the loss events in the same output. That internal contradiction is the real check and should survive into any future version. |
||
|
|
af4f364d6c |
re: multi-witness vote works, its threshold did not, and freezes persist
The graded output is the improvement. Instead of a single bit it reports how many witnesses agree, and the sequence tells a coherent story: 11, 9, 7, 4, 1, then 0 of 31, with the drop to zero at t=183 s coinciding exactly with the last loss and 106 s of nothing after it. That is a real freeze, identified. The threshold was wrong though. Flagging a stall at "fewer than half" marked the entire run stalled, including samples in which craft were destroyed, so 11 of 31 advancing is a healthy guest rather than a stalled one. The cause is the cluster choice: the modal rate was 93/s, far above the ~16.5/s frame rate timer_probe measured, and those are subsystem counters that tick in bursts and sit idle in most 15 s windows even while the game runs. Picking the modal cluster was convenient rather than principled. Fixed to prefer the cluster whose rate falls in the frame-rate band of 8-40/s, falling back to modal only if none exists, and to flag a stall only when zero witnesses advance, which is the signal the data actually supports. Not yet run. The uncomfortable part: this run used the cheap probe and still froze, at about 183 s. The previous iteration's "0 stalled samples" came from the unreliable single-word witness and cannot stand as validation. What the evidence supports now is that the no-probe control ran 300 s clean, the heavy probe froze at 27 to 255 s, and the cheap probe froze at 183 s -- one run on each arm. Cheap sampling plausibly helps but does not remove the freeze, and it is equally possible the freeze is stochastic and the control was lucky. Recorded as unresolved rather than resolved in the probe's favour. Practical consequence: the usable window is roughly three minutes per run, sometimes less, whether or not the probe is cheap. Experiments needing longer have to survive a freeze or be redesigned around one. |
||
|
|
88b47a5448 |
re: persistence rule works; the stall witness gives false positives
Run 12, cheap probe with a bound pilot: 16 losses over 290 s and zero confirmed arrivals, making twelve runs without one. The persistence rule earned its place immediately -- an increase of 13 to 15 was surfaced and correctly not counted, since it does not start from zero. Under the previous rule it would have been invisible, and a similar flicker straddling zero was nearly written up last iteration as the first arrival. The stall witness, on the other hand, is unreliable. Thirteen samples were flagged GUEST STALLED while recording losses in those same samples, and a frozen guest cannot destroy craft, so they are false positives and the run was healthy. The cause is the selection rule: it took the first word in a 4 MB window whose rate fell in a plausible band, and plenty of counters advance intermittently without saying anything about whether frames are being rendered. timer_probe had already solved this properly -- 286 candidates, a rate histogram with a dominant cluster near 17/s -- and that lesson was not carried over when the witness was bolted onto the probe. Now fixed to a majority vote: collect every candidate, keep the modal-rate cluster, sample up to 32 of them, and report a stall only when fewer than half advance. It also prints RUN UNVALIDATED when no witness is found, because an earlier run printed "stalled samples=0" alongside "tick witness: NONE", and a witness that does not exist cannot report zero stalls. Not yet run. Consequence worth flagging: the "0 stalled samples" that validated the cheap probe last iteration came from this same unreliable witness and should be re-confirmed under the majority rule. The pilot-log speed analysis that established the stalls in the first place is unaffected. Also fixed: the entity bind now retries three times and aborts if it never takes, instead of silently flying an unattended craft -- one run was wasted that way this iteration, producing no kills and no information. |
||
|
|
3b91a4c6d1 |
re: cheap probe verified; its one "arrival" is flicker
First run of wave7_probe: zero stalled samples across the whole run, against three consecutive heavy-probe runs that stalled at roughly 27, 83 and 255 s. The fix works. The guest is also visibly healthier -- 19 losses against 8 in a heavy-probe run of comparable length -- so starving the emulator had been suppressing the very activity the probe existed to watch. It also reported the first arrival of the whole line of work, and that arrival does not survive inspection. A record went 1 -> 0 at t=229, 0 -> 2 at t=259 and 2 -> 0 at t=274. Two craft appearing and vanishing within fifteen seconds is not a wave. The same log contains the giveaway: at t=60 a record read 13 and at t=75 the same record read 14, an increase, with nothing printed, because the probe only surfaced decreases. The hull-based liveness read flickers, and a flicker that straddles zero was indistinguishable from an arrival under the old rule. The count therefore stands at zero confirmed arrivals in eleven runs. Two changes, neither yet exercised: every increase is printed rather than only those from zero, and an increase from zero counts only if it persists across two consecutive samples, with a candidate that returns to zero discarded as flicker. Recorded because it was close: under the old rule this run would have been written up as "first arrival observed", which would have been the strongest-looking result so far and wrong. |
||
|
|
52809ab92f |
re: confirmed — the probe was causing the guest stalls
The control needed no new decoding: hunting pilot, keep-out 1400, 300 s, and no memory probe at all, judged only from the pilot's own log. It ran healthy the whole way -- 211, 257, 250, 241 and 200 distinct speed values in successive 60 s windows, with no decline in the last one -- against probed runs that stalled at roughly 27, 83 and 255 s and ended with a single distinct speed across 400 lines. So the instrument was degrading the thing it measured. AGENT.md warned that a full scan competes with the emulator for every core under lavapipe, and the probes grew heavier each iteration until wave6 was reading the entire 32 MB entity heap plus about 300 extra preads every 12 seconds while the game rendered. The control is n=1. The contrast is stark and the mechanism was predicted in advance, so this is recorded as confirmed enough to act on rather than proven; a second clean control would settle it. What it costs is worth stating plainly. Every "no arrival" result so far was collected while the probe was running, under conditions the game was struggling with and in some runs after it had frozen. That does not make them wrong -- the elimination test at t=163 s sits in a window the witness later showed was live -- but none of them is as strong as written, and the arrival question deserves re-running with cheap sampling before silence is read as evidence. wave7_probe.py is the fix: one full enumeration, then polling only the hull word at each known craft base, about 1.2 KB per sample instead of 32 MB, with a full rescan every 90 s to catch anything genuinely new. Three or four heavy scans per run instead of twenty-five. Implemented but not yet run, so the claim that it stops the stalling is untested and the next run must report the witness first. |
||
|
|
e1e035fb57 |
re: the stall witness works, and stalls may be caused by the probe
First run with the tick witness. It flagged a stall from t=27 s and every sample after, and the pilot's own telemetry -- which the probe never reads -- agrees: 35 distinct speed values across the whole log and exactly 1 in the last 400 lines, against 236 in the first 400 of a healthy run. All variation is in the first ~50 s. The witness is validated. It earned its keep on that same run. Without it the output reads as "no arrivals across 313 seconds with 300 craft resident" -- clean, quotable and completely worthless, because the game was frozen for 90 % of it. Rule adopted: a run whose witness reports a stall is discarded, and every write-up states the witness result. Flat samples are not evidence unless the witness says the guest was advancing. Stalls are frequent and early. The last three long runs stalled at roughly 255 s, 83 s (after the player died) and 27 s. That makes long observation windows unreliable, and long windows are exactly what the arrival question needs. Leading suspect is the probe itself, and it is recorded because it is uncomfortable rather than despite it. AGENT.md warns that a full memory scan competes with the emulator for every core under lavapipe, and these probes have grown heavier each iteration: wave6 now reads the entire 32 MB entity heap plus about 300 extra preads every 12 seconds while the game renders. If that is the cause, the instrument has been degrading the thing it measures and the earlier "no arrival" results were collected under conditions the game was struggling with. Next is a control that needs no new decoding: run the hunting pilot for 300 s with no probe at all and judge from the pilot log alone. If it does not stall, sampling has to get much cheaper -- narrow the scan to the roster region, sample less often, or reread only the craft bases already located instead of rescanning the heap. The multi-squadron kill-threshold test did not run: the guest froze before anything was destroyed, so there were no losses to threshold. |
||
|
|
44e9f8dc94 |
re: eliminate an enemy squadron — no wave follows; add a stall witness
SYLPH_KEEPOUT makes the hunt keep-out a knob instead of a hard-coded 600. At 1400 the pilot both kills and survives: hull 1500 and escorted asset 100 % for the whole run, ENGAGE throughout, eight loss events against seven in the run where it died at 83 s. That let the elimination test actually run. An e007 Turret squadron was ground 18 -> 10 -> 8 -> 4 -> 2 -> 0, reaching zero at t=163 s with deployed falling 41 to 40 -- an enemy squadron destroyed outright for the first time in nine runs. No arrival followed. ARRIVALS=0 at every sample including all those after t=163. The hypothesis that a wave is released when a squadron is wiped out rather than merely damaged does not survive its first test. Two further losses occurred at t=176 and t=202, so the mission was demonstrably still live and still processing kills; it simply produced no arrival. This refutes elimination-of-one-squadron as the trigger. It does not refute event-gating generally -- a threshold across several squadrons, an objective completion, or a specific squadron could all still be the gate. The valid window is smaller than the log suggests, about 90 s after the elimination rather than 143 s, because the guest stalled around t=255 s. The pilot's telemetry gives it away: the last 400 log lines contain one distinct speed value against 236 in the first 400, with no timestamp gaps -- the process kept logging while the game stopped advancing. Nothing in the probe output distinguished a stall from a quiet mission, and the same ambiguity affects the trailing flat samples of earlier runs. wave6_probe now locates a counter advancing at frame rate, samples it each tick and prints GUEST STALLED when it fails to advance, so future runs validate themselves. Implemented but not yet exercised in a run. |
||
|
|
a884c62098 |
re: player death bounds every run; fix a harness bug that shortened the windows
Correction first. The sed used to derive each session script from the last stripped the probe's arguments, so wave5, census and wave6 sessions invoked their probes with no arguments and every derived probe has been running on its own defaults. The previous iteration's claim that the run was "cut at 240 s by the turn timeout, not the planned 330 s" is therefore wrong: the probe used its default of 240. The pilot got the requested duration while the probe watched for a different one, and the numbers were plausible enough that it went unnoticed. No earlier conclusion is invalidated -- the windows were real, just shorter than intended and misattributed. All three sessions now pass SECS and EVERY. First n -> 0 ever observed: the player's own record went 2 -> 0 at t=83 s and deployed fell 41 to 40. The signal does register elimination, not just damage. No arrival followed, which is weak evidence against the squadron-elimination trigger since the record eliminated was the player rather than an enemy squadron. Two other turret records dropped from 18 in the same sample; noted without interpretation. The important finding is what came after. For the remaining 220 seconds the mission was frozen -- exactly 288 craft, zero losses, zero arrivals, across 18 consecutive samples. So the usable observation window is not the probe duration but however long the player survives. A 340 s probe that loses its pilot at 83 s yields 83 s of evidence and 257 s of nothing, and several earlier "nothing over 240 s" results may have been much shorter in practice than they look. That also explains why pilot.py was written to survive rather than to shoot. The SYLPH_HUNT mode added two iterations ago drops TURRET_KEEPOUT from 2500 to 600, buying kills at the cost of exactly the survival the run depends on. The elimination test itself did not run: the squadron reached 14, not 0, before the pilot died. What is needed is a pilot that kills and survives -- hunt turrets but keep the evade and retire behaviour, or a keep-out between the two extremes. That is tuning, not discovery. |
||
|
|
c56c93e1e9 |
re: long run — a squadron ground 18 to 2, still no arrival
The per-record instrument works and resolves individual squadrons. Over 240 s with the hunting pilot, seven loss events all landed on the same record, tracking one e007 Turret squadron from 18 craft down to 2 while deployed held at 41 and the global count fell 300 to 284. Losses come in steps of 2 after an opening drop of 4, which is unexplained and recorded rather than smoothed over. This weakens the frame-rate explanation from the previous iteration. At ~16.5 fps against a 30 Hz tick, game time runs at about 55 % of wall-clock, so route entries t = 90 and t = 120 land near 163 s and 218 s wall. The run reached 234 s wall, roughly 129 game-seconds, passing both, and no arrival occurred at either. "The runs were too short" no longer covers t = 90 and t = 120, though it still covers 170, 210 and 240 -- the run was cut at 240 s by the turn timeout rather than the planned 330 s, so t = 170 was never reached. It also sharpens the event-gated model into something testable. The squadron ended at 2, not 0, and no squadron has been eliminated in any run so far. If the trigger is a squadron being wiped out rather than merely damaged, every observation to date is explained: seven kills produced no arrival because they never finished anything off. Next is the cheapest decisive experiment yet available: run 60-90 s longer so that squadron reaches zero and watch for a 0 -> n in the following samples. The ~210 s title movie at boot remains the binding constraint, leaving about 350 s of observation per turn. |
||
|
|
97daa3e493 |
re: the mission clock is running — refute the stopped-clock explanation
The prime suspect from the previous iteration was that the phase scheduler
simply is not running, which would have made every arrival result meaningless.
It is refuted.
timer_probe.py takes three equally-spaced snapshots of the 32 MB game heap with
no pilot -- exactly the condition where nothing had been observed to change --
and keeps words whose two successive deltas are both positive and agree within
12 %, so linear rather than merely noisy. 286 words qualify, with a large
cluster advancing in lockstep at 16.5 per second.
That rate is not a coincidence: the existing performance notes put Canary
playback on this box at roughly 14-19 fps, and the dominant counter sits inside
that band, so these read as per-frame counters.
Which gives a mundane explanation for six arrival-free runs. If the scheduler is
frame-driven and the title targets 30 Hz, game time advances at about 55 % of
wall-clock here, so the 168, 190 and 240 second runs covered roughly 92, 105 and
132 seconds of game time. Route_S02 schedules phase-1 arrivals at 90, 120, 170,
210 and 240, so the longest run passed the first two and came nowhere near the
last three. No model has to be wrong for the observations to be empty.
Kept at 🟡, not promoted: two links are assumed rather than measured -- that
these counters are frame counters, and that the game's tick is 30 Hz. The values
also do not fit a naive frame count, since the cluster read 14193 about 255 s
after emulator start, which matches neither 16.5 nor 30 per second, so their
origin is genuinely unknown and no counter is claimed to be the mission clock.
Next is one long run, ~350 s of probe, watching for a 0 -> n transition near
t = 163 s and 218 s wall, where the t = 90 and t = 120 route entries land at 55 %
speed. If 350 s still yields nothing, the frame-rate explanation is itself
refuted and the event-gated model returns as the front-runner. The ~210 s title
movie at boot is the binding constraint on how much game time one turn can
observe.
|
||
|
|
a3463ef4d9 |
re: six runs with no arrival, and an accidental no-pilot control
wave6_probe refuses to interpret a run whose roster count is not the reproduced baseline of 116, per the discard rule. This run passed and its first sample is identical to the earlier link run: 116 records, 300 craft, 41 deployed, strength histogram 2x24, 4x1, 8x4, 18x12. The deployment is deterministic at mission start. The pilot failed to bind, which accidentally supplied the control condition the kill-versus-no-kill experiment needed. With nobody flying, not one craft was destroyed in four minutes -- exactly 300 across all 22 samples -- against 16-20 losses in each piloted run. So losses are attributable to the player being in the fight, and NPC crossfire destroys nothing by itself. That was an open question two iterations ago. Still no arrival. Zero 0 -> n transitions in either condition, across roughly fifteen minutes of cumulative Stage 02 flight and windows up to 240 s. The 75 records holding no craft at mission start still hold none at the end. Against a route table scheduling phase-1 arrivals at t = 90/120/170/210/240, that is now a strong negative rather than a null result. Three readings survive: the timetable's t is not seconds (at 30 Hz the whole phase-1 schedule finishes inside 8 s, before any first sample); arrivals are event-gated and no run supplied the trigger; or the mission is not advancing its phase clock at all. The third has never been checked and is the cheapest to eliminate, so it is now the prime suspect. Nothing in six runs has confirmed that mission time advances: the craft count freezes without a pilot, REMAINING OB has never read as a counter, and no clock has been located. Every "no arrival" observation is consistent with a scheduler that simply is not running, in which case the arrival results so far are measuring a stopped clock. |
||
|
|
86c918f399 |
re: the 42-vs-116 outlier does not reproduce; losses are observable
Two further runs sampled the counts from the instant flight is detected. Both give 116 roster records in the very first sample, flat thereafter, with 14 unit definitions throughout. That is not a load race: a race would climb toward 116 from below, and it never does. The leading explanation was arithmetic rather than behavioural -- census_probe counts raw aligned vtable hits while wave5_probe counted distinct primary_va values, which are different quantities if an offset can alias to a VA another offset also claims. Measuring both in the same run refutes it: 116 raw hits and 116 distinct VAs in every sample. So the outlier stands unexplained. Four runs now read 116/300, 116/300, 116/296 and one 42/170, with save drift, freezing, load race and VA aliasing each individually refuted. Recorded rather than dropped, because a one-in-four anomaly in the measurement apparatus is what later turns into a wrong conclusion. Practical rule adopted: discard a run that disagrees with 116 rather than interpreting it, and reproduce any finding in at least two runs. Useful new fact: craft declines steadily and monotonically in both runs, 300 to 288 and 296 to 280, roughly 16-20 losses over 168 s under the hunting pilot. Whether the player or NPC crossfire is responsible is not determined here, but destruction is visible in this signal, which is half of what the kill-versus- no-kill experiment needs. Still not observed in any run: an arrival. |
||
|
|
26efe58382 |
re: per-record strength measured, but the run does not reproduce
The link from the previous iteration gives a per-record live strength, and the measurement is internally sound: 11 records at 2, 2 at 8 and 11 at 12 sums to exactly the 170 craft counted, with 24 of 42 records deployed. It does not reproduce. The previous run saw 116 roster records and 300 craft with strengths 2/4/8/18; this one sees 42 and 170 with 2/8/12 -- same disc, same save slot, same launch script, same stage. Two explanations were checked and both fail. The save has not drifted: game01/savedata is unmodified since 2026-08-23 and only the profile .gpd files were written today. The guest was not frozen: the pilot's telemetry over the same 190 s shows speed varying across dozens of values and a live engagement with an e007 Turret at 259-680 m. So the discrepancy is real and unexplained, and until it is understood per-record counts cannot be used as a time series. Recorded as the blocker rather than worked around. The same within-run-only discipline already noted for the global craft count now applies one level down. Also recorded: zero arrivals and zero losses across 190 s. That is weak evidence against clock-driven arrivals at t = 90/120/170 seconds, since a 0 -> n transition should have appeared and none did. It is no test of the event-gated model at all, because nothing was killed -- fc=0, no record lost strength, and the player's hull never moved off 1500. The pilot closes to 259 m and misses, so the gap is accuracy rather than engagement. |
||
|
|
801dfe3e34 |
re: find the roster-to-craft link — a pointer at roster_base + 0x08
The previous iteration was right that neither structure points at the other's base, and wrong to stop there. Craft point eight bytes into a roster record. Resolving the 5101 "pointers into the roster range" by distance to the nearest roster base gives a distribution that is neither flat nor diffuse: +0x08 appears exactly 300 times, matching the 300 live craft, while every other delta appears at most twice and 4780 of 5104 land more than 0x4000 from any base. A count matching a count is the coincidence that has already misled this corpus three times, so the link was tested on something a coincidence cannot survive. Each side's unit type is resolved by a different chain -- the craft's from its definition pointer at +0x130, the record's from its +0x04 name chain -- and they must agree. They do, on all 300, with zero disagreements, and the fan-out histogram is [(1, 300)]: every craft has exactly one such pointer, none has zero or two. The fan-in is the useful half. Only 41 of the 116 roster records are referenced at all; the other 75 have no craft. Craft per referenced record is discrete: 24 records with 2, one with 4, four with 8, and twelve with 18 -- summing to exactly 300. The twelve holding 18 are all e007 Turret. This is the observable four iterations have been hunting. A member with zero craft is not deployed; a member's craft count is its live strength; so an arrival is craft appearing for a record that had none and a kill is that count falling. Both are attributable to a named squadron, because the record is a UnitGroup member and UnitGroup names the squadron that Route_S02.tbl ties to a phase and an arrival time. The chain from a memory observation to a static route entry is complete. Not claimed: that the 75 undeployed members are waiting on phases 2 and 3. That is a plausible reading, but the run only shows they have no craft. |
||
|
|
0feb624651 |
re: record the pointer-link refutation in the backlog index
The previous commit landed the doc and the probe but its BACKLOG edit asserted on stale text and did not apply, so the index still described the expansion rule as merely open rather than recording which candidate had just been eliminated. |
||
|
|
14650e360b |
re: refute a direct pointer link between roster records and live craft
Fourth failed attempt to explain how 116 roster members become ~300 live craft, recorded so it is not retried. Count (116), the member field n (387) and formation slots (630) were rejected earlier; this rejects a head pointer. link_probe.py scans the first 0x400 bytes of every object for an address of the other kind. Roster record to craft base: 0 of 116. Craft to roster base: 0 of 300. Both directions empty. The scan also measured the address spaces, which is the useful part. The two structures live in different regions about 19 MB apart -- roster records at 0xbc372c00..0xbc9bc720, craft at 0xbdb2fd80..0xbdcd1d80 -- and the 14 unit definitions match Stage 02's 14 distinct unit types exactly. The craft count is not fixed: three runs at comparable mission times gave 296, 298 and 300, so the population must be compared within a run and never across runs. One thread is left open but explicitly not counted as evidence. Craft objects hold 5101 words pointing somewhere into the roster VA range, which sounds like a lot until you notice that range spans 6.5 MB and holds many allocations besides the 116 records. The test that would settle it is the distance from each such pointer to the nearest roster-record base: a spike at a single delta means a link at base+X, a flat distribution kills the thread. Not run yet. The association may not be a pointer at all -- an index, a hash, or a third object such as a squadron instance would all look like this. Since an arrival is already known to be a state change rather than an allocation, finding that mediating structure matters more than finding a flag: it is what would let an observed change be attributed to a named squadron and hence to a route. |
||
|
|
b9cb0135e3 |
re: sites are entities 1:1 — withdraw "n is craft-per-member"
The suspected confound turned out not to exist. Gaps between consecutive same-unit definition-pointer sites are all >= 0x1000, with 274 of them exactly 0x1000, so entities are page-spaced and there are no near-adjacent pairs to merge. Clustering at any threshold below 0x1000 gives ratio 1.00 for every unit type, and hull is plausible on 298 of 298 clustered bases at delta 0x130. The player shows two objects because there are two, not because one holds two pointers. That removes the excuse the previous iteration had used to keep the reading alive, and the reading does not survive: sum(n) fits the turret row well (216 against 214, with kills already recorded), but DeltaSaber_T, Player and Acropolis all come out at exactly twice their sum(n). An undershoot can be blamed on phases 2-3 not having started; an overshoot cannot. n goes back to ❔ and the previous 🟡 is withdrawn. All the turret row establishes is that a roster member expands into many craft, not that n is the factor. Formation slot count was tested as the alternative and rejected outright: 630 turret slots against 214 live. Side result worth keeping: a FormationSet record's FrameCount is its slot count, and the name suffix usually agrees -- Turret07_30 -> 30, ArrowHead03_64 -> 64, 4_Bird -> 4 -- with one exception, AttackerS03_12 having 14 slots, so the suffix is a label and not a guarantee. Also recorded: the 298 live entities are not the 116 roster records. Both structures exist at once, and the rule mapping one onto the other is the real open question. Probe caveat noted in the doc: entities2.moving() found no movers this run, so the delta spectrum was empty and the clustering threshold was a fallback rather than a measurement. It does not change the conclusion, since every gap exceeds any threshold below 0x1000. |
||
|
|
9beb60bc1a |
re: motion-independent liveness probe; n looks like craft-per-member
entities2.moving() finds entities by displacement between two samples, so anything stationary is invisible -- the entire explanation for the +/-10 swing that made the previous run's count useless. liveness_probe.py enumerates by definition pointer over the entity heap instead, moving or not, and reads hull as f32 at position+0x154. The series is monotone rather than oscillating: 298 -> 280 over 164 s, with the decline matching the 18 disappearance events exactly. The hunting pilot does kill: one hull crossing caught directly, an e010_ADAN_Attacker_S at t=57 s. The previous run's worry that SYLPH_HUNT shoots but never destroys anything is settled. Recorded as a non-result so the next run does not misread it: zero births in 164 s does NOT favour either wave model. The roster finding already established that every participant is allocated at mission load, so neither a clock nor an event model would produce an allocation. An arrival must be a state change on an existing entity. The mystery member field n now has a candidate meaning: the number of craft a roster member spawns. Static sum(n) for turrets is 216 against 214 sites found, with the count already falling before the first sample, where Count alone predicts 21 -- off by an order of magnitude. Not promoted, and the reason is a confound in my own measurement rather than the data: the probe counts definition-pointer SITES, not entities. The player is one member and yields two sites, and DeltaSaber_T yields exactly double its sum(n), so some entity types hold several pointers to their definition. Until sites are collapsed into distinct entities the turret match could be a coincidence between a x1 multiplicity and a x1 ratio. The capital-ship rows undershoot for a separate and expected reason: phases 2 and 3 have not started. |
||
|
|
f28207851b |
re: add a hunting pilot; withdraw the "10 of 116 records are dynamic" result
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. |
||
|
|
1a7f8f190c |
re: fix record labelling and measure the stride; confirm 116 records == roster
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. |
||
|
|
1d3ebdaa05 |
re: record the event-gated wave model as a competing hypothesis
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.
|
||
|
|
ca0e88eb58 |
re: withdraw the "turrets don't move" hedge — Turret is a craft, not a ship mount
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. |
||
|
|
ff921c097d |
re: diff inside the 116 entity records — no arrival flag found, two probe defects
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.
|
||
|
|
c193b44133 |
re: find the arrival timetable; refute the entity-count proxy for waves
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.
|
||
|
|
a0a0214b24 |
re: probe the runtime phase state — tables are resident, phase counter is not there
Static reading had gone as far as it could: the stage record splits a mission
into Phase_1..3 and every arrival route is phase-tagged, but nothing in the data
says what ends a phase. So this took it to the oracle -- one Stage 02 flight,
160 s under the survival pilot.
Confirmed, and this is the useful half: every string the static decode predicts
is present in live guest memory -- Phase_1, Phase_2, Route_ADN101_p1F,
SUBOBJ_010, AI_ADAN_CraftSquadron_Veteran, UnitGroup_S02.tbl. The game loads
exactly the tables the stage record names, under exactly the names we resolved,
and they can be located in RAM by content. That is the first dynamic
confirmation of the whole static table layer.
Refuted: the phase state is not adjacent to those strings. The probe reported
862 changed words around the anchors, which looks like a signal until you read
the values -- each word takes its predecessor's previous value and every value
points into the same region. It is one block shifted down four bytes, a single
memmove in a pointer list, occurring once between t=66s and t=89s. Diffing
around a string anchor was the cheap thing to try and it did not work.
Also recorded: a defect in my own probe. It scraped hit addresses with
0x([0-9a-f]{8}), but gmem.py find prints both the backing-file offset and the
guest VA, so half the anchors were file offsets read as addresses. Fixed to
match the va column only. It did not change the conclusion -- the anchor that
produced the shift was a real VA -- but a negative result from one of those
junk anchors would have been worthless.
Not settled: what advances a phase. Next handles are watching Route_ADN101_p1F
fire against entity positions, or working back from the SUBOBJ_*_Mes_L1 HUD
strings; the phase state is more likely near the known mutable REMAINING OB
counter than near the tables.
|
||
|
|
4441ca79ec |
re: decode the stage table set — phases, routes, sub-objectives, AI parameters
Following the real stage record (not the _Test template dumped earlier) reaches
the whole mission-parameter layer, all of it in the same self-describing IDXD
container as the squadron roster.
The big one: a stage is divided into Phase_N blocks -- three for Stage 02, each
with its own map path, map mesh, asteroid definition and background -- and
Route_S<NN>.tbl holds the arrival paths, with records named
Route_<squadron>_p<phase><kind>
tying a UnitGroup squadron id to a phase and to a time-stamped keyframed path of
(time, quat x4, pos x3). Route_ADN101_p1F is 3 frames at t = 0, 20, 30. The
identity len(fields) == FrameCount * 8 + 1 holds for 1449 of 1449 route records
across the 28 stages that have one, and 16/16 for FormationSet_S02.
Also decoded: SUBObjectiveSettings (per-objective bonus points by difficulty,
unlock item id, HUD strings) and AIParams (34 profiles, firing/guard/muster/
counter ranges plus 14 manoeuvre weights for Squad-type AI). The AIParams
numbers are exact original values from static RE and are portable as they are.
Adds tools/re-capture/stagetbl.py, which resolves a stage record by content and
can --follow every table it names, and commits two dumps as evidence.
Refuted and kept: the eight-value keyframe is the common case, not universal.
Formation_Fleet_01 has FrameCount=1 with 136 positional fields and
Formation_Fleet_02 has FrameCount=8 with 32, so a parser must not assume the
stride.
Corrects stage-definition-table.md, which was written from the _Test template
and is missing EnumerateSubobjective, EnumerateAIParams, BackGroundID and the
WingmanIconID fields the real record carries.
Not settled: what advances a phase -- the stage declares Phase_1..3 and routes
are phase-tagged, but nothing static says what ends one. That is a question for
the oracle, not for more static reading. Also open: the route-name kind letters
F/S/A/M/B, what activates a sub-objective, and StageMessageSet_S<NN>.tbl, which
does not resolve in GP_MAIN_GAME_E.pak.
|
||
|
|
ecf6887b76 |
re: decode the UnitGroup squadron roster, and correct yesterday's container reading
stage\UnitGroup_S<NN>.tbl is now fully readable. A squadron record is Count
member tuples -- (unit model, message set, n, identity/nameplate) -- followed by
five named fields: Count, SideID, AIID, FormationID, DisableInterval. The
property entries carry their own field names inline, so the tag hash never has
to be inverted.
Two independent self-checks validate it corpus-wide, 1160/1160 each across all
28 stage tables on the disc:
- the length identity len(fields) == Count * 4 + 5, which is what pins the
member-tuple width at 4 and the named-field count at 5;
- agreement with the file's own Enumerate_Squadrons roster, which maps record
key to squadron id independently of the per-record string offset.
Adds tools/re-capture/unitgroup.py (pure static, runs no emulator) with a
--all --check self-check mode, and commits the Stage 02 dump as evidence.
Corrections to the container layout written yesterday, all three wrong:
- the 20-byte "(tag, 0, 0, count, size) section header" does not exist. It
was the file's last 16-byte record followed by a plain npool word. The
corrected layout is uniform across all 28 files; the old one failed on 9.
- squadron ids do not use a separate string base. Every offset in the file is
relative to the one string pool. The earlier "109 of 111" score was an
artefact of the uniform 7-byte id stride and had silently shifted every
name by three entries, which is why 17 TC*-named squadrons came out as
SideID=ADAN. The roster record refuted it outright.
- the roster is not always the last record; 9 stages put it elsewhere, so it
is found by its missing Count.
Refuted and kept: the 4-byte record key is not the squadron id's name hash
(0 of 112).
Not settled: what the key encodes, the member tuple's third field n, and where
the arrival interval values live. DisableInterval is only a per-squadron flag
(Yes for 31 of 1160); the durations, triggers and arrival positions are not in
this file. Formation_*.tbl and EnumSquadron_Test.tbl are next.
|
||
|
|
4dcb2f2719 |
re: find the mission wave data — stage records and the UnitGroup squadron roster
Sweeping the 811 unnamed IDXD objects in GP_MAIN_GAME_E.pak by schema turned up
schema 3c9ae32e: the per-stage definition record. 23 of them, one per stage,
each naming its background, resource package, collision set, message set,
nameplates, MapMesh/MapPath and EnumerateSquadron = UnitGroup_S<NN>.tbl.
That resolves two open threads at once:
- MapPath = test.rgn hashes to 0x3506e972, a REGN object in MiscBin.pak, and
MapMesh = test.col to 0x2cf7eb47, an MCOL object. REGN is a stage's map
path data; MCOL is its collision mesh.
- stage\UnitGroup_S02.tbl (0x019fd129, in all six language paks) is the
Stage 02 squadron roster: 112 records, 112 squadron IDs, and a field
vocabulary of FormationID / AIID / SideID / Count / DisableInterval, plus
the unit model (UN_e010_ADAN_Attacker_S and friends, which match the XBG7
mesh names we already decode), the MessageSet and the pilot character.
DisableInterval is the first direct evidence of the arrival-timing knob, which
is what the user's reframing predicted: the mission has a schedule with
parameters, not a fixed roster.
Container layout is only partly read. The 112x16 entry array was confirmed by
its boundary — keys increase for exactly 112 entries and break at 0x708, where
the next section header sits — not assumed. pak dump mislabels this file's
first key as its schema.
Refuted and recorded: the 16-byte record key is not the squadron ID's name
hash. name_hash("TCN001") = 0xd639f1a4 but the keys start 0x659aff47; 0 of 112
match.
Still open: the per-record payload fields, the meaning of the key, where the
interval values actually live, and the missing S17-S23 stage records.
|
||
|
|
6d9827b0c8 |
docs: update the REGN line - POF0 fixups in, two payload guesses out
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE |
||
|
|
9f76ffa848 |
formats: REGN carries a POF0 fixup table, and two payload readings are refuted
Every one of the 11 objects contains POF0 near the tail, at exactly header[0x04] + 16 - an 11 of 11 relation. POF0 is a pointer-offset fixup table, so the file is a serialised C++ object graph the loader patches on load, which also explains why the offsets inside the cell index are absolute FILE offsets. header[0x04] is therefore the size of the data area. Two readings of the cell payload are recorded as refuted rather than dropped, because both were tempting and both came from the smallest object alone: the f32 at record +0x1c is NOT a bounding-sphere radius (ratio to sqrt(3)*half-extent is 1.001 on that one object and 0.13-0.27 on the other ten), and a record's (count, offset) pairs do NOT point at leaf arrays of count*4 bytes (0 of 11 objects clean). What survives is descriptive only: the payload is dominated by float data, and the printable runs a string scan finds are float high-bytes rather than text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE |
||
|
|
ef61821d73 |
docs: rule DefTables and the XPR2 manifests out of the wave-scheduler search
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE |
||
|
|
dafd8afbf1 |
formats: REGN section 3 is the cell index, self-checked on all 11 objects
The fourth section is one 8-byte (count, offset) record per grid cell, followed by the 32-byte records it points at. The check: the lowest offset any cell refers to equals align16(offsets[3] + cells*8) on 11 of 11 objects - and the alignment term is visible rather than assumed because the three 5x5x5 maps have 125*8 = 1000 bytes of index, which is not 16-aligned, so their payload starts 8 bytes later than the six 10x10x10 maps'. Two further invariants from the same sweep: every occupied cell has count exactly 1 (total items == occupied cells on all 11, so it is one record per cell rather than a bucket list), and counts[4] equals occupied cells + 2 exactly on all 11 - the +2 unexplained and recorded as such. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE |