Multi-rate kernel
Different physics need different clocks.
In the balanced path, crank and piston mechanics step at 10 kHz, the fluid network at 40 kHz, and audio synthesis at 48 kHz. Higher and lower performance modes adjust the solver rates without changing the audio device contract. The rates follow the signals: gas-flow transients need finer resolution than the rotating assembly, while audio must produce the device sample stream.
10 kHz
40 kHz
48 kHz
100 Hz
~60 Hz
Independent accumulators catch each domain up to elapsed time, with a clamp after debugger pauses to prevent a spiral of death. The domains still share causality: piston geometry and crank angle affect cylinder volume; manifold mass balance and combustion create pressure; pressure over piston area and crank geometry produces torque; cylinder pressure and firing phase drive exhaust pulses.
Hot state is shaped for the runtime
The simulation state uses blittable structs, fixed-size arrays, ref access, stackalloc, and no LINQ or boxing in the hot path. Native AOT analyzers police compatibility. A display snapshot is the UI boundary, so rendering never walks the live solver’s arrays. Commands travel back through an explicit queue.
Audio has one handoff: a power-of-two single-producer/single-consumer ring buffer. A dedicated playback worker owns the OpenAL device and queue, prebuffers four 1024-sample chunks, and keeps eight chunks available for scheduling slack. The initial fill is treated as buffering; an empty queue after playback has started is a real underrun. Misfires and limiter cuts are audible because they alter cylinder events, not because the UI selects a “rough engine” sound.
Snapshots and resets are part of correctness
The large display snapshot is published with a sequence marker so UI and dyno readers can retry around a kernel write instead of accepting torn state. Engine swaps and simulation resets preserve that publication boundary, clear the audio buffer, rebuild the solver set, and reset stateful health accumulators. Otherwise a new engine could inherit wear or a reader could observe half of a replacement.
Engines are data, not subclasses
Engine packs are directories of JSON describing geometry, rotating assembly, heads, cam, fuel, ignition, exhaust, accessories, and calibration. EngineFactory discovers packs recursively and validates them into solver inputs. New naturally aspirated configurations do not require recompiling a new class hierarchy, and pack selection works across path separators.
Diagnostics are part of stability
Step counters, timing, kernel jitter, buffer health, pressures, temperatures, wear, torque, power, and dyno sweep data are exposed through snapshots and diagnostic views. Sweeps dwell at each operating point before sampling and can export CSV. Health feeds back into physics: ring-seal loss reduces effective compression, bearing wear adds drag, and detonation can retard timing.