Found while measuring #418, and only visible once the clock stopped hiding it.
What happens
When a plan boundary turns out not to be a random-access point, reanchorGateBelowBoundary steps back 4, then 8, then 16, then 32 s and seeks there. Demuxer.seek sets min_ts == ts, so each attempt opens on the first sync sample AT OR ABOVE where it aimed. What the segment actually needs is the LAST sync sample at or below the boundary, and those are not the same sample whenever the backoff overshoots.
Measured on tc-cues-lie.mkv (Scripts/timecode-fixture.sh plus Scripts/mkv-cue-fixture.py, sync samples at 38.417 and 43.0 with a drought to 55.0), resuming at 53 s, boundary 52.000:
boundary 52000 is not a random-access point; re-aimed the gate 4s below it at 48000 (attempt 1/4)
boundary 52000 is not a random-access point; re-aimed the gate 8s below it at 44000 (attempt 2/4)
boundary 52000 is not a random-access point; re-aimed the gate 16s below it at 36000 (attempt 3/4)
video gate open: actual=38417 target=52000 reaimedTo=36000
Attempt 2 aimed at 44.0 and found 55.0, which is above the boundary and correctly refused. Attempt 3 aimed at 36.0 and took 38.417, the first sample above THAT aim. But 43.0 sits between them and covers the boundary perfectly well, so the epoch opened 4.6 s further back than the source required, and the run is offset from the plan by that much extra.
Why it matters now
Until #418 the offset was invisible: the clock claimed the target either way. With the axis published honestly, this is the difference between a resume landing at 39.4 s and at 43.0 s for a viewer who asked for 53 s. The depth of the re-aim is the landing error, one for one.
Shape of a fix
The scan from the re-aimed position up to the boundary already reads every packet in between, so the covering sample is passing through the loop that currently opens on the first one it sees. Remembering the last sync sample at or below the boundary and opening there costs one extra seek and no extra reading. The escalation ladder itself is fine; it is the choice within an attempt that is wrong.
Note the tolerance rule from #408 still applies at the top end (a container index entry is a decode timestamp while the gate judges presentation time, so a well-formed B-pyramid legitimately presents its keyframe a reorder delay above the boundary and must not pay a second seek for it).
Found while measuring #418, and only visible once the clock stopped hiding it.
What happens
When a plan boundary turns out not to be a random-access point,
reanchorGateBelowBoundarysteps back 4, then 8, then 16, then 32 s and seeks there.Demuxer.seeksetsmin_ts == ts, so each attempt opens on the first sync sample AT OR ABOVE where it aimed. What the segment actually needs is the LAST sync sample at or below the boundary, and those are not the same sample whenever the backoff overshoots.Measured on
tc-cues-lie.mkv(Scripts/timecode-fixture.shplusScripts/mkv-cue-fixture.py, sync samples at 38.417 and 43.0 with a drought to 55.0), resuming at 53 s, boundary 52.000:Attempt 2 aimed at 44.0 and found 55.0, which is above the boundary and correctly refused. Attempt 3 aimed at 36.0 and took 38.417, the first sample above THAT aim. But 43.0 sits between them and covers the boundary perfectly well, so the epoch opened 4.6 s further back than the source required, and the run is offset from the plan by that much extra.
Why it matters now
Until #418 the offset was invisible: the clock claimed the target either way. With the axis published honestly, this is the difference between a resume landing at 39.4 s and at 43.0 s for a viewer who asked for 53 s. The depth of the re-aim is the landing error, one for one.
Shape of a fix
The scan from the re-aimed position up to the boundary already reads every packet in between, so the covering sample is passing through the loop that currently opens on the first one it sees. Remembering the last sync sample at or below the boundary and opening there costs one extra seek and no extra reading. The escalation ladder itself is fine; it is the choice within an attempt that is wrong.
Note the tolerance rule from #408 still applies at the top end (a container index entry is a decode timestamp while the gate judges presentation time, so a well-formed B-pyramid legitimately presents its keyframe a reorder delay above the boundary and must not pay a second seek for it).