# Web worker integration example

Worker Log:

## LLM Agent Integration Guide

The Standalone Physics Worker (`worker.js`) runs the billiards physics engine in a Web Worker thread. Source: github.com/tailuge/billiards — see src/worker.ts.

### 1. Integration Pattern

    // Preload the worker once at page load (not per simulation)
    const worker = new Worker('https://tailuge.github.io/billiards/dist/worker.js');

    // Listen for checkpoint messages during init
    worker.onmessage = (e) => {
      if (e.data.type === 'CHECKPOINT') {
        console.log('Worker checkpoint:', e.data.label);
      }
      if (e.data.type === 'COMPLETE') {
        const { frames, outcomes, computeTime } = e.data;
        // process results...
      }
      if (e.data.type === 'ERROR') {
        console.error(e.data.error, e.data.stack);
      }
    };

    // Reuse the same worker for multiple simulations
    worker.postMessage(configA);
    // ...wait for COMPLETE...
    worker.postMessage(configB);

### 2. Message Types (Worker → UI)

type | When | Key fields
--- | --- | ---
`CHECKPOINT` | At each lifecycle stage | `label`, `t` (performance.now)
`COMPLETE` | Simulation finished | `frames[]`, `outcomes[]`, `computeTime`, `tableX`, `tableY`
`ERROR` | On failure | `error`, `stack`

### 3. Checkpoint Labels (in order)

`worker.js loaded` → `Inputs received` → `Constants set` → `Table geometry set` → `Strike applied` → `Iteration progress` (×N) → `Simulation loop complete` → `COMPLETE`

### 4. SimulationConfig Schema

    {
      ruleType: "threecushion" | "snooker" | "eightball",
      balls: [{ id: number, pos: { x, y, z } }, ...],
      cushionModel: "mathavan" | "stronge",
      shot: {
        cueBallId: number,
        angle: number,       // radians
        power: number,       // m/s impulse scale
        offset: { x, y },   // spin offset from centre (max ±0.5 of R)
        elevation: number    // cue elevation radians
      },
      stepSize: number,         // simulation time step (default 0.001953125)
      maxIterations: number,    // hard cap (default 200000)
      warpClearanceR: number,   // warp clearance in units of R (default 4). Set ~50 to disable warp.
      params: { ... }           // physics constant overrides (see below)
    }

### 5. Settable Physics Constants (`params`)

Pass any of these keys in `params`. The key maps to `set<key>(value)` in constants.ts.

Key | Default | Description
--- | --- | ---
`mu` | 0.007 | Rolling friction (Han)
`muS` | 0.136 | Sliding friction (Han)
`rho` | 0.035 | Spindown rate (Han)
`m` | 0.23 | Ball mass (kg)
`R` | 0.03275 | Ball radius (m)
`e` | 0.86 | Ball-ball coefficient of restitution
`ee` | 0.84 | Cushion coefficient of restitution (Mathavan)
`μs` | 0.2 | Table friction coefficient (Mathavan)
`μw` | 0.2 | Cushion friction coefficient (Mathavan)
`StrongeOmegaRatio` | 1.847 | Stronge omega ratio
`StrongeEN` | 0.979 | Stronge normal restitution
`StrongeMu` | 0.161 | Stronge friction coefficient

### 6. Best Practices

- Preload once: Create the worker at page load. The `worker.js loaded` checkpoint fires immediately — do not wait for a simulation to confirm it loaded.
- Reuse the worker: Post multiple configs sequentially. The worker resets internal state each time `onmessage` fires.
- Avoid stringify on large responses: `frames[]` can contain 1000+ entries. Slice before display or process fields directly.
- Monitor checkpoints: If the worker hangs, the last checkpoint label tells you exactly which stage stalled.
- params override order matters: Setting `R` or `m` triggers a `refresh()` of derived constants (Mz, Mxy, I). Set them before other params that depend on derived values.

### 7. Parallel Search Pattern

Instantiate multiple workers to perform concurrent simulations. Use the `id` field to correlate out-of-order responses.

    const workerCount = 4;
    const workers = Array.from({ length: workerCount }, () => new Worker('dist/worker.js'));

    async function runParallel(tasks) {
      const promises = tasks.map((task, i) => {
        return new Promise((resolve, reject) => {
          const worker = workers[i % workerCount];
          const correlationId = task.id || `task-${i}-${Date.now()}`;

          const handler = (e) => {
            if (e.data.id === correlationId && (e.data.type === 'COMPLETE' || e.data.type === 'ERROR')) {
              worker.removeEventListener('message', handler);
              e.data.type === 'COMPLETE' ? resolve(e.data) : reject(e.data);
            }
          };

          worker.addEventListener('message', handler);
          worker.postMessage({ ...task, id: correlationId });
        });
      });

      return Promise.all(promises);
    }

### 8. Universal Runtime Architecture (Web & Node)

The core `worker.js` logic can be executed seamlessly in Node.js using `node:worker_threads` by injecting a tiny global polyfill wrapper. This avoids the need for environment-specific code in the physics engine.

    // node-runner.js
    import { Worker } from 'node:worker_threads';

    const nodeFriendlyWrapper = `
      const { parentPort } = require('node:worker_threads');
      // Polyfill the Web Worker global context
      global.self = global;
      global.postMessage = (data) => parentPort.postMessage(data);
      parentPort.on('message', (data) => {
        if (global.onmessage) global.onmessage({ data });
      });

      // Load your untouched production worker
      require('./dist/worker.js');
    `;

    const worker = new Worker(nodeFriendlyWrapper, { eval: true });

### 9. High-Scale Parallelism & Search

For optimization tasks (e.g., searching for a specific shot), use the multi-platform `ww.js` module. It provides a `runBatch()` function that manages a worker pool and aggregates results.

    import { runBatch } from './ww.js';

    // Execute 3 simulations in parallel with angle variance
    const results = await runBatch(baseConfig, 3, 0.001745);
    results.forEach(res => console.log(`Outcome: ${res.outcomes.length} events`));

### 10. Headless Node.js Execution

The same logic runs in Node.js using `node:worker_threads`. The `SimulationRunner` in `ww.js` automatically detects the environment and polyfills the global `self` context for the physics engine.

To run the headless batch example:

    # From the project root
    node dist/ww-node.js

This is the preferred entry point for LLM agents performing large-scale trajectory analysis or parameter tuning without a browser environment.

### 11. Time-Warp Optimization

When all rolling balls are well clear of cushions and other balls (by at least `warpClearanceR × R` meters) and moving above a minimum speed, the simulation advances by a larger `dt` instead of one small `stepSize` step. This skips uneventful roll segments and reduces iteration count, giving a speedup factor (shown in the log as `speedup 2.50x`).

Set `warpClearanceR` to a value larger than the table half-width (~50) to disable warping entirely — all segments will use the base `stepSize`.