@inventure71/paddockjs 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/INSTALL_AND_UPDATE.md +165 -0
- package/README.md +217 -20
- package/docs/architecture.md +84 -15
- package/docs/concepts.md +20 -3
- package/docs/data_contract.md +412 -20
- package/docs/data_model.md +2 -1
- package/docs/index.md +1 -0
- package/docs/learnings.md +1 -1
- package/docs/rules.md +140 -19
- package/docs/system_specs.md +74 -20
- package/docs/training.md +206 -0
- package/examples/train-basic-policy.mjs +276 -0
- package/examples/trainingData.mjs +133 -0
- package/package.json +18 -8
- package/src/api/PaddockSimulatorController.js +39 -4
- package/src/app/BrowserExpertAdapter.js +32 -0
- package/src/app/F1SimulatorApp.js +1251 -146
- package/src/app/domBindings.js +10 -2
- package/src/config/defaultOptions.js +11 -3
- package/src/data/championship.js +13 -0
- package/src/data/normalizeDrivers.js +7 -2
- package/src/environment/actions.js +68 -0
- package/src/environment/episode.js +17 -0
- package/src/environment/evaluation.js +184 -0
- package/src/environment/events.js +17 -0
- package/src/environment/index.d.ts +521 -0
- package/src/environment/index.js +15 -0
- package/src/environment/observationOptions.js +12 -0
- package/src/environment/observations.js +284 -0
- package/src/environment/options.js +157 -0
- package/src/environment/recorder.js +28 -0
- package/src/environment/rewards.js +69 -0
- package/src/environment/runtime.js +175 -0
- package/src/environment/scenarios.js +220 -0
- package/src/environment/sensors.js +367 -0
- package/src/environment/specs.js +163 -0
- package/src/environment/workerProtocol.js +52 -0
- package/src/index.d.ts +405 -6
- package/src/index.js +27 -2
- package/src/rendering/proceduralTrackAsset.js +343 -24
- package/src/simulation/collisionGeometry.js +214 -0
- package/src/simulation/driverController.js +431 -65
- package/src/simulation/raceSimulation.js +2140 -171
- package/src/simulation/rules/collisionSteward.js +69 -0
- package/src/simulation/rules/penaltyLedger.js +153 -0
- package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
- package/src/simulation/rules/tireRequirementSteward.js +29 -0
- package/src/simulation/rules/trackLimitsSteward.js +85 -0
- package/src/simulation/rulesConfig.js +352 -0
- package/src/simulation/trackModel.js +1154 -76
- package/src/simulation/units.js +6 -4
- package/src/simulation/vehicleGeometry.js +247 -0
- package/src/simulation/vehiclePhysics.js +82 -50
- package/src/simulation/wheelSurface.js +338 -0
- package/src/styles.css +369 -67
- package/src/ui/bannerTemplates.js +69 -0
- package/src/ui/componentTemplates.js +45 -81
- package/src/ui/raceControlStatusBanner.js +39 -0
- package/src/ui/shellTemplate.js +1 -1
- package/src/ui/templateUtils.js +23 -0
- package/AGENTS.md +0 -86
- package/src/standalone.js +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Promote PaddockJS to the first stable release for reusable browser embeds and headless JavaScript training/control workflows. The package now exposes the production simulator through `mountF1Simulator()`, composable mount helpers through `createPaddockSimulator()`, and a browser-free environment API through `@inventure71/paddockjs/environment`.
|
|
8
|
+
- Stabilize the 1.0 race-simulation feature set around pit-lane and pit-stop behavior, stewarded penalties, more realistic vehicle geometry and wheel-level surface handling, improved timing/DRS data, and lower-cost simulation/runtime paths.
|
|
9
|
+
|
|
10
|
+
### Race Simulation
|
|
11
|
+
|
|
12
|
+
- Add deterministic procedural tracks with pit-lane geometry, service areas, team queue slots, garage boxes, legal pit-lane surfaces, pit entry/exit routes, pit-lane open/closed state, and red-flag gating.
|
|
13
|
+
- Add automatic and host-controlled pit-stop intent, target tire compounds, service timing, penalty service before tire work, team queue behavior, pit-service countdown display, and optional pit-crew service variability.
|
|
14
|
+
- Add penalty ledger support for time, drive-through, stop-go, position-drop, grid-drop, and disqualification consequences, including service/cancel/final-classification conversion behavior.
|
|
15
|
+
- Enforce track limits from all four wheel contact patches, not car center position, and resolve each car's physics surface from the worst wheel surface.
|
|
16
|
+
- Add pit-lane speeding steward enforcement on speed-limited pit-lane parts while keeping pit-entry and pit-exit connector roads legal but not speed-limited.
|
|
17
|
+
- Use body-hull car collision geometry instead of transparent sprite bounds, with swept checks to prevent endpoint tunneling and contact metadata for callbacks/debugging.
|
|
18
|
+
- Fix lapped-traffic collision fault assignment so rear contact is judged by physical track order rather than cumulative race distance.
|
|
19
|
+
- Fix DRS detection so physically-ahead lapped traffic can be the DRS reference car.
|
|
20
|
+
- Improve race timing with fixed timing-line crossing history, interval and leader-gap modes, lap-gap labels for lapped cars, sector telemetry, provisional waved-flag status, and final classification after the full field finishes.
|
|
21
|
+
|
|
22
|
+
### Performance And Runtime
|
|
23
|
+
|
|
24
|
+
- Cache per-car geometry and wheel-surface state for repeated collision, surface, snapshot, and debug consumers.
|
|
25
|
+
- Reduce collision candidate work with circular-track distance-window pruning before swept AABB/SAT checks.
|
|
26
|
+
- Reduce headless/runtime cost by avoiding repeated full snapshot serialization during frame skips and by adding lean render/observation snapshot paths.
|
|
27
|
+
- Reduce observation cost by reusing ray-origin track state and broadphasing ray/car tests before exact footprint intersection.
|
|
28
|
+
- Reduce browser work by throttling noncritical DOM/timing refreshes at high playback speeds, reusing supplied snapshots for camera-control availability, caching stable pit-lane status graphics, pruning DRS trail histories in place, and skipping repeated text/markup writes.
|
|
29
|
+
|
|
30
|
+
### Public API And Docs
|
|
31
|
+
|
|
32
|
+
- Add and document the expert/headless environment API, action and observation specs, starter progress reward helper, rollout recorder/evaluation helpers, and worker-protocol wrapper.
|
|
33
|
+
- Add public controller methods for safety car, red flag, pit-lane open/closed state, pit intent/compound control, and penalty serve/cancel controls.
|
|
34
|
+
- Add the `ui.simulationSpeedControl` TypeScript option and document the browser playback speed control.
|
|
35
|
+
- Expand local preview and browser smoke coverage for templates, composable mounts, API controls, behavior, stewarding, collision lab, expert environment, and policy runner pages.
|
|
36
|
+
- Update package docs for 1.0 behavior, data contracts, architecture boundaries, rules, training/control usage, and installation/update workflow.
|
|
37
|
+
|
|
38
|
+
### Known Future Scope
|
|
39
|
+
|
|
40
|
+
- Weather effects, reliability failures, and fuel-load performance effects remain intentionally out of 1.0.0 and are tracked as future work.
|
|
41
|
+
|
|
42
|
+
## 0.3.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- eabe042: Prepare the major 0.3.0 feature release with published-package docs, runtime restart hardening, and the first expert environment API. Public install docs now target npm consumption, package dry-run contents exclude repo-agent files and unexported standalone code, lockfiles are synchronized to the package version, host driver/entry validation rejects duplicate IDs, invalid lap counts normalize to one-lap races, restart can rebuild deterministic tracks from a new `trackSeed`, asset URL changes are explicitly remount-only, and rerender paths destroy replaced PixiJS display children. The release also adds a browser-free JavaScript environment contract through the `@inventure71/paddockjs/environment` subpath and an opt-in browser expert wrapper that reuses the visual simulator's race state, including opt-in in-canvas ray sensor visualization for controlled expert drivers, center-origin geometry-based ray detection for track exit/re-entry transitions and car footprints, a starter `createProgressReward()` callback, and a dependency-free headless training/evaluation example. It also documents the bring-your-own-model boundary, adds environment action/observation specs, and includes a visual policy-runner example that demonstrates `policy.predict(observation)` driving browser expert mode without adding ML dependencies or model persistence.
|
|
47
|
+
|
|
3
48
|
## 0.2.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# PaddockJS Install and Update Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how host websites consume the published PaddockJS npm package.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install the package from npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @inventure71/paddockjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The host website imports PaddockJS by package name:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { mountF1Simulator } from '@inventure71/paddockjs';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Host applications should not copy simulator assets into their own source tree. PaddockJS owns its bundled car, safety-car, logo, panel, and track texture assets.
|
|
20
|
+
|
|
21
|
+
## Host Website Setup
|
|
22
|
+
|
|
23
|
+
Create a host entry file in the consuming website:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import {
|
|
27
|
+
CHAMPIONSHIP_ENTRY_BLUEPRINTS,
|
|
28
|
+
DEMO_PROJECT_DRIVERS,
|
|
29
|
+
mountF1Simulator,
|
|
30
|
+
} from '@inventure71/paddockjs';
|
|
31
|
+
|
|
32
|
+
const root = document.getElementById('f1-simulator-root');
|
|
33
|
+
|
|
34
|
+
if (root) {
|
|
35
|
+
await mountF1Simulator(root, {
|
|
36
|
+
drivers: DEMO_PROJECT_DRIVERS,
|
|
37
|
+
entries: CHAMPIONSHIP_ENTRY_BLUEPRINTS,
|
|
38
|
+
title: 'F1 Simulator Lab',
|
|
39
|
+
kicker: 'Race Control',
|
|
40
|
+
backLinkHref: '/projects',
|
|
41
|
+
backLinkLabel: 'Projects',
|
|
42
|
+
onDriverOpen(driver) {
|
|
43
|
+
if (driver.link) window.location.href = driver.link;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The host page needs a root element and the bundled JavaScript/CSS produced by the host build:
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<div id="f1-simulator-root"></div>
|
|
53
|
+
<script type="module" src="/dist/f1-simulator.js"></script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If the host bundler does not extract package CSS automatically, import the stylesheet explicitly:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
import '@inventure71/paddockjs/styles.css';
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Build Setup
|
|
63
|
+
|
|
64
|
+
Use a browser bundler that understands JavaScript module imports, CSS imports, and image imports. Vite, Rollup, Webpack, and similar bundlers are valid fits.
|
|
65
|
+
|
|
66
|
+
Use Node `20.19.0` or newer for local package development, showcase builds, and host builds based on the current Vite toolchain.
|
|
67
|
+
|
|
68
|
+
Example Vite entry config:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { defineConfig } from 'vite';
|
|
72
|
+
|
|
73
|
+
export default defineConfig({
|
|
74
|
+
build: {
|
|
75
|
+
outDir: 'dist',
|
|
76
|
+
rollupOptions: {
|
|
77
|
+
input: 'src/paddockjs-entry.js',
|
|
78
|
+
output: {
|
|
79
|
+
entryFileNames: 'f1-simulator.js',
|
|
80
|
+
chunkFileNames: 'chunks/[name]-[hash].js',
|
|
81
|
+
assetFileNames: 'assets/[name]-[hash][extname]',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Run the host's normal install and build checks:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install
|
|
92
|
+
npm run check
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Do not use raw Node as the main import test:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node -e "import('@inventure71/paddockjs')"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
That can fail because PaddockJS imports CSS and image assets. Verify through a browser-oriented host build instead.
|
|
102
|
+
|
|
103
|
+
## Updating The Package
|
|
104
|
+
|
|
105
|
+
Install the latest published version:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install @inventure71/paddockjs@latest
|
|
109
|
+
npm run check
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Install a specific version when the host needs a controlled upgrade:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install @inventure71/paddockjs@0.2.0
|
|
116
|
+
npm run check
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
After updating, smoke-test the page that mounts the simulator. Browser behavior changes should be checked in the consuming host, because host CSS, container size, and route handling are outside the package.
|
|
120
|
+
|
|
121
|
+
## Package Release Workflow
|
|
122
|
+
|
|
123
|
+
The package repo owns its release process:
|
|
124
|
+
|
|
125
|
+
- `npm run check` runs runtime tests, public type verification, dry-pack verification, packed-consumer install/build verification, the tracked showcase build, and Chromium browser smoke tests.
|
|
126
|
+
- `npm run consumer:smoke` packs the package, installs the tarball into a fresh temporary Vite app, and builds that app through public package imports.
|
|
127
|
+
- `npm run browser:smoke` builds `local-preview`, starts a local preview server, and checks desktop/mobile canvas rendering, overflow constraints, API buttons, and visual policy-runner stepping in Chromium.
|
|
128
|
+
- `npm run changeset` records the next version bump and changelog note.
|
|
129
|
+
- `npm run version-packages` applies pending Changesets locally.
|
|
130
|
+
- `.github/workflows/ci.yml` verifies the package on push and pull request.
|
|
131
|
+
- `.github/workflows/release.yml` opens a release PR from Changesets and publishes to npm after merge through npm trusted publishing.
|
|
132
|
+
|
|
133
|
+
For trusted publishing on npm, configure the package settings to trust:
|
|
134
|
+
|
|
135
|
+
- organization or user: `Inventure71`
|
|
136
|
+
- repository: `PaddockJS`
|
|
137
|
+
- workflow filename: `release.yml`
|
|
138
|
+
|
|
139
|
+
No long-lived `NPM_TOKEN` secret is required once trusted publishing is enabled.
|
|
140
|
+
|
|
141
|
+
The release PR must include synchronized `package.json`, `package-lock.json`, `CHANGELOG.md`, and Changesets output for the version being published.
|
|
142
|
+
|
|
143
|
+
## When Something Breaks
|
|
144
|
+
|
|
145
|
+
If the host cannot resolve the package, reinstall from npm:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm install @inventure71/paddockjs@latest
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If the host bundler cannot resolve an asset, verify the package contents from the package repo:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm run pack:dry
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Check that the asset is listed in the dry-pack output.
|
|
158
|
+
|
|
159
|
+
If simulator tests fail after a package change:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm test
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Fix the package first, then update and rebuild the host.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PaddockJS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
PaddockJS is an installable F1-style simulator component for browser host websites. It owns the simulator source, bundled simulator assets, CSS, demo data, and public mount API.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -10,19 +10,13 @@ From npm:
|
|
|
10
10
|
npm install @inventure71/paddockjs
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
For local sibling-repo development:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install ../PaddockJS
|
|
17
|
-
```
|
|
18
|
-
|
|
19
13
|
## Documentation
|
|
20
14
|
|
|
21
15
|
Start with [docs/index.md](docs/index.md) for system specs, rules, concepts, data contracts, and architecture notes.
|
|
22
16
|
|
|
23
17
|
## Package Workflow
|
|
24
18
|
|
|
25
|
-
The repo
|
|
19
|
+
The repo includes the full package-release boundary:
|
|
26
20
|
|
|
27
21
|
- a tracked showcase host in `local-preview/`
|
|
28
22
|
- public TypeScript declarations in `src/index.d.ts`
|
|
@@ -34,12 +28,94 @@ Useful commands:
|
|
|
34
28
|
|
|
35
29
|
```bash
|
|
36
30
|
npm run check
|
|
31
|
+
npm run consumer:smoke
|
|
32
|
+
npm run browser:smoke
|
|
37
33
|
npm run showcase:dev
|
|
38
34
|
npm run showcase:build
|
|
39
35
|
npm run changeset
|
|
40
36
|
```
|
|
41
37
|
|
|
42
|
-
`npm run check` verifies
|
|
38
|
+
`npm run check` verifies runtime tests, public declarations, dry package contents, packed-package consumption in a fresh Vite app, the showcase build, and real Chromium smoke tests against the showcase pages.
|
|
39
|
+
|
|
40
|
+
Local development and showcase builds require Node `20.19.0` or newer. CI currently runs the package check on Node 22 and releases on Node 24.
|
|
41
|
+
|
|
42
|
+
## Expert Environment API
|
|
43
|
+
|
|
44
|
+
Headless training code imports the environment subpath:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { createPaddockEnvironment } from '@inventure71/paddockjs/environment';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The package root remains the browser component API. The environment subpath is intentionally browser-free and does not import DOM, PixiJS, or package CSS.
|
|
51
|
+
PaddockJS is a bring-your-own-model environment. It does not choose an ML framework, store model weights, or ship a trained driver. See [Bring Your Own Model](docs/training.md) for the policy shape and visual playback loop.
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
const env = createPaddockEnvironment({
|
|
55
|
+
drivers,
|
|
56
|
+
entries,
|
|
57
|
+
controlledDrivers: ['budget'],
|
|
58
|
+
frameSkip: 2,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
let result = env.reset();
|
|
62
|
+
result = env.step({
|
|
63
|
+
budget: { steering: 0, throttle: 1, brake: 0 },
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The environment can run with no reward, as above, or with a host-supplied `reward(context)` callback. The repository also includes dependency-free examples that use the same environment contract:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node examples/train-basic-policy.mjs --generations=4 --candidates=5 --episodes=1 --steps=240
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The starter script imports the public `@inventure71/paddockjs/environment` subpath and uses self-contained example data from `examples/trainingData.mjs`, so it does not depend on private package source modules for demo drivers. `createProgressReward()` remains available as example/demo reward code only; it is not the official reward and not part of the environment objective.
|
|
74
|
+
|
|
75
|
+
Each ray reports track-transition distance and car distance. A track hit uses `kind: 'exit'` when the ray leaves the road and `kind: 'entry'` when an off-track ray points back to the road. `observation.object.self.onTrack` follows the simulator's wheel-level legality rules, so track, kerb, and legal pit-lane/box surfaces are on-track for reward and observation purposes. `pitIntent: 0` is always accepted as the no-op clear value, including environments where pit stops are disabled.
|
|
76
|
+
|
|
77
|
+
External training code can inspect the environment contract without guessing field ranges:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
const actionSpec = env.getActionSpec();
|
|
81
|
+
const observationSpec = env.getObservationSpec();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The environment also exposes reset-only scenario placement, neutral rollout recording, deterministic evaluation metrics, and a JSON-serializable worker protocol for external bridges:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const env = createPaddockEnvironment({
|
|
88
|
+
drivers,
|
|
89
|
+
entries,
|
|
90
|
+
controlledDrivers: ['budget'],
|
|
91
|
+
scenario: {
|
|
92
|
+
preset: 'off-track-recovery',
|
|
93
|
+
placements: {
|
|
94
|
+
budget: { distanceMeters: 420, offsetMeters: 16, speedKph: 65 },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Scenario placement is an environment reset feature, not a policy assist. During `step(actions)`, controlled cars still move only through normalized steering, throttle, brake, and pit intent.
|
|
101
|
+
|
|
102
|
+
Browser expert mode is opt-in through the normal mount API. When enabled, the visual simulator advances only when host code calls `simulator.expert.step(actions)`. Expert mode is a mount-time boundary; changing `expert` through `restart(nextOptions)` is rejected so ticker ownership cannot silently change under a mounted simulator.
|
|
103
|
+
Set `expert.visualizeSensors` to draw expert sensor rays inside the actual race canvas for visual debugging:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const simulator = await mountF1Simulator(root, {
|
|
107
|
+
drivers,
|
|
108
|
+
entries,
|
|
109
|
+
expert: {
|
|
110
|
+
enabled: true,
|
|
111
|
+
controlledDrivers: ['budget'],
|
|
112
|
+
frameSkip: 4,
|
|
113
|
+
visualizeSensors: {
|
|
114
|
+
rays: true,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
43
119
|
|
|
44
120
|
## API
|
|
45
121
|
|
|
@@ -68,12 +144,14 @@ const simulator = await mountF1Simulator(document.getElementById('sim-root'), {
|
|
|
68
144
|
vehicle: { id: 'budget-bb01', name: 'BB-01 Ledger', power: 48, braking: 72, aero: 55, dragEfficiency: 66, mechanicalGrip: 63, weightControl: 58, tireCare: 82 },
|
|
69
145
|
},
|
|
70
146
|
],
|
|
147
|
+
initialCameraMode: 'show-all',
|
|
148
|
+
expert: {
|
|
149
|
+
enabled: true,
|
|
150
|
+
controlledDrivers: ['budget'],
|
|
151
|
+
},
|
|
71
152
|
onDriverOpen(driver) {
|
|
72
153
|
window.location.href = driver.link;
|
|
73
154
|
},
|
|
74
|
-
onRaceFinish({ winner, classification }) {
|
|
75
|
-
console.log('Race winner:', winner.name, classification);
|
|
76
|
-
},
|
|
77
155
|
});
|
|
78
156
|
```
|
|
79
157
|
|
|
@@ -96,7 +174,6 @@ simulator.mountSafetyCarControl(document.getElementById('sim-safety-car'));
|
|
|
96
174
|
simulator.mountTimingTower(document.getElementById('sim-timing'));
|
|
97
175
|
simulator.mountRaceCanvas(document.getElementById('sim-race'), {
|
|
98
176
|
includeRaceDataPanel: true,
|
|
99
|
-
includeTelemetrySectorBanner: true,
|
|
100
177
|
});
|
|
101
178
|
simulator.mountTelemetryCore(document.getElementById('sim-telemetry-core'));
|
|
102
179
|
simulator.mountTelemetrySectors(document.getElementById('sim-telemetry-sectors'));
|
|
@@ -114,7 +191,6 @@ The race canvas can also own the timing tower when a host wants a single reusabl
|
|
|
114
191
|
simulator.mountRaceCanvas(document.getElementById('sim-race'), {
|
|
115
192
|
includeTimingTower: true,
|
|
116
193
|
includeRaceDataPanel: true,
|
|
117
|
-
includeTelemetrySectorBanner: true,
|
|
118
194
|
timingTowerVerticalFit: 'scroll',
|
|
119
195
|
});
|
|
120
196
|
```
|
|
@@ -124,21 +200,30 @@ For a packaged race-window template with a right-side telemetry drawer:
|
|
|
124
200
|
```js
|
|
125
201
|
simulator.mountRaceTelemetryDrawer(document.getElementById('sim-race-workbench'), {
|
|
126
202
|
timingTowerVerticalFit: 'expand-race-view',
|
|
203
|
+
raceDataTelemetryDetail: true,
|
|
127
204
|
});
|
|
128
205
|
```
|
|
129
206
|
|
|
130
|
-
`drivers` is the host-owned project/pilot list. `entries` is the optional driver/car/team pairing sheet. The car/driver overview uses the existing driver and vehicle rating components from each entry. Driver and vehicle entries can also include `customFields` as extra label/value metadata. Entries can include `team: { id, name, color, icon }`; the timing tower uses the team icon and defaults team color to the car color when omitted. Assets, including the default car image and generic driver helmet, are bundled by default, so the host website does not need to provide simulator images or textures.
|
|
207
|
+
`drivers` is the host-owned project/pilot list. `entries` is the optional driver/car/team pairing sheet. The car/driver overview uses the existing driver and vehicle rating components from each entry. Driver and vehicle entries can also include `customFields` as extra label/value metadata. Entries can include `team: { id, name, color, icon, pitCrew }`; the timing tower uses the team icon and defaults team color to the car color when omitted. `team.pitCrew` accepts `speed`, `consistency`, and `reliability` values from `0` to `1` for optional pit-service variability. Assets, including the default car image and generic driver helmet, are bundled by default, so the host website does not need to provide simulator images or textures.
|
|
131
208
|
|
|
132
209
|
The returned object supports:
|
|
133
210
|
|
|
134
211
|
- `destroy()`
|
|
135
|
-
- `restart(nextOptions)`
|
|
212
|
+
- `restart(nextOptions)` for non-asset, non-expert race/data/seed changes
|
|
136
213
|
- `selectDriver(driverId)`
|
|
137
214
|
- `setSafetyCarDeployed(deployed)`
|
|
215
|
+
- `setRedFlagDeployed(deployed)`
|
|
216
|
+
- `setPitLaneOpen(open)`
|
|
138
217
|
- `callSafetyCar()`
|
|
139
218
|
- `clearSafetyCar()`
|
|
140
219
|
- `toggleSafetyCar()`
|
|
220
|
+
- `setPitIntent(driverId, intent, targetCompound?)`
|
|
221
|
+
- `getPitIntent(driverId)`
|
|
222
|
+
- `getPitTargetCompound(driverId)`
|
|
223
|
+
- `servePenalty(penaltyId)`
|
|
224
|
+
- `cancelPenalty(penaltyId)`
|
|
141
225
|
- `getSnapshot()`
|
|
226
|
+
- `expert` when explicitly enabled, otherwise `null`
|
|
142
227
|
|
|
143
228
|
Useful UI options:
|
|
144
229
|
|
|
@@ -160,21 +245,133 @@ ui: {
|
|
|
160
245
|
enabled: ['project', 'radio'],
|
|
161
246
|
},
|
|
162
247
|
raceDataBannerSize: 'auto',
|
|
248
|
+
raceDataTelemetryDetail: true,
|
|
163
249
|
timingTowerVerticalFit: 'expand-race-view',
|
|
164
250
|
}
|
|
165
251
|
```
|
|
166
252
|
|
|
167
253
|
`preset` is resolved before explicit host options. Available presets are `dashboard`, `timing-overlay`, `compact-race`, and `full-dashboard`; hosts can start from a preset and override any `ui` or `theme` field. `theme` maps to package CSS variables for the stable sizing/color contract: `accentColor`, `greenColor`, `yellowColor`, `timingTowerMaxWidth`, and `raceViewMinHeight`.
|
|
168
254
|
|
|
169
|
-
If `trackSeed` is omitted, each mounted browser simulator creates a fresh procedural circuit. Passing `trackSeed` makes the track deterministic so multiple embeds can share the same generated circuit; repeated procedural seeds are cached within the page runtime.
|
|
255
|
+
If `trackSeed` is omitted, each mounted browser simulator creates a fresh procedural circuit. Passing `trackSeed` makes the track deterministic so multiple embeds can share the same generated circuit; repeated procedural seeds are cached within the page runtime. `restart({ trackSeed })` rebuilds the race on the deterministic circuit for the new seed. Asset URL changes are not restartable; destroy and mount a new simulator when changing assets.
|
|
256
|
+
|
|
257
|
+
Every generated track includes:
|
|
258
|
+
|
|
259
|
+
- rendered pit lane beside the start/finish straight
|
|
260
|
+
- lane-aligned procedural entry and exit roads
|
|
261
|
+
- main fast lane sized from the team/box layout
|
|
262
|
+
- parallel working lane
|
|
263
|
+
- 10 shared team service areas
|
|
264
|
+
- 20 unused garage boxes arranged as 10 team pairs
|
|
265
|
+
|
|
266
|
+
Pit-lane asphalt, working-lane service areas, and garage boxes are legal drivable surfaces for sensors, runoff handling, and track-limit stewarding. Tire energy degrades down to 1% and affects grip nonlinearly, so badly worn tires are slower and harder to rotate without making the car instantly undrivable.
|
|
267
|
+
|
|
268
|
+
When `rules.modules.pitStops.enabled` is true, cars automatically form bounded pit trains when lane space is available. They brake to the limiter by the main pit-lane start, drive along the main fast lane, pass through the team queue spot as a rolling gate, roll into the team-colored working-lane service area when it is clear, stop, serve eligible penalties before tire work, show the remaining stationary service time above the car, change to the requested configured tire compound or the default alternate compound, and exit back to the race track.
|
|
269
|
+
|
|
270
|
+
Team-mates share one service area. Every car enters through the queue spot first, but it only stops there when the active service spot is blocked. A second team car waits in the queue spot until the active service spot is physically clear, including the previous car's first movement out of the box, without blocking the main fast lane.
|
|
271
|
+
|
|
272
|
+
By default, built-in AI cars request an opportunistic `pitIntent: 1` below 50% tire energy and commit `pitIntent: 2` below 30%. Hosts can change those thresholds with `tirePitRequestThresholdPercent` and `tirePitCommitThresholdPercent`. Expert/headless controlled drivers do not receive those automatic tire-threshold calls and must request service with `pitIntent`.
|
|
273
|
+
|
|
274
|
+
Hosts can override the pit call with `setPitIntent(driverId, 0 | 1 | 2, 'H')` or expert action `{ pitIntent: 2, pitCompound: 'H' }`:
|
|
275
|
+
|
|
276
|
+
- `0` means no request
|
|
277
|
+
- `1` means keep trying until a free pit-entry window is available
|
|
278
|
+
- `2` means commit to entering at the next pit-entry window even when pit-lane capacity or gap checks would block an opportunistic stop
|
|
279
|
+
|
|
280
|
+
Completed stops can be re-armed later by tire condition or by `setPitIntent`, so pit stops are not one-use per race. Optional `pitStops.variability` uses `team.pitCrew` speed, consistency, and reliability to adjust service time and slow-stop chance; `pitStops.variability.perfect: true` forces deterministic default service time for training.
|
|
281
|
+
|
|
282
|
+
`setPitLaneOpen(false)` keeps new pending pit calls on track until reopened and the canvas shows a small red/green/yellow light near pit entry. `setRedFlagDeployed(true)` freezes race movement, closes the effective pit lane, and emits red-flag race-control state until cleared. The pit speed limiter applies on the straight main pit lane/working lane, not on the entry and exit connector roads, but automatic pit-entry routing still keeps connector speeds bounded so cars can reach the limiter safely.
|
|
283
|
+
|
|
284
|
+
Race behavior is configurable through `rules`. Flat existing options such as `standingStart: false` remain supported, and advanced behavior is grouped under module config:
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
const simulator = await mountF1Simulator(root, {
|
|
288
|
+
drivers,
|
|
289
|
+
rules: {
|
|
290
|
+
ruleset: 'fia2025',
|
|
291
|
+
modules: {
|
|
292
|
+
pitStops: {
|
|
293
|
+
enabled: true,
|
|
294
|
+
pitLaneSpeedLimitKph: 80,
|
|
295
|
+
maxConcurrentPitLaneCars: 3,
|
|
296
|
+
minimumPitLaneGapMeters: 20,
|
|
297
|
+
tirePitRequestThresholdPercent: 50,
|
|
298
|
+
tirePitCommitThresholdPercent: 30,
|
|
299
|
+
variability: {
|
|
300
|
+
enabled: true,
|
|
301
|
+
perfect: false,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
penalties: {
|
|
305
|
+
trackLimits: { strictness: 0.8 },
|
|
306
|
+
collision: { strictness: 0.5, consequences: [{ type: 'time', seconds: 5 }] },
|
|
307
|
+
tireRequirement: { strictness: 1, consequences: [{ type: 'time', seconds: 10 }] },
|
|
308
|
+
pitLaneSpeeding: { strictness: 1, speedLimitKph: 80 },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
ui: {
|
|
313
|
+
penaltyBanners: true,
|
|
314
|
+
timingPenaltyBadges: true,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Supported rulesets are `paddock`, `grandPrix2025`, `fia2025`, and `custom`. Presets only choose defaults; explicit module config wins. Penalty subsections use `strictness` from `0` to `1` instead of plain booleans. Track limits use the white line as the legal edge and require all four wheel contact patches to be fully outside the same side of the line before recording a violation, so normal kerb riding is not punished. Per-car `surface` is resolved from the worst wheel surface, snapshots include `car.wheels` for per-wheel surface and white-line state, and asymmetric left/right wheel resistance adds a small capped yaw tug toward the slower side when only one side is on a worse surface. Collision stewarding is driven by a body collision hull, not transparent sprite bounds or wheel-only overlap, and contact events include shape ids, contact type, depth, and time of impact. It considers impact severity, closing speed, and whether one car clearly hit another from behind; clear rear contact penalizes only the physically trailing car, including lapped traffic cases, while unclear meaningful contact records shared-fault penalties for both cars. Pit-lane speeding is enforced on the main fast lane, working lane, service areas, and garage boxes, but not on pit-entry or pit-exit connector roads. Track-limit warnings are emitted as `track-limits` events, while penalty decisions are exposed through `snapshot.penalties` plus `penalty` events. Penalty consequences support warning, time, drive-through, stop-go, position-drop, grid-drop, and disqualification payloads. Time consequences are additive, drive-through and stop-go penalties are service obligations, and unserved service penalties convert to configured time at final classification.
|
|
320
|
+
|
|
321
|
+
`initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, `'show-all'`, or `'pit'`; invalid values fall back to `'leader'`. The overview camera frames the active generated track bounds, including package-owned track padding and pit-lane extent, instead of using a fixed world-center zoom. Camera mode changes ease from the current camera target to the next target after the initial frame, so switching between leader, selected, overview, show-all, and pit views does not snap the world view.
|
|
322
|
+
|
|
323
|
+
The pit camera targets the generated operational pit-lane geometry, including the lane, boxes, service areas, and queue areas, while excluding the longer entry/exit access roads. It zooms out as needed to fit that work area inside the active race-view safe area, and its control is hidden when the active track has no pit lane.
|
|
324
|
+
|
|
325
|
+
Zoom buttons and wheel zoom apply to every camera mode. Zoom-out is bounded by the active generated track frame so the camera cannot pull far beyond the circuit. The renderer keeps grass coverage larger than the simulated world, and the transparent canvas host uses the same grass color as a fallback around the framed circuit.
|
|
326
|
+
|
|
327
|
+
`layoutPreset: 'left-tower-overlay'` creates a left broadcast gutter inside the race view, frames the camera around the remaining race area, and places the timing tower in that gutter without covering camera controls. The project/radio lower-third stays inside the race window and can intentionally render over the timing sidebar instead of shrinking around it.
|
|
328
|
+
|
|
329
|
+
Composable hosts should pass `{ includeRaceDataPanel: true }` to `mountRaceCanvas()` when they want that lower-third clipped and layered by the race window. `mountRaceDataPanel()` remains available for hosts that intentionally want the banner as a standalone surface.
|
|
330
|
+
|
|
331
|
+
`raceDataTelemetryDetail: true` makes the project lower-third include a compact S1/S2/S3 sector strip with live sector elapsed time and per-sector progress. That telemetry project lower-third stays visible until dismissed, muted, or replaced, while radio mode keeps its normal schedule. The standalone `mountTelemetrySectorBanner()` surface remains available only when a host explicitly mounts it.
|
|
332
|
+
|
|
333
|
+
`includeTimingTower: true` embeds the timing tower directly inside the race canvas and reserves camera space from the measured tower gutter when the tower is a side overlay. In narrow mobile hosts, package CSS stacks the embedded timing tower full-width and the camera stops reserving a fake left gutter.
|
|
334
|
+
|
|
335
|
+
`mountRaceTelemetryDrawer()` is a higher-level template that mounts an external top control row, race canvas, embedded timing tower, lower-third, top steward message, safety-car control, and a right telemetry drawer together. Pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact sector detail.
|
|
336
|
+
|
|
337
|
+
The drawer top row contains camera controls, a `1x` simulation-speed button that cycles through `2x`, `3x`, `4x`, `5x`, `10x`, and back to `1x`, a `Mute banners` toggle, safety car, and the telemetry toggle so those controls do not cover the race. For other camera-control placements the speed button is hidden by default and can be enabled with `ui.simulationSpeedControl: true`.
|
|
338
|
+
|
|
339
|
+
`Mute banners` is off by default and temporarily suppresses project/radio lower-thirds while it is pressed; steward penalty banners are separate and remain controlled by `penaltyBanners`. Opening the drawer reduces the race area instead of overlaying it, and closing it removes the drawer from interaction. The drawer uses the same package-owned `telemetry-stack` component as `mountTelemetryPanel()`, so constrained host heights scroll telemetry vertically instead of letting the sidebar escape its frame.
|
|
340
|
+
|
|
341
|
+
Banner and timing layout options:
|
|
342
|
+
|
|
343
|
+
- `raceDataBanners.initial` selects the starting banner state: `'project'`, `'radio'`, or `'hidden'`.
|
|
344
|
+
- `raceDataBanners.enabled` chooses which banner types may appear.
|
|
345
|
+
- `raceDataBannerSize: 'auto'` uses the race space to the right of the timing board when it is wide enough and falls back to the full lower-third overlap when it is not.
|
|
346
|
+
- `raceDataBannerSize: 'custom'` preserves the default CSS-variable-driven lower-third size for hosts that want to tune their own banner geometry.
|
|
347
|
+
- `timingTowerVerticalFit: 'expand-race-view'` lets the race window grow tall enough for the tower.
|
|
348
|
+
- `timingTowerVerticalFit: 'scroll'` keeps the race window height and scrolls the timing list inside the cropped tower.
|
|
349
|
+
|
|
350
|
+
The same timing fit values can be passed to `mountRaceCanvas()` when `includeTimingTower` is enabled. Standalone timing towers are capped by `--timing-board-max-width` and fill their mount root height; placing the root in a fixed-height container makes only the timing entries scroll. Timing entries always stack from the top as fixed rows, so P1/P2 occupy the same vertical positions whether the race has 2 cars or 20.
|
|
351
|
+
|
|
352
|
+
The timing tower includes an `Int`/`Gap` broadcast switch: `Int` shows the interval to the car ahead, while `Gap` shows total gap to the leader. Seconds gaps are measured from hidden fixed timing-line crossings on the track. If a car is one or more whole laps behind in the selected mode, the tower shows labels such as `+1` or `+2` instead of a seconds estimate.
|
|
353
|
+
|
|
354
|
+
Other UI switches:
|
|
355
|
+
|
|
356
|
+
- `cameraControls` defaults to `'external'`; set it to `'embedded'` only when controls should intentionally appear inside the canvas, or `false` to omit them.
|
|
357
|
+
- `penaltyBanners: true` shows warnings and penalties in the top steward message instead of the project/radio lower-third.
|
|
358
|
+
- `showFps: false` hides the FPS readout.
|
|
359
|
+
- `telemetryIncludesOverview: false` keeps the car/driver overview out of the telemetry stack so hosts can mount `mountCarDriverOverview()` separately.
|
|
360
|
+
- `telemetryModules` controls optional package-owned telemetry surfaces: `core`, `sectors`, `lapTimes`, and `sectorTimes`.
|
|
361
|
+
|
|
362
|
+
Project/radio lower-thirds include a package-owned top-right close button that hides the current pill before its scheduled timeout. Time penalties put a large `+10s` style chip in the left block of the steward message and the affected car/rule beside it. Warning messages use warning colors and do not create timing-row `!` badges. The telemetry surfaces are also available as fully detached mount methods.
|
|
363
|
+
|
|
364
|
+
### Composable overlay layout contract
|
|
365
|
+
|
|
366
|
+
`ui.layoutPreset: 'left-tower-overlay'` is package-owned. Hosts should not recreate or depend on internal shell classes such as `.sim-shell--left-tower-overlay` or `.sim-grid` as their public integration contract.
|
|
170
367
|
|
|
171
|
-
|
|
368
|
+
For the all-in-one simulator, call `mountF1Simulator()` and let PaddockJS generate the shell. For composable layouts, hosts should provide mount roots and call package mount methods such as `mountRaceCanvas(root, { includeTimingTower: true, includeRaceDataPanel: true })` or `mountRaceTelemetryDrawer(root)`. Host CSS may size the outer container, but timing-tower placement, gutter measurement, camera safe area, and narrow-screen stacking are owned by PaddockJS.
|
|
172
369
|
|
|
173
370
|
Mounted package surfaces include a lightweight red start-light loading overlay. The runtime removes each overlay after PixiJS, assets, controls, and the initial readouts are ready.
|
|
174
371
|
|
|
175
|
-
The runtime also pauses its render ticker when the race canvas is offscreen or the browser tab is hidden. This keeps pages with multiple PaddockJS embeds responsive without requiring host code to manually start and stop each simulator.
|
|
372
|
+
The runtime also pauses its render ticker when the race canvas is offscreen or the browser tab is hidden. This keeps pages with multiple PaddockJS embeds responsive without requiring host code to manually start and stop each simulator. At `5x` and `10x` browser playback, noncritical timing/readout DOM refreshes run at a lower cadence while fixed-step simulation and race events keep using authoritative race state.
|
|
176
373
|
|
|
177
|
-
Lifecycle callbacks are optional and host-owned. PaddockJS emits `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`; callback errors are routed to `onError` when provided and do not stop the simulator loop. Race snapshots include per-car interval timing, leader-gap timing, calibrated `speedKph`, automatic `track.sectors`, per-car `lapTelemetry`, finish state, `raceControl.winner` after the first finisher, and final `raceControl.classification` only after the whole field finishes. The field then circulates in safety-car mode and the race canvas shows a package-owned winner banner.
|
|
374
|
+
Lifecycle callbacks are optional and host-owned. PaddockJS emits `onLoadingChange`, `onReady`, `onError`, `onDriverSelect`, `onRaceEvent`, `onLapChange`, and `onRaceFinish`; callback errors are routed to `onError` when provided and do not stop the simulator loop. Race snapshots include per-car interval timing, leader-gap timing, whole-lap gap counts, calibrated `speedKph`, automatic `track.sectors`, hidden `track.timingLines`, per-car `lapTelemetry`, finish state, `raceControl.winner` after the first finisher, and final `raceControl.classification` only after the whole field finishes. Sector telemetry clears future-sector values so banners and sidebars show completed splits before the active sector plus the active live timer, not stale later-sector entries. Cars that have crossed the line before full race completion expose `raceStatus: 'waved-flag'` / `wavedFlag: true` and stay frozen in provisional finish order. Final classification applies time penalties and unserved service conversions through `adjustedFinishTime`, then applies position-drop and disqualification consequences. The field then circulates in safety-car mode and the race canvas shows a package-owned winner banner.
|
|
178
375
|
|
|
179
376
|
## License
|
|
180
377
|
|