Resolve the conflict in crates/gpu/src/fx_stylize.rs: main moved ops::warp to
the ops::WarpOptions parameter struct; the branch's per-sample transform loop
(motion-blur samples, now passing the sample by reference) uses the new
signature with the branch's reference argument kept.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
dominant_direction weighted texels by their raw channel sum before the
light's colour tinted the radiance, so equally bright red and blue regions
on opposite sides cancelled to no sun and no key — while a red Environment
light's tint removed the blue contribution and left the scene lit
directionally red, with no shadow for its Cast Shadows to show. The tint
now weights the direction sum (a white light weights [1; 3], a coloured
one its normalised colour), so the sun and key are derived from the
radiance the light actually emits; add_environment computes the tint once,
before the direction pass, and reuses it for the texel tinting. An
achromatic image is unaffected (its weights scale uniformly), so the
default studio and white-lit scenes keep their shadows exactly.
Tests: opposed red and blue texels cancel to a white light but a red tint
points the sun straight at the red one (a blue tint, at the blue one); end
to end, a red Environment light over an image with equally bright red and
blue regions on exactly antipodal positions casts the shadow a white light
over the same image does not — and the shadow falls opposite the red
region, the side the tinted radiance comes from.
Two review points on the merge:
- env_pixels returned None both for an unconfigured light (no Source, no
Environment Layer) and for a failed source (a deleted layer, missing
footage, a failed decode), so a selected source that failed silently
swapped in the default studio environment — relighting and reshadowing
the scene behind the user's back. It now tells the three apart
(EnvPixels::Absent/Image/Failed): only the unconfigured light uses the
default; a failed source contributes nothing, exactly like a layer whose
source fails renders nothing. The repair the user can act on stays where
it already lives — missing footage is flagged on the item, with the
relink commands to fix it.
- The docs claimed only a uniform image casts no shadow. A non-uniform one
can be directionless too: opposing bright regions cancel, and anything
under 1% directional is rejected. agents.md, architecture.md and the
sun/dominant_direction doc comments now say an image without a
sufficiently strong dominant direction.
Tests: an unreadable Environment Layer (auto-picked), an unreadable
selected Source and a deleted one all light nothing — pixel-equal to the
unlit render, no studio default, no default-environment shadow — while an
unconfigured light still lights; two equal bright spots at opposite
azimuths cancel to no sun and no key, and tilting the balance re-emerges
pointing at the brighter spot.
The default studio environment's level-preserving mean of 1 acted like a
100% white Ambient light: with only the Environment light on, a white floor
rendered at full white (95% of the frame saturated) while its shadow core
went to pitch black — a fog over the scene, nothing like After Effects'
default. Three changes bring the look in line, CPU and GPU alike:
- The default environment is a dim studio now: a solid-angle-weighted mean
of 0.3 (and a stronger key gradient), so a white floor reads a mid gray
and the light's colour x intensity still scales the level.
- The shadow occludes only the image's directional (key) fraction of the
light — |sum of dir x radiance| / sum of radiance, computed beside the
dominant direction and carried in the scene — so the fill keeps shining
inside the shadow: an area-light shadow, partial like After Effects'
(a lone bright spot on black still shadows almost fully; a uniform image
not at all).
- The environment shadow map's penumbra has a floor — eight output pixels,
or four map texels where those are coarser — because the map covers the
whole scene bounding sphere rather than the view, and its texels would
otherwise stair-step the edge (all the more in a draft render's flat 512
budget). Shadow Diffusion widens it further, as before.
Tests: the key fraction tracks directionality; the default keeps its dim
level, its direction and its key; the default and a bright key both shadow
the floor (the default partial, the key deep) with Cast Shadows off lit
everywhere; and the full look — a mid-gray lit floor (no fog), a partial
core, and a soft edge ramp in quality and draft renders alike.
After Effects' default environment map casts shadows out of the box: an
Environment light with Cast Shadows on shadows the scene with no Source
set. The flat default cancelled its dominant direction to zero, so the
option did nothing until a Source image arrived. Give the default a soft
studio gradient (bright above and toward the initial camera, dark below
and behind), level-normalised so the light's colour x intensity still
sets the overall brightness; its dominant region drives the existing
shadow pipeline, CPU and GPU alike.
The Casts Shadows option Environment lights got is only half a fix if
nothing shades for it: an image lights the scene from every direction,
and what occludes that light is the image itself — its dominant bright
region, the sun in a sky map.
- dominant_direction finds it: the radiance-weighted mean texel
direction (solid-angle weighted, rotation applied). A uniform image
cancels to zero — a flat default environment shines from everywhere
at once and casts no shadow, which a uniform environment test pins.
- The shadow is one orthographic map over the scene from that
direction, after the lights' maps, like a parallel light's: PCF
widened by Shadow Diffusion, darkened by Shadow Darkness, cleared
when nothing casts, the image is directionless, or the texel budget
drops it.
- It darkens the environment's own diffuse and specular light on
Accepts Shadows surfaces and counts in Cast Shadows Only. CPU and
GPU (WGSL) do the same; Globals carries the environment's sun
direction and shadow settings (40 → 44 floats, preflight updated).
A bright-disc environment turned to shine from behind the camera now
shadows a sphere onto the floor behind it, Cast Shadows off lights it
evenly again, and the GPU scene's sky gradient takes a shadow-casting
Environment light. Also: the renderer-switch toasts read "...renderer,
which draws 3D models", the dialog's Casts Shadows checkbox gets an
auto id (dialog.light.casts) the tests click, and the docs' Environment
notes cover the shadow.
An Environment light had no visible effect, and its Light Settings dialog had
no Casts Shadows option — both halves of #2436.
No effect had two causes:
- Classic 3D (the default for new comps) skips Environment lights entirely,
so creating one did nothing. Adding one (or changing a light's type to it)
now switches the comp to Advanced 3D in the same undo step, with a toast,
exactly as model and primitive layers already do; switching back to
Classic 3D with an Environment light in the comp explains itself too.
- In Advanced 3D, a light with no Source and no Environment Layer produced no
environment at all. It now falls back to a flat default environment of the
light's colour × intensity, as After Effects does, so the light always
lights the scene per its parameters.
Cast Shadows was missing because the dialog and the light's property groups
shared one gate (Falloff and Shadows together) that excluded Environment.
The two are now split: every light but Ambient gets Casts Shadows, Shadow
Darkness and Shadow Diffusion (stored, edited and carried over on type
changes), while Falloff stays on Parallel/Spot/Point only — an environment
light shines from every direction at once and has no use for a falloff.
Tests: the renderer lights a card from a sourceless Environment light (colour
and intensity drive it, zero intensity lights nothing), the engine switches
Classic comps to Advanced 3D on creation and type change (one undo step,
toast, and the Classic 3D switch-back toast), Environment lights take and
carry shadow options but no Falloff, and the dialog ticks Casts Shadows on
a new Environment light.
craft-libs crates move to one rev, 37c907d2 (contains main's craft-update and craft-docpath pins).
Docs: native floating windows are Windows, macOS and X11 only; the UI estimate drops the
multi-monitor panel work that is done.
- Default's right column is a short Preview frame (fixed 76 pt: tab strip and a 46 pt body)
over a Properties frame with Align, Audio and Effects & Presets as tabs. Essential Graphics'
right column is one Properties / Effects & Presets frame. No built-in workspace uses
accordions, so a frame there takes tabs and Maximize acts on one frame.
- Workspace files of version 1: a column of stacked frames becomes tab groups split top to
bottom. Each open frame keeps all its tabs and its height as a fixed size; a closed frame's
tabs join the open frame above it.
- Version 1 stored a native panel window's outer position; craft-ui places windows by their
content. The migrated rect moves by the main window's frame offset once the main window
reports it, and workspaces are not saved before.
craft-ui's native-windows feature is on for every non-wasm build. Keys and
dropped files from a floating window reach the main window's next frame, and
while a modal dialog is open the dock area blocks input in floating windows.
Move Panel to Main Window docks the panel again. The web, Wayland and embedded
viewports keep floating windows in the main window.
Every craft-libs crate and the hayro-syntax and hayro-jbig2 patches are
pinned to storytold/craft-libs 2869b8cc. The workspace is a craft-ui
Workspace<PanelKind>: craft-ui draws the frames, tab strips, stacked
panels, floating windows and drop previews and owns the gestures.
EffectCraft keeps PanelKind, its built-in workspaces, the panel bodies,
the Timeline's comp tabs, viewer locks and the panel menu in each group
header, and the rule that no move or float empties the docked area.
window.dockAction applies craft-ui Actions; craft-ui's window.panel.*
commands, ui.dock and ui.dock.reset parse as DockCommand. Saved
workspaces are version 2 snapshots of craft-ui Workspaces; an earlier
file loads as the default layout and is left untouched.
Reconcile main 991904888a and preserve the reviewed lifecycle, IME, rendering and lockfile changes.
Formatting, strict lint, layers, assets and WASM passed. Full CI retained 3,961 passes, 14 baseline-matching GPU failures and 31 ignored executions. The local review commit has explicit approval to retain these documented failures; native/browser acceptance and paired app performance remain open.
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.
* 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>
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>