PhysicsEngine

Validation

Every scenario below runs the real engine and compares it with a result from physics that's known exactly. The page is regenerated by CI on every change, so it always reflects the current code.

44 of 44 checks pass.

Pendulum period vs. amplitude

2D rigid bodies + joints

A disk on a 1 m rod from a fixed pin, released from rest at 5° to 80°. Small swings take T₀ = 2π√(L/g) = 2.0066 s; larger ones take longer, exactly T = 4√(L/g)·K(sin(θ₀/2)) where K is the complete elliptic integral of the first kind. That curve is far from the small-angle formula at 80° (+13.7%), so matching it tests the full nonlinear motion and the joint solver's energy behavior.

02040608011.051.11.15Amplitude θ₀ (degrees)Period / small-swing periodExact: 4√(L/g) K(sin θ₀/2)Simulated
QuantityExpectedMeasured
Period at 5°2.008 s2.007 s (-0.01%)Pass
Period at 20°2.022 s2.022 s (-0.01%)Pass
Period at 40°2.069 s2.069 s (-0.01%)Pass
Period at 60°2.153 s2.153 s (-0.02%)Pass
Period at 80°2.283 s2.282 s (-0.02%)Pass

Pendulum energy over 30 s

2D rigid bodies + joints

The same pendulum swinging 60° for 30 s, with no friction: its energy should stay constant. The joint solver aims each step at the joint's predicted end-of-step position (a SHAKE-style scheme), so the error oscillates within a bounded band instead of steadily draining. (The textbook velocity-only constraint loses 23% of this pendulum's energy in 10 s.)

0102030-101Time (s)Energy error (% of swing energy)Simulated
QuantityExpectedMeasured
Largest energy error< 2%1.24%Pass
Drift (last 5 s vs. first 5 s)< 0.5%+0.088%Pass

Rolling vs. sliding down a ramp

3D rigid bodies

A ball and a box released on a 20° slope. The ball rolls without slipping, so part of its energy goes into spin: it accelerates at (5/7) g sin θ. The box slides against Coulomb friction μ = 0.2: g (sin θ − μ cos θ). Both are distance-vs-time parabolas.

00.511.5012Time (s)Distance down the ramp (m)Rolling: ½ (5/7) g sin θ t²Sliding: ½ g (sin θ − μ cos θ) t²Ball, rollingBox, sliding (μ = 0.2)
QuantityExpectedMeasured
Ball, rolling: distance after 1.5 s2.695 m2.709 m (+0.50%)Pass
Box, sliding (μ = 0.2): distance after 1.5 s1.700 m1.709 m (+0.56%)Pass

Bounce height vs. restitution

3D rigid bodies

A ball dropped 3 m onto the floor with coefficient of restitution e leaves at e times its impact speed, so it rises to e² of the drop height. The engine applies restitution to the velocity at the start of the contact step, which includes that step's gravity, so lively balls bounce about 2% higher than e²; the check allows ±0.03 h.

00.5100.51Coefficient of restitution eRebound height / drop heighte²Simulated
QuantityExpectedMeasured
Rebound height, e = 0.20.04000 × h0.03299 × h (-0.007)Pass
Rebound height, e = 0.40.1600 × h0.1572 × h (-0.003)Pass
Rebound height, e = 0.60.3600 × h0.3640 × h (+0.004)Pass
Rebound height, e = 0.80.6400 × h0.6530 × h (+0.013)Pass
Rebound height, e = 0.90.8100 × h0.8283 × h (+0.018)Pass

Mass on a spring

2D joints (spring)

Masses from 0.25 to 4 kg hanging on a 40 N/m spring joint, released at the spring's rest length. Each oscillates about the stretched equilibrium with period T = 2π√(m/k). The spring is integrated implicitly (a soft constraint), so it's stable at any stiffness.

12340.511.52Mass (kg)Period (s)2π √(m/k)Simulated
QuantityExpectedMeasured
Period, m = 0.25 kg0.4967 s0.4986 s (+0.37%)Pass
Period, m = 0.5 kg0.7025 s0.7038 s (+0.18%)Pass
Period, m = 1 kg0.9935 s0.9944 s (+0.09%)Pass
Period, m = 2 kg1.405 s1.406 s (+0.05%)Pass
Period, m = 4 kg1.987 s1.987 s (+0.02%)Pass

Soft-body stiffness is physical

2D soft bodies (XPBD)

Five 0.1 kg particles hanging from a pin by springs of stiffness k. Edge i carries the weight of everything below it, so the chain stretches m g n(n+1)/(2k) in total. Plain position-based dynamics gets stiffer the more iterations it runs; XPBD's compliance term makes stiffness a real N/m, so 10 and 40 substeps give the same, correct answer.

10010000.010.1Edge stiffness k (N/m)Total stretch (m)Hooke: m g n(n+1)/(2k)Simulated, 10 substepsSimulated, 40 substeps
QuantityExpectedMeasured
Stretch at 200 N/m, 10 substeps0.07355 m0.07362 m (+0.09%)Pass
Stretch at 200 N/m, 40 substeps0.07355 m0.07355 m (+0.01%)Pass

Hydrostatic pressure in still water

2D SPH fluid

A 1 m column of water left to settle, then particle pressures averaged over 1 s in 0.1 m depth bands away from the walls. Still water's pressure grows linearly with depth, p = ρ₀ g h (ρ₀ = 10 kg/m², a 1 cm slab of water in 2D). The bands nearest the free surface and the floor read low and high: SPH particles there have one-sided neighborhoods.

00.51050100Depth below the surface (m)Pressure (N/m)ρ₀ g hSimulated (band means)
QuantityExpectedMeasured
Pressure gradient dp/dh (interior fit)98.07 N/m²98.26 N/m² (+0.20%)Pass

Archimedes' principle

3D SPH fluid + rigid bodies

0.3 m cubes of density 250, 500, and 750 kg/m³ floating in water (1000 kg/m³). A floating body displaces its own weight of water, so the fraction under water equals the density ratio. The water and the cubes push on each other with exactly opposite impulses.

00.5100.51Density / water densityFraction submergedρ / ρ_waterSimulated
QuantityExpectedMeasured
Submerged fraction, 250 kg/m³0.2500 0.2552 (+2.10%)Pass
Submerged fraction, 500 kg/m³0.5000 0.4994 (-0.12%)Pass
Submerged fraction, 750 kg/m³0.7500 0.7448 (-0.69%)Pass

Heat diffusion

2D smoke & heat grid

A Gaussian hot spot (σ₀ = 0.2 m, 10 K) in still air with thermal diffusivity κ = 0.01 m²/s. It stays Gaussian with variance σ² = σ₀² + 2κt, and in 2D its peak falls as σ₀²/σ² so the total heat is unchanged. The solver diffuses heat implicitly, so it's stable at any κ and step size, and its insulated walls conserve heat exactly.

-10102468Distance from the center (m)Temperature above ambient (K)Analytic: σ² = σ₀² + 2κtSimulated, t = 0.5 sSimulated, t = 2 sSimulated, t = 5 s
QuantityExpectedMeasured
Peak at t = 0.5 s8.000 K8.025 K (+0.31%)Pass
Peak at t = 2 s5.000 K5.025 K (+0.50%)Pass
Peak at t = 5 s2.857 K2.869 K (+0.40%)Pass

Warm air starts to rise

2D and 3D smoke & heat grids

A patch of air 10 K warmer than its surroundings is lighter (Boussinesq: g′ = g β ΔT with β = 1/T for an ideal gas, here 0.3345 m/s²), but it can't rise without pushing the surrounding air out of the way. That air's inertia acts as added mass: equal to the patch's own for a 2D cylinder, half of it for a sphere. So the patch starts out accelerating at g′/2 in 2D and 2g′/3 in 3D, a sharp test of the pressure solve.

QuantityExpectedMeasured
Initial acceleration, 2D cylinder0.5000 × g′0.4881 × g′ (-2.37%)Pass
Initial acceleration, 3D sphere0.6667 × g′0.6581 × g′ (-1.29%)Pass

Pressure through two liquids

2D SPH fluid, several liquids

0.3 m of mercury under 0.5 m of water, settled, then pressures averaged over 2 s in 5 cm bands. Pressure grows at ρg in each liquid: 13.5× faster in the mercury. This needs the multiphase formulation (each particle sums neighbors by number, not mass, so a water particle beside mercury doesn't count mercury's mass as its own density). Bands at the floor and at the interface read off: one-sided SPH neighborhoods there.

00.20.40.60.80200400600Depth below the surface (m)Pressure (N/m)ρ_water g h, then ρ_mercury g hSimulated (band means)
QuantityExpectedMeasured
dp/dh in the water98.07 N/m²104.7 N/m² (+6.73%)Pass
dp/dh in the mercury1328 N/m²1347 N/m² (+1.43%)Pass

Heat conduction in a liquid

2D SPH fluid, heat

Still mercury with a sinusoidal temperature pattern, 5 K amplitude over a 5 cm wavelength (20 particles per wavelength). The heat equation says each Fourier mode decays independently, A(t) = A₀ e^(−αk²t), with mercury's thermal diffusivity α = k/(ρc) = 4.39e-6 m²/s. The SPH conduction term (Cleary & Monaghan) uses each liquid's real conductivity and specific heat and conserves energy pair by pair. The box is closed and there's no gravity (so nothing convects): with an open top, surface tension, which rules in zero gravity, would pull the mercury into a drop and stir it.

00.511.520.90.951Time (s)Amplitude / initial amplitudee^(−αk²t)Simulated
QuantityExpectedMeasured
Decay rate αk²0.06936 1/s0.06591 1/s (-4.97%)Pass

Heat from a hot plate into water

3D SPH fluid, heat

A 0.6 m square plate held at 40, 60, or 80 °C under 20 °C water. The thermal boundary layer on a heated wall is far thinner than a particle, so the engine carries heat across the first layer at the natural-convection rate q = hΔT, with h = 0.13 k (gβΔT/να)^(1/3) (turbulent, McAdams) from water's properties at the film temperature: the flux grows as ΔT^(4/3). This checks that the SPH wall term delivers that flux (plate area 0.36 m²; the plate's edges add a little).

0204060050000Plate above the water (K)Heat flux (W/m²)q = h(ΔT) ΔTSimulated, first 0.25 s
QuantityExpectedMeasured
Heat in over 0.25 s, plate at 40 °C1125 J1151 J (+2.37%)Pass
Heat in over 0.25 s, plate at 60 °C3276 J3354 J (+2.36%)Pass
Heat in over 0.25 s, plate at 80 °C6311 J6459 J (+2.35%)Pass

Water flash-boiling in vacuum

3D SPH fluid, boiling

A 0.4 m pool of 20 °C water with nothing above it. Water boils where the pressure on it is below its vapor pressure (2.34 kPa at 20 °C), and the only pressure here is the water's own weight, ρgh. So the top 24 cm boils, each layer cooling until it reaches its own boiling point, T_sat(ρgh) from the IAPWS-IF97 steam-table equation, while the water below stays at 20 °C. The heat that leaves is exactly the latent heat of the vapor that escapes. The profile is read after 0.25 s: the top band lags because a free surface can only lose molecules at the Hertz–Knudsen rate, and later the chilled water (denser than 20 °C water) sinks and mixes, as it would in reality. (Without freezing in the model, the surface stops at 0.01 °C instead of icing over.)

00.10.20.30.405101520Depth below the surface (m)Temperature (°C)Boiling point at depth, T_sat(ρgh), up to 20 °CSimulated (band means, 0.25 s)
QuantityExpectedMeasured
Largest band deviation from T_sat(ρgh)0 K1.136 K (+1.136)Pass
Water (liquid + vapor) conserved64.00 kg64.00 kg (-0.00%)Pass
Heat lost = latent heat of the vapor1757156 J1757156 J (+0.00%)Pass

Kepler's third law

N-body orbits

Planets placed on circular orbits from Mercury's distance to Neptune's, each timed over two orbits. Kepler's third law says the period grows as the distance to the 1.5 power: T = 2π√(a³/GM), one year at 1 AU. The integrator (leapfrog, at least 256 steps per orbit) runs orbits long by a fixed (2π/256)²/3 = 0.02%, and being symplectic, its energy error stays bounded instead of drifting: the full Solar System, with every planet and moon pulling on every other, is checked over 20 years.

110110100Distance from the Sun (AU)Orbital period (years)T = a^1.5 (years, AU)Simulated
QuantityExpectedMeasured
Period at 0.387 AU0.2408 years0.2408 years (+0.02%)Pass
Period at 1 AU1.000 years1.000 years (+0.02%)Pass
Period at 30.1 AU165.1 years165.2 years (+0.02%)Pass
Solar System energy change, 20 years< 10⁻⁶1.7e-8Pass
Solar System angular momentum change, 20 years< 10⁻⁹2.1e-14Pass