fix(DynamicComponent): pass componentId through to PinTracer

`PinTracer` signature is `(componentId, componentPinName) => number | null`
but the local `getArduinoPin` lambda only accepted one arg and used the
closure-captured `id`. When `createDefaultPinResolver` passed both args
(per the typed signature), JS bound the FIRST arg (the componentId) into
the lambda's single `componentPinName` parameter. `traceDetailed` then
looked up a pin literally named "rgb-led-1" on component "rgb-led-1",
returned null, and the resolver locked itself into 'FLOATING' state —
its onChange path never subscribed and the wokwi-rgb-led element's
ledRed/ledGreen/ledBlue stayed at 0 forever even as the SPICE side
correctly cycled through R, G, B, Y, C, M, W via analogWrite().

Same bug latent for any multi-pin component that goes through the
PinResolver path (multi-pin LEDs, RGB strips, 7-seg drivers, anything
that calls `getPinResolver(<pinName>)` for several pin names).

Fix: lambda now accepts both shapes — `getArduinoPin(pinName)` (legacy
single-arg used by every PartSimulationRegistry handler) AND
`getArduinoPin(componentId, pinName)` (PinTracer 2-arg form used by
createDefaultPinResolver / createSpiceResolvedPinResolver). Picks the
right componentId in either case.

Verified via the rgb-led example: ledRed/ledGreen/ledBlue now cycle
0→255→0 in sync with the SPICE node voltages on pins 9/10/11.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
David Montero Crespo 2026-05-18 22:07:42 -03:00
parent 81837eedb9
commit 55b3dd29e5
1 changed files with 22 additions and 4 deletions

View File

@ -369,11 +369,29 @@ export const DynamicComponent: React.FC<DynamicComponentProps> = ({
// Helper to find Arduino pin connected to a component pin.
// Traces through electrically-transparent passive components so that a
// circuit like LED-cathode → resistor → GND returns -1 (GND) instead
// of null. Delegates to the module-level `traceDetailed` (shared with
// getPinResolver).
const getArduinoPin = (componentPinName: string): number | null => {
// of null. Delegates to the module-level `traceDetailed`.
//
// Two call shapes are supported because this same function is passed
// BOTH to PartSimulationRegistry handlers (which call it as
// `getArduinoPin(componentPinName)`) AND to `createDefaultPinResolver`
// as a `PinTracer` (which calls it as `tracePin(componentId,
// componentPinName)`). When the second arg is present we treat the
// first as a componentId override; otherwise we use the closure-
// captured component id. The previous single-arg signature silently
// matched the PinTracer 2-arg call as `(componentId, undefined)` —
// traceDetailed then looked up a pin literally named "rgb-led-1" on
// component "rgb-led-1", got null, and the PinResolver reported
// FLOATING forever (the canonical "wokwi-rgb-led never lights up
// even though SPICE is driving R/G/B" symptom).
const getArduinoPin = (
componentIdOrPin: string,
maybePinName?: string,
): number | null => {
const state = useSimulatorStore.getState();
return traceDetailed(state, id, componentPinName, 0).arduinoPin;
const componentId = maybePinName !== undefined ? componentIdOrPin : id;
const componentPinName =
maybePinName !== undefined ? maybePinName : componentIdOrPin;
return traceDetailed(state, componentId, componentPinName, 0).arduinoPin;
};
// PinResolver factory — Phase 0 of the mixed-mode simulator project