An Object / Describe mask is stored as a model's logit grid (288² over the whole photo,
~20 px cells on a 24 MP export) and was enlarged by bilinear sampling alone, so its edge
spilled 1–2 px onto the background; raising the mask's Exposure turned that strip into a
bright outline in previews and exports (CPU and GPU: the GPU renderer evaluates these
shapes with the same CPU `shape_alpha`).
`sample_seg` now guided-filters the sampled logits by the photo's log luminance where the
grid is coarser than the output: the fast (subsampled) guided filter, a window of 1.5 grid
cells, so a preview and an export put the edge in the same place. Filtering logits, not
coverage, lets the confident side win (the spill goes to ≈ 0, not to a mean of ≈ 0.1).
Pixels whose window is all inside or all outside, and edges the photo has no contrast
across, keep the plain sample exactly. Refine Edges still runs afterwards on the combined
mask and finds the edges in place: no second halo, no tightening.
One implementation: `guided_cross` (Refine Edges, Auto Mask) and the new
`guided_cross_fast` share `guided_coeffs`; the subsampled form uses
`local::guided_fast_step` and a box `resize`, like `local::guided_fast`. `guided_cross`'s
output is unchanged.
Existing AI masks become 1–2 px tighter at a subject's edges, on every process; their
inside, outside and contrast-less edges are unchanged. This lands in place under the
bug-fix rule of docs/process-versions.md (output that is in neither the photo nor the
edit), noted there.
Cost per AI selection (M2 Pro, 12 cores; one shape_alpha, before → after):
24 MP 25.7 → 48.2 ms, heap peak 96.5 → 108.8 MB; 60 MP 64.1 → 109.9 ms, 239.9 → 256.7 MB;
2.7 MP preview 3.1 → 10.6 ms, 11.2 → 26.5 MB.
Tests (a dark disc on a light background, its mask on a 32² grid): ≈ 0 one and two px
outside and ≈ 1 inside, all around (1 px outside was 0.66), bit-identical away from the
edge; with Refine Edges 50 / 100 (was 0.29 outside) and the edge within 1 px of the
unrefined one; the same edge position at 256, 512 and 1024 px; a Prompt mask's window
matches the whole render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Honor CARGO_BUILD_TARGET in FreeBSD packaging for aarch64 release builds
package.sh always built for the host and took the arch from uname -m, so a
cross build (aarch64 in an x86_64 FreeBSD VM) would read the wrong target
directory and name the archive after the VM. With CARGO_BUILD_TARGET set,
pass it to cargo, read target/<triple>/release, and take the arch from the
target. Unset keeps the old behavior.
* Reject unknown CARGO_BUILD_TARGET in FreeBSD packaging
Only the two FreeBSD triples are packaged. Any other target now fails before the
build instead of being labelled x86_64.
* Skip the --version smoke run for cross-built FreeBSD packages
A binary built for another target cannot run on the build host, so under set -e
the run would fail the release after the archive is written.
The restore actions use the recorded folder without checking that it still
exists, so an upgrade creates it again rather than using the default folder.
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.