Skip to content

The plan boundary carries its container's axis, and two things that followed from it (AE#561) - #578

Merged
superuser404notfound merged 3 commits into
mainfrom
fix/plan-boundary-axis
Sep 20, 2026
Merged

superuser404notfound merged 3 commits into
mainfrom
fix/plan-boundary-axis

Conversation

@superuser404notfound

@superuser404notfound superuser404notfound commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Three commits, all from the AE#561 investigation. The first is the reported defect, the other two are what the same question turned up next to it.

1. A plan boundary is compared on the axis its container stamped

A keyframe-aligned plan's boundaries ARE the container's index entries, and containers disagree about what an entry's timestamp means: a mov/mp4 sample table holds decode times, a Matroska Cue holds a presentation time. Since #358 the VOD cutter gate compared decode times against both. Correct for mov/mp4, wrong for every Matroska whose video carries composition offsets, which is every MKV with B-frames.

On those sources no IRAP ever reaches its own boundary, because its decode time sits a composition offset below it. The gate never opens on the planned keyframe, so audio, which is routed by boundary and not gated, opens the segment instead and the IRAP stays in the segment before. Every segment then begins mid-GOP, roughly one IRAP below its own first random-access point, which is what #412 seg-N opens 0.751s below its first random-access point reported on every single segment of the field report.

Nothing in such a segment can start a decode run. Playback survives while AVPlayer decodes THROUGH the boundaries and stops with CoreMediaErrorDomain -19602 the first time it has to decode FROM one, which is why the reporter measured it at specific places in the film rather than after a fixed time, and why the automatic item reload died on the same segment again.

The gate now compares a packet on the plan's own axis (PlanBoundaryAxis, read from the container the demuxer actually opened, never from the URL or host metadata). mov/mp4 sessions are unchanged byte for byte.

One HEVC stream muxed into both containers by stream copy, so the packets and timestamps are identical and the index stamping is the only difference:

first video sample of seg1 and up first IRAP isolated decode
MKV before dependent picture 19 frames in 184 reference errors
MKV after keyframe 0 clean
MP4, before and after keyframe 0 clean

H.264 in Matroska is affected identically, so this is a property of the container rather than of HEVC.

2. The bridge's published origin accounts for the encoder's priming

baseMediaDecodeTime is unsigned int(64), so a negative published timestamp is unrepresentable rather than merely unusual.

The audio bridge stamps the FRAME it hands the encoder, and an encoder that declares initial_padding stamps its first PACKET a padding below that frame, so a consumer that discards the priming lands back on the source position: 256 samples on the AC-3 family, which surround-compat mode reaches for above two channels, and none on FLAC. Nothing discards it here, because the muxer writes no edit list on purpose. At source position 0 the published timeline therefore started negative: -256 went out as 2^64 - 256 and the whole first audio fragment was placed 584 thousand years out, losing its ~190 ms of audio at the start of every bridged multichannel session.

Visible in the reporter's own captures, where the TrueHD track (bridged, EAC3 encoder) carries tfdt 0xFFFFFFFFFFFFFF00 while the same file's DD+5.1 track (stream copy, no bridge) starts cleanly at 0. Reproduced here on 5.1 PCM in Matroska, down to the same value.

The counter now carries the padding, which costs the content 5.3 ms, two orders below the lip-sync threshold and what an unsignalled priming is worth. The FLAC path is unchanged, measured.

3. The boundary tolerance stops paying for a skew its axis does not have

The AE#408 tolerance, which decides when a segment opened so far past its boundary that a second seek is worth it, carried the stream's reorder depth. That term pays for the axis mismatch above, not for anything the stream does, and a Matroska Cue has no such skew. On that axis the tolerance is now the floor, which sharpens the decision on the container AE#408 was reported against; mov/mp4 keeps the term.

The gate's own comparison is deliberately left lenient, and the reasoning is now written down instead of assumed. It answers a different question than the cutter does, being wrong in the strict direction costs the whole restart, and the reported AE#169 geometry has the boundary falling BETWEEN the anchor keyframe's two timestamps, matching neither axis.

Verification

Each fix has a witness that fails without it: PlanBoundaryAxisTests fails on the Matroska arm alone, BridgedAudioOriginTests fails with exactly the reported 18446744073709551360, and the tolerance arms are pinned in Issue408BoundaryRandomAccessTests. Full suite green over both runners, exit 0. Fixtures come from Scripts/fetch-fixtures.sh; the tests skip when they are absent.

🤖 Generated with Claude Code

https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F

…stamped (AE#561)

The keyframe-aligned plan's boundaries ARE the container's index entries, and
containers disagree about what an entry's timestamp means: a mov/mp4 sample
table holds decode times, a Matroska Cue holds a presentation time. Since #358
the VOD cutter gate compared decode times against both, which is right for
mov/mp4 and wrong for every Matroska whose video carries composition offsets,
i.e. every MKV with B-frames.

There, no IRAP ever reached its own boundary: its decode time sits a
composition offset below it. The gate never opened on the planned keyframe, so
audio, which is routed by boundary and not gated, opened the segment instead
and the IRAP stayed in the segment before. Every segment then began mid-GOP,
roughly one IRAP below its own first random-access point, which is what #412
reported on every segment of the field report. Nothing in such a segment can
start a decode run, so playback survived only while AVPlayer decoded THROUGH
the boundaries and stopped with CoreMediaErrorDomain -19602 the first time it
had to decode FROM one, at a position that depends on the encode rather than on
elapsed time. The automatic item reload re-fetched the same segment and died on
it again.

The gate now compares a packet on the plan's own axis (PlanBoundaryAxis, read
from the demuxer's format name, never from the URL or host metadata), so a
keyframe hits its own boundary exactly on either container. The AE#412 reach is
recorded on that same axis, since it is measured against a boundary. mov/mp4
sessions are unchanged byte for byte.

Measured on one HEVC stream muxed into both containers, the stamping being the
only difference: before, the MKV opened seg1 and up on a dependent picture with
its first IRAP 19 frames in and the isolated decode logged 184 reference
errors; after, every segment opens on a sync sample and the decode is clean,
while the MP4 control keeps its existing timestamps. H.264 in Matroska was
affected identically. PlanBoundaryAxisTests pins both arms and fails on the
Matroska arm alone when the gate is put back on the wrong axis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F
Vincent Herbst and others added 2 commits September 20, 2026 19:17
…priming (AE#561)

`baseMediaDecodeTime` is `unsigned int(64)`, so a negative published timestamp
is not unusual, it is unrepresentable.

The bridge's PTS counter stamps the FRAME handed to the encoder. An encoder
that declares `initial_padding` stamps its first PACKET a padding below that
frame, so that a consumer which discards the priming lands back on the source
position: 256 samples on the AC-3 family, which surround-compat mode reaches
for above two channels, and none on FLAC. Nothing discards it here, because the
muxer writes no edit list on purpose and the init segment has to stay
restart-invariant, so the priming plays as the silence it is.

Without the offset the published timeline therefore started a padding BELOW the
source, and at source position 0 that is negative: -256 went out as
2^64 - 256, and the whole first audio fragment was placed 584 thousand years
out, losing its ~190 ms of audio at the start of every bridged multichannel
session. Measured in the AE#561 reporter's own captures, where the TrueHD track
(bridged, EAC3 encoder) carries tfdt 0xFFFFFFFFFFFFFF00 while the same file's
DD+5.1 track (stream copy, no bridge) starts cleanly at 0, and reproduced here
on 5.1 PCM in Matroska.

The counter now carries the padding, which puts the first packet exactly on the
source position and costs the content the padding's 5.3 ms, two orders below
the lip-sync threshold and what an unsignalled priming is worth. Applied on
every rebase rather than only near zero, so a restart mid-file inherits the same
relationship instead of stepping by a padding. The FLAC path is unchanged,
measured: it declares no padding.

BridgedAudioOriginTests fails without the offset with exactly the reported
value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F
…s does not have (AE#561)

The AE#408 tolerance decides when a segment opened so far past its boundary
that going back for an earlier sync sample is worth a second seek, and it
carried the stream's declared reorder depth. That term pays for an AXIS
MISMATCH, not for anything the stream does: the gate judges presentation time
(AE#169 round 3), so a boundary stamped in DECODE time puts a correctly indexed
keyframe a reorder delay above it.

A Matroska Cue is already a presentation time. There a correctly indexed
keyframe presents exactly at its boundary, and the reorder term only widened the
window in which a genuinely late open escaped its re-aim, on the very container
AE#408 was reported against. On that axis the tolerance is now the floor, which
still absorbs a Cue that is approximate rather than skewed. mov/mp4 keeps the
term, because there the skew is real.

The gate's own comparison is left lenient on purpose, and the reason is now
written down rather than assumed: it answers a different question than the
cutter gate does. Being wrong in the strict direction costs the whole restart,
and the reported AE#169 geometry (target 2878501 with the anchor IRAP at dts
2878495 and pts 2878620) has the boundary falling BETWEEN that keyframe's two
timestamps, so it matches neither axis and only the permissive reading admits
it. Since pts >= dts holds for any conforming stream, judging presentation time
cannot starve on a skew in either direction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015PM3xUJB6ZQyqnmGK1fp6F
@superuser404notfound superuser404notfound changed the title The plan boundary is compared on the axis its container stamped (AE#561) The plan boundary carries its container's axis, and two things that followed from it (AE#561) Sep 20, 2026
@superuser404notfound
superuser404notfound merged commit 1b77bbd into main Sep 20, 2026
7 checks passed
@superuser404notfound
superuser404notfound deleted the fix/plan-boundary-axis branch September 20, 2026 17:36
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