Double Pendulum
Physics & Simulation
Two double pendulums hang from a shared pivot with starting angles that differ by a single
LUT tick — one part in 256 of a full revolution. Their lower-bob paths accumulate as
white and cyan trails in a bitmap canvas. The trails start together, then diverge
exponentially: sensitive dependence on initial conditions, the defining property of
chaos, running on a 1990 games console. Written in C and compiled to a real
.sfc ROM with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator mode). Self-running — no joypad needed.
Self-running — trails build up automatically. White = pendulum A, cyan = pendulum B.
Click the screen to focus, then watch the paths diverge. Tab away and it pauses.
What it is
A double pendulum is two simple pendulums joined end-to-end. The equations of motion for unit masses and unit arm lengths are:
Δ = θ₁ − θ₂, D = 3 − cos(2Δ) N₁ = −3g·sin θ₁ − g·sin(θ₁−2θ₂) − 2·sin Δ·(ω₂²+ω₁²·cos Δ) N₂ = 2·sin Δ·(2·ω₁² + 2g·cos θ₁ + ω₂²·cos Δ) α₁ = N₁/D, α₂ = N₂/D
All angles are stored as uint8_t LUT indices (0–255 = 0–2π) and angular
velocities as int16_t fixed-point. A 256-entry Q8.8 sine LUT
(DPEND_SIN[a] = round(256·sin(2πa/256))) handles all trigonometry at
integer speed. Four substeps per V-blank frame give smooth motion without overrunning
the frame budget.
Sensitive dependence on initial conditions
Pendulum A starts with θ₂ = 50/256·2π ≈ 70°; pendulum B with θ₂ = 51/256·2π ≈ 71.4°. Both begin at rest. For the first few seconds the paths run nearly together. Then the coupling nonlinearity amplifies the initial difference exponentially — the Lyapunov exponent is positive — and the two trails separate completely. A canvas that starts monochrome ends as an equal mix of white and cyan, with no discernible pattern: the hallmark of a chaotic attractor.
Compiler stress-test #14
| Item | What it exercises |
|---|---|
| ω² coupling | Two int16×int16→int32 multiplies per step (__mulsi3) for the velocity-squared coupling terms that transfer energy between the two pendulum joints |
| D denominator | One int32÷int16 signed divide (__divsi3) per step to normalise the shared denominator D = 3 − cos(2Δ) ∈ [2,4] |
| rep/sep | Mode-switch brackets under +mos-a16 mark 16-bit accumulator sections — counted in the disasm gate |
The physics kernel lives in a shared C header (examples/65816/dpend.h).
A host oracle compiles the same code on x86 and prints the golden 16-bit gate hash.
The gate script then builds the ROM with +mos-a16, runs it under both MAME
and bsnes-jg to 500 frames, and asserts corpus_result matches the host —
five-way differential, no far pointers.
Written in C with the llvm-mos 65816 toolchain and verified against two
emulators (MAME + bsnes-jg). Hit Verify fidelity to reproduce the build gate's
WRAM assert (chaos gate CRC 0xE859) live in this tab. No far pointers,
so the same binary is checked five ways: host == default == +mos-a16 ==
+mos-xy16 == bsnes-jg.