@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/INSTALL_AND_UPDATE.md +3 -1
  3. package/README.md +142 -9
  4. package/docs/architecture.md +49 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +185 -27
  7. package/docs/data_model.md +1 -1
  8. package/docs/learnings.md +1 -1
  9. package/docs/rules.md +140 -19
  10. package/docs/system_specs.md +39 -16
  11. package/docs/training.md +103 -1
  12. package/examples/train-basic-policy.mjs +5 -6
  13. package/examples/trainingData.mjs +133 -0
  14. package/package.json +5 -2
  15. package/src/api/PaddockSimulatorController.js +32 -2
  16. package/src/app/F1SimulatorApp.js +1101 -128
  17. package/src/app/domBindings.js +10 -1
  18. package/src/config/defaultOptions.js +4 -4
  19. package/src/environment/actions.js +25 -2
  20. package/src/environment/evaluation.js +184 -0
  21. package/src/environment/index.d.ts +281 -1
  22. package/src/environment/index.js +13 -0
  23. package/src/environment/observationOptions.js +12 -0
  24. package/src/environment/observations.js +189 -17
  25. package/src/environment/options.js +7 -0
  26. package/src/environment/recorder.js +28 -0
  27. package/src/environment/runtime.js +41 -6
  28. package/src/environment/scenarios.js +220 -0
  29. package/src/environment/sensors.js +145 -35
  30. package/src/environment/specs.js +92 -8
  31. package/src/environment/workerProtocol.js +52 -0
  32. package/src/index.d.ts +366 -3
  33. package/src/index.js +21 -0
  34. package/src/rendering/proceduralTrackAsset.js +336 -23
  35. package/src/simulation/collisionGeometry.js +214 -0
  36. package/src/simulation/driverController.js +431 -65
  37. package/src/simulation/raceSimulation.js +2132 -171
  38. package/src/simulation/rules/collisionSteward.js +69 -0
  39. package/src/simulation/rules/penaltyLedger.js +153 -0
  40. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  41. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  42. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  43. package/src/simulation/rulesConfig.js +352 -0
  44. package/src/simulation/trackModel.js +1154 -76
  45. package/src/simulation/units.js +6 -4
  46. package/src/simulation/vehicleGeometry.js +247 -0
  47. package/src/simulation/vehiclePhysics.js +82 -50
  48. package/src/simulation/wheelSurface.js +338 -0
  49. package/src/styles.css +300 -42
  50. package/src/ui/bannerTemplates.js +69 -0
  51. package/src/ui/componentTemplates.js +36 -96
  52. package/src/ui/raceControlStatusBanner.js +39 -0
  53. package/src/ui/shellTemplate.js +1 -1
  54. 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('&', '&amp;')
30
- .replaceAll('<', '&lt;')
31
- .replaceAll('>', '&gt;')
32
- .replaceAll('"', '&quot;')
33
- .replaceAll("'", '&#39;');
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
- <div class="broadcast-safety-banner" data-tower-safety-banner>
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 !== 'external' && ui.cameraControls !== false;
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
- ${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true }) : ''}
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
- ${createTelemetryComponentMarkup(options)}
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
+ }
@@ -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('&', '&amp;')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;')
6
+ .replaceAll('"', '&quot;')
7
+ .replaceAll("'", '&#39;');
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
+ }