Skip to content

fix(routing): let a decode-path correction reach a custom source (#461 follow-up) - #473

Merged
superuser404notfound merged 1 commit into
mainfrom
fix/ae461-custom-source-decode-path
Sep 3, 2026
Merged

fix(routing): let a decode-path correction reach a custom source (#461 follow-up)#473
superuser404notfound merged 1 commit into
mainfrom
fix/ae461-custom-source-decode-path

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

Follow-up to #461, reported by @cmcpherson274 after taking the escape onto his own stack.

The defect

LoadOptions.preferredDecodePath is read at three sites, all inside public func load(. The rebuild that keeps a retained IOReader picks its host from wasOnSoftwarePath, the backend the session was already on, and never reads the option. So a host correcting a playing custom source onto the software path through #460 got the correction written into loadedOptions, logged as applied, and then ignored.

Accepted, named in the log, ignored is the one outcome #460's own rules forbid, and it left the escape unreachable for exactly the population it was built for: a spool-backed source with no second engine to hand a struggling stream to.

The docs were worse than silent on it. "Where a correction lands" said the routing fields all apply on the custom path, which is the opposite of what happened.

The fix

The reopen asks the same routing policy load asks, seeded with the backend it is on. keepNativeHost and the two post-load fixups (the native-only live watchdog, the software-only track republish) follow the target route rather than the previous one.

The one-way type is what makes re-routing this path safe. DecodePath has no .native, so the only flip reachable is native to software, and the software path is the general one; loadNative on a software-routed AV1 source, which the code comment there warns about, stays unreachable by construction rather than by asking the next reader not to write it.

The refusal

What the software path cannot REPRESENT is refused before any teardown, with two new SessionReloadRefusal cases: .softwarePathCannotRepresentSource (IPT-PQ-c2: HEVC P5, AV1 P10.0) and .demuxedAudioLiveIsNativeOnly. Inside load both guards run AFTER the routing decision, so reaching them on a correction means failing a session that is already down, and a refusal has to cost nothing. Deciding them early needs the base-layer compatibility id to outlive the probe, so it is retained next to sourceDVProfile.

This is narrower than the refusal the report offered to consume: only a flip is refusable, and only for a source that genuinely cannot be served, so the sources that CAN use the escape keep it.

Measured

New --reload-decode-path arm on customio, because the existing --reload-at correction (an httpHeaders probe) is inert on a custom source by design and can never measure this field.

Seekable custom VOD source and the live spool reader:

  RELOAD applying preferredDecodePath=software: playhead=0.00s backend=native
[AetherEngine] #461: reload re-routing this session native -> software on the host's preference (custom source: true)
  RELOAD done: playhead 0.00s -> 0.33s, backend native -> software, decoder=libavcodec H264 (SW)

The same run with the routing change reverted: backend native -> native, decoder=VideoToolbox H264 (HW), under an applied-correction log line.

On live the rebuild keeps #460's reach-back of 0.0 MB. The footprint step across the switch (physFP 25 to 66 MB, then flat at 67 for 140 s, ratio 0.18 and still falling) is the software pipeline's own baseline, not retention; a 100 s run reads 1.31 purely because the slope anchor at 60 s lands on the switch.

Refusal arm, on the Dolby browser test kit because it carries its own control:

# Profile 5 (IPT-only)
  REFUSED state=playing backend=native playhead=0.00s (was 0.00s) decoder=VideoToolbox HEVC (HW)
VERDICT: correction refused, session untouched and still playing
# Profile 8.1, same material and grading
  RELOAD done: backend native -> software, decoder=libavcodec HEVC (SW)

Plain reload arms unchanged, no #461 line, backend native -> native, reach-back 0.0 MB.

10 new unit tests; full suite green on both runners (swift-testing 2595 / 355 suites, XCTest 606 with 0 failures).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4QpZDsHPJfbz5gkxxPXF7

…follow-up)

`LoadOptions.preferredDecodePath` is read at three sites, all inside `load`.
The rebuild that keeps a retained `IOReader` picks its host from the backend
the session was already on, so a host correcting a playing custom source onto
the software path through #460 was told the correction had been applied and
stayed native: accepted, named in the log, ignored. That is the one outcome
#460's own rules forbid, and it left the escape unreachable for exactly the
population it was built for, a spool-backed source with no second engine to
hand a struggling stream to.

The reopen now asks the same routing policy `load` asks, seeded with the
backend it is on. The one-way type is what makes that safe: `DecodePath` has
no `.native`, so the only flip reachable is native to software, and the
software path is the general one; `loadNative` on a software-routed source
stays unreachable by construction rather than by a comment. `keepNativeHost`
and the two post-load fixups follow the target route, not the previous one.

Measured with a new `--reload-decode-path` arm on `customio`, on a seekable
VOD custom source and on the live spool reader:

  RELOAD applying preferredDecodePath=software: playhead=0.00s backend=native
  #461: reload re-routing this session native -> software
  RELOAD done: backend native -> software, decoder=libavcodec H264 (SW)

The same run before this reported `backend native -> native, decoder=
VideoToolbox H264 (HW)` under an applied-correction log line. On live the
rebuild keeps #460's reach-back of 0.0 MB, and the footprint step across the
switch (25 to 66 MB, then flat at 67 for 140 s, ratio 0.18 and falling) is
the software pipeline's baseline, not retention.

What the software path cannot REPRESENT is refused before any teardown, with
two new `SessionReloadRefusal` cases: `.softwarePathCannotRepresentSource`
(IPT-PQ-c2, HEVC P5 / AV1 P10.0) and `.demuxedAudioLiveIsNativeOnly`. Both
guards live after the routing decision inside `load`, so on a correction they
would have failed a session already torn down; a refusal has to cost nothing.
Deciding them early needs the base-layer compatibility id to outlive the
probe, so it is retained next to `sourceDVProfile`.

Verified on the Dolby browser test kit, which carries its own control: the
Profile 5 cut is refused and left playing on VideoToolbox, the Profile 8.1 cut
of the same material and grading is honoured and rebuilds in software.

Reported by @cmcpherson274.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4QpZDsHPJfbz5gkxxPXF7
@superuser404notfound
superuser404notfound merged commit d6f7c7d into main Sep 3, 2026
5 checks passed
@superuser404notfound
superuser404notfound deleted the fix/ae461-custom-source-decode-path branch September 3, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant