diff --git a/clients/apple/Sources/PunktfunkClient/ContentView.swift b/clients/apple/Sources/PunktfunkClient/ContentView.swift index caed5c18..08961694 100644 --- a/clients/apple/Sources/PunktfunkClient/ContentView.swift +++ b/clients/apple/Sources/PunktfunkClient/ContentView.swift @@ -500,12 +500,19 @@ struct ContentView: View { } // The resize spinner rides over the (blurred) stream; suppressed under the trust // prompt, which owns the screen. It never hit-tests, so window-drag resizes keep - // steering and the next click still reaches the stream. + // steering and the next click still reaches the stream. Mounted ONLY while a + // resize is live: resident structure above the CAMetalLayer is what the stage-4 + // direct-to-display hunt is eliminating — composited presents reach glass a full + // refresh later. The enter/exit fade rides the call-site transition + the + // .animation(value: resizing) below (the view's internal `if active` fade can't + // run when the whole view unmounts). .overlay { - if pendingFingerprint == nil { - ResizeIndicatorView(active: model.resizing) + if pendingFingerprint == nil, model.resizing { + ResizeIndicatorView(active: true) + .transition(.opacity.combined(with: .scale(scale: 0.92))) } } + .animation(.easeInOut(duration: 0.22), value: model.resizing) if let fp = pendingFingerprint { TrustCardView( fingerprint: fp, diff --git a/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift b/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift index 466b356e..23406643 100644 --- a/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift +++ b/clients/apple/Sources/PunktfunkKit/Video/Stage2Pipeline.swift @@ -216,6 +216,10 @@ private final class DeadlineLinkDelegate: NSObject, CAMetalDisplayLinkDelegate { private let renderSignal: DispatchSemaphore private let hint: FrameRateHint private let stats: PresentDebugStats? + /// One-shot: log the link's EFFECTIVE preferredFrameLatency after the first re-assert — + /// reads 1 while vendLeadMs sits at ~2 periods ⇒ the scheduler ignores the request while + /// the layer is composited (the promotion hunt); reads 2 ⇒ the system clamped it outright. + private var loggedEffective = false init( stash: LatestBox, renderSignal: DispatchSemaphore, @@ -235,6 +239,15 @@ private final class DeadlineLinkDelegate: NSObject, CAMetalDisplayLinkDelegate { // before add(to:), and whether a pre-add set survives scheduling is exactly the kind of // thing the vendLeadMs stat exists to catch — belt and braces. if link.preferredFrameLatency != 1 { link.preferredFrameLatency = 1 } + if !loggedEffective { + loggedEffective = true + let range = link.preferredFrameRateRange + let msg = String( + format: "deadline link up: effective preferredFrameLatency=%.2f " + + "range=%.0f-%.0f preferred=%.0f", + link.preferredFrameLatency, range.minimum, range.maximum, range.preferred ?? 0) + presentLog.info("\(msg, privacy: .public)") + } // The link's own pipeline depth, measured: how far ahead of glass this vend runs. stats?.vendLead( ms: (update.targetPresentationTimestamp - CACurrentMediaTime()) * 1000)