@inventure71/paddockjs 0.3.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 +39 -0
- package/INSTALL_AND_UPDATE.md +3 -1
- package/README.md +142 -9
- package/docs/architecture.md +49 -15
- package/docs/concepts.md +20 -3
- package/docs/data_contract.md +185 -27
- package/docs/data_model.md +1 -1
- package/docs/learnings.md +1 -1
- package/docs/rules.md +140 -19
- package/docs/system_specs.md +39 -16
- package/docs/training.md +103 -1
- package/examples/train-basic-policy.mjs +5 -6
- package/examples/trainingData.mjs +133 -0
- package/package.json +5 -2
- package/src/api/PaddockSimulatorController.js +32 -2
- package/src/app/F1SimulatorApp.js +1101 -128
- package/src/app/domBindings.js +10 -1
- package/src/config/defaultOptions.js +4 -4
- package/src/environment/actions.js +25 -2
- package/src/environment/evaluation.js +184 -0
- package/src/environment/index.d.ts +281 -1
- package/src/environment/index.js +13 -0
- package/src/environment/observationOptions.js +12 -0
- package/src/environment/observations.js +189 -17
- package/src/environment/options.js +7 -0
- package/src/environment/recorder.js +28 -0
- package/src/environment/runtime.js +41 -6
- package/src/environment/scenarios.js +220 -0
- package/src/environment/sensors.js +145 -35
- package/src/environment/specs.js +92 -8
- package/src/environment/workerProtocol.js +52 -0
- package/src/index.d.ts +366 -3
- package/src/index.js +21 -0
- package/src/rendering/proceduralTrackAsset.js +336 -23
- package/src/simulation/collisionGeometry.js +214 -0
- package/src/simulation/driverController.js +431 -65
- package/src/simulation/raceSimulation.js +2132 -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 +300 -42
- package/src/ui/bannerTemplates.js +69 -0
- package/src/ui/componentTemplates.js +36 -96
- package/src/ui/raceControlStatusBanner.js +39 -0
- package/src/ui/shellTemplate.js +1 -1
- package/src/ui/templateUtils.js +23 -0
|
@@ -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,42 +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
|
-
const telemetryDetail = Boolean(ui.raceDataTelemetryDetail);
|
|
172
|
-
const classNames = ['race-data-panel', `race-data-panel--${sizeMode}`];
|
|
173
|
-
if (telemetryDetail) classNames.push('race-data-panel--with-telemetry');
|
|
174
|
-
return `
|
|
175
|
-
<div class="${classNames.join(' ')}" data-paddock-component="race-data-panel" data-race-data-panel aria-live="polite">
|
|
176
|
-
<div class="race-data-copy">
|
|
177
|
-
<span class="race-data-kicker" data-race-data-kicker>Project</span>
|
|
178
|
-
<strong data-race-data-title>Select driver</strong>
|
|
179
|
-
<span class="race-data-subtitle" data-race-data-subtitle>Race entry</span>
|
|
180
|
-
</div>
|
|
181
|
-
${telemetryDetail ? createRaceDataTelemetryMarkup() : ''}
|
|
182
|
-
<strong class="race-data-number" data-race-data-number>--</strong>
|
|
183
|
-
<button class="race-data-link" type="button" data-race-data-open>Open project</button>
|
|
184
|
-
${createLoadingMarkup('Race data')}
|
|
185
|
-
</div>
|
|
186
|
-
`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function createRaceDataTelemetryMarkup() {
|
|
190
|
-
return `
|
|
191
|
-
<div class="race-data-telemetry" data-race-data-telemetry aria-label="Project sector telemetry">
|
|
192
|
-
<span class="race-data-telemetry__label">Sectors</span>
|
|
193
|
-
<div class="race-data-telemetry__bars">
|
|
194
|
-
${[1, 2, 3].map((sector) => `
|
|
195
|
-
<div class="telemetry-sector-bar race-data-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
196
|
-
<span>S${sector}</span>
|
|
197
|
-
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
198
|
-
</div>
|
|
199
|
-
`).join('')}
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
`;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
162
|
export function createCarDriverOverviewMarkup({ assets }) {
|
|
206
163
|
const cells = Array.from({ length: 7 }, (_, index) => `
|
|
207
164
|
<div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
|
|
@@ -300,27 +257,6 @@ export function createTelemetrySectorsMarkup() {
|
|
|
300
257
|
`;
|
|
301
258
|
}
|
|
302
259
|
|
|
303
|
-
export function createTelemetrySectorBannerMarkup() {
|
|
304
|
-
return `
|
|
305
|
-
<section class="telemetry-sector-banner" data-paddock-component="telemetry-sector-banner" data-telemetry-sector-banner aria-label="Broadcast sector telemetry">
|
|
306
|
-
<div class="telemetry-sector-banner__copy">
|
|
307
|
-
<span><b data-selected-code>--</b> sector telemetry</span>
|
|
308
|
-
<strong data-selected-name>Select driver</strong>
|
|
309
|
-
<em data-telemetry-current-sector>S1</em>
|
|
310
|
-
</div>
|
|
311
|
-
<div class="telemetry-sector-banner__bars">
|
|
312
|
-
${[1, 2, 3].map((sector) => `
|
|
313
|
-
<div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
|
|
314
|
-
<span>S${sector}</span>
|
|
315
|
-
<strong data-telemetry-sector-time="${sector}">--</strong>
|
|
316
|
-
</div>
|
|
317
|
-
`).join('')}
|
|
318
|
-
</div>
|
|
319
|
-
${createLoadingMarkup('Sector banner')}
|
|
320
|
-
</section>
|
|
321
|
-
`;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
260
|
export function createTelemetryLapTimesMarkup() {
|
|
325
261
|
return `
|
|
326
262
|
<section class="${getTelemetryModuleClass('lap-times')} telemetry-lap-module" data-paddock-component="telemetry-lap-times" aria-label="Lap timing">
|
|
@@ -396,11 +332,22 @@ export function createRaceTelemetryDrawerMarkup(options, {
|
|
|
396
332
|
...options,
|
|
397
333
|
ui: {
|
|
398
334
|
...(options.ui ?? {}),
|
|
335
|
+
cameraControls: false,
|
|
399
336
|
raceDataTelemetryDetail: Boolean(raceDataTelemetryDetail),
|
|
400
337
|
},
|
|
401
338
|
};
|
|
339
|
+
const showCameraControls = options.ui?.cameraControls !== false;
|
|
402
340
|
return `
|
|
403
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>
|
|
404
351
|
<div class="race-telemetry-drawer__race">
|
|
405
352
|
${createRaceCanvasMarkup({
|
|
406
353
|
...drawerOptions,
|
|
@@ -409,16 +356,9 @@ export function createRaceTelemetryDrawerMarkup(options, {
|
|
|
409
356
|
timingTowerVerticalFit,
|
|
410
357
|
})}
|
|
411
358
|
</div>
|
|
412
|
-
<div class="race-telemetry-drawer__controls" aria-label="Race workbench controls">
|
|
413
|
-
${createSafetyCarControlMarkup({ compact: true })}
|
|
414
|
-
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
|
|
415
|
-
${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
|
|
416
|
-
</button>
|
|
417
|
-
</div>
|
|
418
359
|
<aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
|
|
419
|
-
<div class="telemetry-drawer__header" aria-hidden="true"></div>
|
|
420
360
|
<div class="telemetry-drawer__content">
|
|
421
|
-
${
|
|
361
|
+
${createTelemetryPanelMarkup(options, { includeOverview: false })}
|
|
422
362
|
</div>
|
|
423
363
|
</aside>
|
|
424
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
|
+
}
|