@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createLoadingMarkup } from './templateUtils.js';
|
|
2
|
+
|
|
3
|
+
export function createStewardMessageMarkup() {
|
|
4
|
+
return `
|
|
5
|
+
<div class="steward-message is-hidden" data-paddock-component="steward-message" data-steward-message aria-live="polite">
|
|
6
|
+
<span class="steward-message__kicker" data-steward-message-kicker>Race control</span>
|
|
7
|
+
<strong data-steward-message-title>--</strong>
|
|
8
|
+
<span data-steward-message-detail>--</span>
|
|
9
|
+
</div>
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createRaceDataPanelMarkup({ ui = {} } = {}) {
|
|
14
|
+
const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
|
|
15
|
+
const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
|
|
16
|
+
const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
|
|
17
|
+
if (telemetryDetail) classNames.push('race-data-panel--with-telemetry');
|
|
18
|
+
return `
|
|
19
|
+
<div class="${classNames.join(' ')}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
|
|
20
|
+
<button class="race-data-dismiss" type="button" data-race-data-dismiss aria-label="Close race data pill">x</button>
|
|
21
|
+
<div class="race-data-copy">
|
|
22
|
+
<span class="race-data-kicker" data-race-data-kicker>Project</span>
|
|
23
|
+
<strong data-race-data-title>Select driver</strong>
|
|
24
|
+
<span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
|
|
25
|
+
</div>
|
|
26
|
+
${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
|
|
27
|
+
<strong class="race-data-number" data-race-data-number>--</strong>
|
|
28
|
+
<button class="race-data-link" type="button" data-race-data-open>Open project</button>
|
|
29
|
+
${createLoadingMarkup('Race data')}
|
|
30
|
+
</div>
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createRaceDataTelemetryMarkup() {
|
|
35
|
+
return `
|
|
36
|
+
<div class="race-data-telemetry" data-race-data-telemetry aria-label="Project sector telemetry">
|
|
37
|
+
<span class="race-data-telemetry__label">Sectors</span>
|
|
38
|
+
<div class="race-data-telemetry__bars">
|
|
39
|
+
${[1, 2, 3].map((sector) => `
|
|
40
|
+
<div class="telemetry-sector-bar race-data-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
41
|
+
<span>S${sector}</span>
|
|
42
|
+
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
43
|
+
</div>
|
|
44
|
+
`).join('')}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createTelemetrySectorBannerMarkup() {
|
|
51
|
+
return `
|
|
52
|
+
<section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
|
|
53
|
+
<div class="telemetry-sector-banner__copy">
|
|
54
|
+
<span><b data-selected-code>--</b> sector telemetry</span>
|
|
55
|
+
<strong data-selected-name>Select driver</strong>
|
|
56
|
+
<em data-telemetry-current-sector>S1</em>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="telemetry-sector-banner__bars">
|
|
59
|
+
${[1, 2, 3].map((sector) => `
|
|
60
|
+
<div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
61
|
+
<span>S${sector}</span>
|
|
62
|
+
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
63
|
+
</div>
|
|
64
|
+
`).join('')}
|
|
65
|
+
</div>
|
|
66
|
+
${createLoadingMarkup('Sector banner')}
|
|
67
|
+
</section>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRaceDataPanelMarkup,
|
|
3
|
+
createStewardMessageMarkup,
|
|
4
|
+
createTelemetrySectorBannerMarkup,
|
|
5
|
+
} from './bannerTemplates.js';
|
|
6
|
+
import { createRaceControlStatusBannerMarkup } from './raceControlStatusBanner.js';
|
|
7
|
+
import { createLoadingMarkup, escapeHtml } from './templateUtils.js';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
createRaceDataPanelMarkup,
|
|
11
|
+
createStewardMessageMarkup,
|
|
12
|
+
createTelemetrySectorBannerMarkup,
|
|
13
|
+
} from './bannerTemplates.js';
|
|
14
|
+
export { escapeHtml } from './templateUtils.js';
|
|
15
|
+
|
|
1
16
|
function buttonHiddenAttribute(isVisible) {
|
|
2
17
|
return isVisible ? '' : ' hidden';
|
|
3
18
|
}
|
|
@@ -9,30 +24,6 @@ function createTelemetryDrawerId() {
|
|
|
9
24
|
return `paddock-telemetry-drawer-${telemetryDrawerIdSequence}`;
|
|
10
25
|
}
|
|
11
26
|
|
|
12
|
-
function createLoadingMarkup(label = 'Loading') {
|
|
13
|
-
return `
|
|
14
|
-
<div class="paddock-loading" data-paddock-loading aria-label="${escapeHtml(label)} loading">
|
|
15
|
-
<div class="paddock-loading__lights" aria-hidden="true">
|
|
16
|
-
<span></span>
|
|
17
|
-
<span></span>
|
|
18
|
-
<span></span>
|
|
19
|
-
<span></span>
|
|
20
|
-
<span></span>
|
|
21
|
-
</div>
|
|
22
|
-
<span class="paddock-loading__label">${escapeHtml(label)}</span>
|
|
23
|
-
</div>
|
|
24
|
-
`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function escapeHtml(value) {
|
|
28
|
-
return String(value ?? '')
|
|
29
|
-
.replaceAll('&', '&')
|
|
30
|
-
.replaceAll('<', '<')
|
|
31
|
-
.replaceAll('>', '>')
|
|
32
|
-
.replaceAll('"', '"')
|
|
33
|
-
.replaceAll("'", ''');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
export function createRaceControlsMarkup({
|
|
37
28
|
title,
|
|
38
29
|
kicker,
|
|
@@ -81,10 +72,7 @@ export function createTimingTowerMarkup({ totalLaps, assets }) {
|
|
|
81
72
|
<button type="button" data-timing-gap-mode="interval" aria-pressed="true">Int</button>
|
|
82
73
|
<button type="button" data-timing-gap-mode="leader" aria-pressed="false">Gap</button>
|
|
83
74
|
</div>
|
|
84
|
-
|
|
85
|
-
<span>FIA</span>
|
|
86
|
-
<strong>Safety Car</strong>
|
|
87
|
-
</div>
|
|
75
|
+
${createRaceControlStatusBannerMarkup()}
|
|
88
76
|
<div class="broadcast-column-head" aria-hidden="true">
|
|
89
77
|
<span>Pos</span>
|
|
90
78
|
<span>Team</span>
|
|
@@ -99,16 +87,20 @@ export function createTimingTowerMarkup({ totalLaps, assets }) {
|
|
|
99
87
|
`;
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
export function createCameraControlsMarkup({ embedded = false } = {}) {
|
|
90
|
+
export function createCameraControlsMarkup({ embedded = false, showSimulationSpeed = false, ui = {} } = {}) {
|
|
103
91
|
const className = embedded ? 'camera-controls' : 'camera-controls camera-controls--external';
|
|
92
|
+
const showSpeedControl = showSimulationSpeed || ui.simulationSpeedControl === true;
|
|
104
93
|
return `
|
|
105
94
|
<div class="${className}" data-paddock-component="camera-controls" aria-label="Camera controls">
|
|
106
95
|
<button type="button" data-camera-mode="overview" aria-pressed="false">Overview</button>
|
|
107
96
|
<button type="button" data-camera-mode="leader" aria-pressed="true">Leader</button>
|
|
108
97
|
<button type="button" data-camera-mode="selected" aria-pressed="false">Selected</button>
|
|
109
98
|
<button type="button" data-camera-mode="show-all" aria-pressed="false">Show all</button>
|
|
99
|
+
<button type="button" data-camera-mode="pit" aria-pressed="false">Pits</button>
|
|
110
100
|
<button type="button" data-zoom-out aria-label="Zoom out">-</button>
|
|
111
101
|
<button type="button" data-zoom-in aria-label="Zoom in">+</button>
|
|
102
|
+
${showSpeedControl ? '<button type="button" data-simulation-speed aria-label="Simulation speed">1x</button>' : ''}
|
|
103
|
+
<button type="button" data-race-data-banners-muted aria-pressed="false">Mute banners</button>
|
|
112
104
|
${createLoadingMarkup('Camera controls')}
|
|
113
105
|
</div>
|
|
114
106
|
`;
|
|
@@ -124,7 +116,7 @@ export function createRaceCanvasMarkup({
|
|
|
124
116
|
ui = {},
|
|
125
117
|
} = {}) {
|
|
126
118
|
const showFps = ui.showFps !== false;
|
|
127
|
-
const showEmbeddedCameraControls = ui.cameraControls
|
|
119
|
+
const showEmbeddedCameraControls = ui.cameraControls === 'embedded';
|
|
128
120
|
const timingFit = (timingTowerVerticalFit ?? ui.timingTowerVerticalFit) === 'scroll'
|
|
129
121
|
? 'scroll'
|
|
130
122
|
: 'expand-race-view';
|
|
@@ -153,7 +145,8 @@ export function createRaceCanvasMarkup({
|
|
|
153
145
|
<span></span>
|
|
154
146
|
</div>
|
|
155
147
|
</div>
|
|
156
|
-
${
|
|
148
|
+
${createStewardMessageMarkup()}
|
|
149
|
+
${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true, ui }) : ''}
|
|
157
150
|
${includeRaceDataPanel ? createRaceDataPanelMarkup({ assets, ui }) : ''}
|
|
158
151
|
${includeTelemetrySectorBanner ? createTelemetrySectorBannerMarkup({ ui }) : ''}
|
|
159
152
|
<div class="race-finish-panel" data-race-finish-panel hidden aria-live="polite">
|
|
@@ -166,22 +159,6 @@ export function createRaceCanvasMarkup({
|
|
|
166
159
|
`;
|
|
167
160
|
}
|
|
168
161
|
|
|
169
|
-
export function createRaceDataPanelMarkup({ ui = {} } = {}) {
|
|
170
|
-
const sizeMode = ui.raceDataBannerSize === 'auto' ? 'auto' : 'custom';
|
|
171
|
-
return `
|
|
172
|
-
<div class="race-data-panel race-data-panel--${sizeMode}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
|
|
173
|
-
<div class="race-data-copy">
|
|
174
|
-
<span class="race-data-kicker" data-race-data-kicker>Project</span>
|
|
175
|
-
<strong data-race-data-title>Select driver</strong>
|
|
176
|
-
<span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
|
|
177
|
-
</div>
|
|
178
|
-
<strong class="race-data-number" data-race-data-number>--</strong>
|
|
179
|
-
<button class="race-data-link" type="button" data-race-data-open>Open project</button>
|
|
180
|
-
${createLoadingMarkup('Race data')}
|
|
181
|
-
</div>
|
|
182
|
-
`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
162
|
export function createCarDriverOverviewMarkup({ assets }) {
|
|
186
163
|
const cells = Array.from({ length: 7 }, (_, index) => `
|
|
187
164
|
<div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
|
|
@@ -280,27 +257,6 @@ export function createTelemetrySectorsMarkup() {
|
|
|
280
257
|
`;
|
|
281
258
|
}
|
|
282
259
|
|
|
283
|
-
export function createTelemetrySectorBannerMarkup() {
|
|
284
|
-
return `
|
|
285
|
-
<section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
|
|
286
|
-
<div class="telemetry-sector-banner__copy">
|
|
287
|
-
<span><b data-selected-code>--</b> sector telemetry</span>
|
|
288
|
-
<strong data-selected-name>Select driver</strong>
|
|
289
|
-
<em data-telemetry-current-sector>S1</em>
|
|
290
|
-
</div>
|
|
291
|
-
<div class="telemetry-sector-banner__bars">
|
|
292
|
-
${[1, 2, 3].map((sector) => `
|
|
293
|
-
<div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
294
|
-
<span>S${sector}</span>
|
|
295
|
-
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
296
|
-
</div>
|
|
297
|
-
`).join('')}
|
|
298
|
-
</div>
|
|
299
|
-
${createLoadingMarkup('Sector banner')}
|
|
300
|
-
</section>
|
|
301
|
-
`;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
260
|
export function createTelemetryLapTimesMarkup() {
|
|
305
261
|
return `
|
|
306
262
|
<section class="${getTelemetryModuleClass('lap-times')} telemetry-lap-module" data-paddock-component="telemetry-lap-times" aria-label="Lap timing">
|
|
@@ -368,33 +324,41 @@ export function createTelemetryPanelMarkup(options, { includeOverview = options.
|
|
|
368
324
|
export function createRaceTelemetryDrawerMarkup(options, {
|
|
369
325
|
timingTowerVerticalFit,
|
|
370
326
|
drawerInitiallyOpen = false,
|
|
327
|
+
raceDataTelemetryDetail = options.ui?.raceDataTelemetryDetail,
|
|
371
328
|
} = {}) {
|
|
372
329
|
const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
|
|
373
330
|
const drawerId = createTelemetryDrawerId();
|
|
331
|
+
const drawerOptions = {
|
|
332
|
+
...options,
|
|
333
|
+
ui: {
|
|
334
|
+
...(options.ui ?? {}),
|
|
335
|
+
cameraControls: false,
|
|
336
|
+
raceDataTelemetryDetail: Boolean(raceDataTelemetryDetail),
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
const showCameraControls = options.ui?.cameraControls !== false;
|
|
374
340
|
return `
|
|
375
341
|
<section class="race-telemetry-drawer${openClass}" data-paddock-component="race-telemetry-drawer" data-race-telemetry-drawer aria-label="Race view with telemetry drawer">
|
|
342
|
+
<div class="race-telemetry-drawer__toolbar" aria-label="Race workbench controls">
|
|
343
|
+
${showCameraControls ? createCameraControlsMarkup({ showSimulationSpeed: true, ui: options.ui }) : ''}
|
|
344
|
+
<div class="race-telemetry-drawer__controls">
|
|
345
|
+
${createSafetyCarControlMarkup({ compact: true })}
|
|
346
|
+
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
|
|
347
|
+
${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
|
|
348
|
+
</button>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
376
351
|
<div class="race-telemetry-drawer__race">
|
|
377
352
|
${createRaceCanvasMarkup({
|
|
378
|
-
...
|
|
353
|
+
...drawerOptions,
|
|
379
354
|
includeRaceDataPanel: true,
|
|
380
355
|
includeTimingTower: true,
|
|
381
|
-
includeTelemetrySectorBanner: true,
|
|
382
356
|
timingTowerVerticalFit,
|
|
383
357
|
})}
|
|
384
358
|
</div>
|
|
385
|
-
<div class="race-telemetry-drawer__controls" aria-label="Race workbench controls">
|
|
386
|
-
${createSafetyCarControlMarkup({ compact: true })}
|
|
387
|
-
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
|
|
388
|
-
${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
|
|
389
|
-
</button>
|
|
390
|
-
</div>
|
|
391
359
|
<aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
|
|
392
|
-
<div class="telemetry-drawer__header">
|
|
393
|
-
<span>Live telemetry</span>
|
|
394
|
-
<button type="button" data-telemetry-drawer-close>Close</button>
|
|
395
|
-
</div>
|
|
396
360
|
<div class="telemetry-drawer__content">
|
|
397
|
-
${
|
|
361
|
+
${createTelemetryPanelMarkup(options, { includeOverview: false })}
|
|
398
362
|
</div>
|
|
399
363
|
</aside>
|
|
400
364
|
</section>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { escapeHtml } from './templateUtils.js';
|
|
2
|
+
|
|
3
|
+
export const RACE_CONTROL_STATUS_BANNERS = {
|
|
4
|
+
'safety-car': {
|
|
5
|
+
status: 'safety-car',
|
|
6
|
+
className: 'is-safety-car',
|
|
7
|
+
kicker: 'FIA',
|
|
8
|
+
title: 'Safety Car',
|
|
9
|
+
},
|
|
10
|
+
'red-flag': {
|
|
11
|
+
status: 'red-flag',
|
|
12
|
+
className: 'is-red-flag',
|
|
13
|
+
kicker: 'FIA',
|
|
14
|
+
title: 'Red Flag',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function getRaceControlStatusBanner(mode) {
|
|
19
|
+
return RACE_CONTROL_STATUS_BANNERS[mode] ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createRaceControlStatusBannerMarkup({ status = null } = {}) {
|
|
23
|
+
const banner = getRaceControlStatusBanner(status);
|
|
24
|
+
const className = [
|
|
25
|
+
'broadcast-race-control-banner',
|
|
26
|
+
banner?.className,
|
|
27
|
+
].filter(Boolean).join(' ');
|
|
28
|
+
const hiddenAttribute = banner ? '' : ' hidden';
|
|
29
|
+
const statusAttribute = banner ? ` data-race-control-status="${escapeHtml(banner.status)}"` : '';
|
|
30
|
+
const kicker = banner?.kicker ?? 'FIA';
|
|
31
|
+
const title = banner?.title ?? 'Race Control';
|
|
32
|
+
|
|
33
|
+
return `
|
|
34
|
+
<div class="${className}" data-tower-race-control-banner${statusAttribute}${hiddenAttribute}>
|
|
35
|
+
<span data-tower-race-control-kicker>${escapeHtml(kicker)}</span>
|
|
36
|
+
<strong data-tower-race-control-title>${escapeHtml(title)}</strong>
|
|
37
|
+
</div>
|
|
38
|
+
`;
|
|
39
|
+
}
|
package/src/ui/shellTemplate.js
CHANGED
|
@@ -24,7 +24,7 @@ export function createF1SimulatorShell({
|
|
|
24
24
|
shellClasses.push(`sim-shell--timing-${timingFit}`);
|
|
25
25
|
shellClasses.push(`sim-shell--race-data-${raceDataBannerSize}`);
|
|
26
26
|
const cameraControls = ui.cameraControls === 'external'
|
|
27
|
-
? `<div class="sim-external-camera-controls">${createCameraControlsMarkup()}</div>`
|
|
27
|
+
? `<div class="sim-external-camera-controls">${createCameraControlsMarkup({ ui })}</div>`
|
|
28
28
|
: '';
|
|
29
29
|
|
|
30
30
|
return `
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function escapeHtml(value) {
|
|
2
|
+
return String(value ?? '')
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>')
|
|
6
|
+
.replaceAll('"', '"')
|
|
7
|
+
.replaceAll("'", ''');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createLoadingMarkup(label = 'Loading') {
|
|
11
|
+
return `
|
|
12
|
+
<div class="paddock-loading" data-paddock-loading aria-label="${escapeHtml(label)} loading">
|
|
13
|
+
<div class="paddock-loading__lights" aria-hidden="true">
|
|
14
|
+
<span></span>
|
|
15
|
+
<span></span>
|
|
16
|
+
<span></span>
|
|
17
|
+
<span></span>
|
|
18
|
+
<span></span>
|
|
19
|
+
</div>
|
|
20
|
+
<span class="paddock-loading__label">${escapeHtml(label)}</span>
|
|
21
|
+
</div>
|
|
22
|
+
`;
|
|
23
|
+
}
|
package/AGENTS.md
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
## Project
|
|
4
|
-
|
|
5
|
-
PaddockJS is an installable browser component package for an F1-style project race simulator.
|
|
6
|
-
|
|
7
|
-
The package lives independently from the portfolio website. Host websites install it and mount it with:
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
import { mountF1Simulator } from '@inventure71/paddockjs';
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
It can also be mounted as rearrangeable package-owned pieces with:
|
|
14
|
-
|
|
15
|
-
```js
|
|
16
|
-
import { createPaddockSimulator } from '@inventure71/paddockjs';
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## What Is In This Repo
|
|
20
|
-
|
|
21
|
-
- `src/index.js`: public package API.
|
|
22
|
-
- `src/api/`: public composable mounting controller.
|
|
23
|
-
- `src/app/`: browser runtime, DOM bindings, PixiJS app lifecycle, controls, readouts, and camera orchestration.
|
|
24
|
-
- `src/config/`: default options and bundled default asset mapping.
|
|
25
|
-
- `src/data/`: driver/project data normalization, driver/car pairing, rating conversion, and demo data.
|
|
26
|
-
- `src/rendering/`: PixiJS track rendering helpers and render interpolation.
|
|
27
|
-
- `src/simulation/`: race rules, driver AI, vehicle physics, track model, and math utilities.
|
|
28
|
-
- `src/ui/`: generated markup for the all-in-one shell and individually mounted UI surfaces.
|
|
29
|
-
- `src/__tests__/`: package tests.
|
|
30
|
-
- `assets/`: simulator-owned default assets.
|
|
31
|
-
- `docs/`: package specs and design documentation.
|
|
32
|
-
- `INSTALL_AND_UPDATE.md`: install/update workflow.
|
|
33
|
-
|
|
34
|
-
## Required Documentation Discipline
|
|
35
|
-
|
|
36
|
-
When changing simulator behavior, update docs in the same change.
|
|
37
|
-
|
|
38
|
-
Use this mapping:
|
|
39
|
-
|
|
40
|
-
- Public API, lifecycle, install assumptions: update `docs/system_specs.md` and `README.md` if needed.
|
|
41
|
-
- Race behavior, DRS, safety car, starts, physics, collisions: update `docs/rules.md`.
|
|
42
|
-
- Vocabulary or domain model: update `docs/concepts.md`.
|
|
43
|
-
- Driver/entry/options/assets callback shapes: update `docs/data_contract.md`.
|
|
44
|
-
- Module ownership or data/control flow: update `docs/architecture.md`.
|
|
45
|
-
- Local install/update workflow: update `INSTALL_AND_UPDATE.md`.
|
|
46
|
-
|
|
47
|
-
Do not leave docs describing old behavior after code changes.
|
|
48
|
-
|
|
49
|
-
## Linear Work Logging
|
|
50
|
-
|
|
51
|
-
For any implementation that is more than a trivial/no-op change, log the work in the PaddockJS project on Linear.
|
|
52
|
-
|
|
53
|
-
The Linear update must include:
|
|
54
|
-
|
|
55
|
-
- what task was done
|
|
56
|
-
- how it was solved
|
|
57
|
-
- any important verification or remaining follow-up
|
|
58
|
-
|
|
59
|
-
## Engineering Rules
|
|
60
|
-
|
|
61
|
-
- Preserve the package boundary: PaddockJS should stay reusable and host-agnostic.
|
|
62
|
-
- Host-specific routing belongs in `onDriverOpen(driver)`, not package internals.
|
|
63
|
-
- Host-specific project data should be passed as `drivers` and `entries`, not hardcoded into the runtime.
|
|
64
|
-
- The package should own default simulator assets so hosts do not need to copy them.
|
|
65
|
-
- Treat the timing board width and broadcast proportions as package-owned. Do not resize or retune timing-board width as a casual fix; preserve its intended width and solve layout bugs through internal column/content constraints unless the user explicitly asks to redesign the timing board sizing.
|
|
66
|
-
- Prefer small, focused modules when extracting from `F1SimulatorApp.js`.
|
|
67
|
-
- Keep simulation logic deterministic for the same seed, track seed, drivers, entries, and rules.
|
|
68
|
-
- Do not claim completion without running verification.
|
|
69
|
-
|
|
70
|
-
## Verification
|
|
71
|
-
|
|
72
|
-
Run before claiming package changes are complete:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npm run check
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
For portfolio integration changes, also run from the portfolio repo:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
cd /Users/inventure71/VSProjects/Inventure71.github.io
|
|
82
|
-
npm install ../PaddockJS
|
|
83
|
-
npm run check
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Browser behavior changes need a browser smoke test against the host page.
|
package/src/standalone.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { DEMO_PROJECT_DRIVERS, mountF1Simulator } from './index.js';
|
|
2
|
-
|
|
3
|
-
const root = document.getElementById('f1-simulator-root');
|
|
4
|
-
|
|
5
|
-
if (root) {
|
|
6
|
-
mountF1Simulator(root, {
|
|
7
|
-
drivers: DEMO_PROJECT_DRIVERS,
|
|
8
|
-
onDriverOpen(driver) {
|
|
9
|
-
if (driver.link) window.location.href = driver.link;
|
|
10
|
-
},
|
|
11
|
-
}).catch((error) => {
|
|
12
|
-
root.dataset.simulatorState = 'error';
|
|
13
|
-
console.error('F1 simulator failed to initialize.', error);
|
|
14
|
-
});
|
|
15
|
-
}
|