docs: the in-mission freeze ends in 1171 refused resumes of one thread
A Stage 02 run froze at TIME 01:02 with a radio line caught mid-word. The emulator was alive at ~200% CPU with its main thread in state R, two screenshots six seconds apart were byte-identical, and the log ended in a spin: 1171 of the run's 1200 "host resume was refused" lines are the single pair F80002AC -> F8000240, starting at the line immediately after F80002AC is created, and the log never grows again. The commit that added that warning records what normal looks like - about 7 in a whole boot - so this is a 150x anomaly on one pair rather than noise. F8000240 itself appears exactly once outside the spin, at creation, and calls nothing. Eleven of the frozen process's 79 host threads have zero CPU, four of them consecutive late-created guest threads - the same signature as the lost resume that c1b57f93b fixed for the title screen. That fix IS in this build, so either there is a second window in that race or this only looks alike. The inference is named as one: nothing here maps a guest handle to a host tid, so "the zero-CPU threads are the ones being resumed" is a reading of two consistent observations. And the refusals could equally be the game's reaction to a worker stuck for another reason - log_mask=13 has the Kernel channel disabled, so not one of F8000240's waits is visible. The next experiment is written down concretely: reproduce with LOG_MASK=12 LOG_LEVEL=3 and map the handle to a tid. Also caps ob_hunt's survivor listing at 40 - an aborted run printed all 21482 and buried the line that mattered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -41,7 +41,12 @@ import gmem # noqa: E402
|
||||
import gworld # noqa: E402
|
||||
import ob_read # noqa: E402
|
||||
|
||||
VA = 0xBDB59668
|
||||
# The counter is NOT at a fixed address across runs — see
|
||||
# structures/mission-objective-counter.md. 0xbdb59668 is the value it takes in
|
||||
# 3 of the 5 runs measured; when it is wrong this script refuses to run rather
|
||||
# than reporting nonsense, and ob_hunt.py finds the run's own address in ~1 min
|
||||
# of flight. Override with OB_VA.
|
||||
VA = int(os.environ.get("OB_VA", "0xBDB59668"), 0)
|
||||
RADIUS = 0x400
|
||||
DELTA = 0x130
|
||||
|
||||
@@ -103,6 +108,15 @@ def main():
|
||||
|
||||
candA = {k: v for k, v in gA.items() if len(v) == nA}
|
||||
print(f"[A] offsets where exactly {nA} entities agree: {len(candA)}", flush=True)
|
||||
# WHO a candidate groups is a discriminator available without waiting for a
|
||||
# transition: an OB flag should mark a subset of the hostiles, not a mix that
|
||||
# includes the player and its wingmen. Printed for every candidate rather
|
||||
# than filtered on, because "objectives are always hostile" is an assumption
|
||||
# about the mission, not a measurement -- an escort objective would be
|
||||
# friendly, and Stage 02 has one (the ACROPOLIS).
|
||||
for (d, val), mem in sorted(candA.items()):
|
||||
who = Counter(nm.replace("UN_", "") for nm in mem)
|
||||
print(f" pos{d:+#07x} = {val.hex()} {dict(who)}", flush=True)
|
||||
|
||||
stuck = 0
|
||||
while time.time() < deadline:
|
||||
|
||||
@@ -101,8 +101,13 @@ def main():
|
||||
payload = {"scan_value": v0, "transitions": results,
|
||||
"survivors": [{"off": o, "va": gmem.primary_va(o)} for o in sorted(cands)]}
|
||||
json.dump(payload, open(f"{out}/hunt.json", "w"), indent=1)
|
||||
for o in sorted(cands):
|
||||
# Cap the listing. On a clean run this is a handful of addresses; on an
|
||||
# aborted one (frozen guest, no transition) it is the whole scan, and 21 482
|
||||
# lines of noise buried the one line that mattered.
|
||||
for o in sorted(cands)[:40]:
|
||||
log(f" va {gmem.primary_va(o):#x}")
|
||||
if len(cands) > 40:
|
||||
log(f" ... and {len(cands) - 40} more (see hunt.json)")
|
||||
log(f"wrote {out}/hunt.json")
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user