@inventure71/paddockjs 1.0.0 → 2.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 (190) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/INSTALL_AND_UPDATE.md +4 -3
  3. package/README.md +194 -12
  4. package/docs/architecture.md +119 -44
  5. package/docs/concepts.md +4 -2
  6. package/docs/custom_model_controller.md +329 -0
  7. package/docs/data_contract.md +299 -17
  8. package/docs/index.md +1 -0
  9. package/docs/learnings.md +22 -0
  10. package/docs/rules.md +37 -10
  11. package/docs/system_specs.md +23 -10
  12. package/docs/training.md +272 -30
  13. package/examples/python/README.md +62 -0
  14. package/examples/python/base_policy_server.py +191 -0
  15. package/examples/python/requirements.txt +2 -0
  16. package/package.json +12 -4
  17. package/src/api/PaddockSimulatorController.js +4 -0
  18. package/src/app/BrowserExpertAdapter.js +367 -9
  19. package/src/app/F1SimulatorApp.js +305 -1603
  20. package/src/app/banners/bannerFormatters.js +55 -0
  21. package/src/app/banners/raceDataBannerController.js +141 -0
  22. package/src/app/banners/stewardMessageController.js +99 -0
  23. package/src/app/camera/cameraConstants.js +21 -0
  24. package/src/app/camera/cameraController.js +265 -0
  25. package/src/app/camera/cameraGeometry.js +176 -0
  26. package/src/app/domBindings.js +4 -0
  27. package/src/app/readouts/carOverviewRenderer.js +109 -0
  28. package/src/app/readouts/controlStateRenderer.js +37 -0
  29. package/src/app/readouts/raceStatusRenderer.js +133 -0
  30. package/src/app/readouts/readoutFormatters.js +58 -0
  31. package/src/app/readouts/telemetryRenderer.js +113 -0
  32. package/src/app/readouts/timingTowerRenderer.js +177 -0
  33. package/src/app/rendering/appAssets.js +38 -0
  34. package/src/app/rendering/carRenderer.js +286 -0
  35. package/src/app/rendering/displayUtils.js +22 -0
  36. package/src/app/rendering/drsTrailRenderer.js +90 -0
  37. package/src/app/rendering/expertSensorRenderer.js +126 -0
  38. package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
  39. package/src/app/rendering/replayGhostRenderer.js +124 -0
  40. package/src/app/rendering/trackRenderer.js +25 -0
  41. package/src/app/runtime/frameLoop.js +66 -0
  42. package/src/app/runtime/runtimeTiming.js +34 -0
  43. package/src/app/runtime/runtimeVisibility.js +45 -0
  44. package/src/config/defaultOptions.js +20 -0
  45. package/src/environment/actions.js +13 -5
  46. package/src/environment/controllerLoop.js +246 -0
  47. package/src/environment/episode.js +86 -2
  48. package/src/environment/evaluation.js +54 -32
  49. package/src/environment/index.d.ts +361 -9
  50. package/src/environment/index.js +1 -0
  51. package/src/environment/metrics.js +159 -0
  52. package/src/environment/observationOptions.js +22 -1
  53. package/src/environment/observations.js +325 -40
  54. package/src/environment/options.js +37 -5
  55. package/src/environment/runtime.js +222 -21
  56. package/src/environment/scenarios.js +37 -8
  57. package/src/environment/sensors/bodySenses.js +23 -0
  58. package/src/environment/sensors/boundarySenses.js +17 -0
  59. package/src/environment/sensors/carRays.js +75 -0
  60. package/src/environment/sensors/contactSenses.js +32 -0
  61. package/src/environment/sensors/index.js +157 -0
  62. package/src/environment/sensors/indexedRayBands.js +103 -0
  63. package/src/environment/sensors/nearbyCars.js +52 -0
  64. package/src/environment/sensors/opponentRadar.js +65 -0
  65. package/src/environment/sensors/rayConfig.js +110 -0
  66. package/src/environment/sensors/rayDefaults.js +6 -0
  67. package/src/environment/sensors/rayGeometry.js +60 -0
  68. package/src/environment/sensors/rayGuards.js +19 -0
  69. package/src/environment/sensors/sensorTargets.js +60 -0
  70. package/src/environment/sensors/surfaceRays.js +296 -0
  71. package/src/environment/sensors/trackRays.js +261 -0
  72. package/src/environment/sensors.js +1 -367
  73. package/src/environment/specs.js +100 -5
  74. package/src/environment/workerProtocol.js +13 -1
  75. package/src/index.d.ts +169 -6
  76. package/src/index.js +5 -0
  77. package/src/rendering/proceduralTrackAsset.js +20 -823
  78. package/src/rendering/renderSnapshot.js +38 -12
  79. package/src/rendering/track/finishLineRenderer.js +34 -0
  80. package/src/rendering/track/gridRenderer.js +47 -0
  81. package/src/rendering/track/offsetStrokeSafety.js +136 -0
  82. package/src/rendering/track/pitLaneRenderer.js +248 -0
  83. package/src/rendering/track/trackMaterialRenderer.js +326 -0
  84. package/src/rendering/track/trackRenderConstants.js +38 -0
  85. package/src/rendering/track/trackRenderGeometry.js +94 -0
  86. package/src/simulation/collisionGeometry.js +1 -1
  87. package/src/simulation/driver/driverControlConstants.js +28 -0
  88. package/src/simulation/driver/driverController.js +47 -0
  89. package/src/simulation/driver/driverInput.js +28 -0
  90. package/src/simulation/driver/driverMath.js +6 -0
  91. package/src/simulation/driver/driverPersonality.js +25 -0
  92. package/src/simulation/driver/edgeRecovery.js +48 -0
  93. package/src/simulation/driver/racingControls.js +399 -0
  94. package/src/simulation/driver/racingLinePlan.js +166 -0
  95. package/src/simulation/driver/recoveryDynamics.js +38 -0
  96. package/src/simulation/driver/rejoinControls.js +213 -0
  97. package/src/simulation/driver/safetyCarControls.js +35 -0
  98. package/src/simulation/driver/trafficScan.js +51 -0
  99. package/src/simulation/driverController.js +4 -619
  100. package/src/simulation/participants/participantInteractions.js +120 -0
  101. package/src/simulation/pit/pitFlow.js +234 -0
  102. package/src/simulation/pit/pitIntent.js +92 -0
  103. package/src/simulation/pit/pitOccupancy.js +47 -0
  104. package/src/simulation/pit/pitPenaltyService.js +71 -0
  105. package/src/simulation/pit/pitQueue.js +55 -0
  106. package/src/simulation/pit/pitRouteMovement.js +160 -0
  107. package/src/simulation/pit/pitRouting.js +319 -0
  108. package/src/simulation/pit/pitService.js +41 -0
  109. package/src/simulation/pit/pitServiceConstants.js +21 -0
  110. package/src/simulation/pit/pitServiceProfile.js +45 -0
  111. package/src/simulation/pit/pitSnapshots.js +61 -0
  112. package/src/simulation/pit/pitState.js +113 -0
  113. package/src/simulation/pit/pitTireService.js +92 -0
  114. package/src/simulation/race/classification.js +104 -0
  115. package/src/simulation/race/gridPenalties.js +37 -0
  116. package/src/simulation/race/raceControlState.js +38 -0
  117. package/src/simulation/race/raceDistance.js +32 -0
  118. package/src/simulation/race/raceFinish.js +65 -0
  119. package/src/simulation/race/raceLifecycle.js +71 -0
  120. package/src/simulation/race/raceOrder.js +113 -0
  121. package/src/simulation/race/raceProgress.js +124 -0
  122. package/src/simulation/race/raceSetup.js +93 -0
  123. package/src/simulation/race/raceStep.js +80 -0
  124. package/src/simulation/race/redFlag.js +15 -0
  125. package/src/simulation/race/retirements.js +44 -0
  126. package/src/simulation/race/safetyCar.js +86 -0
  127. package/src/simulation/race/stalledDnf.js +86 -0
  128. package/src/simulation/raceSimulation.js +230 -2817
  129. package/src/simulation/replay/replayGhosts.js +128 -0
  130. package/src/simulation/rules/moduleConfig.js +72 -0
  131. package/src/simulation/rules/penaltyConfig.js +99 -0
  132. package/src/simulation/rules/penaltyEvents.js +18 -0
  133. package/src/simulation/rules/penaltyStats.js +26 -0
  134. package/src/simulation/rules/ruleConfigMerge.js +41 -0
  135. package/src/simulation/rules/ruleDefaults.js +85 -0
  136. package/src/simulation/rules/rulesReview.js +141 -0
  137. package/src/simulation/rules/rulesetPresets.js +47 -0
  138. package/src/simulation/rules/trackLimitsSteward.js +1 -1
  139. package/src/simulation/rulesConfig.js +5 -323
  140. package/src/simulation/snapshots/raceSnapshots.js +192 -0
  141. package/src/simulation/timing/drsTiming.js +87 -0
  142. package/src/simulation/timing/gapEstimation.js +38 -0
  143. package/src/simulation/timing/lapTelemetry.js +182 -0
  144. package/src/simulation/timing/raceTiming.js +15 -0
  145. package/src/simulation/timing/sectorPerformance.js +60 -0
  146. package/src/simulation/timing/timingConstants.js +5 -0
  147. package/src/simulation/timing/timingHistory.js +46 -0
  148. package/src/simulation/timing/timingLines.js +79 -0
  149. package/src/simulation/track/centerlineSpline.js +59 -0
  150. package/src/simulation/track/drsZones.js +67 -0
  151. package/src/simulation/track/pitLaneAccess.js +103 -0
  152. package/src/simulation/track/pitLaneLayout.js +395 -0
  153. package/src/simulation/track/pitLaneState.js +160 -0
  154. package/src/simulation/track/proceduralCenterline.js +237 -0
  155. package/src/simulation/track/sampleGeometry.js +49 -0
  156. package/src/simulation/track/spatialQueries.js +154 -0
  157. package/src/simulation/track/startStraight.js +141 -0
  158. package/src/simulation/track/trackConstants.js +109 -0
  159. package/src/simulation/track/trackGenerationOptions.js +283 -0
  160. package/src/simulation/track/trackMath.js +241 -0
  161. package/src/simulation/track/trackModel.js +240 -0
  162. package/src/simulation/track/trackQueryIndex.js +945 -0
  163. package/src/simulation/track/trackSectors.js +19 -0
  164. package/src/simulation/track/trackStatePolicy.js +24 -0
  165. package/src/simulation/track/trackValidation.js +98 -0
  166. package/src/simulation/trackModel.js +1 -1706
  167. package/src/simulation/vehicle/contactResolution.js +116 -0
  168. package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
  169. package/src/simulation/vehicle/pitWheelSurface.js +69 -0
  170. package/src/simulation/vehicle/runoffResponse.js +88 -0
  171. package/src/simulation/vehicle/surfacePriority.js +28 -0
  172. package/src/simulation/vehicle/vehicleGeometry.js +247 -0
  173. package/src/simulation/vehicle/vehiclePhysics.js +363 -0
  174. package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
  175. package/src/simulation/vehicle/vehicleState.js +186 -0
  176. package/src/simulation/vehicle/wheelSurface.js +150 -0
  177. package/src/simulation/vehicleGeometry.js +1 -247
  178. package/src/simulation/vehiclePhysics.js +1 -136
  179. package/src/simulation/warmup/runtimeWarmup.js +224 -0
  180. package/src/simulation/wheelSurface.js +1 -338
  181. package/src/styles.css +42 -0
  182. package/src/ui/cameraControlsTemplate.js +20 -0
  183. package/src/ui/carOverviewTemplate.js +33 -0
  184. package/src/ui/componentTemplates.js +14 -360
  185. package/src/ui/raceCanvasTemplate.js +69 -0
  186. package/src/ui/raceControlsTemplate.js +36 -0
  187. package/src/ui/shellTemplate.js +5 -4
  188. package/src/ui/telemetryDrawerTemplate.js +55 -0
  189. package/src/ui/telemetryTemplates.js +136 -0
  190. package/src/ui/timingTowerTemplate.js +34 -0
package/src/styles.css CHANGED
@@ -653,6 +653,28 @@
653
653
  0 0 0 1px rgba(255, 255, 255, 0.68);
654
654
  }
655
655
 
656
+ .timing-row.is-dnf {
657
+ color: rgba(244, 247, 251, 0.56);
658
+ background:
659
+ linear-gradient(90deg, rgba(148, 163, 184, 0.14), transparent 42%),
660
+ rgba(10, 12, 17, 0.58);
661
+ }
662
+
663
+ .timing-row.is-dnf .timing-icon,
664
+ .timing-row.is-dnf .timing-tire {
665
+ filter: grayscale(1);
666
+ opacity: 0.64;
667
+ }
668
+
669
+ .timing-row.is-dnf .timing-position {
670
+ color: rgba(244, 247, 251, 0.68);
671
+ background: rgba(30, 35, 44, 0.78);
672
+ }
673
+
674
+ .timing-row.is-dnf .timing-gap {
675
+ color: rgba(244, 247, 251, 0.68);
676
+ }
677
+
656
678
  .timing-position,
657
679
  .timing-icon,
658
680
  .timing-tire,
@@ -800,6 +822,26 @@
800
822
  display: block;
801
823
  }
802
824
 
825
+ .physics-mode-indicator {
826
+ position: absolute;
827
+ z-index: 18;
828
+ top: 0.85rem;
829
+ left: 0.85rem;
830
+ width: 0.8rem;
831
+ height: 0.8rem;
832
+ border: 1px solid rgba(255, 255, 255, 0.72);
833
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.7), 0 0 12px rgba(0, 0, 0, 0.52);
834
+ pointer-events: none;
835
+ }
836
+
837
+ .physics-mode-indicator--arcade {
838
+ background: #168cff;
839
+ }
840
+
841
+ .physics-mode-indicator--simulator {
842
+ background: #e10600;
843
+ }
844
+
803
845
  .race-telemetry-drawer {
804
846
  --telemetry-drawer-width: min(390px, max(300px, 30cqw));
805
847
  --race-toolbar-height: 3.4rem;
@@ -0,0 +1,20 @@
1
+ import { createLoadingMarkup } from './templateUtils.js';
2
+
3
+ export function createCameraControlsMarkup({ embedded = false, showSimulationSpeed = false, ui = {} } = {}) {
4
+ const className = embedded ? 'camera-controls' : 'camera-controls camera-controls--external';
5
+ const showSpeedControl = showSimulationSpeed || ui.simulationSpeedControl === true;
6
+ return `
7
+ <div class="${className}" data-paddock-component="camera-controls" aria-label="Camera controls">
8
+ <button type="button" data-camera-mode="overview" aria-pressed="false">Overview</button>
9
+ <button type="button" data-camera-mode="leader" aria-pressed="true">Leader</button>
10
+ <button type="button" data-camera-mode="selected" aria-pressed="false">Selected</button>
11
+ <button type="button" data-camera-mode="show-all" aria-pressed="false">Show all</button>
12
+ <button type="button" data-camera-mode="pit" aria-pressed="false">Pits</button>
13
+ <button type="button" data-zoom-out aria-label="Zoom out">-</button>
14
+ <button type="button" data-zoom-in aria-label="Zoom in">+</button>
15
+ ${showSpeedControl ? '<button type="button" data-simulation-speed aria-label="Simulation speed">1x</button>' : ''}
16
+ <button type="button" data-race-data-banners-muted aria-pressed="false">Mute banners</button>
17
+ ${createLoadingMarkup('Camera controls')}
18
+ </div>
19
+ `;
20
+ }
@@ -0,0 +1,33 @@
1
+ import { createLoadingMarkup, escapeHtml } from './templateUtils.js';
2
+
3
+ export function createCarDriverOverviewMarkup({ assets }) {
4
+ const cells = Array.from({ length: 7 }, (_, index) => `
5
+ <div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
6
+ <span data-overview-field-label>--</span>
7
+ <strong data-overview-field-value>--</strong>
8
+ </div>
9
+ `).join('');
10
+
11
+ return `
12
+ <section class="car-overview" data-paddock-component="car-driver-overview" aria-label="Selected car and driver overview">
13
+ <div class="car-overview-header">
14
+ <span data-car-overview-title>Car overview</span>
15
+ <strong data-car-overview-code>---</strong>
16
+ </div>
17
+ <div class="car-overview-toggle" role="group" aria-label="Overview mode">
18
+ <button type="button" data-overview-mode="vehicle" aria-pressed="true">Car</button>
19
+ <button type="button" data-overview-mode="driver" aria-pressed="false">Driver</button>
20
+ </div>
21
+ <div class="car-overview-diagram" style="--driver-color: #e10600">
22
+ ${cells}
23
+ <div class="car-overview-car" aria-hidden="true">
24
+ <img class="car-overview-car-image" data-car-overview-image src="${escapeHtml(assets.carOverview)}" alt="" />
25
+ <span class="car-overview-icon" data-car-overview-icon>--</span>
26
+ <span class="car-overview-number" data-car-overview-number>00</span>
27
+ <span class="car-overview-core-stat" data-car-overview-core-stat>Car</span>
28
+ </div>
29
+ </div>
30
+ ${createLoadingMarkup('Car and driver overview')}
31
+ </section>
32
+ `;
33
+ }
@@ -1,366 +1,20 @@
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
1
  export {
10
2
  createRaceDataPanelMarkup,
11
3
  createStewardMessageMarkup,
12
4
  createTelemetrySectorBannerMarkup,
13
5
  } from './bannerTemplates.js';
6
+ export { createCameraControlsMarkup } from './cameraControlsTemplate.js';
7
+ export { createCarDriverOverviewMarkup } from './carOverviewTemplate.js';
8
+ export { createRaceCanvasMarkup } from './raceCanvasTemplate.js';
9
+ export { createRaceControlStatusBannerMarkup } from './raceControlStatusBanner.js';
10
+ export { createRaceControlsMarkup, createSafetyCarControlMarkup } from './raceControlsTemplate.js';
11
+ export { createRaceTelemetryDrawerMarkup } from './telemetryDrawerTemplate.js';
12
+ export {
13
+ createTelemetryCoreMarkup,
14
+ createTelemetryLapTimesMarkup,
15
+ createTelemetryPanelMarkup,
16
+ createTelemetrySectorsMarkup,
17
+ createTelemetrySectorTimesMarkup,
18
+ } from './telemetryTemplates.js';
19
+ export { createTimingTowerMarkup } from './timingTowerTemplate.js';
14
20
  export { escapeHtml } from './templateUtils.js';
15
-
16
- function buttonHiddenAttribute(isVisible) {
17
- return isVisible ? '' : ' hidden';
18
- }
19
-
20
- let telemetryDrawerIdSequence = 0;
21
-
22
- function createTelemetryDrawerId() {
23
- telemetryDrawerIdSequence += 1;
24
- return `paddock-telemetry-drawer-${telemetryDrawerIdSequence}`;
25
- }
26
-
27
- export function createRaceControlsMarkup({
28
- title,
29
- kicker,
30
- backLinkHref,
31
- backLinkLabel,
32
- showBackLink,
33
- }) {
34
- return `
35
- <header class="sim-topbar" data-paddock-component="race-controls">
36
- <a class="sim-backlink" href="${escapeHtml(backLinkHref)}"${buttonHiddenAttribute(showBackLink)}>${escapeHtml(backLinkLabel)}</a>
37
- <div class="sim-title-block">
38
- <p class="sim-kicker">${escapeHtml(kicker)}</p>
39
- <h1>${escapeHtml(title)}</h1>
40
- </div>
41
- <div class="sim-controls" aria-label="Race controls">
42
- ${createSafetyCarControlMarkup({ compact: true })}
43
- <button class="sim-control" type="button" data-restart-race>Restart</button>
44
- </div>
45
- ${createLoadingMarkup('Race controls')}
46
- </header>
47
- `;
48
- }
49
-
50
- export function createSafetyCarControlMarkup({ compact = false } = {}) {
51
- const className = compact
52
- ? 'sim-control sim-control--safety'
53
- : 'sim-control sim-control--safety standalone-control';
54
- const componentAttribute = compact ? '' : ' data-paddock-component="safety-car-control"';
55
- return `<button class="${className}" type="button" data-safety-car aria-pressed="false"${componentAttribute}>Safety Car</button>`;
56
- }
57
-
58
- export function createTimingTowerMarkup({ totalLaps, assets }) {
59
- return `
60
- <aside class="sim-timing broadcast-tower" data-paddock-component="timing-tower" data-timing-tower aria-label="Timing tower">
61
- <div class="broadcast-tower-frame">
62
- <div class="broadcast-brand">
63
- <img class="broadcast-f1-logo" src="${escapeHtml(assets.f1Logo)}" alt="F1" />
64
- </div>
65
- <div class="broadcast-lap">
66
- <span>Lap</span>
67
- <strong data-tower-lap-readout>1</strong>
68
- <span>/</span>
69
- <span data-tower-total-laps>${escapeHtml(totalLaps)}</span>
70
- </div>
71
- <div class="broadcast-gap-mode" role="group" aria-label="Timing gap mode">
72
- <button type="button" data-timing-gap-mode="interval" aria-pressed="true">Int</button>
73
- <button type="button" data-timing-gap-mode="leader" aria-pressed="false">Gap</button>
74
- </div>
75
- ${createRaceControlStatusBannerMarkup()}
76
- <div class="broadcast-column-head" aria-hidden="true">
77
- <span>Pos</span>
78
- <span>Team</span>
79
- <span>Project</span>
80
- <span data-timing-gap-label>Int</span>
81
- <span>Tyre</span>
82
- </div>
83
- <ol class="timing-list" data-timing-list></ol>
84
- </div>
85
- ${createLoadingMarkup('Timing tower')}
86
- </aside>
87
- `;
88
- }
89
-
90
- export function createCameraControlsMarkup({ embedded = false, showSimulationSpeed = false, ui = {} } = {}) {
91
- const className = embedded ? 'camera-controls' : 'camera-controls camera-controls--external';
92
- const showSpeedControl = showSimulationSpeed || ui.simulationSpeedControl === true;
93
- return `
94
- <div class="${className}" data-paddock-component="camera-controls" aria-label="Camera controls">
95
- <button type="button" data-camera-mode="overview" aria-pressed="false">Overview</button>
96
- <button type="button" data-camera-mode="leader" aria-pressed="true">Leader</button>
97
- <button type="button" data-camera-mode="selected" aria-pressed="false">Selected</button>
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>
100
- <button type="button" data-zoom-out aria-label="Zoom out">-</button>
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>
104
- ${createLoadingMarkup('Camera controls')}
105
- </div>
106
- `;
107
- }
108
-
109
- export function createRaceCanvasMarkup({
110
- includeRaceDataPanel = false,
111
- includeTimingTower = false,
112
- includeTelemetrySectorBanner = false,
113
- timingTowerVerticalFit,
114
- assets,
115
- totalLaps,
116
- ui = {},
117
- } = {}) {
118
- const showFps = ui.showFps !== false;
119
- const showEmbeddedCameraControls = ui.cameraControls === 'embedded';
120
- const timingFit = (timingTowerVerticalFit ?? ui.timingTowerVerticalFit) === 'scroll'
121
- ? 'scroll'
122
- : 'expand-race-view';
123
- const classNames = ['sim-canvas-panel'];
124
- if (includeTimingTower) {
125
- classNames.push('sim-canvas-panel--with-timing-tower', `sim-canvas-panel--timing-${timingFit}`);
126
- }
127
-
128
- return `
129
- <section class="${classNames.join(' ')}" data-paddock-component="race-canvas" aria-label="Track view">
130
- <div class="track-canvas" data-track-canvas></div>
131
- ${includeTimingTower ? createTimingTowerMarkup({ totalLaps, assets }) : ''}
132
- ${showFps ? `
133
- <div class="fps-counter" aria-label="Frames per second">
134
- <span>FPS</span>
135
- <strong data-fps-readout>--</strong>
136
- </div>
137
- ` : ''}
138
- <div class="start-lights" data-start-lights aria-live="polite">
139
- <div class="start-lights__label" data-start-lights-label>Race start</div>
140
- <div class="start-lights__gantry" aria-hidden="true">
141
- <span></span>
142
- <span></span>
143
- <span></span>
144
- <span></span>
145
- <span></span>
146
- </div>
147
- </div>
148
- ${createStewardMessageMarkup()}
149
- ${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true, ui }) : ''}
150
- ${includeRaceDataPanel ? createRaceDataPanelMarkup({ assets, ui }) : ''}
151
- ${includeTelemetrySectorBanner ? createTelemetrySectorBannerMarkup({ ui }) : ''}
152
- <div class="race-finish-panel" data-race-finish-panel hidden aria-live="polite">
153
- <span>Race winner</span>
154
- <strong data-race-finish-winner>--</strong>
155
- <ol data-race-finish-classification></ol>
156
- </div>
157
- ${createLoadingMarkup('Race view')}
158
- </section>
159
- `;
160
- }
161
-
162
- export function createCarDriverOverviewMarkup({ assets }) {
163
- const cells = Array.from({ length: 7 }, (_, index) => `
164
- <div class="car-overview-cell car-overview-cell--slot-${index + 1}" data-overview-field data-overview-slot="${index}">
165
- <span data-overview-field-label>--</span>
166
- <strong data-overview-field-value>--</strong>
167
- </div>
168
- `).join('');
169
-
170
- return `
171
- <section class="car-overview" data-paddock-component="car-driver-overview" aria-label="Selected car and driver overview">
172
- <div class="car-overview-header">
173
- <span data-car-overview-title>Car overview</span>
174
- <strong data-car-overview-code>---</strong>
175
- </div>
176
- <div class="car-overview-toggle" role="group" aria-label="Overview mode">
177
- <button type="button" data-overview-mode="vehicle" aria-pressed="true">Car</button>
178
- <button type="button" data-overview-mode="driver" aria-pressed="false">Driver</button>
179
- </div>
180
- <div class="car-overview-diagram" style="--driver-color: #e10600">
181
- ${cells}
182
- <div class="car-overview-car" aria-hidden="true">
183
- <img class="car-overview-car-image" data-car-overview-image src="${escapeHtml(assets.carOverview)}" alt="" />
184
- <span class="car-overview-icon" data-car-overview-icon>--</span>
185
- <span class="car-overview-number" data-car-overview-number>00</span>
186
- <span class="car-overview-core-stat" data-car-overview-core-stat>Car</span>
187
- </div>
188
- </div>
189
- ${createLoadingMarkup('Car and driver overview')}
190
- </section>
191
- `;
192
- }
193
-
194
- function getTelemetryModules(ui = {}) {
195
- const defaults = {
196
- core: true,
197
- sectors: true,
198
- lapTimes: true,
199
- sectorTimes: true,
200
- };
201
- const modules = ui.telemetryModules;
202
- if (modules === false) return Object.fromEntries(Object.keys(defaults).map((name) => [name, false]));
203
- if (Array.isArray(modules)) {
204
- const requested = new Set(modules);
205
- return Object.fromEntries(Object.keys(defaults).map((name) => [name, requested.has(name)]));
206
- }
207
- if (!modules || typeof modules !== 'object') return defaults;
208
- return Object.fromEntries(Object.entries(defaults).map(([name, fallback]) => [
209
- name,
210
- modules[name] == null ? fallback : Boolean(modules[name]),
211
- ]));
212
- }
213
-
214
- function getTelemetryModuleClass(componentName) {
215
- return `sim-telemetry telemetry-component telemetry-component--${componentName}`;
216
- }
217
-
218
- export function createTelemetryCoreMarkup() {
219
- return `
220
- <section class="${getTelemetryModuleClass('core')}" data-paddock-component="telemetry-core" aria-label="Selected car core telemetry">
221
- <div class="telemetry-header">
222
- <span data-selected-code>---</span>
223
- <strong data-selected-name>Select car</strong>
224
- </div>
225
- <dl class="telemetry-grid">
226
- <div><dt>Speed</dt><dd data-telemetry-speed>0 km/h</dd></div>
227
- <div><dt>Throttle</dt><dd data-telemetry-throttle>0%</dd></div>
228
- <div><dt>Brake</dt><dd data-telemetry-brake>0%</dd></div>
229
- <div><dt>Tyres</dt><dd data-telemetry-tyres>0%</dd></div>
230
- <div><dt>DRS</dt><dd data-telemetry-drs>OFF</dd></div>
231
- <div><dt>Surface</dt><dd data-telemetry-surface>TRACK</dd></div>
232
- <div><dt>Interval</dt><dd data-telemetry-gap>--</dd></div>
233
- <div><dt>Leader</dt><dd data-telemetry-leader-gap>--</dd></div>
234
- </dl>
235
- ${createLoadingMarkup('Core telemetry')}
236
- </section>
237
- `;
238
- }
239
-
240
- export function createTelemetrySectorsMarkup() {
241
- return `
242
- <section class="${getTelemetryModuleClass('sectors')} telemetry-sector-strip" data-paddock-component="telemetry-sectors" data-telemetry-sector-strip aria-label="Sector progress">
243
- <div class="telemetry-module-header">
244
- <span>Sector map</span>
245
- <strong data-telemetry-current-sector>S1</strong>
246
- </div>
247
- <div class="telemetry-sector-bars">
248
- ${[1, 2, 3].map((sector) => `
249
- <div class="telemetry-sector-bar" data-telemetry-sector-bar="${sector}" style="--sector-fill: 0%">
250
- <span>S${sector}</span>
251
- <strong data-telemetry-sector-time="${sector}">--</strong>
252
- </div>
253
- `).join('')}
254
- </div>
255
- ${createLoadingMarkup('Sector telemetry')}
256
- </section>
257
- `;
258
- }
259
-
260
- export function createTelemetryLapTimesMarkup() {
261
- return `
262
- <section class="${getTelemetryModuleClass('lap-times')} telemetry-lap-module" data-paddock-component="telemetry-lap-times" aria-label="Lap timing">
263
- <div class="telemetry-module-header">
264
- <span>Lap timing</span>
265
- <strong data-telemetry-completed-laps>0 laps</strong>
266
- </div>
267
- <table class="telemetry-lap-table" data-telemetry-lap-table>
268
- <tbody>
269
- <tr><th scope="row">Current</th><td data-telemetry-current-lap-time>--</td></tr>
270
- <tr><th scope="row">Last</th><td data-telemetry-last-lap-time>--</td></tr>
271
- <tr><th scope="row">Best</th><td data-telemetry-best-lap-time>--</td></tr>
272
- </tbody>
273
- </table>
274
- ${createLoadingMarkup('Lap telemetry')}
275
- </section>
276
- `;
277
- }
278
-
279
- export function createTelemetrySectorTimesMarkup() {
280
- return `
281
- <section class="${getTelemetryModuleClass('sector-times')} telemetry-sector-table-module" data-paddock-component="telemetry-sector-times" aria-label="Sector timing table">
282
- <div class="telemetry-module-header">
283
- <span>Sector timing</span>
284
- <strong>Last / Best</strong>
285
- </div>
286
- <table class="telemetry-sector-table" data-telemetry-sector-table>
287
- <thead>
288
- <tr><th scope="col">Sector</th><th scope="col">Last</th><th scope="col">Best</th></tr>
289
- </thead>
290
- <tbody>
291
- ${[1, 2, 3].map((sector) => `
292
- <tr data-telemetry-sector-row="${sector}">
293
- <th scope="row">S${sector}</th>
294
- <td data-telemetry-sector-last="${sector}">--</td>
295
- <td data-telemetry-sector-best="${sector}">--</td>
296
- </tr>
297
- `).join('')}
298
- </tbody>
299
- </table>
300
- ${createLoadingMarkup('Sector table')}
301
- </section>
302
- `;
303
- }
304
-
305
- function createTelemetryComponentMarkup(options, modules = getTelemetryModules(options.ui)) {
306
- return `
307
- ${modules.core ? createTelemetryCoreMarkup(options) : ''}
308
- ${modules.sectors ? createTelemetrySectorsMarkup(options) : ''}
309
- ${modules.lapTimes ? createTelemetryLapTimesMarkup(options) : ''}
310
- ${modules.sectorTimes ? createTelemetrySectorTimesMarkup(options) : ''}
311
- `;
312
- }
313
-
314
- export function createTelemetryPanelMarkup(options, { includeOverview = options.ui?.telemetryIncludesOverview !== false } = {}) {
315
- const modules = getTelemetryModules(options.ui);
316
- return `
317
- <aside class="telemetry-stack" data-paddock-component="telemetry-stack" aria-label="Selected car telemetry stack">
318
- ${createTelemetryComponentMarkup(options, modules)}
319
- ${includeOverview ? createCarDriverOverviewMarkup(options) : ''}
320
- </aside>
321
- `;
322
- }
323
-
324
- export function createRaceTelemetryDrawerMarkup(options, {
325
- timingTowerVerticalFit,
326
- drawerInitiallyOpen = false,
327
- raceDataTelemetryDetail = options.ui?.raceDataTelemetryDetail,
328
- } = {}) {
329
- const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
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;
340
- return `
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>
351
- <div class="race-telemetry-drawer__race">
352
- ${createRaceCanvasMarkup({
353
- ...drawerOptions,
354
- includeRaceDataPanel: true,
355
- includeTimingTower: true,
356
- timingTowerVerticalFit,
357
- })}
358
- </div>
359
- <aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
360
- <div class="telemetry-drawer__content">
361
- ${createTelemetryPanelMarkup(options, { includeOverview: false })}
362
- </div>
363
- </aside>
364
- </section>
365
- `;
366
- }
@@ -0,0 +1,69 @@
1
+ import {
2
+ createRaceDataPanelMarkup,
3
+ createStewardMessageMarkup,
4
+ createTelemetrySectorBannerMarkup,
5
+ } from './bannerTemplates.js';
6
+ import { createCameraControlsMarkup } from './cameraControlsTemplate.js';
7
+ import { createTimingTowerMarkup } from './timingTowerTemplate.js';
8
+ import { createLoadingMarkup } from './templateUtils.js';
9
+
10
+ export function createRaceCanvasMarkup({
11
+ includeRaceDataPanel = false,
12
+ includeTimingTower = false,
13
+ includeTelemetrySectorBanner = false,
14
+ timingTowerVerticalFit,
15
+ assets,
16
+ totalLaps,
17
+ physicsMode = 'arcade',
18
+ ui = {},
19
+ debug = {},
20
+ } = {}) {
21
+ const showFps = ui.showFps !== false;
22
+ const showEmbeddedCameraControls = ui.cameraControls === 'embedded';
23
+ const timingFit = (timingTowerVerticalFit ?? ui.timingTowerVerticalFit) === 'scroll'
24
+ ? 'scroll'
25
+ : 'expand-race-view';
26
+ const classNames = ['sim-canvas-panel'];
27
+ if (includeTimingTower) {
28
+ classNames.push('sim-canvas-panel--with-timing-tower', `sim-canvas-panel--timing-${timingFit}`);
29
+ }
30
+ const showPhysicsModeIndicator = debug.physicsModeIndicator === true;
31
+ const physicsModeLabel = physicsMode === 'simulator' ? 'Simulator physics mode' : 'Arcade physics mode';
32
+ const physicsModeClass = physicsMode === 'simulator' ? 'simulator' : 'arcade';
33
+
34
+ return `
35
+ <section class="${classNames.join(' ')}" data-paddock-component="race-canvas" aria-label="Track view">
36
+ <div class="track-canvas" data-track-canvas></div>
37
+ ${showPhysicsModeIndicator ? `
38
+ <div class="physics-mode-indicator physics-mode-indicator--${physicsModeClass}" data-physics-mode-indicator aria-label="${physicsModeLabel}" title="${physicsModeLabel}"></div>
39
+ ` : ''}
40
+ ${includeTimingTower ? createTimingTowerMarkup({ totalLaps, assets }) : ''}
41
+ ${showFps ? `
42
+ <div class="fps-counter" aria-label="Frames per second">
43
+ <span>FPS</span>
44
+ <strong data-fps-readout>--</strong>
45
+ </div>
46
+ ` : ''}
47
+ <div class="start-lights" data-start-lights aria-live="polite">
48
+ <div class="start-lights__label" data-start-lights-label>Race start</div>
49
+ <div class="start-lights__gantry" aria-hidden="true">
50
+ <span></span>
51
+ <span></span>
52
+ <span></span>
53
+ <span></span>
54
+ <span></span>
55
+ </div>
56
+ </div>
57
+ ${createStewardMessageMarkup()}
58
+ ${showEmbeddedCameraControls ? createCameraControlsMarkup({ embedded: true, ui }) : ''}
59
+ ${includeRaceDataPanel ? createRaceDataPanelMarkup({ assets, ui }) : ''}
60
+ ${includeTelemetrySectorBanner ? createTelemetrySectorBannerMarkup({ ui }) : ''}
61
+ <div class="race-finish-panel" data-race-finish-panel hidden aria-live="polite">
62
+ <span>Race winner</span>
63
+ <strong data-race-finish-winner>--</strong>
64
+ <ol data-race-finish-classification></ol>
65
+ </div>
66
+ ${createLoadingMarkup('Race view')}
67
+ </section>
68
+ `;
69
+ }
@@ -0,0 +1,36 @@
1
+ import { createLoadingMarkup, escapeHtml } from './templateUtils.js';
2
+
3
+ function buttonHiddenAttribute(isVisible) {
4
+ return isVisible ? '' : ' hidden';
5
+ }
6
+
7
+ export function createRaceControlsMarkup({
8
+ title,
9
+ kicker,
10
+ backLinkHref,
11
+ backLinkLabel,
12
+ showBackLink,
13
+ }) {
14
+ return `
15
+ <header class="sim-topbar" data-paddock-component="race-controls">
16
+ <a class="sim-backlink" href="${escapeHtml(backLinkHref)}"${buttonHiddenAttribute(showBackLink)}>${escapeHtml(backLinkLabel)}</a>
17
+ <div class="sim-title-block">
18
+ <p class="sim-kicker">${escapeHtml(kicker)}</p>
19
+ <h1>${escapeHtml(title)}</h1>
20
+ </div>
21
+ <div class="sim-controls" aria-label="Race controls">
22
+ ${createSafetyCarControlMarkup({ compact: true })}
23
+ <button class="sim-control" type="button" data-restart-race>Restart</button>
24
+ </div>
25
+ ${createLoadingMarkup('Race controls')}
26
+ </header>
27
+ `;
28
+ }
29
+
30
+ export function createSafetyCarControlMarkup({ compact = false } = {}) {
31
+ const className = compact
32
+ ? 'sim-control sim-control--safety'
33
+ : 'sim-control sim-control--safety standalone-control';
34
+ const componentAttribute = compact ? '' : ' data-paddock-component="safety-car-control"';
35
+ return `<button class="${className}" type="button" data-safety-car aria-pressed="false"${componentAttribute}>Safety Car</button>`;
36
+ }
@@ -11,9 +11,10 @@ export function createF1SimulatorShell({
11
11
  kicker,
12
12
  backLinkHref,
13
13
  backLinkLabel,
14
- showBackLink,
15
- totalLaps,
16
- assets,
14
+ showBackLink,
15
+ totalLaps,
16
+ physicsMode,
17
+ assets,
17
18
  ui = {},
18
19
  }) {
19
20
  const layoutPreset = ui.layoutPreset === 'left-tower-overlay' ? 'left-tower-overlay' : 'standard';
@@ -34,7 +35,7 @@ export function createF1SimulatorShell({
34
35
  ${cameraControls}
35
36
  <div class="sim-grid">
36
37
  ${createTimingTowerMarkup({ totalLaps, assets })}
37
- ${createRaceCanvasMarkup({ includeRaceDataPanel: ui.showRaceDataPanel !== false, assets, ui })}
38
+ ${createRaceCanvasMarkup({ includeRaceDataPanel: ui.showRaceDataPanel !== false, assets, totalLaps, physicsMode, ui })}
38
39
  ${createTelemetryPanelMarkup({ assets, ui })}
39
40
  </div>
40
41
  </section>