fix: the initial wall-model migration resolves every opening conflict itself (#316)

Implements spec revision 4 (green r4). §3.1 — a legacy open_spans/open_to cut
never zeroes the atom that carries an existing contour opening: the opening's
edges become atom boundaries, the door keeps its real wall and the zero run
continues on both sides. §3.2 — an ambiguous carrier resolves
deterministically: current host, then distance, thicker cm, smaller id.
§3.3 — an opening with no usable carrier persists unhosted: a valid degraded
v9 state, inert in the physics, rendered by its own x/y, kept by later writes
and re-placeable in the editor (backend schema accepts it). §3.4 — the
initial migration never throws over an opening; a post-v9 write that LOST its
carrier keeps the fail-closed opening-host refusal. The Python migration
mirror implements the same rules with byte-identical output (verified on the
span+door fixture including the segment id).

The new smoke replays #316 end to end: a conflicted space no longer blocks
drawing on an empty plan. The golden scene span-over-door-migrated-dark
renders the migrated fixture pinned byte-for-byte to the real writer; two
gate mutants revert §3.1 and §3.4 and are red by execution.

Issue: #316
User-Visible: yes
This commit is contained in:
Codex
2026-08-26 22:06:43 +03:00
parent 33a5b46990
commit ffb232398a
18 changed files with 878 additions and 135 deletions
+6
View File
@@ -7,6 +7,12 @@
example one that still carried an orphan legacy border) instead of demanding
a card update that was already installed
([#319](https://github.com/Matysh/houseplan-card/issues/319)).
- Drawing works again everywhere: converting a plan to the new wall model no
longer stops on a legacy border/opening conflict. A door standing inside a
former «border» keeps its real wall (the gap continues on both sides), an
ambiguous carrier is resolved deterministically, and an opening with no
usable wall at all is kept as data — inert until you re-place it
([#316](https://github.com/Matysh/houseplan-card/issues/316)).
## v1.68.0-beta.3 — 2026-08-26
+6
View File
@@ -13,6 +13,12 @@
документа (например, с осиротевшей легаси-границей) вместо требования
обновить уже обновлённую карточку
([#319](https://github.com/Matysh/houseplan-card/issues/319)).
- Рисование снова работает везде: преобразование плана к новой модели стен
больше не останавливается на конфликте легаси-границы с проёмом. Дверь
внутри бывшей «границы» сохраняет настоящую стену (разрыв продолжается по
обе стороны), неоднозначный носитель выбирается детерминированно, а проём
вовсе без пригодной стены сохраняется в данных — бездействует, пока его не
переставят ([#316](https://github.com/Matysh/houseplan-card/issues/316)).
## v1.68.0-beta.3 — 2026-08-26
+10 -3
View File
@@ -90,9 +90,16 @@ transmit Glow and sunlight, while solid zero walls are exact line barriers.
documents only. Explicit valid spans win; `open_to` expands to the full proven
shared boundary only when spans are absent. The first structural write,
confirmed **Optimize plans**, or import into a current target atomizes that
geometry into stable `wall_segments[].cm=0`, preserves surviving IDs, verifies
that no opening would acquire a zero host, and then removes both legacy fields
in one transaction. A conflict rejects the complete candidate. Presentation,
geometry into stable `wall_segments[].cm=0`, preserves surviving IDs, and then
removes both legacy fields in one transaction. An opening never blocks that
migration (#316): the atom carrying an existing opening keeps its positive
thickness (the zero run continues on both sides), an ambiguous carrier is
resolved deterministically (current host, then distance, thicker cm, smaller
id), and an opening with no usable carrier at all persists **unhosted** — a
valid degraded v9 state that is inert in the physics (no body, tunnel or cut),
renders by its own `x/y`, survives later writes untouched and may be re-placed
in the editor. Post-migration structural writes keep the fail-closed refusal
for an opening that LOST its carrier. Presentation,
marker and ordinary space-settings writes do not trigger the migration.
There is deliberately no provenance flag. Existing v8 `cm:0` and atoms derived
+11 -11
View File
@@ -2,7 +2,7 @@
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"sourceFingerprint": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceFingerprint": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -14,7 +14,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "9190bd86a07b0cb0502019d76d3286c0542181e1b4a4f862077d9cefa120613c"
},
"view-touch": {
@@ -25,7 +25,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "cfa2f77eb68df69ae9fc9b51ffe2f14df01f7b74444fd2b038cadc1e9dfee22b"
},
"space-create": {
@@ -36,7 +36,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "a86d4211af24923d5048e9094d0d35f9b28129394d00cd72582a8f8c2dd7ff75"
},
"room-contour-close": {
@@ -47,7 +47,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "c7effd4ccd19bcaff9138458583368aaab488dcb6fc7ea5ab766383cb5377ac4"
},
"plan-context-tray": {
@@ -58,7 +58,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "af1194f073f00b03af02b949a072cffb21bfd29995aa65a067eaaaec74ef8490"
},
"device-editor": {
@@ -69,7 +69,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "36ed21b66920ff2de9e3346cf6f27c67e64a9f81692599b9c70d8373f5eb7241"
},
"device-display-preview": {
@@ -80,7 +80,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "7151c96dc048381f7a7070ecb85a82a890b2f4c703628f14fa472eb8dacbf3dc"
},
"background-editor": {
@@ -91,7 +91,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "9f08f3f3711b100d68fb1a05af269e43e47d8f8d12c0cbe4f9ab557138722989"
},
"room-card": {
@@ -102,7 +102,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "66d8b0a509909822ff6b891a483bad9d2c72b20f08e287355883aa5205c6cee0"
},
"device-info": {
@@ -113,7 +113,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "4e7b4a7dc71cb9f1bb89caddbac0e70fee49088369165368845fcf1988867577",
"sourceSha256": "1f20c9d64ca59363be3909accd069595b4fe18404ee7a28a5db479dac605661a",
"imageSha256": "83620033bb66edf7c804619261e4a62327049c178267f0837bd997627ffdc003"
}
}