File ▸ Scripts gets a Recent Script Files submenu with the ten scripts run
last, newest first: script files run with Run Script File… or a command, and
the installed and bundled scripts of File ▸ Scripts. ScriptUI panels are not
listed. The list is kept in the settings. Each entry runs its script again
through `file.runScript {"recent": i}`, and Cmd+Alt+Shift+D runs the most
recent one; the key can be changed in Edit ▸ Keyboard Shortcuts. An entry
records whether it ran a file or an installed script, so a file named like a
bundled script runs again as that file.
An extension that ships several panels no longer adds one Window menu entry per panel:
`Scripts/ScriptUI Panels/Motion Kit/Animate.jsx` shows as Window > Motion Kit > Animate.jsx,
and a subfolder of `Scripts/` is a submenu of File > Scripts. Top-level scripts are unchanged.
- Script names may be `Folder/File.jsx`: exactly one folder, each part a bare name (no `..`,
`\`, NUL or empty part), never `ScriptUI Panels/...` under `Scripts/`.
- window.scriptPanel, file.runScript, file.uninstallScript, file.scripts.list and
scriptui_panels.json take such names; a panel's tab is titled after its file.
- ConfigStore::list_dirs (DirConfig, MemoryConfig) lists the subfolders.
- menus::dynamic_nodes groups a folder's entries into a MenuNode::Submenu; the egui and native
menus draw it with their submenu code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scripts in Scripts/Startup and Scripts/Shutdown in the settings folder
were never run. The desktop and web apps now run the Startup scripts at
launch, before a project opens, and the desktop app runs the Shutdown
scripts as it quits, after the project is closed. Each folder runs in
alphabetical order with case ignored, and files larger than 16 MiB are
not run. A Startup script that fails is reported in a toast and in the
log, a Shutdown script that fails is reported in the log, and the next
one still runs.
A Startup script that waits in a modal dialog goes on when the dialog
closes, while the next scripts run. A Shutdown script's dialogs close
as if canceled. With the settings folder on disk, each script runs as
its file, so #include, $.fileName and $.evalFile resolve against the
script's own folder.
The new commands file.runStartupScripts and file.runShutdownScripts
return the scripts that ran, those still waiting in a dialog, and those
that failed. Like script.run, both are refused in confined automation
sessions, in batches and in script steps, which now read one list of
script commands (RUNS_SCRIPTS). The list also holds window.scriptPanel
and window.restoreScriptPanels, which run ScriptUI panel scripts and
were not refused on main either. The apps still call
window.restoreScriptPanels at launch. plugins.md, scripting-parity.md,
gaps.md, target-app-parity.md and agents.md describe the folders. Each script
still runs in its own global scope, so a Startup script's variables are
not available to later scripts.
* Translations: delete coverage and wording tests
A missing translation shows in English and never fails a test. Delete the
coverage and per-language parity tests, the tests that pin one language's
wording and the source scans for strings not routed through the catalog.
Keep the stale-row, duplicate-row and placeholder checks, and make the
engine tests that switch language compare against the catalog instead of a
literal. Drop the Traditional Chinese panel rows that only repeated the
English source to satisfy the parity test.
* Translations: missing-strings report in xtask ci
missing_translations_report prints, per language, the fixed menu entries,
panel strings, effect names and effect categories with no row. It always
passes. cargo xtask ci runs it with --nocapture after the test runs, so the
list shows in the gate log. The docs say a missing string shows in English
and where the list is.
* Dev profile: line tables only for workspace crates, no debug info for dependencies
* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going
* One integration-test binary per crate
* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process
* xtask ci: run every stage and report all failures
* xtask ci: name the stage type (clippy type_complexity)
* xtask ci: run every test pass and report all failures
* cargo fmt
Reload Footage probed every item the way File ▸ Import does by default,
so a still such as Photo_2.jpg with Photo_1.jpg beside it was grouped
into a run and measured from the run's first file. The still took
Photo_1's size. Sequence items still probe as a run; everything else
reads its one file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-up to the panel/dialog step: the composition-settings tabs
translate "Basic" and "Advanced" through `tr(variable)` and the Preview
transport translates "Play/Stop", but none of the three had a catalog
row, so they stayed English. Add them to the Japanese, Simplified and
Traditional Chinese catalogs and pin them in
`japanese_rows_translate_the_panels`.
The shared "Type" key means "kind" in the Project column and the Camera
Settings dialog, so the Chinese rows read 类型/類型 instead of 文字
("text").
Keep the graph button bar's automation labels English: translate only
the drawn label and tooltip, and translate the stored English strings
when the hidden-button menu draws them.
Recount the docs from the catalogs: 628 menu rows, 996 Japanese and 998
Chinese panel rows; count Simplified Chinese's 306 effect names in its
numerator (46%) and average the ten After Effects languages the table
actually names (21%, not the stale 27%). Update the measurement basis,
the cross-references in gaps.md and target-app-parity.md, and ROADMAP.
The panel catalog behind the menu bar translated the dialogs and
Preferences, but much of the work area was still English: panel tabs, form
fields, panel context menus, the tracker and graph controls, text-panel
options, the File panel's footers and the credits. Add 300 rows to the
Japanese catalog — and the matching Simplified and Traditional Chinese
sources the tests keep it in step with (the other languages may lag, an
untranslated row falls back to English):
- panel tabs (dock.rs / dock_ui.rs titles), the Project panel's columns,
footer tooltips and column menu;
- form and dialog labels and choice options (the forms the panels open —
import, interpret footage, render queue, trackers, text animators…);
- panel context menus (timeline layer/property menus, graph editor);
- the tracker, graph editor, text-panel, animator and Content-Aware Fill
labels;
- the Preview panel's transport tooltips, credits table headers and sort
options, the composition-settings tabs and the About links;
- the engine's text-animator menu labels, added as a data source like the
settings pages (they are interface text the panels draw).
Strings are wrapped where they are drawn with the existing `tr`/`tr_args`
mechanism (the label helpers already ask the catalog, so the data tables
need no new ids); the catalog tests keep the three complete languages
listing the same sources, reject duplicate or empty rows and keep the
placeholder counts. A new test assertion in
`japanese_rows_translate_the_panels` pins a sample of this step.
The panel catalogs are now 993 Japanese and 995 Chinese rows (the two
Chinese catalogs are the complete source list); with the 628-row menu
catalogs Japanese is 1,621 of ≈ 4,200 interface strings (39%, up from 27%).
Still English: effect parameter names (≈ 1,650 registry names, drawn from
the model, not the catalogs), Home-screen literals, tooltips drawn from
engine data, engine error messages, and the rows other languages lack.
Docs: localization-parity (header, per-language table, overall share,
revision history), ROADMAP (language table, progress log, revision history).
macOS does not install the on-demand PingFang SC (macOS 27 on an English
install ships Hiragino Sans GB, Heiti SC, Songti SC and STSong instead),
and the Simplified Chinese fallback list named only Windows and Linux
families. A Chinese character therefore fell through to the exhaustive
search, which picks the first installed face that covers it — Hiragino
Sans, the Japanese face — so Chinese text in a composition was drawn
with Japanese glyph forms (#453). Japanese kana and kanji were already
handled, and a Chinese-only character the Japanese face lacks (e.g.
页) still found a Chinese face, which is why the symptom looked
selective.
Add the macOS Chinese faces to the fallback lists: Hiragino Sans GB,
Heiti SC, Songti SC and STSong for Simplified Chinese, Songti TC for
Traditional (Heiti TC was already named). The interface language still
chooses between the scripts and an English interface keeps the
Japanese-first order for shared Han ideographs, as designed in #312;
what changes is that a Chinese-locale request has an installed Chinese
family to answer it on macOS.
Tests: a text-layer render test draws Japanese and Chinese text and
checks glyph coverage (skipped where no CJK face is installed); a layout
test checks every CJK glyph comes from a face that has the character, so
a missing-glyph box cannot creep back; and the fonts test fails without
the list change ("Hiragino Sans GB missing from the Han list"), passes
with it, and walks the Han branch's list to prove the first installed
Simplified family is a Chinese face, not a Japanese one.
Verified with CARGO_BUILD_JOBS=2 RUST_TEST_THREADS=2:
effectcraft-text 64 passed; effectcraft-render 233 passed, 1 ignored;
cargo clippy -p effectcraft-text -p effectcraft-render --all-targets --
-D warnings clean; cargo fmt --all -- --check clean. A 900x240 render of
"骨直真具画 中文测试" with the zh-hans request now uses Hiragino Sans GB
and differs from the Japanese render, which still uses Hiragino Sans.
Alt/Option-drag a Project item onto a Timeline layer, or press Ctrl+Alt+/ (Cmd+Opt+/) with layers and an item selected, to swap the layer's source. The layer keeps its name, timing, transforms, keyframes, expressions, effects, masks, switches, parenting and track matte; a source with audio adds Audio properties when missing. Precompositions can't be nested inside themselves.
AVLayer.replaceSource now runs the same engine command and closes the replaceSource part of #813.
kernels.wgsl repeats Glow's bright-pass gain 8.0 and knee 0.0611 as literals; they now
name effects::GLOW_GAIN / GLOW_KNEE (exported) and the test
glow_constants_match_the_cpu (no GPU needed) fails if they drift. docs/effects-parity.md:
the summary row no longer says no effect is compared with After Effects while the
Blur & Sharpen row reports Radial Blur's comparison (CodeRabbit on #1873), and the
Radial Blur changelog row says CPU only (it has no GPU path).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WGSL cannot read Rust constants, so the Rec. 601 luma (Tint, Tritone, Luma Key /
Extract), Black & White's tint weights and Exposure's 2.2 power stay literals in the
kernels; they now name their Rust source (effects::LUMA_601, BW_TINT_LUMA,
EXPOSURE_LINEAR_GAMMA, newly exported) and the test shader_constants_match_the_cpu
(no GPU needed) fails if a literal drifts. gpu_tint_defaults_match_after_effects_greys
says "skipped: no GPU adapter" when it skips (EFFECTCRAFT_REQUIRE_GPU=1 still makes it
fail). docs/effects-parity.md: Brightness & Contrast is close to, not the same as,
After Effects (up to 6.2/255), and the summary row lists what is measured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects' Curves effect interpolates its points with a natural cubic
spline (zero curvature at the ends), clamped to 0..1: AE 2026 renders of an
RGB curve (0,0) (125,199) (255,255) and a Red curve (0,0) (128,44)
(255,255) (cases 962 / 963, ROUND4) give exactly the natural spline's values
on every grey step (0, 30, 59, 115, 164, 202, 227, 242, 250, 255 and 0, 0, 1,
6, 19, 44, 83, 135, 194, 255). EffectCraft used a monotone (Fritsch-Carlson)
spline: 34 / 67 / 125 / 171 ... on the RGB curve, up to 10/255 off (ROUND4
measured up to 6 levels on real projects); now within 1/255, CPU and GPU (the
same Hermite tables carry the spline's knot slopes). Lumetri's curves and
Glow's Arbitrary Map keep the monotone spline (not measured).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With per-character 3D on, After Effects turns each character by Rx * Ry * Rz
(Z first), as it turns a 3D layer's X / Y / Z Rotation (case 931, AE 2026:
"H" at 400 px with animator X / Y / Z Rotation 40 / 30 / 20). EffectCraft
composed Rz * Ry * Rx, so the glyph faced another way: 23 of 5184 grid
points off (max 255), now 3 at the anti-aliased edge (mean 0.015/255). The
same order fix-orientation uses for layers; texts with one rotation axis
are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A text animator's Tracking Amount is in pixels in After Effects 2026, not in
1/1000 em like the Character panel's tracking: Amount 30 on every character
adds 30 px between neighbours (half before, half after each) at 80 px and at
40 px. And a Subtract range selector over characters the first selector
leaves out keeps them at 0 instead of selecting them negatively. With both,
the EXPORTER's t8 layer D (Tracking 30 on characters 2-4 by index, Subtract
over the last 20 %) puts every glyph where AE does (it ended 88 px short,
1553 vs 1641; now 1641).
The "Tracking In" text preset animates 40 -> 0 px (was 400 -> 0 thousandths
of an em, about the same at 100 px text); Lottie export writes a style run's
tracking (1/1000 em) as pixels for its tracking animator, which Lottie
players add in pixels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects 2026 lays out right-to-left point text whose paragraph carries a
justified code (justify last left / centre / right, justify all) flush at
its start, the right, like right-aligned text (EXPORTER read-back t7: point
rows with codes 3-6 at -152..-8 from the anchor). EffectCraft placed it by
the last-line side (left / centre / left). Left-to-right point text and box
text are unchanged; AE's slightly wider right-to-left box wrapping (1-2 px
narrower word gaps) stays open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Justify All After Effects stretches the paragraph's last line too, and
its every-line composer breaks the paragraph so that line is not a lone
word: a 400 px box that the other justifications break into 6 / 6 / 1 words
breaks 4 / 4 / 4 (AE 2026, EXPORTER read-back t6). EffectCraft's composer
left the last line's slack out of its cost, so "twelve" stood alone on a
stretched line; it now counts that slack for Justify All and every word
starts within 1 px of AE's (what remains in that box is the known box-text
baseline offset). Other justifications and the single-line composer are
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects draws a Rectangle Path clockwise from its top-right corner, or
with round corners from the end of the top-right arc on the right edge;
Trim Paths measures from there (AE 2026, case 930: 400 x 300 rectangles,
Trim 0-25 % and 70 / 30). EffectCraft built the path with kurbo, starting at
the top-left corner, so trimmed rectangles drew another segment: case 930
was 86 of 5184 grid points off (max 255), now 5 at stroke edges. Start > End
was already drawn swapped, as in AE. Convert to Bezier now gives AE's vertex
order (top-right first); the engine test follows. Lottie rectangles share
the path and now start where lottie players start them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects draws the Transform effect as position + K * R * S * (p - anchor)
with the skew K = rot(-axis) * shearY(tan skew) * rot(axis) applied after the
rotation, and keeps what lands outside the layer's bounds. EffectCraft skewed
before rotating (shearX about the axis) and clipped to the layer.
Measured in AE 2026 on a grey 960x540 solid (cases 723, 819, 820): 723 (anchor,
position, scale 120/80, skew 10 axis 20, rotation 30, opacity 70) differed in
11.25 % of the frame and now in 0.065 % (edges, max 16/255), CPU and GPU alike;
819 / 820 (skew 15 axis 60, rotation -25, scale 130/70, anchor) match to the
edge. The buffer grows on each side by that side's overshoot (also for the
motion-blur samples), capped at 16 384 px a side and 16 Mpx in all so a layer
moved far away costs a bounded allocation (regression test); adjustment layers
keep their size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects 2026 draws Fractal Noise's Block noise at Scale 100 and
Complexity 1 in cells of 66.67 px (the value changes every 66.67 px along a
row), not 100 px; Soft Linear noise has the matching correlation length.
EffectCraft's lattice is now Scale x 2/3 px (NOISE_CELL), CPU and GPU. Case
904 E03 (Soft Linear, Complexity 4, Scale 100): the noise's autocorrelation
at 32 px was 0.68 (AE 0.41), now 0.54; pixel values cannot match (other
seeds). Still open: AE's noise spreads about 20 % wider around its mean (std
48 vs 40/255 in E03). Existing projects and the Backgrounds presets with
Fractal Noise get finer noise (two thirds of the former cell size).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects feathers both edges of the wiped wedge (the start ray too) with
a Gaussian whose width is constant in pixels: Completion 40, Start Angle 30,
Counterclockwise, Feather 20 (AE 2026, case 904 E12) fits sigma = 0.4 x
Feather = 8.0 px at every radius (mean alpha error 0.0002 over the frame).
EffectCraft ramped only the moving edge, linearly over Feather degrees, so
the edge was 14 px wide near the centre and 140 px wide at 400 px out, and
the start edge was hard: 378 of 5184 grid points off (max 111/255, 6.6 % of
pixels > 8), now 1 (max 3), 0.009 % of pixels > 8 (near the centre). CPU and
GPU (the kernel now measures distances to the two rays). 0 % and 100 % are
unchanged, so the three Transitions presets keep their end frames; their
Feather (6 / 10) now means pixels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects measures the blinds' phase from the layer's centre and softens
both edges of the transparent band with a Gaussian of sigma = Feather / 2; the
band runs from the origin's phase to Completion x Width. EffectCraft measured
from the buffer corner and ramped only one edge linearly over Feather (a hard
edge on the other side). Fitted to AE 2026 renders of Completion 40,
Direction 30, Width 50, Feather 5 on a 960 x 540 and a 480 x 270 layer (the
origin is the centre + 0.6 px): case 715 was 499 of 5184 grid points off
(max 91/255, 7.1 % of pixels > 8), now 28 (0.056 %), all within ~6 px of the
layer edge where AE differs (up to 48/255, not modelled); away from it within
6/255 (test: one AE row of the 480 x 270 render). CPU and GPU (new erf-based
kernel, same term count). Completion 0 / 100 are unchanged, so the
Transitions presets keep their start and end frames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects' Radial Blur keeps a layer's edges: Zoom 20 on a flat grey 960x540
solid over red (case 713) stays flat and opaque to its edge, and a zoomed
full-frame ramp (case 829) keeps the frame opaque (2/255 at x = 2, 253/255 at
x = 1917 on row 540). EffectCraft sampled transparency outside the layer, so its
edges faded and the red below showed through (713: 3.9 % of the frame off, max
64/255). Now 713 is identical to AE (max 0/255), CPU and GPU. CC Radial Blur
(already matching AE) is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Glow Colors = Original Colors, After Effects thresholds each channel of
the straight colour separately and the glow is 8 x (value - threshold +
0.061) per channel (times Intensity), nothing below: fitted to AE 2026
renders of grey 0.65 / 0.8 / 0.9 / 1, orange (1, 0.5, 0) and (0.2, 0.6, 1)
solids at Threshold 40 / 60 / 80 % and Radius 10 / 30 (36 channel values,
exact to the 8-bit capture; an orange solid glows red only). EffectCraft
scaled the colour by a luminance threshold (k = (L - thr) / (1 - thr)), about
3.7 x too dim on white. Cases 806 / 810 / 811 (32 bpc): outside the layer
now within 3/255 of AE (806 white at Intensity 1: 119 = AE, was 32); grid
0 points off for 806-810. The glow's alpha (the blurred layer alpha) and the
Arbitrary Map / A & B colours and alpha-based glows are unchanged.
Stacked on fix-glow-halo (same lines). Not changed: Composite Original's
semantics are swapped against AE's names (EffectCraft's "On Top" adds the
glow over the original, which AE calls "Behind"; AE's "On Top" keeps the
original over the glow), so imported AE Glows at the default Behind show
no glow inside the layer (811 interior 204 vs AE 255).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Glow Based On Color Channels, After Effects gives the glow the layer's whole
alpha (blurred), not the thresholded part, and Intensity scales only its colour:
a layer below the threshold darkens what lies under its edge. Fitted to AE 2026
renders of a grey solid over red (cases 524, 807-809): the halo is the same at
Intensity 1 and 3 and with Composite Original Behind or On Top, and its edge is
an erfc step of sigma = 0.384 x Glow Radius at radius 10 and 30 (was radius / 2).
524 (Glow defaults on a 50 % grey solid): EffectCraft had no halo (max 116/255
off at the edge); now red 248/234/208/165/140 at 7.5/5.5/3.5/1.5/0.5 px outside
vs AE 249/235/206/164/140, max 2/255 over the frame, CPU and GPU. The blur
width applies to every glow; alpha-based glows keep their coverage rule
(thresholded alpha, scaled by Intensity). Not matched yet: the brightness of glows above the
threshold (AE about 3.7 x brighter for a white solid, cases 806, 810, 811).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>