6.15.3 - A display-criteria gate that measures what it reports
Two defects in the tvOS display-criteria play gate, both in what it measures rather than in what it does, found by reading the second round of unfiltered device logs on Sodalite#49 (thanks to @nathanpiper for capturing them). Drop-in from 6.15.2, internal surface only, with one behavioural note below.
A label that claimed the opposite of what it showed
StartSignal.preGate means the panel was already switching while the AVPlayerItem was being built, which is the ordering the Sodalite issue was filed on. Its documentation says "in-progress flag already set when polling began", and that is the only reading the flag supports: the switch observers are registered on gate entry, so a switch that predates entry can only be seen as a flag that is already true on the very first poll.
Stage 1 tested that flag on every poll instead. A run whose flag was false at entry, and stayed false for roughly 37 polls after it, was therefore logged as:
[DisplayCriteria] host switch ended (start pre-gate after 376ms, total 3238ms, ...)
A switch observed starting 376 ms into the gate is by definition not one that predates it. The single question the previous release's instrumentation work existed to answer was being answered backwards, and all three reporter runs came back the same way.
classifyStart is now a pure, tested function with a third case for the honest one:
start in-gate (flag only, start notification missed) after 376ms, total 3238ms
The switch began inside the gate either way; what is missing is the start notification. That is a statement about where the engine begins listening, not about the panel, and it is the evidence that would justify moving observer registration to the criteria write (#339).
Budgets that stretched 40 % under the load they exist to survive
Both stages spent poll counts. for _ in 0..<40 { sleep(50ms) } is a 2000 ms cap only on an idle scheduler, and the device these logs came from reports thermal=serious from its first telemetry sample onward. The same Stage 2 cap was measured at 2082 ms in one run and 2862 ms in another, in the same session series on the same hardware.
A timeout whose real value depends on machine load cannot be compared across runs, and any latency decision read off such a log measures scheduler pressure along with the thing under test. Both stages now measure elapsed time against StartGrace.budgetMs and stage2CapMs; the sleep stays the poll interval, and overshoot is bounded by one interval rather than unbounded.
Behavioural note for hosts: this makes Stage 1's .full budget exactly 1000 ms, where load could previously stretch it further. 1000 ms is the documented and reviewed value that #274 verified, but the guarantee it backs is DV Profile 5 cold start, so it is worth knowing that the effective wait may have been longer in practice.
Deliberately unchanged
The same logs contain the first direct measurement of a rate-only SDR switch on that panel: ~2.9 s end to end, with two further runs never reporting an end inside the 2 s cap at all. That contradicts the sub-second premise the .brief budget rests on, and it is exactly the mechanism the original judder report describes, since a cap that expires lets play() start into a switch that may still be running.
Neither the cap nor .brief was widened here. Three runs on one panel are not grounds for making every session's cold start wait longer, especially as all three played cleanly. The measurements now sit in the doc comments so the next change to those budgets starts from numbers rather than from an assumption, and the remaining work is tracked in #339.