port: check-capture needed two numbers -- the rate alone passed a 50%-silent file

The Decoder found a blind spot in the bar I shipped last iteration. Raising the
PulseAudio client buffer keeps cutting the gap RATE while total silence bottoms
out and then doubles -- an over-large buffer starves in a few enormous holes
instead of many small ones. Its 500 ms capture scores 1.3 gaps/s, better than a
genuine music bed at 3.3, while being 50% silence. My 20/s bar passed it.

Same shape as the level table that cannot see a duplicated channel: one number,
blind to the failure next door.

I did not set a bar on their numbers, because I do not hold those files and the
last two bars in this tool were wrong precisely from being invented. Instead I
built a control in that regime -- `bigholes`, a real bed with 350 ms holes
punched in -- and set the rule from four controls I can run:

  real music+SFX bed          1.1% silence,  3.3 gaps/s   PASS
  voice track, mono, pauses  53.2% silence,  0.3 gaps/s   PASS
  bed with 350 ms holes      46.3% silence,  3.2 gaps/s   FAIL
  the starved capture        35.6% silence, 30.9 gaps/s   FAIL

Rate alone cannot separate rows 2 and 3; silence alone cannot separate 1 and 3.
The pair does: fail when >=10% is silent on every channel AND there is at least
one gap per second. Real audio is either mostly not silent, or silent in a few
long stretches -- not both at once.

AND THE REGIME IT STILL CANNOT JUDGE IS PRINTED RATHER THAN PASSED. High silence
with very few gaps is what a real voice track looks like and what an
over-buffered capture looks like; nothing here separates them, so the tool says
UNJUDGED and tells the reader to check against a known source. Inventing a bar
for a regime with no control in it is how the previous two bars came to be wrong.

A CONTROL THAT DOES NOT EXECUTE IS NOT A CONTROL: the tool returned immediately
for single-channel input, so the mono voice track -- one of the four controls --
was never run through the check it was meant to control. Mono now skips only the
duplicate test.

Also recorded: the Decoder has withdrawn "the monitor-sink route cannot be fixed
by configuration". A ~200 ms client buffer is worth a retry BEFORE anyone spends
a session on a Canary rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-29 16:40:24 +00:00
parent 57bb630c69
commit ba364dba5a
2 changed files with 101 additions and 15 deletions

View File

@@ -215,19 +215,62 @@ it produced said nothing about the game.
gaps at a median of 1.4 ms** — quiet 16-bit passages really are zero for
milliseconds — so neither statistic separates it from a starved file.
**What does separate them is the RATE**, by an order of magnitude in both
directions:
3. 🔴 **The gap RATE alone.** This one shipped, and the Decoder found it: raising
the client buffer keeps cutting the rate while total silence **bottoms out and
then doubles**, because an over-large buffer starves in a few enormous holes
instead of many small ones. Its `PULSE_LATENCY_MSEC=500` capture scores
**1.3 gaps/s — better than a genuine music bed at 3.3 — while being 50 %
silence**, and a 20/s bar passed it.
| | gaps/s | median gap | all-channel silence |
**It takes two numbers, because either one alone is blind to the failure next
door** — the same shape as a level table that cannot see a duplicated channel.
Reproduced on a file held here (`bigholes`: a real bed with 350 ms holes punched
into it) so the regime is controlled rather than quoted:
| control | all-channel silence | gaps/s | verdict |
|---|---|---|---|
| the starved capture | **32.9** | 3.9 ms | 35.6 % |
| a real music+SFX bed | **3.3** | 1.4 ms | 1.1 % |
| a voice track, 53 % pauses | **0.03** | — | — |
| real music+SFX bed | 1.1 % | 3.3 | **PASS** |
| voice track, mono, real pauses | 53.2 % | 0.3 | **PASS** |
| bed with 350 ms holes | **46.3 %** | 3.2 | **FAIL** |
| the starved capture | **35.6 %** | 30.9 | **FAIL** |
The bar is **20 gaps/s** — 1.6× below the bad case, 6× above the worst good one,
and set from those controls rather than chosen and then justified.
Rate alone cannot separate rows 2 and 3; silence alone cannot separate rows 1 and
3. **The pair does:** fail when ≥ 10 % of the file is silent on every channel
*and* there is at least 1 gap per second. Real audio is either mostly not silent,
or silent in a few long stretches — not both at once.
### 🔴 The monitor-sink route cannot be fixed by configuration
### ⚠️ The regime this tool cannot judge, and says so
**High silence with very few gaps is what a real voice track looks like (53.2 %
in 0.3 gaps/s) and also what an over-buffered capture looks like.** No statistic
here separates them. The tool prints `UNJUDGED` and tells you to check the file
against a known source rather than passing it silently — because inventing a bar
for a regime with no control in it is how the two bars above came to be wrong.
⚠️ **A control that does not execute is not a control.** An earlier version
returned immediately for a single-channel file, so the mono voice track — one of
the four controls — was never actually run through the check it was meant to
control. Mono now skips only the duplicate test.
### 🟡 The monitor-sink route may be fixable after all — retry before rebuilding
An earlier version of this section said the route *"cannot be fixed by
configuration"*. **Withdrawn.** That inferred from the holes that the guest runs
below real time, without testing the alternative: **the client buffer is simply
tiny.** Xenia asks SDL for 256 samples — **5.33 ms** at 6 ch — against a stock
`daemon.conf` with no fragment tuning.
| client buffer | silence | gaps/s |
|---|---|---|
| Xenia default (~5.3 ms) | 39.3 % | 30.5 |
| `PULSE_LATENCY_MSEC=200` | **15.6 %** | 3.5 |
| `PULSE_LATENCY_MSEC=500` | 50.1 % | 1.3 |
⚠️ Not clean, and not like-for-like — 88 s against 347 s, and the short run covers
the splash logos where silence is real. But **the capture route deserves a retry
at ~200 ms before anyone spends a session on a Canary rebuild.**
### The tap, if configuration is not enough
`parec` reads a monitor that advances at wall-clock rate and substitutes silence,
so **every moment the emulator runs below real time is a hole**, and the timebase

View File

@@ -38,8 +38,15 @@ rate=$(probe stream=sample_rate)
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0:nk=1 "$f" | head -1)
printf '%s: %sch %sHz %.3fs\n' "$f" "$ch" "$rate" "$dur"
if [ "$ch" -lt 2 ]; then echo " single channel -- nothing to compare"; exit 0; fi
# ⚠️ MONO SKIPS THE DUPLICATE TEST AND STILL GETS THE STARVATION ONE. An earlier
# version returned immediately for a single channel, so the mono voice track --
# one of this tool's four controls -- was never actually run through the check it
# was supposed to control. A control that does not execute is not a control.
dupes=0
if [ "$ch" -lt 2 ]; then
echo " single channel -- no duplicate test, starvation still checked"
else
layout=5.1; [ "$ch" = 2 ] && layout=stereo
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
map=""; for i in $(seq 0 $((ch-1))); do map="$map -map [c$i] $tmp/c$i.wav"; done
@@ -57,7 +64,6 @@ for i in $(seq 0 $((ch-1))); do
printf ' ch%-2d peak %-12s %s\n' "$i" "$peak" "$s"
done
dupes=0
for i in $(seq 0 $((ch-1))); do
for j in $(seq $((i+1)) $((ch-1))); do
if [ "${sums[i]}" = "${sums[j]}" ]; then
@@ -66,6 +72,7 @@ for i in $(seq 0 $((ch-1))); do
fi
done
done
fi
# STARVATION: the second way a capture looks perfect and carries nothing.
#
@@ -147,11 +154,47 @@ print(" all-channel silence %.1f%%, %d gap(s) over 1 ms (%.1f/s), median gap %.
# length separates them. The RATE does, by an order of magnitude in both
# directions, and 20/s sits between with a 1.6x margin below the bad case and
# 6x above the worst good one.
if rate_per_s >= 20.0 and med < 50.0:
print(" 🔴 STARVED: %.1f gaps per second at a median of %.1f ms."
% (rate_per_s, med))
print(" The producer was not keeping the sink fed.")
# TWO NUMBERS, BECAUSE ONE CANNOT SEE THE FAILURE NEXT DOOR.
#
# The first version of this tested the gap RATE alone, at 20/s. The Decoder then
# measured what a LARGER client buffer does, and the relationship is not
# monotonic: raising `PULSE_LATENCY_MSEC` keeps cutting the rate while total
# silence bottoms out and then doubles, because an over-large buffer starves in a
# few enormous holes instead of many small ones. Its 500 ms capture scores
# **1.3 gaps/s -- better than a genuine music bed at 3.3 -- while being 50 %
# silence**, and my bar passed it. Reproduced here on a file I hold: `bigholes`,
# a real bed with 350 ms holes punched in, is 46.3 % silence at 3.2 gaps/s.
#
# That is the same shape as the level table that could not see a duplicated
# channel. One number, blind to the neighbouring failure.
#
# Controls, all four measured here:
#
# real music bed 1.1 % silence, 3.3 gaps/s PASS
# voice track, mono 53.3 % silence, 0.3 gaps/s PASS (real pauses)
# bed with big holes 46.3 % silence, 3.2 gaps/s FAIL
# the starved capture 35.6 % silence, 30.9 gaps/s FAIL
#
# Rate alone cannot separate rows 2 and 3; silence alone cannot separate rows 1
# and 3, nor 2 and 3. The pair does.
if tot / float(n) >= 0.10 and rate_per_s >= 1.0:
print(" 🔴 STARVED: %.1f%% of the file is silent on every channel, in %.1f gaps"
% (100.0 * tot / n, rate_per_s))
print(" per second (median %.1f ms). Real audio is either mostly not" % med)
print(" silent, or silent in a few long stretches -- not both at once.")
raise SystemExit(3)
# ⚠️ THE REGIME THIS TOOL CANNOT JUDGE, said out loud rather than passed
# silently. High silence with FEW gaps is what a real voice track looks like
# (53.3 % in 0.3 gaps/s) and also what an over-buffered capture looks like. No
# statistic here separates them, and inventing a bar for a regime I have no
# control in is how the last two bars in this file came to be wrong.
if tot / float(n) >= 0.10:
print(" ⚠️ %.1f%% silent in only %.1f gaps/s -- UNJUDGED. That is the shape of"
% (100.0 * tot / n, rate_per_s))
print(" a real voice track AND of an over-buffered capture, and this tool")
print(" cannot tell them apart. Check it against a known source before")
print(" concluding anything from it.")
PYEOF
starved=$?
set -e