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/mission-freeze-and-ob-flag.md
Sylpheed RE agent 26421d5266 docs+tools: the per-entity OB flag is refuted, and the counter decrements when the player kills
With the pilot finally shooting, the experiment completed both halves. Sample A
at counter 12 over 120 entities gave 2 offsets where exactly 12 entities share a
value; the counter then went 12 -> 11 and NEITHER survived. So within +-0x400 of
an entity's position triple there is no 4-byte word whose shared-value population
tracks REMAINING OB.

The limits are recorded as part of the result, because they bound it: the test
asks which entities share an EXACT 32-bit value, so a single bit ORed into a word
that also carries health or a timer would never show up - a bit-level version of
the same differential is the follow-on. Anything outside the window, or on
entities that entities2 cannot see (it types by position CHANGING, so stationary
objectives are invisible), is untested too, and the populations differed a lot
between samples - 120 against 194.

Separately: REMAINING OB went 12 -> 11, the first decrement of this whole
investigation, while pilot.py logged 411 fire=1 samples and the HUD reached YOU
KILLED WARPLANES 0003. Stated carefully - it does NOT show the counter counts
kills, since an earlier run had the hostile population fall by a third with no
movement; it shows some kills close something the counter tracks.

Two robustness fixes: ob_flag retries an empty entity sample (one void run was
caused by exactly that), and Pad releases everything on interpreter exit - a
file-backed pad PERSISTS after its writer dies, so a tool killed mid-press would
leave a button held and the game would walk through menus on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-23 23:21:08 +00:00

10 KiB
Raw Blame History

An in-mission freeze, and the first cut at what REMAINING OB counts

2026-08-23. One Stage 02 run, aimed at the that structures/mission-objective-counter.md leaves open — what the counter counts. It produced one clean refutation, one new confirmation, and one experiment that proves nothing, for a reason worth having a tool for.

The address recurs a third time

HUD=4 RAM=4 at 0xbdb59668, read before anything else was attempted. That is the third run putting the counter at that exact address (runs 1, 4, 5 of the five measured), against runs 2 and 3 where it read a hard 0. The rule in the structure file — try it, check it against the HUD, re-scan when it reads 0 — survives another test.

🔴 The counter is not a live class head-count

With the counter at 4, the typed live entity list was:

n class
8 UN_e010_ADAN_Attacker_S
7 UN_f001_TCAF_DeltaSaber_T
7 UN_e007_ADAN_Turret
1 UN_f001_TCAF_DeltaSaber_T_Player

No class has 4 members, and no sum of two of them does either. This is a sharper version of the corpus's existing note (012 on the HUD against 118 live ADAN): it is not merely "not the hostile count", it is not the count of any class the enumeration can see. So the counter is either a subset marked some other way — the OB badge — or a number the mission script keeps of its own.

🔴 The flag experiment proves nothing — the guest had frozen

The plan was the same shape that settled the address: find every 4-byte offset around an entity where exactly N entities agree, then require the survivors to agree exactly N times after the counter moves. The first half ran — 20 candidate offsets where exactly 4 of the 23 entities share a value — and then the counter sat at 4 for the whole 600 s window and the run reported "counter never moved".

That reading was wrong, and the correction is the useful part. The guest had stopped advancing about ten seconds into flight:

  • pilot.py logged 5 910 samples over 734 s and every one after t=10.6 s carried the identical speed, yaw and pitch — 724 s of frozen state.
  • Two screenshots six seconds apart were byte-identical: max difference 0 over 863 325 pixels (captures/stage02-inmission-freeze.png).
  • Everything that is supposed to notice, did not: screen_id.py still said flight, the emulator was alive at 212 % CPU, and the process liveness check every script uses was satisfied.

So "the counter never moved" was a fact about a dead world, not about the game. 🔴 Withdrawn with it: the claim written into ob_session.sh that the counter "climbs on its own as waves spawn in the first minutes" — in a run that is advancing it did (004 → 008 → 012 twice), but this run cannot support the general statement and the two facts had been about to be conflated.

The freeze is intermittent, not universal: the run an hour earlier stepped the counter twice and had pilot.py in DEFEND at t=629 s with changing state.

frozen.py

Two screenshots, a gap, exact comparison — no tolerance, because a stopped simulation is identical, not merely similar. Checked in both directions: 0 on the frozen pair, max_pixel_delta=254 on two frames of a live one. ob_hunt.py and ob_flag.py now consult it after a minute of no movement and say GUEST FROZEN instead of quietly waiting out their timeout.

What is still open

  • The flag itself. 20 candidate offsets exist but have never been put to the second test. Repeat ob_flag.py on a run that is actually advancing; it now aborts early instead of wasting the window if it is not.
  • Whether the enumeration can even see the objectives. entities2.typed finds entities by their position changing, so a stationary objective is invisible to it. Stage 02's objective is "shoot down all invading enemy fighters", which move — but a null result would not rule out a flag on objects this method never enumerates, and that limit is now written into the tool.
  • What causes the freeze. Not investigated here at all; this run only establishes that it happens, that it can happen within seconds of the HUD appearing, and how to detect it in one call.

2026-08-23 (later) — the flag experiment finally ran, and still cannot verify

A run that stayed animating for its whole 25 minutes (checked repeatedly with frozen.py) got the first real sample:

HUD=12 RAM=12                       (0xbdb59668 again — 5 of 7 runs now)
[A] counter=12 entities=161
      114  UN_e007_ADAN_Turret
       32  UN_e010_ADAN_Attacker_S
       14  UN_f001_TCAF_DeltaSaber_T
        1  UN_f001_TCAF_DeltaSaber_T_Player
[A] classes whose head-count equals the counter: NONE
[A] offsets where exactly 12 entities agree: 11

with a much bigger entity list than the frozen run's 23, so the earlier "🔴 not a class head-count" result is now confirmed on a 161-entity sample too.

The 11 candidates, with who they group — the part worth looking at:

offset value members
+0x00d0 3f57f496 12 of the 14 friendly Delta Sabers
+0x017c+0x0198 (7 words) floats 12 of the 32 e010 attackers
+0x00b0, +0x015c 43f9ffff 10 turrets + 2 attackers
+0x0238 00000004 12 turrets

The seven consecutive floats at +0x017c…+0x0198 shared by exactly twelve attackers look far more like one shared parameter block — a squad flying the same AI or weapon configuration — than like a per-entity objective flag, and +0x0238 = 4 on twelve turrets is hard to reconcile with an objective that reads "shoot down all invading enemy fighters". None of this is verified, and the reason is the next paragraph.

🔴 REMAINING OB held at 012 for fifteen minutes of live flight

Not a frozen world this time — the game was animating throughout, and the run ended only because the experiment's window did. So the counter genuinely did not move, and there was no second sample to test the candidates against.

And it held while the hostile population fell by a third. pilot.py's own rescans went 132 ADAN93 ADAN over the window: roughly 39 hostiles died (to wingmen and to the ACROPOLIS's guns) and REMAINING OB did not change by one. That is a real negative about what the counter counts — it is not "hostiles left", on a much stronger sample than the earlier head-count argument.

🔴 A tool defect that bounds every kill-based experiment

pilot.py never fires: fire=1 appears in 0 of its 13 521 logged samples in this run. It flies, chases and evades, but the trigger is never pulled. Every "kill something and watch X" experiment in this corpus is therefore relying on the wingmen, and the pilot's own contribution to a kill count is zero. Not fixed here — recorded because it silently weakens conclusions drawn from runs that assumed the pilot was shooting.

Freeze frequency, updated

This run did not freeze in ~25 minutes. Of the seven Stage 02 runs that have reached flight, three froze — so it is common but not the majority, and a run that survives the first minute or two seems to keep going.


🔴 2026-08-23 (final) — the per-entity flag hypothesis is REFUTED, on a transition it was not selected by

With the pilot actually shooting (pilot-never-fires.md), the experiment this file was written for finally completed both halves:

HUD=12 RAM=12
[A] counter=12 entities=120        96 turrets, 16 attackers, 7 wingmen, 1 player
[A] classes whose head-count equals the counter: NONE
[A] offsets where exactly 12 entities agree: 2
      pos+0x0238 = 00000006  {12 × e007_ADAN_Turret}
      pos+0x0250 = 239d6732  {12 × e010_ADAN_Attacker_S}
counter 12 -> 11
[B] counter=11 entities=194
[B] of 2 candidates, 0 still hold exactly 11

(captures/ob-flag-refuted-stage02.json)

Both candidates die on the verification. So: within ±0x400 of an entity's position triple there is no 4-byte word whose shared-value population tracks REMAINING OB. The simplest reading of "objectives are marked in the entity object" is dead.

What that does and does not rule out — the limits are the result too

  • 🔴 Ruled out: a per-entity word, in that window, holding a common value on exactly the objective entities.
  • Not ruled out — a flag packed into a word that also varies. The test asks which entities share an exact 32-bit value. A single bit ORed into a word that also carries health, a timer or a state machine would never produce a shared value, and this method cannot see it. A bit-level version of the same differential is the obvious follow-on.
  • Not ruled out — anything outside the window, or on entities the enumeration cannot see: entities2.typed types entities by their position changing, so a stationary objective is invisible to it.
  • ⚠️ The populations differ a lot between samples — 120 entities at A, 194 at B — so a flag on entities that spawned between the two would not have been tested either.

And the counter DECREMENTS when the player kills

REMAINING OB went 12 → 11 during this run — the first decrement seen in this whole investigation — while pilot.py logged 411 fire=1 samples and the HUD's own tally reached YOU KILLED WARPLANES 0003 (captures/stage02-you-killed-warplanes-0003.png).

That is worth stating carefully. It does not show that the counter counts kills — the earlier fifteen-minute run had the hostile population fall by a third with no movement at all. What it shows is that some kills close something the counter tracks, which is exactly the behaviour an objective counter should have, and it is the first time the player's own guns have been part of the experiment rather than a bystander.

One more robustness fix, from a void run

ob_flag.py now retries an empty sample. entities2.moving() types entities by position changing, so a sample that lands on a load or loses the race with another scanner returns nothing — and a zero-entity sample silently yields zero candidates and a run that proves nothing. Seen exactly that way once, with the following sample finding 81 entities moments later.