A restarted, healthy session still refused: two sectors "lost their
notification", both past 125117. The game Pauses right after that
sector, so the drive's continued read-ahead is never consumed and those
notifications are lost by design -- normal behaviour, flagged as the
failure state.
The window now ends at the load's last sector, and loss is only counted
inside it. The refusal also names the sectors instead of one example, so
a genuine case can be checked against the load's own extent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The refusal was my heuristic, not the user's session. These rings
accumulate for the entire run, so replaying the scene legitimately
produces several records per sector; accumulating them conflates passes,
and a single earlier degraded pass then poisons every sector's summary
and blocks the diff forever.
Entries arrive oldest-first, so the most recent record for each sector
now wins, and degradation is judged only on whether THAT pass lost a
notification. Replay count is reported as pN rather than treated as
damage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two faults in the first live run.
psx-runtime's records were byte-identical to the disc_speed=2x
experiment -- lost notifications, sectors read 2-7 times each. That
session was still running from before the config was reverted, so a
degraded native side was diffed against a healthy oracle and the tool
concluded the notification patterns agreed. It now refuses when `lost`
is set or any sector was read more than once, and says to restart.
And every oracle row reported drained=0. FlagCdSector looks a sector up
by buffer index, but with 8 slots rotating over 14 sectors the slot is
reused before the drain completes, so the flag landed on a newer entry.
The delivered sector's sequence number is now recorded when its INT1 is
presented, and the drain flags that entry directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Transfers are understood: psx-runtime fills the game's buffers
109,111,111,113,113 where DuckStation fills 109,110,111,112,113. Sectors
110 and 112 -- the second of each two-sector read, one carrying the
effect's palette -- are READ on psx-runtime ([D], never lost) and never
drained. Continuations are not broken in general: the five-sector read
113..117 drains completely on both sides. It fails on the TWO-sector
reads, which is a boundary condition, not a missing feature.
Speed was ruled out by experiment: disc_speed=2x made it strictly worse
(lost 0 -> 70, every Setloc retried), so the drive outrunning the guest
is a distinct failure and not this one. Reverted.
That leaves what the guest is TOLD. psx-runtime raises an immediate INT1
for a sector arriving with the flag clear, which moves the read slot at
once; DuckStation may instead DROP the notification and re-announce it
after the guest finishes draining (SetAsyncInterrupt's early return, and
CheckForSectorBufferReadComplete's missed-sector path).
The oracle now records per sector which of those happened -- stored,
dropped, queued, delivered, re-announced, drained -- hooked at the six
sites that decide it, and served by cd_sector_events with the same
server-side filtering the socket requires. notify_diff.py prints both
sides per LBA and names the one pattern that would explain the missing
sectors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>