Skip to content

Add billiard ball textures with procedural generation and visual rotation#15

Merged
TimBeyer merged 11 commits intomasterfrom
claude/add-billiard-textures-2W155
Mar 29, 2026
Merged

Add billiard ball textures with procedural generation and visual rotation#15
TimBeyer merged 11 commits intomasterfrom
claude/add-billiard-textures-2W155

Conversation

@TimBeyer
Copy link
Copy Markdown
Owner

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

…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
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages bot commented Mar 29, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 29, 2026

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1242.68 1032.52 -16.91% 🔴
20 circles / 60s 519.66 537.04 +3.34%
40 circles / 60s 214.49 220.54 +2.82%
80 circles / 60s 41.56 43.04 +3.56%
150 circles / 60s 5.03 3.59 -28.70% 🔴
300 circles / 60s 2.33 2.18 -6.15% ⚠️
500 circles / 60s 1.30 1.62 +25.03% 🚀
1000 circles / 60s 0.83 1.07 +28.27% 🚀

Overall: -9.19% ⚠️


Merge base: 4412e54 | PR commit: 6dd261b | 2026-03-29 13:46 UTC

Previous runs

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1304.86 1025.54 -21.41% 🔴
20 circles / 60s 567.61 578.56 +1.93% ➡️
40 circles / 60s 231.33 225.24 -2.63% ⚠️
80 circles / 60s 47.05 44.80 -4.79% ⚠️
150 circles / 60s 5.17 3.75 -27.46% 🔴
300 circles / 60s 2.39 2.32 -2.92% ⚠️
500 circles / 60s 1.29 1.61 +24.68% 🚀
1000 circles / 60s 0.87 1.10 +26.83% 🚀

Overall: -12.85% 🔴


Merge base: 4412e54 | PR commit: 96134a4 | 2026-03-29 13:25 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1077.45 1296.35 +20.32% 🚀
20 circles / 60s 554.98 562.76 +1.40% ➡️
40 circles / 60s 172.12 219.68 +27.63% 🚀
80 circles / 60s 46.54 43.65 -6.19% ⚠️
150 circles / 60s 5.01 3.71 -26.01% 🔴
300 circles / 60s 2.35 2.27 -3.47% ⚠️
500 circles / 60s 1.30 1.67 +28.32% 🚀
1000 circles / 60s 0.85 1.13 +32.74% 🚀

Overall: +14.54% 🚀


Merge base: 4412e54 | PR commit: 37d0f57 | 2026-03-29 13:08 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1306.85 1283.51 -1.79% ➡️
20 circles / 60s 569.29 552.78 -2.90% ⚠️
40 circles / 60s 222.44 219.98 -1.11% ➡️
80 circles / 60s 43.73 47.24 +8.02%
150 circles / 60s 5.19 5.06 -2.44% ⚠️
300 circles / 60s 2.34 2.38 +2.01%
500 circles / 60s 1.34 1.33 -0.56% ➡️
1000 circles / 60s 0.87 0.87 +0.16% ➡️

Overall: -1.81% ➡️


Merge base: 4412e54 | PR commit: 33a86a8 | 2026-03-29 13:04 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1120.08 766.74 -31.55% 🔴
20 circles / 60s 579.26 524.97 -9.37% ⚠️
40 circles / 60s 228.69 208.70 -8.74% ⚠️
80 circles / 60s 43.60 44.96 +3.11%
150 circles / 60s 5.03 4.91 -2.30% ⚠️
300 circles / 60s 2.37 2.38 +0.28% ➡️
500 circles / 60s 1.33 1.31 -1.37% ➡️
1000 circles / 60s 0.86 0.87 +0.52% ➡️

Overall: -21.52% 🔴


Merge base: 4412e54 | PR commit: 6411e0d | 2026-03-29 12:28 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1007.50 1171.70 +16.30% 🚀
20 circles / 60s 544.59 435.09 -20.11% 🔴
40 circles / 60s 227.19 210.20 -7.48% ⚠️
80 circles / 60s 46.03 34.85 -24.29% 🔴
150 circles / 60s 5.07 4.71 -7.07% ⚠️
300 circles / 60s 2.33 2.33 -0.10% ➡️
500 circles / 60s 1.31 1.34 +2.77%
1000 circles / 60s 0.86 0.88 +1.43% ➡️

Overall: +1.43% ➡️


Merge base: 4412e54 | PR commit: abcf1a4 | 2026-03-29 12:03 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1323.91 1143.73 -13.61% 🔴
20 circles / 60s 567.28 608.02 +7.18%
40 circles / 60s 230.09 230.25 +0.07% ➡️
80 circles / 60s 44.33 45.12 +1.78% ➡️
150 circles / 60s 5.24 5.21 -0.69% ➡️
300 circles / 60s 2.42 2.41 -0.55% ➡️
500 circles / 60s 1.35 1.35 -0.43% ➡️
1000 circles / 60s 0.88 0.87 -1.04% ➡️

Overall: -6.37% ⚠️


Merge base: 4412e54 | PR commit: 9d01838 | 2026-03-29 11:35 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1337.97 390.72 -70.80% 🔴
20 circles / 60s 543.97 440.04 -19.11% 🔴
40 circles / 60s 224.47 204.22 -9.02% ⚠️
80 circles / 60s 42.45 42.32 -0.32% ➡️
150 circles / 60s 4.92 4.65 -5.59% ⚠️
300 circles / 60s 2.32 2.21 -4.75% ⚠️
500 circles / 60s 1.29 1.25 -2.70% ⚠️
1000 circles / 60s 0.83 0.79 -5.50% ⚠️

Overall: -49.67% 🔴


Merge base: 4412e54 | PR commit: f15ab62 | 2026-03-29 11:31 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1123.59 1317.48 +17.26% 🚀
20 circles / 60s 566.57 574.24 +1.35% ➡️
40 circles / 60s 222.31 216.68 -2.53% ⚠️
80 circles / 60s 44.87 41.49 -7.53% ⚠️
150 circles / 60s 5.01 5.02 +0.20% ➡️
300 circles / 60s 2.34 2.36 +1.05% ➡️
500 circles / 60s 1.32 1.31 -0.32% ➡️
1000 circles / 60s 0.85 0.84 -1.28% ➡️

Overall: +9.79% ✅


Merge base: 4412e54 | PR commit: 807f51f | 2026-03-29 11:22 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1269.06 970.60 -23.52% 🔴
20 circles / 60s 533.00 479.42 -10.05% 🔴
40 circles / 60s 206.96 201.42 -2.68% ⚠️
80 circles / 60s 39.80 41.93 +5.36%
150 circles / 60s 4.72 4.61 -2.34% ⚠️
300 circles / 60s 2.18 2.23 +2.28%
500 circles / 60s 1.21 1.21 +0.18% ➡️
1000 circles / 60s 0.77 0.78 +0.67% ➡️

Overall: -17.28% 🔴


Merge base: 4412e54 | PR commit: f7faeba | 2026-03-29 11:17 UTC

Benchmark Comparison

Benchmark Baseline (ops/sec) PR (ops/sec) Change
10 circles / 60s 1110.42 1182.19 +6.46%
20 circles / 60s 566.31 576.20 +1.75% ➡️
40 circles / 60s 228.39 223.15 -2.29% ⚠️
80 circles / 60s 46.86 46.33 -1.13% ➡️
150 circles / 60s 5.17 4.95 -4.26% ⚠️
300 circles / 60s 2.35 2.38 +1.54% ➡️
500 circles / 60s 1.34 1.34 -0.28% ➡️
1000 circles / 60s 0.87 0.86 -1.33% ➡️

Overall: +3.86% ✅


Merge base: 4412e54 | PR commit: 78bace1 | 2026-03-29 11:12 UTC

claude added 10 commits March 29, 2026 11:16
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
@TimBeyer TimBeyer merged commit 68672bb into master Mar 29, 2026
5 checks passed
@TimBeyer TimBeyer deleted the claude/add-billiard-textures-2W155 branch March 29, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants