This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/remaining-ob-hunt.md
Sylpheed RE agent 738df50803 re: widen the OB scan to seven encodings; run inconclusive, probe lacked a witness
ob_by_hud.py now scans seven readings of the same bytes and keeps a separate
candidate set for each, as byte offsets: u32 big and little endian, u16 both
endiannesses at both alignments, and u8. The big-endian u32 reading had been
refuted, so widening rather than assuming is the point.

u32le is much the tightest at 154 candidates against u32be's 4452. That is a
hint about the encoding rather than a result, since a rarer bit pattern narrows
faster regardless of meaning.

The run is inconclusive. The HUD read 4 at every sample, so there was no second
value to collapse the sets against, and from t=136 the candidate counts are
byte-identical across five samples in all seven encodings, which is what a
frozen guest looks like -- nothing in 32 MB changed at all. The probe had no
stall witness, so the run cannot prove it either way. One is added now.

Worth stating plainly: this is the fourth probe written without a witness and
the third whose flat output could not be distinguished from a freeze. Each time
the fix gets applied to that one script. The durable fix is the shared probe
harness already noted in this file, and the lesson recurring four times is
itself the argument for building it.

What the hunt needs is unchanged: two HUD readings at different values in
non-stalled samples. The counter moves on kills, which lands back on the combat
limit, though the earlier 4 to 11 observation shows it does move.
2026-08-24 23:14:59 +00:00

261 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hunting REMAINING OB by correlation — method works, run did not finish
Status: ✅ the correlation method is sound and demonstrated; 🔴 the hunt is
**unfinished**; 🔴 two self-inflicted defects, one a repeat.
## The method
[mission-phase-objectives.md](mission-phase-objectives.md) settles what the
counter is: Stage 02 phase 1 asks to *"shoot down all invading enemy fighters"*
and the hints say red **[OB]** markers indicate the targets. So `REMAINING OB`
must fall when a marked fighter dies — and the per-record craft strength already
says exactly when that happens, for a *named* unit.
So instead of scanning for a value, intersect: keep every word in the 32 MB heap
that fell by the same amount, in the same interval, as an `e010` loss. Each kill
event should cut the survivor set hard.
`tools/re-capture/ob_probe2.py`.
## ✅ It works — one event cut 8 million words to 1056
```
t= 219s e010 losses=2 (all=4) words falling by 2: 1056 -> candidates 1056
```
From ~8 M candidate words to **1056** on a single event. Two or three more should
leave a handful.
## 🔴 The run did not finish, and the candidates were lost
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. The follow-up attach then
started from nothing.
**This is the same mistake already recorded in
[guest-stalls.md](guest-stalls.md)**: "the first attempt deferred all analysis to
the end of the run, and the turn's timeout killed it with 240 s of collected data
still in memory and nothing written." I wrote that lesson down and then repeated
it in a new script four iterations later.
Fixed: candidates are now written to `/tmp/ob_candidates.json` **after every
event**, and `SYLPH_OB_RESUME=1` loads them so a chained attach keeps
intersecting on the same mission.
## 🔴 The attach could not tell a quiet mission from a frozen one
The follow-up attach logged **535 s with zero losses of any kind**. That is
exactly what a freeze looks like, and `ob_probe2` had no stall witness, so the
run cannot say which it was. Also fixed — the witness from `wave7_probe` is now
carried here.
Two defects in one iteration, both of them things this corpus had already
learned. The pattern is that each new probe starts from scratch and re-earns the
same lessons; the fix that would actually stick is a shared probe harness rather
than a family of one-off scripts.
## What is still open
The hunt itself. The method is demonstrated but no address is identified. What it
needs is a run that catches **two or three** `e010` kill events, which is the same
combat-effectiveness limit recorded in
[mission-objectives-text.md](mission-objectives-text.md) — the preference knob
gets about two marked-fighter kills per five minutes against a dozen turrets.
---
# Second attempt: saving works, the filter was wrong (2026-08-24)
## ✅ Incremental saving works
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 it. The fix from the previous iteration is verified.
The session also now clears `/tmp/ob_candidates.json` at launch: candidate
*offsets* are only meaningful within one emulator instance, so resuming across
launches would intersect unrelated addresses.
## 🔴 The correlation had no value filter, and the survivors were floats
The 1187 survivors are not counters:
```
va 0xbd0a42b8 value 1044450858 (~0.1f)
va 0xbd140bc0 value 3200164558 (a negative float)
va 0xbd14a120 value 3212461993
```
The filter matched on the **delta** alone, so any two float bit patterns whose
integer representations differ by exactly `lost` qualified. In a 32 MB heap full
of positions and velocities that is thousands of words.
Fixed: candidates must also *look like a counter* — a small non-negative integer
(`0 ≤ v < 1000`) in both samples. That removes float noise by construction rather
than hoping the intersection washes it out.
## 🔴 The attach was frozen, and the witness said so
The follow-up attach logged **0 events across 520 s**, which reads like the
combat-effectiveness limit again. It was not: **25 of its 26 samples were flagged
`GUEST STALLED`**. The guest was frozen for essentially the whole window.
The witness added last iteration did its job. The lesson is about reading it —
the run summary quoted "0 events" first and the stall count only turned up 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
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 the pilot managing roughly two marked-fighter kills per five
minutes.
---
# ✅ The method narrows hard — and refutes turret tracking (2026-08-24)
With the value filter in place, an attach watching **turret** losses (which are
frequent, unlike marked-fighter kills):
```
t= 45s e007 losses=2 words falling by 2: 374 -> candidates 374
t=134s e007 losses=4 words falling by 4: 1001 -> candidates 2
t=156s e007 losses=2 words falling by 2: 526 -> candidates 0
```
**374 → 2 → 0.** That is exactly the behaviour a correlation search should show,
and it ends in a refutation rather than fizzling out:
> **No plain `u32` in `0xBD000000–0xBE000000` decrements consistently with turret
> kills.**
Witness first, as the rule now says: 12 of 25 samples stalled, but **all four kill
events fall in the early non-stalled stretch** (t = 45–177 s), so the events
themselves are sound.
## 🟡 The negative fits the objective text
This is what the objective text predicts. 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. The refutation is
evidence *for* the reading in
[mission-phase-objectives.md](mission-phase-objectives.md), not against it.
It also rules out the cheaper alternative reading, that `REMAINING OB` is a
general kill or enemy-remaining tally. It is not: it ignored ten turret deaths.
## What this leaves
The method is proven and the search region is right — `0xbdb59668`, where the
counter was originally seen, is inside the scanned range. What is missing is
still **two or three `e010` kill events in non-stalled samples**, and the same
two obstacles: the freeze rate, and the pilot's ~2 marked-fighter kills per five
minutes.
One clean 220 s run this iteration produced **zero** `e010` kills, which is the
limit stated plainly.
---
# ✅ A kill-free route via the HUD — and it refutes the u32 assumption (2026-08-24)
The correlation route is gated on marked-fighter kills, which the pilot gets at
about two per five minutes. But `ob_read.py` already reads the counter off the
screen, so the value can be matched against memory directly — no kills needed.
`tools/re-capture/ob_by_hud.py`: screenshot → read the digits → keep heap words
equal to that value → intersect across readings.
```
t= 7s HUD=4 words==4: 6156 -> candidates 6156
t= 42s HUD=4 words==4: 6256 -> candidates 5153
t= 75s HUD=4 words==4: 6327 -> candidates 4620
t=108s HUD=4 words==4: 6451 -> candidates 4312
t=142s .. t=312s HUD unreadable ('00?', '??1', '???')
t=347s HUD=11 words==11: 1052 -> candidates 0
```
## 🔴 Refuted: the counter is not a plain big-endian u32 in the entity heap
Four readings at value 4 narrowed 6156 → 4312 — the expected slow drift. Then the
HUD read **11**, and the intersection collapsed to **zero**.
A word that genuinely holds this counter must equal 4 at the first four samples
*and* 11 at the last. None does. So within `0xBD000000–0xBE000000`, read as
big-endian `u32`, **the counter does not exist**. It may be `u16`, `u8`,
little-endian, or simply outside that region.
That is worth having: both hunts so far assumed BE-`u32` in the entity heap, and
that assumption is now eliminated rather than merely unproductive.
## 🟡 The displayed value went UP, 4 → 11
Over ~340 s the counter *increased*. A pure countdown of remaining marked targets
should not do that — unless targets were added, which the deployment work says
does not happen for phase 1
([mission-phase-deployment.md](mission-phase-deployment.md)).
Possible readings, none tested: the cell being read is not `REMAINING OB`; the
digits are misread (the template strip only covers **0 1 2 4 8**, so 3/5/6/7/9
come back as `?` — the many unreadable samples above); or the counter genuinely
counts something that can rise.
The two clean readings scored 0.95–0.98 against their templates, so a misread of
those specific frames is unlikely — but "4" and "11" use only digits that *are*
in the strip, which is exactly the selection effect that would hide a wrong
reading.
## Next
Widen the encoding: search `u16` and `u8`, little-endian as well as big, and
beyond the entity heap. That is a change to one scan function, and unlike the
kill-driven route it costs no combat.
Also worth extending `ob_digits.png` with the missing digits — most samples were
unreadable, which is why only two data points survived a 480 s run.
---
# Widened to every plausible encoding (2026-08-24)
`ob_by_hud.py` now scans seven readings of the same bytes and keeps a separate
candidate set for each, expressed as byte offsets:
`u32be`, `u32le`, `u16be`, `u16be@1`, `u16le`, `u16le@1`, `u8`.
First run, HUD showing **4** throughout:
```
t= 7s u32be:6285 u32le:414 u16be:10668 u16be@1:2718 u16le:656 u16le@1:9012 u8:25559
t=110s u32be:4472 u32le:156 u16be: 8794 u16be@1:1386 u16le:286 u16le@1:8124 u8:20381
t=136s u32be:4452 u32le:154 u16be: 8773 u16be@1:1380 u16le:284 u16le@1:8119 u8:20346
t=162s .. t=267s identical to t=136 in every encoding
```
**`u32le` is much the tightest at 154**, an order of magnitude below `u32be`.
That is a hint about the encoding, not a result — a rarer bit pattern narrows
faster regardless of meaning.
## 🔴 Inconclusive, and probably a frozen guest
The HUD read **4** at every sample, so there was no second value to collapse the
sets against. Worse, from t = 136 s the candidate counts are **byte-identical
across five samples in all seven encodings**, which is what a frozen guest looks
like — nothing in 32 MB changed at all.
`ob_by_hud.py` had **no stall witness**, so the run cannot prove it. Added now.
**This is the fourth probe written without a witness and the third whose flat
output could not be told from a freeze.** Each time the fix is applied to that
one script. The durable fix is the shared harness already noted in this file, and
the fact that the lesson keeps recurring is itself the argument for building it.
## What is needed
Two HUD readings at **different** values, in non-stalled samples. The counter
changes on kills, so this lands back on the combat limit — unless a phase change
or another event moves it. The earlier 4 → 11 observation shows it does move,
which is what makes the approach worth continuing.