fix(trace): recognise runtime boards and same-hole junctions in pin tracing
An ESP32 clock built by the agent stayed dark while QEMU was verifiably
emitting hundreds of GPIO edges per second (437/pin measured on the live
websocket). Reload did not help — this was not the seating race. Two
independent tracing bugs, reproduced from the real project circuit (fixture
included) and each sufficient to kill the display:
Boards added at runtime were invisible
--------------------------------------
isBoardComponent matches static id prefixes ('arduino-uno', ...), which only
covers the default board. Every board added at runtime gets a minted UUID id
— the agent's add_board always does — so traceDetailed treated the board
endpoint as an unknown component and resolved null, and SimulatorCanvas's
direct-wire subscription path skipped it entirely. Every Uno project happened
to work because they reuse the default board whose instance id IS the literal
'arduino-uno'. Both sites now consult the live boards list first, keeping
isBoardComponent as the legacy-id fallback.
Strip walking missed wires stacked on one hole
----------------------------------------------
The breadboard group walk continued the trace from every OTHER wired hole of
the strip, excluding the arrival hole by name. But two wires may legitimately
share one hole — the agent bridges strips straight into the seat hole (8 of
this circuit's 9 bridges land exactly on a resistor's own hole), which is
electrically identical to using a free hole of the strip. The name exclusion
made those junctions dead ends. Exclusion is now by incoming WIRE id, so
same-hole connections resolve; the depth bound already prevents ping-ponging
between two wires of one net.
With both fixes the exact saved circuit resolves every display pin to its
GPIO (A..DP -> 32,33,25,26,27,14,12,13; DIG1..4 -> 15,2,4,5; COM -> GND) and
the live project now shows 12:00 on the real QEMU simulation. traceDetailed
is exported for the regression test, which drives the real store with the
real circuit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
91965e9824
commit
3ac00ae5ff
|
|
@ -0,0 +1,79 @@
|
|||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Reproduction of the reported "ESP32 clock through a breadboard is dead"
|
||||
* bug, using the exact circuit the agent built (fixture exported from the
|
||||
* real project). QEMU was proven to emit every GPIO edge (437 events/pin on
|
||||
* the live site) — the break is in the frontend resolution/subscription.
|
||||
*
|
||||
* These tests interrogate each stage separately so a failure names the
|
||||
* broken stage rather than just "display dead".
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import { traceDetailed } from '../components/DynamicComponent';
|
||||
import circuit from './fixtures/esp32-clock-circuit.json';
|
||||
|
||||
const BOARD = circuit.boardId;
|
||||
|
||||
function loadCircuit() {
|
||||
const s = useSimulatorStore.getState();
|
||||
s.setComponents(circuit.components.map((c) => ({ ...c, x: 0, y: 0 })) as never);
|
||||
s.setWires(circuit.wires.map((w) => ({
|
||||
...w, waypoints: [], color: '#000',
|
||||
start: { ...w.start, x: 0, y: 0 }, end: { ...w.end, x: 0, y: 0 },
|
||||
})) as never);
|
||||
}
|
||||
|
||||
describe('ESP32 + breadboard-seated 7segment (real agent circuit)', () => {
|
||||
beforeAll(() => {
|
||||
const s = useSimulatorStore.getState();
|
||||
s.addBoard('esp32' as never, 0, 0, BOARD);
|
||||
loadCircuit();
|
||||
});
|
||||
|
||||
it('traceDetailed resolves every segment pin to its GPIO through the breadboard', () => {
|
||||
const state = useSimulatorStore.getState();
|
||||
const expected: Record<string, number> = {
|
||||
A: 32, B: 33, C: 25, D: 26, E: 27, F: 14, G: 12, DP: 13,
|
||||
DIG1: 15, DIG2: 2, DIG3: 4, DIG4: 5,
|
||||
};
|
||||
for (const [pin, gpio] of Object.entries(expected)) {
|
||||
const r = traceDetailed(state as never, '7segment_1', pin, 0);
|
||||
expect(r.arduinoPin, `7segment.${pin} should reach GPIO ${gpio}`).toBe(gpio);
|
||||
}
|
||||
});
|
||||
|
||||
it('COM resolves to ground (-1) through the rail', () => {
|
||||
const state = useSimulatorStore.getState();
|
||||
const r = traceDetailed(state as never, '7segment_1', 'COM', 0);
|
||||
expect(r.arduinoPin).toBe(-1);
|
||||
});
|
||||
|
||||
it('the esp32 board exposes a simulator with a live pinManager', () => {
|
||||
// DynamicComponent's attach effect reads store.simulator; the 7segment's
|
||||
// attachEvents bails out entirely when simulator.pinManager is missing,
|
||||
// which strands the display on the breadboard-blind onPinStateChange
|
||||
// path (single-hop isBoardComponent — never true through a breadboard).
|
||||
const s = useSimulatorStore.getState();
|
||||
s.setActiveBoardId(BOARD);
|
||||
const after = useSimulatorStore.getState();
|
||||
expect(after.simulator, 'store.simulator for the esp32 board').toBeTruthy();
|
||||
const pm = (after.simulator as { pinManager?: unknown } | null)?.pinManager;
|
||||
expect(pm, 'simulator.pinManager (attachEvents bails without it)').toBeTruthy();
|
||||
});
|
||||
|
||||
it('a GPIO edge from the bridge reaches a resolver subscribed via the pinManager', () => {
|
||||
const s = useSimulatorStore.getState();
|
||||
const pm = (s.simulator as { pinManager?: {
|
||||
onPinChange: (pin: number, cb: (pin: number, state: boolean) => void) => () => void;
|
||||
triggerPinChange: (pin: number, state: boolean, source?: string) => void;
|
||||
} } | null)?.pinManager;
|
||||
expect(pm).toBeTruthy();
|
||||
let seen: boolean | null = null;
|
||||
const un = pm!.onPinChange(32, (_p, st) => { seen = st; });
|
||||
// What Esp32Bridge.onPinChange does on a gpio_change frame:
|
||||
pm!.triggerPinChange(32, true, 'mcu');
|
||||
un();
|
||||
expect(seen, 'edge on GPIO 32 must reach subscribers').toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,700 @@
|
|||
{
|
||||
"boardId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"components": [
|
||||
{
|
||||
"id": "breadboard_1",
|
||||
"metadataId": "breadboard",
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"id": "7segment_1",
|
||||
"metadataId": "7segment",
|
||||
"properties": {
|
||||
"digits": 4,
|
||||
"colon": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_1",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_2",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_3",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_4",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_5",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_6",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_7",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resistor_220_8",
|
||||
"metadataId": "resistor-220",
|
||||
"properties": {
|
||||
"rotation": 90
|
||||
}
|
||||
}
|
||||
],
|
||||
"wires": [
|
||||
{
|
||||
"id": "699bda65-cc5a-40",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "21t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "35t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "ded18cb8-6804-4e",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "25t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "37t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "210ac86a-dd84-42",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "23b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "39t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "828e06ff-3b51-47",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "21b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "41t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "020ff259-c6ad-47",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "20b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "43t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "3d57ce29-1844-49",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "22t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "45t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "c24471d2-8acf-42",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "24b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "47t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "09dff781-b2a2-4c",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "22b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "49t.b"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "6c11a4bf-4271-4b",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "35b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "32"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "56becf80-4217-49",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "37b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "33"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "35148f93-0dfb-47",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "39b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "25"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "8e68b903-6bc1-41",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "41b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "26"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "74a77153-c2c4-4d",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "43b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "27"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "2a0eb2fd-d583-4d",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "45b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "14"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "b526bda1-01aa-47",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "47b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "12"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "966f0a6e-bf67-4e",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "49b.g"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "13"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "96d6e3a9-6ca0-44",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "20t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "15"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "7ebaeaa1-38ca-47",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "23t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "2"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "2fe1ff73-a9bd-49",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "24t.d"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "4"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "88cad420-25f5-4a",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "25b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "5"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "26f601ef-fb7d-4d",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "26b.h"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "bn.1"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "a86f8a48-6cca-4d",
|
||||
"start": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "bn.1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "56b16fd2-8904-4a5e-b269-5c6071ec285c",
|
||||
"pinName": "GND"
|
||||
},
|
||||
"bb": false
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "A"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "21t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "B"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "25t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "C"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "23b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "D"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "21b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "E"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "20b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "F"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "22t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "G"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "24b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "DP"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "22b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "DIG1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "20t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "DIG2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "23t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "DIG3"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "24t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "DIG4"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "25b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "COM"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "26b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-7segment_",
|
||||
"start": {
|
||||
"componentId": "7segment_1",
|
||||
"pinName": "CLN"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "26t.a"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_1",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "35t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_1",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "35b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_2",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "37t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_2",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "37b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_3",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "39t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_3",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "39b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_4",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "41t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_4",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "41b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_5",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "43t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_5",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "43b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_6",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "45t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_6",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "45b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_7",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "47t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_7",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "47b.f"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_8",
|
||||
"pinName": "1"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "49t.b"
|
||||
},
|
||||
"bb": true
|
||||
},
|
||||
{
|
||||
"id": "bbwire-resistor_",
|
||||
"start": {
|
||||
"componentId": "resistor_220_8",
|
||||
"pinName": "2"
|
||||
},
|
||||
"end": {
|
||||
"componentId": "breadboard_1",
|
||||
"pinName": "49b.f"
|
||||
},
|
||||
"bb": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ type TraceState = ReturnType<typeof useSimulatorStore.getState>;
|
|||
// Lifted to module scope (was inside getArduinoPin) so that getPinResolver
|
||||
// can call it too — the previous nested-scope version caused a runtime
|
||||
// ReferenceError "traceDetailed is not defined" on the simulator page.
|
||||
function traceDetailed(
|
||||
export function traceDetailed(
|
||||
state: TraceState,
|
||||
fromId: string,
|
||||
fromPin: string,
|
||||
|
|
@ -146,10 +146,16 @@ function traceDetailed(
|
|||
w.start.componentId === fromId && w.start.pinName === fromPin ? w.start : w.end;
|
||||
const otherEp = selfEp === w.start ? w.end : w.start;
|
||||
|
||||
if (isBoardComponent(otherEp.componentId)) {
|
||||
const boardKind =
|
||||
state.boards.find((b) => b.id === otherEp.componentId)?.boardKind ??
|
||||
otherEp.componentId;
|
||||
// A board endpoint is recognised by the LIVE boards list first.
|
||||
// `isBoardComponent` matches static id prefixes ('arduino-uno', …), which
|
||||
// only covers the default board — every board added at runtime (the agent
|
||||
// mints UUID ids) failed the check, so tracing treated it as an unknown
|
||||
// component and returned null. Symptom: an ESP32 clock whose QEMU was
|
||||
// emitting hundreds of GPIO edges/second at a display that stayed dark,
|
||||
// because no resolver ever attached.
|
||||
const boardEp = state.boards.find((b) => b.id === otherEp.componentId);
|
||||
if (boardEp || isBoardComponent(otherEp.componentId)) {
|
||||
const boardKind = boardEp?.boardKind ?? otherEp.componentId;
|
||||
const pin = boardPinToNumber(boardKind, otherEp.pinName);
|
||||
if (pin !== null) return { arduinoPin: pin, crossedActiveDevice: activeSeen };
|
||||
} else {
|
||||
|
|
@ -176,14 +182,22 @@ function traceDetailed(
|
|||
// Breadboards join N holes per internal group (5-hole strip / power
|
||||
// rail), which the 2-terminal PASSIVE_PIN_PAIRS map can't express.
|
||||
// Continue the trace from every OTHER wired hole in the same group.
|
||||
//
|
||||
// Exclusion is by INCOMING WIRE, not by hole name: two wires may
|
||||
// legitimately share one hole (a seated pin plus a jumper landing in
|
||||
// that same hole — the agent bridges strips straight into the seat
|
||||
// hole). Excluding the arrival hole made those stacked connections
|
||||
// invisible: an ESP32 clock with QEMU firing hundreds of GPIO edges
|
||||
// per second sat dark because every segment's bridge landed on its
|
||||
// resistor's own seat hole and the trace dead-ended there.
|
||||
const bbGroup = comp && breadboardGroupKey(comp.metadataId, otherEp.pinName);
|
||||
if (bbGroup && comp) {
|
||||
const groupPins = new Set<string>();
|
||||
for (const gw of state.wires) {
|
||||
if (gw.id === w.id) continue; // never bounce back on the same wire
|
||||
for (const ep of [gw.start, gw.end]) {
|
||||
if (
|
||||
ep.componentId === comp.id &&
|
||||
ep.pinName !== otherEp.pinName &&
|
||||
breadboardGroupKey(comp.metadataId, ep.pinName) === bbGroup
|
||||
) {
|
||||
groupPins.add(ep.pinName);
|
||||
|
|
|
|||
|
|
@ -1153,11 +1153,15 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
|
|||
const selfEndpoint = isStartSelf ? wire.start : wire.end;
|
||||
const otherEndpoint = isStartSelf ? wire.end : wire.start;
|
||||
|
||||
if (isBoardComponent(otherEndpoint.componentId)) {
|
||||
// Recognise board endpoints from the LIVE boards list first —
|
||||
// isBoardComponent only matches static id prefixes, so every
|
||||
// runtime-added board (agent-minted UUID ids) failed it and its
|
||||
// directly-wired components were never subscribed.
|
||||
const boardInstance = boards.find((b) => b.id === otherEndpoint.componentId);
|
||||
if (boardInstance || isBoardComponent(otherEndpoint.componentId)) {
|
||||
// Use the board's actual boardKind (not just its instance ID) so that
|
||||
// a board whose ID is 'arduino-uno' but whose kind is 'esp32' gets the
|
||||
// correct GPIO mapping ('GPIO4' → 4, not null).
|
||||
const boardInstance = boards.find((b) => b.id === otherEndpoint.componentId);
|
||||
const lookupKey = boardInstance ? boardInstance.boardKind : otherEndpoint.componentId;
|
||||
const pin = boardPinToNumber(lookupKey, otherEndpoint.pinName);
|
||||
if (pin !== null && pin >= 0) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue