feat(sim): P2 wiring ERC slice 2 — VCC-to-GND short + shorted-out parts

- Power short (blocking error): a wire joining a VCC-type pin directly to a
  GND-type pin shorts the supply to ground. The current-based short-circuit
  rule only inspects battery/signal-generator/power-supply sources, so it
  misses a board-rail-to-GND short with no such source -> name it structurally.
- Shorted-out part (warning): a 2-terminal part with both terminals on the same
  node has no effect on the circuit.

Both graph-based, run before the solve. Zero false positives across the 69
gallery examples; gallery pre-flight tests still pass (no spurious blocking).
This commit is contained in:
David Montero 2026-06-18 03:32:17 +02:00
parent 8683c1ecf0
commit 6f3603d88b
2 changed files with 76 additions and 0 deletions

View File

@ -426,6 +426,41 @@ describe('verifyCircuit — wiring ERC (bad connections)', () => {
expect(mc, JSON.stringify(result.warnings)).toBeDefined();
},
);
it(
'errors when a VCC pin is wired directly to a GND pin',
{ timeout: 30_000 },
async () => {
const input: BuildNetlistInput = {
components: [{ id: 'o1', metadataId: 'ssd1306', properties: {} }],
wires: [w('w1', ['o1', '3V3'], ['o1', 'GND'])], // VCC tied straight to GND
boards: [],
analysis: { kind: 'op' },
};
const result = await verifyCircuit(input);
const codes = result.errors.map((e) => e.code);
expect(codes, JSON.stringify(result.errors)).toContain('power-short');
},
);
it(
'warns when a part has both terminals on the same node (shorted out)',
{ timeout: 30_000 },
async () => {
const input: BuildNetlistInput = {
components: [pwr('src', 5), res('r1', '1k')],
wires: [
w('w1', ['src', 'SIG'], ['r1', '1']),
w('w2', ['src', 'SIG'], ['r1', '2']), // both terminals on the SIG net
],
boards: [],
analysis: { kind: 'op' },
};
const result = await verifyCircuit(input);
const sc = result.warnings.find((x) => x.code === 'shorted-component' && x.componentId === 'r1');
expect(sc, JSON.stringify(result.warnings)).toBeDefined();
},
);
});
// ── Sanity: shipping examples never trigger errors ─────────────────────────

View File

@ -36,6 +36,8 @@ export type WarningCode =
| 'over-voltage'
| 'reverse-polarity'
| 'missing-connection'
| 'power-short'
| 'shorted-component'
| 'resistor-overpower'
| 'led-no-current';
@ -211,6 +213,45 @@ export async function verifyCircuit(
}
}
// Power rail tied to ground: a wire directly joining a VCC-type pin and a
// GND-type pin shorts the supply to ground. When a board drives the rail and
// no battery/signal-generator source is present, the current-based
// short-circuit rule (which only inspects those sources) misses it — so name
// it structurally here. Blocking error.
const VCC_PIN_RE = /^(vcc|vdd|vcc_rail|5v|3v3|3\.3v)$/i;
const GND_PIN_RE = /^(gnd|vss|vee|ground|gnd\.\d+)$/i;
let powerShortReported = false;
for (const wire of input.wires) {
const a = wire.start.pinName;
const b = wire.end.pinName;
const shorted = (VCC_PIN_RE.test(a) && GND_PIN_RE.test(b)) || (GND_PIN_RE.test(a) && VCC_PIN_RE.test(b));
if (shorted && !powerShortReported) {
powerShortReported = true;
errors.push({
severity: 'error',
code: 'power-short',
message: `Power is shorted to ground — a wire connects a VCC pin (${a}) directly to a ground pin (${b}). Remove that connection; it would dump the full supply current through the wire.`,
});
}
}
// Two-terminal part shorted across itself: both terminals on the same net, so
// it carries no voltage and has no effect on the circuit. Non-blocking hint.
for (const comp of input.components) {
const pins = twoTerminalPins(comp.metadataId);
if (!pins) continue;
const n0 = pinNetMap.get(`${comp.id}:${pins[0]}`) ?? (pins[0] === '' ? pinNetMap.get(`${comp.id}:-`) : undefined);
const n1 = pinNetMap.get(`${comp.id}:${pins[1]}`) ?? (pins[1] === '' ? pinNetMap.get(`${comp.id}:-`) : undefined);
if (n0 !== undefined && n1 !== undefined && n0 === n1) {
warnings.push({
severity: 'warning',
code: 'shorted-component',
componentId: comp.id,
message: `${comp.metadataId} ${comp.id} has both terminals on the same node — it is shorted out and has no effect on the circuit.`,
});
}
}
let solve: ElectricalSolveResult | undefined;
try {
const cooked = await runSpice(netlist);