Skip to content

fix(video): recover the axis when a picture is not a whole number of ticks long (#409) - #427

Merged
superuser404notfound merged 2 commits into
mainfrom
fix/ae409-rational-cadence
Aug 25, 2026
Merged

fix(video): recover the axis when a picture is not a whole number of ticks long (#409)#427
superuser404notfound merged 2 commits into
mainfrom
fix/ae409-rational-cadence

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

What this fixes

@orut34iop retested the released #413 + #415 repair on the original reporting asset and it still juddered from the first frame. The diagnosis in that retest is correct and this change is built on it: the asset is constant rate, but a picture is not a whole number of ticks long, so its sample table can only alternate between the two neighbouring counts (200202/5 ticks at a 1200000 timescale, cycling 40040/40041). The classifier demanded one identical step, read that as variable frame timing, and fell closed. Zero packets repaired.

Reproduced here before touching anything, on a fixture pair built the same way the first pair was: the engine passes the whole file through with PTS == DTS, every picture still in decode order.

The change

A two-valued ladder is read as the quantization it is.

  • The cycle names the fraction. The smallest period the sampled deltas repeat under gives the cadence exactly, and a cycle only counts once it has been seen through twice, so a short run of jitter cannot pose as one.
  • The rounding pattern names the phase. This is the part a whole-tick ladder cannot carry and a quantized one can, and it is not implied by anything else: the reporting asset sits on phase 2 while its reorder delay is 1. Exactly one phase of the period can reproduce the sampled window picture for picture, and if more than one does, or none, nothing is repaired.
  • Ranks are placed on the lattice rather than on anchor + index * step, so the repair reproduces what the muxer would have written instead of landing a tick beside it. A whole-tick ladder takes the same path it always did, byte for byte.

Reading the phase has a second effect worth naming: the verdict no longer depends on where the sample was taken. Measured before the change, sampling the same file after a seek classified it as unrepairable; now a session that starts inside the file describes the same axis as one that starts at byte 0.

What deliberately does not change

  • How far the ladder runs ahead of presentation still comes from the container header. Every alignment fits the ladder equally well, so the ladder cannot answer that question, and presentationShift already answers it from start_time with a clamp that bounds the damage to one reorder head. The lattice work only requires that the answer resolve to a whole number of pictures.
  • The container index is still folded by one constant. Index entries and the packets they point at therefore cannot disagree, which is what keeps a segment boundary on its keyframe.
  • A picture the lattice cannot place falls back to the rounded step, which still describes the stream to within a tick. Nothing is handed on in decode order because an arithmetic path did not close.
  • Variable frame timing, a ladder with a dropped picture, and a wobble that never repeats are all still left exactly as the container delivered them, with tests to keep them that way.

Verification

A fractional twin pair, built the way @orut34iop built the first one: one encode at 1000000/33367 fps in a 1200000 timescale muxed twice, the composition offsets stream-copied away from the second. 33 packets, three coded video sequences, both writer shapes (edit list retained and lifted to non-negative timestamps).

  • Every repaired packet carries the healthy twin's PTS and DTS exactly, from the head and after a seek to the second and third sequence.
  • The whole-tick twin pair from fix(video): recover H.264 with missing composition offsets #411 still matches exactly, unchanged.
  • swift test: 2091 tests in 296 suites, green, run against a checkout with Fixtures/ present so the file-backed suites actually execute.

Closes nothing on its own: #409 stays open for @orut34iop's retest on the original asset.

superuser404notfound and others added 2 commits August 25, 2026 21:18
#409's repair reads a rank out of the bitstream and puts it back on the ladder the
container wrote, and it needed that ladder to advance by one constant. A constant
frame rate does not always produce one: at a 1200000 timescale the retest asset's
pictures are 200202/5 ticks apart, so its sample table can only alternate between
40040 and 40041, and the classifier fell closed on a ladder it read as variable
frame timing. The file stayed exactly as broken as it was reported.

A two-valued ladder is now read as the quantization it is. The cycle it repeats
names the fraction, and a cycle counts only when it has been seen through twice.
The pattern it rounds to names the phase of the lattice it was quantized from,
which is the one thing a whole-tick ladder cannot carry and this one can: the
reporting asset sits on phase 2 while its reorder delay is 1, so the phase is not
implied by anything else and has to be read. Ranks are then placed on that lattice
instead of on a step, which reproduces the muxer exactly rather than a tick beside
it, and the whole-tick ladder stays the special case it always was.

Reading the phase also makes the verdict independent of where the sample was
taken: a session that starts inside the file now describes the same axis as one
that starts at byte 0. Measured before the change, a sample taken after a seek
classified the same file as unrepairable.

What does not change: how far the ladder runs ahead of presentation still comes
from the container header, because every alignment fits the ladder equally well
and only the header can decide; the container index is still folded by one
constant, so an index entry can never disagree with the packet it points at; and
a picture the lattice cannot place falls back to the rounded step rather than
being handed on in decode order. Variable frame timing, a ladder with a dropped
picture, and a wobble that never repeats are all still left untouched.

Verified against a fractional twin pair built the way @orut34iop built the first
one (33 packets, three coded video sequences, both writer shapes, from the head
and after a seek): every repaired packet carries the healthy twin's PTS and DTS
exactly. Full suite 2091 tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbWZLwBVLGM1xUVXa9NeJ1
The property that separates placing a rank on a lattice from requiring the ladder
to sit on one: when a coded video sequence opens beside the lattice, the rounded
step still describes the stream to within a tick and anchors on the container's
own timestamp, so it cannot drift. Handing the picture on with its raw timestamps
would be the defect returning mid-file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbWZLwBVLGM1xUVXa9NeJ1
@superuser404notfound
superuser404notfound merged commit 69daa3d into main Aug 25, 2026
4 checks passed
@superuser404notfound
superuser404notfound deleted the fix/ae409-rational-cadence branch August 25, 2026 19:37
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