Resizing a rectangle or ellipse while keeping its proportions (a circle
that stays a circle, a 16:9 card that stays 16:9) needed W and H typed
or keyed separately. The Timeline's Size row of a shape's Rectangle
Path and Ellipse Path now has the chain link that Scale and Mask
Feather have. It starts off; turned on, typing or scrubbing W or H
keeps the ratio (Alt still edits one value).
Scale and Mask Feather keep their linked-by-default state in `unlinked`;
Size, which starts unlinked, records a turned-on link in a new `linked`
set, so neither default changes the other's saved state.
Found while testing crates/engine/src/cmd/align_data.rs:75 (parse_stamp)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/svg/src/lib.rs:510-520 (parse_css)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The merge of main put craft_quit::on_close_request inside
EffectcraftApp::tick, so the control channel, autosave, screenshots and the
desktop host's file dialogs still ran after it in the same logic callback.
The tick keeps the unsaved-changes check; each desktop host's logic now calls
panels::unsaved::quit_on_close last, after the device-loss close as well.
Found while testing crates/lottie/src/import.rs:48-51 (hex_color)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The policy check now reads the compiled AppSearch and RegLocator rows for the
remembered-folder properties (registry root, key, value name and type for both
scopes) and runs the DIR, FOLDER, PATH and legacy exe actions in sequence order
against seeded values, so their precedence and the preset-folder guard cannot
regress. It also replays the wizard folder actions in sequence order (a
remembered folder beats the new-install default, a typed folder beats both) and
requires the machine restore action between the scope page and costing.
An 8 bpc render has no finer values to dither, so its exports stay byte for byte what
they were (the pinned H.264 and ProRes files) and keep their size; 16 and 32 bpc renders
are still dithered on the way to 8 bits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
`backpressure` stopped writing at the first WouldBlock. On macOS the
accepted stream does not keep the listener's 4 KiB receive buffer (it
gets about 114 KiB), so that WouldBlock only meant the 4 KiB send buffer
was full; it drained into the peer, and the 64 KiB write in
a_write_to_a_peer_that_stops_reading_fails_after_the_timeout and
a_blocked_socket_doesnt_hold_up_the_others_and_close_interrupts_it then
succeeded instead of blocking. Both tests failed on every run.
The helper now stops only after writes have stayed blocked for 200 ms,
still within its 16 MiB and 5 s limits. The socket code is unchanged.
The ROI crop clamped every converted edge to +/-20_000 px, which changed
valid crops: a supported 30_000 x 100 frame at scale 1 with ROI
[10000, 0, 20000, 100] produced 10_000 x 100 instead of 20_000 x 100.
Valid edges reach about 120_000 px (30_000 px at render scale 4.0).
Clamp converted edges to +/-1_000_000 instead, subtract from the frame size
in i64 and narrow to i32 with saturation, and widen the arithmetic in
OutputModule::frame_size to match. Hostile quads (huge, inf, NaN) still
degrade to a valid 1 px frame without overflow.
Regression test next to hostile_roi_cannot_overflow_crop_math covers the
30_000 x 100 case, a scale 4.0 wide case and hostile inputs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
Exports converted the float frame to 8 bits by plain rounding, so a smooth
gradient rendered at 16 or 32 bpc came out as flat bands, the same as at
8 bpc: raising the depth changed nothing visible. After Effects dithers
colours converted to 8 bpc for render and export; do the same.
- raster: dither_u8 and Image::to_rgba8_dithered / to_rgba8_over_dithered
round each channel up or down against a hash of the pixel position with
a fixed seed (deterministic, no clock or global RNG). A value already on
an 8-bit code, as everything rendered at 8 bpc is, is unchanged.
- export: Cx::pixels dithers the colour for every 8-bit output (stills,
H.264, ProRes, HEVC, AV1, VP9); GIF keeps plain rounding (palette).
- export: the Render Settings' Color Depth now overrides the project's
depth for the whole render instead of only quantising the finished
frame, so a 16 bpc render of an 8 bpc project keeps its precision.
Tests: crates/export/tests/it/banding.rs (ramp export at 8/16 bpc and with
Color Depth overrides), raster unit tests for dither_u8.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
Turbulent Displace read its noise through value_noise, whose quintic fade also
eased the evolution axis. Near every whole revolution the pattern hardly moved:
Evolution 0 and 37 degrees (or 360 and 400) differed by well under a hundredth of
the frame, so different Evolution values looked identical, and the motion sped up
and slowed down within each revolution (the #1816 symptom).
Add value_noise_linear_z (x and y keep the fade, z is linear) on the CPU and in the
WGSL kernels and use it for Turbulent Displace on both paths. value_noise is
unchanged, so the other effects and the pinned simulation goldens are unaffected.
Tests: CPU test that non-equivalent Evolution values (fractional, past 360, past
720) all differ and that Cycle Evolution still matches 0/360; unit test of the
linear z axis; GPU-vs-CPU cases at small and large Evolution.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM