Evaluate the third cube coordinate symmetrically to retain the intended centerline tie. Keep the existing pixel oracle and tolerance.
Scope the Mem fixture mutex unwrap allowance so the original strict CI can validate this base.
Co-authored-by: Codex <codex@openai.com>
CPU renders kept one frame per thread in flight with no cap, so heavy comps
could be OOM-killed. EFFECTCRAFT_CPU_INFLIGHT=N renders at most N frames at
once through the existing capped path; unset keeps today's behaviour. With a
GPU cap too, the smaller cap holds for the whole batch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quit EffectCraft and Finder's Quit send `ViewportCommand::Close`, and
the close button sends the window's close request. In each case eframe
drops the window while AppKit's run loop is still running. On a Mac
with a Touch Bar, AppKit's Touch Bar code then raises an uncaught
Objective-C exception (`-[_NSTouchBarFinderObservation invalidate]`)
and the app crashes (storytold/craft-libs#3).
After the unsaved-changes check, `close_or_quit` calls
`craft_quit::on_close_request` from craft-libs, pinned to the rev that
craft-docpath already uses. When nothing holds the window open, it
cancels the close and quits through `-[NSApplication terminate:]`,
which still runs `on_exit` (window position, workspace, crash-recovery
cleanup). The prompt for a modified project still holds the window.
The window close after a lost graphics device takes the same route.
On other platforms it does nothing. pdfcraft adds craft-quit the same
way (storytold/pdfcraft#1533).
A quit while the window is minimized or hidden still closes the window
as before: eframe does not run `ui` then, and the unsaved-changes check
and the new call both run from `ui`.
`terminate:` ends the process before `eframe::run_native` returns, so
the log line for the closing window moves from `main` into `on_exit`.
clippy's allow-unwrap-in-tests only recognises #[cfg(test)], not cfg(all(test, ...)),
so cargo xtask ci failed on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rfd's Linux backend goes through the XDG desktop portal, and a blocking
`rfd::FileDialog` leaves the main thread waiting on the portal's answer
without pumping winit's events: while the Open dialog is up the window stops
answering the compositor, which offers to force-quit the app ("EffectCraft is
not responding"), until a file is picked.
The non-blocking host hook for the commands' dialogs (`rfd::AsyncFileDialog`
on a worker thread, run when the dialog closes) was macOS-only since #511.
Install it on Linux too. Windows keeps the blocking pickers (its dialog pumps
its own messages), and the panel buttons' pickers (Settings, effect file
pickers) are unchanged.
Checked headlessly against a mock XDG portal: with the hook the main thread
stays in the event loop while a worker blocks in the portal wait, `ui.inspect`
still answers 7.5 s after the dialog opened, and an answered dialog runs
File ▸ Import; without it the main thread itself blocks in the portal wait and
the app answers nothing until the dialog closes.
Co-Authored-By: DeepSeek V4.1 Flash Free <noreply@deepseek.com>
Pasting Trim Paths, Wiggle Paths or a paint into a shape layer with nothing
selected put it on top of Contents, where it acts on no shapes. It now goes
to the bottom, as Add places it. Also fixes#1629.
Layer times were already saved and read back as exact ticks; the values in
the issue are 34f, 315f and 3033f at 59.94 fps. The viewer was black because
the playhead wasn't saved: a reopened comp started at 0, before its layers.
The project file's view (`activeComp`, `openComps`) now also records each
comp's current time (`compTimes`), restored on open within the comp's frames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Advanced 3D renderer folded Accepts Shadows (Off/On/Only) into a
bool, so a shadow catcher set to Only drew as an ordinary lit surface.
The material now carries shadows_only. Such a fragment draws black at
the strongest shadow a light casts on it, as Classic 3D does, and it
goes in the blended pass so what is behind it shows through. The CPU
shader and advanced3d.wgsl get the same branch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
crypto_aux_image_with takes CryptoAux::{ColorsAndMatte, MatteOnly}, and
CryptoAux::for_display holds the rule for which displays read the ID
colours. A new GPU test checks every display x Matte Only against the CPU,
so the GPU fails the test if the shader starts reading RGB in a mode that
skips it (checked by mutation: forcing MatteOnly fails display 0).
Effects & Presets kept its favourites in `UiState::effects_favorites`,
which nothing writes out (only the workspace layout is saved), so the
stars were gone the next time the app opened. They now live in the
settings (`Prefs::effects_favorites`, saved when a star is clicked) like
the recent fonts and presets, survive Reset All like them, and a
hand-edited list loses empty and repeated ids on load.
Turning Solo on or off hides or shows every other layer at every frame, so it
now recaches the whole comp. The kept span is widened by the comp's shutter
(phase and angle), since a frame's motion blur samples cameras, lights and
transforms up to three frames away. Comp::any_solo is shared with the
renderer so both read Solo the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worse half of #1531: when the property's comp remembers a key the user
selected, the old code didn't just do nothing, it eased, held or removed
that selected key instead of the one named. This test, written by
@switch87 in #1558, pins it: only the named keys change, removeKey(1)
removes key 1, the active comp stays active with its own key selection,
and the other comp's remembered selection comes back as it was. It fails
on the code before #1558 and passes on main.