@inventure71/paddockjs 0.1.0 → 0.3.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 +16 -0
- package/INSTALL_AND_UPDATE.md +163 -0
- package/README.md +96 -16
- package/docs/architecture.md +55 -11
- package/docs/concepts.md +5 -1
- package/docs/data_contract.md +324 -8
- package/docs/data_model.md +1 -0
- package/docs/index.md +1 -0
- package/docs/rules.md +2 -0
- package/docs/system_specs.md +66 -11
- package/docs/training.md +104 -0
- package/examples/train-basic-policy.mjs +277 -0
- package/package.json +14 -7
- package/src/api/PaddockSimulatorController.js +64 -3
- package/src/app/BrowserExpertAdapter.js +32 -0
- package/src/app/F1SimulatorApp.js +308 -38
- package/src/app/domBindings.js +35 -11
- package/src/config/defaultOptions.js +33 -0
- package/src/data/championship.js +13 -0
- package/src/data/normalizeDrivers.js +7 -2
- package/src/environment/actions.js +45 -0
- package/src/environment/episode.js +17 -0
- package/src/environment/events.js +17 -0
- package/src/environment/index.d.ts +241 -0
- package/src/environment/index.js +2 -0
- package/src/environment/observations.js +112 -0
- package/src/environment/options.js +150 -0
- package/src/environment/rewards.js +69 -0
- package/src/environment/runtime.js +140 -0
- package/src/environment/sensors.js +257 -0
- package/src/environment/specs.js +79 -0
- package/src/index.d.ts +97 -4
- package/src/index.js +12 -2
- package/src/rendering/proceduralTrackAsset.js +7 -1
- package/src/simulation/raceSimulation.js +214 -1
- package/src/simulation/trackModel.js +21 -0
- package/src/styles.css +495 -2
- package/src/ui/componentTemplates.js +201 -4
- package/AGENTS.md +0 -86
- package/src/standalone.js +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 846f388: Add detached telemetry surfaces, sector timing status colors, a broadcast sector banner, and a race telemetry drawer template with embedded timing tower, safety-car control, and lower-third race data.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 21cf4e5: Document the official release workflow and switch automated publishing to npm trusted publishing through GitHub Actions.
|
|
18
|
+
|
|
3
19
|
All notable changes to this project will be documented in this file.
|
|
4
20
|
|
|
5
21
|
The npm release workflow updates this changelog from committed Changesets.
|
|
@@ -0,0 +1,163 @@
|
|
|
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, and the tracked showcase build.
|
|
126
|
+
- `npm run changeset` records the next version bump and changelog note.
|
|
127
|
+
- `npm run version-packages` applies pending Changesets locally.
|
|
128
|
+
- `.github/workflows/ci.yml` verifies the package on push and pull request.
|
|
129
|
+
- `.github/workflows/release.yml` opens a release PR from Changesets and publishes to npm after merge through npm trusted publishing.
|
|
130
|
+
|
|
131
|
+
For trusted publishing on npm, configure the package settings to trust:
|
|
132
|
+
|
|
133
|
+
- organization or user: `Inventure71`
|
|
134
|
+
- repository: `PaddockJS`
|
|
135
|
+
- workflow filename: `release.yml`
|
|
136
|
+
|
|
137
|
+
No long-lived `NPM_TOKEN` secret is required once trusted publishing is enabled.
|
|
138
|
+
|
|
139
|
+
The release PR must include synchronized `package.json`, `package-lock.json`, `CHANGELOG.md`, and Changesets output for the version being published.
|
|
140
|
+
|
|
141
|
+
## When Something Breaks
|
|
142
|
+
|
|
143
|
+
If the host cannot resolve the package, reinstall from npm:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install @inventure71/paddockjs@latest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If the host bundler cannot resolve an asset, verify the package contents from the package repo:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm run pack:dry
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Check that the asset is listed in the dry-pack output.
|
|
156
|
+
|
|
157
|
+
If simulator tests fail after a package change:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm test
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
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,24 +10,19 @@ 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`
|
|
29
23
|
- GitHub Actions CI in `.github/workflows/ci.yml`
|
|
30
24
|
- Changesets release automation in `.github/workflows/release.yml`
|
|
25
|
+
- npm trusted publishing support through GitHub Actions OIDC
|
|
31
26
|
|
|
32
27
|
Useful commands:
|
|
33
28
|
|
|
@@ -40,6 +35,67 @@ npm run changeset
|
|
|
40
35
|
|
|
41
36
|
`npm run check` verifies the runtime tests, public declaration file, dry package contents, and showcase build.
|
|
42
37
|
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
## Expert Environment API
|
|
41
|
+
|
|
42
|
+
Headless training code imports the environment subpath:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import { createPaddockEnvironment, createProgressReward } from '@inventure71/paddockjs/environment';
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The package root remains the browser component API. The environment subpath is intentionally browser-free and does not import DOM, PixiJS, or package CSS.
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
const env = createPaddockEnvironment({
|
|
53
|
+
drivers,
|
|
54
|
+
entries,
|
|
55
|
+
controlledDrivers: ['budget'],
|
|
56
|
+
frameSkip: 2,
|
|
57
|
+
reward: createProgressReward(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let result = env.reset();
|
|
61
|
+
result = env.step({
|
|
62
|
+
budget: { steering: 0, throttle: 1, brake: 0 },
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The repository includes a dependency-free starter training loop that uses the same environment contract:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
node examples/train-basic-policy.mjs --generations=4 --candidates=5 --episodes=1 --steps=240
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
External training code can inspect the environment contract without guessing field ranges:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const actionSpec = env.getActionSpec();
|
|
78
|
+
const observationSpec = env.getObservationSpec();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
Set `expert.visualizeSensors` to draw expert sensor rays inside the actual race canvas for visual debugging:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const simulator = await mountF1Simulator(root, {
|
|
86
|
+
drivers,
|
|
87
|
+
entries,
|
|
88
|
+
expert: {
|
|
89
|
+
enabled: true,
|
|
90
|
+
controlledDrivers: ['budget'],
|
|
91
|
+
frameSkip: 4,
|
|
92
|
+
visualizeSensors: {
|
|
93
|
+
rays: true,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
43
99
|
## API
|
|
44
100
|
|
|
45
101
|
All-in-one mount:
|
|
@@ -67,12 +123,14 @@ const simulator = await mountF1Simulator(document.getElementById('sim-root'), {
|
|
|
67
123
|
vehicle: { id: 'budget-bb01', name: 'BB-01 Ledger', power: 48, braking: 72, aero: 55, dragEfficiency: 66, mechanicalGrip: 63, weightControl: 58, tireCare: 82 },
|
|
68
124
|
},
|
|
69
125
|
],
|
|
126
|
+
initialCameraMode: 'show-all',
|
|
127
|
+
expert: {
|
|
128
|
+
enabled: true,
|
|
129
|
+
controlledDrivers: ['budget'],
|
|
130
|
+
},
|
|
70
131
|
onDriverOpen(driver) {
|
|
71
132
|
window.location.href = driver.link;
|
|
72
133
|
},
|
|
73
|
-
onRaceFinish({ winner, classification }) {
|
|
74
|
-
console.log('Race winner:', winner.name, classification);
|
|
75
|
-
},
|
|
76
134
|
});
|
|
77
135
|
```
|
|
78
136
|
|
|
@@ -96,7 +154,11 @@ simulator.mountTimingTower(document.getElementById('sim-timing'));
|
|
|
96
154
|
simulator.mountRaceCanvas(document.getElementById('sim-race'), {
|
|
97
155
|
includeRaceDataPanel: true,
|
|
98
156
|
});
|
|
99
|
-
simulator.
|
|
157
|
+
simulator.mountTelemetryCore(document.getElementById('sim-telemetry-core'));
|
|
158
|
+
simulator.mountTelemetrySectors(document.getElementById('sim-telemetry-sectors'));
|
|
159
|
+
simulator.mountTelemetrySectorBanner(document.getElementById('sim-telemetry-sector-banner'));
|
|
160
|
+
simulator.mountTelemetryLapTimes(document.getElementById('sim-telemetry-laps'));
|
|
161
|
+
simulator.mountTelemetrySectorTimes(document.getElementById('sim-telemetry-sector-times'));
|
|
100
162
|
simulator.mountCarDriverOverview(document.getElementById('sim-overview'));
|
|
101
163
|
|
|
102
164
|
await simulator.start();
|
|
@@ -112,18 +174,28 @@ simulator.mountRaceCanvas(document.getElementById('sim-race'), {
|
|
|
112
174
|
});
|
|
113
175
|
```
|
|
114
176
|
|
|
177
|
+
For a packaged race-window template with a right-side telemetry drawer:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
simulator.mountRaceTelemetryDrawer(document.getElementById('sim-race-workbench'), {
|
|
181
|
+
timingTowerVerticalFit: 'expand-race-view',
|
|
182
|
+
raceDataTelemetryDetail: true,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
115
186
|
`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.
|
|
116
187
|
|
|
117
188
|
The returned object supports:
|
|
118
189
|
|
|
119
190
|
- `destroy()`
|
|
120
|
-
- `restart(nextOptions)`
|
|
191
|
+
- `restart(nextOptions)` for non-asset, non-expert race/data/seed changes
|
|
121
192
|
- `selectDriver(driverId)`
|
|
122
193
|
- `setSafetyCarDeployed(deployed)`
|
|
123
194
|
- `callSafetyCar()`
|
|
124
195
|
- `clearSafetyCar()`
|
|
125
196
|
- `toggleSafetyCar()`
|
|
126
197
|
- `getSnapshot()`
|
|
198
|
+
- `expert` when explicitly enabled, otherwise `null`
|
|
127
199
|
|
|
128
200
|
Useful UI options:
|
|
129
201
|
|
|
@@ -139,26 +211,34 @@ ui: {
|
|
|
139
211
|
cameraControls: 'external',
|
|
140
212
|
showFps: false,
|
|
141
213
|
telemetryIncludesOverview: false,
|
|
214
|
+
telemetryModules: ['core', 'sectors', 'lapTimes', 'sectorTimes'],
|
|
142
215
|
raceDataBanners: {
|
|
143
216
|
initial: 'project',
|
|
144
217
|
enabled: ['project', 'radio'],
|
|
145
218
|
},
|
|
146
219
|
raceDataBannerSize: 'auto',
|
|
220
|
+
raceDataTelemetryDetail: true,
|
|
147
221
|
timingTowerVerticalFit: 'expand-race-view',
|
|
148
222
|
}
|
|
149
223
|
```
|
|
150
224
|
|
|
151
225
|
`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`.
|
|
152
226
|
|
|
153
|
-
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.
|
|
227
|
+
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.
|
|
228
|
+
|
|
229
|
+
`initialCameraMode` accepts `'overview'`, `'leader'`, `'selected'`, or `'show-all'`; invalid values fall back to `'leader'`. `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. 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. `raceDataTelemetryDetail: true` makes the project lower-third include a compact S1/S2/S3 sector strip; radio mode stays the same and the standalone `mountTelemetrySectorBanner()` surface remains available only when a host explicitly mounts it. `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. `mountRaceTelemetryDrawer()` is a higher-level template that mounts the race canvas, embedded timing tower, lower-third, safety-car control, and a right telemetry drawer together; pass `{ raceDataTelemetryDetail: true }` when the drawer lower-third should include compact sector detail. Opening the drawer reduces the race area instead of overlaying it, and closing it removes the drawer from interaction. `raceDataBanners.initial` selects the starting banner state (`'project'`, `'radio'`, or `'hidden'`), while `raceDataBanners.enabled` chooses which banner types may appear. `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; `'custom'` preserves the default CSS-variable-driven lower-third size for hosts that want to tune their own banner geometry. `timingTowerVerticalFit: 'expand-race-view'` lets the race window grow tall enough for the tower; `'scroll'` keeps the race window height and scrolls the timing list inside the cropped tower. The same 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. 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. `cameraControls: 'external'` moves view controls out of the canvas so hosts can mount them with `mountCameraControls()`. `showFps: false` hides the FPS readout. `telemetryIncludesOverview: false` keeps the car/driver overview out of the telemetry stack so hosts can mount `mountCarDriverOverview()` separately. `telemetryModules` controls optional package-owned telemetry surfaces: `core`, `sectors`, `lapTimes`, and `sectorTimes`; those same surfaces are also available as fully detached mount methods.
|
|
230
|
+
|
|
231
|
+
### Composable overlay layout contract
|
|
232
|
+
|
|
233
|
+
`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.
|
|
154
234
|
|
|
155
|
-
|
|
235
|
+
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.
|
|
156
236
|
|
|
157
237
|
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.
|
|
158
238
|
|
|
159
239
|
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.
|
|
160
240
|
|
|
161
|
-
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`, 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.
|
|
241
|
+
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.
|
|
162
242
|
|
|
163
243
|
## License
|
|
164
244
|
|
package/docs/architecture.md
CHANGED
|
@@ -48,13 +48,36 @@ host page
|
|
|
48
48
|
-> simulator.mountSafetyCarControl(safetyCarRoot)
|
|
49
49
|
-> simulator.mountTimingTower(timingRoot)
|
|
50
50
|
-> simulator.mountRaceCanvas(canvasRoot, { includeRaceDataPanel })
|
|
51
|
-
-> simulator.
|
|
51
|
+
-> simulator.mountTelemetryCore(coreTelemetryRoot)
|
|
52
|
+
-> simulator.mountTelemetrySectors(sectorGraphRoot)
|
|
53
|
+
-> simulator.mountTelemetrySectorBanner(sectorBannerRoot)
|
|
54
|
+
-> simulator.mountTelemetryLapTimes(lapTimesRoot)
|
|
55
|
+
-> simulator.mountTelemetrySectorTimes(sectorTimesRoot)
|
|
56
|
+
-> simulator.mountRaceTelemetryDrawer(raceWorkbenchRoot) [optional template alternative]
|
|
52
57
|
-> simulator.mountRaceDataPanel(raceDataRoot) [optional standalone alternative]
|
|
53
58
|
-> simulator.start()
|
|
54
59
|
-> new F1SimulatorApp(compositeRoot, resolvedOptions)
|
|
55
60
|
-> PixiJS render loop + DOM readout updates
|
|
56
61
|
```
|
|
57
62
|
|
|
63
|
+
Headless environment flow:
|
|
64
|
+
|
|
65
|
+
```txt
|
|
66
|
+
training script
|
|
67
|
+
-> import from @inventure71/paddockjs/environment
|
|
68
|
+
-> createPaddockEnvironment(options)
|
|
69
|
+
-> resolveEnvironmentOptions(options)
|
|
70
|
+
-> createRaceSimulation(...)
|
|
71
|
+
-> env.reset()
|
|
72
|
+
-> env.step(actions)
|
|
73
|
+
-> resolveActionMap(actions)
|
|
74
|
+
-> RaceSimulation.step(...) repeated by frameSkip
|
|
75
|
+
-> buildEnvironmentObservation(...)
|
|
76
|
+
-> optional reward(context)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Browser expert mode reuses the same environment runtime around the visual app's existing `RaceSimulation` instance. When `expert.enabled` is true, the visual ticker does not advance simulation time automatically; `simulator.expert.reset()` and `simulator.expert.step(actions)` are the only simulation-advance entry points.
|
|
80
|
+
|
|
58
81
|
## Public Entry
|
|
59
82
|
|
|
60
83
|
`src/index.js` is the public package entry.
|
|
@@ -70,6 +93,18 @@ Responsibilities:
|
|
|
70
93
|
- Return the controller methods.
|
|
71
94
|
- Re-export the composable mount API.
|
|
72
95
|
|
|
96
|
+
`src/index.js` does not export the headless environment API. Training and automation code imports `@inventure71/paddockjs/environment`, which resolves to `src/environment/index.js`.
|
|
97
|
+
|
|
98
|
+
`src/environment/` owns the browser-free expert environment:
|
|
99
|
+
|
|
100
|
+
- `index.js` and `index.d.ts` expose the public subpath API.
|
|
101
|
+
- `runtime.js` owns environment-loop `reset()`, `step(actions)`, `getObservation()`, and `getState()` around an injected race-simulation host.
|
|
102
|
+
- `options.js` validates `controlledDrivers`, scenario participants, frame skip, action policy, sensor config, and episode limits.
|
|
103
|
+
- `actions.js` maps normalized public controls onto simulator steering/throttle/brake controls.
|
|
104
|
+
- `observations.js`, `sensors.js`, and `events.js` build sensor-style observations, fixed-schema vectors, ray/nearby-car readings, and global/per-driver events.
|
|
105
|
+
|
|
106
|
+
The environment subpath must not import `src/index.js`, package CSS, PixiJS, `F1SimulatorApp`, or DOM-specific code.
|
|
107
|
+
|
|
73
108
|
`src/api/PaddockSimulatorController.js` owns composable mounting.
|
|
74
109
|
|
|
75
110
|
Responsibilities:
|
|
@@ -99,8 +134,12 @@ Responsibilities:
|
|
|
99
134
|
- Race data panel rendering.
|
|
100
135
|
- Safety car button.
|
|
101
136
|
- Multiple package-rendered safety-car buttons bound to one simulation state.
|
|
102
|
-
- Restart behavior.
|
|
103
|
-
- Lifecycle cleanup.
|
|
137
|
+
- Restart behavior for race data, seed, and track changes. Asset URL changes and browser expert mode changes are intentionally outside restart because texture loading and ticker ownership are part of initialization.
|
|
138
|
+
- Lifecycle cleanup, including partial-init failure cleanup and destruction of replaced PixiJS display children without destroying shared textures.
|
|
139
|
+
|
|
140
|
+
When `options.expert.enabled` is true, `F1SimulatorApp` creates a narrow browser expert adapter around its existing `this.sim` race simulation. The adapter uses the same shared environment runtime as the headless API, but it must not create a second race simulation for the visual mount. Expert browser mounts disable automatic ticker-driven simulation advancement; the canvas updates only after `simulator.expert.reset()` or `simulator.expert.step(actions)` renders the new snapshot. Expert mode is a mount-time boundary; runtime restart rejects `expert` changes instead of attempting to rewire ticker ownership in place.
|
|
141
|
+
|
|
142
|
+
Expert sensor visualization belongs to the browser app layer, not the headless environment. The environment result owns the observation contract; `BrowserExpertAdapter` passes that observation into `F1SimulatorApp.renderExpertFrame()`, and `F1SimulatorApp` draws opt-in sensor rays in a Pixi world layer so they share the same camera transform as the track and cars.
|
|
104
143
|
|
|
105
144
|
This file is still large. When changing it substantially, prefer extracting cohesive modules rather than adding unrelated responsibilities.
|
|
106
145
|
|
|
@@ -118,6 +157,7 @@ Responsibilities:
|
|
|
118
157
|
- Car creation.
|
|
119
158
|
- Race ordering.
|
|
120
159
|
- Lap calculation.
|
|
160
|
+
- Per-car lap and sector telemetry.
|
|
121
161
|
- Timing history.
|
|
122
162
|
- DRS eligibility.
|
|
123
163
|
- Collision response.
|
|
@@ -127,13 +167,13 @@ Responsibilities:
|
|
|
127
167
|
|
|
128
168
|
`src/simulation/vehiclePhysics.js` owns vehicle integration and surface physics.
|
|
129
169
|
|
|
130
|
-
`src/simulation/trackModel.js` owns track construction, procedural track generation, DRS zones, and nearest-track queries. Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; explicit procedural track seeds are cached so repeated mounts with the same seed do not regenerate or rebuild the same model.
|
|
170
|
+
`src/simulation/trackModel.js` owns track construction, procedural track generation, automatic three-sector metadata, DRS zones, and nearest-track queries. Browser mounts generate a fresh procedural seed when `trackSeed` is omitted; explicit procedural track seeds are cached so repeated mounts with the same seed do not regenerate or rebuild the same model.
|
|
131
171
|
|
|
132
172
|
`src/simulation/units.js` owns conversion between simulator units and public meter/km/h display values. Physics stays in simulator units; snapshots expose calibrated display fields such as `speedKph`, `distanceMeters`, and `gapMeters`.
|
|
133
173
|
|
|
134
174
|
`src/rendering/renderSnapshot.js` owns interpolation for rendering.
|
|
135
175
|
|
|
136
|
-
The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
|
|
176
|
+
The app runtime pauses its PixiJS ticker when the race canvas is outside the viewport or the document is hidden, then resets the frame clock before resuming. This prevents host pages with several simulator embeds from running every race while only one is visible, and avoids a large simulation catch-up step when the canvas re-enters view. Camera safe-area layout measurements are cached and invalidated by resize observation so the 60 FPS render path does not force repeated DOM geometry reads. Runtime readouts avoid redundant DOM writes for unchanged text, timing markup, finish classification, and static selected-car overview data. Long project-radio delays are also treated as stale schedule state instead of replaying every missed lower-third transition.
|
|
137
177
|
|
|
138
178
|
## Data Layer
|
|
139
179
|
|
|
@@ -141,9 +181,9 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
|
|
|
141
181
|
|
|
142
182
|
`src/data/vehicleData.js` converts vehicle rating sheets into physical setup values.
|
|
143
183
|
|
|
144
|
-
`src/data/championship.js` pairs drivers with entries and generates timing codes, numbers, team metadata, and converted constructor data.
|
|
184
|
+
`src/data/championship.js` pairs drivers with entries and generates timing codes, numbers, team metadata, and converted constructor data. It rejects duplicate entry driver IDs, ignores omitted driver numbers during uniqueness checks, and falls back to stable grid-order numbers when host entries do not provide numbers.
|
|
145
185
|
|
|
146
|
-
`src/data/normalizeDrivers.js` validates host driver data and invokes championship pairing.
|
|
186
|
+
`src/data/normalizeDrivers.js` validates host driver data, rejects duplicate driver IDs before runtime maps are created, and invokes championship pairing.
|
|
147
187
|
|
|
148
188
|
`src/data/demoDrivers.js` is demo/portfolio-flavored sample data. It should not become the only supported data path.
|
|
149
189
|
|
|
@@ -156,13 +196,16 @@ The app runtime pauses its PixiJS ticker when the race canvas is outside the vie
|
|
|
156
196
|
- Safety-car control.
|
|
157
197
|
- Timing tower.
|
|
158
198
|
- Race canvas.
|
|
159
|
-
- Race-data panel.
|
|
160
|
-
- Telemetry
|
|
199
|
+
- Race-data panel, including the optional project telemetry detail variant.
|
|
200
|
+
- Telemetry stack template.
|
|
201
|
+
- Detached telemetry core, sector graph, lap-time table, and sector-time table.
|
|
202
|
+
- Broadcast sector banner.
|
|
203
|
+
- Race telemetry drawer template.
|
|
161
204
|
- Car/driver overview panel.
|
|
162
205
|
|
|
163
|
-
`src/ui/shellTemplate.js` composes those component templates into the default all-in-one simulator DOM and package-owned layout presets such as `left-tower-overlay`. The telemetry
|
|
206
|
+
`src/ui/shellTemplate.js` composes those component templates into the default all-in-one simulator DOM and package-owned layout presets such as `left-tower-overlay`. Telemetry graph/table surfaces are independent package-owned components controlled by `ui.telemetryModules` when used through stack/drawer templates; the app renders them from `car.lapTelemetry` without requiring host-owned DOM. The telemetry stack can embed the car/driver overview, but the overview is also a separately mountable package-owned component. The broadcast sector banner is a separate lower-third-style sector graph that can be mounted standalone or embedded inside the race canvas only when a host explicitly asks for that independent popup; `F1SimulatorApp` binds the selected car name/code/color to it through the same readout path as the other telemetry surfaces. The race telemetry drawer template composes a race canvas with embedded timing tower, project/radio lower-third, safety-car control, and detached telemetry components in a right drawer. Its open/close state is owned by `F1SimulatorApp`; the safety-car control remains available while telemetry is open. The drawer reserves final race-view space with a stable margin while the sidebar itself animates with a compositor transform, avoiding grid-template reflow during the slide. The drawer race area inherits the workbench height so host embeds do not leave unused black space below the simulation. The left-tower overlay preset is responsible for internal component placement and package-owned proportions; the project/radio lower-third remains owned by the race canvas so it can either use the race space beside the timing tower in `auto` sizing mode or overlap the tower when space is constrained. Composable hosts can also ask the race-canvas template to embed the timing tower directly with `includeTimingTower`, using the same expand-vs-scroll vertical fit contract. `F1SimulatorApp` measures the resulting timing-tower gutter when framing the PixiJS camera, whether the tower comes from the prebuilt shell or from an embedded composable race canvas. On narrow hosts, CSS stacks timing boards full-width and the camera safe-area measurement treats those boards as stacked content instead of side gutters. Hosts should not provide the internal simulator markup or tune preset internals with raw sizing options.
|
|
164
207
|
|
|
165
|
-
`src/config/defaultOptions.js` owns preset resolution and the public theme contract. Presets are merged before host options; theme fields are applied as package CSS variables by the all-in-one app and composable controller. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
|
|
208
|
+
`src/config/defaultOptions.js` owns preset resolution, telemetry module normalization, and the public theme contract. Presets are merged before host options; theme fields are applied as package CSS variables by the all-in-one app and composable controller. This keeps sizing/color customization explicit without turning internal layout ratios into public API.
|
|
166
209
|
|
|
167
210
|
Race completion is owned by `src/simulation/raceSimulation.js`. The app layer reads `raceControl.finished`, `winner`, and `classification` from snapshots, renders the winner banner, and emits lifecycle callbacks. UI code must not infer finish state from lap text or timing rows.
|
|
168
211
|
|
|
@@ -191,6 +234,7 @@ Current coverage includes:
|
|
|
191
234
|
- Championship metadata.
|
|
192
235
|
- Component API and callback behavior.
|
|
193
236
|
- Driver controller behavior.
|
|
237
|
+
- Expert environment actions, observations, rewards, events, and browser adapter behavior.
|
|
194
238
|
- Procedural track rendering helpers.
|
|
195
239
|
- Race simulation rules.
|
|
196
240
|
- Render interpolation.
|
package/docs/concepts.md
CHANGED
|
@@ -131,6 +131,10 @@ Progress is the wrapped distance around the current lap.
|
|
|
131
131
|
|
|
132
132
|
Race distance is cumulative and can increase beyond one lap. Ranking uses race distance, not wrapped progress.
|
|
133
133
|
|
|
134
|
+
## Sectors
|
|
135
|
+
|
|
136
|
+
Every built track is divided into three equal sectors, published as `track.sectors`. Sector timing is derived from cumulative race distance crossing those sector boundaries, not from UI state.
|
|
137
|
+
|
|
134
138
|
## Snapshot
|
|
135
139
|
|
|
136
140
|
A snapshot is the read-only state returned by the race simulation and exposed through `getSnapshot()`.
|
|
@@ -146,7 +150,7 @@ Snapshots include:
|
|
|
146
150
|
- Events from the last step.
|
|
147
151
|
- Ordered cars with telemetry and setup data.
|
|
148
152
|
|
|
149
|
-
Per-car timing exposes both interval to the car ahead and cumulative gap to the leader. Per-car speed and distance display fields are calibrated through the simulator unit conversion helpers instead of treating rendered world units as meters.
|
|
153
|
+
Per-car timing exposes both interval to the car ahead and cumulative gap to the leader. Per-car `lapTelemetry` exposes current lap, current sector, current/last/best lap times, current/last/best sector times, sector progress, and sector performance status. Sector performance status marks completed sector times as `overall-best`, `personal-best`, or `slower`, which drives the purple/green/yellow timing colors in sector graphs and tables. Per-car speed and distance display fields are calibrated through the simulator unit conversion helpers instead of treating rendered world units as meters.
|
|
150
154
|
|
|
151
155
|
## Render Snapshot
|
|
152
156
|
|