Commit Graph
248 Commits
Author SHA1 Message Date
vangel76andClaude Opus 5.5 c7060e2d75 Exposure: gamma 2.2 linear-light conversion, as After Effects
With Bypass Linear Light Conversion off, Exposure decoded and re-encoded
with the piecewise sRGB curve. After Effects 2026 renders (sRGB working
space, 32 bpc) of Offset 0.0005 / 0.002 / 0.02 / 0.1 on black measure
8 / 15 / 43 / 90, which a pure 2.2 power predicts (8.06 / 15.13 / 43.08 /
89.54) and the sRGB curve does not (1.65 / 6.59 / 38.68 / 89.04).

The effect now uses its own 2.2 power curve on the CPU and in the GPU
kernel; the shared colour functions and other effects are unchanged. As
before, the conversion applies whatever the working space, also a
linearised one. Negative values go through both curves mirrored (input
was clamped at 0 before), so defaults are an identity for 32-bpc values
and a negative Offset darkens past black without NaN.

New test against the AE values; existing Exposure tests still hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:14:26 +02:00
vangel76andClaude Opus 5.5 70c1349cbc Black & White: Tint keeps the gray's luminosity, as After Effects
After Effects tints Black & White's gray like a Color blend: the tint colour's
hue and saturation at the gray's luminosity (weights 0.3 / 0.59 / 0.11, channels
outside 0..1 pulled back toward the gray). EffectCraft used the gray as HSL
lightness. Measured in AE 2026, Tint Color [1, 0.5, 0]: a red solid at Reds 10
(case 717) AE (43, 21, 0), EffectCraft was (51, 26, 0), now (43, 21, 0); a
yellow solid at the default sliders (gray 0.6, case 825) AE (255, 129, 2), now
(255, 129, 3); white stays white. The six sliders already matched AE on eight
colours (cases 816, 817). CPU and GPU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:14:21 +02:00
vangel76andClaude Opus 5.5 960f41d50b Tritone: Rec. 601 luminance, as After Effects
After Effects' Tritone weighs the working-space values with Rec. 601 (0.299,
0.587, 0.114), as its Tint does; EffectCraft used Rec. 709. Case 718 (red solid,
Highlights [1,1,0.8], Midtones [0.6,0.2,0.2], Shadows [0,0,0.3], Blend 25):
AE (132, 23, 46), EffectCraft was (113, 16, 49), now (132, 23, 46). On a grey
ramp (case 815) the shadows / midtones / highlights mapping already matched and
still does within 1/255. CPU and GPU (fx_color op 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:13:41 +02:00
vangel76andClaude Opus 5.5 fb16d5573e Tint: Rec. 601 luminance weights, as After Effects
After Effects 2026 (sRGB working space, not linearised) maps red, green,
blue and 50% grey solids at Tint's defaults to 76, 150, 29 and 128/255:
Rec. 601 weights on the working-space values, not Rec. 709. CPU and GPU
kernel changed; tests pin the four greys within 1/255.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:12:50 +02:00
NetterSilverandClaude Haiku 5.5 26636bfb18 Make Fractal Noise evolution progress linearly
Fixes #1816

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 07:00:02 +00:00
Matej Kozlovský d74cb70938 Cryptomatte pick: snap to the effect's input buffer grid, not the preview scale
At preview scales from 0.75 to 1 footage keeps native pixels, a proxy sets
its own buffer scale at any render scale, a Preserve Resolution precomp is
drawn at full size, and earlier effects can pad or offset the buffer, so
snapping to the preview grid could pick a different ID than the rendered
matte. Use the scale and offset of the buffer the Cryptomatte effect
receives (layer_input, rendered once and then cached).

The pixel centre goes to an aux point through effectcraft_effects::aux_point,
which index_map_at now calls too, so the two round identically (a division
vs the reciprocal picked the other ID on a 104 px proxy of 1920 px footage).

An unknown effect uid errors "no such effect" and a layer without pixels
"this layer has no pixels", as effect.pickColor does.
2026-10-19 08:40:16 +02:00
Matej Kozlovský 76098665b0 Cryptomatte: effect.pickCryptomatte picks the ID under a point into the Selection (#473)
Engine part of viewer picking: replace / add / remove the top-coverage ID at a
layer-space point in one undo step, at the time the Selection was read. The
aux channels come from the render's own path (Renderer::layer_aux, shared with
the effect host; footage at its source time, 3D precomps) through a renderer
set up like Session::render (expressions, render prefs, layer cache). An
optional scale snaps the point to the pixel a preview at that resolution
samples. New effects::crypto_select quotes names and falls back to <hex> for
unnamed, wildcard-looking or ambiguous names. effect.cryptomatteAt is the
read-only hover query.
2026-10-19 06:10:27 +02:00
Anubhav-RaiandClaude Opus 5 3b9d243ac5 Shift Channels: After Effects' Take … From menu order, so 10 is Full Off (#1797)
The menu had an extra "Half On" at position 10, where After Effects has
"Full Off", so a project or script written for After Effects set a channel
to 50 % grey instead of turning it off, with no error: the RGB-split recipe
(three copies, each keeping one channel and setting the other two to Full
Off, blended with Add) rendered pastel instead of pure red, green and blue.

After Effects' ten options now come first, in its order, and "Half On" sits
last as an EffectCraft extra, so values 1 to 10 mean what they mean in After
Effects and nothing is lost. Instances saved with the old order keep the
option they chose: upgrade_instance already remaps popup values by label.

The GPU path keeps its own copy of the table, so it is reordered to match;
a GPU-vs-CPU parity case now covers the constant sources at the end of the
menu, where the two tables could drift apart unnoticed. Both paths clamped
the index to a hardcoded 10 and now use SHIFT_ORDER.len() - 1, so the clamp
cannot go stale if the list changes again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-18 23:34:25 +05:30
Matej Kozlovský 14f4c56732 Cryptomatte: define CryptoAux, which the previous commit used but never added (#473)
crypto_aux_image_with now takes CryptoAux::{ColorsAndMatte, MatteOnly}
instead of a bool, and CryptoAux::for_display holds the rule for which
displays read the ID colours (Colors without Matte Only). IdColorMemo uses
get_mut instead of indexing, and the CPU effect zips the pixels with the
aux index map instead of indexing it. No behaviour change.
2026-10-18 16:42:36 +02:00
Brandon Thomas 681d99c0e7 Merge pull request #1731 2026-10-18 09:35:35 -04:00
Brandon Thomas 8c43fe7375 Merge pull request #1662 2026-10-18 09:35:34 -04:00
Brandon Thomas f16ef4a6b5 Merge pull request #1265 2026-10-18 09:35:32 -04:00
MarekSpodBiedry 0e5b366574 Keep CC Simple Wire Removal between points A and B (#1724) 2026-10-17 14:33:06 +02:00
Matej Kozlovský 0fbe76b2bf Cryptomatte: CryptoAux enum instead of a positional bool; GPU test for every display mode (#473)
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).
2026-10-17 01:48:48 +02:00
Vikram Bhaskaran 31d74e268d Normalize cardinal Wave Warp directions to preserve CPU and GPU parity. 2026-10-17 03:12:36 +11:00
Marco Arnulfo ad4ed3981e Lumetri: hue/saturation curves stay inside their box beside a close point (fixes #1656) 2026-10-16 16:08:18 +02:00
BFlat 908c946fc1 Merge pull request #1538 from guillehr2/black-white-artifacts-1463
Effects: Black & White no longer turns faint colour noise in shadows into blocks
2026-10-16 19:02:00 +07:00
BFlat 88014aceb7 Merge pull request #1292 from vbasky/fix/1195-particle-mblur
CC Particle World: motion blur (#1195)
2026-10-16 19:01:49 +07:00
BFlat 5db68bf34a Merge pull request #1302 from vbasky/fix/1194-liquify-offset
Liquify: expose Distortion Mesh Offset in Effect Controls (#1194)
2026-10-16 18:10:30 +07:00
bflatasticandClaude Opus 5.5 604f2d0ea7 Wave Warp: the pinning test compares the middle row at matching coordinates (#1181)
The old check zipped the padded output with the unpadded source, so pixels never lined up and
it always passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:53:12 +07:00
bflatasticandClaude Opus 5.5 fc38904b41 Liquify: saved projects show Distortion Mesh Offset too (#1194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:33:31 +07:00
Matej Kozlovský 34d5f00af8 Cryptomatte: Colors remembers each rank's last ID colour instead of hashing every pixel (#473)
Bit-identical output. At 2560x1440, 3 ranks, 2000 IDs on 8 cores: CPU Colors ~69 -> ~49 ms, the GPU path's aux reduction ~35 -> ~14 ms.
2026-10-15 23:01:42 +02:00
Matej Kozlovský 74517a52da Cryptomatte: the GPU path skips the ID colours unless the Colors display reads them, and the CPU path keys the layer in place instead of copying it (#473)
Output is bit-identical (all displays, CPU and GPU). At 2560x1440 with 3 ranks and 2000 IDs on 8 cores the CPU effect drops from ~87-135 ms to ~44-54 ms and the GPU path's aux reduction from 38 to 8 ms for Matte, Preview and Matte Only.
2026-10-15 23:01:42 +02:00
Guillem Hermida 749e810174 Effects: Black & White keeps faint colour noise in shadows faint (#1463)
Black & White offset each pixel's luminance by (hue weight - 0.5) times
its HSL saturation. HSL saturation is near 1 for any tinted near-black
pixel, so the code or two of colour noise in compressed footage's
shadows came out as bright and black blocks, and a 32-bpc overbright
such as (1.5, 0.5, 0.5) divided by zero and went black.

The gray is now the pixel's smallest channel plus its chroma (largest -
smallest channel) times the same hue-interpolated slider weight. Neutral
pixels keep their value, a pure hue becomes its slider's percentage
(Reds 40% turns pure red into 40% gray), noise moves the gray by no
more than its own size, and nothing divides. The CPU effect and the GPU
kernel change the same way.
2026-10-15 07:24:22 +02:00
Marco Arnulfo 7df16a5e20 Effects: 4-Color Gradient keeps the layer's alpha with Blending Mode None (refs #1381) 2026-10-14 02:56:17 +02:00
Vikram Bhaskaran d15c82b163 Liquify: expose Distortion Mesh Offset in Effect Controls (#1194)
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.
2026-10-14 02:46:26 +11:00
Vikram Bhaskaran 4ddf5ffd6f CC Particle World: motion blur smears particles across the shutter (#1195)
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.
2026-10-14 02:01:30 +11:00
Vikram Bhaskaran 2720ae5fe2 Wave Warp: cover the Top/Bottom edge pinning paths end to end (#1181)
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.
2026-10-13 23:40:32 +11:00
Brandon ThomasandClaude Opus 5.5 91ee7bf1b3 cargo fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:23:52 -04:00
Brandon ThomasandClaude Opus 5.5 3ac283eee4 Review fixes for the landed batch
- 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>
2026-10-13 04:47:52 -04:00
Brandon Thomas 4574fb62bc Merge pull request #792 from vangel76/gap-object-matte
# Conflicts:
#	crates/ui-egui/src/i18n/ui.rs
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-13 04:30:49 -04:00
Brandon Thomas 90621e2b04 Merge pull request #1111 from eliasx45/upstream/effect-fixes 2026-10-13 04:29:09 -04:00
Brandon Thomas 0cfb563764 Merge pull request #1183 from MatejKozlovsky/cryptomatte-rank-speedup 2026-10-13 04:28:58 -04:00
Kristian MyramandClaude Haiku 5.5 8eece41c7e #1063: keep the Gradient Ramp inside the layer's alpha
The Gradient Ramp wrote alpha 1.0 to every pixel, so a shape layer's transparent area filled up with
the ramp and the layer's bounds showed instead of the shape. The ramp now uses the pixel's own alpha
on the CPU and in the GPU kernel, as the #939 fix does for the noise effects. A regression test
checks that transparent pixels stay transparent at two Blend With Original values.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-13 01:37:03 +02:00
Elias 55ee9c9e76 Effects: re-pin CC Particle World / Systems II golden hashes
3eb3b99 makes the particles replace their layer as in AE, so their 112 pinned
CPU hashes change intentionally; no other effect changed.

(cherry picked from commit 9900eae773e1cad43a6d1bdcacbadc45a84b063a)

(cherry picked from commit 7aa27c0dd7c24b9f24591bb144a95951dc0ec510)
(cherry picked from commit 73377e8eca5c639715e5f3f623ed53ff329638a0)

(cherry picked from commit f07497dfee)
2026-10-12 19:18:53 +01:00
Elias c3fe609ee4 Effects: Time effects see the effects applied before them
Echo, Posterize Time and the other Time effects read the layer at other times with none of
its effects. After Effects renders them with the effects before them: SceneYard "Task
Preparation UI" applies Echo after CC Particle Systems II on black solids, and AE shows the
echoed fireworks over a white page where we echoed the black solids and blacked it out
(Posterize Time after Turbulent Displace, the usual stop-motion setup, depends on the same).
The host already bounds the count to the effects before the one rendering.

Mean abs error vs AE: Task Preparation UI 0.1855 -> 0.0071, Lightbulb Idea 0.0126 -> 0.0102;
the 15 other corpus projects with a Time effect after another effect are pixel-identical.

(cherry picked from commit 46870452db2656c72406f63245e8b1609ceb04d4)

(cherry picked from commit 334d102582e5a64db32e2f3e55836d55ac96bdaa)
(cherry picked from commit 734da3e35dc57c01b73823458bdd87611ac1bf99)

(cherry picked from commit 87a733e076)
2026-10-12 19:18:53 +01:00
Elias 4e7eafa82d Effects: CC Particle World / Systems II replace their layer with the particles
The particles were composited with the original layer by the Transfer Mode, so the solid an
artist applies them to stayed visible. After Effects 2026 frames show no trace of it:
SceneYard "Burnout matchstick" (a red rectangle around the flame), "Documentary Man Falling"
(a black block over the light), "Task Preparation UI" (black solids under fireworks). The
Transfer Mode now only sets how the particles meet each other.

Mean abs error vs AE: Burnout matchstick 0.0191 -> 0.0130, Documentary Man Falling
0.1177 -> 0.0937 (Task Preparation also needs the next commit).

(cherry picked from commit 3eb3b9958f9cd929dcf060865e604d42cc0f9337)

(cherry picked from commit 729638710e63f4da10711fc5e6a1f7ddc42c37aa)
(cherry picked from commit 083332116c936f1b4863f0bb390d0647f2b567a1)

(cherry picked from commit 338421de36)
2026-10-12 19:18:52 +01:00
vangel76andClaude Sonnet 5.5 4acea86d4f Merge upstream/main into gap-object-matte
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 19:03:42 +02:00
Matej Kozlovský 47dbd8dbd5 Cryptomatte: the rank planes are resolved once per evaluation and the GPU path reduces them on all cores, instead of formatting and looking up channel names for every pixel in a single-threaded loop (#473)
AuxChannels::crypto_rank_planes resolves a layer's (ID, coverage) planes once; crypto_ranks keeps its signature on top of it. The CPU effect and the GPU effect's aux image (effectcraft_effects::crypto_aux_image, rayon) index the planes per pixel. Output is bit-identical.
2026-10-12 13:29:03 +02:00
Brandon Thomas 6b69881826 Merge pull request #686 from vbasky/fix/659-text-effect-bounds
Effects: text and shape layers run their stack over the whole layer region (#659, #658)
2026-10-12 04:21:15 -04:00
Brandon Thomas 7864ea41ae Merge pull request #951 from HEOJUNFO/fix-939-noise-mask
Effects: Fractal and Turbulent Noise stay inside the layer's alpha (#939)
2026-10-12 04:21:08 -04:00
Brandon ThomasandClaude Opus 5.5 7f9aa28c03 fmt: Brightness & Contrast test (#702 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:12:04 -04:00
Brandon Thomas 27e095e60e Merge pull request #702 from vbasky/fix/681-brightness-contrast
Match After Effects non-legacy Brightness & Contrast
2026-10-12 03:03:02 -04:00
Brandon Thomas 64b7088867 Merge pull request #733 from vangel76/fix-473-crypto-quoted-names
#473: Cryptomatte Selection keeps quoted names with commas whole
2026-10-12 03:02:55 -04:00
Brandon Thomas 686280cc5f Merge pull request #711 from Ni-zav/fix/704-cylinder-axis-rotation-s20261011T035800Z-cf39
fix(effects): add XYZ rotation to CC Cylinder on CPU and GPU
2026-10-12 03:02:48 -04:00
Brandon Thomas b84b5f47ed Merge pull request #695 from vbasky/fix/535-pixel-motion-vectors
Pixel Motion Blur: synthesize from motion vectors, honour Vector Detail (#535)
2026-10-12 03:02:42 -04:00
heojunfoandClaude Opus 5.5 09b3042e20 Effects: Fractal and Turbulent Noise stay inside the layer's alpha (#939)
Masks are applied before effects, but the noise was composited with the
Opacity alpha alone, so a masked solid filled the whole layer. The result
is now multiplied by the original alpha on the CPU and on both GPU paths
(kernels.wgsl for the default settings, fx_noise.wgsl for the rest).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 10:31:31 +09:00
vangel76andClaude Opus 5.5 3466538d0e #783: Object Matte mattes persist in the disk cache
Problem: computed Object Matte segmentations lived only in the in-memory
cache, so every restart or memory eviction segmented the shot again (After
Effects 26.5 keeps Object Matte results in its disk cache).

Fix: the persistent disk cache gets a third entry kind, Matte (folder
`mattes`, LZ4-compressed FrameSeg bytes, same checksum, atomic writes, LRU
eviction under the one size limit, purged with the rest). Entries are keyed by
the segmentation's chain key and scale; chain keys already hash the Input Key
(the layer's source, timing, masks and effects above), the prompt, the mode and
the model, so any change simply misses. The effects crate gets a SegStore
trait behind its memory cache: Chain::ensure reads from it on a memory miss
and writes what it computes. The engine installs the session's DiskCache as
the store (held weakly, so switching the disk cache off drops it). Roto Brush
segmentations go through the same chain and are persisted too. Frozen mattes
already persist in the project (the Frozen parameter).

Tests: engine tests_roto::object_matte_mattes_persist_in_the_disk_cache (a
second session on the same folder and project gets all tracked frames back
without being able to read a single input frame, renders identically, and a
moved prompt finds nothing); render disk_cache
segmentations_round_trip_and_reopen (round trip, reopen, damaged file is a
miss, size limit evicts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:06:33 +02:00
vangel76andClaude Opus 5.5 560767dfe7 #783: Object Matte tracks in the background and freezes
Problem: Object Matte computed its frames only on demand while rendering, so
playing a shot stalled on every untracked frame, there was no progress or way
to stop, and nothing kept the mattes once computed.

Fix: Object Matte runs through Roto Brush's job machinery (one job slot,
cancellable with roto.cancel, a worker thread on the desktop, a Web Worker in
the browser, never the UI thread). objectMatte.propagate computes the frames
around the reference frame (clipped to the layer) outward, filling the same
content-keyed segmentation cache the renderer reads; the engine's sync now
queues an instance for background tracking when its frames or prompt change.
objectMatte.freeze stores the final mattes in a new hidden Frozen parameter
(Roto Brush's FrozenCache format) under a key of the input, prompt, span,
model and matte settings; renders use them without segmenting while the key
matches; a frozen prompt is refused until objectMatte.unfreeze. Freeze is one
undo step and is dropped if the prompt changed meanwhile.
objectMatte.status reports progress, frozen state and computed frames. Effect
Controls shows Freeze / Unfreeze and Track / Stop with a status line (progress
banner). The frontend now polls Roto Brush / Object Matte jobs on their own:
before, a finished job (and so a Freeze result) was only picked up while a
Warp Stabilizer job was active. Job progress is set before the worker starts.

Tests: engine tests_roto::object_matte_background_job_cancel_and_freeze
(queued by the edit, returns at once with progress, cancel keeps a partial
result, completion computes all 25 frames, frozen renders recompute nothing,
unfreeze recomputes, undo restores); effects object_matte job_frames and
frozen-matte tests (stored pattern used, key mismatch and damaged data
ignored); ui-egui ui_object_matte Effect Controls Freeze end to end.
docs/agents.md: Object Matte section; docs/effects.md regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:02:50 +02:00
vangel76andClaude Opus 5.5 0c695cff77 #783: Object Matte tool in the Composition viewer
Problem: Object Matte could only be applied through the objectMatte.apply
command; After Effects 26.2 picks the object directly in the viewer (click it,
or drag a box around it).

Fix: an Object Matte tool (with Roto Brush & Refine Edge in the Alt+W slot,
icon drawn in code). In the Composition viewer a click sets a point prompt and
a drag a box prompt (its centre the point) on the footage, solid or comp layer
under the pointer, converted to layer pixels through the layer's transform.
The layer's Object Matte (the selected one, else its last) is re-prompted;
Shift adds another. Everything goes through objectMatte.apply, which gained
`effect` to re-prompt an instance (point, box or no box, reference frame; the
reach kept unless given), so each prompt is one undo step; the effect is then
selected, so its point controls show in the viewer. Hover draws a crosshair
and the outline of the layer that would get the matte (a live matte preview
would need a segmentation per pointer move). Automation ids:
viewer.objectMatte.crosshair / .target / .box, tools.ObjectMatte.

Tests: ui-egui ui_object_matte (hover, click, drag box re-prompts the same
effect, Shift-click adds one, undo steps); engine
tests_roto::object_matte_reprompt_updates_the_instance. Checked with the
headless snapshot example (hover crosshair, box drag isolating a disk).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:50:01 +02:00