diff --git a/crates/engine/src/cmd/smart_cmds/tests.rs b/crates/engine/src/cmd/smart_cmds/tests.rs
--- a/crates/engine/src/cmd/smart_cmds/tests.rs
+++ b/crates/engine/src/cmd/smart_cmds/tests.rs
@@ -929,6 +929,38 @@ fn convert_to_layers_keeps_a_smart_layer_mask_on_a_group() {
     assert_eq!(max_diff(&flat(&s), &before), 0.0);
 }
 
+/// #9753: a smart object isolates its contents, so a knockout inside it stops at its bottom.
+/// Folded into the parent stack, it would punch through the layers beneath to the Background; a
+/// Normal group keeps the look, as Photoshop's Convert to Layers does.
+#[test]
+fn convert_to_layers_keeps_a_knockout_inside_a_group() {
+    let fill = |s: &mut Session, r: Rect, rgba: [f32; 4]| {
+        s.edit("paint", |doc, active| {
+            doc.layer_mut(active.unwrap()).unwrap().surface_mut().unwrap().fill_rect(r, &rgba);
+            Ok(())
+        })
+        .unwrap();
+    };
+    for knockout in ["shallow", "deep"] {
+        let mut s = Session::new();
+        s.execute("file.new", json!({"width": W, "height": H})).unwrap();
+        fill(&mut s, Rect::new(0, 0, W, H), [0.0, 0.0, 1.0, 1.0]);
+        s.execute("layer.new.layer", json!({})).unwrap();
+        fill(&mut s, Rect::new(0, 0, W, H), [1.0, 1.0, 0.0, 1.0]);
+        s.execute("layer.new.layer", json!({})).unwrap();
+        fill(&mut s, Rect::new(10, 10, 30, 30), [1.0, 0.0, 0.0, 1.0]);
+        s.execute("layer.layerStyle.blendingOptions", json!({"knockout": knockout, "fillOpacity": 50})).unwrap();
+        convert(&mut s);
+        let before = flat(&s);
+        let r = convert_to_layers(&mut s);
+        assert_eq!(r["group"], true, "{knockout}: the contents stay isolated");
+        let g = active_layer(&s);
+        assert_eq!(g.blend, BlendMode::Normal, "{knockout}");
+        assert!(max_diff(&flat(&s), &before) <= 1.0 / 255.0 + 1e-6, "{knockout}: the look is kept");
+        assert_eq!(g.children().unwrap()[0].advanced.knockout.name(), knockout, "the knockout setting survives");
+    }
+}
+
 #[test]
 fn convert_to_layers_applies_the_placement() {
     let mut s = session(8);
diff --git a/crates/engine/src/cmd/smart_cmds/unpack.rs b/crates/engine/src/cmd/smart_cmds/unpack.rs
--- a/crates/engine/src/cmd/smart_cmds/unpack.rs
+++ b/crates/engine/src/cmd/smart_cmds/unpack.rs
@@ -9,7 +9,7 @@ use std::sync::Arc;
 use photocraft_algo::transform::Interp;
 use photocraft_cms::Intent;
 use photocraft_color::BlendMode;
-use photocraft_doc::{Document, Group, Layer, LayerContent, LayerId, Metadata, SmartObject, SmartSource};
+use photocraft_doc::{Document, Group, Knockout, Layer, LayerContent, LayerId, Metadata, SmartObject, SmartSource};
 use serde_json::{Value, json};
 
 use super::{authorize_source, authorized_source_bytes, decode_source, detach_psd, other, placement, shift_layer, smart};
@@ -125,7 +125,8 @@ fn released(l: &mut Layer, f: impl FnOnce(&mut Layer) -> Result<()>) -> Result<(
 /// Whether the smart layer's own properties fold into its one unpacked layer `c` without
 /// changing the look: nothing on the smart layer acts on `c`'s composite (masks, style, fill
 /// opacity, Blend If, channel exclusions), and `c` blends normally and doesn't look at what's
-/// beneath it (alone in the smart object, it lands on transparency).
+/// beneath it (alone in the smart object, it lands on transparency; a knockout there stops at
+/// the smart object's bottom instead of punching through to the Background, #9753).
 fn foldable(so: &Layer, c: &Layer) -> bool {
     so.mask.is_none()
         && so.vector_mask.is_none()
@@ -137,6 +138,7 @@ fn foldable(so: &Layer, c: &Layer) -> bool {
         && matches!(c.blend, BlendMode::Normal | BlendMode::PassThrough)
         && c.blend_if.is_default()
         && c.excluded_channels == 0
+        && c.advanced.knockout == Knockout::None
 }
 
 /// What replaces smart layer `so`: its one unpacked layer with `so`'s properties folded in, or a
