Compare device and inode on Linux alongside existing path checks. Cover
template and values aliases with input-preservation and diagnostic checks.
Sort the existing footage test's expected paths for temporary-directory portability.
Co-authored-by: Codex <codex@openai.com>
After Effects' Liquify shows Distortion ▸ Offset (x, y), and the renderer
already applied distortionMeshOffset — sampling the mesh at layer
coordinates minus the offset — but the parameter was declared hidden, so
users could neither see nor edit it, and #1194 read as 'the effect does
not work and the Offset option is missing'.
It is now a visible 2D Point control (still defaulting to 0,0, still
scriptable by its old path). Regression test: the spec exposes it as a
Point, a [20,0] offset moves the displaced-region centroid right by the
offset (the render differs, and the left of the shifted mesh is back to
untouched pixels), docs/effects.md refreshed via UPDATE_DOCS.
The reported flow — launch to the start screen, Open Demo Project, press
Space — does not reproduce on main: the demo comp activates, the start
screen closes, and playback starts. The behaviour already held (the
#279 menu-focus and #204 start-screen work fixed the neighbouring bugs),
so this lands as a regression test:
space_previews_after_opening_the_demo_from_home walks the exact flow
headlessly and asserts playing=true.
cargo test -p effectcraft-ui-egui --test ui_menus: 8 passed.
Looped *video* footage had a regression (#1045); a grouped PNG sequence
going through the same path — interpret a fresh placeholder to Loop 15,
drag it onto the timeline — did not. The behaviour is already correct on
main (addItem sizes a sequence layer from looped_duration, the pool's
frame_index wraps per cycle), so this is coverage, not a change:
interpret_footage_loop_extends_sequence_layers asserts a 15-loop of a
0.4 s sequence lands a 6 s layer.
cargo test -p effectcraft-engine --lib interpret_footage: 4 passed.
With the comp's and the layer's motion-blur switches on, the renderer
hands effects the shutter (angle, phase, samples) — CC Particle World
now uses it: eight to sixty-four sprite fields sampled across the shutter
are averaged into the transfer, so fast particles draw the motion trails
After Effects shows instead of one sharp frozen field.
The GPU particle pass defers to the CPU while the shutter is open
(catalog::gpu_supported), keeping the oracle identical on both backends.
Tests: sim::particle_world_motion_blur_trails (blurred differs from
sharp, lights more pixels, keeps alpha energy) and a catalog assertion
for the CPU gate; effects 450 and gpu 212 lib tests pass; fmt and
clippy -D warnings clean.
On a selected layer's row, the Parent menu and pick whip and the Track
Matte menu now set every selected layer's parent or matte, as moving a
selected layer's bar moves them all; on an unselected row they set that
layer's alone. Each is one undo step. A selected layer chosen as the
parent or the matte is left out of the layers it links, and parenting
that would make a loop is refused as a whole.
A new matte layer keeps each layer's mode (layer.setTrackMatte with
kind: preserve); a mode and No Track Matte apply to all of them.
The Track Matte column gets a pick whip, like the parent's
(timeline.layer.<id>.trackMattePickWhip, with a translated tooltip):
dropped on an audio-video layer's row, it makes that layer the matte.
The layers a parent or matte pick whip links are taken when its drag
starts.
Tests (ui_batch_links): the Parent menu and pick whip, and the Track
Matte menu, mode, No Track Matte and pick whip, with real clicks and
drags: selected layers change together and unselected ones don't, modes
are kept, one undo puts links, compensated transforms and the matte's
visibility back, and a refused cycle changes nothing. Both fail on main.
layer.setTrackMatte took one layer. It now also takes `layers`, set in
one undo step; `layer` and the first selected layer still work as
before. `kind: "preserve"` keeps each layer's own matte mode (Alpha for
a layer without one), so giving several layers a new matte doesn't turn
a mix of Alpha and Luma mattes into one mode; without `kind` the mode
is Alpha, as before. A layer can't be its own matte and an empty list
is refused, before anything changes.
Test: several_layers_take_one_matte_and_keep_their_modes.
File > Dependencies > Find Missing Fonts listed a text layer whenever
its font matched no family name, while text layout finds a font by its
family, PostScript or full name (effectcraft_text::resolve). For a
layer set to the PostScript name Inter-Bold, EffectCraft draws the text
in Inter Bold, and text.fontFeatures reports the layer's font as not
missing. Find Missing Fonts listed that layer and a layer set to the
full name Inter SemiBold.
find_missing now lists a layer when effectcraft_text::resolve, which
text layout uses for each run, reports the layer's font as missing.
fonts_named_by_their_postscript_or_full_name_are_not_missing adds text
layers in Noto Serif, Inter-Bold, Inter SemiBold and a font that no face
has, and checks that Find Missing Fonts lists only the last one.
A Merge Paths item appended to a shape layer's Contents cleared every draw
collected so far, including fills and strokes inside groups above it, so
the layer's shapes vanished from the render. AE's rule is per group: only
paint items in the merge's own group above it are dropped. Draws now
record which contents level created them; the merge drops only its own
group's paint, and the group-bubbled draws keep rendering.
Adds a regression test (Merge Shapes and Add modes) that renders nothing
without the fix.
The pinning regression only exercised All Edges (and Left Edge in the CPU
unit test), so the reported 'does not work with Top/Bottom Edge' had no
coverage to catch or clear it. This holds the pinned first/last row in a
CPU test at Direction 90, and adds CPU/GPU parity cases for Top (4),
Bottom (6), diagonal directions, and single-edge pinning on shape layers.
CPU pinning verified correct on main: the guards in dst_wave_pin/wave_pin
agree; the gap was that neither edge path was tested.
- verified: 0.9.2 on every page, checked against the v0.9.2 tag and release assets
- install, price-and-platforms: Windows x86 builds, riscv64 deb/rpm, macOS CLI zip
- gpu-not-used: GPU Information now names the adapter or why the GPU is off;
OpenGL means CPU compositing; #613 still open
- import-footage: TGA, HDR, DPX, Cineon sequences; dropped PSDs ask; drop on
an empty viewer makes a comp
- render-a-video: QuickTime (Uncompressed) format
- make-a-composition: Layer > New > Text starts typing; save and delete presets
- CLAUDE.md: regular file instead of a symlink
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: on Windows two tests failed on main although the app is fine; they assumed
macOS/Linux path formats.
Fix: the CLI test strips the verbatim path prefix that canonicalize adds on Windows (a
child's current_dir() never reports it); the host sink test uses a drive-letter path on
Windows (a path rooted without a drive is joined to the home drive by the engine, the test
expected the current drive). Test code only.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upstream's #1167 fix and this branch both stop marker edits from invalidating
cached frames. Kept this branch's shared Project::reads_markers/same_pixels_with/
pixel_form_with (viewer frames, disk cache, nested comps) and folded upstream's
rules into them: any expression counts as reading markers, protected time
markers with a duration still change the frames.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The startup hold waited for the current frame to be cached before
anything else, so a frame that can never be cached held the first frame
for ever: a zero RAM preview budget, or a single frame larger than the
budget (which the comp-size estimate misses for Extended Viewer frames).
It now gives up at once when the budget can't hold even one comp-sized
frame, and otherwise after STARTUP_WAIT (2 s from pressing play): the
sound starts from the first frame, and the preview goes on as a stalled
one does. frame_bytes() is factored out of frames_that_fit() for it.
Tests (ui_audio_scrub): with a budget of 0 or half a frame the sound
starts on the first step; with an Extended Viewer frame larger than the
whole budget (the estimate says one fits) the first frame holds for about
two seconds, then the sound starts. Both wait for ever on the previous
commit. The Extended Viewer setup is now shared by its two tests.
- Keyboard Shortcuts: Enter is not Delete while the delete confirmation is open (#1113 with #1019).
- Speed graph drag test compares proportions, since the Graph Editor now fits its selection (#1182 with #1109).
- Spanish panel test no longer demands the Chinese catalogs' exact rows (#996 with #1189, #1019).
- Object Matte gets its Simplified Chinese effect-name row (#1199 with #792).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Cryptomatte (#1183): planes shorter than the image read as empty instead of panicking.
- layer.addMarker (#1180): a comp marker no longer replaces one at the same time.
- Desktop (#1005): ignore macOS's -psn_ argument instead of refusing to launch.
- Doc comments put back on their own items (#1153, #1037).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The six PSD PRs merged one by one leave #1131's test calling samples()/to_rgb(), which #1139
replaced; vangel76's integration draft #1149 has the adapted version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nightly FreeBSD run measured 119 ms/frame against a 100 ms limit: the mean included
Tracker::new and any scheduling stalls on the two-core VM. The best single step with a
250 ms bound still catches an order-of-magnitude regression.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>