Ball in a Square Hole — LLM Implementation Comparison
1024×1024 toroidal field · 200×200 square with 100×100 hole · ball Ø10 · both bodies mass 1 · perfectly elastic, no friction
Each file below is an independent attempt by a different LLM at the same brief:
a ball bouncing inside a concave square on a wrapping 1024² plane, with total momentum and kinetic
energy exactly conserved. This index summarises what each one does, what is broken or risky, and
highlights the strongest entries. Source files live next to this index.html.
Best / reference
Notable / ambitious
Caveats
Bugs / wrong physics
Round 0 — minimal brief (no rotation)
First wave of attempts, mostly short single-file scripts. Mixed quality.
Most polished. Impulse-based 1-D normal-velocity swap on the four inner edges, fixed 1/240 s sub-step accumulator, wrap-aware relative coordinates, full conservation diagnostics (Σp, |Σp|, ΣKE, COM drift), velocity vectors, trail, time-scale slider.
- Only inner edges are checked — if a future edit lets the ball enter the frame body it will not be pushed back out.
- No cap on |v|; with time-scale = 2 and a kick the per-step travel can exceed the ball radius, risking tunneling.
Equal-mass velocity-swap on the inner X and Y walls with sub-stepping and 3×3 ghost-tile rendering. The swap is exact and conserves Σp and ΣKE on paper.
- Re-seating across the seam: after a contact near the 1024 boundary, the ball is repositioned at wrap(square.x ± LIMIT), which can place it in a different toroidal copy than the one drawn for the square → visible teleports.
- No tunneling guard: a single sub-step travel larger than the hole-wall thickness lets the ball slip past the wall with no test firing.
- Kick button only nudges the ball — Σp is not preserved when the ball was at rest, contradicting the stated conservation law.
Four AABB wall rects built from the frame minus the hole, with a 3×3 toroidal tile search. Positional correction is split by inverse mass.
- Velocity impulse uses j = −dv·n with no (1+e) factor and no inverse-mass denominator — only correct for the hard-coded equal-mass case.
- Renders a single square, no ghost tiles, so the ball appears to teleport on wrap.
- 4 walls × 9 tiles = 36 collision tests per frame; trivially optimisable.
Per-edge checkWall with wrap-aware range test, plus a fading ball trail. Visually nice.
- Wrap shift uses Math.round((wallCoord − ballPos) / size) — a wall more than half a tile away is snapped to the wrong tile, so contacts on the seam can be missed silently.
- Velocity swap is one-axis-at-a-time, but the X- and Y-pass repositions the ball with wrap() after the first swap, so the second pass uses an inconsistent state — a small order-of-operations bug.
Inner walls as four normals; square reflects off hard field edges.
- Field is not wrapped (the brief asks for an XY torus) — the square reflects off field edges, which is an external impulse and breaks the closed-system conservation claim.
- Only the inner edges are tested; if the ball ever sits between the frame and the field wall it can pass straight through the outer face.
- Field-wall bounce is not split by mass: the ball is reflected with −v regardless of the square's motion.
Simplest implementation: a 1D bounce in the hole-relative frame, with a wrap.
- Ball is reflected with −v and the square is never updated, so Σp and ΣKE are not conserved.
- Square is decorative — it has vx, vy in state but they are not used in physics.
Eight wall passes against 1×1 slabs at the inner and outer edges.
- 1×1 slabs make closestPoint return the corner of the slab, so the "distance to wall" is a diagonal — the ball triggers a collision up to √2 · R away and is pushed along the wrong normal near corners. The ball escapes through the diagonal.
- Field-wall bounce uses Math.abs(v), which adds kinetic energy on every reflection.
- Update is driven by DT = 1/120 with no requestAnimationFrame time delta — the simulation speed depends on the display refresh rate.
Round 1 — brief extended with optional rotation
Now the square should support free rotation about its centre, with the analytic moment of inertia for a frame with a square hole. A toggle locks rotation while leaving translation free.
Impulse-based collision with analytic frame inertia, free-rotation toggle, fixed sub-step accumulator, and wrap-aware 9-copy rendering. Matches the quality of 01_hy3.html in fewer lines.
- No conservation HUD — Σp / ΣKE drift is invisible.
- No |v| cap or outer-wall guard (common to the set).
Compact implementation with adaptive sub-stepping, 9-copy tiling, and a rotation toggle. Initially contained two bugs: a sign error in the collision physics and a malformed layout that blocks checkbox.
- Sign bug in physics — the relative-velocity / normal sign check in the collision response is inverted for some contact configurations, causing missed bounces.
- Layout-blocking HTML bug — the canvas closing tag is malformed (
</div> instead of </canvas>), which breaks the layout and can prevent the checkbox from working.
01_hy3.html
Notable — full rigid body
Angular-momentum invariant
The only implementation that actually carries angular momentum: analytic moment of inertia for the frame with hole, impulse split between linear and rotational degrees of freedom, diagnostics for linear and angular Σ.
- The frame's I is large (≈ 4167 for the default sizes) so the square barely spins after a hit. Not a bug, but the rotational effect is hard to see without an explicit "spin the frame" control.
- Wrap is implemented as "snap object, shift ball by the same delta" — correct, but easy to break in a future edit.
- Single ghost-tile render is fine for a square, but the trail points are drawn in all 9 tiles individually, which is slow.
Compact, well-commented. The rotation checkbox toggles free rotation with moment of inertia.
- No conservation HUD — drift in Σp / ΣKE is not visible, so the rotation coupling is hard to verify visually.
- Single copy render.
A long single-file attempt with rotation support and an HTML/CSS UI. Useful as a baseline to compare line counts against.
- No conservation diagnostics in the HUD.
- Filename ends in .htm — some static servers will still serve it, but the broken extension is a smell.
Variation that runs more than one ball inside the same hole — collision-against-other-balls logic is added on top of the wall collision.
- Without seeing the full file, multi-ball inter-collision is the most likely place to leak energy; worth checking the impulse split between equal-mass balls.
A more elaborate cousin of 0!_glm52max.html: same impulse core but with the rotation toggle added, a side panel, time-scale slider, and a full live readout (ball/square speed and components, Σp now/init, ΣKE now/init, COM-drift speed, FPS).
- Pulls Tailwind from a CDN, so it needs network to render the panel styling.
- Same caveat as 0!_glm52max.html: no outer-wall guard and no |v| cap.
A near-twin of 0!_co48t.html extended with rotation. Clean visual style and explicit tile-relative rendering.
- Inherits the same seam re-seating subtlety as 0!_co48t.html — needs the same fix if the brief is ever revisited.
Round 2 — synthesised "best of" + extra features
Later passes that combine ideas from earlier implementations and add presentation features.
Combines the rigid-body rotation of 01_hy3.html with the wrap-aware impulse core and the conservation diagnostics of the GLM 0/1 entries. Closest to a reference implementation in this set.
- Longest file in the comparison — the duplication between impulse, wrap, and HUD code invites copy-paste drift.
- Same outer-wall / |v| caveats as the other impulse-based entries.
Sibling of 02_hy3+synth.html with minor variations — useful to see what small refactors change about the simulation behaviour.
Another sibling of 02_hy3+synth.html. Same engine, different cosmetic / UI choices.
A late-stage attempt that adds an audio synthesiser: a different tone is generated for each bounce, with selectable mapping (mean of pre/post speeds, constant pitch, speed-proportional, or only-on-bounce).
- Audio requires a user gesture on most browsers; the sound checkbox alone is not enough to start the AudioContext.
- Adds another invariant to verify: the chosen pitch mapping should be reproducible from the post-collision velocity alone.
Common issues across the set
- No |v| cap. Every file can be kicked past a speed that moves the ball more than its own radius in a single sub-step — a tunneling risk for any future higher-resolution test.
- No outer-wall guard. Impulse-based cores test only the four inner edges. If a refactor ever lets the ball enter the frame body, none of them will push it back out.
- Conservation diagnostics are inconsistent. Only the GLM-family entries show Σp and ΣKE against an initial value; everywhere else you have to take the implementation on trust.
- Wrap is re-implemented each time. Each file rolls its own wrap / delta / tile-render helper. A shared utility would eliminate the "Math.round vs floor" / "teleport on seam" classes of bug.
- No DPR / HiDPI scaling. All canvases are 1024×1024; on a 4K display the result is upscaled and soft. 01_hy3.html uses image-rendering: pixelated as a partial workaround.
If you only open three
- 0!_glm52max.html — best impulse-based, no rotation, with full conservation HUD.
- 01_hy3.html — best angular-momentum implementation, with rotation.
- 02_hy3+synth.html — best combined "everything" version (rotation + HUD + wrap).