# Bouncing Balls (Real Physics) — `bouncing-balls@1.1.0`

Balls drop into a box, bounce off each other and the walls, roll and settle — an actual physics simulation.

A small, real rigid-body simulation: spheres with mass, bounciness and friction collide with each other and the walls of a box, spin as they roll, and come to rest. It is calculated once at a fixed step, then played back frame by frame, so the same seed always gives the same motion and video export is exact. The result is just numbers (positions and spins), so you can draw the balls any way you like: flat 2D, glossy 3D, chrome or glass.

- Kind: physics; status: published; digest sha256:25a29def87560c0a12e03dfed36f2fe090df37af0480a1579d2d5f7597521a89
- Role: Motion source: per-frame positions and orientations of colliding spheres in a box.
- Implementation: js; entry `src/bouncing-balls.js`
- Files: https://aipixelwizard.com/registry/bouncing-balls/1.1.0/<path> (`LICENSE`, `asset.json`, `demo.js`, `index.html`, `src/bouncing-balls.js`)
- Manifest JSON: https://aipixelwizard.com/api/v1/visuals/bouncing-balls/versions/1.1.0
- Runnable demo: https://aipixelwizard.com/registry/bouncing-balls/1.1.0/index.html (open over http; it imports sibling packages by relative path)
- Dependencies (exact): `apw-runtime@1.0.0` (demo) — Demo harness and colour helpers. src/bouncing-balls.js has no dependencies.

## Exports
- `simulateBouncingBalls({ count?, seed?, box?, radius?, restitution?, friction?, gravity?, fps?, durationSeconds?, stepHz?, spawnHeight? }) => { count, radii, frameCount, positionAt(frame, i), quaternionAt(frame, i), positions, quaternions, box, stats }` — Runs the fixed-timestep simulation once and returns baked per-frame transforms.
- `rotateByQuaternion([x, y, z], [qx, qy, qz, qw]) => [x, y, z]` — Helper for drawing spin in 2D.

## Parameters
- `count` (integer 1–40, default 16) — Number of balls
- `bounciness` (number 0.3–0.92, default 0.72) — Bounciness: Low is heavy and dead; high is super-bouncy.
- `ballSize` (number 0.6–1.5, default 1) — Ball size
- `gravity` (number 2–20, default 9.81) — Gravity: 9.81 is Earth. Lower feels floaty, like slow motion.
- `seed` (integer 1–9999, default 7) — Variation: Each number gives a different but repeatable drop.
- `palette` (select one of candy | sunset | mono, default "candy") — Colours
- `background` (color, default "#0d0d12") — Background

## Composition
- Inputs: Options (count, seed, box size, bounciness, gravity) and the clip's fps/duration.
- Outputs: positionAt(frame, i) → [x, y, z] and quaternionAt(frame, i) → [x, y, z, w] for each ball; radii per ball.
- Coordinates: Y-up. Box floor at y = 0, centred on x = 0, z = 0. Default inner box 4 × 3 × 4 units. Walls are infinitely tall.
- Lighting: None (motion only).
- Timing: Bake once with the clip's fps and duration, then look up by frame. Frames are random-access.
- Works with: chrome-material, glass-container, orbit-camera
- Match the box option to any container you draw (Glass Container uses the same 4 × 3 × 4 default).
- Re-bake when count, seed, bounciness, gravity, fps or duration change.

## Output and loop
- 1080×1080, 30 fps, 8 s, simulation
- Seamless loop: no — Physics runs forward and settles; it does not return to the starting state.
- Verified 2026-10-03 in Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0 (Canvas 2D); deterministic: yes

## Use it
New project: Call simulateBouncingBalls({ fps, durationSeconds, ...options }) once, then each frame read positionAt(frame, i) and draw ball i at that position with radius radii[i].

Existing project: Copy src/bouncing-balls.js. Bake once where your scene is created, then update the transforms of your existing ball objects from positionAt/quaternionAt in your frame update. Keep your renderer, materials and camera.

Reuse ideas:
- Draw the balls as chrome spheres inside a glass box in Three.js (see the Bouncing Marbles in a Box example).
- Use positions to drive 2D emoji, logos or product photos falling into a pile.
- Use only the first second (balls still in the air) for a drop-in transition.

## Limitations
- Spheres only, inside an axis-aligned box. Not a general physics engine.
- During hard impacts balls can overlap by up to about 0.05 units for a step before correction (measured ≤ 0.05 with default sizes).
- Slow contacts are treated as resting (no bounce) so piles settle instead of jittering.

## License
MIT. Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.

Provenance: Original code written for AI Pixel Wizard in October 2026. Demo scene rebuilt in October 2026 to the catalog quality bar (docs/QUALITY.md); the reusable source is unchanged.

