tooling(micromate): exercise the event chain, and test the 0x06 count candidate

mm_client_check now drives the step-4 client methods rather than the protocol
layer directly -- list_events(), iter_events(), get_event() and decode_error()
-- so a bench run exercises the code that will actually be used.

Prints each ref with the filename it would be stored under, and reports the PVS
self-check result per event.  The download uses iter_events(), which reproduces
THOR's interleaved order.

Adds the 0x06 test: reads storage range BEFORE the chain walk, prints
content[0:4] as the candidate event count and content[4:8] as the unexplained
companion value, then reports AGREES or DISAGREES against the chain length.
Two samples on one unit said the count is right; a third value from a different
unit either confirms it or kills it, and the tool now answers that in one run.

The 0x06 read is wrapped in the same step() helper as everything else, so a unit
that does not answer it degrades to a FAILED line rather than aborting the run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ru8Lg9HkkYvX9VWWo65SmL
This commit is contained in:
2026-09-30 18:48:58 -04:00
co-authored by Claude Opus 5
parent d7d72cadf8
commit 15be9eafdb
+47 -17
View File
@@ -314,25 +314,55 @@ def main() -> int:
if setups: if setups:
print(f" first={setups[0]!r} last={setups[-1]!r}") print(f" first={setups[0]!r} last={setups[-1]!r}")
# ── SUB 0x06: is content[0:4] really the event count? ─────────────
# Two samples on one unit said yes, and THOR reads it BEFORE the chain
# walk then stops without ever reading the sentinel. A third value
# either confirms it or kills it.
claimed = None
raw06 = step("0x06 storage range", mm.protocol.read_storage_range)
if raw06:
c = _content(raw06)
claimed = int.from_bytes(c[0:4], "big")
print(f" content[0:4] = {claimed} <- CANDIDATE: event count")
print(f" content[4:8] = {int.from_bytes(c[4:8],'big')} "
f"<- unexplained (read 9 alongside a 6 on UM12947)")
if a.download: if a.download:
print("\n event chain (read-only):") print("\n event chain (read-only), via MicromateClient:")
proto = mm.protocol
proto.arm_event()
hdr = _content(proto.read_event_first())
key, size = hdr[0:4], int.from_bytes(hdr[4:8], "big")
if not size:
print(" no events stored")
else:
rec = _content(proto.read_event_record(key))
print(f" first event key={key.hex()} size={size} B "
f"type={_event_type(rec)}")
t1 = time.monotonic() t1 = time.monotonic()
blob = proto.read_event_file(key, size) refs = mm.list_events() # walks to the sentinel
dt = time.monotonic() - t1 walk = time.monotonic() - t1
print(f" downloaded {len(blob)} B in {dt:.1f} s " print(f" {len(refs)} events in {walk:.1f} s "
f"({len(blob)/dt/1024:.1f} KiB/s)") f"({walk/max(len(refs),1):.2f} s each, 3 round trips per event)")
assert len(blob) == size
_decode(blob, key, rec) if claimed is not None:
verdict = ("✓ AGREES" if claimed == len(refs)
else f"✗ DISAGREES (0x06 said {claimed})")
print(f" 0x06 count vs chain length: {verdict}")
for ref in refs:
print(f" {ref}")
print(f" would be filed as {ref.filename}")
if refs:
# Download via iter_events, which reproduces THOR's interleaved
# order -- the one with captures behind it.
print("\n download (first event, THOR's interleaved order):")
for ref in mm.iter_events():
t1 = time.monotonic()
try:
result = mm.get_event(ref) # verify=True
except Exception as e:
print(f" {ref.key_hex}: {type(e).__name__}: {e}")
break
dt = max(time.monotonic() - t1, 1e-6)
n = sum(len(v) for v in getattr(result, "samples", {}).values())
err = mm.decode_error(ref, result)
check = ("PVS agrees to %+.4f%%" % (100 * err)
if err is not None else "PVS check n/a (histogram)")
print(f" {ref.key_hex} {ref.record_type:9} {ref.size:6} B "
f"in {dt:.1f} s -> {n} samples, {check}")
break
except ProtocolError as e: except ProtocolError as e:
print(f"\n ABORTED {type(e).__name__}: {e}") print(f"\n ABORTED {type(e).__name__}: {e}")