@inventure71/paddockjs 1.0.0 → 2.0.1
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 +58 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +219 -12
- package/docs/architecture.md +126 -45
- package/docs/concepts.md +9 -5
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +317 -22
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +71 -14
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +75 -13
- package/docs/training.md +280 -32
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +6 -33
- package/src/app/BrowserExpertAdapter.js +76 -9
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +291 -1630
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +4 -0
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +20 -0
- package/src/config/restartOptions.js +34 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +261 -0
- package/src/environment/episode.js +98 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +376 -10
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +199 -141
- package/src/environment/options.js +37 -5
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +272 -23
- package/src/environment/scenarios.js +57 -9
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +75 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +66 -0
- package/src/environment/sensors/surfaceRays.js +360 -0
- package/src/environment/sensors/trackRays.js +424 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +159 -26
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +171 -6
- package/src/index.js +6 -32
- package/src/rendering/proceduralTrackAsset.js +20 -823
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +1 -1
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -619
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +239 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +60 -0
- package/src/simulation/pit/pitRouteMovement.js +167 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +43 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +94 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +67 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +129 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +92 -0
- package/src/simulation/race/stalledDnf.js +77 -0
- package/src/simulation/raceSimulation.js +231 -2817
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +144 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- package/src/simulation/snapshots/raceSnapshots.js +193 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +659 -0
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -1706
- package/src/simulation/vehicle/contactResolution.js +181 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +87 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +364 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
- package/src/simulation/vehicle/vehicleState.js +220 -0
- package/src/simulation/vehicle/wheelSurface.js +152 -0
- package/src/simulation/vehicleGeometry.js +1 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -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
|
+
}
|
package/src/ui/shellTemplate.js
CHANGED
|
@@ -11,9 +11,10 @@ export function createF1SimulatorShell({
|
|
|
11
11
|
kicker,
|
|
12
12
|
backLinkHref,
|
|
13
13
|
backLinkLabel,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
38
|
+
${createRaceCanvasMarkup({ includeRaceDataPanel: ui.showRaceDataPanel !== false, assets, totalLaps, physicsMode, ui })}
|
|
38
39
|
${createTelemetryPanelMarkup({ assets, ui })}
|
|
39
40
|
</div>
|
|
40
41
|
</section>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createCameraControlsMarkup } from './cameraControlsTemplate.js';
|
|
2
|
+
import { createRaceCanvasMarkup } from './raceCanvasTemplate.js';
|
|
3
|
+
import { createSafetyCarControlMarkup } from './raceControlsTemplate.js';
|
|
4
|
+
import { createTelemetryPanelMarkup } from './telemetryTemplates.js';
|
|
5
|
+
|
|
6
|
+
let telemetryDrawerIdSequence = 0;
|
|
7
|
+
|
|
8
|
+
function createTelemetryDrawerId() {
|
|
9
|
+
telemetryDrawerIdSequence += 1;
|
|
10
|
+
return `paddock-telemetry-drawer-${telemetryDrawerIdSequence}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createRaceTelemetryDrawerMarkup(options, {
|
|
14
|
+
timingTowerVerticalFit,
|
|
15
|
+
drawerInitiallyOpen = false,
|
|
16
|
+
raceDataTelemetryDetail = options.ui?.raceDataTelemetryDetail,
|
|
17
|
+
} = {}) {
|
|
18
|
+
const openClass = drawerInitiallyOpen ? ' is-telemetry-open' : '';
|
|
19
|
+
const drawerId = createTelemetryDrawerId();
|
|
20
|
+
const drawerOptions = {
|
|
21
|
+
...options,
|
|
22
|
+
ui: {
|
|
23
|
+
...(options.ui ?? {}),
|
|
24
|
+
cameraControls: false,
|
|
25
|
+
raceDataTelemetryDetail: Boolean(raceDataTelemetryDetail),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const showCameraControls = options.ui?.cameraControls !== false;
|
|
29
|
+
return `
|
|
30
|
+
<section class="race-telemetry-drawer${openClass}" data-paddock-component="race-telemetry-drawer" data-race-telemetry-drawer aria-label="Race view with telemetry drawer">
|
|
31
|
+
<div class="race-telemetry-drawer__toolbar" aria-label="Race workbench controls">
|
|
32
|
+
${showCameraControls ? createCameraControlsMarkup({ showSimulationSpeed: true, ui: options.ui }) : ''}
|
|
33
|
+
<div class="race-telemetry-drawer__controls">
|
|
34
|
+
${createSafetyCarControlMarkup({ compact: true })}
|
|
35
|
+
<button class="telemetry-drawer-toggle" type="button" data-telemetry-drawer-toggle aria-expanded="${drawerInitiallyOpen ? 'true' : 'false'}" aria-controls="${drawerId}">
|
|
36
|
+
${drawerInitiallyOpen ? 'Close telemetry' : 'Telemetry'}
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="race-telemetry-drawer__race">
|
|
41
|
+
${createRaceCanvasMarkup({
|
|
42
|
+
...drawerOptions,
|
|
43
|
+
includeRaceDataPanel: true,
|
|
44
|
+
includeTimingTower: true,
|
|
45
|
+
timingTowerVerticalFit,
|
|
46
|
+
})}
|
|
47
|
+
</div>
|
|
48
|
+
<aside id="${drawerId}" class="telemetry-drawer" data-telemetry-drawer aria-label="Telemetry drawer" aria-hidden="${drawerInitiallyOpen ? 'false' : 'true'}"${drawerInitiallyOpen ? '' : ' inert'}>
|
|
49
|
+
<div class="telemetry-drawer__content">
|
|
50
|
+
${createTelemetryPanelMarkup(options, { includeOverview: false })}
|
|
51
|
+
</div>
|
|
52
|
+
</aside>
|
|
53
|
+
</section>
|
|
54
|
+
`;
|
|
55
|
+
}
|