Fix multi-pin wire/owner desync in shove-in-rooms recovery

_shove_recover_room clobbered a net's _Accepted with a fresh one whose
wire_owners held only the just-recovered wire, while its wires list (the
shared result.wires[net_no]) already held earlier committed connections.
That left wire_owners shorter than the parallel wires list, so a later
net's shove either removed the wrong wire's copper from the search tree
(a silent different-net short: copper stayed in the emitted SES but
vanished from the tree, and has_violation falsely certified it clean) or
raised IndexError and aborted the whole route.

Reuse the net's existing _Accepted on recovery so wire_owners stays
parallel to wires. Fixes both the silent DRC violation and the crash
sibling without touching the DRC-clean or determinism invariants.

Also correct the overstated "density win" framing of the shove_channel
fixture: its plain-routing net drop is an artifact of greedy net order,
not a density limit (the same board routes both nets with zero shoves
under a reorder or with the cosmetic seals removed). Reframed the test
and docstring; the genuine sub-cell win remains the wide_door occupancy
packing (shoves == 0).

Regression tests assert the invariant against the emitted output
directly (reconstructed copper tiles), not just the tree that the bug
fooled, plus determinism, no-crash, and the ordering-artifact bound.
This commit is contained in:
2026-07-13 11:08:02 -06:00
parent a06adbee13
commit 9cf2392c4b
4 changed files with 276 additions and 13 deletions
+12 -4
View File
@@ -742,12 +742,20 @@ def _shove_recover_room(
return 0
owners = force_place_owners(net_no, layer, corners, tree, owner, half_width)
acc = _Accepted(
wires=result.wires.setdefault(net_no, []), vias=result.vias.setdefault(net_no, [])
)
# Reuse this net's existing registry if it already committed connections, so
# ``wire_owners`` stays parallel to ``wires``. Clobbering with a fresh
# ``_Accepted`` here would leave ``wire_owners`` shorter than ``wires`` (its
# ``result.wires[net_no]`` list already holds the earlier connections),
# desyncing the two lists and making a later shove remove the wrong wire's
# copper from the tree while it stays in the emitted route.
acc = accepted.get(net_no) if accepted is not None else None
if acc is None:
acc = _Accepted(
wires=result.wires.setdefault(net_no, []), vias=result.vias.setdefault(net_no, [])
)
accepted[net_no] = acc
result.add_wire(net_no, layer, corners)
acc.wire_owners.append(owners)
accepted[net_no] = acc
return len(journal) + 1