Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6f01a6f
Implement realistic pool physics with friction, spin, and Han 2005 cu…
claude Mar 25, 2026
194ba72
Fix visualization: scale velocities to realistic pool speeds and keep…
claude Mar 25, 2026
02d55e2
Fix time unit mismatch: convert playback progress from ms to seconds
claude Mar 25, 2026
3a862b5
Fix rendering: don't advance non-involved balls, breaking their traje…
claude Mar 25, 2026
15da12e
Fix cell transitions corrupting motion state and add cushion velocity…
claude Mar 25, 2026
0a9d651
Fix balls escaping through cushions due to spin-induced acceleration
claude Mar 25, 2026
c9f3139
Fix ball-ball collision: stationary balls now receive momentum, enfor…
claude Mar 25, 2026
4397293
Fix z-spin accumulation and energy conservation in ball-ball collisions
claude Mar 25, 2026
0f1bd5b
Fix crash when all balls come to rest
claude Mar 25, 2026
f96ee0c
Refactor physics into OO architecture with swappable profiles
claude Mar 25, 2026
20133ae
Fix physics formulas against reference and add airborne ball support
claude Mar 25, 2026
1cddeea
Fix Han 2005 cushion model: sx formula and angular velocity deltas
claude Mar 25, 2026
ad6cac2
Fix performance regression and corner bounce handling
claude Mar 25, 2026
c96a84a
Fix phantom collision cascade with proper contact verification
claude Mar 25, 2026
ec14fce
Update cascade diagnostic to verify phantom collision rates
claude Mar 25, 2026
44651be
Fix Zeno collision cascade with skip-pair recomputation
claude Mar 25, 2026
3391b49
Fix sliding angular acceleration sign error
claude Mar 26, 2026
94e26dd
Eliminate Zeno cascade with inelastic low-energy collisions
claude Mar 26, 2026
87d631b
Snap balls to exact touching distance before collision resolution
claude Mar 26, 2026
45e323e
Add comprehensive physics test suite with scenario visualization
claude Mar 26, 2026
5417b5f
Fix simultaneous collision pass-through with contact guard and oscill…
claude Mar 26, 2026
f438d60
Add ContactResolver for systemic simultaneous collision handling
claude Mar 26, 2026
8eff7eb
Fix near-contact collision detection and trajectory consistency
claude Mar 26, 2026
a4d34ae
Add debug visualization system: future trails, pause/step, ball inspe…
claude Mar 26, 2026
737fc2f
Replace Tweakpane controls with React + Tailwind debug UI overlay
claude Mar 26, 2026
49f59c7
Add step-backward playback via full replay from initial state
claude Mar 26, 2026
e7a72b6
Fix restart after step-back: reset playback controller state
claude Mar 26, 2026
c34c20f
Add event detail panel and ball event history for debugging
claude Mar 27, 2026
9b01094
Add timeline scrubber slider for seeking through simulation
claude Mar 27, 2026
2580457
Fix spurious ball-ball collisions from stale trajectory coefficients
claude Mar 27, 2026
6867354
Make debug UI responsive for mobile screens
claude Mar 27, 2026
4e3df33
Fix spontaneous energy gain by reverting rebaseTrajectory in cell tra…
claude Mar 27, 2026
abb402c
Fix cell transition trajectory inconsistency + add fuzz testing
claude Mar 27, 2026
ec52fb3
Centralize trajectory sync, add bounds clamping, runtime assertions, …
claude Mar 27, 2026
f14a01b
Fix wall penetration: clamp after every advanceTime, add trajectory b…
claude Mar 27, 2026
50826e8
Fix cushion tunneling: detect t=0 wall collisions the quadratic solve…
claude Mar 27, 2026
739d030
Make replay bar work like a normal media player
claude Mar 27, 2026
e0dc193
Fix ball overlap: quartic solver spurious roots + overlap guard impro…
claude Mar 28, 2026
ca2d1a3
Fix infinite re-collision loop and trajectory maxDt handling
claude Mar 28, 2026
86f25bb
Progressive restitution + energy quiescence for cluster settling
claude Mar 28, 2026
61c1967
Add pair collision rate limiter to prevent Zeno cascades
claude Mar 28, 2026
4f1d2d1
Add eBallBall parameter to BallPhysicsParams for ball-ball restitution
claude Mar 28, 2026
df15876
Add ball event debugger and fix mobile EventDetailPanel z-index
claude Mar 28, 2026
f7e5041
Replace sequential contact cascade with simultaneous cluster solver
claude Mar 28, 2026
cc0e9c7
Fix Newton's cradle: worker now applies scenario physics profile corr…
claude Mar 28, 2026
4af7041
Update CLAUDE.md with physics system, cluster solver, and full projec…
claude Mar 28, 2026
36dd867
Add physics parameter controls and preserve camera across restarts
claude Mar 28, 2026
944421a
Fix cushion-with-backspin: start further from wall with more velocity
claude Mar 28, 2026
aa40e4a
Fix pause/unpause time jump and step-back rendering lag
claude Mar 28, 2026
33b9d44
Fix step-back visual bug: restore trajectory maxDt during replay
claude Mar 28, 2026
531b456
Revert unnecessary maxDt clamping and snapshot storage
claude Mar 28, 2026
e512aec
Fix seek slider snapping to event boundaries instead of target time
claude Mar 28, 2026
2e7a654
Simplify playback system: single code path, delta-based timing
claude Mar 28, 2026
c0776e9
Add seek diagnostics to identify stale ball root cause
claude Mar 28, 2026
55d7b6c
Fix stale ball positions after slider seek by rebasing trajectories
claude Mar 28, 2026
546efa6
Fix 2D rendering skipped when no next event, clear tails on seek
claude Mar 28, 2026
80cb941
Clean up docs: trim CLAUDE.md, create ARCHITECTURE.md
claude Mar 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Architecture

Detailed technical reference for the billiards simulation. For a quick overview, see [CLAUDE.md](./CLAUDE.md).

## Physics System

### Physics Profiles

Two swappable profiles bundle motion models, collision resolvers, and state determination:

- **Pool** (`createPoolPhysicsProfile`): 5 motion states (Stationary, Spinning, Rolling, Sliding, Airborne), Han 2005 cushion resolver, 4-state friction model
- **Simple 2D** (`createSimple2DProfile`): 2 motion states (Stationary, Rolling), simple cushion reflection, no friction

### Physics Config

Per-ball physics parameters (`BallPhysicsParams`):
- `mass`: 0.17 kg (pool), 100 kg (zero-friction tests)
- `radius`: 37.5 mm
- `muSliding`: 0.2, `muRolling`: 0.01, `muSpinning`: 0.044
- `eRestitution`: 0.85 (cushion), `eBallBall`: 0.93 (ball-ball)

Global config (`PhysicsConfig`): `gravity` = 9810 mm/s², `cushionHeight` = 10.1 mm

Three presets: `defaultPhysicsConfig` (pool), `zeroFrictionConfig` (ideal elastic, e=1.0, µ=0)

### Motion States

`Stationary` → `Spinning` → `Rolling` → `Sliding` → `Airborne`

Each state has a motion model that computes trajectory coefficients and transition times. State transitions are scheduled as events in the priority queue, just like collisions.

### Trajectory System

Position: `r(t) = a·t² + b·t + c` (polynomial relative to ball's reference time). Angular velocity: `ω(t) = α·t + ω₀`. Each trajectory has a `maxDt` validity horizon beyond which extrapolation is unphysical.

### Contact Cluster Solver

When a ball-ball collision fires, the solver:
1. **BFS discovery** — finds all balls within `CONTACT_TOL` (0.001 mm) via spatial grid
2. **Snap-apart** — iteratively resolves overlaps (5 passes)
3. **Constraint building** — creates constraints only for approaching pairs (vRelN < 0)
4. **Sequential impulse** (Gauss-Seidel) — iterates up to 20 times until convergence (0.01 mm/s threshold)
5. **Atomic application** — updates trajectories once for all affected balls

Key constants: `V_LOW` = 5 mm/s (below this, e=0 perfectly inelastic), `MAX_CLUSTER_SIZE` = 200.

Accumulated impulse clamping (≥ 0) guarantees convergence — impulses can only push balls apart.

### Pair Rate Limiter

Prevents Zeno cascades (infinite collisions in finite time) for ball pairs:
- **Tier 0** (≤ 30 collisions per 0.2s window): normal physics
- **Tier 1** (31–60): force fully inelastic
- **Tier 2** (> 60): suppress pair entirely until window resets

## Project Structure

```
src/
├── index.ts # Entry point, animation loop, worker management
├── benchmark.ts # Performance benchmarking
├── lib/
│ ├── ball.ts # Ball class: 3D position/velocity, trajectory, epoch, physicsParams
│ ├── circle.ts # Legacy Circle class (still used by some renderers)
│ ├── collision.ts # CollisionFinder: MinHeap priority queue, spatial grid integration
│ ├── simulation.ts # simulate() — core event loop, cluster solver integration
│ ├── simulation.worker.ts # Web Worker: initialization, scenario loading, simulation
│ ├── config.ts # SimulationConfig interface + defaults
│ ├── physics-config.ts # BallPhysicsParams, PhysicsConfig, default/zeroFriction presets
│ ├── motion-state.ts # MotionState enum (Stationary, Spinning, Rolling, Sliding, Airborne)
│ ├── trajectory.ts # TrajectoryCoeffs (a·t²+b·t+c), evaluation functions
│ ├── spatial-grid.ts # SpatialGrid: broadphase, cell transitions
│ ├── min-heap.ts # Array-backed binary min-heap sorted by (time, seq)
│ ├── generate-circles.ts # Non-overlapping circle generation (brute-force placement)
│ ├── scenarios.ts # 50+ test/demo scenarios (single ball, multi-ball, edge cases)
│ ├── polynomial-solver.ts # Cubic/quartic algebraic solvers for collision detection
│ ├── vector2d.ts # Vector2D = [number, number]
│ ├── vector3d.ts # Vector3D = [number, number, number]
│ ├── string-to-rgb.ts # Deterministic ID → color mapping
│ ├── worker-request.ts # Worker request message types
│ ├── worker-response.ts # Worker response message types
│ ├── ui.ts # Tweakpane UI controls
│ ├── physics/
│ │ ├── physics-profile.ts # PhysicsProfile interface, Pool + Simple2D factories
│ │ ├── detection/
│ │ │ ├── collision-detector.ts # Unified detector: dispatches to ball-ball and cushion
│ │ │ ├── ball-ball-detector.ts # Quartic D(t) via Cardano + bisection
│ │ │ └── cushion-detector.ts # Linear/quadratic cushion collision times
│ │ ├── collision/
│ │ │ ├── collision-resolver.ts # Dispatcher: routes to ball/cushion resolvers
│ │ │ ├── contact-cluster-solver.ts # Simultaneous constraint solver (BFS + Gauss-Seidel)
│ │ │ ├── contact-resolver.ts # Post-collision contact resolution (legacy, kept for simple2d)
│ │ │ ├── elastic-ball-resolver.ts # Two-ball impulse resolver (used by simple2d profile)
│ │ │ ├── han2005-cushion-resolver.ts # Han 2005 cushion physics (spin effects, realistic angles)
│ │ │ └── simple-cushion-resolver.ts # Simple reflection cushion resolver
│ │ └── motion/
│ │ ├── motion-model.ts # MotionModel interface (getTrajectory, getTransitionTime)
│ │ ├── sliding-motion.ts # Sliding: friction decelerates, computes rolling transition
│ │ ├── rolling-motion.ts # Rolling: muRolling deceleration to stationary
│ │ ├── spinning-motion.ts # Spinning: z-axis spin decay via muSpinning
│ │ ├── stationary-motion.ts # Stationary: no motion, no transitions
│ │ └── airborne-motion.ts # Airborne: ballistic trajectory with gravity
│ ├── debug/
│ │ ├── playback-controller.ts # Pause, step, step-back, step-to-ball-event
│ │ ├── simulation-bridge.ts # Connects debug UI to simulation state
│ │ └── ball-inspector.ts # Per-ball state inspection
│ ├── renderers/
│ │ ├── renderer.ts # Base renderer class
│ │ ├── circle-renderer.ts # Ball rendering with collision indicators
│ │ ├── tail-renderer.ts # Motion trails
│ │ ├── future-trail-renderer.ts # Predicted future paths
│ │ ├── collision-renderer.ts # Next collision visualization
│ │ └── collision-preview-renderer.ts # Future collision previews
│ ├── scene/
│ │ └── simulation-scene.ts # Three.js 3D scene, lights, camera
│ └── __tests__/ # See Testing section
└── ui/
├── index.tsx # React UI entry point
├── components/
│ ├── Sidebar.tsx # Main debug sidebar
│ ├── BallInspectorPanel.tsx # Per-ball inspector with "Next Ball Event" button
│ ├── EventDetailPanel.tsx # Collision event details (collapsible on mobile)
│ ├── EventLog.tsx # Event history
│ ├── DebugOverlay.tsx # Debug overlay
│ ├── DebugVisualizationPanel.tsx # Debug visualization controls
│ ├── OverlayTogglesPanel.tsx # Renderer toggle controls
│ ├── PhysicsPanel.tsx # Physics preset buttons + parameter sliders
│ ├── ScenarioPanel.tsx # Scenario selection UI
│ ├── SimulationStatsPanel.tsx # Performance stats
│ └── TransportBar.tsx # Play/pause/step controls
└── hooks/
├── use-simulation.ts # Simulation state management hook
└── use-keyboard-shortcuts.ts # Keyboard shortcuts (Space, arrows, Shift+→)
```

## Testing

Tests are in `src/lib/__tests__/` using Vitest with globals enabled. Run with `npm test`.

**Test files:**
- `single-ball-motion.test.ts` — friction deceleration, sliding→rolling, spin decay, energy conservation
- `cushion-collision.test.ts` — head-on, angled, with spin, airborne, corner bounces
- `ball-ball-collision.test.ts` — velocity swap, mass ratios, glancing, spin preservation, inelastic threshold, energy conservation
- `multi-ball.test.ts` — Newton's cradle (3 & 5 ball), V-shape, triangle break, 4-ball convergence, 150-ball stress test
- `edge-cases.test.ts` — exactly-touching, at cushion, zero-velocity-z-spin, simultaneous collisions
- `invariants.test.ts` — no-overlap, monotonic time, momentum conservation, bounds enforcement
- `seek-replay.test.ts` — seek + replay position accuracy across scenarios
- `collision.test.ts` — collision detection unit tests
- `circle.test.ts` — Circle/Ball class unit tests
- `spatial-grid.test.ts` — spatial grid unit tests
- `polynomial-solver.test.ts` — cubic/quartic solver accuracy
- `perf-150.test.ts`, `perf-quick.test.ts`, `perf-compare.test.ts` — performance benchmarks
- `fuzz.test.ts` — randomized stress testing

**Test helpers:** `test-helpers.ts` provides ball factories, `runScenario()`, and assertion helpers (`assertNoOverlaps`, `assertInBounds`, `assertMonotonicTime`).

## Keyboard Shortcuts

- **Space** — pause/resume
- **→** — step to next event (when paused)
- **←** — step back (when paused)
- **Shift+→** — step to next event for selected ball (when paused, ball inspector open)
- **+** / **-** — increase/decrease simulation speed (by 0.5x)
- **1**–**5** — set simulation speed to 1x–5x
- **I** — toggle ball inspector
- **F** — toggle future trails
- **T** — toggle tails
- **C** — toggle collision visualization
- **Escape** — clear ball selection
Loading
Loading