Commit Graph

3 Commits

Author SHA1 Message Date
Alex Vanderveen 651acb42e5 lockstep: say what a skip reason MEANS, not just how many
The segment-level run came back with 10,619 checked and 74,111 skipped, and the
weak-coverage guard correctly refused to call it a result. But "74,111" on its
own does not distinguish the two situations that produce it, and they call for
opposite responses: something is wrong, or this mode structurally cannot cover
this game.

Here it is the second. 99% of the skips are segments containing an interrupt,
which cannot be replayed — so in an interrupt-heavy game segment granularity
skips almost everything by construction. The tool now names the dominant reason
when one accounts for 60% or more, explains it, and in this case says outright
to use block granularity instead.

That is not hypothetical: the block-level run over the same game covered
5,679,898 blocks with ZERO skips. Same instrument, same question, and the two
modes differ by four orders of magnitude in what they actually compared.

No single reason above 60% names no cause at all, since inventing one would be
worse than staying quiet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:55:57 -04:00
Alex Vanderveen 499deb0c87 lockstep: keep the document type out of the divergence type's way
The first real run came back clean over 5,679,898 blocks with zero skips —
and the Studio would have rejected its own output. The engine calls the
DIVERGENCE type "kind", every report document in this toolchain calls the
DOCUMENT type "kind", and merging the reply as-is overwrote one with the other.
On a clean run that wrote "kind": "none", and the loader's type check then
failed on a file the tool had just produced.

The C++ loader carried a comment warning about exactly this collision. Writing
the warning and not preventing it is worse than not noticing at all, since the
comment reads as though it had been handled. The divergence type is now
div_kind and the document type is restored after the merge.

Also sharpened what "clean" is allowed to claim. It said recompilation was not
the fault; that overstates it. Lockstep compares the compiled path against the
interpreted one INSIDE one process, so it exonerates the translation and
nothing else — the memory subsystem, HLE, DMA and device emulation are common
to both paths, and a fault living there produces identical results on both
sides and is invisible here. A clean lockstep narrows the search; it does not
close it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:55:57 -04:00
Alex Vanderveen 762a62e95f studio: lockstep — compare compiled code against our own interpreter
The code bisect, as a button. It runs the same guest code twice inside
psx-runtime -- once compiled, once interpreted -- and reports the first place
they disagree, with the register or memory value each produced.

This is the right instrument for a recompilation bug because it needs none of
what every cross-emulator comparison in this investigation has had to fight
first: frame alignment, address correspondence between two allocators, and
getting both emulators to the same point in the game. Each of those has
produced a confident wrong answer at least once here -- most recently
"source-differs", which turned out to be the two sides sitting on opposite
halves of a double buffer.

It also has a target rather than being a fishing expedition. The colour routine
is built from lwl/lwr/swl/swr and stages entirely through scratchpad, and
unaligned access is the classic recompiler weak spot.

The load-bearing detail is the verdict, not the flag. "found: false" is
produced by two completely different situations -- the compiled code matched
everywhere it was compared, and nothing was compared at all -- because the
segment comparator skips whatever it cannot replay. A run that skipped
everything is indistinguishable from a clean one unless the counters are read
alongside it, so:

  nothing checked          -> INCONCLUSIVE, said in those words
  more skipped than checked-> WEAK, with the suggestion to use block granularity
  checked and quiet        -> clean, naming how many units backed the claim
  divergence               -> kind, pc, both values, and the memory ops before it

Reporting an unearned pass here would be worse than reporting nothing, since
this is the one instrument whose result does not need caveating.

Divergence kinds that mean the comparator gave up (path-cap, unsupported) are
called out as limits rather than findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 12:55:57 -04:00