PaddockJS is an installable browser component package for an F1-style project race simulator. It owns the simulator UI, PixiJS runtime, bundled simulator assets, CSS, demo data, and public mount APIs. Host websites provide data, page placement, routing, and optional theme choices.
Code snippets in this README are illustrative host integration fragments unless a section says they are a complete runnable file.
npm install @inventure71/paddockjsUse Node 20.19.0 or newer for local package development and modern Vite-based host builds.
For a normal website page, mount the all-in-one simulator from the root browser package:
import { mountF1Simulator } from '@inventure71/paddockjs';
const root = document.getElementById('sim-root');
if (root) {
await mountF1Simulator(root, {
drivers: [
{
id: 'budget',
name: 'Budget Buddy',
color: '#ff2d55',
link: '/projects/budget-buddy',
raceData: ['AI finance coach', 'Python + LLM', 'Budget guardrails'],
},
],
entries: [
{
driverId: 'budget',
driverNumber: 71,
timingName: 'Budget',
},
],
onDriverOpen(driver) {
if (driver.link) window.location.href = driver.link;
},
});
}The root package is browser-oriented and imports package CSS. If your host build does not extract CSS from JavaScript imports, import the stylesheet explicitly:
import '@inventure71/paddockjs/styles.css';PaddockJS CSS is scoped under package mount/component roots and uses system font stacks. The package does not load Google Fonts or any other remote font. Hosts that want branded typography should load fonts in the host app.
mountF1Simulator(root, options) writes a lightweight package-owned HTML shell into root before it awaits PixiJS setup, asset loading, control binding, simulation creation, and the first canvas frame. That shell includes package loading overlays for the race controls, timing tower, race canvas, race-data panel, telemetry stack, and any other startup-capable package surface in the selected template. The overlays are removed only after the runtime has initialized and the initial readouts/frame are ready.
One host root may own only one live all-in-one simulator. Call the returned controller's destroy() before mounting into that root again. If initialization fails, PaddockJS destroys the partial runtime, clears the root, and releases it so the host can retry.
Composable hosts get the same behavior per mounted root: every public simulator.mount*() surface renders static package markup plus its own loading overlay before await simulator.start().
This is a client-side startup shell, not server rendering. If a host wants visible content before the PaddockJS JavaScript bundle has downloaded and executed, use the tiny placeholder subpath and CSS:
import { createPaddockLoadingPlaceholder } from '@inventure71/paddockjs/placeholder';
import '@inventure71/paddockjs/placeholder.css';
root.innerHTML = createPaddockLoadingPlaceholder({
label: 'Loading simulator',
});
const loadSimulator = async () => {
const { mountF1Simulator } = await import('@inventure71/paddockjs');
await mountF1Simulator(root, { drivers, entries });
};
if ('requestIdleCallback' in window) {
window.requestIdleCallback(loadSimulator);
} else {
setTimeout(loadSimulator, 0);
}@inventure71/paddockjs/placeholder does not import PixiJS, simulator assets, package runtime CSS, or the root browser mount. It is intended for the pre-JS/network gap. The full simulator mount replaces that placeholder and then uses the built-in package overlays while PixiJS and the race runtime finish booting. Static HTML hosts should run the placeholder helper in their build step and copy @inventure71/paddockjs/placeholder.css into their static output instead of expecting a plain browser page to resolve bare npm imports.
Start here:
- Getting Started: install, browser mount, CSS, assets, host responsibilities, and container sizing.
- Startup Loading: pre-JS placeholders, package loading overlays, and static-host build-time integration.
- Data Contract: driver, entry, callback, rules, and option shapes.
- Troubleshooting: common integration failures and supported checks.
Use createPaddockSimulator() when a host wants to place package-owned surfaces in separate roots while keeping one simulator controller:
import { createPaddockSimulator } from '@inventure71/paddockjs';
const simulator = createPaddockSimulator({ drivers, entries });
simulator.mountRaceControls(document.getElementById('sim-controls'));
simulator.mountRaceCanvas(document.getElementById('sim-race'), {
includeTimingTower: true,
includeRaceDataPanel: true,
});
simulator.mountRaceDataPanel(document.getElementById('sim-race-data'));
await simulator.start();Each composable surface key owns one host root. Mounting the same surface into a replacement root clears the previous root, while mounting two different surfaces into the same root is rejected. Concurrent start() callers await the same initialization; a failed start destroys the partial app and may be retried on the same controller.
The controller methods are the canonical composable API. See Composable Layouts for the available surfaces, lifecycle, restart behavior, and sizing contract.
Do not import the root package from raw Node or headless training code. The root package is for browser mounts and imports CSS/assets.
Use the browser-free environment subpath for simulation/training loops:
import { createPaddockEnvironment } from '@inventure71/paddockjs/environment';Use the CSS-free data subpath for tooling, server-side data normalization, and procedural track helpers:
import { DriverData, normalizeSimulatorDrivers } from '@inventure71/paddockjs/data';Read Headless Environment, Data Helpers, and Bring Your Own Model for those paths.
Use mount-time theme for defaults and controller methods for runtime site theme toggles:
const simulator = await mountF1Simulator(root, { drivers, entries });
simulator.setThemeMode(document.documentElement.dataset.theme === 'light' ? 'light' : 'dark');
const stopThemeSync = simulator.syncThemeFrom(document.documentElement, {
attribute: 'data-theme',
map: { light: 'light', dark: 'dark' },
});Do not restart the simulator only to change presentation. Runtime theme APIs keep race state, selected driver, penalties, speed, camera state, and pit intent state. See Theming.
If a host is moving from 2.x-era usage, use Upgrading Hosts To PaddockJS 4.x. The short version:
- install
@inventure71/paddockjs@4 - remove
trackQueryIndex - replace
physicsMode: 'simulator'withphysicsMode: 'advanced'or omit it forarcade - import package CSS intentionally for browser mounts
- use
/environmentfor headless simulation and/datafor CSS-free helpers - rebuild and browser-smoke the host page
Normal consumers should start with the task docs above. These reference docs are the deeper package source of truth:
- System Specs: public API, runtime guarantees, and verification standards.
- Rules: race-control behavior, DRS, safety car, starts, ordering, contact handling, pits, penalties, and simulation rules.
- Concepts: simulator vocabulary.
- Architecture: module ownership and data/control flow for package contributors.
- Component Inventory: package-owned UI surfaces and template ownership.
site/ is the repository's single website and package integration host. One server and build provide:
/: the live product demo, starting in Grid Walk, with Race controls, composable surfaces, presets, advanced physics, a headless example and searchable feature inventory./guide/: focused integration guides for templates, components, customization, API, Grid Walk and race rules./labs/: playable controls, behavior and stewarding fixtures, collision diagnostics and Policy Runner./reference/: readable pages generated from the repository's package documentation.
Shared navigation connects every section. Old root-level showcase URLs redirect to their canonical Guide or Labs route while preserving query parameters and fragments. The site installs this checkout through the package boundary. Its explicitly labeled collision and Policy Runner tools also use private geometry and presentation diagnostics; those modules are not supported consumer exports. Website files are excluded from the npm package.
Useful local commands:
npm run docs:check
npm run check
npm run check:release
npm run audit:release
npm run consumer:smoke
npm run consumer:bundle-boundaries
npm run browser:smoke:quick -- --skip-build
npm run site:install
npm run site:dev
npm run demo:smokenpm run check is the normal local gate: docs checks, fast runtime tests, public declarations, dry package contents, packed-package consumption in a fresh Vite app, packed subpath bundle-boundary checks, one website install/build, the quick Guide/Labs browser smoke, and the complete demo smoke. npm run check:release installs the locked private release tools and audits the package, website and release-tool dependency graphs, then adds slow characterization tests and the full Guide/Labs browser matrix while retaining the demo smoke. Run release checks/Changesets commands on a supported release-tool Node version, such as Node 24; the simulator package and ordinary checks still support Node 20.19. See Install and Update for the tooling boundary and release workflow.
Pass gridWalk: { enabled: true } to either mounting API to explore the starting grid before racing. Supply entry presentation metadata for full names, summaries and ordinary links in the same bottom project banner used by Race. The arrow selector sits centered at the top, with centered entry text; Grid Walk adds no separate project details panel.
Its package-styled Grid Walk / Race switch lives outside the scene: mount simulator.mountViewControls(viewControlsRoot) before start() alongside a composable mountRaceCanvas(), or use the equivalent mountViewControls(viewControlsRoot, simulator) helper. The all-in-one shell and race telemetry drawer include these controls above the scene automatically. Choose ui: { viewControls: 'toggle' } for one green Start race / red Stop race button instead of the default two-button switch. Stop pauses the race and returns to its original display grid; Start resumes saved progress. The main demo demonstrates this single-button option.
setView('grid-walk' | 'race') switches presentation without resetting progress; the same grid camera rotates for actual component dimensions and supports drag, touch and wheel browsing. Browser-native scrolling drives that camera and hands scrolling to the host page at the grid's ends. Race wheel zoom uses continuous scroll distance, yields at its limits and preserves native page touch gestures. Neither mode registers a canceling wheel listener or locks body/document scrolling; Ctrl/Meta wheel retains browser zoom. This avoids the nonpassive wheel-listener path implicated in macOS Gecko scroll freezes; it does not patch the browser itself. Grid Walk reuses race controls and panels, with varied preparation crews and a continuous flow of visitors entering and leaving beyond the camera bounds. Existing consumers retain Race defaults. See Grid Walk for the API, lifecycle and example, or open /guide/grid-walk.html on the website. The main demo also starts with Grid Walk.
PaddockJS is released under Apache-2.0. See LICENSE.
Advanced physics remains opt-in with physicsMode: 'advanced'; ordinary mounts and demos default to arcade. Advanced mode uses a planar Formula-style tire-force model with load transfer, yaw inertia and aerodynamic forces. Its keyboard lab is available in the demo's Advanced physics chapter. See physics rules and model scope and validation for the calibration, approximations and verification evidence.