digiwleeaBlogOpen the lab →
← All posts

Every way dragging a component can silently corrupt a circuit

2026-07-09 · Tacettin Sancak

My circuit editor had a bug report I couldn't reproduce: "I moved a transistor and my circuit stopped working." No crash, no error, no visual glitch. The schematic looked fine. It just... computed the wrong thing.

It turned out there wasn't one bug. There were five distinct ways that moving a component could silently corrupt a circuit's connectivity, and finding them required building a test harness that could prove a circuit's MEANING was unchanged, not just its appearance. Here is the whole safari, because I suspect every schematic-style editor (circuit tools, node graphs, diagram editors) has some of these bugs right now.

The ground rule: geometry IS connectivity

In a schematic editor, two things are connected when their geometry touches: a wire endpoint on a pin, two pins on the same grid cell, a wire endpoint resting on another wire's body. There is no separate connectivity database to keep consistent - the picture is the data.

That's elegant until things move. Every drag is a geometry edit, and every geometry edit is potentially a connectivity edit you didn't intend. The whole problem space falls out of one question: when a component moves, which geometric coincidences were load-bearing, and which are accidents?

Bug 1: the T-junction that stayed behind

A wire from A to B, with a third wire tapping its middle (a T-junction). Move the component that owns A. The moving logic dutifully brought along every wire endpoint that sat on one of the component's pins... and the tap point wasn't on a pin. It sat on the moving wire's INTERIOR. The wire left; the tap stayed; the net silently split in two.

The fix taught me the pattern for everything that followed: make implicit geometry explicit before you transform it. Before any move, we split wires at every junction point, so a T-junction becomes three wires sharing an endpoint. Shared endpoints survive any transformation - interior residence doesn't. The split is invisible (the pieces are colinear) and electrically neutral, so it can run at the start of every drag.

Bug 2: the corner that landed on a power pin

Moving a component drags its attached wires along, and a dragged wire's corner can land ANYWHERE. In one reproducible case, moving a transistor put a wire corner exactly on the ground rail's pin. Endpoint-on-pin means connected: the input net was now shorted to ground. Nothing looked wrong - there was simply a wire corner touching a pin, which is also what a deliberate connection looks like.

That's the nasty part: the corrupt geometry is INDISTINGUISHABLE from intended geometry after the fact. No later pass can repair it, because no later pass can know you didn't mean it. The only correct place to stop it is BEFORE the drop: we simulate the landing per grid cell during the drag, and if any moved wire corner would coincide with a foreign pin or wire, the drop is refused - red outline, red wash, not-allowed cursor, and a toast saying which obstacle refused it.

Bug 3: the 180° rotation that swapped a gate's pins through its body

Rotate a one-in-one-out component 180° and its input and output pins swap positions. The wire-following logic correctly moved each wire endpoint to its pin's new location - which dragged the input wire straight ACROSS the component's body and over the output pin. Pin-on-wire-interior means connected: input and output fused into one net. The component became a wire.

The fix: after a rotation moves wire endpoints, any followed wire whose span now crosses a pin or the component's own body gets re-routed around it with an A* router that treats every pin as an obstacle. Rotation is not a connecting gesture; its wire artifacts never get to create contacts.

Bug 4: my own healing pass was manufacturing corruption

This one hurt. We had a pipeline pass that split wires at junctions (the Bug 1 medicine) running AFTER moves as well as before. But after a move, a stretched wire lying across a pin is an ACCIDENT (Bug 2's cousin, produced by the stretch) - and splitting there codifies the accident into a real junction. My repair pass was laundering corruption into legitimate topology.

The rule that fell out is the deepest thing I learned: the same geometric situation means different things depending on WHEN it arose. A pin on a wire's interior at gesture START is user intent (drawing a wire across a pin is a standard tap gesture). The same configuration appearing mid-pipeline, after a move deformed things, is always an artifact. So: split at gesture start only; after the geometry changes, artifacts get re-routed around, never consecrated.

Bug 5: docking, and the move that "deleted" a connection

Two pins on the same cell are connected (that's how you butt components together). Now watch this sequence: a user drops component B so its pin lands on a wire's endpoint - fine, deliberate connection. The connecting wire happens to shrink to zero length and gets cleaned up, leaving a pure pin-to-pin dock. Weeks later they nudge B one cell left. The dock separates. The connection is gone, and nothing on screen ever showed a wire to miss.

Two-part fix. Separating a dock now BRIDGES it: a real wire is drawn from the anchor pin to the moved pin, so the connection becomes visible and survives. And going forward, moves can't create docks at all - which brings me to the endgame.

The endgame: moves are connection-neutral

After fixing each corruption individually, we made the invariant absolute: a move can neither break nor create a connection. Repositioning is not a wiring gesture. If a moved pin would land on a pin, endpoint, or wire body of a DIFFERENT net, the drop is refused (same-net contact stays allowed, so you can tuck a part against its own wiring). If you want to connect two things, you draw a wire - an act with unambiguous intent. The refusal toast literally teaches this: "Moving never connects parts. Leave a gap, then draw a wire."

This one policy retired the entire class. Every bug above was some flavor of "the move accidentally edited connectivity"; now moves provably can't.

How we know it actually works

None of this would be trustworthy from code review alone. The harness that found these bugs drives REAL pointer events (not synthetic model calls) through hundreds of seeded random drags, rotations, and group operations, and after every single operation asserts two things: the circuit's structural netlist partition is byte-identical, and its simulated behavior (toggle the inputs, read the probes) is unchanged. Geometry may differ; meaning may not.

The seeds make every failure deterministic: when operation 37 of seed 12345 breaks the net, it breaks identically every run, and you can dump the wire list before and after the exact guilty operation. Three of the five bugs were found this way at operations 6, 17, and 37 of random walks - inputs no human tester would have composed.

The same suite now runs in minutes against every editor change. It has been green for hundreds of operations across mouse, keyboard, and touch, across primitive and hierarchical components, on boards from six to 170 parts.

Takeaways for anyone building a spatial editor

  1. If geometry is your connectivity model, every transform is a semantic edit.

Enumerate the coincidences that mean "connected" and audit every one against every transform.

  1. Make implicit relationships explicit BEFORE transforming, at the moment

user intent is still readable.

  1. Refuse at drop time what you cannot repair afterward - corrupt geometry

that looks like intended geometry is unrepairable by definition.

  1. Decide what each gesture is FOR. Making "move" connection-neutral deleted

more bugs than any fix, because it deleted the ambiguity the bugs lived in.

  1. Test meaning, not pixels: a seeded random-input harness asserting semantic

invariants finds what neither unit tests nor manual QA will.

The editor this came from is digiwleea, a free browser lab where you build a CPU starting from single transistors. All of the above is live in it - go move things around and try to break a circuit. I'd genuinely like to hear about it if you manage.

This is from building digiwleea, a free browser lab where you build a CPU starting from single transistors. Go move things around and try to break a circuit.

← All posts