Files
Sylpheed/tools/re-capture/frozen.py
Sylpheed RE agent ca133c0220 tools+docs: the bit-level OB test, and a liveness hole - GAME OVER animates
ob_bitflag.py is the follow-on the word-level refutation named: for every 4-byte
offset in the window and every one of its 32 bits, count how many entities have
it set, keep the pairs whose count is exactly the counter, and require them to
match again after a transition. Both polarities, since an objective could be
marked by a bit that is CLEAR on it.

Three runs, no verification, and the reasons are recorded: run 1 gave 187 + 33
candidates at counter 4 and then reported "the counter never moved" for 700 s -
about a mission that had ENDED in GAME OVER partway through; run 2 hit the same
dead mission; run 3 had the counter at a different address (the guard refused,
correctly) and then froze after one filter.

The hole is closed. frozen() asks whether the guest is ANIMATING, and the GAME
OVER screen animates happily - mean colour (114,22,63) - so every liveness check
passed while the mission was over. frozen.in_flight() classifies the screen with
screen_id, and ob_hunt/ob_flag/ob_bitflag now abort with NO LONGER IN FLIGHT.
That is the second confident negative in this investigation that was really about
a dead world, so the rule is written down: before believing "X never happened",
show that the thing that would produce X was still running.

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

93 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Is the guest still ANIMATING, or has the mission frozen?
Measured 2026-08-23: a Stage 02 run entered flight, drew a correct HUD, and then
stopped advancing about ten seconds later. `screen_id.py` still said `flight`,
the emulator still burned 212 % CPU, `pilot.py` still logged 5 900 samples, and
every one of them carried the same speed, yaw and pitch — 724 s of identical
state. Two screenshots six seconds apart were **byte-identical**, max difference
0 over 863 325 pixels.
That is worth its own test because of what it costs when missed: an experiment
that waits for `REMAINING OB` to change will wait out its whole timeout and then
report "the counter never moved", which reads as a fact about the game and is
really a fact about a dead world. Nothing else in the toolkit notices — the
screen classifier, the liveness check and the CPU are all happy.
A frozen frame is EXACTLY identical, not merely similar: this is a stopped
simulation, not a still scene, so no tolerance is needed and none is used. The
one thing that must be excluded is a screenshot that failed.
Usage: frozen.py [gap_s] -> prints frozen|animating, exit 0 if FROZEN
frozen.py --pair <a.png> <b.png> -> same test on two saved frames, so the
NEGATIVE side can be checked without
a live game to be un-frozen in
"""
import subprocess
import sys
import time
from PIL import Image, ImageChops
def grab(path):
subprocess.run(["screenshot", path], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
return Image.open(path).convert("RGB")
def frozen(gap=6.0):
a = grab("/tmp/frz-a.png")
time.sleep(gap)
b = grab("/tmp/frz-b.png")
if a.size != b.size:
return None, -1
bbox = ImageChops.difference(a, b).getbbox()
px = list(ImageChops.difference(a.convert("L"), b.convert("L")).getdata())
return (bbox is None), max(px)
def compare(pa, pb):
a, b = Image.open(pa).convert("RGB"), Image.open(pb).convert("RGB")
if a.size != b.size:
return None, -1
px = list(ImageChops.difference(a.convert("L"), b.convert("L")).getdata())
return (ImageChops.difference(a, b).getbbox() is None), max(px)
def in_flight(shot="/tmp/frz-flight.png"):
"""Is the flight HUD still on screen?
`frozen()` answers "is the guest animating", and that is NOT the same
question: the GAME OVER screen animates happily, and a run that ends there
passes every liveness check while the mission is over. One bit-level
differential spent its whole 700 s window reporting "the counter never
moved" about a world that had stopped being a mission — mean colour
(114, 22, 63), the magenta GAME OVER plate, not flight.
Classified by `screen_id.py`'s whole-image statistics, for the reasons its
own header gives.
"""
import subprocess as _sp
import os as _os
_sp.run(["screenshot", shot], stdout=_sp.DEVNULL, stderr=_sp.DEVNULL)
here = _os.path.dirname(_os.path.abspath(__file__))
out = _sp.run(["python3", _os.path.join(here, "screen_id.py"), shot],
capture_output=True, text=True).stdout.split()
return bool(out) and out[0] == "flight"
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--pair":
f, mx = compare(sys.argv[2], sys.argv[3])
gap = "pair"
else:
gap = float(sys.argv[1]) if len(sys.argv) > 1 else 6.0
f, mx = frozen(gap)
if f is None:
print("unknown (screenshot failed)")
sys.exit(2)
suffix = gap if gap == "pair" else f"{gap}s"
print(f"{'frozen' if f else 'animating'} max_pixel_delta={mx} gap={suffix}")
sys.exit(0 if f else 1)