read_aepx, read_aepx_with_limits and parse_any build the owned chunk
tree element by element again, as before the .aepx index, instead of
converting the index (which held the decoded buffer and the owned tree
at the same time). Import keeps the index reader. The tests now
compare the two production readers.
The .aepx reader no longer builds the owned chunk tree element by
element. It decodes every body (hex, text, alias JSON, numbers) into
one buffer and records the chunks as depth-first entries whose bodies
are ranges of that buffer; TreeIndex::from_drafts turns them into the
index the importer reads, which borrows the buffer. read_aepx converts
that index into the owned tree in one pass.
The tree and the errors are those of the previous reader, which the
tests keep as the reference: generated and damaged documents under
seven limit sets, invalid UTF-8, hostile input, the fixture directory,
and imports through both. Bound tests pin the chunk list to the input
and the limit, and check that dropped content leaves nothing behind.
The container's `Pard` is now the only decoder of parameter definitions:
`EffectParam` delegates to it and the export's test reference reads its
encoded table back through it (no big-endian readers of its own). The
export stores a checkbox default as on/off, as the importer reads it.
Inside a strongly connected component of candidate precomp links (a file
with a precomp loop), the per-composition searches share one budget of
link visits; past it, a layer that would need a search is refused and
reported, which never lets a loop in. Below the budget the decisions are
the old ones exactly.
Import: precomp loop check by candidate-graph components (no search
without a loop), one LayerId -> index map for mattes, parents and effect
layer references, popup mapping linear in the file's items, lazy loss
details (lose_with), orientation keys at one time reported, mask path
ease speeds under the property limit, Blending Options nested past the
read depth reported.
Export: precomp loops by components, parent loops found once, cached
natural sort keys, one exported-layer set, parameter definitions built
from the effect table (old encode-and-parse path kept as a test
reference), shared key-time loop, key-list rewrite, spatial record,
tdmn/tdsn helpers, unreadable curve and mesh entries reported.
Corpus import and export dumps are byte-identical to the base.
Import reads an .aep through TreeIndex, one depth-first array of chunks that
borrows the file's bytes, instead of building and freeing the owned Chunk tree
(one Vec per list). The parser mirrors read::Parser step for step: the same
chunk count order against max_chunks, the same depth limit and opaque
fallbacks, the same errors. The typed view reads through Node, a handle to a
chunk of either tree, so the accessors work unchanged on both; .aepx keeps its
owned tree. Chunk, parse and write stay for export and editing.
The array starts small and doubles, never past what the input can hold or the
chunk limit allows; a failed allocation is TooLarge, a tree past u32 indices
TooManyChunks.
decode_hex reserved the decoded size before it knew the value decodes,
so a large bdata with a bad digit reserved half its length first. It now
refuses a value larger than a chunk's 32-bit size field (TooLarge), checks
every digit in one vectorisable pass, and only then reserves, with
try_reserve_exact so a value that does not fit in memory is an error
instead of an abort.
The cached natural keys were Vecs of (bool, String, u128) segments: a 255-byte
name of alternating letters and digits took about 14 KB, held for every item at
once. Each key is now one byte string whose byte order is the natural order,
at most 5 bytes per name byte. Order and output are unchanged.
- Remove 35 public container accessors that only tests called (header,
camera/light, keys, footage, masks, shapes, version) and the dead
StyleMap::switch_name. Tests now check the importer's own reading of the
same bytes, or the decoder it uses; three import tests gain the values
the removed checks covered.
- Import and export reports share one loss entry (Loss<C>) with record,
total, count and summary; the JSON shape is pinned for both.
- Collector::lose_with builds a detail only below the detail cap (callers
switch later).
- The export uses the container's TimeBase; one NTSC table serves import
and export; TimeBase::frames saturates like units instead of returning 0.
* Preview: the sound plays on through stalls, the picture drops frames (#2473)
A preview whose rendering fell behind stopped its sound at the first frame
that wasn't cached and started it again once frames were cached, which
chopped it into bursts. Now the sound plays on: the picture shows the latest
frame rendered up to the sound, and the prefetch and the cache's eviction
order follow the sound's frame rather than the picture's.
The stall backoff from #3272 no longer has anything to do and is removed.
The start of a preview still waits for its first frames (#947).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Preview: a picture behind its sound never shows past the sound's frame
`latest_cached_before` stopped only on the sound's exact frame, which a
picture off the plan's frame step steps over and a sound behind the picture
in a non-looping preview never reaches, so it could return a frame ahead of
the sound. The walk now stops at the first frame that isn't between the
picture and the sound in play order (across the loop's end once the sound
has wrapped). Also brings `Playback::starting`'s doc up to date.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
text_document reads the first btdk in the Source Text record, the documents
text_document_count counts. The old slot checks (same, is_neutral, undecoded_fields)
join the test oracle and are compared on 20 000 generated slot dictionaries; mixed
dictionary/array chains and a document left open at the depth limit join the parser
differential. An escaped string's byte order mark is drained without a copy.
- tree::Fixed replaces tree::fixed: a fixed layout writes every field that
fits (as the old code did) and keeps the first error; finish() logs it
once with the layout's description and counts it per thread, so the
every-layout test cannot race with other tests.
- base::bytes logs one line naming its length and fields.
- masks::build_atom errors when the prototype has no Mask Shape instead of
dropping the path of every mask quietly.
- log sorted into the dependency list.
In masks, shapes, styles, synth, fxsynth, base, camlight, skeleton,
textanim and tree (effects.rs, plan.rs and emit.rs keep their copies for
the follow-up work):
- New export/keys.rs: the key-time loop (timed_keys), the key-list rewrite
(fill_key_list, set_key_list, replace_cdat) and the spatial key record,
shared by masks, shapes and styles.
- One tdmn decoder (synth::tdmn_name, lossy UTF-8 up to the first NUL, as the
importer reads it) and one encoder (synth::tdmn) instead of the copies in
masks, styles, shapes, textanim, camlight and skeleton.
- One byte writer: tree::put, which errors. base::bytes, synth::tdb4,
synth::key_list, fxsynth::pard, masks::shph and the keyed path records write
through it; their fixed layouts keep the infallible signatures behind
tree::fixed, which logs, and a test builds every layout and requires no error.
- camlight::put_group uses the shared group split (tree::split_group), which
errors on a match name without a node instead of stopping quietly.
Export output is byte-identical on the corpus and the synthetic fixtures.