chore: sync upstream (192 Commits) #24

Merged
alf merged 192 commits from chore/sync-upstream-20261111 into main 2026-11-11 21:50:04 +00:00
Owner

Upstream-Sync von upstream/main (192 Commits). Fast-Forward.

  • b47a7104 Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975)
  • 00d50b8c ScriptUI: bound the edges and bands a fillPath is tessellated with (#3001)
  • b7e848a6 Import, Relink: index sequence runs and folder listings; probe relink candidates once (#3000)
  • e339a5b3 Effects: cap Camera Lens Blur and median radii (#2951)
  • 3ddfbd8a Tracking: face skin search visits only window pixels; stabilizer smoothing window capped (#3022)
  • 5735095c Camera tracking: repeated observations of one camera combine in the bundle reduction (#3502)
  • 7c0c57cb Segment: bound MobileSAM prompt points, TFLite metadata and folded DEQUANTIZE sizes (#3509)
  • 0b196915 VP9 encoder and WebAssembly plug-ins: refuse short planes, limit memories and tables (#2995)
  • cbddc9d2 Fix Flatpak updates and bump to 0.24.1 (#3516)
  • 072e1af9 Render settings: a saved Frame Rate override is held to the supported rates (#3503)
  • 4ba6ff32 PDF functions and mesh shadings: charge retained values, refuse short tables, bound lattice rows and raster work (#2972)
  • 6c1391d4 Text: run lookup by bisection, bounded path flattening, font folders and files shared (#2991)
  • 428a8613 Project: layer name suffixes in one pass; template names bounded and never a parent folder (#2973)
  • 0f200006 EPS: bound copies, storage, text outlines, group nesting and font discovery (#2953)
  • 0e248459 Tracking: bounded crop size, Roto chain span and resection copies (#2989)
Upstream-Sync von `upstream/main` (192 Commits). Fast-Forward. - b47a7104 Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) - 00d50b8c ScriptUI: bound the edges and bands a fillPath is tessellated with (#3001) - b7e848a6 Import, Relink: index sequence runs and folder listings; probe relink candidates once (#3000) - e339a5b3 Effects: cap Camera Lens Blur and median radii (#2951) - 3ddfbd8a Tracking: face skin search visits only window pixels; stabilizer smoothing window capped (#3022) - 5735095c Camera tracking: repeated observations of one camera combine in the bundle reduction (#3502) - 7c0c57cb Segment: bound MobileSAM prompt points, TFLite metadata and folded DEQUANTIZE sizes (#3509) - 0b196915 VP9 encoder and WebAssembly plug-ins: refuse short planes, limit memories and tables (#2995) - cbddc9d2 Fix Flatpak updates and bump to 0.24.1 (#3516) - 072e1af9 Render settings: a saved Frame Rate override is held to the supported rates (#3503) - 4ba6ff32 PDF functions and mesh shadings: charge retained values, refuse short tables, bound lattice rows and raster work (#2972) - 6c1391d4 Text: run lookup by bisection, bounded path flattening, font folders and files shared (#2991) - 428a8613 Project: layer name suffixes in one pass; template names bounded and never a parent folder (#2973) - 0f200006 EPS: bound copies, storage, text outlines, group nesting and font discovery (#2953) - 0e248459 Tracking: bounded crop size, Roto chain span and resection copies (#2989)
alf added 192 commits 2026-11-11 21:50:03 +00:00
A 3D text or shape layer close to the camera was rasterised whole at its on-screen scale, up to
~16 million pixels (a quarter gigabyte as f32) per frame worker, though the frame showed a small
part of it; behind the camera it was rasterised and not drawn. With 16 preview workers one such
layer took the app to 8-14 GB.

Renderer::raster_clip projects the output frame back onto the layer's plane (as the Classic 3D
compositor draws it) and rasterises only those cells of an 8 x 8 grid over the content, in a
window of the whole raster's pixel grid; nothing when the content is behind the camera. Cells
keep a moving camera on one cached raster. Layers whose off-frame pixels can matter (effects,
styles, masks, motion blur, depth of field, lights, Advanced 3D, per-character 3D, collapsed 2D
transforms) are rasterised whole as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: effects that take another layer as a parameter (Set Matte, Displacement Map, time
effects…) read it through Renderer::nested in layer space, and the motion tracker reads
layer_source; both could get only the part of the layer the frame shows. The nested renderer and
layer_source now rasterise whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #595 (v0.19.0) a playing preview and Cache Frames When Idle render one frame per
lane, a lane per core, each recording, submitting and reading back a whole frame on the
GPU the window draws with. On a GPU that can't keep up, the window froze for hundreds of
milliseconds to seconds and the playhead jumped ahead when it came back.

Lanes now take a GPU frame only while fewer than a limit of them composite. The limit
starts at one lane and adapts to the window's frame gaps while a preview plays: a stall
(>100 ms) with lanes compositing halves it, half a second of smooth frames (<50 ms) with
every allowed lane busy adds one, up to every lane; a limit that stalled is tried again
only after 10 s. Frames on the CPU (Software Only, no or retired GPU, GPU memory
pressure) and disk-cache hits use every lane; the pool and the viewer's frame are not
limited, and frames ahead of the playhead still start first.
# Conflicts:
#	crates/render/src/lib.rs
Preview: the window stays smooth while lanes render on the GPU (#3345)
#3357: avoid whole-layer cover for Drop Shadow
GPU render speed: footage uploaded once per frame, single-copy readback, Render Queue picks the faster renderer (2.6× on a 1080p EXR job)
#3357: release retired GPU resources before CPU fallback
Report unexpected desktop exits with local diagnostics
#1388: 3D text and shapes rasterise only the part the frame shows
Preview: render at most two frames on the GPU at once so video memory stays flat (#3028)
Release: EffectCraft v0.23.1
Dropping an .aep or .aepx on the window, or picking one in File > Import > File...
or Multiple Files... (also from the Home screen and the Project panel), imports it
as File > Import > Adobe After Effects Project... does, instead of failing as
unsupported footage: the losses prompt first when parts cannot come over, its
items in a folder named after the file, its first composition open.

- Several projects are asked about one at a time: the others wait in the
  prompt's queue, and Cancel skips only the one it asks about. A prompt another
  dialog replaced (Save changes?) strands nothing: its projects are tried with
  the next import or queued behind the next prompt.
- The other files of a drop or pick import as footage, into the Project panel
  only when a project is in the drop. While the prompt shows, a Photoshop file
  or image sequence imports with its defaults instead of replacing the prompt,
  an Interpret Footage question leaves a note instead, and a dropped .ecproj is
  skipped with a note. While Save changes? waits for a dropped .ecproj, the rest
  of the drop is skipped with a note instead of landing in the replaced project.
- Errors of every picked project reach the status line on both the blocking and
  the non-blocking file dialog.
- The Import File dialogs list .aep, .aepx, .ecproj and .ecprojx; Replace
  Footage offers footage only.
- For scripts and agents, file.import hands After Effects projects to
  file.importAep; a refusal (new EngineError::Refused) names the menu entry and
  `file.importAep` with `allowLosses`.
- File > Import / Export name the entry Adobe After Effects Project..., as
  Adobe Premiere Pro Project...; File > Import keeps After Effects' order with
  the project imports together and the formats only EffectCraft imports after a
  separator. All 13 menu catalogs and the losses dialog title are updated.
- Docs: aep-import, aep-export, footage, file-format-parity, agents.

Supersedes #3312.
File ▸ Import: drop or pick After Effects projects to import them; name the project imports alike (#3103)
The unit test re-implemented Marquee::selection's box/outline split, so it
only covered `targets`. Move the anchor math into `Anchor::press` /
`Anchor::selection` over a plain `View`, call it from the test, and add a
scrolled-drag case that fails with a screen-fixed anchor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Timeline: keep marquee selection anchored while scrolling
Moves the six craft-libs 92d392c pins to e11f9d9a so cargo vendor (and the Nix flake) can load the craft-libs workspace. Refs #3291.
Removes the unused egui_demo_lib, egui_inspection and egui_system_font_provider patches and adds cargo xtask lock-patches to cargo xtask ci. Fixes #3403.
* #3389: a viewer click selects the topmost layer over a selected one

Clicks in the viewer went through pick(), which prefers a selected layer
under the pointer so a covered layer can be dragged (#230). A large
selected layer lower in the stack therefore captured every click inside
its bounds, and the layers in front of it could not be selected there
(#1535, #3389).

Add click_pick(), the same hit test without that preference: the
topmost layer, keeping the selection when it is already selected. The
left click (Selection and fallback tools, Roto Brush), the double-click
and the right-click menu use it; drag starts keep pick(), so dragging a
covered selected layer works as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #3389: rustfmt the viewer's shapes imports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #3389: test the right-click where both layers cover the pointer

The right-click check ran after the drag had moved the layer behind out
from under the pointer, so the old pick() passed it too. It now runs
before the drag with the layer behind selected, then closes the menu.
pick()'s doc names the Puppet tool's pin placement as a click that uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #3389: a click on a covered path of the selected layer keeps that layer

With clicks going to the topmost layer, a click on a mask or shape path
of the selected layer where a layer in front covers it selected the
layer in front. Its paths went away, so a double-click there could no
longer free-transform the path. A plain click on a shown path now stays
on its layer, as pick() did; Shift-click still toggles the topmost one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #3389: a right-click on a covered path of the selected layer keeps it

The context menu picked as a left click did before the path fix, so a
right-click on the selected layer's path, under a layer in front,
selected the layer in front. It now keeps the path's layer like a left
click. pick() and click_pick() docs describe the Puppet and path
exceptions exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Merge current main while preserving continuous audio and prefetch behavior. Fix sibling method visibility, audio-only unmute, queue/cursor handoff, end-of-span replay, and expression-driven audio invalidation. Add mixer and preview-output regression coverage. Formatting checked; builds and tests run in hosted CI.
* docs: track startup adapter recovery investigation (#477)

* Fix launch guard ownership during concurrent startup

* Launch guard (#477): fold the ownership tests into the tests module

Review cleanup, no behaviour change:
- Drop docs/issue-477-startup-recovery.md (investigation notes, not docs).
- Move the investigation_477 and root_patch_477_tests cases into the existing
  tests module under behaviour names, without println! output; every
  assertion is kept.
- Rewrap the module doc comment.

* #477: preserve inaccessible launch markers on Windows

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Moving a layer bar or dragging its In / Out end with Shift held snaps the moved edge, within
8 px of the pointer, to the current time, comp and layer markers, visible keys, the work area,
the comp's start and end, and other layers' In / Out points, as in After Effects. The edited
layers' own ends are no targets (nor, when moving, their markers and keys). Targets are
gathered once per drag; snapping is measured from the pointer, so dragging on lets go.
Without Shift a drag moves by the whole frames the pointer covered, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #3357: retain process, memory and typed GPU failure evidence

* #3357: tighten reporter protocol regression cases
Make the audio preview follow mute changes made during playback (#2104)
A Shift-press on an unselected bar adds it to the selection, so the drag now moves that
selection and its layers are no snap targets. Tests cover the selection drag and that a
moved layer's own marker is no target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* AG4: xtask ci --affected

cargo xtask ci --affected [--base REV] [--dry-run] runs fmt, clippy, tests (nextest + doctests when available), layers, assets (only on asset changes) and wasm (only for affected L0-L4/web crates) for the packages a change can affect: changed files map to the owning package and to packages whose sources read them via ../ paths, expanded to reverse dependents. Workspace-wide inputs (root Cargo.toml, Cargo.lock, .cargo/, xtask/, toolchain, clippy.toml) fall back to full CI. Documented in docs/testing.md, AGENTS.md and CLAUDE.md.

(cherry picked from commit f3ca84fc2ee3948582cbc94e4c99ee510e829540)

(cherry picked from commit 8a2cdcadf447eafa57ab14016beae4420f002af0)
(cherry picked from commit d8a4301bc10c7bbcc2c4e5c70980f4b774d6efe3)

(cherry picked from commit 36402c61ac2f7e29ecd76a6cfc89365cc2572e0a)

* AO1: fast CI profile, looser timing tests

- [profile.ci] (release without LTO, 16 codegen units); `xtask ci` runs clippy
  and tests with --profile ci instead of --release.
- Wall-clock asserts in engine memory_tick, gpu OpenGL decline and script
  socket tests keep their intent with bounds that survive a loaded machine.
- docs/testing.md and CONTRIBUTING.md mention the profile.

(cherry picked from commit b45c8a379351ebdfda0d7076ad4b740baf6f2087)

(cherry picked from commit fa1ea6a107c4c1936cde89b518a05adc73f2b4b0)
(cherry picked from commit e5d5ed80e9ead43c2ce62b75ec1902340826a8e1)

(cherry picked from commit d2c67660c70764f9c56880a642ad7f0d004a7f92)

* AG6: journal replay repros

Bug reports become replay scripts and crash regression tests become data files:
- engine::replay: a JSON-lines repro format (start line, command steps with
  expect_error, expect queries with JSON pointer + equals, expect_no_error),
  a never-panicking runner that reports per-step ok/error and the first failure,
  and journal export (from the last project open; saves left out; size-capped).
- journal.export {path?} and help.exportRepro {path?} (not journaled).
- Crash capture: when the last-resort guard catches a panic in the outermost
  command, the journal plus that command is saved to Repros/crash-*.jsonl in the
  settings store (newest 20 kept) and the error names the file.
- Commands run by a journaled command (script steps) are no longer journaled twice.
- effectcraft-cli replay SCRIPT.jsonl [--json] [--keep-going]: exits 1 on a failed
  step, unmet expectation or panic.
- crates/host/tests/repros.rs replays every tests/repros/*.jsonl; three samples
  from past crash fixes (zero frame rate, Layer Settings with no selection,
  New Comp from Selection with hostile overlap).
- Docs: docs/agents.md "Repro scripts"; AGENTS.md: crash fix regression test =
  a .jsonl in tests/repros.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

(cherry picked from commit c38509d7dafe507856c48c78db65e00b94b0cebc)

(cherry picked from commit e55b56dbf76716adfcbfe4cc773df19a74edc419)
(cherry picked from commit 96a63855a98b4a05c24f9095b949aebf29d8665a)

(cherry picked from commit c59351ab3bd697f0868e41230dd883960d94a540)

* xtask: ci and ci --affected run cargo test and nextest with --no-fail-fast

One run now lists every failing crate instead of stopping at the first.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019qXL3GPNnY9BSFF2XoU2Yu

(cherry picked from commit e46c7debe738eca62b35bf7392ab77637f6a33ae)

* Agents: confinement write-checks journal.export, help.exportRepro and file.exportTimeline

Review on #859 (split into this PR): their output paths were only read-checked (relative ones
not at all). Test: confinement_covers_every_command_that_writes_a_file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d65YdNxQmjRkbXRtP2NwH

* #1224: handle bin-only CI targets and BOM repros without weakening timing tests

* #1224: run the full gate when nextest policy changes

---------

Co-authored-by: Elias <hakifx91@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
A render thread handed each frame to the disk cache already copied pixel by
pixel and LZ4-compressed (~9 ms per 1080p frame) before it could start the
next one, and layer buffers were encoded even when the queue then dropped
them. The queue now holds raw RGBA (one memcpy via ColorImage::as_raw) and
Arc<Buf>s; the writer thread encodes them. Every frame of a 120-frame bench
run now reaches disk (113–119 before). cache_bench prints the flush time.
A comp left alone for a second fills its RAM preview from the current time,
so the first playback is cached instead of rendering frame by frame. Saved
settings keep their value. Two UI tests that assumed the old default set it
explicitly.
The sampled Gaussian (sigma < 3) read every tap through a bounds-checked
get, and its vertical pass walked a column per pixel. Taps inside a row are
now one slice, and the vertical pass adds whole input rows in 128-pixel
tiles. Same f64 sums in the same order: bit-identical output (tested
against the per-pixel kernel). Each pass is 15–25% faster.
A footage layer's frame was copied from the footage cache, converted from
its colour profile, then quantised to the project depth in masked_source:
three passes over a 33 MB 1080p float frame. Footage without masks now
comes quantised from one fused pass (Pipe::copy_converted); other source()
callers still get unquantised pixels. Bit-identical (tested at 8/16/32 bpc).
--gpu renders on a headless GPU of its own and reads the frames back, as the
desktop viewer does; --backend cpu|gpu|auto picks the compositor (default
auto, the viewer's Mercury GPU Acceleration). With auto the bench prints
Auto's per-comp timing history: how many frames each side rendered and how
long they took.
A preview renders up to 12 frames at once, and Auto only learns a frame's
time when it is done. A starting preview sent every lane to the GPU for
its warm-up, and on an integrated GPU 2–4× slower than the CPU each first
playback stalled. Now at most one GPU warm-up frame (and one re-probe frame
per side) runs at once, a frame's time counts per frame running on its side
at once, and the warm-up starts on the CPU, so the frames on screen come
first. Iris Xe, 1080p: Auto went from 13–34% slower than CPU only to 0–17%.
An 8 bpc render at Full quantises plain footage straight away, so the
media pool now keeps those movie frames as the 8-bit levels it is
quantised to (a quarter of the memory) and the renderer expands them
into the layer buffer: the same pixels to the bit. Other depths,
reduced renders, masks, colour profiles, fields, frame blending,
Cineon, Invert Alpha, linear light and 10/12-bit or float movies keep
the float path.

First playback of plain 1080p clips at Full caches ~43% faster on the
CPU, and the footage cache holds 4x the frames.
--play SECS runs the app headless (egui_kittest) on the bench comp, with
the viewer at 100 %, Resolution Auto, nothing cached and Cache Frames When
Idle off, presses play and steps it for SECS seconds of wall time. It
prints the frames shown per second, the time the clock stalled on uncached
frames, the frame reached and the share of passes at each viewer scale.
--gpu composites on the GPU (Mercury Auto) as the desktop app does;
--res N sets the comp's Resolution to every N-th pixel.
Settings > Previews > Lower Resolution When Playback Can't Keep Up (on by
default): a preview at Resolution Auto that stalls on uncached frames and
shows under 80 % of its frame rate for two seconds plays on at half the
scale, and two seconds later at a quarter if still slow, no lower than the
Adaptive Resolution Limit. Stopping goes back to the viewer's resolution
and Cache Frames When Idle fills it in. An explicit Resolution, the comp's
or the Preview panel's, is kept as set.

First playback of a 1080p comp with a blur (cache_bench --play): 9.0 to
22.1 frames/s on the CPU (about 27 once at Half), 4.1 to 16.1 with Mercury
Auto on an integrated GPU; plain clips that keep up stay at Full.
A GPU frame that failed or failed its health check, and a remote frame replaced before it finished, stayed counted as running on its side, so every later frame there was timed as one of several side by side and that side looked cheaper. AutoPick::abandoned releases them.
footage_buf8 asked for a movie's 8-bit levels outside Renderer::read, so the media pool noted the movie in no recording: a layer buffer made of it listed no files and was still marked complete, and could be served stale after the movie changed on disk.
The paced clock counted the cached lead out of twice the prefetch depth even when fewer frames were left before the end, so a fully cached preview that doesn't loop slowed down over its last frames, and one shorter than that depth played slow throughout (and could step down). The lead now counts out of the frames left.
Layer jobs keep their float buffer until the writer compresses it, so the 64-job limit could hold ~8.5 GB of evicted 4K buffers. Layer and matte writes now also have a byte budget, like frames; a single bigger buffer is still taken when nothing else is queued.
A 300-pixel-wide case runs the row-by-row pass over tiles at x 128 and 256 and a partial last tile.
The test source notes its file while a read is recorded, as the media pool does, and the test checks the render's read log lists it.
AutoPick::ticket chooses a side and reports the frame's time once; dropped without one it ends the frame unmeasured. The viewer's frames, the renderer's top-level frames and frames rendered in passes use it, so an Auto CPU frame turned away by GPU admission (#3028), a cancelled export frame or any later exit no longer leaves its frame counted as running. The frame server keeps its choice across calls and still ends a replaced frame itself.
Only a lane's drain thread waits for a GPU slot. A lane runs one drain at a time (it is idle, and started again, only once its drain found no job), and its other threads only help with that frame's parallel work, as Rayon helpers that never wait. The comments said lanes had one thread; the test now runs on a lane LANE_WIDTH wide and checks that its other threads decline while the drain thread waits.
It never steps back up during a play, so a looping preview kept playing at a quarter of its resolution after its first loop. Without it the paced first loop caches the viewer's resolution and the next loops play in real time, as in After Effects. The setting stays for whoever prefers a lower first pass.
In an 8 bpc comp, a movie used by both a plain and a masked layer has an 8-bit and a float entry in the media pool, decoded separately. Rare and only a speed cost, so it is noted where the 8-bit path starts rather than changed.
The Radius parameter was limited to 400 %, so a larger value was clamped and drew
a smaller cylinder than After Effects' CC Cylinder, whose range the report gives
as 0 to 30000 %. Raise the hard maximum; the slider's visible range is unchanged.
Timeline: Shift-dragging a layer bar snaps to layer ends, the current time, markers and keys (#1828)
The time display in the Composition panel's bottom bar only reacted to
Ctrl/Cmd+click, which switches timecode and frames. A plain click now opens the
Go to Time dialog (View > Go to Time), as in After Effects. The Ctrl/Cmd+click
test follows: its plain click now opens the dialog, which it closes.
The import report keeps only the first 20 example lines per category, and each
unmapped effect instance was one line in the Effects category. With other losses
in that category, an effect used on many layers could be counted without being
named. Each effect with no counterpart now gets one line, with how many instances
there were ('... and 68 more like it'), placed before the other examples. The
category's count is unchanged.
The step-down was skipped while the achieved frame rate read as keeping up, and that rate comes from the frames shown in the last second they were shown: through a stall it kept the rate from before it, so a preview that played a cached stretch and then stalled never stepped down. A rate whose last frame is more than a second old no longer counts.
APFS refuses file names that aren't UTF-8 (EILSEQ, "Illegal byte sequence"), so the test's
fixture write panicked on the macOS gate before reaching changed_files. Such a file can't
exist on macOS; Linux keeps running the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dialog's Frame field held the engine's zero-based frame, so with frames shown
from 1 the viewer's 00013 opened as 12, and typing 13 went to the frame displayed
00014. It now holds the time in the project's display style (timecode, frames from
Start Numbering Frames At, or feet + frames), which time.set already reads that
way. The now-unused 'Frame' translation rows go.
The pixel it read was inside the cylinder at 400 % too. Put the axis on the left
edge and read a pixel past the 400 % radius, with a 400 % run showing it is empty.
An effect past the limit was still added to the example lines when they had room,
so a report could name up to 120. Past the limit an effect is only counted.
xtask: skip the non-UTF-8 changed-file test on macOS, where APFS can't hold such names
The pace counted cached frames among the next ahead x 2, but the prefetch never requests more than the RAM preview holds (offsets 0..fit-1 from the current frame). With room for only a few frames the lead could never fill, so a preview that rendered every frame at once was slowed to a fraction of its frame rate, with Lower Resolution When Playback Can't Keep Up off (the default). The lead is now counted over at most the frames the prefetch keeps after the current one, and with none of those (room for at most two frames) the clock runs in real time as before pacing.
Preview: smoother first playback of uncached comps
sonar: leave out the AEP corpus generators and document the expression eval wrappers
Horizontal GPU box passes restarted the running sum every 256 pixels, while
the CPU keeps one sum per row; use the full row width as the block so f32
rounding matches. The regression test requires exact equality on llvmpipe
(CI); other adapters only get an absolute 2e-6 sanity bound.
Reuse the existing diff() helper so a non-finite pixel fails the test
(f32::max ignored NaN). Keep exact equality on llvmpipe, which still
fails without the row-block fix; other adapters use the usual Bpc32
tolerance, because a 2e-6 bound can fail on unmeasured hardware and
does not catch the regression there.
* fix(lottie): export embeds only files that are PNG, JPEG, GIF or WebP images

* address review: match full PNG and GIF signatures
* Segment checkpoints: charge memo copies and string bytes; refuse impossible zip sizes; bounded inflate errors

The pickle reader charged memo reads but not memo writes, and counted a
string as one object whatever its length, so memoizing one long string
many times kept unbounded copies; writes are charged too and strings count
one node per 64 bytes. A zip entry stating more than deflate can expand
its data to is refused before inflating, and inflate errors keep only the
status, not the partial output.

* address review: charge every owned field of Global, Storage and Tensor to the pickle clone budget
* PDF fonts and fax: bound charstring work, ToUnicode ranges and reference search

Type 1 and CFF charstrings bounded subroutine depth but not total work:
subroutines that call each other several times multiplied a small font into
unbounded instructions and geometry. Each glyph now has a shared budget of
100,000 instructions. The Type 1 Subrs parser looked up /CharStrings again
for every entry; it is found once. A ToUnicode bfrange expanded a long
destination string for each of up to 65,536 codes; destinations past 256
bytes are skipped and a CMap keeps at most 2^20 mappings. CCITT 2-D decoding
scanned the reference row from its start for every code; it bisects.

* address review: cap bfchar and array bfrange unicode mappings

* address review: seac components share the glyph instruction budget (CFF and Type 1)
* fix(render-queue): keep Output Module crop padding within the largest output frame

* address review: crop padding is taken back only when the side would pass the cap
* fix(color): interpolate only the CLUT axes that need it

* address review: CLUT upper grid points are not interpolated, stride arithmetic saturates
* Box blur: count repeated edge pixels instead of visiting them, and bound the radius

* address review: box blur first mean ignores an unused edge term (inf * 0 gave NaN)
* Auto-trace: bound the nesting, simplification and fitting work

Many separate regions made the nesting depth test every pair of contours,
Douglas-Peucker could rescan a span once per point (and recurse as deep as
the loop is long), and the curve fit searched the raw contour from its start
for every simplified vertex.

- Depth: each loop's bounding box goes into a coarse grid; a loop's first
  point is tested only against loops whose box holds it. Same depths as
  testing every pair.
- Douglas-Peucker: spans wait on an explicit stack, with a budget of 64
  distance evaluations per point; spans still waiting when it runs out keep
  all their points, so the outline stays exact.
- Fitting: uses the raw indices the simplifier kept instead of searching.

* cargo fmt; clippy manual_is_multiple_of

* address review: keep wide contour boxes out of the nesting grid
* Text: merge variable font axes by tag and cap their number

Each tag in a variations object scanned the style's growing axis list, so a
large object cost quadratic time, twice (validation probe and apply). The
object is now merged in one keyed pass, refused when it names more than 64
axes, and a merge that would leave a style with more than 64 is refused
without changing the style.

* cargo fmt

* address review: refuse variation over every run before changing any

* address review: empty-document range cannot overflow in set_attr
* Gradients: bound the stops sampled per pixel

Gradient sampling scans the stops linearly and shape painting samples once
per pixel, so a gradient with a huge stop list multiplied every frame's
work. Sampling now looks at the first 256 colour and opacity stops, and the
plain JSON form (coerce_json) refuses gradients with more than 256.

* address review: bound tagged gradients to the stop limit
Clears all 29 open SonarCloud issues.

- `examples/plugin-catalog/scripts/validate.sh`: `[[ ]]` in place of `[ ]` for bash tests (SonarCloud shell:S7688). Every script has a bash shebang; no test uses `-a`/`-o`, escaped parentheses or an unquoted pattern on the right of `=`.
- `crates/expr/src/prelude.js`, `crates/script/src/prelude.js`, `crates/script/src/scriptui.js`: `Number.isFinite` / `Number.isNaN` / `Number.parseInt` (javascript:S7773). Every call already has a number (a `typeof` check or `Number()` before it), and `Number.parseInt === parseInt`, so nothing changes in boa.
- `packaging/windows/test-legacy-fixture.ps1`: dot-sources `[scriptblock]::Create($block)` in place of `Invoke-Expression $block` (powershelldre:S8659). It runs in the same scope, so `$LegacyWxs` is still set after it.
- `sonar-project.properties`: S7773 and S8786 are not raised on `*.jsx`. Those are After Effects ExtendScript (ES3), which also runs in After Effects and has no `Number.isFinite` or `String.prototype.trim`. The ignore covers 7 issues in `Ease Presets.jsx` and `Rename Layers.jsx`.
GPU: Fast Box Blur and Gaussian Blur rows round like the CPU
#1615: document EFFECTCRAFT_CPU_INFLIGHT in docs/preferences.md
architecture.md and web.md still said every job renders on the project's
renderer (#414); since #3335 a GPU job times both compositors on its first
frames and keeps the faster.
#2944 (497a056c) normalises each box-blur mean in f64 and rounds once
(it was rounded to f32 first, then scaled), and counts repeated edge pixels
with one multiply instead of adding them one by one. Its PR states results
change only by float rounding, and it added the focused regression test the
golden-test rule asks for (box_row matches a direct sum). The golden hashes
are only enforced on aarch64 macOS, so Linux PR CI passed, and every
completed macOS CI run on main since #2944 has failed (497a056c's own run
was cancelled by a newer push).

Bisected on an M5: the golden tests pass at ed085bd4 and fail at 497a056c
with the same 52 hashes macOS CI reports. All 52 are 32 bpc frames of
effects that blur through box_plane/gauss_plane (Lightning, Advanced
Lightning, Inner/Outer Key, Caustics, Paint Bucket, Vegas); every 8 bpc frame
is unchanged. All 52 frames differ by at most 4.8e-6 per channel, with no new
non-finite values.

Only the pinned hash values change; LIBM_VARIANTS is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings ▸ General ▸ Fit New Layers to Work Area (off by default) makes
new text, solid, shape, null and adjustment layers start at the work
area start and end at its end. It applies to Layer ▸ New, the shape
tools and the Pen tool, and wins over Create Layers at Composition
Start Time. Footage and compositions keep their own length.
With Fit New Layers to Work Area on and the playhead outside the work
area, the Type tool, shape tools and Pen now move the playhead to the
work area start so the new layer and its controls show. Layer timing is
unchanged; Layer ▸ New commands leave the playhead.
show_span now checks and moves the playhead of the comp the layer was
made in, through a new Session::set_time_of that set_time uses for the
active comp, so time sync and the text editor boundary stay the same.
Docs: scope EFFECTCRAFT_CPU_INFLIGHT to exports, document EFFECTCRAFT_RQ_RENDERER
The work area need not sit on frames (a typed start, a frame-rate
change), so rounding its start to the nearest frame could land before
the new layer's in-point. show_span now rounds up to the first frame in
the span and moves nothing when the span holds no frame.
A frame-rate change can leave the duration off the frame grid. The
current-time clamp used duration minus one frame, which put the last
reachable frame before a final partial frame, so a layer fitted to a
work area inside it never got the playhead. Clamp to the last frame
that starts before the end instead; on-grid durations are unchanged.
Synchronize Time of All Related Items clamped each related comp's time
with its own duration-minus-one-frame rule, so a nested comp whose
duration is off the frame grid could not reach its final partial frame.
Use comp_frame_at, as direct moves and restored times do.
Effects: re-pin the macOS golden hashes #2944 changed by rounding
Settings: option to fit new layers to the work area
AEP import: name every unmapped effect in the report, with its instance count (#3365)
Viewer: clicking the current-time display opens Go to Time (#3373)
In a comp whose Start Timecode is negative, the current-time display shows
the first frames as `-0:00:00:12`, but typing that back (the Go to Time
dialog opens with it since #3419, and the Timeline's time field takes it)
read the leading `-` as "move back", so OK on an unedited dialog jumped to
another frame. There, a `-` before a full timecode is now the time's sign,
as it already is before Feet + Frames; a bare `-n` still steps back, and
comps that start at 0 or later are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
picks_what_the_viewer_shows_across_sizes_scales_zoom_pan_resolution_and_proxies looped over four
independent window-size and display-scale cases, each with its own session. As one test it took
~90 s in the gate and ~590 s under coverage, where it alone set the slowest SonarCloud shard.

Each case is now its own test, running the same checks, so nextest runs them in parallel and
partitions them across shards. The "more than 100 hovers checked" floor becomes more than 25 per
case, which still adds up to more than 100.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prompt showed 3 detail lines per category under a bare count, which read
as the project's total. The headline now says how many occurrences are left
out or changed, every line the report keeps shows, and "… and N more" counts
the rest. The import report records that number as `unlisted` (an unmapped
effect's line covers all its instances); unmapped effects are listed most
used first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shape Direction, blend mode, composite order and the gradient highlight
values were reported when their record was in the default state (tdsb 3),
which holds After Effects' default (docs/aep-format.md 3.4), so a plain
guide rectangle reported "shape direction not imported". They are now read
only from written records, keys or an expression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An Orientation with an expression was reported as "not decoded" and lost its
value and keys too. The expression is stored in the otst's tdbs like on any
property, so it now arrives with the stored value or keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test(ui-egui): split the crypto-pick viewer matrix into one test per window size
Time entry: a negative displayed timecode goes to the frame it shows (#3373)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* .aep import: recognize French names for Levels' Channel and Clip popups

After Effects stores a popup's items in the UI's own language. A project saved
by a French After Effects writes "RVB", "Activé" and "Désactivé pour la
couleur 32 bpc" for Levels' Channel and Clip to Output Black / White.
EffectCraft's own popup options are English only, so these never matched by
name and the parameter fell back to its default, reported as "has no
counterpart".

Add popup_alias, a small table of confirmed After Effects labels in other
languages, used as a fallback in popup_mapping after the exact name match
fails. A synthetic test reproduces the three losses from a real file and
checks they are gone, with the same values as the English original.

* Cover Levels' French Red, Green and Blue popup items too

popup_alias only mapped "rvb" (RGB), so a French Levels with Red, Green or
Blue selected still lost that selection the same way. Add "rouge", "vert"
and "bleu" (from the same real pdnm list, confirmed against it); "Alpha" is
already the same word in both languages. A direct test on popup_alias covers
every item of the Channel popup it claims to know.

---------

Co-authored-by: sunkidda <sunkidda@users.noreply.github.com>
popup_key lowered only ASCII letters, so an item starting with a capital
outside ASCII (French "Écran") kept it and never matched the same word in
lower case. After Effects stores popup items in its UI language, and
#3432 starts matching such names through popup_alias, keyed by
popup_key. Use Unicode lower case; ASCII keys are unchanged.
# Conflicts:
#	crates/aep/tests/it/import/synthetic.rs
Scripting: setValue stores string effect parameters, such as Apply Color LUT's LUT (#3395)
load_lut took any source containing "LUT_" for inline .cube text, so a
path like /luts/LUT_Rec709.cube was parsed as text, failed, and the
effect passed the image through. A .cube can only parse with a size
header and data rows on separate lines, so a newline (after trimming,
as the path already is) is what tells text from a path.
Preserve frozen Object Mattes with pre-0.23 footage keys
* Scripting: export commands a script runs directly respect Allow Scripts to Write Files

CompItem.saveFrameToPng and exportAsMotionGraphicsTemplate checked the
file-write preference only in their JavaScript wrappers. A script could call
comp.saveFrameAs or essential.exportTemplate itself (app.run, __exec) and the
native handlers wrote the file with the preference off.

Session::refuse_export and write_output_with now refuse when the running
command was issued by a script and the preference is off, so every command
that writes a path it is given is gated the same way. The wrappers check the
gate with a new side-effect-free __file("checkWrite") instead of writing an
empty file at the destination.

* address review: clear stale export test dir before use

* test: allow script writes in script_imports_footage_and_saves
A GPU project whose Render Queue job measured the CPU faster (#3335) rendered
every frame of a batch at once on the CPU, one per thread, with no memory cap:
on a 16-thread machine at about 1.4 GB a frame the app ran out of RAM and
exited without an error (0.23.1, Windows).

While a job renders on the CPU after a GPU frame measured its working memory,
CPU frames in flight now fit half the memory the system has free at that size
per frame, 1 to one per thread, settled once per job and logged. The probe's
timed CPU frames use the same cap. EFFECTCRAFT_CPU_INFLIGHT still overrides
it; Software Rendering projects, with no measurement, are unchanged. The host
reads the free memory through the new JobOptions::free_memory.
Cylinder Wrap: Radius goes up to 30000 % (#3368)
Apply Color LUT loads files whose path contains LUT_ (#3454)
.aep import: every loss detail in the prompt; no false shape loss; Orientation expressions (#3365, #3087)
Adds step-security/harden-runner (audit, pinned to v2.22.4 like
craft-common-actions) as the first step of each job that did not have it,
so every workflow in the org's public repos reports its egress to
StepSecurity. Audit, not block: block-mode allow lists come from each
job's observed traffic.
Updates: take craft-update from craft-libs main at 6e03f65bf
* Variable stroke: the first curve approximation's tolerance scales with the path's extent

* Variable stroke: cut only curves with huge extents, leaving small curves at the pixel tolerance
* fix(logging): continue a message's line breaks on indented lines

* address review: normalize CRLF before stripping the trailing newline
* UI: ScriptUI images decoded within a size limit; effect detection answers each comp once

ScriptUI decoded any image and converted it to full RGBA without a size
check; images are decoded through image's limits (4096 px per side and a
matching allocation cap). comp_has_effects walked a nested comp again for
every layer using it; each comp is now answered once.

* address review: effect detection by breadth-first search so depth cannot hide a comp
* fix(engine): draw vector footage thumbnails at the thumbnail's size

* address review: shrink a one-pixel-over vector thumbnail to the requested size
* fix(shortcuts): list each binding once and cap the conflict list

* address review: compute the shortcut bindings once per keyboard draw
* automation: confine batch steps whose params are a reference, and sequence render folders holding outward links

A confined engine.batch checked each step's file parameters before the batch
ran, but a step whose whole params was a `$N` reference became an earlier
step's result only at run time, so a file path in that result (for example a
manifest from essential.templateInfo) reached file.saveAs unchecked. Such
steps are now refused while file access is confined.

An image-sequence render writes numbered frame names that the check of its
output template never resolved, and each frame is created through any link
already at its name. A render output whose folder holds a link leading
outside the write roots (or one that cannot be resolved) is now refused when
it is queued and again when the queue renders.

* address review: check numbered render folders for outward links

* address review: fail closed when the numbered-folder scan cannot list its parent; unix-only symlink test

* address review: render-folder link scan fails closed on unreadable folders, entries and file types
* interchange: end export walks on folder and precomp cycles, and index each clip's transitions on import

Export followed a folder's parent chain recursively before caching it, so a
project whose folders loop back to themselves overflowed the stack. The walk
is now iterative and a repeated folder ends it (the cycle's top becomes a
top-level bin, with a warning).

comp_has_audio recursed into nested comps with no visited set: a precomp
cycle never ended and a chain of comps that each use the next twice took
2^depth steps. It now walks with a stack and a visited set.

Import scanned every transition of a track for every clip on it, quadratic in
a long flat edit. Each track now indexes its transitions by clip once, and
the per-transition warnings are said once per track. Warning de-duplication
uses a set instead of a linear search.

* interchange: reuse a folder's cached bin on export

Exporter::bin returned None when the folder itself was already cached, so
every item after the first in a folder was exported at the project root.
Return the cached bin when the walk starts at a cached folder.
* Puppet: clamp an imported Expansion before padding the outline grid

* address review: non-finite expansion compares with zero expansion in test

* address review: trace over-budget puppet outlines at a coarser resolution; NaN expansion test
* Roto Brush, Object Matte: decode frozen mattes only at the layer's full-resolution grid

* address review: frozen matte size mismatch falls back to computed matte

* address review: frozen-size-mismatch test compares against a render with no cache
* Simulations: cap the steps a layer time can ask for and bound checkpoints as they are made

* address review: keep the requested sim state when trimming checkpoints

* address review: drop only the excess sim checkpoints when trimming
* SVG: bound tessellation, kept geometry, attributes, entity expansion and re-rendering

Circles and rounded rects flattened at a fixed 0.01 tolerance, so a huge
radius made unbounded curves; the tolerance now grows with the radius as
arcs already did. Every path a <use> copies and every gradient reference's
stops were parsed and kept without a document-wide limit; both now share a
budget of 8 million items. An element with thousands of attributes made
the XML parser's duplicate check quadratic, and repeated entity references
could expand attribute text to gigabytes below the node limit; a preflight
refuses more than 1,024 attributes per element and entity references that
expand past 64 MiB. Nested knockout and non-isolated groups re-rendered
their children several times per level; node draws are now held to 64 per
node of the tree.

* address review: entity_bytes takes the largest declaration of each name

* address review: drop tessellation-tolerance, attribute-count and entity-size refusals; keep retained and draw budgets, warn when the draw budget is hit
* PSD: bounded zip errors, budgeted prediction row, row-bounded PackBits, channel checks before the result

A failed inflate was formatted with Debug, which prints the whole partial
output; only the status is kept. The 32-bit ZIP prediction scratch row was
allocated outside the decode budget; it is charged now. PackBits packets
were appended whole even past the row's end, growing the reserved plane;
they are cut at the row. The composite and layer decodes allocated the
document-sized result before checking that the channel data exists; the
compression and raw/RLE lengths are checked first.

* address review: check short ZIP composite data and the layer mask channel before allocating the result

* address review: drop the channel prechecks that rejected PSDs the lenient decode accepts; keep the allocation bounds
* Box blur: count repeated edge pixels instead of visiting them

The starting window of each horizontal and vertical box pass visited every
position of the radius, including any number outside the image that repeat
the edge pixel, so a radius from a project could make one pass take
arbitrarily long. The pixels inside are summed once and the repeated edge
pixels are added as a count times the edge value.

* address review: skip zero-count edge pixels in the box passes
* Tracking: bounded crop size, Roto chain span and resection copies

A stored pre-tracking blur or search area sized the padded crop with no
limit; both are held to what the frame can use (blur at most 100, as the
options command allows). A Roto Brush span from the project made one render
build a key for every frame of it, rescanning all strokes per frame; the
chain reaches at most 200,000 frames either side of the base and strokes
are grouped by frame once. The camera solver copied the whole
reconstruction for every unsolved frame, even frames seeing too few points
to resect; those are skipped first.

* address review: in_span uses the MAX_CHAIN-bounded span, forward key range cannot overflow
* EPS: bound copies, storage, text outlines, group nesting and font discovery

The PostScript interpreter's numeric copy grew the operand stack past its
limit and charged nothing for the values copied; array and string
constructors retained storage without charging it; show and charpath copied
every glyph outline into the text path before any path or budget check; and
the font pre-scan searched the whole prefix again for every eexec, as the
bounding-box scan did for every rejected marker on a line. Clipping groups
also nested without limit.

Copies past the operand limit copy nothing; copies, arrays and strings count
against the operation budget; each outline is charged and checked against the
path limit before it is copied; headers are found in one pass and each is
tried once; a rejected bounding-box comment is skipped to its line end; and
the output builder drops groups past 256 open ones.

* address review: EPS font look-back stops at the previous font; charge input characters

* address review: count enclosing groups against the nesting limit in pattern and mask builders

* address review: close a replaced soft mask before nesting the new one; skip patterns whose wrappers pass the depth limit
* Project: layer name suffixes in one pass; template names bounded and never a parent folder

unique_layer_name tried each numeric suffix against every layer, so
importing many layers of one name was cubic. The suffixes in use are now
gathered once. sanitize kept '.' and '..' (a comp name used as a path
component in an output template could point at the parent folder) and
copied a name of any length into every template token; dot-only names
become underscores and names are cut to 255 characters.

* address review: limit sanitized names to 255 bytes

* address review: keep long sanitized names distinct with a hash suffix

* address review: unique layer names use the numbered_name stem rule
* Text: run lookup by bisection, bounded path flattening, font folders and files shared

run_at scanned the style runs from the first for every character; the
ordered runs are now bisected (the scan remains for gaps). Path text
flattened at a fixed 0.05 tolerance, so huge coordinates made hundreds of
millions of points; the tolerance grows with the path's size and
non-finite paths measure nothing. Font name records aliasing one string
are decoded once. The font folder walk listed folders reached through
several links again along every path; each real folder is listed once.
Faces of one font collection each read the whole file; they share it.

* address review: font walk breadth-first across roots, font file cache keyed by size and mtime and read outside the lock, first-subpath path measure with finite check, scratch dir guard

* address review: font files shared through file_bytes with the open file's metadata; run lookup by bisection only for ordered runs

* Pass the runs' ordered flag with them to stay under the argument limit

* address review: reversed run ranges take the scan
* PDF functions and mesh shadings: charge retained values, refuse short tables, bound lattice rows and raster work

Depth-limit fallbacks were not charged to the function node budget; cached
copies of a function deep-copied its coefficient and program vectors while
only its nodes were charged; sampled functions made up missing samples as
zeros; calculator programs had no size limit; a lattice row was filled
before any limit applied; and overlapping mesh triangles each scanned their
whole bounding box.

The node is charged before the depth check; coefficients, bounds, encode
arrays and calculator operators are charged (eight to a node) so cached
copies carry their cost; a sampled function shorter than its table is
unsupported; calculator programs hold at most 4,096 operators; lattice rows
past the triangle limit are refused and vertices stop at it; and triangle
bounding boxes share a budget of 64 passes over the raster.

* address review: bound calculator token count while parsing

* address review: count skipped calculator tokens toward MAX_CALC_OPS

* address review: skip arrays and dictionaries in a calculator program without building them

* address review: scalar tokens in a skipped compound are passed over without building them
* Render settings: a saved Frame Rate override is held to the supported rates

* Render settings: describe shows the rate an out-of-range override renders at
* fix(flatpak): allow release checks and self-update helpers

* test: keep Flatpak permission parsing within strict Clippy rules

* chore: bump version to 0.24.1 for the Flatpak update fix
* VP9 encoder and WebAssembly plug-ins: refuse short planes, limit memories and tables

encode_frame sized and filled superblock-aligned coding planes from the
configured dimensions (up to 65536 x 65536) even when the planes passed in
held a few bytes, making up the missing samples; it now encodes nothing for
planes shorter than the frame. The plug-in sandbox capped each linear
memory but not how many memories or tables a module declares, or their
elements; one memory, four tables and 65,536 elements per table are
allowed.

* address review: checked size arithmetic in the encoder's short-plane guard
* Camera tracking: repeated observations of one camera combine into one contribution per parameter

* Camera tracking: parameter-slot lookup for points that repeat a camera
* track: face skin search visits only window ring pixels; cap stabilizer smoothing window

* address review: stabilizer window test asserts the cap with a far jump

* address review: smoothing radius covers every supported Smoothness (1000%, Smooth More, 240 fps); a frames x radius work cap bounds planning
* Effects: cap Camera Lens Blur and median radii

Camera Lens Blur built its iris table (one row per pixel of radius) from an
uncapped Blur Radius before any size check, and the median window is filled
(2r + 1)^2 per row from an uncapped Radius, on the CPU and the GPU. The
radius is now capped where the work is built: 2000 buffer pixels for the
lens iris (UI max 500) and 512 for the median window (UI max 255), on both
paths.

* address review: cap camera lens reach with the spans
* fix(engine): index sequence runs and folder listings on import; probe relink candidates once

* address review: cap relink candidates before cloning paths; reuse a picked folder's listing for its run lookups
* fix(scriptui): bound the edges and bands a fillPath is tessellated with

* address review: cap the triangles a ScriptUI fill produces

* address review: raise fillPath limits (65,536 edges, 262,144 bands, 4M triangles, work cap); fill what fits and log a warning instead of nothing

* address review: bound the fillPath crossing scan: edges swept by top, pairs and boundaries counted against the work and band budgets

* address review: bands with crossings that were not cut are left unfilled when the crossing scan is over budget

* address review: bands left out of a fill still count against the work budget
Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975)
Some checks failed
ci.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (push) Failing after 0s
nix.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (push) Failing after 0s
packaging-lint.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (push) Failing after 0s
sonarcloud.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (push) Failing after 0s
ci.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (pull_request) Failing after 0s
nix.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (pull_request) Failing after 0s
packaging-lint.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (pull_request) Failing after 0s
sonarcloud.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (pull_request) Failing after 0s
ci-macos.yml / Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads (#2975) (push) Failing after 0s
b47a7104ac
* Scripting: run-time compiled code passes the nesting check; bounded file masks, socket line search and file reads

eval and the Function constructors compiled script-built strings without
the bracket-nesting check the script itself passes, so a short script could
overflow the parser's stack. A host hook now checks every dynamic
compilation. Folder.getFiles turned masks into a backtracking regular
expression; a two-pointer wildcard match replaces it. Socket readln
rescanned the whole buffer after every read; only new bytes are searched.
Script files that are not regular files (pipes, devices) are not read.

* address review: check_nesting scans template interpolations, file masks keep wildcard length

* address review: file masks fold case as /i does; the nesting check skips regular expression literals

* address review: nesting scan reads ++/-- and keyword-named properties as division context

* address review: property-name state set at each word start; a number's decimal point is not a property dot

* address review: a leading hashbang line is a comment for the nesting scan; eval source is checked as it stands
alf merged commit b47a7104ac into main 2026-11-11 21:50:04 +00:00
alf deleted branch chore/sync-upstream-20261111 2026-11-11 21:50:05 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
iris/effectcraft!24
No description provided.