diff --git a/crates/engine/src/cmd/modify/stretch.rs b/crates/engine/src/cmd/modify/stretch.rs
--- a/crates/engine/src/cmd/modify/stretch.rs
+++ b/crates/engine/src/cmd/modify/stretch.rs
@@ -34,6 +34,18 @@ fn add_mline_vertex_hits(d: &cadcraft_doc::Drawing, space: &cadcraft_doc::Space,
     }
 }
 
+/// The bulge that keeps an arc's height (bulge × chord / 2) when its chord changes length; none
+/// once the chord collapses to a point, which has no arc left to keep.
+fn keep_height(bulge: f64, old_chord: f64, new_chord: f64) -> f64 {
+    if new_chord.is_nan() || new_chord <= 1e-12 {
+        0.0
+    } else if old_chord.is_finite() {
+        bulge * old_chord / new_chord
+    } else {
+        bulge
+    }
+}
+
 /// Move the vertices of `e` that lie inside any of the areas `wins` by `d` (STRETCH).
 fn stretch_entity(e: &mut Entity, wins: &[StretchArea], d: Vec2) {
     let inside = |p: Vec2| wins.iter().any(|a| a.contains(p));
@@ -49,20 +61,43 @@ fn stretch_entity(e: &mut Entity, wins: &[StretchArea], d: Vec2) {
             mv(&mut l.b);
         }
         EntityKind::LwPolyline(pl) => {
+            let old: Vec<Vec2> = pl.vertices.iter().map(|v| v.p).collect();
             for v in &mut pl.vertices {
                 if inside(v.p) {
                     v.p += d;
                 }
             }
+            // An arc segment keeps its height (bulge × chord / 2) as its ends move, as in AutoCAD.
+            let n = pl.vertices.len();
+            let segs = if pl.closed { n } else { n.saturating_sub(1) };
+            for i in 0..segs {
+                let j = (i + 1) % n;
+                let (Some(a0), Some(b0), Some(b1)) = (old.get(i), old.get(j), pl.vertices.get(j).map(|v| v.p)) else { continue };
+                if let Some(v) = pl.vertices.get_mut(i)
+                    && v.bulge != 0.0
+                {
+                    v.bulge = keep_height(v.bulge, a0.dist(*b0), v.p.dist(b1));
+                }
+            }
         }
         EntityKind::MLine(m) => m.vertices.iter_mut().for_each(|v| mv(&mut v.p)),
         EntityKind::Arc(a) => {
-            // Arcs keep their shape: move when the centre is inside.
-            if inside(a.center.xy()) {
-                a.center.x += d.x;
-                a.center.y += d.y;
+            // AutoCAD moves the ends inside the area and keeps the arc's height (bulge × chord / 2);
+            // the centre alone moves nothing.
+            let g = Arc::new(a.center.xy(), a.radius, a.start, a.end);
+            let (s0, e0) = (g.start_point(), g.end_point());
+            let (s1, e1) = (if inside(s0) { s0 + d } else { s0 }, if inside(e0) { e0 + d } else { e0 });
+            if (s1 != s0 || e1 != e0)
+                && let Some((r, _)) =
+                    cadcraft_geom::bulge_to_arc(s1, e1, keep_height(cadcraft_geom::arc_to_bulge(g.sweep()), s0.dist(e0), s1.dist(e1)))
+            {
+                (a.center.x, a.center.y) = (r.center.x, r.center.y);
+                (a.radius, a.start, a.end) = (r.radius, r.start, r.end);
             }
         }
+        // Control points inside the area move (AutoCAD); a spline built from fit points moves
+        // whole by its first grip, as before.
+        EntityKind::Spline(sp) if sp.fit.is_empty() => sp.control.iter_mut().filter(|p| inside(**p)).for_each(|p| *p += d),
         EntityKind::Solid(so) | EntityKind::Trace(so) => so.corners.iter_mut().for_each(mv),
         EntityKind::Dimension(dm) => {
             for p in [&mut dm.p13, &mut dm.p14, &mut dm.defpt, &mut dm.text_mid] {
diff --git a/crates/engine/src/cmd/modify2/tests.rs b/crates/engine/src/cmd/modify2/tests.rs
--- a/crates/engine/src/cmd/modify2/tests.rs
+++ b/crates/engine/src/cmd/modify2/tests.rs
@@ -769,3 +769,112 @@ fn offset_elliptical_arc_follows_picked_side() {
     let outer = max_x(json!([20, 20]));
     assert!((outer - 11.0).abs() < 0.05, "outer side went inward: max x = {outer}");
 }
+
+/// STRETCH against what AutoCAD 2024 stores for the same crossing window and displacement (5, 3)
+/// (black-box: synthetic objects in accoreconsole, read back with `entget`): the ends of an arc
+/// that are inside move and the arc keeps its height (bulge × chord / 2), as does a polyline arc
+/// segment; the centre alone moves nothing; a spline's control points inside move.
+#[test]
+fn stretch_arcs_bulges_and_splines_match_autocad() {
+    enum Want {
+        Arc([f64; 2], f64, f64, f64),
+        Bulges(&'static [f64]),
+        Control(&'static [[f64; 2]]),
+    }
+    // ((seed command, params), crossing window, what AutoCAD stores)
+    let cases: Vec<((&str, Value), [[f64; 2]; 2], Want)> = vec![
+        // SX-ARC-1END: one end of the arc inside.
+        (
+            ("arc", json!({"center": [0.0, 0.0], "radius": 20.0, "start": 0.0, "end": 90.0})),
+            [[15.0, -5.0], [30.0, 5.0]],
+            Want::Arc([3.17984478246544, -2.20611061402141], 22.43262716319083, 0.2342130724507349, 1.713026264953702),
+        ),
+        // SX2-ARC-ENDPT: the other end inside.
+        (
+            ("arc", json!({"center": [0.0, 0.0], "radius": 20.0, "start": 0.0, "end": 90.0})),
+            [[-5.0, 15.0], [5.0, 25.0]],
+            Want::Arc([1.476584509301813, 4.310815984327269], 19.01841360081773, 6.054532724492258, 1.384456456611818),
+        ),
+        // SX2-ARC-BIG-END: an arc over 180 degrees, its end inside.
+        (
+            ("arc", json!({"center": [0.0, 0.0], "radius": 20.0, "start": 30.0, "end": 300.0})),
+            [[5.0, -25.0], [15.0, -12.0]],
+            Want::Arc([1.341732093243006, -0.7463649967436295], 19.25631435504786, 0.5920576993601167, 5.500876277544912),
+        ),
+        // SX2-ARC-CEN: only the centre inside: unchanged.
+        (
+            ("arc", json!({"center": [0.0, 0.0], "radius": 20.0, "start": 0.0, "end": 90.0})),
+            [[-3.0, -3.0], [16.0, 16.0]],
+            Want::Arc([0.0, 0.0], 20.0, 0.0, std::f64::consts::FRAC_PI_2),
+        ),
+        // SX-ARC-ALL: the whole arc inside: moved.
+        (
+            ("arc", json!({"center": [0.0, 0.0], "radius": 20.0, "start": 0.0, "end": 90.0})),
+            [[-5.0, -5.0], [30.0, 30.0]],
+            Want::Arc([5.0, 3.0], 20.0, 0.0, std::f64::consts::FRAC_PI_2),
+        ),
+        // SX-PLA-V1: the vertex starting an arc segment.
+        (
+            (
+                "pline",
+                json!({"vertices": [{"p": [0.0, 0.0], "bulge": 0.0}, {"p": [30.0, 0.0], "bulge": 0.5}, {"p": [45.0, 20.0], "bulge": 0.0}, {"p": [60.0, 20.0], "bulge": 0.0}], "closed": false}),
+            ),
+            [[25.0, -5.0], [35.0, 5.0]],
+            Want::Bulges(&[0.0, 0.6337751582042422, 0.0, 0.0]),
+        ),
+        // SX-PLA-V2: the vertex ending an arc segment.
+        (
+            (
+                "pline",
+                json!({"vertices": [{"p": [0.0, 0.0], "bulge": 0.0}, {"p": [30.0, 0.0], "bulge": 0.5}, {"p": [45.0, 20.0], "bulge": 0.0}, {"p": [60.0, 20.0], "bulge": 0.0}], "closed": false}),
+            ),
+            [[40.0, 15.0], [50.0, 25.0]],
+            Want::Bulges(&[0.0, 0.4101116968279916, 0.0, 0.0]),
+        ),
+        // SX2-PLA-NEG: a negative bulge over 180 degrees.
+        (
+            (
+                "pline",
+                json!({"vertices": [{"p": [0.0, 0.0], "bulge": 0.0}, {"p": [30.0, 0.0], "bulge": -1.5}, {"p": [45.0, 20.0], "bulge": 0.0}, {"p": [60.0, 20.0], "bulge": 0.0}], "closed": false}),
+            ),
+            [[40.0, 15.0], [50.0, 25.0]],
+            Want::Bulges(&[0.0, -1.230335090483974, 0.0, 0.0]),
+        ),
+        // SX-SPL: two control points inside.
+        (
+            ("spline", json!({"control": [[0.0, 0.0], [10.0, 10.0], [20.0, 0.0], [30.0, 10.0]], "degree": 3})),
+            [[15.0, -5.0], [35.0, 15.0]],
+            Want::Control(&[[0.0, 0.0], [10.0, 10.0], [25.0, 3.0], [35.0, 13.0]]),
+        ),
+    ];
+    let same = |a: f64, b: f64| (a - b).abs() <= 1e-9 * a.abs().max(b.abs()).max(1.0);
+    let same_angle = |a: f64, b: f64| {
+        let d = (a - b).rem_euclid(std::f64::consts::TAU);
+        d < 1e-9 || std::f64::consts::TAU - d < 1e-9
+    };
+    for ((seed, params), window, want) in cases {
+        let mut s = Session::new();
+        let hd = h(&s.execute(seed, &params).unwrap());
+        s.execute("stretch", &json!({"window": window, "delta": [5.0, 3.0]})).unwrap();
+        match (kind(&s, &hd), want) {
+            (EntityKind::Arc(a), Want::Arc(c, r, start, end)) => assert!(
+                same(a.center.x, c[0]) && same(a.center.y, c[1]) && same(a.radius, r) && same_angle(a.start, start) && same_angle(a.end, end),
+                "{window:?}: {a:?}"
+            ),
+            (EntityKind::LwPolyline(p), Want::Bulges(want)) => {
+                let got: Vec<f64> = p.vertices.iter().map(|v| v.bulge).collect();
+                assert!(got.len() == want.len() && got.iter().zip(want).all(|(a, b)| same(*a, *b)), "{window:?}: {got:?} / {want:?}");
+            }
+            (EntityKind::Spline(sp), Want::Control(want)) => {
+                let ok = sp.control.len() == want.len() && sp.control.iter().zip(want).all(|(p, w)| same(p.x, w[0]) && same(p.y, w[1]));
+                assert!(ok, "{window:?}: {:?} / {want:?}", sp.control);
+            }
+            (k, _) => panic!("{window:?}: {k:?}"),
+        }
+    }
+    // A stretch that brings an arc segment's ends together leaves no arc to keep: the bulge goes.
+    let mut s = Session::new();
+    let p = pline(&mut s, json!([{"p": [0, 0], "bulge": 0.5}, [10, 0], [20, 0]]), false);
+    s.execute("stretch", &json!({"window": [[9, -1], [11, 1]], "delta": [-10, 0]})).unwrap();
+    assert_eq!(poly(&kind(&s, &p)).vertices.iter().map(|v| v.bulge).collect::<Vec<_>>(), [0.0, 0.0, 0.0]);
+}
