Replaces the Phase 1b vcc/2-flat threshold with per-logic-family
Vil/Vih thresholds + Schmitt-trigger hysteresis where applicable.
SPICE-resolved digital reads now match what real ICs actually do —
TTL noise margins, CMOS rail-to-rail, 74HC14 Schmitt hysteresis,
LVCMOS33 vs CMOS-5V interop.
New module: simulation/LogicFamilies.ts
- LogicFamily interface (vcc, vil, vih, vil_schmitt?, vih_schmitt?,
cin_pF, vol_max?, voh_min?, output_impedance_ohm?)
- FAMILIES catalog: TTL, CMOS-5V, CMOS-5V-SCHMITT, CMOS-5V-TTL-INPUTS,
LVCMOS33, AVR_HC, CMOS-3.3V — all sourced from TI / ATmega328P /
JEDEC datasheets.
- BOARD_FAMILY: per-board lookup. Uno/Mega/Nano/ATtiny → AVR_HC,
ESP32 family + Pi Pico → LVCMOS33, fall back to AVR_HC for
unknown boards.
- getBoardLogicFamily() and getLogicFamilyById() helpers.
PinResolver:
- SpiceResolvedConfig docstring rewritten with Phase 3 wording.
- New `configFromLogicFamily()` builder — picks Schmitt thresholds
when the family declares them, falls back to vih/vil otherwise.
DynamicComponent:
- When the trace crosses an active device, the SPICE-resolved
resolver is now built with the OWNER BOARD's logic family
instead of vcc/2. Hysteresis comes through automatically for
boards whose native family is Schmitt-capable.
- Phase 3 continued: per-component logicFamily override from
components-metadata.json (so e.g. a 74HC14 placed on an Arduino
Uno gets Schmitt thresholds even though the BOARD is AVR_HC).
Tests:
- logic-families.test.ts (new) — 19/19 passing.
Covers catalog sanity (vil < vih, vol_max ≤ vil, voh_min ≥ vih),
per-board lookup, Schmitt vs non-Schmitt config, noise rejection
behavior of 74HC14 Schmitt resolver, last-state-wins behavior
of CMOS-5V dead band.
- Phase 0 + Phase 1b regression: 16/16 still passing.
- tsc --noEmit on new files: clean.
No deploy in this commit — staged for end-of-session rebuild.
Adds the architecture pieces for mixed-mode coupling without yet
driving the SPICE engine. Components on a path that crosses an active
device (BJT, MOSFET, op-amp, diode, regulator, LED, relay) now route
through a new SPICE-resolved PinResolver variant; everything else
keeps the digital fast-path from Phase 0.
What ships:
- simulation/PinResolver.ts
* `isActiveDevice(metadataId)` predicate + `ACTIVE_DEVICE_PREFIXES`
list (BJTs, MOSFETs, op-amps, diodes, regulators, LED, relay).
* `DetailedPinTrace` / `DetailedPinTracer` types — the trace
function now reports whether it crossed an active device, on
top of the Arduino pin number.
* `createSpiceResolvedPinResolver()` — new factory; reads voltages
from a `SpiceVoltageSource` and threshold-converts to HIGH/LOW
with hysteresis (thresholdHigh != thresholdLow → Schmitt-like).
- simulation/spice/MixedModeScheduler.ts (new)
* Singleton orchestrator that holds the NgSpiceInteractive engine
and the SpiceVoltageSource subscription registry.
* `start()` / `stop()` / `dispose()` lifecycle.
* `subscribe()` + `getCurrentVoltage()` implement SpiceVoltageSource.
* `onMcuPinChange()` placeholder for the alter+tran event loop.
* Skeleton: subscribers register but never receive events yet.
Phase 1b continued will wire NgSpiceInteractive into the loop.
- components/DynamicComponent.tsx
* Trace function extended with `traceDetailed()` that tracks
whether the BFS crossed an active component.
* PinResolver factory branches: active-path → SPICE-resolved (uses
the scheduler), digital-only → existing default impl. Default
threshold = vcc/2 with no hysteresis; Phase 3 will replace with
per-logic-family Vil/Vih.
Phase 0 LED behavior intact (digital path). Phase 1b SPICE-resolved
path falls back to FLOATING until Phase 1b continued wires the engine.
Tests:
- pin-resolver-phase1b.test.ts (new) — 8/8 passing.
Covers isActiveDevice for every BJT/MOSFET/op-amp/diode/regulator
metadata id; SPICE-resolved resolver state reporting, threshold
conversion, hysteresis dead-band, unsubscribe.
- pin-resolver.test.ts (Phase 0) — 8/8 still passing (no regression).
- tsc --noEmit on the new files: clean.
No deploy in this commit — staged for end-of-session rebuild + push
per user preference.
Decouple per-component handlers from direct pinManager.onPinChange +
getArduinoPinHelper subscriptions by introducing a small PinResolver
interface. The Phase 0 default impl is functionally identical to the
legacy path — it just routes through PinResolver instead of being
inlined in every handler. Zero behavior change.
The point is to make Phase 1 possible: swap the default impl for a
SPICE-resolved version that watches node voltages and threshold-
converts to digital events, without rewriting every handler.
Files:
- simulation/PinResolver.ts (new) — interface + default factory
- parts/PartSimulationRegistry.ts — additive 5th arg to
attachEvents (getPinResolver?), legacy 4-arg signatures keep
working unchanged
- components/DynamicComponent.tsx — assembles the PinResolver from
the wire-trace logic + PinManager subscriptions + board Vcc
lookup, passes it as the 5th arg to attachEvents
- parts/BasicParts.ts — LED handler migrated as proof of concept
(resolver-first path, legacy 4-arg path kept as fallback for
tests / unmigrated harnesses)
- __tests__/pin-resolver.test.ts (new) — 8 unit tests covering
FLOATING / GND / HIGH / LOW / GPIO subscriptions / unsubscribe
Vitest: 8/8 pin-resolver tests pass. 1300+ existing tests still pass;
the one pre-existing flake (spice-rectifier-live-repro timing out >60s)
is unrelated to this commit — verified by running the test on plain
HEAD without these changes (same timeout).
See project/sim-mixedmode/phase-00-pin-resolver.md (in the velxio-prod
repo) for full phase context.