Reference example · Project 1 · Basic Skills for Experimentalists, TIGP 2026
A cart on a rail with a pole hinged on it. Push the cart and the pole swings; switch balance on and a controller keeps it upright. It exists to show the shape of a finished Project 1 — and, more than that, to show a simulation being checked against a number worked out on paper before any code existed.
The specification named one number that could be predicted by hand, before a line of code was written: the small-swing period of a simple pendulum.
T = 2π√(L/g) = 2π√(1.00 m / 9.81 m s−²) = 2.006 sThe app measures its own period by timing successive crossings of the hanging position and prints it next to the prediction. Let the pole swing and the two numbers sit side by side on screen.
Two real effects, both visible on screen, both predictable without touching the code.
The cart recoils. 2π√(L/g) assumes the pivot is nailed
down. This one is not: the cart is free to slide, so the pivot moves the other way as the
pole swings, which stiffens the oscillation and shortens the period by
√(M/(M+m)). With M = 5.0 kg and m = 0.2 kg that is a
factor 0.9806.
The amplitude is not small. Released with the bob moving at 1.2 rad s−¹
the swing reaches 22°, and a real pendulum slows as it swings wider —
lengthening the period by about 1 + θ₀²/16, a factor
1.0093.
Ideal small-swing period 2π√(L/g) | 2.0061 s |
× cart recoil √(M/(M+m)) | × 0.9806 |
× finite amplitude 1 + θ₀²/16, θ₀ = 22.1° | × 1.0093 |
| Predicted | 1.9854 s |
| Measured by the app | 1.987 s |
| Agreement | 0.1 % |
This is what a check is for. The first comparison was 1 % out. The useful response was not to shrug, and not to adjust the code until the numbers matched — it was to ask which physics the hand calculation had left out, put it back, and find agreement to 0.1 %. Both corrections were predicted on paper and then confirmed.
You can switch one of them off. Make the cart heavy and it stops recoiling, leaving the
amplitude correction alone — so the period does not converge on 2.006 s, it lands
about 1 % above it. Edit const M in app/index.html:
| cart mass M | measured T | predicted | what is left |
|---|---|---|---|
| 5 kg | 1.9875 s | 1.9854 s | both corrections; they nearly cancel |
| 50 kg | 2.0208 s | 2.0207 s | recoil almost gone |
| 500 kg | 2.0250 s | 2.0243 s | amplitude only |
The 500 kg row is the cleanest result on this page: with the pivot
effectively fixed, 2π√(L/g) × (1 + θ₀²/16) =
2.0243 s against 2.0250 s measured. Two lines of algebra, four significant figures, no
fitting.
Switch balance on and a PD controller takes over the same single input a player has — a horizontal force on the cart:
F = (120 θ + 25 θ̇) + (2.0 x + 3.0 ẋ)The first bracket rights the pole; the second walks the cart back toward the middle. Push the cart with the arrow keys and watch it catch the pole.
The sign of that first bracket is the whole trick, and it looks backwards. To catch a pole falling to the right you must drive the cart to the right, under it — not away from it. The controller shipped with the opposite sign from 2026-09-06 until 2026-09-12, which drove the pole over from any starting lean and parked the cart against the rail every time. Measured twelve seconds after release from the same three leans, with only that sign changed:
| released from | before — |θ|, cart |
after — |θ|, cart |
|---|---|---|
| 3° | 1.810 rad, −3.00 m | 0.001 rad, 0.02 m |
| 9° | 1.811 rad, −3.00 m | 0.003 rad, 0.05 m |
| 17° | 1.809 rad, −3.00 m | 0.007 rad, 0.10 m |
1.81 rad is the pole lying flat with the cart jammed against the end of the rail. 0.00 rad is upright. The period check never caught this because it runs with balance off.
app/index.html — canvas and plain JavaScript. No
framework, no build step, no dependencies. It works on a phone, with touch.Not the pendulum — the method, and the repository layout the course asks for:
SPEC.md written first, naming one number you can
predict on paper.app/, published at …/app/.PROGRESS.md: done, verified, next, gotchas. One entry per
session.If your own session stalls in class, fork this repository and carry on from it —
but do the timing yourself. The measured number in your SPEC.md has to
be one you took.