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.

loading core…

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

ItemWhat it exercises
ω² couplingTwo int16×int16→int32 multiplies per step (__mulsi3) for the velocity-squared coupling terms that transfer energy between the two pendulum joints
D denominatorOne int32÷int16 signed divide (__divsi3) per step to normalise the shared denominator D = 3 − cos(2Δ) ∈ [2,4]
rep/sepMode-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.