The limits read `wall_segments`, so a document older than the catalogue
reports no walls at all — and therefore no violations, whatever its geometry.
Comparing that raw baseline against a candidate the card had already migrated
counted every inherited violation as new, and a legacy plan could not take an
unrelated edit at all: renaming a room was refused with junction_limit_angle.
Spec §3 forbids exactly this, and the frontend had already learned the same
lesson in 4758767e; the backend mirror simply never got the second half.
validate_junction_limits now runs both documents through
commit_wall_segment_model before counting. A document that cannot be migrated
is not this validator's verdict — the wall-model barrier owns that error and
reports it with its own code — so it degrades to "no baseline to inherit".
The regression is pinned twice: a test that asserts the legacy baseline reads
clean raw and carries the apex once migrated, and the mutant
junction-limit-backend-raw-baseline. Both fixtures that exercise the barrier
were rebuilt as real documents (rooms plus walls), because the previous ones
put walls in wall_segments with no rooms and did not survive migration.
Issue: #329
User-Visible: no
П3 measures the WALL, not the catalogue atom: a short filler segment that
compensates a thickness step (owner's fixture, 5 cm = (30-20)/2) is a legal
continuation of a long same-thickness wall, so the rule walks the maximal
collinear run through the shared nodes before judging the length.
Resize stops at the last allowed position and names the broken rule instead
of the generic "geometry cannot be saved"; the Thickness dialog refuses
through its own toast. Both channels are pinned by demo/smoke_junction_limits
plus three mutants (angle threshold, write barrier, degenerate apex bevel).
Issue: #329
User-Visible: yes
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