🐛 Compare pushed swaps against the updated parent frame

Use the parent's pushed geometry when deciding whether a promoted swap
needs an orientation override. Its original container still has the old
rotation, which can create a phantom geometry group during update-main
of a rotated parent (F172).

The portable regression fails its touched assertion before the fix. All
54 swap tests pass 214 assertions, the library-308 shrink passes, and the
pure golden master matches 1149 assertions without recording. Common and
frontend lint and format pass.

Benchmark ratios against Phase 1e: main 10/100/1000 copies 0.49/0.58/0.59,
three levels 0.54, chain 0.52, library 0.56, many shapes 0.48, variant
switch 0.63. No regression flags.

AI-assisted-by: GPT-6
This commit is contained in:
alonso.torres 2026-10-24 10:13:30 +02:00
parent 3002b6b9f5
commit c36d15db20
3 changed files with 30 additions and 1 deletions

View File

@ -78,3 +78,6 @@ component. If the old position supplied rotation or flips that differ from
that component's expected parent placement, the new nested copy needs a
geometry-group override. Otherwise a later sync discards the orientation
that update-main just pushed (F168). The source copy clears its overrides.
Compare against the parent's pushed frame: the inverse sync has already
written its new rotation, while the original container still has the old
one. Using that old frame creates phantom geometry overrides (F172).

View File

@ -777,13 +777,23 @@
(ctf/find-content-ref-shape nil container libraries child-inst
:include-deleted? true
:with-context? true))
;; The parent's inverse sync already pushed its geometry. Compare
;; against that frame, rather than its original container value.
placement-container
(cond-> main-container
content-main
(assoc-in [:objects (:id parent-main)]
(-> (ctn/get-shape container (:parent-id child-inst))
(reposition-shape root-inst root-main)
(assoc :id (:id parent-main)
:shape-ref (:shape-ref parent-main)))))
update-new-shape (fn [new-shape original-shape]
(cond-> (reposition-shape new-shape root-inst root-main)
(and (= (:id original-shape) (:id child-inst))
(ctk/swapped? child-inst)
(not swap-in-copy?))
(rehead-pushed-swap content-main main-container)))
(rehead-pushed-swap content-main placement-container)))
;; the copy now follows the main, swap included
update-original-shape (fn [original-shape new-shape]

View File

@ -917,3 +917,19 @@
(:points (ths/get-shape-by-id full (:id head))) 0.001))
(t/is (= (thn/normalize-file (tm/file swapped))
(thn/normalize-file (tm/file (r/step updated (n/undo)))))))))
(t/deftest pushing-a-swap-uses-the-parents-updated-orientation
(let [swapped (-> (tm/make-situation (setup-sized))
(r/step (n/swap-shape :copy-star :heart)) r/settle
(r/step (n/rotate :copy 45)) r/settle)
updated (-> swapped (r/step (n/update-main :copy)) r/settle)
file (tm/file updated)
head (tm/resolve-shape updated :copy-star)
main (tm/shape-by-id updated (:shape-ref head))
full (:file (r/sync-cascade file {} (keys (get-in file [:data :components]))))]
(t/is (empty? (:touched main)))
(t/is (mth/close? 45 (:rotation main) 0.001))
(t/is (gsh/close-attrs? :points (:points head)
(:points (ths/get-shape-by-id full (:id head))) 0.001))
(t/is (= (thn/normalize-file (tm/file swapped))
(thn/normalize-file (tm/file (r/step updated (n/undo))))))))