A physics engine, a measurement model, a fitting library, a document editor and a citation engine, written to be correct rather than convincing. There is no backend doing the physics — all of it runs in the browser.
Every simulator is a plugin implementing one contract: fixed time step, RK4 by default, and events located by root-finding rather than by noticing afterwards that something has happened. The model entry touches no DOM at all, so the same code runs in a Web Worker and in the test suite — which is what makes a benchmark against the textbook result possible for all 36 of them.
Some of them are more than they look. The circuit simulator is modified nodal analysis — one linear solve per step by dense LU with partial pivoting, with trapezoidal companion models for capacitors and inductors, in DC and AC, on a circuit you can draw freehand and whose wires have resistance ρL/A. The string is the wave equation on a lattice of a hundred and fifty coupled points, which is why resonance builds over cycles and a pulse comes back inverted off a pinned end instead of being drawn that way. The gas has no integrator in it at all: between collisions an atom feels no force, so free flight is exact arithmetic — which is why the step size cannot affect the measured pressure.
A probe gives the exact value; an instrument quantises it to its resolution, adds seeded random error and attaches an uncertainty by the usual school rule — half a division for an analogue scale, one in the last digit for a digital display. There are 26 of them, and the order of the catalogue is behaviour rather than presentation: the default instrument for a quantity is the first one whose dimensions match.
Uncertainties propagate by forward-mode automatic differentiation over the expression tree, so partial derivatives are exact and a propagated uncertainty matches the hand-calculated textbook value digit for digit. Both conventions are offered, because students are taught both. Fitting is 8 models: linear ones by weighted least squares through a Householder QR with parameter uncertainties from the covariance, the rest by Levenberg–Marquardt with analytic Jacobians — tested against the NIST certified datasets.
Radioactive decay is sampled nucleus by nucleus from an injected generator, so a run is reproducible from its seed while repeats genuinely scatter. It samples from a binomial, never a Poisson: Poisson's variance is wrong by a factor of (1 − p) and it has no upper bound, so it can decay more nuclei than exist and the source gains atoms — at exactly the late times a half-life experiment is about.
Every statistical test in the suite asserts the scatter is not too small, which is the only check that would notice the model quietly ceasing to be random.
The report editor is ProseMirror, because every requirement turned out to be a document-model problem: headings that renumber, a contents list that tracks them, a figure bound to a dataset, a citation that reformats. Heading and figure numbers are derived and never stored — they reach the page as decorations, because writing them into the DOM from outside looks like it works and then silently stops.
Pagination is a pure function over an array of measurements, so the editor's guides and the printed pages come from the same code and cannot disagree. The 7 citation styles are data rather than 7 formatters — a list of parts per source type over four different mechanisms, including footnotes that take room off the page they sit on — and a citation stores source ids and never rendered text, so changing the style leaves the document byte-identical.
This project has had eight distinct data-loss bugs. Every one of them is written down in the repository with the rule it left behind, because the rules are worth more than the fixes: three of them shared one shape — an await between changing a value and changing whose value it is — and naming that shape is what stopped the fourth.
Several of the most interesting faults were found by looking at a screenshot rather than by any assertion: an energy-level diagram drawn below the bottom of the canvas because screen y grows downwards, a graph that lost its data and kept its axes because two inlined copies of an SVG shared element ids, and an uncertainty printed as ± 0.00 on this very site. There is a standing rule about it.
An intermittent test failure is treated as a bug report and never as noise. The data-loss races are pinned by throttling the browser twentyfold through the debug protocol so that they happen every time, rather than waited for with a longer timeout.
The quickest way to see any of this is to draw a circuit and watch the electrons move at a speed derived from the drift velocity, or to count a radioactive source and see the scatter.