CircuitSimulation
public partial class CircuitSimulation : System.IDisposableThe circuit simulation a part belongs to. It steps every component and solves the circuit matrix each tick. Circuit logic reaches it through Simulation, and stamps into it.
Nested types
Section titled “Nested types”CircuitSimulation.BusCollision: Two devices answering one master on one bus.
Constructors
Section titled “Constructors”CircuitSimulation
Section titled “CircuitSimulation”public CircuitSimulation();Not documented yet.
Properties you read and set
Section titled “Properties you read and set”CircuitRowInfo
Section titled “CircuitRowInfo”public NativeArray<RowInfo_t> CircuitRowInfo { get; }Not documented yet.
NodeMesh
Section titled “NodeMesh”public NativeArray<long> NodeMesh { get; }Not documented yet.
NodeMeshRowSize
Section titled “NodeMeshRowSize”public NativeArray<int> NodeMeshRowSize { get; }Not documented yet.
public NativeArray<long> Leads { get; }Not documented yet.
NativeNodeList
Section titled “NativeNodeList”public NativeArray<long> NativeNodeList { get; }Not documented yet.
RightSide
Section titled “RightSide”public double* RightSide { get; }Direct pointer to the C++ right_side array. Set by Set_right_side during Analyze. Non-linear components use this to stamp current sources directly without P/Invoke overhead.
NullStamp
Section titled “NullStamp”public DynamicSingle NullStamp { get; }Not documented yet.
PaceScale
Section titled “PaceScale”public double PaceScale { get; set; }Not documented yet.
HasTimeFollower
Section titled “HasTimeFollower”public bool HasTimeFollower { get; }Not documented yet.
FollowerHeldSeconds
Section titled “FollowerHeldSeconds”public double FollowerHeldSeconds { get; }Not documented yet.
LoopAnalyzeCount
Section titled “LoopAnalyzeCount”public int LoopAnalyzeCount { get; }Not documented yet.
LastLoopAnalyzeSimTime
Section titled “LastLoopAnalyzeSimTime”public double LastLoopAnalyzeSimTime { get; }Not documented yet.
FollowerStepSeconds
Section titled “FollowerStepSeconds”public double FollowerStepSeconds { set; }Not documented yet.
Solver
Section titled “Solver”public SolverContext Solver { get; }This simulation’s solver context, for benches that read ITS tape and profiler counters rather than some other simulation’s. The handle-free KLU_Tape_Stats / KLU_Profile_Read report the default context, which no simulation uses any more, so they would read zero.
UseSolverKernel
Section titled “UseSolverKernel”public bool UseSolverKernel { get; set; }Per simulation switch, defaulting to the process-wide one; tests A/B against KLU with it.
SolverKernelBackend
Section titled “SolverKernelBackend”public string SolverKernelBackend { get; set; }“native” or “il”; null takes the process-wide default. Tests A/B the two backends with it.
KernelSolveCount
Section titled “KernelSolveCount”public long KernelSolveCount { get; }Solves served by the generated kernel, builds performed, and the last build report.
KernelBuildCount
Section titled “KernelBuildCount”public int KernelBuildCount { get; }Not documented yet.
LastKernelReport
Section titled “LastKernelReport”public string LastKernelReport { get; }Not documented yet.
TargetFrequency
Section titled “TargetFrequency”public double TargetFrequency { get; set; }Not documented yet.
Unthrottled
Section titled “Unthrottled”public bool Unthrottled { get; set; }Not documented yet.
CatchUpForgivenSeconds
Section titled “CatchUpForgivenSeconds”public double CatchUpForgivenSeconds { get; }How many sim seconds of schedule have been written off since the last anchor (diagnostics).
DevicePaced
Section titled “DevicePaced”public bool DevicePaced { get; }True while the throttled loop is pacing against the device clock (diagnostics).
AutoThrottle
Section titled “AutoThrottle”public bool AutoThrottle { get; set; }Not documented yet.
EffectiveFrequency
Section titled “EffectiveFrequency”public double EffectiveFrequency { get; }The rate the throttled loop actually paces to — equals TargetFrequency unless auto-throttle has stepped down (HUD shows both when they differ).
ElementDtCeiling
Section titled “ElementDtCeiling”public double ElementDtCeiling { get; }The tightest element-declared BaseCircuitLogic.MaxTimeStep in the analyzed circuit, floored at 100 ns and capped at the 1 ms integration ceiling. Recomputed per Analyze; both throttle branches clamp TimeStep to it so a microcontroller is never handed a step bigger than its cycle budget (which it would silently truncate — the unthrottled “sim collapses at high clock” spiral).
DebugDumpEnabled
Section titled “DebugDumpEnabled”public bool DebugDumpEnabled { get; set; }Master switch for the diagnostic dumps (topology after Analyze, voltages on the next Tick). Written through DebugLog to CRUMB’s log. Set false to silence the spam.
IsRunning
Section titled “IsRunning”public bool IsRunning { get; }Not documented yet.
Elements
Section titled “Elements”public ICircuitLogic[] Elements { get; }Not documented yet.
DebugLog
Section titled “DebugLog”public System.Action<string> DebugLog { get; set; }Not documented yet.
ApiMisuseCount
Section titled “ApiMisuseCount”public int ApiMisuseCount { get; }How many times an element called a stamping/declaration API with arguments the engine had to refuse. Anything above zero is an authoring bug in a part.
NewtonPassesLastTick
Section titled “NewtonPassesLastTick”public int NewtonPassesLastTick { get; }Newton passes the last tick actually used.
NewtonPassesWorst
Section titled “NewtonPassesWorst”public int NewtonPassesWorst { get; }Most passes any tick has used since the run started.
UnconvergedTickCount
Section titled “UnconvergedTickCount”public int UnconvergedTickCount { get; }Ticks that ran out of Newton passes without converging, and were published anyway.
The pass loop exits either because everything converged or because it hit the budget, and until this counter existed those two were indistinguishable from outside. The published answer in the second case is whatever the last pass happened to produce, which for a part that is still moving is not a solution to anything. It presents as a component doing something physically impossible for one tick, with nothing reporting a problem.
LastUnconvergedElement
Section titled “LastUnconvergedElement”public string LastUnconvergedElement { get; }The element being stepped when a tick last ran out of passes.
LastApiMisuse
Section titled “LastApiMisuse”public string LastApiMisuse { get; }The most recent misuse message, for tests and error surfaces.
DiscoveredPairCount
Section titled “DiscoveredPairCount”public int DiscoveredPairCount { get; }How many distinct dynamic pairs were DISCOVERED mid-run rather than declared up front. Legal, self-healing, and each one costs an aborted tick - so a release part wants zero, but it is a cost, not a bug.
ReanalyzeCount
Section titled “ReanalyzeCount”public int ReanalyzeCount { get; }How many ticks were aborted into a mid-run re-Analyze by a dynamic stamp that was never declared. Each one resets every element on the bench, including any MCU, which silently reboots - so a part wants this to be zero.
HasConverged
Section titled “HasConverged”public bool HasConverged { get; set; }Global flag indicating if the non-linear elements have converged. Elements should set this to false during Step() if their values change significantly.
ConvergenceBlocker
Section titled “ConvergenceBlocker”public string ConvergenceBlocker { get; }The element that last cleared HasConverged, i.e. the part still moving when the passes ran out. Null until a tick fails to converge.
ConvergenceClears
Section titled “ConvergenceClears”public int ConvergenceClears { get; }How many times elements cleared HasConverged during the last unconverged tick. A number near the pass count means one part is moving every pass.
ConvergenceClearsThisTick
Section titled “ConvergenceClearsThisTick”public int ConvergenceClearsThisTick { get; }Not documented yet.
ConvergenceReason
Section titled “ConvergenceReason”public string ConvergenceReason { get; }Which stamp held the last unconverged tick open - see ConvergenceClearTag.
SubIterations
Section titled “SubIterations”public int SubIterations { get; }Not documented yet.
NeedsAnalyze
Section titled “NeedsAnalyze”public bool NeedsAnalyze { get; set; }Flag indicating that the topology has changed and MatrixMesh needs to be rebuilt.
NeedsDynamicReanalyze
Section titled “NeedsDynamicReanalyze”public bool NeedsDynamicReanalyze { get; set; }Not documented yet.
RunEpoch
Section titled “RunEpoch”public int RunEpoch { get; }Increments every time sim-time resets to 0 (ResetElements — a sim stop→start). Consumers holding time-stamped history compare epochs to know their capture belongs to a previous run and must be dropped.
LastPowerCycleFailures
Section titled “LastPowerCycleFailures”public int LastPowerCycleFailures { get; }Not documented yet.
FailureVersion
Section titled “FailureVersion”public int FailureVersion { get; }Bumped every time a component failure latches or failures are cleared. The host polls this from its frame loop and snapshots the failed set on movement — the same lock-free version pattern as the framebuffer/EEPROM mirrors. Written on whichever thread latches (Interlocked), read anywhere (Volatile).
ShortCircuitCurrentThreshold
Section titled “ShortCircuitCurrentThreshold”public double ShortCircuitCurrentThreshold { get; set; }Engine-side short-circuit detection: a voltage source whose post-convergence branch current exceeds this (amps) fails with reason “short circuit” — a dead short through an ideal source, far above any legitimate bench current. Zero or negative disables the check. Per-component limits stay USER-defined via BaseCircuitLogic.CheckFailure.
FailureChecksEnabled
Section titled “FailureChecksEnabled”public bool FailureChecksEnabled { get; set; }Master switch for the per-tick failure sweep — authored BaseCircuitLogic.CheckFailure AND the engine’s own short detection. ON by default. Off skips the sweep entirely: nothing can latch, and an already-latched component stays latched (and stays neutralised) rather than silently healing. The perf escape hatch for a circuit the user has already proven.
ShortCircuitDebounceTicks
Section titled “ShortCircuitDebounceTicks”public int ShortCircuitDebounceTicks { get; set; }Consecutive over-threshold ticks required before the short-circuit detection latches. Companion-model seeding after ANY re-Analyze legitimately draws a huge inrush for a tick (an ideal capacitor straight across a battery ≈ V·C/dt amps) — a single converged tick must never blow a healthy source.
ShortCircuitDwellSeconds
Section titled “ShortCircuitDwellSeconds”public double ShortCircuitDwellSeconds { get; set; }How long (SIM SECONDS) a source must sit over ShortCircuitCurrentThreshold before it latches. Time-based, not tick-based, because the transient this must ride out is physical: a bulk capacitor that starts EMPTY, at power-on or when it is placed on a rail that is already running, draws a genuine multi-millisecond recharge current (τ·ln(I₀/threshold): ~0.6 ms for 1000 µF through a regulator, ~6 ms for 10 mF). An Analyze is not such an event: the shipped capacitors keep their charge across its Reset (capacitor_common), and so do the MCU boards’ own (RailRegulator); only the engine’s test capacitors forget it. Its DURATION is fixed by the physics; its tick COUNT is not, so a tick debounce silently means different things at 1 kHz and 100 kHz.
The dwell is deliberately long and costs nothing: a genuine short is sustained INDEFINITELY, so waiting 50 ms only delays an inevitable latch by a hair of sim time, while a legitimate inrush is always over long before it. The MCU brown-out’s dwell rides out the same recharge from the board’s side (McuBoardProfile.BrownoutDwellSeconds, reasoned in MicrocontrollerElement’s supervisor comment).
This matters far more now the default threshold is bench-realistic (1 A) rather than near-mathematical (75 A): at 75 A only a single seeded tick ever crossed it, so 5 ticks was enough. At 1 A an ordinary Arduino + 1000 µF rail cap crosses it for ~64 ticks at 100 kHz, and blew its own supply until this became time-based.
SolverError
Section titled “SolverError”public string SolverError { get; }Human-readable description of why the circuit cannot be solved, or null while it can. Written on the sim thread, read anywhere (volatile reference assignment).
SolverErrorVersion
Section titled “SolverErrorVersion”public int SolverErrorVersion { get; }Bumped whenever SolverError appears or clears — poll it exactly like FailureVersion.
SolverErrorDwellTicks
Section titled “SolverErrorDwellTicks”public int SolverErrorDwellTicks { get; set; }Consecutive failed solves before the error is reported. Small — unlike a current transient there is no legitimate reason for a converged tick to be singular, so this only suppresses flicker across a re-Analyze, not a physical inrush.
HaltOnFailure
Section titled “HaltOnFailure”public bool HaltOnFailure { get; set; }“Pause on fail”: when true, the worker stops ticking (the run halts) as soon as any failure latches. The latch and neutralisation survive the halt — pressing Play resumes with the component still failed. The worker cannot pause-fence itself, so the halt is a plain run-stop the host’s run-state polling picks up.
HaltedOnFailure
Section titled “HaltedOnFailure”public bool HaltedOnFailure { get; }True when the worker’s most recent stop was the HaltOnFailure auto-halt (as opposed to a user pause). Lets the host’s “Reset failures” action resume the run without guessing from run-state timing. Cleared by the next StartSimulationThread and by Clear.
LastSolveFailed
Section titled “LastSolveFailed”public bool LastSolveFailed { get; }True while the most recent tick’s KLU solve FAILED (genuinely singular matrix — e.g. two ideal sources fighting). The element buffers hold the raw RHS for such ticks, not a solution: on a purely linear singular circuit nothing clears HasConverged, so the tick “converges” on garbage — this flag is the only reliable signal for that class. Cleared by the next successful solve.
StampWireCurrents
Section titled “StampWireCurrents”public bool StampWireCurrents { get; set; }Current View (OBS-1): when set, ideal wires are NOT collapsed at analyze - each spans two real matrix nodes joined by an engine-stamped WireStampResistance, so every wire carries a computable current I = (Va - Vb) / R read straight from the lead voltages the native scatter already publishes. Costs matrix growth (measured 2.8x tick time on the RAT board, WireStampCostBench); toggle under the pause fence and set NeedsAnalyze. Wires stay INERT either way - the stamp lives in the base matrix, never in per-tick element work.
HasLeadSenses
Section titled “HasLeadSenses”public bool HasLeadSenses { get; }Not documented yet.
NodeCount
Section titled “NodeCount”public int NodeCount { get; }Total number of unique matrix nodes in the circuit (including Node 0).
VoltageSourceCount
Section titled “VoltageSourceCount”public int VoltageSourceCount { get; }Total number of voltage sources in the circuit.
MatrixSize
Section titled “MatrixSize”public int MatrixSize { get; }The dimension of the MNA matrix.
FloatingNodes
Section titled “FloatingNodes”public List<int> FloatingNodes { get; }Nodes that have no path to Ground (Node 0) and require a 1E8 virtual resistor to prevent a singular matrix.
public NativeArray<double> CMatx { get; }Not documented yet.
CircuitRS
Section titled “CircuitRS”public NativeArray<double> CircuitRS { get; }Not documented yet.
CircuitRightSide
Section titled “CircuitRightSide”public NativeArray<double> CircuitRightSide { get; }Not documented yet.
Probes
Section titled “Probes”public ProbeManager Probes { get; }The observability probe registry (Phase 1 spine). Samples bound element leads / voltage-source currents at the END of each tick into per-probe ring buffers that the multimeter / scope / audio consumers read off-thread. Lives outside the element set — a probe never adds a matrix node. Register/deregister from the frontend UNDER PauseSimulationWait/ResumeSimulation.
public FeedManager Feeds { get; }The signal-feed registry — the probe spine’s INVERSE tap (audio-in). Advanced once at the START of each tick, before PreStep, publishing one sample per feed to its bound element’s BaseCircuitLogic.ExternalSignal — constant across all Newton sub-iterations. Same threading contract as Probes: Register/Rebind/Deregister from the frontend UNDER PauseSimulationWait/ResumeSimulation.
public WirelessField Field { get; }The wireless medium — the over-the-air counterpart to the wired nets. Per-simulation, never static: two open circuits (or two parallel headless tests) must never hear each other, and the native solver’s global state means only one simulation ticks at a time anyway.
Unlike Probes and Feeds it holds NO registry — membership is derived by scanning the element set for components that declared a BaseCircuitLogic.FieldChannel, so it cannot drift out of step with a circuit that is being edited. Sending is safe from any thread; see WirelessField.
SimTimeSeconds
Section titled “SimTimeSeconds”public double SimTimeSeconds { get; }Not documented yet.
LiveWorkers
Section titled “LiveWorkers”public int LiveWorkers { get; }Not documented yet.
LiveWorkersMost
Section titled “LiveWorkersMost”public int LiveWorkersMost { get; }Not documented yet.
StabilityDtCeiling
Section titled “StabilityDtCeiling”public double StabilityDtCeiling { get; }The largest step the circuit has proven it can SOLVE, learned per run: quartered on an unconverged tick, halved when convergence needs a deep pass count, drifting back up while ticks stay clean. The SimulationLoop folds it into both branches like ElementDtCeiling - the sim dilates honestly instead of publishing garbage. Reset by Clear only: a re-Analyze keeps the learned edge, because the topology mostly survives an edit and re-learning it costs a garbage tick.
Fields
Section titled “Fields”FailedLeadResistance
Section titled “FailedLeadResistance”const
public const double FailedLeadResistance;The neutralisation impedance: every lead of a FAILED component is tied to ground through this (the floating-node / MCU hi-Z precedent — a dead part reads like a dangling one). Public so tests and host heuristics share the single value.
DevicePaceLeadSeconds
Section titled “DevicePaceLeadSeconds”const
public const double DevicePaceLeadSeconds;How far sim production leads device consumption under device pacing. Small: the delivery cushions (resampler lag, ring fill) live OUTSIDE this lead and are preserved by the anchor, so this only covers report jitter.
CatchUpForgiveSeconds
Section titled “CatchUpForgiveSeconds”const
public const double CatchUpForgiveSeconds;Not documented yet.
CatchUpKeepSeconds
Section titled “CatchUpKeepSeconds”const
public const double CatchUpKeepSeconds;Not documented yet.
AutoThrottleFloorHz
Section titled “AutoThrottleFloorHz”public double AutoThrottleFloorHz;Auto-throttle never steps below this: 24 kHz keeps ~12 kHz audio bandwidth and clears the MCU’s MaxCyclesPerTick real-time bound (4 kHz at 16 MHz) six times over.
TimeStep
Section titled “TimeStep”public double TimeStep;Not documented yet.
TicksPerSecond
Section titled “TicksPerSecond”public volatile int TicksPerSecond;Not documented yet.
ProfilingEnabled
Section titled “ProfilingEnabled”public bool ProfilingEnabled;Not documented yet.
DefaultShortCircuitThreshold
Section titled “DefaultShortCircuitThreshold”const
public const double DefaultShortCircuitThreshold;The out-of-the-box short-circuit threshold every new circuit starts at (amps). Deliberately near bench reality rather than near the maths: a hobby rig that genuinely pulls this much is rare, so a source exceeding it is almost always a wiring fault. The host exposes it per circuit (persisted) because “almost always” is not “always” — a large TTL board or a bench supply legitimately draws more, and those circuits raise it.
BusTransparentMaxOhms
Section titled “BusTransparentMaxOhms”const
public const double BusTransparentMaxOhms;Series resistance up to this is bus-transparent (a logic level survives it).
WireStampResistance
Section titled “WireStampResistance”const
public const double WireStampResistance;The stamped wire resistance for Current View. 0.01 ohm: electrically negligible (bench-verified identical circuit behaviour), comfortably bus-transparent, and a round constant the render side divides by to recover current from the lead voltage delta.
UartRecursionDrops
Section titled “UartRecursionDrops”public long UartRecursionDrops;Deliveries abandoned because the reply chain ran too deep. COUNTED rather than silent: an echo storm that just stops looks exactly like a protocol bug.
UartFramingMismatches
Section titled “UartFramingMismatches”public long UartFramingMismatches;Bytes delivered between two ends whose declared framings disagree on baud, data bits or parity - the disagreements a real link garbles on. Counted, never acted on, in this version: the byte is delivered exactly as before, and this number is what has to read zero across every shipped sketch before a mismatch is allowed to corrupt anything. Stop-bit disagreements are recorded in LastUartFramingMismatch and do not count: a one-stop transmitter into a two-stop receiver works on a bench.
LastUartFramingMismatch
Section titled “LastUartFramingMismatch”public int LastUartFramingMismatch;The UartFraming mismatch flags of the most recent disagreement of any kind, for a test or a diagnostic to name the field.
PacketRecursionDrops
Section titled “PacketRecursionDrops”public long PacketRecursionDrops;Packets abandoned because the reply chain ran too deep. Counted, never silent.
PacketReceiverFaults
Section titled “PacketReceiverFaults”public long PacketReceiverFaults;Receivers that THREW out of OnPacket and had it swallowed. Non-zero means a node on the bus is faulty rather than absent — the difference between “nobody heard it” and “somebody heard it and fell over”, which every other counter reports identically.
I2CAddressCollisions
Section titled “I2CAddressCollisions”public long I2CAddressCollisions;How many times two devices answered one address. Monotonic for the run.
SpiSelectCollisions
Section titled “SpiSelectCollisions”public long SpiSelectCollisions;How many times two devices were selected on one SPI bus at once.
BusCollisions
Section titled “BusCollisions”public BusCollision[] BusCollisions;One record per (master, address) clash. Swapped whole, never mutated in place.
FailureSweepStride
Section titled “FailureSweepStride”const
public const int FailureSweepStride;The failure sweep’s MAXIMUM stride, in ticks. The sweep runs on every tick whose index is a multiple of the active stride - this value at audio-rate steps, backed off at coarse steps so one sweep window stays at or under 250 us - and for the length of the authored-check loop TimeStep reads as the whole covered window, so per-call dt integration in CheckFailure implementations stays time-accurate without any part changes. Power of two, so the gate is a mask. At 48 kHz this is a 167 microsecond check granularity against millisecond-scale overstress budgets.
Methods you call
Section titled “Methods you call”GetMatrixEntryPointer
Section titled “GetMatrixEntryPointer”public DynamicSingle GetMatrixEntryPointer(int i, int j);A direct pointer to ONE matrix entry (row i, column j), for a stamp whose coefficient has to change during Step rather than only its right-hand side.
The working matrix is restored from its static state before every solve pass, so a value written here applies to that pass alone and never accumulates. Returns a no-op stamp when either row was eliminated during simplification, which keeps callers from writing through a pointer the solver does not own.
GetConductanceMatrixPointer
Section titled “GetConductanceMatrixPointer”public DynamicConductance GetConductanceMatrixPointer(int i, int j);Returns a DynamicConductance holding direct targets into the CCS Ax array (or the right-hand side, for a column held at a constant voltage) for stamping a conductance between nodes i and j. Node 0 (GND) is not in the matrix, so its entries come back with no target and are skipped. Must be called AFTER Analyze() completes.
GetRightSideIndex
Section titled “GetRightSideIndex”public int GetRightSideIndex(int i);Returns the mapped row index into the right_side array for node i. Returns -1 for node 0 (GND) — callers must guard against this.
GetRightSidePointer
Section titled “GetRightSidePointer”public double* GetRightSidePointer(int i);Returns a raw pointer directly to the right side array for node i. Returns null if the node is 0 or was dropped during simplification.
Analyze
Section titled “Analyze”public void Analyze();Not documented yet.
AttachTimeFollower
Section titled “AttachTimeFollower”public unsafe void AttachTimeFollower(nint block, double stepSeconds);Not documented yet.
DetachTimeFollower
Section titled “DetachTimeFollower”public unsafe void DetachTimeFollower();Not documented yet.
StampMatrix
Section titled “StampMatrix”public void StampMatrix(int i, int j, double x);Stamps a value into the matrix. A voltage change of dv in node j will increase the current into node i by x * dv. (Unless i or j is a voltage source node.)
StampRightSide
Section titled “StampRightSide”public void StampRightSide(int i, double x);Stamps a value on the right side of row i, representing an independent current source flowing into node i.
StampRightSideDynamic
Section titled “StampRightSideDynamic”public void StampRightSideDynamic(int i);Indicates that the value on the right side of row i will dynamically change during the Step() phase.
AddDynamicRightSide
Section titled “AddDynamicRightSide”public unsafe void AddDynamicRightSide(int rsIndex, double x);Fast inline helper for dynamically stamping the right-hand side during the Step() loop. This directly modifies the right_side pointer values without P/Invoke overhead and includes null/bounds guards. Use this instead of direct Simulation.RightSide[i] array access to avoid crashes.
StampNonLinear
Section titled “StampNonLinear”public void StampNonLinear(int i);Indicates that the values on the left side of row i will dynamically change during the Step() phase (non-linear).
StampCurrentSource
Section titled “StampCurrentSource”public void StampCurrentSource(int n1, int n2, double i);Stamps a fixed current source of i amps from n1 to n2: it draws the current out of node n1 and delivers it into node n2. Call it from StampInitial. For a current that changes every tick use StampCurrentSourceDynamic from Step instead.
Parameters
n1: The node the current is drawn from (a ConnectionNodes or InternalNode value).n2: The node the current is delivered into.i: The current, in amps.
StampVoltageSource
Section titled “StampVoltageSource”public void StampVoltageSource(int n1, int n2, int vs, double v);Stamps an independent voltage source (vs) from n1 to n2 with amount v.
StampVoltageSourceDynamic
Section titled “StampVoltageSourceDynamic”public void StampVoltageSourceDynamic(int n1, int n2, int vs);Use this if the amount of voltage is going to be dynamically updated in Step().
StampResistor
Section titled “StampResistor”public void StampResistor(int n1, int n2, double r);Stamps a fixed resistance of r ohms between n1 and n2. Call it from StampInitial; it is the same as StampConductance with 1 / r. For a value that changes while the circuit runs, declare the pair in StampInitial and stamp StampConductanceDynamic from Step.
Parameters
n1: One node of the resistor (a ConnectionNodes or InternalNode value).n2: The other node.r: The resistance, in ohms. Must be greater than zero.
StampConductance
Section titled “StampConductance”public void StampConductance(int n1, int n2, double r0);Stamps a fixed conductance of r0 siemens between n1 and n2. Call it from StampInitial. Prefer it to StampResistor when the value can be zero (an open circuit), which a resistance cannot express.
Parameters
n1: One node (a ConnectionNodes or InternalNode value).n2: The other node.r0: The conductance, in siemens (1 / ohms).
DefineConductanceUnknown
Section titled “DefineConductanceUnknown”public void DefineConductanceUnknown(int i, int j);Pre-allocates CCS matrix slots for a conductance between nodes i and j. Stamps PREPROCESS_VAL (1e-14) directly into the raw matrix, bypassing the node-0 guard in StampMatrix. This ensures the CCS structure contains entries for all four conductance positions so that GetMatrixPointers() can fetch valid pointers for direct writes during Step().
Must only be called during the Analyze phase (from StampDynamic).
StampVoltageControlledVoltageSource
Section titled “StampVoltageControlledVoltageSource”public void StampVoltageControlledVoltageSource(int n1, int n2, double coef, int vs);Voltage-controlled voltage source (VCVS). Control voltage source vs with voltage from n1 to n2. (Must also call StampVoltageSource or StampVoltageSourceDynamic).
StampVoltageControlledCurrentSource
Section titled “StampVoltageControlledCurrentSource”public void StampVoltageControlledCurrentSource(int cn1, int cn2, int vn1, int vn2, double g);Voltage-controlled current source (VCCS). Current from cn1 to cn2 is equal to voltage from vn1 to vn2, divided by g.
StampCurrentControlledCurrentSource
Section titled “StampCurrentControlledCurrentSource”public void StampCurrentControlledCurrentSource(int n1, int n2, int vs, double gain);Current-controlled current source (CCCS). Stamp a current source from n1 to n2 depending on current through vs.
StampConductanceDynamic
Section titled “StampConductanceDynamic”public unsafe void StampConductanceDynamic(int n1, int n2, double g);Stamps a conductance of g siemens between n1 and n2 for this solve pass. Call it from Step: it is the workhorse of switches, diodes, transistors and the companion models of capacitors and windings. Declare every pair you will stamp with DeclareDynamicConductance in StampInitial. An undeclared pair still works, but its first use aborts the tick into a re-Analyze that resets the whole bench.
Parameters
n1: One node (a ConnectionNodes or InternalNode value).n2: The other node.g: The conductance for this pass, in siemens. Zero leaves the pair open.
StampCurrentSourceDynamic
Section titled “StampCurrentSourceDynamic”public void StampCurrentSourceDynamic(int n1, int n2, double i);Stamps a current source of i amps from n1 to n2 for this solve pass, drawn out of n1 and delivered into n2. Call it from Step, typically beside StampConductanceDynamic as the history term of a capacitor or winding. Mark both nodes with StampRightSideDynamic in StampInitial so the injected current survives the matrix simplification.
Parameters
n1: The node the current is drawn from.n2: The node the current is delivered into.i: The current for this pass, in amps.
ReportDeviceClock
Section titled “ReportDeviceClock”public void ReportDeviceClock(double simAtAnchor, double consumedSeconds);Host audio sink: report the device’s consumption. Pass the sim time captured when this sink started consuming and the cumulative stream seconds consumed since. Thread-safe; call from the audio pump. Stops pacing (falls back to wall clock) when reports stop.
ClearDeviceClock
Section titled “ClearDeviceClock”public void ClearDeviceClock();Host audio sink teardown: stop device pacing immediately.
Dispose
Section titled “Dispose”public void Dispose();Releases this simulation’s native solver context. OPT-IN: nothing in the app calls it, and not calling it is exactly the behaviour that existed before contexts - the SafeHandle’s critical finalizer reclaims the context either way. Tests that build many simulations in a loop should call it so KLU’s factors are freed promptly rather than at the GC’s convenience.
The worker is stopped first, because Tick calls into the context with no other guard. That stop is best-effort (Join with a timeout), so the SafeHandle is the real protection: it refuses to release a handle that is currently inside a P/Invoke, and a call STARTED after disposal fails loudly as ObjectDisposedException rather than quietly reading freed memory.
AddElement
Section titled “AddElement”public void AddElement(ICircuitLogic element);Adds a compiled circuit logic component to the simulation loop.
RemoveElement
Section titled “RemoveElement”public void RemoveElement(ICircuitLogic element);Removes a circuit logic component from the simulation loop.
public void Clear();Clears all elements from the simulation.
EnsureFieldDriveSlots
Section titled “EnsureFieldDriveSlots”public void EnsureFieldDriveSlots(int slots);Host bridge: grow the drive array to cover an id the host has just handed out.
ResetElements
Section titled “ResetElements”public void ResetElements();Resets the runtime state (voltages, currents, and accumulated per-element history) of every element back to zero without changing the circuit topology. Intended to be called only while the simulation thread is stopped — the frontend’s Reset button is paused-only — so this is a no-op guarded against a live sim thread to avoid racing Tick().
LatchFailure
Section titled “LatchFailure”public void LatchFailure(BaseCircuitLogic element, string reason);Latches a component failure: sets the element’s BaseCircuitLogic.Failed latch (and BaseCircuitLogic.FailureReason unless the author’s Fail(...) already set one), zeroes its exposed currents (they freeze once the element leaves the hot loops — a dead part must read dead), schedules the neutralising re-Analyze, and bumps FailureVersion. Called by the per-tick sweep and the Analyze-time short detection on the sim thread; hosts may call it while the sim is stopped or fenced.
SnapshotFailedElements
Section titled “SnapshotFailedElements”public List<BaseCircuitLogic> SnapshotFailedElements();Every currently-failed element, in registration order — the host maps these back to proxies for attribution (it owns proxy→element; invert by scan).
ClearFailures
Section titled “ClearFailures”public void ClearFailures();Clears every failure latch and schedules the re-Analyze that restores the real stamps — the designer’s “Reset failures” action. Runtime state only (never undoable). Call while the sim is stopped or under the pause fence, like every other topology mutation.
Connect
Section titled “Connect”public bool Connect(ICircuitLogic leftElem, int leftLeadIdx, ICircuitLogic rightElem, int rightLeadIdx);Connect two leads together, assigning them the same node ID. Handles all 4 cases: both unconnected, one unconnected, both connected (merge). Returns false when either element is not registered in the node mesh yet — callers MUST treat that as “not connected” (keep searching / SelfConnect), never as success. A silent void no-op here let AttachToSim mark leads as connected that never were, fragmenting nets in attach-enumeration order (the historic “sometimes breaks on move/attach” dice roll).
SelfConnect
Section titled “SelfConnect”public void SelfConnect(ICircuitLogic elem, int leadIdx);Self-connect a lead (assign it its own unique node ID if unconnected). Used when a component’s pin has no physical neighbor yet.
GetLeadNodeId
Section titled “GetLeadNodeId”public long GetLeadNodeId(ICircuitLogic elem, int leadIdx);Gets the node ID currently assigned to a lead. Returns -1 if unconnected.
DeclareLeadSense
Section titled “DeclareLeadSense”public void DeclareLeadSense(BaseCircuitLogic logic, int leadIdx);Declare a branch-current sense on one lead. Idempotent. MUST be called under the pause fence; takes effect at the next PushNodeMeshToElements + Analyze while StampWireCurrents is on (parked and inert otherwise).
TryGetLeadSense
Section titled “TryGetLeadSense”public bool TryGetLeadSense(BaseCircuitLogic logic, int leadIdx, out LeadSenseElement sense);The live sense for a declared lead, if any. Safe to read from the frontend thread at frame rate (the registry only mutates under the pause fence, on the same thread).
ClearLeadSenses
Section titled “ClearLeadSenses”public void ClearLeadSenses();Drop every declared sense (their elements included). MUST be called under the pause fence.
WireNetOf
Section titled “WireNetOf”public long WireNetOf(long netId);The net a lead is on with ideal wires collapsed - the same answer in both wire modes, and the EXACT identity of a pin. BusGroupOf is wider: it also unions nets joined through a bus-transparent series element (a 1 k protection resistor, a declared pass-through), which is the right question for “does this control line reach that pin” and the wrong one for “which pin is this” - a 1 k speaker between two GPIOs unions them too, and a group-keyed identity then answers the first pin for both (BUSG-1). Negative ids pass through.
RequestBusRegroup
Section titled “RequestBusRegroup”public void RequestBusRegroup();Re-read every part’s BaseCircuitLogic.BusTransparentConnections and rebuild the group table at the next tick boundary. A part asks for this through BaseCircuitLogic.NotifyBusTransparencyChanged when its own answer has moved with its state: a switch closed, a mux select changed, an enable went active.
WITHOUT IT THE GROUPS STAND UNTIL THE NEXT ANALYZE, and a bus through a switch the user has just closed stays dead while every voltage on the bench reads correct. Measured on a 2-terminal switch: the contact drops to 0.002499 V and BusNetsMatch is still false 200 ticks later.
AND THIS IS NOT AN ANALYZE AND MUST NOT BECOME ONE. An Analyze resets every element, re-mints every node id and refactorises the matrix - measured 2.7086 ms on a 201-element board against 0.1391 ms for the regroup, 19.5x - so a part toggling at firmware rate would reboot every chip on the bench, every toggle.
Latched, not immediate, and drained between PreStep and Step, so transparency is fixed for the whole tick and a switch bouncing mid-transfer can never re-route a live transaction. Safe from any thread: one bool store.
IsTickPacedNet
Section titled “IsTickPacedNet”public bool IsTickPacedNet(long netId);True when a master driving this net must stop until the next tick after each write. Null-set fast path: one reference compare when no device asked for pacing.
BusGroupOf
Section titled “BusGroupOf”public long BusGroupOf(long netId);The bus group a net belongs to (identity when nothing unions it). Negative ids (unconnected) pass through — callers must not treat two -1s as a match.
BusNetsMatch
Section titled “BusNetsMatch”public bool BusNetsMatch(long a, long b);True when two nets are the same bus — identical or joined through bus-transparent series resistance. Unconnected (negative) nets never match anything.
UartRxLeadOn
Section titled “UartRxLeadOn”public int UartRxLeadOn(BaseCircuitLogic e, long txNet);Which of e’s connections listens on txNet, or -1.
Resolves the MULTI-PORT declaration first: a component that overrides BaseCircuitLogic.UartRxConnections is answered entirely from that array, and the single-port BaseCircuitLogic.UartRxConnection is then ignored — one declaration is authoritative, so a part can never half-listen through two surfaces. Multi-port is what a real transceiver needs (an RS-485 driver bridges a controller-side pair to a bus-side pair) and what a real board has (an ATmega2560 carries four serial ports).
FindUartReceiver
Section titled “FindUartReceiver”public BaseCircuitLogic FindUartReceiver(BaseCircuitLogic sender, int senderTxLead);Find A component listening on senderTxLead’s net — first match only. Kept for callers that want a single peer handle to cache; DeliverUartByte is what actually carries traffic and reaches EVERY listener.
UART is CROSSED, so this matches TX to RX: a module that merely transmits (a GPS) works exactly as it does in hardware with nothing wired back. A FAILED peer is invisible, the same engine-side gate FindI2CSlave applies, so no authored device writes its own !Failed check. Null means nothing is wired. Sim-thread only.
HasUartReceiver
Section titled “HasUartReceiver”public bool HasUartReceiver(BaseCircuitLogic sender, int senderTxLead);True when anything — a plain component or a microcontroller — would hear a byte sent from this lead.
PacketBusLeadOn
Section titled “PacketBusLeadOn”public int PacketBusLeadOn(BaseCircuitLogic e, long busNet);Which of e’s connections sits on busNet, or -1.
HasPacketPeer
Section titled “HasPacketPeer”public bool HasPacketPeer(BaseCircuitLogic asker, int connectionIdx);Is any other node on this net? The reachability question a protocol asks before transmitting, so it can report an honest failure rather than assuming success.
ClearBusCollisions
Section titled “ClearBusCollisions”public void ClearBusCollisions();Drop every collision record. A repaired wire has to stop reporting, so this runs wherever the other per-run bus caches die.
FindI2CSlave
Section titled “FindI2CSlave”public BaseCircuitLogic FindI2CSlave(BaseCircuitLogic master, int masterSdaLead, int masterSclLead, int address);Find the I2C slave answering address on the master’s SDA/SCL nets, or null (→ NACK, like an empty bus). A FAILED slave is invisible here — a burnt-out part must go silent on the bus, not just lose its stamps (see LatchFailure). Gating it in the engine keeps the `!Failed` check out of every authored device.
Callable from the sim thread or from a chip/pacer thread: everything it reads (the node mesh, the element cache, the bus-group table) is rebuilt only in Analyze, which hard-quiesces every element pacer first. It reads converged voltages and user-authored virtuals concurrently with the solve; both are point-in-time reads by design.
FindSpiSlave
Section titled “FindSpiSlave”public BaseCircuitLogic FindSpiSlave(BaseCircuitLogic master, int masterSckLead, int masterMosiLead, Func<BaseCircuitLogic, int, bool> isSelected);Find the SELECTED SPI slave on the master’s SCK net. SCK nets must match; chip-select is decided by isSelected — the master supplies it because a CS driven by the master’s OWN GPIO must use the LIVE mid-tick pin state (firmware toggles CS and transfers within one tick; the solved node voltage lags a tick), while an externally-driven CS falls back to the converged voltage (< BusLowThreshold). A slave that DECLARES its data pin (BaseCircuitLogic.SpiMosiConnection) must also have it wired to the master’s MOSI — clock alone carries no data, exactly like hardware; undeclared legacy devices route on SCK + CS alone. A FAILED slave is invisible here (a burnt-out part goes silent on the bus, not merely un-stamped — the same engine-side gate as FindI2CSlave). Null → MISO reads 0xFF. Same threading contract as FindI2CSlave: sim thread or a chip/pacer thread, against tables that only Analyze rebuilds.
IsBusPinLow
Section titled “IsBusPinLow”public bool IsBusPinLow(BaseCircuitLogic slave, int lead);The externally-driven CS fallback: the slave’s converged CS node voltage reads low. Bounds-guarded — lead indices come from free-form user virtuals and the voltage buffer is raw native memory.
SenseBusPinIsHigh
Section titled “SenseBusPinIsHigh”public bool SenseBusPinIsHigh(BaseCircuitLogic device, int lead);The digital level of one of device’s pins, transaction-aware: inside a bus callback a control line driven by the transacting master’s own GPIO (an SPI display’s D/C, for example) reads its LIVE mid-tick state — firmware toggles such lines and transfers within one tick, so the converged node voltage lags. Everywhere else (or when the master doesn’t drive the net) it falls back to the converged electrical level.
PushNodeMeshToElements
Section titled “PushNodeMeshToElements”public void PushNodeMeshToElements();Pushes all node mesh IDs to the elements’ SetConnectionNode, mapping snowflake IDs to sequential integer node IDs for the matrix. Called before Analyze().
ResetNodeMesh
Section titled “ResetNodeMesh”public void ResetNodeMesh(ICircuitLogic element);Resets all node mesh entries for a given element back to -1 (unconnected). Called during DetachFromSim before re-attaching.
ResetSharedLeads
Section titled “ResetSharedLeads”public void ResetSharedLeads(ICircuitLogic element);Not documented yet.
Initialize
Section titled “Initialize”public void Initialize();Initializes the simulation matrix. Called once before the simulation begins or whenever the circuit topology changes.
StartSimulationThread
Section titled “StartSimulationThread”public bool StartSimulationThread();Not documented yet.
StopSimulationThread
Section titled “StopSimulationThread”public void StopSimulationThread();Not documented yet.
WaitUntilStopped
Section titled “WaitUntilStopped”public bool WaitUntilStopped(int ms);Not documented yet.
PauseSimulationWait
Section titled “PauseSimulationWait”public void PauseSimulationWait(int timeoutMs = 1000);Not documented yet.
ResumeSimulation
Section titled “ResumeSimulation”public void ResumeSimulation();Not documented yet.
public unsafe void Tick();The main tick loop. This advances the simulation forward by one fixed step.
TakeProfileReport
Section titled “TakeProfileReport”public string TakeProfileReport();Take the accumulated tick profile as a string and reset the accumulators.
The report is normally published once a second from the run loop, which a HEADLESS BENCH never enters - it calls Tick() directly, so the accumulators filled up and were never read. That made the one piece of per-phase instrumentation in the engine unavailable from exactly the place perf questions get answered. Returns null when no ticks have been profiled.
Set ProfilingEnabled before ticking, then take the report afterwards.
Generated from the CRUMB source, commit fcec660d, 2026-10-29.

