BT410 5.3.77: the overrun drain loop is not the trigger either

serialnamedpipe's P_RX_BLOCKED path drains its whole backlog when the guest
hasn't read in time -- directserial's `while (doReceive());`.  That looked
like the trigger, since a named pipe's backlog is unbounded where a real
port's is capped by the line rate.  Bounded it to one byte (real UART overrun
semantics) and re-ran the conf that had faulted 3/3: FAULT 283s, FAULT 226s,
FAULT 204s.  Killed.

The measurement that explains why it was never plausible: overruns during a
run are 1-3 per report period, because vRIO sends about a byte every 1-3ms --
the backlog is shallow and the loop had nothing to teleport.  The 494/499
counts all land after the game exits.  I read the code and inferred a burst
without measuring the queue depth it operates on.

Default restored to drain-all: it is the validated directserial behaviour the
RIO's rxpollus/rxburst tuning was calibrated against, and changing it on a
dead hypothesis would risk real-cockpit timing for nothing.
VPX_RX_OVERRUN_ONE=1 opts into the bounded form.

The roadmap now carries a fault ledger of every dead hypothesis so none get
re-run.  What survives: a deterministic DPMI-host path walking a
{next,handler} chain into a node whose pointer is an unhooked IVT value,
entered under RIO interrupt load.  Naming the owning routine needs a trace of
entries to host 0x66CF, clean vs faulting -- a DPMI32VM reversing
sub-project, now decoupled from the reconstruction (vRIO down = 100%
reliable, logged conf = ~70%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-29 19:33:05 -05:00
co-authored by Claude Fable 5
parent d378148c14
commit e69d761fb8
10 changed files with 1981 additions and 689 deletions
+41 -1
View File
@@ -274,7 +274,47 @@ void CSerialNamedPipe::handleUpperEvent(uint16_t type) {
rx_retry = 0;
removeEvent(SERIAL_RX_EVENT);
if (doReceive()) {
while (doReceive()); // overrun, like directserial
//
// OVERRUN SEMANTICS. directserial drains its whole
// backlog here (`while (doReceive());`) because a real
// port's backlog is bounded by the physical line rate
// -- there is never much to drain. A NAMED PIPE has no
// such bound: vRIO can have hundreds of bytes queued,
// and this loop teleported all of them into the UART in
// zero guest-time, each one raising receive/line-status
// state with no instruction executed in between.
//
// That burst is the trigger for the pod's residual
// page fault (2026-07-29): it fires only when the guest
// is briefly slow to read -- which is the mission-load
// window -- and its probability moves with anything
// that changes guest timing, which is why our own COM3
// logging SUPPRESSED it (quiet conf 3/3 faults vs the
// logged conf's 25-40%).
//
// TESTED, NOT THE CULPRIT (2026-07-29). Bounding the
// drain to one byte -- real UART overrun semantics,
// where the ARRIVING byte overwrites the unread one
// rather than pulling the whole line forward -- did NOT
// stop the fault (2/2 faults with it in place). The
// measurement that explains why: overruns DURING a run
// are only 1-3 per report period, because vRIO sends
// about one byte every 1-3ms, so the backlog is shallow
// and this loop never had hundreds to teleport. The
// big 494/499 counts in the logs all land AFTER the
// game exits.
//
// So the drain-all default stays -- it is the validated
// directserial behaviour the RIO's rxpollus/rxburst
// tuning was calibrated against, and changing it
// without evidence would risk the real-cockpit timing
// for nothing. VPX_RX_OVERRUN_ONE=1 opts into the
// bounded form if a future experiment wants it.
//
static int overrun_one = -1;
if (overrun_one < 0)
overrun_one = getenv("VPX_RX_OVERRUN_ONE") ? 1 : 0;
if (!overrun_one) { while (doReceive()); }
rx_state = P_RX_WAIT;
setEvent(SERIAL_RX_EVENT, bytetime * 0.9f / rx_burst_div);
} else {