The Apple HUD froze its clock offset at connect, so every host-anchored number lied — and the tvOS present floor is closed on sound evidence now #208

Merged
enricobuehler merged 2 commits from worktree-appletv-present-depth into main 2026-08-13 21:21:43 +00:00
Owner

Two commits from the Apple TV latency hunt. No latency win shipped — the hunt itself is what exposed the stats defects.

fix(apple): the stats overlay lied three ways (0c944b48 pre-rebase)

Field evidence: two sessions minutes apart against the same wired host read hostnet_p50 17–21 ms, then a physically impossible 4.4 ms.

  1. Frozen clock offset. The client consumed the connect-time skew estimate and froze it — a Stage2Pipeline field, a StreamPump let, and a ContentView closure capture list feeding the hostnet meter and the host/network splitter. The core keeps a live estimate (punktfunk_connection_clock_offset_now_ns, ABI v10, re-synced every 60 s + on suspected wall-clock steps) and its own doc says the connect-time value "silently corrupts every capture-clock comparison" — a VM host's wall clock steps. PunktfunkConnection.clockOffsetNs is now the live read (an atomic load behind the FFI); every consumer — meters, splitter, audio A/V sync — reads it at use.
  2. Silent impossible-sample trim. LatencyMeter.record refused ≤ 0 samples silently, so a wrong offset trimmed the impossible half of the shifted distribution and presented the surviving tail as a plausible small number ("e2e 0–3 ms p50"). Refusals are now counted (drainTrimmed(), deliberately separate from drain() so a fully-poisoned window can't swallow the count), surfaced as an orange "clock offset suspect" HUD line and skew_trim= on the stats line.
  3. -1 fallbacks printed NaN. In String(format:)'s variadic CVarArg context a ternary does not unify to Double — a bare -1 goes in as Int, and %f reads Int64(-1)'s all-ones bit pattern, which is a quiet NaN. Latent since the stats line existed; fixed with typed -1.0 / Double() fallbacks.

On-glass verified on the Apple TV: the equation closes (e2e 38.3 ≈ hostnet 4.7 + decode 7.6 + display 25.7), skew_trim=0, zero NaN.

feat(apple): the present-floor ladder levers (e4bc7497 pre-rebase)

The earlier "tvOS 2-refresh floor is immovable" verdict rested on a preferredFrameLatency readback — a plain float property that echoes whatever we stored; there is no grant channel in the API. The ladder run with these levers settled it soundly: PUNKTFUNK_FRAME_LATENCY=2 left the vend lead at 1.95 refresh periods (an honored ask would trivially have moved it), so the property is inert on tvOS and ~32.5 ms is the platform cost of the compositor regime. PUNKTFUNK_PRESENTER=stage1 (the AVSampleBufferDisplayLayer video plane, previously unreachable on Release builds) was visibly worse in the field. Kept: the levers, the drawable-pool clamp to 2, the tvOS fixed-rate range pin, the ask/readback HUD + stats fields, and the stdout stats mirror (PUNKTFUNK_STATS_STDOUT=1 — the only log channel that exists on an Apple TV).

Two commits from the Apple TV latency hunt. No latency win shipped — the hunt itself is what exposed the stats defects. ## fix(apple): the stats overlay lied three ways (`0c944b48` pre-rebase) Field evidence: two sessions minutes apart against the same wired host read `hostnet_p50` 17–21 ms, then a physically impossible 4.4 ms. 1. **Frozen clock offset.** The client consumed the connect-time skew estimate and froze it — a `Stage2Pipeline` field, a `StreamPump` `let`, and a ContentView closure **capture list** feeding the hostnet meter and the host/network splitter. The core keeps a live estimate (`punktfunk_connection_clock_offset_now_ns`, ABI v10, re-synced every 60 s + on suspected wall-clock steps) and its own doc says the connect-time value "silently corrupts every capture-clock comparison" — a VM host's wall clock steps. `PunktfunkConnection.clockOffsetNs` is now the live read (an atomic load behind the FFI); every consumer — meters, splitter, audio A/V sync — reads it at use. 2. **Silent impossible-sample trim.** `LatencyMeter.record` refused ≤ 0 samples silently, so a wrong offset trimmed the impossible half of the shifted distribution and presented the surviving tail as a plausible small number ("e2e 0–3 ms p50"). Refusals are now counted (`drainTrimmed()`, deliberately separate from `drain()` so a fully-poisoned window can't swallow the count), surfaced as an orange **"clock offset suspect"** HUD line and `skew_trim=` on the stats line. 3. **`-1` fallbacks printed NaN.** In `String(format:)`'s variadic `CVarArg` context a ternary does not unify to `Double` — a bare `-1` goes in as `Int`, and `%f` reads `Int64(-1)`'s all-ones bit pattern, which is a quiet NaN. Latent since the stats line existed; fixed with typed `-1.0` / `Double()` fallbacks. On-glass verified on the Apple TV: the equation closes (`e2e 38.3 ≈ hostnet 4.7 + decode 7.6 + display 25.7`), `skew_trim=0`, zero NaN. ## feat(apple): the present-floor ladder levers (`e4bc7497` pre-rebase) The earlier "tvOS 2-refresh floor is immovable" verdict rested on a `preferredFrameLatency` **readback** — a plain float property that echoes whatever we stored; there is no grant channel in the API. The ladder run with these levers settled it soundly: `PUNKTFUNK_FRAME_LATENCY=2` left the vend lead at 1.95 refresh periods (an honored ask would trivially have moved it), so the property is **inert on tvOS** and ~32.5 ms is the platform cost of the compositor regime. `PUNKTFUNK_PRESENTER=stage1` (the AVSampleBufferDisplayLayer video plane, previously unreachable on Release builds) was visibly worse in the field. Kept: the levers, the drawable-pool clamp to 2, the tvOS fixed-rate range pin, the ask/readback HUD + stats fields, and the stdout stats mirror (`PUNKTFUNK_STATS_STDOUT=1` — the only log channel that exists on an Apple TV).
enricobuehler added 2 commits 2026-08-13 21:16:18 +00:00
The 2026-08-13 field ladder closed 'the tvOS two-refresh present floor is
immovable' on the strength of a 'link granted latency 1.00 frames' HUD line.
But that line reads back preferredFrameLatency — a plain read-write float
(CAMetalDisplayLink.h carries no doc contract) that echoes whatever we
stored. A readback is not a grant; the measured vend lead (1.95 refresh
periods) was the only truth-teller, and two levers were never actually
pulled. This commit also carries the ladder instrumentation that run used:
the 1 Hz stats mirror to stdout (the only log channel that exists on an
Apple TV), the PresentLinkInfo HUD plumbing, the stage-4 drawable-pool
clamp to 2, and the tvOS fixed-rate range pin.

- PUNKTFUNK_FRAME_LATENCY makes the ask a lever (float 0...4, default 1) so
  an on-device ladder can prove whether the property does ANYTHING on tvOS:
  ask=2 growing the vend lead to ~3 means it works and the floor is ~ask+1;
  a lead pinned at ~2 means it is inert and the compositor regime is fixed.
  ask=0.5 is the in-regime win probe (the property is a float for a reason).
  Ask + readback go to the HUD line and the stats line (link_ask/
  link_readback) so the ladder reads HUD-off over stdout.

- PUNKTFUNK_PRESENTER=stage1 now resolves on Release builds (env only; the
  persisted picker stays DEBUG-gated — an env var is never a leftover, it
  takes a devicectl/Xcode launch to exist). Stage-1 presents on the hardware
  video plane (AVSampleBufferDisplayLayer + DisplayImmediately) instead of
  through the GPU compositor — the only rung that can dodge the two-refresh
  regime — and the field A/B silently ran stage-4 because the gate keyed on
  build config. The pump gains stage-1's only latency instrument:
  capture→enqueue into the e2e meter (offset-corrected, displayed frames
  only), so cross-rung runs can pin any felt difference on the present tail.
fix(apple): the stats overlay lied three ways — a frozen clock offset, silently trimmed impossible samples, and Int -1 fallbacks printing as NaN
ci / web (pull_request) Successful in 1m13s
ci / bun-nix (pull_request) Successful in 1m47s
apple / swift (pull_request) Successful in 2m3s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 2m57s
ci / rust-arm64 (pull_request) Successful in 3m15s
ci / rust (pull_request) Successful in 4m20s
90d13de81e
Field 2026-08-13, Apple TV vs Bazzite VM host, two sessions minutes apart on
the same wire: hostnet_p50 read 17-21 ms, then a physically impossible
4.4 ms (host-side encode alone is ~4.7). Root causes, each its own defect:

- The client consumed the CONNECT-TIME skew offset and froze it: cached in
  a Stage2Pipeline field, in a StreamPump let, and in a ContentView closure
  CAPTURE LIST feeding the hostnet meter and the host/network splitter.
  The core keeps a live estimate (punktfunk_connection_clock_offset_now_ns,
  ABI v10, re-synced every 60 s + on suspected wall-clock steps) and its
  own doc says the connect-time value 'silently corrupts every
  capture-clock comparison' after an NTP step — a VM host steps. Now
  PunktfunkConnection.clockOffsetNs IS the live read (an atomic load
  behind the FFI) and every consumer reads it at use: per record, per AU,
  per enqueue. The Swift audio plane's AvSync observation gets the live
  value through the same property.

- LatencyMeter's impossible-sample guard (≤ 0 after offset correction)
  dropped samples SILENTLY, so a wrong offset didn't invalidate a window —
  it trimmed the impossible half of the shifted distribution and presented
  the surviving tail as a plausible small number ('e2e 0-3 ms p50 /
  23 ms p95' on a session whose true hostnet was ~18 ms; also the
  historical '0 ms network / 0 ms e2e' readings). The refusals are now
  counted and drained separately from Stats — deliberately, because a
  fully-poisoned window drains to nil and a count inside Stats would
  vanish with it. The HUD shows an orange 'clock offset suspect' line and
  the stats line grew skew_trim=N; nonzero means disregard e2e/hostnet.

- Every invalid-field fallback in the 1 Hz stats line was a bare -1: in
  the variadic CVarArg context the ternary does NOT unify to Double, the
  literal goes in as Int, and %f reads Int64(-1)'s all-ones bit pattern —
  which is a quiet NaN. Latent since the line existed; stage-1 (the first
  rung with invalid fields while frames flow) printed nan for every one.
  All fallbacks are now typed -1.0 / Double()-wrapped.
enricobuehler merged commit c591b7b4af into main 2026-08-13 21:21:43 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#208