Skip to content

Releases: skymanbp/autoshade

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 17:26

Built by GitHub Actions from 0739c4add191acf70da2d2ea13b38299f3eb5e07.

Every asset below is reproducible from this tag: https://github.com/skymanbp/autoshade/actions/runs/33536185764

checksums.txt carries the SHA-256 of each file.

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 05:18

Two things happen in this release. The application is now called AutoShade,
which touches its name on disk, in your environment and in every URL. And the
reverse fit stops publishing statistics whose premises it had itself denied:
white balance is solved on one population instead of three, the zone boundary
gate charges only for what a correction introduced, and six places that
printed, persisted or documented a claim the code could not support now say
what is true.

Everything that changes a render is described first, and every render change
here is a correction to a statistic — none of them is a taste adjustment.


The app is called AutoShade

The rename is complete in this release and reaches everything a user touches:

v1.1.0 v1.2.0
Executables autoshop.exe, autoshop-gui.exe autoshade.exe, autoshade-gui.exe
Installer Autoshop-Setup-<v>.exe AutoShade-Setup-<v>.exe
Environment variables AUTOSHOP_* AUTOSHADE_*
Settings file autoshop.local.json autoshade.local.json
Repository github.com/skymanbp/autoshop github.com/skymanbp/autoshade
Site skymanbp-autoshop.dev autoshade.dev

Nothing you have made is renamed with it without being moved: see Upgrading
below for the three directories that migrate themselves on first launch, and
for what still answers to the old spelling and for how long.

Render changes

White balance is solved jointly, on one population

The global Atmosphere solve read three independent weighted medians, one per
channel. On a bimodal frame those medians come from different sub-populations:
on the calibration island pair median(R) = 0.1488 sits in the dark warm land
while median(B) = 0.2421 sits in the bright blue sky, so their ratio (0.8293)
is not the colour of any pixel in the frame — the linear MEAN ratio for the
same pair is 0.9991, i.e. the target's white balance simply was the source's.

v1.2.0 takes a weighted median of the per-pixel log-ratio over ONE population,
read through the correspondence-remapped target the zoned path already used. A
pair whose pixels changed no chromaticity now persists no cast at all: measured
K = 5500, tint = 0.0, against tint = +55.2 / K = 4400 before.

Exposure and white balance are read over the shared content

median(target)/median(source) over two whole frames presumes the two frames
describe the same content — which is exactly what selecting Atmosphere mode
denies. Where a cross-image correspondence field exists, both medians are now
read over the SHARED-CONTENT population: target pixels no confident source cell
maps onto (generated content, not a rendition of this frame) and source pixels
whose content the target replaced are both dropped first.

Both sides, not one. On a synthetic pair whose invented region owns every
whole-frame median (truth 1.2181 at 0.00 EV), whole-frame answers 0.911/+0.694,
target-only 1.945/−2.867, source-only 0.512/+3.593, and the two-sided cut
1.216/+0.032.

The cut is binary at the same CONFIDENT_MATCH = 0.5 the disclosure already
publishes, from the very bitmap that share is counted from — one derivation,
two consumers, so the sentence and the population cannot disagree. When either
side keeps less of its own evidence mass than SHARED_POPULATION_MIN_RETENTION
(0.35, the evidence model's own range-survival floor) the restriction is
refused and the whole-frame reading stands: solving a global control on a
corner of the frame is the same failure in a different costume.

Across the seven-pair corpus: the island pair restricts to 84 % / 76 % and its
render changes; the calibration pair restricts to 58 % / 50 % with identical
dials; one pair retains 11 % / 7 % and refuses; the four Full pairs never
consult a field. Six of seven recipes are field-for-field identical and three
full-resolution renders were verified byte-identical. A pair with no
correspondence field is byte-identical by construction.

A zone correction can now be bought on an absolute gain

The two existing acceptance arms for Full zones are both RATIO yardsticks with
nothing absolute in them. The calibration land zone improved 0.078 → 0.054 with
the frame moving −0.00004 and every quality gate clear, and was dropped only
for landing at 69 % of its start instead of 50 %.

A third arm buys such a correction when the ABSOLUTE zone gain clears
ZONE_MIN_ABS_GAIN = 0.012 and the frame-global reading does not regress at
all
— zero regression, stricter than the semantic route's own 0.02 drift
insurance and equal to what the spatial, range and free-mask routes already
demand. The floor is derived twice over: half of the one measured instance
(0.024, n = 1, so it sits a factor of two under rather than on it), and the
ceiling of the observed already-matched domain. It is a separate constant
because those two calibrations only happen to agree today. The arm never
reaches an Atmosphere zone, and an admitted correction names the arm that
bought it — including naming the one quality gate known NOT to discriminate.

The boundary gate charges only what the correction introduced

  • boundary_rim measured an absolute quantity on a single render with no
    reference, while its sibling boundary_step was already differential and the
    gate's own comment claimed it was "monotone in the differential". It
    therefore billed a zone correction for a rim the global stage had already
    produced — the disclosure could read "still 0.058 against a 0.012 budget with
    k = 0", which by the gate's own construction cannot be caused by the
    correction it had just refused. Four plane-synthetic fixtures hid this,
    because on a flat frame an absolute reading equals a differential one.
  • The rim is now transported: M = linear(rendered) / linear(reference) is
    applied to the reference luma before differencing, and the percentile ranks
    by MAGNITUDE so a dark seam cannot hide behind a bright one.
  • A shrink factor of zero used to attach anyway: k reached only the
    disclosure note, so a k = 0 tile still occupied the exclusion quota while
    printing a false before/after. Differencing makes k = 0 read exactly 0.0,
    which turns that from a rare path into the guaranteed one — so an inert
    correction is now refused through the two gates' existing rejection paths.
    The test is byte identity of the render, deliberately not a threshold on k.
  • ZONE_BOUNDARY_RIM_MAX is split from ZONE_BOUNDARY_STEP_MAX (both 0.012).
    One constant feeding two rulers would silently retune three spatial tiles
    sitting on 1.7–3.3 % margin the moment either is recalibrated.

Style and generation

Distillation carries colour, not only tone

At Style 1.0 the pull reached twelve flat sliders and nothing else, so
vibrance and saturation were replaced by the library's means while the
mixer, the grade wheels, the curve and the masks carried no target at all —
colour could only be subtracted. The fix is not a cap on the pull; it is the
rest of the vocabulary. Five channels, one mechanism (lerp toward a retrieved
mean), and the pull curve itself is untouched:

  • the twelve flat globals — unchanged and ungated;
  • the 8-band mixer's saturation and luminance, per band;
  • the four grade wheels' sat/lum, and their hue as a saturation-weighted
    CIRCULAR mean, learned only for a wheel whose own intensity is a habit;
  • the master tone curve's shape, written back through points that pin inputs
    0/64/191/255 so the pull lands exactly and the curve stays monotone;
  • each mask's slider amounts — AMPLITUDES ONLY, no coordinate read or written,
    addressed by name so a widened slider list cannot pull the wrong one.

Also honest now: style_targets and blend_toward both documented a cap that
no caller applies. Style 100 % reaches the target; three doc comments and the
call site say so. And the distillation disclosure, which used to carry one
percentage and no answer to "toward what?", names every field that moved,
measured from the two recipes. Masks are named by position, never by their
name field — that is user text and can carry a photo's file name.

Colour has dimensions in the prompt, not adjectives

Tone had numbers that opened and closed with the intensity axis; colour and
curves had only adjectives, so the model treated "don't touch colour" as the
safe default. Every one of the 17 corpus curves has exactly five points and a
largest departure from the straight line of median 13/255 — a 2 % use of a
control whose engine limit is 256 points, and whose truncation counter has
never once fired. The prompt was the whole cause: it asked for "a 3-5 point
tone curve forming a gentle S", so the model read the top of the range as the
target shape and "gentle" as the amplitude.

  • HSL single-band guardrails ±10–20 → ±20–40; the four grade wheels' combined
    budget 20–40 → 40–80. Both ride a ONE-SIDED ramp above the calibration point,
    so at intensity ≤ 0.5 the factory wording is byte-identical and the axis only
    ever loosens.
  • Master curve 3–5 → 7–9 points and 8–15 → 15–30 of 255 in amplitude; channel
    curves 4–8 → 8–15 of 255; plus an unconditional monotonicity constraint, so
    the extra points buy a more precise shape rather than ripples.
  • The neutral escape hatch is now templated: at the restrained setting it is
    byte-identical to the factory sentence; above it, a neutral answer must
    explain in the rationale why this frame really has no colour to shape.
    Neutral is an answer to defend, not a default to slide into.
  • Two things deliberately do NOT ride the axis: the judge's rubric gains a
    positive colour criterion, and the verifier's checklist gains colour
    completeness and curve monotonicity — both written both ways. Judging
    standards should not swing with the user's dial.
  • M...
Read more

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 03:37

Built by GitHub Actions from 7e61288475623ef263e9270369951abc508ded49.

Every asset below is reproducible from this tag: https://github.com/skymanbp/autoshop/actions/runs/33353741532

checksums.txt carries the SHA-256 of each file.

Autoshop v1.0.0

Choose a tag to compare

@skymanbp skymanbp released this 24 Aug 18:07

Autoshop v1.0.0 — the first major release

Autoshop v1.0.0 turns the post-v0.35.0 Lightroom measurement work into a
clearer mask-frame contract, improves modern sidecar import, and makes the
project documentation and release checks harder to drift. The deterministic
develop path remains recipe-based: AI proposes or derives intent, while the
Rust renderer owns the pixels.

What changed since v0.35.0

Rendering fidelity

  • Angled LINEAR masks now evaluate their projection in the frame's pixel/aspect
    metric instead of normalized-coordinate distance (ecb6505). On the measured
    9504×6336 probe, the half-contour error fell from 874 px to 9.8 px;
    axis-aligned gradients and square frames remain byte-stable.
  • RADIAL mask transport now uses Sony 0x7037's measured (i+1)/16 native
    radius law, a metadata-derived full-raw-frame centre, a 64-knot persisted map,
    and the exact-once m_lr^-1 ∘ T_engine sampler (706ac84). All 41 measured
    radial point vectors close to at most 1 px.
  • LINEAR masks now use their measured H2 topology instead of sharing RADIAL's
    pointwise sampler (ad6de62). With correction enabled, Autoshop reconstructs
    the straight gradient in the corrected frame. With correction disabled, it
    transports only the Zero/Full handles and rebuilds one straight line in the
    raw pixel metric.

Lightroom interoperability

  • Camera-metadata lens profiles now preserve the mask-warp centre as
    LensProfile.mask_warp_center; disabled-sidecar LINEAR transport preserves
    its separate camera map as LensProfile.linear_handle_warp.
  • Modern Lightroom MaskBrushTable data can be read from the companion .acr
    content store with strict directory, digest, envelope, Brotli, and payload
    validation (2cb59a5). Supported table-encoded brushes now import and render
    instead of producing the previous named out-of-model refusal.
  • Unmodified table-backed mask groups remain byte-preserved on sidecar merge.
    Unknown or malformed table forms are still refused by name rather than
    guessed or partially imported.
  • The lens-profile refusal model remains explicit: unsupported fisheye LCPs,
    unparseable profiles, and missing profile roots do not silently invent a map.

Masks and local AI

  • Gesture dabs attached to subtype-0 object masks now become ordered positive
    SAM 2.1 point prompts (1e99e84). Prompt transfer uses a bounded temporary
    JSON file rather than an unbounded command line.
  • Only subtype-0 masks that carry gestures receive the new gp1 cache identity
    and re-derive once. Subject masks, sky masks, and gesture-free object masks
    retain their previous cache keys and behavior.
  • Object/background intent remains ambiguous in Lightroom's subtype-0 carrier;
    Autoshop continues to disclose that it locally re-derives an alpha rather than
    importing Adobe's unavailable raster.
  • The training-data scope comments were audited and corrected (70832eb):
    Autoshop distributes neither ADE20K nor SA-1B, while the downloaded
    OneFormer and SAM runtime artifacts remain governed and digest-gated under
    their own MIT and Apache-2.0 terms.

Application and evaluation

  • A new per-user Windows installer needs no administrator access, creates Start
    Menu shortcuts, offers optional desktop and user PATH tasks, and preserves
    the develop store on uninstall.
  • Evaluation rows with fewer than 20 observations are marked [low n], and a
    supplementary n-weighted gap is reported without changing the established
    headline, state-file, or n≥20 definitions (ef5a71a).
  • Retry disclosures now distinguish native Anthropic 529 refusals from relay
    failures that may have completed upstream, and warn that retrying relay
    524/529 failures can incur a second charge.
  • Advisor error bodies use the same bounded read policy as the other response
    paths. Store safety tests now use explicit temporary roots rather than a real
    user data location.
  • A same-version rerun measured a 16.3% evaluation gap versus the 17.7%
    baseline. That 1.4-point resampling swing is comparable to the prior
    1.7-point cross-version change, so the 17.7% figure is not evidence of a
    rendering regression.

Documentation and release gates

  • The README is now a structured install guide, user manual, CLI reference,
    Lightroom/AI explainer, and measured showcase (53bb77f).
  • Architecture and planning docs now state the RADIAL and LINEAR frame laws,
    schema breaks, precision limits, and the current test battery.
  • scripts/check_docs.py now checks the exact README RAW-extension membership,
    the exact no-preview format membership, and README/ARCHITECTURE battery
    consistency in addition to the existing version, count, dependency,
    toolchain, camera, and corpus checks.

Hard changes and compatibility

v1.0.0 remains backward-compatible with older recipes: both new lens-frame
fields have defaults, so recipes written by earlier releases remain readable.
Forward compatibility is deliberately strict. Older executables use
deny_unknown_fields for LensProfile and therefore refuse a v1.0.0 recipe
that carries either mask_warp_center or linear_handle_warp. Refusal is safer
than silently discarding a coordinate transform and rendering the mask in the
wrong frame.

The following existing content can render differently in v1.0.0:

  • angled LINEAR masks on non-square frames, because their metric is now
    pixel/aspect-correct;
  • RADIAL masks paired with camera-metadata lens profiles, because the native
    knot law, map centre, and exact-once transport are now applied;
  • LINEAR masks paired with camera-metadata lens profiles, in both the
    correction-on and correction-off paths, because LINEAR now follows H2;
  • modern table-encoded Lightroom brushes, which move from named refusal to
    actual brush rendering; and
  • subtype-0 object masks with gesture dabs, whose SAM prompt set and scoped
    cache identity now include those dabs.

No released Autoshop version shipped the intermediate 706ac84 LINEAR
behavior; ad6de62 is the v1.0.0 LINEAR contract.

Known limitations

  • RADIAL point transport is 41/41 at ≤1 px, but the R2 large-mask dilation has
    an open residual of about 1.2 percentage points. Clean cells are within
    0.35 pp and R1 is about 0.5 pp.
  • LINEAR H2 is not 1 px-closed. Correction-on residuals are
    9.748/7.025/6.336 px RMS; correction-off residuals are
    12.449/9.943/4.979 px RMS. A fitted anisotropic-aspect term remains diagnostic
    only and is not shipped.
  • AI masks are local re-derivations, not Adobe raster imports. Model or sidecar
    failure leaves the adjustment skipped and disclosed rather than applying an
    inverted mask to the whole frame.
  • The X-Trans path is a geometry-aware approximation, not a
    Markesteijn-class directional demosaic.
  • Generative reimagine/retouch outputs are lossy, resolution-limited targets,
    not full-resolution deliverable masters.
  • Prebuilt artifacts are Windows-only for this release; Linux and macOS are
    built and tested in CI but remain less exercised interactively.

Validation

The v1.0.0 source battery is 871 library tests (862 pass + 9 ignored forensic
probes), 14 CLI tests, 132 GUI-feature tests, and two integration suites with
2 tests each. The GUI-feature Clippy and compile gates are clean.

Checksums

Artifact Size SHA-256
autoshop.exe (CLI) 31,180,152 bytes 116a38410a810b1b27602c97daa4db614241b89fffbb80c6691a275fc7f168c0
autoshop-gui.exe (desktop app) 40,810,704 bytes 847f42c4b35c09ab5dd040fdf8e90f99d597c66624ef131ac02d93071bcb58ce
Autoshop-Setup-1.0.0.exe (installer) 19,768,387 bytes 28c4acd37089e78bf02182cd8b20a214a63cababb1b02971209be3fdf33d4750
autoshop-1.0.0-windows-x64.zip (portable archive) 27,131,443 bytes 47389ed42f80798ead96980d69ce10f5063ece606e0f0d548482c58aef9f717e

v0.35.0

Choose a tag to compare

@skymanbp skymanbp released this 22 Aug 01:03

Autoshop v0.35.0

The largest render-correctness release on this branch: brush masks actually render, the radial feather falloff is a measured 290×11 table, negative texture has its measured shape, every mask samples at pixel centres, parametric masks live in their measured Lightroom frames, and the subject segmenter is a pinned BiRefNet with a named fallback. Seven of these changes are breaking in one direction or another — read the list below before upgrading a shared library.

⚠ Seven hard changes (read before upgrading)

  1. LensProfile schema forward break. The recipe's LensProfile block gains mask_warp + mask_warp_src, always serialised, and the block refuses unknown fields — so every recipe.json written by v0.35.0 is refused by name by v0.34.0 and earlier (the whole recipe, not a dropped field). The reverse is lossless: v0.35.0 reads every old recipe, an empty mask_warp is identity, and rendering of old recipes is bit-identical on this axis.

  2. Parametric masks change frames. Lightroom stores a radial/linear in the post-lens-correction frame and a brush in the pre-correction frame; the engine used to read both in one frame. Radial/linear now map through the exact inverse of this engine's own geometry stage; a brush is identity. Old recipes: on a geometry-active frame, radial/linear masks land 0.09–0.30 px from their stored coordinates instead of 8–24 px out — which means those renders change. No lens profile, or geometry off: bit-identical.

  3. Brush masks render. The brush arm was a literal 0.0 (carried, disclosed, not rendered). It is now a real rasteriser — a measured dab kernel, screen accumulation, LR-anchored. Every recipe with an LR brush mask renders differently (the mask used to not exist). The disclosure renames with the behaviour: BrushCarriedBrushRendered — the alpha is our measured model, not Adobe's rasteriser.

  4. Negative texture changes shape. The notch filter is replaced by a measured dual-lowpass elevated step with a hyperbolic depth law, pinned to 45 LR anchors. Everything with texture < 0 (global or per-mask) re-renders; texture ≥ 0 is bit-identical.

  5. Radial feather falloff is now a measured table. After three refuted closed forms, the falloff ships as a measured 290×11 LUT (11 feather columns from a dedicated export ladder); Feather = 0 keeps the analytic hard edge byte-for-byte. Radials with Feather ≥ 10 re-render — at Feather 100 the old law painted 2.08× Lightroom's area.

  6. AI masks all recompute once. The subject backend is a commit-pinned BiRefNet (e2bf8e44…) with a named U²-Net fallback tier, and AI_BACKEND_GENERATION moves 1→2: cached alphas from v0.34 and earlier recompute once on first develop, all three subtypes (sky/object backends did not change; one generation constant covers three fields by design). A machine that fell back re-upgrades itself once the dependencies appear, and says why.

  7. Half-pixel sampling. The whole mask family (radial, linear, brush, bitmap, AI) now samples at pixel centres ((x+0.5)/w) — measured against Lightroom on two different frames. Every mask shifts up-left by 0.5 px, independent of feather, geometry, or aspect.

Also in this release

  • Adobe .lcp lens-profile reading: profiles are located, parsed and solved into the recipe as a named model of Lightroom's warp with a seven-state provenance tag (in-camera knots, solved .lcp, or an honest named refusal). The solved model is carried provenance — not yet a render-time input; a frame this engine does not itself correct still renders masks at their stored coordinates.
  • --long-edge export sizing on the three render commands.
  • One bounded retry for HTTP 524/529 with per-class billing disclosure; eval checkpoint/resume on by default.
  • Segmentation closeout: named backend in every report line, one-shot backend marker migration, digest-pinned model sources, a self-authored ADE20K class table (no third-party licence carry).
  • Pre-release: an independent read-only model review was adjudicated finding-by-finding; two confirmed gaps (a cache entry bound, a bounded .lcp read) landed before this tag. Ledger: docs/ROADMAP.md.

Gates at this tag

  • Battery: 816 lib (9 ignored) / 14 CLI / 132 GUI / 2+2 contract — both configs (default & --features gui), clippy clean.
  • RAW zoo: 9/9 makes decode and self-agree (AUTOSHOP_RAW_ZOO).
  • Real Lightroom sidecars: 7 sidecars / 42 masks imported, 0 refused (AUTOSHOP_MB_FIXTURES).
  • LR probe fixtures: measured-ellipse decode green (AUTOSHOP_LR_PROBE_FIXTURES).
  • check_docs --gates: 22/22. GUI font subset: 803/803. i18n audit: clean.

Downloads

file bytes sha256
autoshop.exe (CLI) 31,063,300 e48f6bbc9a6e9bf4aa98b01240eb6d733136d68320b6e79312d9b239bddfa6c6
autoshop-gui.exe (GUI) 40,706,348 b58b7be4b5e83b3f70e33b8023aba3bcfb985a1762d6eed7e39ef1deafb9a492

Recipes are JSON; v0.35.0 reads every recipe earlier releases wrote. v0.34.0 and earlier refuse v0.35.0 recipes by name (see item 1).

v0.34.0 - X-Trans colour, hardened budgets, typed scopes

Choose a tag to compare

@skymanbp skymanbp released this 20 Aug 12:39

Autoshop v0.34.0 — the R28 round: X-Trans colour, hardened budgets, typed scopes

Five batches, all landed 2026-08-20. No recipe-schema changes: a v0.33.0
binary reads a v0.34.0 recipe and vice versa
— the first feature release
since v0.30 with no forward break.

The headline: Fujifilm X-Trans renders correct colour

Through v0.33.0, X-Trans RAFs developed visibly green and dark. The root cause
was starker than a detail approximation: the Bayer demosaic's chroma pass
assumes the right/down neighbours of a green photosite carry the two chroma
colours, and inside X-Trans's four 2×2 all-green blocks per tile that
assumption fails 16 times per 36 pixels — R was never written (left at zero)
at 8 of every 36 photosites, B at another 8.
No white balance can repair a
per-channel hole.

v0.34.0 demosaics non-Bayer RGB arrays in-tree over the array's own geometry:
every channel is interpolated only from photosites that actually measured it,
with per-phase plane-fit weights (convex, no ringing). Measured on the release
binary against the CC0 X-S10 zoo sample: whole-frame G/R 1.5503 → 0.9473
(the camera's own preview reads 0.95), per-phase channel spread R 157.8 % →
0.34 %. All eight Bayer-format zoo renders hash byte-identical before and
after. Fine detail remains softer than a dedicated X-Trans converter and the
render's disclosure line says so — the old line's "colour, tone and framing
are unaffected" clause was measured false and is gone.

Behaviour changes (all deliberate, all disclosed)

  • Negative Texture is band-limited. texture = −100 used to degenerate to
    a full Gaussian blur (the transfer's endpoint was literally the blurred
    frame). It is now a mid-band smoothing: at −100 a 4 px pattern keeps 96 % of
    its contrast while a 16 px pattern still loses 71 %. The positive branch is
    untouched (it was measured against Lightroom in R27). The negative curve is
    ours and stated as unmeasured — there is no LR ground truth for it in the
    tree, and the sidecar still carries the raw slider value.
  • A RAW too big to develop is refused by name. The RAW door gains the same
    4 GiB per-file peak ceiling the baked door has had for months, charged on a
    header read before any sensor row is decompressed. At the measured
    31 bytes/pixel that is ~138.5 MP and up (a 150 MP back is caught; a 102 MP
    GFX is well clear). The message names the estimate, its basis, and says
    outright that --jobs 1 will not help. Such files used to be attempted and
    would page the machine.
  • AI-mask alphas re-derive once. The segmentation cache key now records
    the frame (quarter turns) it was computed in — closing a bug where a mask
    clicked exactly at the frame centre kept serving its pre-rotation alpha
    sideways. Every alpha cached by v0.33.0 re-runs the local segmenter on its
    first develop (seconds of GPU per mask), reported as a normal re-derivation.
  • AI masks on a saved retouch master now render. The resolver takes the
    photo for identity and the render source for pixels; a baked master used to
    fail the RAW-only staging decoder and the mask was carried inert.
  • The eval hue rows changed meaning. The four color_grade.*_hue rows
    count a photo only when BOTH sides put saturation on that wheel — a hue at
    zero saturation renders nothing, and the old rule manufactured a 141°
    "disagreement" between two colourless wheels. The report discloses that
    these rows are not comparable with pre-v0.34.0 runs.
  • The style-embedding sidecar loads in half precision by default and is
    single-flighted (one resident model regardless of worker count):
    4 × 1.50 GB of fp32 SigLIP weights becomes 1 × 0.75 GB.
    AUTOSHOP_EMBED_FP32=1 restores the old arithmetic exactly.
  • Batch transcripts are attributable. Worker-reachable warnings carry
    their photo's stem, and batch renders the proposer's note channel into
    each photo's own output block the way eval always did.

Hardening (defect classes closed, each with a gate that keeps it closed)

  • Every store-file read goes through the capped readers (four bypasses closed;
    a source-scanning test now fails on any new bare read).
  • Brush-dab truncation cuts on token boundaries and is disclosed on every
    surface; one dab token is bounded at 256 bytes; the import-side clamp
    summary is no longer discarded.
  • XMP read scope is a type (Tag vs Scope): a per-element read can no
    longer silently become a subtree search. Four adversarial scope fixtures pin
    the difference; the 16 real-Lightroom sidecars byte-round-trip unchanged,
    which is the proof the redesign moved no real-LR behaviour.
  • The docs gate lexers handle block comments, cross-check literal counts
    against [&str; N], and mask quoted spans in the TOML census.
  • The --jobs planner consults per-file peaks where headers are free (a
    native-resolution 16-bit TIFF from LR's "Edit in…" can exceed the corpus
    constant on its own, and now says so); the 1,800 MB constant was re-measured
    with the full-resolution render included (peak 1,771 MB — the render tail
    does not raise it) and the probe harness is now in-tree and re-runnable.

Gates at release (both build configurations)

clippy clean ×2 · 727 library (9 #[ignore]d forensic probes) / 11
CLI
/ 131 GUI / 2+2 contract · RAW zoo 9/9 (full test name) · 16/16 real
Lightroom radial sidecars byte-round-tripped · M-B forensic corpus 42/42 masks
· i18n audit 0 · doc-drift gate 11/11 with the release battery transcript.

The format-strip .raf tile in the README was re-rendered with this release.

Also in this release

Cross-platform compilation is CI-verified on ubuntu-latest and
macos-latest in both feature configurations on every push to main (compile
only — no tests run in CI, and no binary has been executed on either
platform). Prebuilt binaries remain Windows-only.

Autoshop v0.33.0

Choose a tag to compare

@skymanbp skymanbp released this 20 Aug 03:55

Autoshop v0.33.0 — R27: multi-format input, parallel processing, and the biggest forensic round yet

Ten batches, five pixel-measurement campaigns, and three controlled Lightroom
experiments (two of them executed via synthesized sidecars — no hand painting).

⚠ Forward-compatibility breaks (v0.32.0 reading v0.33.0 recipes)

  • A v0.33.0 recipe that carries a 90° rotation (quarter_turns), a
    brush mask, or an AI mask entry is hard-refused by the v0.32.0
    exe — a loud schema refusal by design (deny_unknown_fields), never a
    silent drop. Recipes using none of those serialize byte-identically to
    v0.32.0 and stay readable both ways.

Behavior changes

  • Radial masks now render at the sidecar's stored geometry. The former
    1.032 frame constant was proven — by a LensProfileEnable A/B export and
    an 11-dab displacement field — to be one frame's lens-profile warp
    mistaken for a universal affine. Previous builds dilated every imported
    radial by 3.2%. The residual on any frame is now that frame's own
    (unmodelled) Adobe lens warp, 0–3.4% observed; an .lcp reader is the
    candidate fix for R28. Byte round-trips are unaffected.
  • batch transcripts are index-ordered (previously completion-ordered:
    [7/50] could print before [3/50]).

New

  • Multi-format input: 24 RAW extensions (Canon CR2/CR3, Nikon NEF,
    Fujifilm RAF, Olympus ORF, Panasonic RW2, Pentax PEF, DNG, and more —
    validated against a CC0 9-camera zoo) plus baked formats
    (JPEG/PNG/TIFF/WebP/BMP/GIF). Formats without an embedded preview degrade
    to a neutral develop with disclosure; X-Trans demosaics through the
    Bayer path, disclosed per render. .x3f is excluded (upstream decoder
    panics; guarded).
  • --jobs N parallelism on batch and eval, capped by a memory
    budget
    (one 61 MP photo's pipeline pass peaks at ~1.77 GB commit —
    measured, and the cap discloses when it overrules you). The 147-photo eval
    went from 2.3 h serial to 38 min.
  • Lightroom brush and AI masks are carried first-class (byte-exact
    round-trip of dab streams and mask intent). AI masks can be recomputed
    locally
    (SAM 2.1 point-prompted / OneFormer sky / u2net subject), always
    disclosed as a recomputation, never passed off as Adobe's raster.
  • SigLIP2 style embedding (opt-in via AUTOSHOP_STYLE_EMBED): the style
    index can now rank by image content, licence-vetted (Apache-2.0 model),
    sha256-pinned weights.
  • 90° rotation skeleton (⭯/⭮ in the GUI; rotated recipes are the
    forward-break above).
  • Concurrency race fixed in style embedding staging: two photos developed
    in parallel could receive each other's style vectors (shipped since
    R26's three-worker batch pool). Root-fixed for every caller.
  • New 147-pair eval baseline (relay endpoint + 3-way parallel, zero
    fallbacks): gap 15.5%; mask-import refusals 2.35 → 0.05 per photo
    (the mask-import root fix, proven on the full corpus).

Measured this round (docs/ROADMAP.md 当前状态 — the measurement ledger)

The brush alpha model is now fully measured — screen accumulation, density
as a pre-screen scale, a one-parameter flow law (κ = 0.1219 ± 0.0027), an
11-rung hardness kernel table — and the radial feather law was refuted at
both endpoints. Brush masks still render nowhere: the kernel has no closed
form and the mask lives in Lightroom's pre-lens-correction frame, which this
engine cannot yet reproduce. That stays an honest BrushCarried disclosure
rather than a confidently wrong image — the R28 work is named in the docs.

Post-release documentation refresh (2026-08-20)

The docs on main were fully re-aligned to this release after it shipped:
docs/ARCHITECTURE.md now opens at v0.33.0 and names the complete dependency
stack (commit 5cb287e), a doc-drift gate (scripts/check_docs.py, an 11-row
claims registry run before every release) keeps the numbers honest from now
on, and the internal development ledgers (M1_PLAN/V2_PLAN) moved off the
public tree (ea15f99) — docs/ARCHITECTURE.md and this page are the
public-facing documentation. The release binaries above are unchanged: every
commit since the tag touches documentation and code comments only.

v0.32.0 — 径向蒙版几何根修

Choose a tag to compare

@skymanbp skymanbp released this 19 Aug 05:07

v0.32.0 —— 径向蒙版几何根修

素材:用户为此专门拍的 12 张受控 Lightroom 导出 + 对成片的像素级测量(XMP 级预言 8/8 命中,两个旋转主体像素级证实)。

① Lightroom 径向蒙版首次被正确读进来。 crs:Top/Left/Bottom/Right 不是外接框,是椭圆自己那个框在像素帧里的旋转后两个对角。旧的天真读法(本项目、以及可搜索范围内每一个第三方实现都在用)把轴比读错了中位 1.84 倍(p90 4.86、max 40.7),195 个分量里有 16 个根本读不出来(解出负半轴)。新解码零自由参数,符号律(Left>Right ⇒ Angle>0)图库 16/16 契合、p=2.5×10⁻⁵,两个渲染主体在像素上对上(_DSC9689 8.33:1 @ +24.35°;_DSC9685 解码倾角 −60.486° 对实测 −60.5°)。crs:Angle 不再扣留,双向映射(正角=顺时针、pixel-space、椭圆中心枢轴——三路独立定案);16 份真实 sidecar 四角字节级往返,含 LR 自己写的 Left>Right 排布。另测出蒙版活在比导出图大 1.032× 的同心帧里——中心也会动,不只是半轴(角落蒙版差 88 px,2799 px 杠杆上定到 3 px)。

② ⚠ 引擎落笔外沿变宽,旧配方的径向渲染会漂。 曝光梯子反推真实蒙版权重(五帧,长宽比 1.03…7.46)定下 d_in = 1−fd_out = 1+f/2(零自由参数律)。本引擎的外沿一直钉在 d=1,而实测 f=0.5 时在 1.25——即过去每一个径向蒙版都小了 29%。曲线形状(三次平滑)本来就是对的,只有两端点错。修完之后,v0.32.0 之前存的配方里的径向会画得略大、略软;版本快照可回溯,蒙版若来自 LR sidecar,重新导入原 sidecar 即可拿回原意图。

③ ⚠ 每张 Sony ARW 渲染位移 (32, 20) px。 块配准实测本项目渲染整体偏 Lightroom 帧 (+31±6, +20±1) px 纯平移:渲染窗口从传感器左上角起算,而相机与 Lightroom 从 DefaultCropOrigin=(32,20) 起算。现已收编(窄射程:仅同尺寸纯平移时移动 ROI,越界拒收不钳位)。渲染窗口对齐 LR 之后,按旧窗口存下的裁剪构图会相对平移那么多。

另:crs:LocalHue 的文件尺度实测为 180(UI +50 ⇒ 文件 0.277778,×180=50.00004),双向换算与域门同步。极性真值表四行以像素证据补齐合约测试(v0.31.2 的导入语义获像素级确认)。recipe schema 零新字段,旧版 exe 不会因蒙版硬拒新 recipe。

仍未测(码内逐条登记触发条件+收口路径):Roundness/Midpoint 像素含义、Feather=100、竖幅/裁剪帧/|Angle|>45°、半轴尺度 1.0325 vs 1.0065 的 n=2 分歧(最坏 2.6%,低于落笔端点 ±5% 帧间散布)。

门:clippy 0 双配置;620(+10) lib / 8 CLI / 129 GUI / 2+2 合约双配置全绿;audit_i18n 全 9 门 0;字体门 801/801;17 项变异检查修前必红(主审抽查带符号半展 abs 变异复现红);16 份真实径向 sidecar 在 AUTOSHOP_LR_PROBE_FIXTURES 门下字节级往返。

v0.31.2 — 证据修复批(Flipped 解绑 + 多蒙版底形)

Choose a tag to compare

@skymanbp skymanbp released this 18 Aug 17:04

v0.31.2(证据修复批) — 三项,素材=用户自有图库的只读取证(E1-verdict + 多蒙版取证)。

crs:Flipped 与反相解绑(主项)。全库 201 个径向普查:crs:Flippedcrs:MaskInverted 完美反相关(155 + 46,两种相同值组合各 0 例;本批在 7 份 M-B sidecar 上独立复算 23/23,且 27/27 线性渐变根本不带 crs:Flipped)。即 Lightroom 把同一个反相位写了两遍。本引擎的 Radial::flippedinverted 按 XOR 合成,两半都读进来等于把一个值和它的补 XOR——导入的每一个 LR 径向都被无条件反相,与文件内容无关;对 LR 不反相的那 155 个是错的。修=导入只认 crs:MaskInverted,写出侧两个属性都从净反相派生,因此只会写出 Lightroom 自己会写的组合——这正是该投影在「LR 渲染器到底读哪个属性」两种读法下都成立的原因。实测(E1-verdict §6):DSC09568 对真实 LR 导出的 tone-matched RMS 0.1099 → 0.0751,蓝通道 0.1901 → 0.0869。我方 flipped 语义不变(仍渲染、仍是 GUI「翻转」勾选),既有 recipe 渲染一像素不变,recipe schema 零改动(不新增字段=不触发 v0.31.0/1 exe 的蒙版硬拒)。已知边界:≤v0.31.1 我方写的带翻转 sidecar 现在读回为不反相——这不是新损失,Lightroom 本来就那样读它,两个方向自此与 Lightroom 一致;往返后净反相精确保留,但承载位从「翻转」并入「反相」勾选。

两处注释旋向改正(只改注释,码不动):render.rs / recipe.rs 都写成「逆时针,y-down 屏幕意义」,实测为顺时针(引擎角 +30 渲出右端朝下;符号检验 5/5,p=0.031,supported 级、未到 proven)。矩阵 R(+θ) 在 y-up 数学系里确是逆时针——旧注释带着 y-up 的读数却声称 y-down。

多蒙版 Correction 取错底形。原选择器按种类选几何(Mask/Gradient 恒优先)且完全无视 crs:MaskBlendModeDSC08960 蒙版 3 与 _DSC9583 Mask 9 都是「径向底形 + 线性减法」,结果留下 LR 用来抠掉的那块、丢掉底形——是意图反转,不是截断。修=优先默认 BlendMode 的组件(全为减法时才落到首个)。另修披露失真:Rotation 注记只对实际导入的几何发(DSC08960 原本报四条,三条描述从未进入 recipe 的径向);BlendMode 保持组件级——它对被丢弃的减法组件是真话,也正是 v0.31.1 为该情形加的披露。实测恢复 3 个蒙版(DSC08960 径向 {3}→{1,3,4},_DSC9583 {0..7} 全入)。登记不修:CLI 零蒙版披露(通道设计缺口)、unknown_component 预占的参数化形状账(披露粒度设计)。

门:clippy 0(双配置);610(+4) lib / 8 CLI / 129 GUI / 2+2 合约双配置全绿;audit_i18n 全 9 门 0;无新 zh 词条(字体门不受影响);6 项变异检查修前必红(主审抽查 first_base.or(first)first 复现红)。

v0.31.1 — 证据修正补丁:锐化 1:1 / Subtract / Roundness

Choose a tag to compare

@skymanbp skymanbp released this 18 Aug 13:55

v0.31.1 — 证据修正补丁

三条被当作既定事实的 Lightroom 假设,经对 GitHub 公开 sidecar 的只读普查证伪并根修。

  • 锐化数量改为 1:1(会改变导入值)。LR「细节 > 锐化 数量」滑杆上限是 150,sidecar 存原值——15 份真实 sidecar 带 crs:Sharpness="150"(两个仓库、两台相机、两代 LR),旧的 0..100 出自第三方滑杆表而非 Adobe。带锐化的 Lightroom 照片重新导入,数值将变为 1:1(旧读法放大 1.5×,例如 Sharpness="40" 过去读成 60,现在读成 40);写回同样 1:1,你在 Autoshop 里看到的锐化 60 现在写成 "60"(过去写成 "40")。旧配方不迁移:recipe 的 sharpening 驱动的是本机 σ 引擎、语义本就不同于 LR,引擎渲染一个像素都不变;旧写法的真问题是「渲的 60 写成 40」的所见非所写。另外,一份普通的 Sharpness="150" 文件过去会被误报为「数值无法解析」,现已消失。
  • 减法(Subtract)蒙版不再整条丢弃MaskBlendMode="1" 恒伴 MaskValue="0",是 LR 的减法编码而非「哑音蒙版」(157 文件 479 实例全量普查,26/26 无反例)。现在保留基础形状并具名披露「混合模式未复现」,那个 0 绝不会被当成强度乘进去。哑音与真部分覆盖度仍照旧拒收。
  • 圆度(Roundness)滑杆不再导致丢蒙版crs:Roundness 是 ±100 整数滑杆,不是 0..1 比例;旧读法域外即整条拒收。现按原值携带、按原值写回,不做未验证的换算,也不改渲染。

另:eval 报告里 sharpening 一行的用户侧口径随之修正,与 v0.31.0 的 147 张基线该行不可比(其余行不受影响)。