Add billiard ball textures with procedural generation and visual rotation#15
Add billiard ball textures with procedural generation and visual rotation#15
Conversation
…tion Procedurally generate billiard ball textures (American, British, Snooker) using Canvas2D and apply them as Three.js CanvasTextures. Balls rotate visually using angular velocity data from the physics simulation. Texture set is switchable live from a new "Ball Appearance" panel in the sidebar. When there are more balls than textures in a set, textures cycle. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
balls | 6dd261b | Commit Preview URL Branch Preview URL |
Mar 29 2026, 01:46 PM |
Benchmark Comparison
Overall: -9.19% Merge base: Previous runsBenchmark Comparison
Overall: -12.85% 🔴 Merge base: Benchmark Comparison
Overall: +14.54% 🚀 Merge base: Benchmark Comparison
Overall: -1.81% ➡️ Merge base: Benchmark Comparison
Overall: -21.52% 🔴 Merge base: Benchmark Comparison
Overall: +1.43% ➡️ Merge base: Benchmark Comparison
Overall: -6.37% Merge base: Benchmark Comparison
Overall: -49.67% 🔴 Merge base: Benchmark Comparison
Overall: +9.79% ✅ Merge base: Benchmark Comparison
Overall: -17.28% 🔴 Merge base: Benchmark Comparison
Overall: +3.86% ✅ Merge base: |
The previous rotation approach read angularTrajectory coefficients that are never synced from the worker to the main thread, causing stale data to produce incorrect rotations. Switch to per-frame incremental rotation using angularVelocity (which IS synced via event snapshots). Cap frame delta at 100ms to prevent huge rotations after pauses or event catch-up. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
…alls Two issues fixed: 1. Coordinate sign error: The physics→Three.js position mapping (X,Y,Z)→(X,Z,Y) is a handedness-flipping reflection. Angular velocity is a pseudovector, so it transforms as ω' = -M·ω — all components must be negated. Without this, balls rotated in the exact opposite direction. 2. Stale angular velocity: The snapshot angularVelocity goes stale between events, but for Rolling balls we can derive it exactly from the interpolated velocity using the rolling constraint (ωx = -vy/R, ωy = vx/R). Linear velocity IS accurately interpolated via trajectory coefficients on the main thread. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
The previous approach drew flat shapes (circles, stripes) on a canvas that gets mapped to a sphere via equirectangular UV projection, causing visible stretching. Now all features are rendered per-pixel in spherical coordinates: - Number circle is defined as an angular radius on the sphere surface and tested via dot product (arc distance), so it appears perfectly round - Stripe band boundaries follow latitude lines, which are naturally correct in equirectangular projection - Number text is drawn with ctx.scale(0.5, 1) to compensate for the 2:1 horizontal stretch at the equator https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
The main thread previously only had stale angularVelocity snapshots, causing spinning balls to visually stop abruptly when the Spinning→ Stationary transition event arrived. Now CircleSnapshot includes angularAlpha and angularOmega0 from the physics angular trajectory, allowing the renderer to interpolate angular velocity smoothly: omega(dt) = alpha*dt + omega0. For Rolling balls, angular velocity is still derived from the interpolated linear velocity (rolling constraint) for maximum accuracy. For Spinning/Sliding/Airborne, the trajectory coefficients provide correct deceleration curves. For Stationary, both are zero. Also attempted lowering QUIESCENCE_SPEED from 2 to <2 mm/s but this triggers a pre-existing trajectory.c precision edge case in fuzz tests. Deferred to a separate fix. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
Tests the Han 2005 cushion resolver at various speeds and spin states to investigate reported spin anomalies near cushions. Results show the resolver is well-behaved: spin scales linearly with approach speed, pre-existing spin barely changes at low speed, and no disproportionate spin is produced at any energy level. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
…ossing The angular trajectory omega(dt) = alpha*dt + omega0 extrapolates linearly, but the physics simulation schedules state transitions at the exact zero- crossing time. Between events, the visualization was extrapolating past zero, causing the spin to reverse and grow unbounded. A ball with wz=-0.1 and spinDecel=28.8 rad/s² would appear to have wz=+28.7 after 1 second of rolling — massive phantom spin on a nearly-stopped ball. Fix: clamp each angular velocity component so it cannot cross zero relative to its initial value (omega0). This matches the physics behavior where deceleration stops at zero, not reverses. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
The root cause: advanceTime() evaluates omega(dt) = alpha*dt + omega0 with no zero-crossing protection. When a rolling ball has residual wz=-0.1 from a cushion hit, the spin deceleration (alpha=+28.78) drives wz to zero in 3.5ms — but the next event may be 1s later. At that point advanceTime() computes wz=+28.7, a massive reversed spin that gets snapshotted and sent to the main thread. Fix: clamp each angular velocity component in advanceTime() so friction deceleration cannot reverse the spin direction. The visualization retains its own clamp for the same reason (it interpolates between events without going through advanceTime). https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
…iant checks - Rewrite cushion spin tests with comparative assertions (spin vs no-spin baselines) - Add Han 2005 angular velocity tests (z-spin generation, four-wall symmetry) - Add south/west wall cushion tests and airborne landing scenarios - Add no-sliding vs full-sliding regime tests for Han 2005 resolver - Add trajectory clamping test (ball doesn't drift back into wall) - Strengthen spinning state invariants and diagonal rolling constraint test - Add cluster solver separation test (all pairs separating post-collision) - Add 4 new scenarios and resolveHan2005Direct test helper 304 tests passing (up from 289), all assertions meaningful. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
All cushion scenarios now start balls ~1270mm from the wall instead of 50mm, so the approach and post-bounce trajectory are clearly visible in the UI. Increased sidespin from 30 to 80 rad/s so it survives friction deceleration during the longer travel. Fixed test to use pre-impact speed reference instead of launch speed. https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK
Procedurally generate billiard ball textures (American, British, Snooker)
using Canvas2D and apply them as Three.js CanvasTextures. Balls rotate
visually using angular velocity data from the physics simulation. Texture
set is switchable live from a new "Ball Appearance" panel in the sidebar.
When there are more balls than textures in a set, textures cycle.
https://claude.ai/code/session_01MPBE3Q3AifTEDnUdZBHaDK