Commit Graph

3 Commits

Author SHA1 Message Date
David Montero Crespo 0635e15e7a fix(router): escape corridors for endpoint-in-obstacle + checked-elbow parity
Three router bugs found by replaying a real agent session (reloj_3333) where
wires ran straight across a seated 4-digit display. Each fix is covered by a
regression test built from the failing geometry.

Endpoint inside an obstacle no longer drops the whole obstacle
--------------------------------------------------------------
Breadboard strips under a seated display start INSIDE its inflated bbox, so
the "rects containing an endpoint are dropped" rule deleted the display as
an obstacle for every wire leaving those strips — 15 wires crossed it end to
end. The rect is now carved instead: an escape corridor (ROUTE_MARGIN wide)
from the endpoint to the chosen edge, with the rest of the body still
blocking. Side blocks overlap the endpoint's row by 1px, or the strict
segment-hit test leaves the row as a free seam straight across the body.

Overlapping rects escape in ONE shared direction
------------------------------------------------
Seated resistors overlap heavily (19px pitch, ~66px inflated boxes). When
each containing rect picked its own nearest edge, the corridors pointed
different ways and walled each other off — A* found no exit, fell back to
the direct elbow, and the wire crossed the display anyway. The escape
direction is now chosen once against the UNION of containing rects and
every carve uses it, so the corridors chain into a continuous exit.

Null route materialises the CHECKED elbow
------------------------------------------
routeAroundObstacles returns null when the PREVIEW elbow (longer-axis-first)
is clear — but the re-route pass stored empty waypoints, which the renderer
expands as the horizontal-first corner: a DIFFERENT elbow the router never
validated. Three wires shipped crossing a display whose checked route was
clean. The pass now materialises previewElbow explicitly, exactly like
finishWireCreation always did.

Verified E2E: the same agent prompt that produced 15 crossings now builds
the ESP32 clock with ZERO wire segments crossing the display body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 21:35:58 +02:00
David Montero Crespo 651161559a feat(wires): avoid other wires + live routed preview + system-owned shapes
Extends the existing component-avoiding A* (wireAutoRoute.ts) into the full
auto-router the canvas was missing. Three pieces:

Wire avoidance with soft costs
------------------------------
Component bodies stay hard-blocked, but wires get graded costs: running
parallel on top of another wire (within an 8px corridor) is charged per px,
a perpendicular crossing costs a small fixed amount, and bends keep their
existing penalty. Crossings must stay possible — hard-blocking wires makes
dense boards unroutable and everything would degrade to the default elbow.

The compressed grid gains "corridor" coordinates 8px to each side of every
wire segment, so the router actually has a lane to run BESIDE a wire; that
is also what lays multi-wire runs out as a tidy side-by-side bus, since
each new wire routes seeing the previous ones. Wires sharing an endpoint
with the route are exempt (wires meeting on a pin must touch there), and
only wires within 120px of the route's bbox participate, keeping the grid
under the coordinate cap on dense canvases.

autoRouted: the system owns the shape until the user takes it
-------------------------------------------------------------
New Wire flag, set by pin-to-pin creation and by agent add_wire. Every
shape-editing gesture (segment drag, waypoint drag, waypoint insert — five
call sites) clears it: from that moment the wire is hand-authored and is
NEVER re-shaped, exactly where the user put it. Wires from older projects
have no flag and are treated as hand-authored.

recalculateAllWirePositions re-routes flagged wires after endpoints move
(component drag end, agent batches, mount settle — never per drag frame).
This is also what routes agent wires at all: they are created before their
elements mount and before pin coords are final, so creation-time routing
is impossible; the settle-timer recalc routes them once geometry is real.

Live routed preview
-------------------
updateWireInProgress routes start->cursor (throttled to 40ms) and the
preview renders that path, so the wire dodges components and wires AS THE
MOUSE MOVES instead of snapping into shape on the final click. Hand-guided
previews (user-placed waypoints) keep the classic path untouched.

Verified in the live app: an agent-built breadboard circuit shows 0 wire
overlap px and 0 body crossings across all wires, and a hand-started wire
aimed collinear with an existing run previews 21px beside it, overlap 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 20:08:25 +02:00
David Montero 7ed9c51bd3 feat(wires): first-time auto-routing around components
Creating a wire with a direct pin-to-pin click (no user waypoints) now
routes around other components' bounding boxes instead of crossing
them. Routing happens exactly once, at creation: the routed corners are
stored as ordinary waypoints, so every later manual edit stays where
the user puts it — never re-routed.

Router (utils/wireAutoRoute.ts):
- tries the preview elbow first (clear -> keep existing behavior and
  the WYSIWYG shape), then the opposite elbow, then A* over the
  compressed grid spanned by pin coordinates and obstacle edges
  inflated by an 8 px clearance, with a 40 px per-bend penalty so
  straighter routes win
- obstacles are component boxes only (never boards — pins sit on both
  board edges and detouring around a board produces absurd routes),
  excluding the wire's own endpoint components, measured from the
  rendered DOM; rects containing an endpoint are dropped
- any failure (walled-off target, oversized grid, no DOM) falls back
  to the previous direct-elbow behavior
2026-07-18 05:59:45 +02:00