@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.
Files changed (203) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/INSTALL_AND_UPDATE.md +4 -3
  3. package/README.md +219 -12
  4. package/docs/architecture.md +126 -45
  5. package/docs/concepts.md +9 -5
  6. package/docs/custom_model_controller.md +329 -0
  7. package/docs/data_contract.md +317 -22
  8. package/docs/data_model.md +1 -1
  9. package/docs/index.md +10 -0
  10. package/docs/learnings.md +22 -0
  11. package/docs/rules.md +71 -14
  12. package/docs/sense_contract.md +59 -0
  13. package/docs/system_specs.md +75 -13
  14. package/docs/training.md +280 -32
  15. package/examples/python/README.md +62 -0
  16. package/examples/python/base_policy_server.py +191 -0
  17. package/examples/python/requirements.txt +2 -0
  18. package/package.json +12 -4
  19. package/src/api/PaddockSimulatorController.js +6 -33
  20. package/src/app/BrowserExpertAdapter.js +76 -9
  21. package/src/app/BrowserExpertExternalRenderer.js +303 -0
  22. package/src/app/F1SimulatorApp.js +291 -1630
  23. package/src/app/banners/bannerFormatters.js +55 -0
  24. package/src/app/banners/raceDataBannerController.js +141 -0
  25. package/src/app/banners/stewardMessageController.js +99 -0
  26. package/src/app/camera/cameraConstants.js +21 -0
  27. package/src/app/camera/cameraController.js +265 -0
  28. package/src/app/camera/cameraGeometry.js +176 -0
  29. package/src/app/domBindings.js +4 -0
  30. package/src/app/readouts/carOverviewRenderer.js +109 -0
  31. package/src/app/readouts/controlStateRenderer.js +37 -0
  32. package/src/app/readouts/raceStatusRenderer.js +133 -0
  33. package/src/app/readouts/readoutFormatters.js +58 -0
  34. package/src/app/readouts/telemetryRenderer.js +113 -0
  35. package/src/app/readouts/timingTowerRenderer.js +177 -0
  36. package/src/app/rendering/appAssets.js +38 -0
  37. package/src/app/rendering/carRenderer.js +286 -0
  38. package/src/app/rendering/displayUtils.js +22 -0
  39. package/src/app/rendering/drsTrailRenderer.js +90 -0
  40. package/src/app/rendering/expertSensorRenderer.js +126 -0
  41. package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
  42. package/src/app/rendering/replayGhostRenderer.js +124 -0
  43. package/src/app/rendering/trackRenderer.js +25 -0
  44. package/src/app/runtime/frameLoop.js +66 -0
  45. package/src/app/runtime/runtimeTiming.js +34 -0
  46. package/src/app/runtime/runtimeVisibility.js +45 -0
  47. package/src/config/defaultOptions.js +20 -0
  48. package/src/config/restartOptions.js +34 -0
  49. package/src/environment/actions.js +13 -5
  50. package/src/environment/controllerLoop.js +261 -0
  51. package/src/environment/episode.js +98 -2
  52. package/src/environment/evaluation.js +54 -32
  53. package/src/environment/index.d.ts +376 -10
  54. package/src/environment/index.js +1 -0
  55. package/src/environment/metrics.js +159 -0
  56. package/src/environment/observationOptions.js +22 -1
  57. package/src/environment/observationVector.js +200 -0
  58. package/src/environment/observations.js +199 -141
  59. package/src/environment/options.js +37 -5
  60. package/src/environment/recorder.js +13 -6
  61. package/src/environment/rewards.js +13 -2
  62. package/src/environment/runtime.js +272 -23
  63. package/src/environment/scenarios.js +57 -9
  64. package/src/environment/sensors/bodySenses.js +23 -0
  65. package/src/environment/sensors/boundarySenses.js +17 -0
  66. package/src/environment/sensors/carRays.js +75 -0
  67. package/src/environment/sensors/contactSenses.js +32 -0
  68. package/src/environment/sensors/index.js +157 -0
  69. package/src/environment/sensors/indexedRayBands.js +103 -0
  70. package/src/environment/sensors/nearbyCars.js +52 -0
  71. package/src/environment/sensors/opponentRadar.js +75 -0
  72. package/src/environment/sensors/rayConfig.js +110 -0
  73. package/src/environment/sensors/rayDefaults.js +6 -0
  74. package/src/environment/sensors/rayGeometry.js +60 -0
  75. package/src/environment/sensors/rayGuards.js +19 -0
  76. package/src/environment/sensors/sensorTargets.js +66 -0
  77. package/src/environment/sensors/surfaceRays.js +360 -0
  78. package/src/environment/sensors/trackRays.js +424 -0
  79. package/src/environment/sensors.js +1 -367
  80. package/src/environment/specs.js +159 -26
  81. package/src/environment/workerProtocol.js +13 -1
  82. package/src/index.d.ts +171 -6
  83. package/src/index.js +6 -32
  84. package/src/rendering/proceduralTrackAsset.js +20 -823
  85. package/src/rendering/renderSnapshot.js +38 -12
  86. package/src/rendering/track/finishLineRenderer.js +34 -0
  87. package/src/rendering/track/gridRenderer.js +47 -0
  88. package/src/rendering/track/offsetStrokeSafety.js +136 -0
  89. package/src/rendering/track/pitLaneRenderer.js +248 -0
  90. package/src/rendering/track/trackMaterialRenderer.js +326 -0
  91. package/src/rendering/track/trackRenderConstants.js +38 -0
  92. package/src/rendering/track/trackRenderGeometry.js +94 -0
  93. package/src/simulation/collisionGeometry.js +1 -1
  94. package/src/simulation/driver/driverControlConstants.js +28 -0
  95. package/src/simulation/driver/driverController.js +47 -0
  96. package/src/simulation/driver/driverInput.js +28 -0
  97. package/src/simulation/driver/driverMath.js +6 -0
  98. package/src/simulation/driver/driverPersonality.js +25 -0
  99. package/src/simulation/driver/edgeRecovery.js +48 -0
  100. package/src/simulation/driver/racingControls.js +399 -0
  101. package/src/simulation/driver/racingLinePlan.js +166 -0
  102. package/src/simulation/driver/recoveryDynamics.js +38 -0
  103. package/src/simulation/driver/rejoinControls.js +213 -0
  104. package/src/simulation/driver/safetyCarControls.js +35 -0
  105. package/src/simulation/driver/trafficScan.js +51 -0
  106. package/src/simulation/driverController.js +4 -619
  107. package/src/simulation/participants/participantInteractions.js +120 -0
  108. package/src/simulation/pit/pitFlow.js +239 -0
  109. package/src/simulation/pit/pitIntent.js +92 -0
  110. package/src/simulation/pit/pitKinematics.js +24 -0
  111. package/src/simulation/pit/pitOccupancy.js +47 -0
  112. package/src/simulation/pit/pitPenaltyService.js +71 -0
  113. package/src/simulation/pit/pitQueue.js +60 -0
  114. package/src/simulation/pit/pitRouteMovement.js +167 -0
  115. package/src/simulation/pit/pitRouting.js +319 -0
  116. package/src/simulation/pit/pitService.js +43 -0
  117. package/src/simulation/pit/pitServiceConstants.js +21 -0
  118. package/src/simulation/pit/pitServiceProfile.js +45 -0
  119. package/src/simulation/pit/pitSnapshots.js +61 -0
  120. package/src/simulation/pit/pitState.js +113 -0
  121. package/src/simulation/pit/pitTireService.js +94 -0
  122. package/src/simulation/race/classification.js +104 -0
  123. package/src/simulation/race/gridPenalties.js +37 -0
  124. package/src/simulation/race/raceControlState.js +38 -0
  125. package/src/simulation/race/raceDistance.js +32 -0
  126. package/src/simulation/race/raceFinish.js +65 -0
  127. package/src/simulation/race/raceLifecycle.js +67 -0
  128. package/src/simulation/race/raceOrder.js +113 -0
  129. package/src/simulation/race/raceProgress.js +129 -0
  130. package/src/simulation/race/raceSetup.js +93 -0
  131. package/src/simulation/race/raceStep.js +80 -0
  132. package/src/simulation/race/redFlag.js +15 -0
  133. package/src/simulation/race/retirements.js +44 -0
  134. package/src/simulation/race/safetyCar.js +92 -0
  135. package/src/simulation/race/stalledDnf.js +77 -0
  136. package/src/simulation/raceSimulation.js +231 -2817
  137. package/src/simulation/replay/replayGhosts.js +128 -0
  138. package/src/simulation/rules/moduleConfig.js +72 -0
  139. package/src/simulation/rules/penaltyConfig.js +99 -0
  140. package/src/simulation/rules/penaltyEvents.js +18 -0
  141. package/src/simulation/rules/penaltyStats.js +26 -0
  142. package/src/simulation/rules/ruleConfigMerge.js +41 -0
  143. package/src/simulation/rules/ruleDefaults.js +85 -0
  144. package/src/simulation/rules/rulesReview.js +144 -0
  145. package/src/simulation/rules/rulesetPresets.js +47 -0
  146. package/src/simulation/rules/trackLimitsSteward.js +1 -1
  147. package/src/simulation/rulesConfig.js +5 -323
  148. package/src/simulation/snapshots/raceSnapshots.js +193 -0
  149. package/src/simulation/timing/drsTiming.js +87 -0
  150. package/src/simulation/timing/gapEstimation.js +38 -0
  151. package/src/simulation/timing/lapTelemetry.js +182 -0
  152. package/src/simulation/timing/raceTiming.js +15 -0
  153. package/src/simulation/timing/sectorPerformance.js +60 -0
  154. package/src/simulation/timing/timingConstants.js +5 -0
  155. package/src/simulation/timing/timingHistory.js +46 -0
  156. package/src/simulation/timing/timingLines.js +79 -0
  157. package/src/simulation/track/centerlineSpline.js +59 -0
  158. package/src/simulation/track/drsZones.js +67 -0
  159. package/src/simulation/track/pitLaneAccess.js +103 -0
  160. package/src/simulation/track/pitLaneLayout.js +395 -0
  161. package/src/simulation/track/pitLaneState.js +160 -0
  162. package/src/simulation/track/proceduralCenterline.js +237 -0
  163. package/src/simulation/track/sampleGeometry.js +49 -0
  164. package/src/simulation/track/spatialQueries.js +154 -0
  165. package/src/simulation/track/startStraight.js +141 -0
  166. package/src/simulation/track/trackConstants.js +109 -0
  167. package/src/simulation/track/trackGenerationOptions.js +283 -0
  168. package/src/simulation/track/trackMath.js +241 -0
  169. package/src/simulation/track/trackModel.js +240 -0
  170. package/src/simulation/track/trackQueryGrid.js +94 -0
  171. package/src/simulation/track/trackQueryIndex.js +659 -0
  172. package/src/simulation/track/trackQueryPitIndex.js +140 -0
  173. package/src/simulation/track/trackQueryScratch.js +46 -0
  174. package/src/simulation/track/trackQueryStats.js +28 -0
  175. package/src/simulation/track/trackSectors.js +19 -0
  176. package/src/simulation/track/trackStatePolicy.js +24 -0
  177. package/src/simulation/track/trackValidation.js +98 -0
  178. package/src/simulation/trackModel.js +1 -1706
  179. package/src/simulation/vehicle/contactResolution.js +181 -0
  180. package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
  181. package/src/simulation/vehicle/pitWheelSurface.js +69 -0
  182. package/src/simulation/vehicle/runoffResponse.js +87 -0
  183. package/src/simulation/vehicle/surfacePriority.js +28 -0
  184. package/src/simulation/vehicle/vehicleGeometry.js +247 -0
  185. package/src/simulation/vehicle/vehicleKinematics.js +37 -0
  186. package/src/simulation/vehicle/vehiclePhysics.js +364 -0
  187. package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
  188. package/src/simulation/vehicle/vehicleState.js +220 -0
  189. package/src/simulation/vehicle/wheelSurface.js +152 -0
  190. package/src/simulation/vehicleGeometry.js +1 -247
  191. package/src/simulation/vehiclePhysics.js +1 -136
  192. package/src/simulation/warmup/runtimeWarmup.js +224 -0
  193. package/src/simulation/wheelSurface.js +1 -338
  194. package/src/styles.css +42 -0
  195. package/src/ui/cameraControlsTemplate.js +20 -0
  196. package/src/ui/carOverviewTemplate.js +33 -0
  197. package/src/ui/componentTemplates.js +14 -360
  198. package/src/ui/raceCanvasTemplate.js +69 -0
  199. package/src/ui/raceControlsTemplate.js +36 -0
  200. package/src/ui/shellTemplate.js +5 -4
  201. package/src/ui/telemetryDrawerTemplate.js +55 -0
  202. package/src/ui/telemetryTemplates.js +136 -0
  203. package/src/ui/timingTowerTemplate.js +34 -0
@@ -1,338 +1 @@
1
- import { nearestTrackState } from './trackModel.js';
2
- import { metersToSimUnits } from './units.js';
3
- import { getVehicleGeometryState } from './vehicleGeometry.js';
4
-
5
- const SURFACE_PRIORITY = {
6
- track: 0,
7
- 'pit-entry': 1,
8
- 'pit-exit': 1,
9
- 'pit-lane': 2,
10
- 'pit-box': 3,
11
- kerb: 4,
12
- grass: 5,
13
- gravel: 6,
14
- barrier: 7,
15
- };
16
- const PIT_CONNECTOR_FULL_SAMPLE_WINDOW = metersToSimUnits(35);
17
-
18
- function finiteSignatureValue(value) {
19
- return Number.isFinite(value) ? Number(value).toFixed(4) : '';
20
- }
21
-
22
- function centerStateSignature(centerState) {
23
- if (!centerState) return 'auto';
24
- return [
25
- finiteSignatureValue(centerState.distance),
26
- finiteSignatureValue(centerState.signedOffset),
27
- finiteSignatureValue(centerState.crossTrackError),
28
- centerState.surface ?? '',
29
- centerState.inPitLane ? 1 : 0,
30
- centerState.pitLanePart ?? '',
31
- centerState.pitBoxId ?? '',
32
- ].join(':');
33
- }
34
-
35
- function priority(surface) {
36
- return SURFACE_PRIORITY[surface] ?? SURFACE_PRIORITY.barrier;
37
- }
38
-
39
- function worstState(states) {
40
- return states.reduce((worst, state) => (
41
- priority(state.surface) > priority(worst.surface) ? state : worst
42
- ), states[0]);
43
- }
44
-
45
- function patchSamples(patch) {
46
- return [patch.center, ...patch.corners];
47
- }
48
-
49
- function wrappedDistanceDelta(first, second, length) {
50
- if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(length) || length <= 0) return Infinity;
51
- const delta = Math.abs(first - second);
52
- return Math.min(delta, length - delta);
53
- }
54
-
55
- function isNearPitConnector(track, centerState) {
56
- const pitLane = track.pitLane;
57
- if (!pitLane?.enabled) return false;
58
- const distance = centerState.distance;
59
- const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
60
- const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
61
- return wrappedDistanceDelta(distance, entryDistance, track.length) <= PIT_CONNECTOR_FULL_SAMPLE_WINDOW ||
62
- wrappedDistanceDelta(distance, exitDistance, track.length) <= PIT_CONNECTOR_FULL_SAMPLE_WINDOW;
63
- }
64
-
65
- function mainTrackSurface(track, crossTrackError) {
66
- const trackEdge = track.width / 2;
67
- const kerbEdge = trackEdge + (track.kerbWidth ?? 0);
68
- const gravelEdge = kerbEdge + track.gravelWidth;
69
- const runoffEdge = gravelEdge + track.runoffWidth;
70
- if (crossTrackError <= trackEdge) return 'track';
71
- if (crossTrackError <= kerbEdge) return 'kerb';
72
- if (crossTrackError <= gravelEdge) return 'gravel';
73
- if (crossTrackError <= runoffEdge) return 'grass';
74
- return 'barrier';
75
- }
76
-
77
- function localTrackState(track, centerState, point) {
78
- const signedOffset =
79
- (point.x - centerState.x) * centerState.normalX +
80
- (point.y - centerState.y) * centerState.normalY;
81
- const crossTrackError = Math.abs(signedOffset);
82
- const surface = mainTrackSurface(track, crossTrackError);
83
- return {
84
- ...centerState,
85
- signedOffset,
86
- crossTrackError,
87
- surface,
88
- onTrack: surface === 'track' || surface === 'kerb',
89
- inPitLane: false,
90
- pitLanePart: null,
91
- pitBoxId: null,
92
- };
93
- }
94
-
95
- function wheelFullyOutside(samples, trackLimit) {
96
- const rightOutside = samples.every((state) => !state.inPitLane && state.signedOffset > trackLimit);
97
- const leftOutside = samples.every((state) => !state.inPitLane && state.signedOffset < -trackLimit);
98
- return {
99
- rightOutside,
100
- leftOutside,
101
- fullyOutsideWhiteLine: rightOutside || leftOutside,
102
- outsideSide: rightOutside ? 1 : leftOutside ? -1 : 0,
103
- };
104
- }
105
-
106
- function wheelOutsideFromOffsets(minimumSignedOffset, maximumSignedOffset, trackLimit) {
107
- const rightOutside = minimumSignedOffset > trackLimit;
108
- const leftOutside = maximumSignedOffset < -trackLimit;
109
- return {
110
- rightOutside,
111
- leftOutside,
112
- fullyOutsideWhiteLine: rightOutside || leftOutside,
113
- outsideSide: rightOutside ? 1 : leftOutside ? -1 : 0,
114
- };
115
- }
116
-
117
- function stateFromSignedOffset(centerState, track, signedOffset) {
118
- const crossTrackError = Math.abs(signedOffset);
119
- const surface = mainTrackSurface(track, crossTrackError);
120
- return {
121
- ...centerState,
122
- signedOffset,
123
- crossTrackError,
124
- surface,
125
- onTrack: surface === 'track' || surface === 'kerb',
126
- inPitLane: false,
127
- pitLanePart: null,
128
- pitBoxId: null,
129
- };
130
- }
131
-
132
- function analyticWheelState(patch, centerState, track, trackLimit) {
133
- const wheelCenterOffset =
134
- (patch.center.x - centerState.x) * centerState.normalX +
135
- (patch.center.y - centerState.y) * centerState.normalY;
136
- const projectedHalfWidth =
137
- Math.abs(patch.forward.x * centerState.normalX + patch.forward.y * centerState.normalY) * patch.halfLength +
138
- Math.abs(patch.right.x * centerState.normalX + patch.right.y * centerState.normalY) * patch.halfWidth;
139
- const minimumSignedOffset = wheelCenterOffset - projectedHalfWidth;
140
- const maximumSignedOffset = wheelCenterOffset + projectedHalfWidth;
141
- const signedOffset = Math.abs(minimumSignedOffset) > Math.abs(maximumSignedOffset)
142
- ? minimumSignedOffset
143
- : maximumSignedOffset;
144
- const state = stateFromSignedOffset(centerState, track, signedOffset);
145
- const outside = wheelOutsideFromOffsets(minimumSignedOffset, maximumSignedOffset, trackLimit);
146
-
147
- return {
148
- id: patch.id,
149
- x: patch.center.x,
150
- y: patch.center.y,
151
- signedOffset: state.signedOffset,
152
- crossTrackError: state.crossTrackError,
153
- surface: state.surface,
154
- onTrack: Boolean(state.onTrack),
155
- inPitLane: false,
156
- pitLanePart: null,
157
- pitBoxId: null,
158
- minimumSignedOffset,
159
- maximumSignedOffset,
160
- fullyOutsideWhiteLine: outside.fullyOutsideWhiteLine,
161
- outsideSide: outside.outsideSide,
162
- sampledStates: [state],
163
- };
164
- }
165
-
166
- function patchPitOffsetRange(patch, centerState) {
167
- const wheelCenterOffset =
168
- (patch.center.x - centerState.x) * centerState.normalX +
169
- (patch.center.y - centerState.y) * centerState.normalY;
170
- const projectedHalfWidth =
171
- Math.abs(patch.forward.x * centerState.normalX + patch.forward.y * centerState.normalY) * patch.halfLength +
172
- Math.abs(patch.right.x * centerState.normalX + patch.right.y * centerState.normalY) * patch.halfWidth;
173
- return {
174
- minimum: wheelCenterOffset - projectedHalfWidth,
175
- maximum: wheelCenterOffset + projectedHalfWidth,
176
- };
177
- }
178
-
179
- function canUseAnalyticPitWheels(geometry, centerState) {
180
- const roadWidth = Number(centerState?.pitLaneRoadWidth);
181
- if (!centerState?.inPitLane || !Number.isFinite(roadWidth) || roadWidth <= 0) return false;
182
- const halfRoadWidth = roadWidth / 2;
183
- return geometry.contactPatches.every((patch) => {
184
- const range = patchPitOffsetRange(patch, centerState);
185
- return range.minimum >= -halfRoadWidth - 0.001 && range.maximum <= halfRoadWidth + 0.001;
186
- });
187
- }
188
-
189
- function analyticPitWheelState(patch, centerState) {
190
- const range = patchPitOffsetRange(patch, centerState);
191
- return {
192
- id: patch.id,
193
- x: patch.center.x,
194
- y: patch.center.y,
195
- signedOffset: centerState.mainTrackSignedOffset ?? centerState.signedOffset,
196
- crossTrackError: centerState.mainTrackCrossTrackError ?? centerState.crossTrackError,
197
- surface: centerState.surface,
198
- onTrack: true,
199
- inPitLane: true,
200
- pitLanePart: centerState.pitLanePart ?? null,
201
- pitBoxId: centerState.pitBoxId ?? null,
202
- minimumSignedOffset: centerState.mainTrackSignedOffset ?? centerState.signedOffset,
203
- maximumSignedOffset: centerState.mainTrackSignedOffset ?? centerState.signedOffset,
204
- pitLaneMinimumSignedOffset: range.minimum,
205
- pitLaneMaximumSignedOffset: range.maximum,
206
- fullyOutsideWhiteLine: false,
207
- outsideSide: 0,
208
- sampledStates: [centerState],
209
- };
210
- }
211
-
212
- export function getEffectiveSurface(wheels = []) {
213
- if (!wheels.length) return 'track';
214
- return wheels.reduce((surface, wheel) => (
215
- priority(wheel.surface) > priority(surface) ? wheel.surface : surface
216
- ), 'track');
217
- }
218
-
219
- export function isWholeCarOutsideTrackLimits(wheels = [], track, relaxedMargin = 0) {
220
- if (!wheels.length || !track) {
221
- return {
222
- violating: false,
223
- side: 0,
224
- outsideBy: 0,
225
- };
226
- }
227
- const trackLimit = track.width / 2 + relaxedMargin;
228
- const right = wheels.every((wheel) => wheel.outsideSide === 1);
229
- const left = wheels.every((wheel) => wheel.outsideSide === -1);
230
- if (!right && !left) {
231
- return {
232
- violating: false,
233
- side: 0,
234
- outsideBy: 0,
235
- };
236
- }
237
- const side = right ? 1 : -1;
238
- const outsideBy = right
239
- ? Math.min(...wheels.map((wheel) => wheel.minimumSignedOffset)) - trackLimit
240
- : -trackLimit - Math.max(...wheels.map((wheel) => wheel.maximumSignedOffset));
241
-
242
- return {
243
- violating: outsideBy > 0,
244
- side,
245
- outsideBy: Math.max(0, outsideBy),
246
- };
247
- }
248
-
249
- export function calculateWheelSurfaceState({ car, track, centerState: providedCenterState = null }) {
250
- const geometry = getVehicleGeometryState(car);
251
- const trackLimit = track.width / 2;
252
- const centerState = providedCenterState ?? nearestTrackState(track, car, car.progress);
253
- const useAnalyticPitSampling = canUseAnalyticPitWheels(geometry, centerState);
254
- const useFullSampling = !useAnalyticPitSampling && Boolean(centerState.inPitLane || isNearPitConnector(track, centerState));
255
- const wheels = useAnalyticPitSampling
256
- ? geometry.contactPatches.map((patch) => analyticPitWheelState(patch, centerState))
257
- : useFullSampling
258
- ? geometry.contactPatches.map((patch) => {
259
- const sampleState = (point) => nearestTrackState(track, point, car.progress);
260
- const sampledStates = patchSamples(patch).map(sampleState);
261
- const state = worstState(sampledStates);
262
- const offsets = sampledStates.map((sample) => sample.signedOffset);
263
- const outside = wheelFullyOutside(sampledStates, trackLimit);
264
-
265
- return {
266
- id: patch.id,
267
- x: patch.center.x,
268
- y: patch.center.y,
269
- signedOffset: state.signedOffset,
270
- crossTrackError: state.crossTrackError,
271
- surface: state.surface,
272
- onTrack: Boolean(state.onTrack),
273
- inPitLane: Boolean(state.inPitLane),
274
- pitLanePart: state.pitLanePart ?? null,
275
- pitBoxId: state.pitBoxId ?? null,
276
- minimumSignedOffset: Math.min(...offsets),
277
- maximumSignedOffset: Math.max(...offsets),
278
- fullyOutsideWhiteLine: outside.fullyOutsideWhiteLine,
279
- outsideSide: outside.outsideSide,
280
- sampledStates,
281
- };
282
- })
283
- : geometry.contactPatches.map((patch) => analyticWheelState(patch, centerState, track, trackLimit));
284
- const effectiveSurface = getEffectiveSurface(wheels);
285
- const representative = wheels.reduce((best, wheel) => (
286
- Math.abs(wheel.signedOffset) > Math.abs(best.signedOffset) ? wheel : best
287
- ), wheels[0]);
288
- const trackLimits = isWholeCarOutsideTrackLimits(wheels, track);
289
-
290
- return {
291
- wheels,
292
- effectiveSurface,
293
- trackLimits,
294
- sampleMode: useAnalyticPitSampling ? 'pit-analytic' : useFullSampling ? 'full' : 'analytic',
295
- representativeState: representative ? {
296
- ...centerState,
297
- surface: effectiveSurface,
298
- onTrack: wheels.every((wheel) => wheel.onTrack || wheel.inPitLane),
299
- inPitLane: wheels.every((wheel) => wheel.inPitLane),
300
- signedOffset: representative.signedOffset,
301
- crossTrackError: representative.crossTrackError,
302
- } : nearestTrackState(track, car, car.progress),
303
- };
304
- }
305
-
306
- export function applyWheelSurfaceState(car, track, options = {}) {
307
- const geometry = getVehicleGeometryState(car);
308
- const cacheKey = {
309
- track,
310
- geometrySignature: geometry.signature,
311
- centerSignature: centerStateSignature(options.centerState),
312
- };
313
- const cached = car.wheelSurfaceCache;
314
- if (
315
- cached?.track === cacheKey.track &&
316
- cached.geometrySignature === cacheKey.geometrySignature &&
317
- cached.centerSignature === cacheKey.centerSignature
318
- ) {
319
- car.wheelStates = cached.result.wheels;
320
- car.trackLimitState = cached.result.trackLimits;
321
- car.trackState = cached.trackState;
322
- return cached.result;
323
- }
324
-
325
- const result = calculateWheelSurfaceState({ car, track, centerState: options.centerState });
326
- car.wheelStates = result.wheels;
327
- car.trackLimitState = result.trackLimits;
328
- car.trackState = {
329
- ...result.representativeState,
330
- wheelSurface: result.effectiveSurface,
331
- };
332
- car.wheelSurfaceCache = {
333
- ...cacheKey,
334
- result,
335
- trackState: car.trackState,
336
- };
337
- return result;
338
- }
1
+ export * from './vehicle/wheelSurface.js';
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
+ }