The user-reported 'LED with proper series resistor shows 1.84 V at
the anode but never visually lights up' had its root cause here,
not in ngspice / not in the LED brightness handler / not in any
component id naming choice. ngspice parses 'V_led-builtin_sense'
just fine; the diode conducts and the node voltage is exactly what
you'd compute by hand.
What breaks is the JS regex that scans the emitted netlist to
collect voltage-source names so CircuitSimulationService can ask
the scheduler to read their branch-current vectors:
const m = card.match(/^([Vv][_\w]*)\s/);
[_\w]* doesn't accept '-'. For a card 'V_led-builtin_sense …' the
capture is 'V_led' (truncated at the hyphen). The voltageSources
array gets the wrong name; CircuitSimulationService pushes
'i(v_led)' into extraVectorsOfInterest; ngspice has no such vector
so the readVec promise rejects silently; branchCurrents['v_led-builtin_sense']
is never populated; the LED handler in BasicParts.ts sees raw =
undefined, the SPICE-memo path is skipped, and the digital fallback
runs but only sets the LED on when the PinResolver classifies the
anode as a direct GPIO connection (which it does NOT when an
intermediate resistor is in series). Dark LED.
Fix is adding '-' to the character class. One character. All five
existing examples I previously 'fixed' by just adding a series
resistor will now light up correctly without renaming any of their
component ids. Same for any saved user project with hyphenated ids
and for the auto-generated picker ids that used to contain hyphens.
The earlier underscore-id workarounds (default canvas + picker
template) stay in place as defense in depth — they don't break
anything and they keep the SPICE side clear of avoidable special
characters.
The service is the single owner of the simulation loop. Replaces
the trio of wireElectricalSolver + connectLegacySolverToMixedMode +
connectMixedModeSchedulerToStore once G* lands.
Architecture:
- Depends on PORTS only — SimulatorStorePort, ElectricalStorePort,
MixedModeSchedulerPort. Zero coupling to useSimulatorStore /
useElectricalStore / WASM. Easy to test with fakes (and that's
what circuit-simulation-service.test.ts does).
- Single tick(): build netlist → load → solve → extract → publish.
Coalesces concurrent triggers so rapid store changes collapse to
one trailing solve.
- Domain ElectricalSnapshot type covers nodeVoltages + branchCurrents
+ pinNetMap + timeWaveforms + analysisMode + warnings. Shape
matches what the 12 existing useElectricalStore consumers read.
NetlistBuilder extension: BuildNetlistResult now reports `nets`
(every non-ground SPICE net) and `voltageSources` (every V card the
builder emitted). The service uses these to construct the full
vectorsOfInterest list — every node voltage + every branch current
— so the solver returns the data the legacy consumers want.
Scheduler addition: `setExtraVectorsOfInterest(vectors)` lets the
orchestrator add to the per-pin set. Branch currents (i(v_*))
flow through this hook.
8 service tests cover initial solve, branch current extraction,
re-solve on store change, no-spurious-solve, coalescing, .tran
waveforms, warnings forwarding, error-tolerance.
Next: C1+C2 — extract ADC injection / waveform replay into a
solver-agnostic module that just subscribes to useElectricalStore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires can now carry a `length_cm` property. When set, the NetlistBuilder
treats them as a real resistor (0.01 ohm/cm ≈ AWG 22 copper) instead of
the legacy perfect-conductor union. Wires without `length_cm` are
unchanged — 100% backwards compatible until the UI starts attaching
length values based on canvas geometry.
Implementation:
- `WireForSpice.length_cm?: number` added to types
- Union-Find pass skips `union(a, b)` when length_cm > 0, so endpoints
end up in separate nets
- After component-card emission, scan `resistiveWires` and emit
`R_wire_<id> <netA> <netB> <ohms>` for each
- Pull-down detection runs after so the wire R counts as a DC path
Verified end-to-end with real ngspice:
- 100/100 divider at 5V → vmid = 2.5V (legacy, no wire R)
- Same with 1 cm supply wire → vmid = 2.4999 V (0.25 mV drop)
- Same with 500 cm supply wire → vmid ≈ 2.439 V (~6% drop)
5 new Phase 4 tests + 208 regression tests pass.
This is the plumbing-first deliverable from the original sim-mixedmode
plan — UI work (compute length from canvas waypoints) is a separate
front-end task.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes the gap that Phase 1b step 4 surfaced: the legacy pinNetMap was
built from board endpoints only, so the bridge from legacy solver to
MixedModeScheduler had nothing to publish for component pins like
"q1:C" — every SpiceResolvedPinResolver was stuck on FLOATING.
Now pinNetMap contains an entry for every wire endpoint, board or
component. Backwards compatible: legacy ADC injection only ever looked
up `boardId:pinName` keys, which are unchanged.
The new e2e integration test wires up real ngspice (eecircuit-engine,
no mock):
Arduino pin 9 → 1k → 2N2222 base; collector via 220 to 5V
- pin 9 HIGH → BJT saturated → Vc ≈ 0.05V → resolver emits LOW
- pin 9 LOW → BJT cut off → Vc ≈ 5V → resolver emits HIGH
Validated against the AVR_HC logic family (Phase 3). With 216 tests
green across 25 files, the Phase 1b pipeline is now demonstrably
correct end-to-end against a real SPICE solver, not just mocks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Modified the index file to reflect the new naming convention for Velxio components.
- Changed JSX declarations to use 'velxio-' prefix for various components.
- Updated component overrides to replace 'wokwi-' with 'velxio-' for logic gates and other components.
- Adjusted SVG generation script to use 'velxio-' prefix for BMP280 and Raspberry Pi components.
- Marked submodules as dirty in QEMU and RP2040 libraries.
- Added .prettierignore and .prettierrc.json for consistent code formatting.
- Introduced InstrumentComponent with support for Voltmeter and Ammeter, including pin information handling.
- Decoupled electrical simulation from the simulator store, ensuring SPICE is always active for accurate circuit analysis.
- Removed feature flag for electrical simulation, simplifying the state management.
- Preloaded SPICE engine at app start to eliminate latency during the first solve.
- Added comprehensive tests for MOSFET PWM LED behavior and NPN transistor switch functionality, ensuring correct current flow and response to pin states.
- Implemented diagnostics for floating input nodes in RC low-pass filter circuits, addressing singular matrix issues in SPICE simulations.
- Introduced active semiconductor metadata registry for better component management and simulation fidelity.
- Updated Vite configuration to force re-bundling of local wokwi-elements after component additions.
Electrical simulation is now active by default (mode='spice' instead of
'off') — users no longer need to toggle the mode on manually. The engine
lazy-loads on first solve, so there is no startup cost penalty.
Changes:
- useElectricalStore: default mode = 'spice' when ELECTRICAL_SIM_ENABLED
- subscribeToStore: ADC_PIN_MAP expanded to all 18 board types (Uno, Nano,
Mega with 16 ADC channels, ATtiny85, RP2040 GP26-29, ESP32/S3/C3 GPIO
ADCs). Voltages from SPICE solutions now inject into MCU ADC peripherals
for all boards.
- BasicParts LED: reads branchCurrents from useElectricalStore when SPICE
is active. Brightness = clamp(|I_led| / 20mA, 0, 1) instead of boolean.
Subscribes to store changes to update in real time.
- ElectricalOverlay: shows per-wire voltage labels (gold monospace on dark
pill) using buildWireNetMap() which replicates the NetlistBuilder's
Union-Find to map wireId -> netName -> nodeVoltage. Summary pill shows
net count + solve time.
- NetlistBuilder: new export buildWireNetMap() for lightweight wire-to-net
resolution without running ngspice.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>