diff --git a/crates/engine/src/crs_masks.rs b/crates/engine/src/crs_masks.rs
--- a/crates/engine/src/crs_masks.rs
+++ b/crates/engine/src/crs_masks.rs
@@ -208,13 +208,18 @@ fn component(m: &XmpValue, container: &str, aspect: f64) -> Result<Value, String
             if pts.is_empty() {
                 return Err("empty brush".into());
             }
-            let erase = num(m, "crs:MaskValue").is_some_and(|v| v <= 0.0);
+            let value = num(m, "crs:MaskValue").unwrap_or(1.0);
+            let erase = value <= 0.0;
+            // Zero means paint toward zero (erase), not an eraser with zero strength.
+            let density = if value == 0.0 { 1.0 } else { value.abs() };
             json!({"kind": "brush", "strokes": [{
                 "points": pts,
                 "size": num(m, "crs:Radius").unwrap_or(0.03).clamp(0.001, 1.0),
-                "feather": (num(m, "crs:CenterWeight").unwrap_or(0.5) * 100.0).clamp(0.0, 100.0),
+                // CenterWeight is hardness: zero is a fully soft dab, one a hard disk.
+                // Copying it as feather turns soft imported brushes into oversized solid disks.
+                "feather": ((1.0 - num(m, "crs:CenterWeight").unwrap_or(0.5)) * 100.0).clamp(0.0, 100.0),
                 "flow": (num(m, "crs:Flow").unwrap_or(1.0) * 100.0).clamp(0.0, 100.0),
-                "density": (num(m, "crs:MaskValue").unwrap_or(1.0).abs() * 100.0).clamp(0.0, 100.0),
+                "density": (density * 100.0).clamp(0.0, 100.0),
                 "erase": erase,
             }]})
         }
diff --git a/crates/engine/src/crs_masks/tests.rs b/crates/engine/src/crs_masks/tests.rs
--- a/crates/engine/src/crs_masks/tests.rs
+++ b/crates/engine/src/crs_masks/tests.rs
@@ -38,6 +38,7 @@ fn settings() -> (DevelopSettings, Vec<String>) {
     (DevelopSettings::default().merged(&partial).expect("valid settings"), unmapped)
 }
 
+/// Verify correction containers preserve adjustments, component geometry and brush properties.
 #[test]
 fn corrections_become_masks() {
     let (s, unmapped) = settings();
@@ -63,7 +64,7 @@ fn corrections_become_masks() {
     assert_eq!(brush.adjust.clarity, 40.0);
     assert_eq!(brush.components.len(), 1, "the brush's dab runs are one component");
     let MaskShape::Brush { strokes } = &brush.components[0].shape else { panic!() };
-    assert_eq!((strokes.len(), strokes[0].points.len(), strokes[0].flow, strokes[0].feather), (2, 2, 80.0, 30.0));
+    assert_eq!((strokes.len(), strokes[0].points.len(), strokes[0].flow, strokes[0].feather), (2, 2, 80.0, 70.0));
     assert_eq!(unmapped, vec!["Mask: AI mask (9)".to_string()]);
 }
 
@@ -76,6 +77,116 @@ fn masks_render() {
     assert_ne!(render(&DevelopSettings { light: s.light, ..Default::default() }), render(&s), "the masks change the picture");
 }
 
+/// Synthetic single-dab correction, including the soft-brush attributes seen in the supplied
+/// Lightroom 6 sidecar. Keep personal metadata and the original photograph out of fixtures.
+fn brush_packet(container: &str, center_weight: &str) -> String {
+    format!(
+        r#"<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/">
+ <crs:{container}><rdf:Seq><rdf:li><rdf:Description crs:What="Correction" crs:LocalExposure2012="0.25">
+  <crs:CorrectionMasks><rdf:Seq><rdf:li crs:What="Mask/Paint" crs:Radius="0.065549" crs:Flow="1" crs:MaskValue="1" {center_weight}>
+   <crs:Dabs><rdf:Seq><rdf:li>M 0.5 0.5</rdf:li></rdf:Seq></crs:Dabs>
+  </rdf:li></rdf:Seq></crs:CorrectionMasks>
+ </rdf:Description></rdf:li></rdf:Seq></crs:{container}>
+</rdf:Description></rdf:RDF></x:xmpmeta>"#
+    )
+}
+
+/// Convert Lightroom hardness to feather in both brush containers, bounding invalid inputs.
+#[test]
+fn imported_brush_center_weight_is_hardness() {
+    for container in ["PaintBasedCorrections", "MaskGroupBasedCorrections"] {
+        for (attr, feather) in [
+            ("", 50.0),
+            (r#"crs:CenterWeight="0""#, 100.0),
+            (r#"crs:CenterWeight="0.3""#, 70.0),
+            (r#"crs:CenterWeight="1""#, 0.0),
+            (r#"crs:CenterWeight="-2""#, 100.0),
+            (r#"crs:CenterWeight="2""#, 0.0),
+            (r#"crs:CenterWeight="NaN""#, 50.0),
+        ] {
+            let d = lightcraft_meta::parse_xmp(&brush_packet(container, attr)).unwrap();
+            let (partial, unmapped) = crate::crs::to_partial_report(&d.properties, Some(&d.values), crate::crs::Target::Any, 1.5);
+            assert!(unmapped.is_empty(), "{unmapped:?}");
+            let s = DevelopSettings::default().merged(&partial).unwrap();
+            let MaskShape::Brush { strokes } = &s.masks[0].components[0].shape else { panic!() };
+            assert_eq!(strokes[0].feather, feather, "{container} {attr}");
+            assert_eq!(strokes[0].size, 0.065549, "feather conversion must preserve the outer radius");
+        }
+    }
+}
+
+/// Preserve a soft brush's falloff and outer radius through a portrait crop at two resolutions.
+#[test]
+fn lr6_soft_brush_renders_a_falloff_after_crop_at_each_resolution() {
+    let d = lightcraft_meta::parse_xmp(&brush_packet("PaintBasedCorrections", r#"crs:CenterWeight="0""#)).unwrap();
+    let (mut partial, _) = crate::crs::to_partial_report(&d.properties, Some(&d.values), crate::crs::Target::Any, 4928.0 / 3264.0);
+    // The supplied photo is cropped from a landscape sensor to a portrait frame.
+    partial["crop"] = json!({"geometry": {"rect": {"x0": 0.297266, "y0": 0.0, "x1": 0.738825, "y1": 1.0}}});
+    let s = DevelopSettings::default().merged(&partial).unwrap();
+    let frame = lightcraft_pipeline::geometry::Frame::new(4928, 3264, &s, true);
+    let shape = &s.masks[0].components[0].shape;
+    for width in [220, 440] {
+        let (w, h) = frame.fit(width, 1000);
+        let img = lightcraft_raster::Image { width: w, height: h, data: vec![[0.18f32; 3]; w * h] };
+        let log_l = lightcraft_raster::Plane::new(w, h);
+        let alpha = lightcraft_pipeline::masks::shape_alpha(shape, &frame, w, h, &img, &log_l, 0.0, None);
+        let center = frame.norm_to_out(w, h).apply(lightcraft_geom::Point::new(0.5, 0.5));
+        let radius = 0.065549 * frame.px_per_long(w);
+        let sample = |fraction: f64| {
+            let x = (center.x + radius * fraction).floor() as usize;
+            alpha.data[center.y.floor() as usize * w + x]
+        };
+        assert!(sample(0.0) > 0.99);
+        assert!((sample(0.5) - 0.5).abs() < 0.08, "half-radius falloff at {w} px: {}", sample(0.5));
+        assert!(sample(0.9) < 0.06, "the outer ring must not be a solid disk: {}", sample(0.9));
+        assert_eq!(sample(1.1), 0.0);
+    }
+}
+
+/// Preserve soft brush edges when importing catalog-style Lua develop settings.
+#[test]
+fn catalog_style_lua_brush_keeps_soft_edges() {
+    // Catalog develop settings use the same Lua-table parser as lrtemplate presets.
+    let t = r#"s = { title = "Soft", value = { settings = { PaintBasedCorrections = {
+        { What = "Correction", LocalExposure2012 = 0.25, CorrectionMasks = {
+            { What = "Mask/Paint", Radius = 0.065549, CenterWeight = 0, Flow = 1, MaskValue = 1,
+              Dabs = { "d 0.5 0.5" } } } } } } } }"#;
+    let imported = crate::preset_import::read_presets("Soft.lrtemplate", t.as_bytes(), None).unwrap();
+    let s = DevelopSettings::default().merged(&imported[0].preset.settings).unwrap();
+    let MaskShape::Brush { strokes } = &s.masks[0].components[0].shape else { panic!() };
+    assert_eq!((strokes[0].size, strokes[0].feather), (0.065549, 100.0));
+}
+
+/// Apply zero-valued erase strokes to earlier paint without changing paint outside the eraser.
+#[test]
+fn lightroom_zero_mask_value_erases_previous_paint() {
+    // Lightroom 6 writes both M and d dab runs, and MaskValue=0 on erasers. The supplied
+    // sidecar uses these to trim the animal's outline; zero-density erasers did nothing.
+    let eraser = r#"<rdf:li crs:What="Mask/Paint" crs:Radius="0.02" crs:Flow="1" crs:CenterWeight="1" crs:MaskValue="0">
+        <crs:Dabs><rdf:Seq><rdf:li>d 0.5 0.5</rdf:li></rdf:Seq></crs:Dabs></rdf:li>"#;
+    for container in ["PaintBasedCorrections", "MaskGroupBasedCorrections"] {
+        let packet = brush_packet(container, r#"crs:CenterWeight="1""#)
+            .replace("</rdf:Seq></crs:CorrectionMasks>", &format!("{eraser}</rdf:Seq></crs:CorrectionMasks>"));
+        let d = lightcraft_meta::parse_xmp(&packet).unwrap();
+        let (partial, unmapped) = crate::crs::to_partial_report(&d.properties, Some(&d.values), crate::crs::Target::Any, 1.5);
+        assert!(unmapped.is_empty(), "{unmapped:?}");
+        let s = DevelopSettings::default().merged(&partial).unwrap();
+        let shape = &s.masks[0].components[0].shape;
+        let MaskShape::Brush { strokes } = shape else { panic!() };
+        assert_eq!(strokes.len(), 2);
+        assert!(!strokes[0].erase && strokes[1].erase);
+        assert_eq!(strokes[1].density, 100.0, "MaskValue=0 is a full-strength eraser");
+        let frame = lightcraft_pipeline::geometry::Frame::new(300, 200, &s, true);
+        let img = lightcraft_raster::Image { width: 300, height: 200, data: vec![[0.18f32; 3]; 300 * 200] };
+        let log_l = lightcraft_raster::Plane::new(300, 200);
+        let alpha = lightcraft_pipeline::masks::shape_alpha(shape, &frame, 300, 200, &img, &log_l, 0.0, None);
+        assert_eq!(alpha.data[100 * 300 + 150], 0.0, "erase removes the painted centre");
+        assert_eq!(alpha.data[100 * 300 + 162], 1.0, "paint beyond the eraser stays");
+        assert_eq!(alpha.data[100 * 300 + 175], 0.0, "the outer brush radius stays fixed");
+    }
+}
+
 #[test]
 fn range_to_white_stays_open_and_unmapped_local_sliders_are_reported() {
     // written for this test: a highlights range (full from 0.85 up to white) carrying a local slider we lack
diff --git a/docs/gaps.md b/docs/gaps.md
--- a/docs/gaps.md
+++ b/docs/gaps.md
@@ -1,6 +1,6 @@
 # Gaps: where LightCraft falls short of Lightroom
 
-> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-24 · **Change:** minor (gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF)
+> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-27 · **Change:** minor (Lightroom brush import preserves soft edges and zero-valued erasures; gap 30: the Info panel shows the selected photos' shared metadata and marks mixed fields; gap 28: ⌘⌥⇧C / ⌘⌥⇧V copy and paste metadata; gap 13: before a Lightroom catalog import the app asks merge or new library, #2672; gap 28: F8 shows / hides the right panel; import review album hierarchy, #2362; gap 20: Minolta MRW decodes; gap 10: 551 models verified; gap 15: Print Page Options — page numbers, page info, crop marks; gap 28: `\` shows / hides the Filter Bar in the grids; gap 21: Indonesian interface shipped, every message but What's New; gap 27: the Windows x64 installer verified on Windows 11; the Settings → Apps uninstall confirmation remains; gap 28: ⌘L Enable Filters switches the filter off and on; gap 15: Japanese photo and postcard paper sizes in the Print page setup and PDF)
 
 Every known shortfall, one entry each, ranked by user impact (how many Lightroom users hit it, and
 how hard it blocks them), then by effort. **Agents pick work from the top** (the first entry nobody is
@@ -61,6 +61,8 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
 
 ## Closed recently (kept for calibration)
 
+- 2026-10-27: Lightroom 6 brush masks import with the correct feather direction (`CenterWeight` is hardness) and full-strength erase strokes (`MaskValue=0`), preventing oversized solid-disk overlays and restoring erased outlines. Stored outer radii are preserved. Rendering remains approximate; no ranked gap or readiness estimate changes.
+
 - 2026-10-22: saving XMP preserves foreign namespaces even when they use `lc:` or `xmp:` prefixes; importing them no longer interprets their properties as native settings or ratings. Parser, merge and save-command regressions cover the correction; no ranked gap or readiness estimate changes.
 
 - 2026-10-22: PSD/PSB imports and metadata readers now expose embedded EXIF resource 1058 and XMP resource 1060 for capture dates, GPS and All Metadata (#2519); imported PSD edit copies retain their capture date, and the `removeLocation` Original-export guard sees embedded PSD/PSB GPS. No ranked gap or headline feature count closes.
@@ -84,6 +86,7 @@ Beta-blocking gaps (B) total **~230–430 h**; with the stability and platform w
 
 | Date | Change | Summary |
 |---|---|---|
+| 2026-10-27 | patch | Lightroom brush import: CenterWeight is hardness and MaskValue=0 is full-strength erasure, preserving soft edges and erased outlines; XMP, catalog-style Lua and cropped multi-resolution alpha and erase-stroke regressions; statuses and counts unchanged |
 | 2026-10-24 | minor | Gap 28: ⌘⌥⇧C / ⌘⌥⇧V (Ctrl+Alt+Shift+C / V on Windows and Linux) copy and paste metadata; no key leaves the entry; hours unchanged |
 | 2026-10-24 | minor | Gap 30 shrinks: the Info panel's Show Metadata For (Target Photo / Selected Photos, `view.infoSelectedPhotos`) shows the selected photos' shared values and marks fields that differ as Mixed; the All Metadata dialog remains; 1–3 h (was 4–8) |
 | 2026-10-23 | minor | Gap 13: before a Lightroom catalog import the app asks whether it merges into the open library or goes into a new one (opt-out in Settings, #2672); hours unchanged |
diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md
--- a/docs/parity-checklist.md
+++ b/docs/parity-checklist.md
@@ -1,6 +1,6 @@
 # Lightroom parity checklist
 
-> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-27 · **Change:** minor (Lightroom brush import preserves soft edges and zero-valued erasures; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
 
 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we
 have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good
@@ -296,7 +296,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
 | LR-MASK-OBJECTS | Object selection | P2 | 🟡 | `cmd:mask.add` (`object`, `prompt`), `cmd:mask.objectPoint`, `cmd:mask.refineDetail`, `cmd:segment.prepare`, `cmd:segment.model.status`, `cmd:segment.model.download`, `cmd:segment.model.cancel`, `cmd:app.openSamModelFolder`, `crates/segment`, `crates/engine/src/segment.rs` | SAM 3 in pure Rust (candle; Metal on macOS, CPU elsewhere): Object tile → click to include, ⌥-click to leave out; Describe tile → a text prompt selects every instance ("sky", "the red car"); both also as Add/Subtract/Intersect components; + / − under the selected mask; comma lists (`car, road`); a zoomed-in detail pass for 5–10× finer edges on small objects; per-selection Edge (hard ↔ soft). The model runs on its own worker thread (the UI never waits; panics become errors; a job that fails on the Mac's GPU runs again on the CPU, which then serves the rest of the session, #1974), is unloaded after 10 min idle, and is optional: the segmentation is stored with the mask (288² logits), so renders and exports never need it. Weights are never bundled (SAM License): without them the app offers a consented background download (mirrors, resume, timeouts, SHA-256) — **but no default download location is configured yet** (users set `LIGHTCRAFT_SAM3_MIRRORS` or install by hand: the dialog names the three files, opens the configured model folder with the host opener, preserves setup on an open error, and links docs/ai-masks.md). Not verified against Lightroom's Select Object; no brush/box object mode; the first click on a photo waits for its analysis (~4 s on an M4 Pro, much longer on CPU) |
 | LR-MASK-PEOPLE | People parts | P2 | ⬜ | | shape exists (no UI): uses the photo's person / skin / hair / teeth / glasses mattes when it carries them (DNG semantic masks), else the subject heuristic |
 | LR-MASK-LANDSCAPE | Landscape classes | P2 | ⬜ | | shape exists, evaluates empty |
-| LR-MASK-BRUSH | Brush mask | P0 | 🟡 | `cmd:tool.brush`, `cmd:mask.brushStroke` (`autoMask`), `crates/pipeline/src/masks.rs`, `crates/ui-egui/src/brush_live.rs` | size/feather/flow/density/erase; Create New Mask starts a separate brush in paint mode; the stroke shows while the button is held with the committed stroke's dab spacing, feather and flow, so fast strokes have no gaps (#517); coverage builds up by passes, not hold time (holding the brush still adds nothing, live or committed, #518); Auto Mask: dabs weighted by similarity to the colour under the dab centre, refined by a guided filter on luminance (CPU + GPU); ⇧-click draws a straight stroke from where the last stroke ended (`connect`), tapering size, feather and flow from that stroke's values when they changed; no A/B brushes; pen pressure (Brush ▸ Pen Pressure, on by default; `pressure` per point in `mask.brushStroke`) scales each dab's size (a tenth to all of it) and flow, live and committed alike, where winit reports a pen force (Windows; not macOS / Linux tablets yet); no tilt |
+| LR-MASK-BRUSH | Brush mask | P0 | 🟡 | `cmd:tool.brush`, `cmd:mask.brushStroke` (`autoMask`), `crates/pipeline/src/masks.rs`, `crates/ui-egui/src/brush_live.rs` | size/feather/flow/density/erase; imported Lightroom brush `CenterWeight` is converted from hardness to feather (0 → 100%, 1 → 0%), preserving the outer radius; `MaskValue=0` imports as a full-strength erase stroke so trimmed outlines survive; Create New Mask starts a separate brush in paint mode; the stroke shows while the button is held with the committed stroke's dab spacing, feather and flow, so fast strokes have no gaps (#517); coverage builds up by passes, not hold time (holding the brush still adds nothing, live or committed, #518); Auto Mask: dabs weighted by similarity to the colour under the dab centre, refined by a guided filter on luminance (CPU + GPU); ⇧-click draws a straight stroke from where the last stroke ended (`connect`), tapering size, feather and flow from that stroke's values when they changed; no A/B brushes; pen pressure (Brush ▸ Pen Pressure, on by default; `pressure` per point in `mask.brushStroke`) scales each dab's size (a tenth to all of it) and flow, live and committed alike, where winit reports a pen force (Windows; not macOS / Linux tablets yet); no tilt |
 | LR-MASK-LINEAR | Linear gradient | P0 | ✅ | `cmd:tool.linear`, `cmd:mask.update` | |
 | LR-MASK-RADIAL | Radial gradient | P0 | ✅ | `cmd:tool.radial`, `cmd:mask.update`, `crates/ui-egui/src/panels/detail.rs` | drag the selected ellipse's body or centre pin to move; edge grips resize, outer grip rotates; rotated and compound radial components, hidden pins, outside hit rejection and one-step undo tested |
 | LR-MASK-COLORRANGE | Colour range | P1 | ✅ | `cmd:mask.add` (`colorRange`), `cmd:mask.sampleColor`, `cmd:mask.update`, `crates/pipeline/src/lib.rs` (`color_range_sample`) | Color tile → click the photo to sample (⇧-click adds, up to 5; samples taken in the space the mask compares in, respecting section eyes and the requested HDR/SDR rendition), Pick button, Refine slider |
@@ -799,6 +799,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove
 
 | Date | Change | Summary |
 |---|---|---|
+| 2026-10-27 | patch | Lightroom brush import: CenterWeight is hardness and MaskValue=0 is full-strength erasure, preserving soft edges and erased outlines; XMP, catalog-style Lua and cropped multi-resolution alpha and erase-stroke regressions; statuses and counts unchanged |
 | 2026-10-25 | patch | LR-BEHAV-TITLEBAR: the top bar's title-bar gestures come from craft-window's `title_bar`: a double-click follows the system setting, and a press on a button, the search field or a panel floating over the bar never moves the window; status counts unchanged |
 | 2026-10-24 | minor | KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 (`keyword.nextSet`, `keyword.previousSet`) go to the next / previous keyword set; status counts unchanged (KEYC-METADATA stays 🟡) |
 | 2026-10-24 | minor | KEYC-METADATA: ⌘K / Ctrl+K (`panel.addKeywords`, Add Keywords) shows the Keywords panel, opens Keywording and puts the cursor in the Add keyword box; status counts unchanged (KEYC-METADATA stays 🟡) |
diff --git a/docs/xmp-interop.md b/docs/xmp-interop.md
--- a/docs/xmp-interop.md
+++ b/docs/xmp-interop.md
@@ -225,7 +225,7 @@ mask: `CorrectionName` → name, `CorrectionAmount` → Amount, inactive correct
 | other `Local*` sliders (`LocalGrain`…) | — | no counterpart in our masks: reported as `Mask: LocalGrain` when set (`LocalCurveRefineSaturation` 100 is neutral) |
 | `Mask/Gradient` (`FullX/Y`, `ZeroX/Y`) | linear gradient | full effect at the Full point |
 | `Mask/CircularGradient` (`Top/Left/Bottom/Right`, `Angle`, `Feather`, `Flipped`) | radial gradient | box fractions → long-edge radii (presets assume 3:2; sidecars use the photo's shape). `Flipped="true"` means effect inside: in `MaskGroupBasedCorrections` with `Version` 2 (what Lightroom writes for an ordinary radial) and in the legacy `CircularGradientBasedCorrections` Radial Filter, whose default (no `Flipped`) is outside. A `MaskGroupBasedCorrections` radial without `Version` is inside whatever `Flipped` says, and its `Feather` is halved (Lightroom rescales it that way when it upgrades one). Inverting is `MaskInverted` |
-| `Mask/Paint` (`Dabs` "d x y", `Radius`, `Flow`, `CenterWeight`, `MaskValue`) | brush | one brush component per correction; `MaskValue` ≤ 0 erases |
+| `Mask/Paint` (`Dabs` "d x y", `Radius`, `Flow`, `CenterWeight`, `MaskValue`) | brush | one brush component per correction; `CenterWeight` is hardness (0 = fully soft, 1 = hard), converted to feather as `100 × (1 − CenterWeight)`; `Radius` retains the outer radius; `MaskValue` ≤ 0 erases (`0` is full-strength erasure, not zero density) |
 | `Mask/Image` `MaskSubType` 1 / 2 | Subject / Sky | other AI selections are reported, not guessed |
 | `Mask/RangeMask` `Type` 2 / 3 (`LumRange`, `DepthRange`) | luminance / depth range | lightness converted to our range scale; a range at full strength at white (third `LumRange` value 1) stays open above our scene white; colour ranges are reported |
 | `MaskBlendMode` 0 / 1 / 2, `MaskInverted` | add / subtract / intersect, invert | |
@@ -234,6 +234,11 @@ Applying a preset adds its masks to the photo's own (masks the photo already has
 slider scales them. A sidecar's masks replace the photo's, because a sidecar holds the whole edit. Components we can't
 carry over are listed in `preset.import`'s `unmapped` as `Mask: <kind>`.
 
+Previously imported brush masks are not rewritten automatically. To refresh masks imported with reversed
+feathering or ineffective erase strokes, reread the original Lightroom XMP or replace the edits by reimporting
+the Lightroom catalog (`updateExisting=true`). A packet containing `lc:settings` restores those saved native
+settings instead of converting its `crs:` corrections again.
+
 ## Local adjustment presets (mask effects)
 
 `mask.importEffects {path | paths, group?, dryRun?}` (and `preset.import`, File ▸ Import Profiles & Presets…, drag & drop)
