@inventure71/paddockjs 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/INSTALL_AND_UPDATE.md +10 -1
- package/README.md +92 -17
- package/docs/architecture.md +26 -16
- package/docs/component_inventory.md +72 -0
- package/docs/concepts.md +7 -5
- package/docs/custom_model_controller.md +2 -2
- package/docs/data_contract.md +121 -33
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +6 -6
- package/docs/rules.md +39 -9
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +76 -20
- package/docs/training.md +27 -20
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +75 -36
- package/src/app/BrowserExpertAdapter.js +9 -300
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +190 -68
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +62 -10
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +51 -38
- package/src/config/restartOptions.js +34 -0
- package/src/config/themeOptions.js +825 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/environment/controllerLoop.js +34 -5
- package/src/environment/episode.js +14 -2
- package/src/environment/index.d.ts +17 -4
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +58 -285
- package/src/environment/options.js +1 -1
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +65 -25
- package/src/environment/scenarios.js +22 -3
- package/src/environment/sensors/index.js +3 -3
- package/src/environment/sensors/opponentRadar.js +13 -3
- package/src/environment/sensors/sensorTargets.js +8 -2
- package/src/environment/sensors/surfaceRays.js +84 -12
- package/src/environment/sensors/trackRays.js +173 -15
- package/src/environment/specs.js +78 -40
- package/src/index.d.ts +112 -9
- package/src/index.js +26 -33
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +5 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitQueue.js +5 -0
- package/src/simulation/pit/pitRouteMovement.js +7 -0
- package/src/simulation/pit/pitRouting.js +2 -4
- package/src/simulation/pit/pitService.js +2 -0
- package/src/simulation/pit/pitTireService.js +2 -0
- package/src/simulation/race/raceLifecycle.js +2 -6
- package/src/simulation/race/raceProgress.js +6 -1
- package/src/simulation/race/raceSetup.js +2 -6
- package/src/simulation/race/raceStep.js +2 -2
- package/src/simulation/race/redFlag.js +3 -3
- package/src/simulation/race/safetyCar.js +10 -4
- package/src/simulation/race/stalledDnf.js +8 -17
- package/src/simulation/raceSimulation.js +1 -0
- package/src/simulation/rules/rulesReview.js +3 -0
- package/src/simulation/snapshots/raceSnapshots.js +1 -0
- package/src/simulation/timing/lapTelemetry.js +28 -9
- package/src/simulation/track/pitLaneLayout.js +7 -3
- package/src/simulation/track/spatialQueries.js +5 -28
- package/src/simulation/track/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +193 -434
- 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/trackStatePolicy.js +0 -1
- package/src/simulation/vehicle/contactResolution.js +67 -2
- package/src/simulation/vehicle/runoffResponse.js +11 -12
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +5 -4
- package/src/simulation/vehicle/vehicleSnapshots.js +22 -0
- package/src/simulation/vehicle/vehicleState.js +36 -2
- package/src/simulation/vehicle/wheelSurface.js +3 -1
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1185 -136
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
package/src/environment/specs.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { normalizeLookaheadMeters } from './observationOptions.js';
|
|
2
|
+
import { effectiveSensorOptions } from './observations.js';
|
|
2
3
|
import { normalizeRayOptions } from './sensors.js';
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
+
const MODEL_RAY_SURFACE_CHANNELS = Object.freeze(['kerb', 'illegalSurface']);
|
|
5
6
|
|
|
6
7
|
export function buildActionSpec(options) {
|
|
7
8
|
const compounds = Array.isArray(options.rules?.modules?.tireStrategy?.compounds) &&
|
|
@@ -32,7 +33,7 @@ export function buildObservationSpec(options) {
|
|
|
32
33
|
: normalizeLookaheadMeters(options.observation?.lookaheadMeters);
|
|
33
34
|
const profile = options.observation?.profile ?? 'default';
|
|
34
35
|
return {
|
|
35
|
-
version: profile === 'physical-driver' ?
|
|
36
|
+
version: profile === 'physical-driver' ? 6 : 3,
|
|
36
37
|
controlledDrivers: [...options.controlledDrivers],
|
|
37
38
|
object: {
|
|
38
39
|
profile,
|
|
@@ -45,12 +46,15 @@ export function buildObservationSpec(options) {
|
|
|
45
46
|
{ name: 'steeringAngleRadians', unit: 'rad' },
|
|
46
47
|
{ name: 'throttle', unit: 'normalized' },
|
|
47
48
|
{ name: 'brake', unit: 'normalized' },
|
|
49
|
+
{ name: 'appliedControls', unit: 'nullable:normalized-controls' },
|
|
48
50
|
{ name: 'lateralG', unit: 'g' },
|
|
49
51
|
{ name: 'longitudinalG', unit: 'g' },
|
|
50
52
|
{ name: 'gripUsage', unit: 'ratio' },
|
|
51
53
|
{ name: 'slipAngleRadians', unit: 'rad' },
|
|
52
54
|
{ name: 'tractionLimited', unit: 'boolean' },
|
|
53
55
|
{ name: 'stabilityState', unit: 'label' },
|
|
56
|
+
{ name: 'destroyed', unit: 'boolean' },
|
|
57
|
+
{ name: 'destroyReason', unit: 'nullable:label' },
|
|
54
58
|
{ name: 'lap', unit: 'count' },
|
|
55
59
|
{ name: 'completedLaps', unit: 'count' },
|
|
56
60
|
{ name: 'lapProgressMeters', unit: 'm' },
|
|
@@ -99,40 +103,8 @@ export function buildObservationSpec(options) {
|
|
|
99
103
|
{ name: 'redFlag', unit: 'boolean' },
|
|
100
104
|
{ name: 'totalLaps', unit: 'count' },
|
|
101
105
|
],
|
|
102
|
-
rays:
|
|
103
|
-
|
|
104
|
-
anglesDegrees: [...rayOptions.anglesDegrees],
|
|
105
|
-
lengthMeters: rayOptions.lengthMeters,
|
|
106
|
-
defaultLengthMeters: rayOptions.defaultLengthMeters,
|
|
107
|
-
rays: rayOptions.rays.map((ray) => ({ ...ray })),
|
|
108
|
-
channels: [...rayOptions.channels],
|
|
109
|
-
precision: rayOptions.precision,
|
|
110
|
-
track: {
|
|
111
|
-
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
112
|
-
hit: { unit: 'boolean' },
|
|
113
|
-
kind: { values: ['exit', 'entry', null] },
|
|
114
|
-
},
|
|
115
|
-
roadEdge: {
|
|
116
|
-
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
117
|
-
hit: { unit: 'boolean' },
|
|
118
|
-
kind: { values: ['exit', 'entry', null] },
|
|
119
|
-
},
|
|
120
|
-
kerb: surfaceRaySpec(rayOptions.lengthMeters),
|
|
121
|
-
illegalSurface: surfaceRaySpec(rayOptions.lengthMeters),
|
|
122
|
-
car: {
|
|
123
|
-
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
124
|
-
hit: { unit: 'boolean' },
|
|
125
|
-
driverId: { nullable: true },
|
|
126
|
-
targetId: { nullable: true },
|
|
127
|
-
targetType: { values: ['car', 'replayGhost', null] },
|
|
128
|
-
relativeSpeedKph: { unit: 'kph' },
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
nearbyCars: {
|
|
132
|
-
enabled: Boolean(nearbyOptions.enabled),
|
|
133
|
-
maxCars: nearbyOptions.maxCars,
|
|
134
|
-
radiusMeters: nearbyOptions.radiusMeters,
|
|
135
|
-
},
|
|
106
|
+
rays: buildRayObjectSpec(rayOptions),
|
|
107
|
+
nearbyCars: buildNearbyObjectSpec(nearbyOptions),
|
|
136
108
|
track: {
|
|
137
109
|
lengthMeters: { unit: 'm' },
|
|
138
110
|
widthMeters: { unit: 'm' },
|
|
@@ -149,6 +121,69 @@ export function buildObservationSpec(options) {
|
|
|
149
121
|
vector: {
|
|
150
122
|
schema: buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile }),
|
|
151
123
|
},
|
|
124
|
+
perDriver: buildPerDriverObservationSpecs(options, { rayOptions, nearbyOptions, lookaheadMeters, profile }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function buildPerDriverObservationSpecs(options, { rayOptions, nearbyOptions, lookaheadMeters, profile }) {
|
|
129
|
+
if (Object.keys(options.sensorsByDriver ?? {}).length === 0) return {};
|
|
130
|
+
const defaultSensors = { rays: rayOptions, nearbyCars: nearbyOptions };
|
|
131
|
+
return Object.fromEntries(options.controlledDrivers.map((driverId) => {
|
|
132
|
+
const sensors = effectiveSensorOptions(options, driverId, defaultSensors, true);
|
|
133
|
+
return [driverId, {
|
|
134
|
+
object: {
|
|
135
|
+
rays: buildRayObjectSpec(sensors.rays),
|
|
136
|
+
nearbyCars: buildNearbyObjectSpec(sensors.nearbyCars),
|
|
137
|
+
},
|
|
138
|
+
vector: {
|
|
139
|
+
schema: buildVectorSchema({
|
|
140
|
+
rayOptions: sensors.rays,
|
|
141
|
+
nearbyOptions: sensors.nearbyCars,
|
|
142
|
+
lookaheadMeters,
|
|
143
|
+
profile,
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
}];
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildRayObjectSpec(rayOptions) {
|
|
151
|
+
return {
|
|
152
|
+
enabled: Boolean(rayOptions.enabled),
|
|
153
|
+
anglesDegrees: [...rayOptions.anglesDegrees],
|
|
154
|
+
lengthMeters: rayOptions.lengthMeters,
|
|
155
|
+
defaultLengthMeters: rayOptions.defaultLengthMeters,
|
|
156
|
+
rays: rayOptions.rays.map((ray) => ({ ...ray })),
|
|
157
|
+
channels: [...rayOptions.channels],
|
|
158
|
+
precision: rayOptions.precision,
|
|
159
|
+
track: {
|
|
160
|
+
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
161
|
+
hit: { unit: 'boolean' },
|
|
162
|
+
kind: { values: ['exit', 'entry', null] },
|
|
163
|
+
},
|
|
164
|
+
roadEdge: {
|
|
165
|
+
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
166
|
+
hit: { unit: 'boolean' },
|
|
167
|
+
kind: { values: ['exit', 'entry', null] },
|
|
168
|
+
},
|
|
169
|
+
kerb: surfaceRaySpec(rayOptions.lengthMeters),
|
|
170
|
+
illegalSurface: surfaceRaySpec(rayOptions.lengthMeters),
|
|
171
|
+
car: {
|
|
172
|
+
distanceMeters: { unit: 'm', noHitValue: rayOptions.lengthMeters },
|
|
173
|
+
hit: { unit: 'boolean' },
|
|
174
|
+
driverId: { nullable: true },
|
|
175
|
+
targetId: { nullable: true },
|
|
176
|
+
targetType: { values: ['car', 'replayGhost', null] },
|
|
177
|
+
relativeSpeedKph: { unit: 'kph' },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildNearbyObjectSpec(nearbyOptions) {
|
|
183
|
+
return {
|
|
184
|
+
enabled: Boolean(nearbyOptions.enabled),
|
|
185
|
+
maxCars: nearbyOptions.maxCars,
|
|
186
|
+
radiusMeters: nearbyOptions.radiusMeters,
|
|
152
187
|
};
|
|
153
188
|
}
|
|
154
189
|
|
|
@@ -193,6 +228,7 @@ function buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile
|
|
|
193
228
|
{ name: `contactPatches[${index}].surfaceCode`, scale: 'surface-code' },
|
|
194
229
|
{ name: `contactPatches[${index}].onLegalSurface`, scale: 'boolean' },
|
|
195
230
|
{ name: `contactPatches[${index}].signedOffsetMeters`, unit: 'm', scale: 'fixed:meters' },
|
|
231
|
+
{ name: `contactPatches[${index}].crossTrackErrorMeters`, unit: 'm', scale: 'fixed:meters' },
|
|
196
232
|
);
|
|
197
233
|
}
|
|
198
234
|
}
|
|
@@ -212,15 +248,16 @@ function buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile
|
|
|
212
248
|
{ name: `rays[${index}].car.distanceRatio`, scale: '0..1' },
|
|
213
249
|
{ name: `rays[${index}].car.hit`, scale: 'boolean' },
|
|
214
250
|
{ name: `rays[${index}].car.relativeSpeedKph`, unit: 'kph', scale: 'fixed:200' },
|
|
251
|
+
{ name: `rays[${index}].car.targetTypeReplayGhost`, scale: 'boolean' },
|
|
215
252
|
);
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
MODEL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
254
|
+
if (rayOptions.channels?.includes?.(channel)) {
|
|
218
255
|
schema.push(
|
|
219
256
|
{ name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
|
|
220
257
|
{ name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
|
|
221
258
|
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
224
261
|
});
|
|
225
262
|
}
|
|
226
263
|
if (nearbyOptions.enabled) {
|
|
@@ -234,6 +271,7 @@ function buildVectorSchema({ rayOptions, nearbyOptions, lookaheadMeters, profile
|
|
|
234
271
|
{ name: `nearbyCars[${index}].relativeHeadingRadians`, unit: 'rad', scale: 'fixed:pi' },
|
|
235
272
|
{ name: `nearbyCars[${index}].ahead`, scale: 'boolean' },
|
|
236
273
|
{ name: `nearbyCars[${index}].sameLap`, scale: 'boolean' },
|
|
274
|
+
{ name: `nearbyCars[${index}].entityTypeReplayGhost`, scale: 'boolean' },
|
|
237
275
|
);
|
|
238
276
|
if (includePhysicalDriverSenses) {
|
|
239
277
|
schema.push(
|
package/src/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
PaddockActionSpec,
|
|
3
3
|
PaddockDriverControllerLoop,
|
|
4
4
|
PaddockDriverControllerLoopOptions,
|
|
5
|
+
PaddockEnvironmentOptions,
|
|
5
6
|
PaddockObservationSpec,
|
|
6
7
|
PaddockParticipantInteraction as EnvPaddockParticipantInteraction,
|
|
7
8
|
PaddockParticipantInteractionsOptions as EnvPaddockParticipantInteractionsOptions,
|
|
@@ -23,7 +24,7 @@ export type {
|
|
|
23
24
|
} from './environment/index.js';
|
|
24
25
|
|
|
25
26
|
export type TireCompound = 'S' | 'M' | 'H';
|
|
26
|
-
export type PaddockPhysicsMode = 'arcade' | '
|
|
27
|
+
export type PaddockPhysicsMode = 'arcade' | 'advanced';
|
|
27
28
|
export type PaddockWarmupPolicy = 'config-change' | 'always' | 'never';
|
|
28
29
|
export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
|
|
29
30
|
|
|
@@ -81,11 +82,12 @@ export type PaddockPitIntentRequest = PaddockPitIntent | {
|
|
|
81
82
|
pitTargetCompound?: TireCompound | string;
|
|
82
83
|
targetTire?: TireCompound | string;
|
|
83
84
|
};
|
|
84
|
-
export type CameraMode = 'overview' | 'leader' | 'selected' | 'show-all' | 'pit';
|
|
85
|
+
export type CameraMode = 'overview' | 'leader' | 'selected' | 'driver' | 'show-all' | 'pit';
|
|
85
86
|
export type RaceBannerMode = 'project' | 'radio' | 'hidden';
|
|
86
87
|
export type RaceBannerEnabledMode = 'project' | 'radio';
|
|
87
88
|
export type RaceDataBannerSize = 'auto' | 'custom';
|
|
88
89
|
export type TimingTowerVerticalFit = 'expand-race-view' | 'scroll';
|
|
90
|
+
export type TimingGapMode = 'interval' | 'leader';
|
|
89
91
|
export type LayoutPreset = 'standard' | 'left-tower-overlay';
|
|
90
92
|
export type CameraControlsMode = 'embedded' | 'external' | false;
|
|
91
93
|
export type PaddockPresetName = 'dashboard' | 'timing-overlay' | 'compact-race' | 'full-dashboard';
|
|
@@ -111,6 +113,7 @@ export interface TeamData {
|
|
|
111
113
|
name?: string;
|
|
112
114
|
color?: string;
|
|
113
115
|
icon?: string;
|
|
116
|
+
theme?: PaddockThemeSelector;
|
|
114
117
|
pitCrew?: PaddockPitCrewStats;
|
|
115
118
|
pitCrewStats?: PaddockPitCrewStats;
|
|
116
119
|
}
|
|
@@ -254,12 +257,84 @@ export interface NormalizedSimulatorDriver extends SimulatorDriver {
|
|
|
254
257
|
};
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
export
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
260
|
+
export type PaddockThemeMode = 'dark' | 'light' | 'system';
|
|
261
|
+
export type PaddockThemeSelector = 'default' | 'active' | 'selectedTeam' | 'team' | `team:${string}` | string;
|
|
262
|
+
export type PaddockThemeTokenValue = string | { light?: string; dark?: string };
|
|
263
|
+
export type PaddockThemeTokenName =
|
|
264
|
+
| 'primary'
|
|
265
|
+
| 'primaryText'
|
|
266
|
+
| 'secondary'
|
|
267
|
+
| 'secondaryText'
|
|
268
|
+
| 'surface'
|
|
269
|
+
| 'surfaceRaised'
|
|
270
|
+
| 'surfacePanel'
|
|
271
|
+
| 'text'
|
|
272
|
+
| 'mutedText'
|
|
273
|
+
| 'border'
|
|
274
|
+
| 'success'
|
|
275
|
+
| 'warning'
|
|
276
|
+
| 'danger'
|
|
277
|
+
| 'info'
|
|
278
|
+
| 'yellowFlag'
|
|
279
|
+
| 'greenFlag'
|
|
280
|
+
| 'redFlag'
|
|
281
|
+
| 'safetyCar'
|
|
282
|
+
| 'drsActive'
|
|
283
|
+
| 'pitLane'
|
|
284
|
+
| 'track'
|
|
285
|
+
| 'trackEdge'
|
|
286
|
+
| 'timingTowerMaxWidth'
|
|
287
|
+
| 'raceViewMinHeight';
|
|
288
|
+
|
|
289
|
+
export interface F1SimulatorThemeTokens extends Partial<Record<PaddockThemeTokenName, PaddockThemeTokenValue>> {
|
|
290
|
+
/** Compatibility alias for `primary`. Prefer `tokens.primary` in new 3.0.0 code. */
|
|
291
|
+
accentColor?: PaddockThemeTokenValue;
|
|
292
|
+
/** Compatibility alias for `greenFlag`. Prefer `tokens.greenFlag` or `tokens.success`. */
|
|
293
|
+
greenColor?: PaddockThemeTokenValue;
|
|
294
|
+
/** Compatibility alias for `yellowFlag`. Prefer `tokens.yellowFlag` or `tokens.warning`. */
|
|
295
|
+
yellowColor?: PaddockThemeTokenValue;
|
|
296
|
+
/** Compatibility alias for `info`. */
|
|
297
|
+
blueColor?: PaddockThemeTokenValue;
|
|
298
|
+
/** Compatibility alias for `redFlag`. Prefer `tokens.redFlag` or `tokens.danger`. */
|
|
299
|
+
raceControlRedColor?: PaddockThemeTokenValue;
|
|
300
|
+
/** Compatibility alias for `surface`. */
|
|
301
|
+
surfaceColor?: PaddockThemeTokenValue;
|
|
302
|
+
/** Compatibility alias for `surfaceRaised`. */
|
|
303
|
+
surfaceRaisedColor?: PaddockThemeTokenValue;
|
|
304
|
+
/** Compatibility alias for `surfacePanel`. */
|
|
305
|
+
surfacePanelColor?: PaddockThemeTokenValue;
|
|
306
|
+
/** Compatibility alias for `border`. */
|
|
307
|
+
lineColor?: PaddockThemeTokenValue;
|
|
308
|
+
/** Compatibility alias for `border`. */
|
|
309
|
+
lineStrongColor?: PaddockThemeTokenValue;
|
|
310
|
+
/** Compatibility alias for `text`. */
|
|
311
|
+
textColor?: PaddockThemeTokenValue;
|
|
312
|
+
/** Compatibility alias for `mutedText`. */
|
|
313
|
+
mutedTextColor?: PaddockThemeTokenValue;
|
|
314
|
+
/** Compatibility alias for `track`. */
|
|
315
|
+
trackColor?: PaddockThemeTokenValue;
|
|
316
|
+
/** Compatibility alias for `trackEdge`. */
|
|
317
|
+
trackEdgeColor?: PaddockThemeTokenValue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface F1SimulatorThemePackage extends F1SimulatorThemeTokens {
|
|
321
|
+
extends?: string;
|
|
322
|
+
tokens?: F1SimulatorThemeTokens;
|
|
323
|
+
components?: Record<string, Partial<Record<string, PaddockThemeTokenName>>>;
|
|
324
|
+
light?: F1SimulatorThemeTokens;
|
|
325
|
+
dark?: F1SimulatorThemeTokens;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface F1SimulatorTheme extends Omit<F1SimulatorThemePackage, 'components'> {
|
|
329
|
+
mode?: PaddockThemeMode;
|
|
330
|
+
use?: string;
|
|
331
|
+
themes?: Record<string, F1SimulatorThemePackage>;
|
|
332
|
+
componentThemes?: Record<string, PaddockThemeSelector | { theme: PaddockThemeSelector }>;
|
|
333
|
+
teamThemes?: Record<string, PaddockThemeSelector | { theme: PaddockThemeSelector }>;
|
|
334
|
+
components?: Record<
|
|
335
|
+
string,
|
|
336
|
+
PaddockThemeSelector | { theme: PaddockThemeSelector } | Partial<Record<string, PaddockThemeTokenName>>
|
|
337
|
+
>;
|
|
263
338
|
}
|
|
264
339
|
|
|
265
340
|
export interface F1SimulatorUiOptions {
|
|
@@ -277,10 +352,15 @@ export interface F1SimulatorUiOptions {
|
|
|
277
352
|
};
|
|
278
353
|
raceDataBannerSize?: RaceDataBannerSize;
|
|
279
354
|
raceDataTelemetryDetail?: boolean;
|
|
355
|
+
driverCamera?: boolean;
|
|
280
356
|
penaltyBanners?: boolean;
|
|
281
357
|
timingPenaltyBadges?: boolean;
|
|
282
358
|
simulationSpeedControl?: boolean;
|
|
283
359
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
360
|
+
timingGapMode?: TimingGapMode;
|
|
361
|
+
timingGapModeToggle?: boolean;
|
|
362
|
+
timingEntryVerticalPadding?: number;
|
|
363
|
+
responsiveNarrowLayout?: boolean;
|
|
284
364
|
}
|
|
285
365
|
|
|
286
366
|
export interface F1SimulatorDebugOptions {
|
|
@@ -638,6 +718,18 @@ export interface WheelSurfaceSnapshot {
|
|
|
638
718
|
fullyOutsideWhiteLine: boolean;
|
|
639
719
|
}
|
|
640
720
|
|
|
721
|
+
export interface CarTrackStateSnapshot {
|
|
722
|
+
distance: number;
|
|
723
|
+
signedOffset: number;
|
|
724
|
+
crossTrackError: number;
|
|
725
|
+
surface: string;
|
|
726
|
+
inPitLane: boolean;
|
|
727
|
+
pitLanePart: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
|
|
728
|
+
pitBoxId: string | null;
|
|
729
|
+
curvature: number;
|
|
730
|
+
heading: number;
|
|
731
|
+
}
|
|
732
|
+
|
|
641
733
|
export interface CarSnapshot {
|
|
642
734
|
id: string;
|
|
643
735
|
rank: number | null;
|
|
@@ -648,6 +740,8 @@ export interface CarSnapshot {
|
|
|
648
740
|
tire: TireCompound;
|
|
649
741
|
interaction?: PaddockParticipantInteraction;
|
|
650
742
|
lap: number;
|
|
743
|
+
velocityX?: number | null;
|
|
744
|
+
velocityY?: number | null;
|
|
651
745
|
speedKph: number;
|
|
652
746
|
finishRank?: number | null;
|
|
653
747
|
status?: 'racing' | 'waved-flag' | 'destroyed' | string;
|
|
@@ -681,6 +775,7 @@ export interface CarSnapshot {
|
|
|
681
775
|
stabilityState?: PaddockStabilityState;
|
|
682
776
|
signedOffset?: number;
|
|
683
777
|
crossTrackError?: number;
|
|
778
|
+
trackState?: CarTrackStateSnapshot | null;
|
|
684
779
|
inPitLane?: boolean;
|
|
685
780
|
wheels?: WheelSurfaceSnapshot[];
|
|
686
781
|
pitLanePart?: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
|
|
@@ -789,6 +884,7 @@ export interface F1SimulatorExpertOptions {
|
|
|
789
884
|
enabled: boolean;
|
|
790
885
|
controlledDrivers: string[];
|
|
791
886
|
frameSkip?: number;
|
|
887
|
+
episode?: PaddockEnvironmentOptions['episode'];
|
|
792
888
|
visualizeSensors?: boolean | {
|
|
793
889
|
rays?: boolean;
|
|
794
890
|
drivers?: 'selected' | 'all' | string[];
|
|
@@ -860,7 +956,6 @@ export interface F1SimulatorOptions extends F1SimulatorCallbacks {
|
|
|
860
956
|
seed?: number;
|
|
861
957
|
trackSeed?: number;
|
|
862
958
|
trackGeneration?: PaddockProceduralTrackOptions;
|
|
863
|
-
trackQueryIndex?: boolean;
|
|
864
959
|
warmup?: PaddockWarmupOptions | boolean;
|
|
865
960
|
totalLaps?: number;
|
|
866
961
|
physicsMode?: PaddockPhysicsMode;
|
|
@@ -887,6 +982,7 @@ export interface MountRaceCanvasOptions {
|
|
|
887
982
|
includeTimingTower?: boolean;
|
|
888
983
|
includeTelemetrySectorBanner?: boolean;
|
|
889
984
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
985
|
+
responsiveNarrowLayout?: boolean;
|
|
890
986
|
}
|
|
891
987
|
|
|
892
988
|
export interface MountTelemetryPanelOptions {
|
|
@@ -897,6 +993,7 @@ export interface MountRaceTelemetryDrawerOptions {
|
|
|
897
993
|
timingTowerVerticalFit?: TimingTowerVerticalFit;
|
|
898
994
|
drawerInitiallyOpen?: boolean;
|
|
899
995
|
raceDataTelemetryDetail?: boolean;
|
|
996
|
+
responsiveNarrowLayout?: boolean;
|
|
900
997
|
}
|
|
901
998
|
|
|
902
999
|
export interface F1MountedSimulator {
|
|
@@ -914,6 +1011,9 @@ export interface F1MountedSimulator {
|
|
|
914
1011
|
getPitIntent(driverId: string): PaddockPitIntent;
|
|
915
1012
|
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
916
1013
|
getSimulationSpeed(): number;
|
|
1014
|
+
setTimingGapMode(mode: TimingGapMode): TimingGapMode;
|
|
1015
|
+
getTimingGapMode(): TimingGapMode;
|
|
1016
|
+
toggleTimingGapMode(): TimingGapMode;
|
|
917
1017
|
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
918
1018
|
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
919
1019
|
getSnapshot(): RaceSnapshot | null;
|
|
@@ -951,6 +1051,9 @@ export interface PaddockSimulatorController {
|
|
|
951
1051
|
getPitIntent(driverId: string): PaddockPitIntent;
|
|
952
1052
|
getPitTargetCompound(driverId: string): TireCompound | string | null;
|
|
953
1053
|
getSimulationSpeed(): number;
|
|
1054
|
+
setTimingGapMode(mode: TimingGapMode): TimingGapMode;
|
|
1055
|
+
getTimingGapMode(): TimingGapMode;
|
|
1056
|
+
toggleTimingGapMode(): TimingGapMode;
|
|
954
1057
|
servePenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
955
1058
|
cancelPenalty(penaltyId: string): PaddockPenaltyEntry | null;
|
|
956
1059
|
getSnapshot(): RaceSnapshot | null;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './styles.css';
|
|
2
2
|
import { F1SimulatorApp } from './app/F1SimulatorApp.js';
|
|
3
3
|
import { resolveF1SimulatorOptions } from './config/defaultOptions.js';
|
|
4
|
+
import { mergeRestartOptions } from './config/restartOptions.js';
|
|
4
5
|
import { createF1SimulatorShell } from './ui/shellTemplate.js';
|
|
5
6
|
export {
|
|
6
7
|
createPaddockSimulator,
|
|
@@ -51,38 +52,6 @@ function assertMountRoot(root) {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function mergeRestartOptions(previousOptions, nextOptions) {
|
|
55
|
-
const resetFromPreset = Object.hasOwn(nextOptions, 'preset');
|
|
56
|
-
const previousUi = resetFromPreset ? {} : previousOptions.ui;
|
|
57
|
-
const previousTheme = resetFromPreset ? {} : previousOptions.theme;
|
|
58
|
-
return {
|
|
59
|
-
...previousOptions,
|
|
60
|
-
...nextOptions,
|
|
61
|
-
ui: {
|
|
62
|
-
...previousUi,
|
|
63
|
-
...(nextOptions.ui ?? {}),
|
|
64
|
-
raceDataBanners: {
|
|
65
|
-
...(previousUi.raceDataBanners ?? {}),
|
|
66
|
-
...(nextOptions.ui?.raceDataBanners ?? {}),
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
theme: {
|
|
70
|
-
...previousTheme,
|
|
71
|
-
...(nextOptions.theme ?? {}),
|
|
72
|
-
},
|
|
73
|
-
assets: {
|
|
74
|
-
...previousOptions.assets,
|
|
75
|
-
...(nextOptions.assets ?? {}),
|
|
76
|
-
trackTextures: {
|
|
77
|
-
...previousOptions.assets.trackTextures,
|
|
78
|
-
...(nextOptions.assets?.trackTextures ?? {}),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
drivers: nextOptions.drivers ?? previousOptions.drivers,
|
|
82
|
-
entries: nextOptions.entries ?? previousOptions.entries,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
55
|
export async function mountF1Simulator(root, options = {}) {
|
|
87
56
|
assertMountRoot(root);
|
|
88
57
|
|
|
@@ -92,6 +61,21 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
92
61
|
const app = new F1SimulatorApp(shell, resolvedOptions);
|
|
93
62
|
await app.init();
|
|
94
63
|
|
|
64
|
+
const syncResolvedTimingGapMode = (timingGapMode) => {
|
|
65
|
+
resolvedOptions = {
|
|
66
|
+
...resolvedOptions,
|
|
67
|
+
ui: {
|
|
68
|
+
...resolvedOptions.ui,
|
|
69
|
+
timingGapMode,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
return timingGapMode;
|
|
73
|
+
};
|
|
74
|
+
const syncResolvedRuntimeOptions = () => {
|
|
75
|
+
syncResolvedTimingGapMode(app.getTimingGapMode());
|
|
76
|
+
return resolvedOptions;
|
|
77
|
+
};
|
|
78
|
+
|
|
95
79
|
return {
|
|
96
80
|
get expert() {
|
|
97
81
|
return app.expert ?? null;
|
|
@@ -101,7 +85,7 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
101
85
|
root.innerHTML = '';
|
|
102
86
|
},
|
|
103
87
|
restart(nextOptions = {}) {
|
|
104
|
-
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(
|
|
88
|
+
const nextResolvedOptions = resolveF1SimulatorOptions(mergeRestartOptions(syncResolvedRuntimeOptions(), nextOptions));
|
|
105
89
|
app.restart(nextResolvedOptions);
|
|
106
90
|
resolvedOptions = nextResolvedOptions;
|
|
107
91
|
},
|
|
@@ -139,6 +123,15 @@ export async function mountF1Simulator(root, options = {}) {
|
|
|
139
123
|
getSimulationSpeed() {
|
|
140
124
|
return app.simulationSpeed;
|
|
141
125
|
},
|
|
126
|
+
setTimingGapMode(mode) {
|
|
127
|
+
return syncResolvedTimingGapMode(app.setTimingGapMode(mode));
|
|
128
|
+
},
|
|
129
|
+
getTimingGapMode() {
|
|
130
|
+
return app.getTimingGapMode();
|
|
131
|
+
},
|
|
132
|
+
toggleTimingGapMode() {
|
|
133
|
+
return syncResolvedTimingGapMode(app.toggleTimingGapMode());
|
|
134
|
+
},
|
|
142
135
|
servePenalty(penaltyId) {
|
|
143
136
|
return app.servePenalty(penaltyId);
|
|
144
137
|
},
|
|
@@ -37,7 +37,7 @@ export function decideDriverControls({ car, orderIndex, race }) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function shouldStabilizeSimulatorCar(car, race) {
|
|
40
|
-
if (race.physicsMode !== '
|
|
40
|
+
if (race.physicsMode !== 'advanced') return false;
|
|
41
41
|
const gripUsage = car.gripUsage ?? 0;
|
|
42
42
|
const slipAngle = Math.abs(car.slipAngleRadians ?? 0);
|
|
43
43
|
const edgeDistance = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 2.25;
|
|
@@ -5,7 +5,7 @@ import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
|
5
5
|
import { REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX } from './driverControlConstants.js';
|
|
6
6
|
|
|
7
7
|
export function calculateTrackEdgeGuard(car, race) {
|
|
8
|
-
const simulatorMode = race.physicsMode === '
|
|
8
|
+
const simulatorMode = race.physicsMode === 'advanced';
|
|
9
9
|
const trackLimit = race.track.width / 2;
|
|
10
10
|
const halfCarWidth = VEHICLE_LIMITS.carWidth / 2;
|
|
11
11
|
const wholeCarOutsideLimit = trackLimit + halfCarWidth;
|
|
@@ -10,7 +10,7 @@ import { calculateActualOverlapPenalty, calculatePlannedTrafficPenalty, planRaci
|
|
|
10
10
|
import { analyzeTrackEdgeMotion } from './recoveryDynamics.js';
|
|
11
11
|
|
|
12
12
|
export function decideRacingControls(car, orderIndex, race) {
|
|
13
|
-
if (race.physicsMode === '
|
|
13
|
+
if (race.physicsMode === 'advanced') {
|
|
14
14
|
return decideSimulatorRacingControls(car, orderIndex, race);
|
|
15
15
|
}
|
|
16
16
|
return decideArcadeRacingControls(car, orderIndex, race);
|
|
@@ -389,7 +389,7 @@ export function calculateRacingLineOffset(car, race, lookahead, curvature, edgeG
|
|
|
389
389
|
if (turnDirection === 0) return 0;
|
|
390
390
|
|
|
391
391
|
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
392
|
-
const simulatorMode = race.physicsMode === '
|
|
392
|
+
const simulatorMode = race.physicsMode === 'advanced';
|
|
393
393
|
const safeEdge = race.track.width / 2 - VEHICLE_LIMITS.carWidth *
|
|
394
394
|
(simulatorMode ? 2.05 - aggression * 0.08 : 1.15 - aggression * 0.12);
|
|
395
395
|
const apexOffset = turnDirection * safeEdge *
|
|
@@ -8,7 +8,7 @@ import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
|
8
8
|
import { analyzeTrackEdgeMotion } from './recoveryDynamics.js';
|
|
9
9
|
|
|
10
10
|
export function decideRejoinControls(car, race) {
|
|
11
|
-
if (race.physicsMode === '
|
|
11
|
+
if (race.physicsMode === 'advanced') {
|
|
12
12
|
return decideSimulatorRejoinControls(car, race);
|
|
13
13
|
}
|
|
14
14
|
return decideArcadeRejoinControls(car, race);
|
|
@@ -103,7 +103,7 @@ export function decideSimulatorRejoinControls(car, race) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
function decideRejoinControlsForMode(car, race, profile) {
|
|
106
|
-
const simulatorMode = race.physicsMode === '
|
|
106
|
+
const simulatorMode = race.physicsMode === 'advanced';
|
|
107
107
|
const lookahead = clamp(car.speed * (profile.simulatorMode ? 0.58 : 0.72) + REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX);
|
|
108
108
|
const edgeMotion = analyzeTrackEdgeMotion(car, race);
|
|
109
109
|
const lowSpeedOffTrack = !car.trackState.onTrack && car.speed < kphToSimSpeed(24);
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isPitServiceQueueOccupied,
|
|
18
18
|
releasePitQueue,
|
|
19
19
|
} from './pitService.js';
|
|
20
|
+
import { syncPitCarKinematics } from './pitKinematics.js';
|
|
20
21
|
import {
|
|
21
22
|
createPitApproachPoints,
|
|
22
23
|
createRoute,
|
|
@@ -209,6 +210,10 @@ export function advancePitStopCar(sim, car, delta) {
|
|
|
209
210
|
car.speed = 0;
|
|
210
211
|
car.throttle = 0;
|
|
211
212
|
car.brake = 1;
|
|
213
|
+
car.steeringAngle = 0;
|
|
214
|
+
car.yawRate = 0;
|
|
215
|
+
car.turnRadius = Infinity;
|
|
216
|
+
syncPitCarKinematics(car, { physicsMode: sim.physicsMode });
|
|
212
217
|
applyWheelSurfaceState(car, sim.track);
|
|
213
218
|
car.progress = car.trackState.distance;
|
|
214
219
|
car.raceDistance = getPitBoxRaceDistance(sim, stop, {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function syncPitCarKinematics(car, { physicsMode = 'arcade' } = {}) {
|
|
2
|
+
const speed = Number.isFinite(car.speed) ? car.speed : 0;
|
|
3
|
+
const heading = Number.isFinite(car.heading) ? car.heading : 0;
|
|
4
|
+
if (physicsMode === 'advanced') {
|
|
5
|
+
car.velocityX = Math.cos(heading) * speed;
|
|
6
|
+
car.velocityY = Math.sin(heading) * speed;
|
|
7
|
+
} else {
|
|
8
|
+
car.velocityX = null;
|
|
9
|
+
car.velocityY = null;
|
|
10
|
+
}
|
|
11
|
+
car.appliedControls = {
|
|
12
|
+
steering: car.steeringAngle ?? 0,
|
|
13
|
+
throttle: car.throttle ?? 0,
|
|
14
|
+
brake: car.brake ?? 0,
|
|
15
|
+
};
|
|
16
|
+
car.lateralAcceleration = 0;
|
|
17
|
+
car.longitudinalAcceleration = 0;
|
|
18
|
+
car.lateralG = 0;
|
|
19
|
+
car.longitudinalG = 0;
|
|
20
|
+
car.gripUsage = 0;
|
|
21
|
+
car.slipAngleRadians = 0;
|
|
22
|
+
car.tractionLimited = false;
|
|
23
|
+
car.stabilityState = 'stable';
|
|
24
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyWheelSurfaceState } from '../vehicle/wheelSurface.js';
|
|
2
2
|
import { createRoute, routePoint } from './pitRouting.js';
|
|
3
|
+
import { syncPitCarKinematics } from './pitKinematics.js';
|
|
3
4
|
import { PIT_QUEUE_RELEASE_SPEED } from './pitServiceConstants.js';
|
|
4
5
|
import { getPitBoxRaceDistance } from './pitOccupancy.js';
|
|
5
6
|
|
|
@@ -17,6 +18,10 @@ export function beginPitQueue(sim, car, box) {
|
|
|
17
18
|
car.speed = 0;
|
|
18
19
|
car.throttle = 0;
|
|
19
20
|
car.brake = 1;
|
|
21
|
+
car.steeringAngle = 0;
|
|
22
|
+
car.yawRate = 0;
|
|
23
|
+
car.turnRadius = Infinity;
|
|
24
|
+
syncPitCarKinematics(car, { physicsMode: sim.physicsMode });
|
|
20
25
|
applyWheelSurfaceState(car, sim.track);
|
|
21
26
|
car.progress = car.trackState.distance;
|
|
22
27
|
car.raceDistance = getPitBoxRaceDistance(sim, stop, {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
routeLimiterActiveAt,
|
|
9
9
|
sampleRoute,
|
|
10
10
|
} from './pitRouting.js';
|
|
11
|
+
import { syncPitCarKinematics } from './pitKinematics.js';
|
|
11
12
|
import { completePitPenaltyService } from './pitPenaltyService.js';
|
|
12
13
|
import { completePitService } from './pitTireService.js';
|
|
13
14
|
import { getPitBoxRaceDistance, getPitStopBox } from './pitOccupancy.js';
|
|
@@ -51,6 +52,7 @@ export function applyPitRoutePosition(sim, car, delta) {
|
|
|
51
52
|
VEHICLE_LIMITS.maxSteer,
|
|
52
53
|
);
|
|
53
54
|
car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
|
|
55
|
+
syncPitCarKinematics(car, { physicsMode: sim.physicsMode });
|
|
54
56
|
stop.routeProgress = nextProgress;
|
|
55
57
|
const routeAmount = route.length > 0 ? stop.routeProgress / route.length : 1;
|
|
56
58
|
car.raceDistance = stop.routeStartRaceDistance +
|
|
@@ -111,6 +113,7 @@ export function applyPitRoutePosition(sim, car, delta) {
|
|
|
111
113
|
VEHICLE_LIMITS.maxSteer,
|
|
112
114
|
);
|
|
113
115
|
car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
|
|
116
|
+
syncPitCarKinematics(car, { physicsMode: sim.physicsMode });
|
|
114
117
|
applyWheelSurfaceState(car, sim.track);
|
|
115
118
|
stop.routeProgress = nextProgress;
|
|
116
119
|
const routeAmount = route.length > 0 ? stop.routeProgress / route.length : 1;
|
|
@@ -148,6 +151,10 @@ export function advancePitService(sim, car, delta) {
|
|
|
148
151
|
car.speed = 0;
|
|
149
152
|
car.throttle = 0;
|
|
150
153
|
car.brake = 1;
|
|
154
|
+
car.steeringAngle = 0;
|
|
155
|
+
car.yawRate = 0;
|
|
156
|
+
car.turnRadius = Infinity;
|
|
157
|
+
syncPitCarKinematics(car, { physicsMode: sim.physicsMode });
|
|
151
158
|
applyWheelSurfaceState(car, sim.track);
|
|
152
159
|
car.progress = car.trackState.distance;
|
|
153
160
|
car.raceDistance = getPitBoxRaceDistance(sim, stop, box);
|
|
@@ -285,11 +285,9 @@ export function nearestDistanceOnRoute(route, position, previousDistance = 0) {
|
|
|
285
285
|
export function createPitApproachPoints(track, car, pitLane, entryRaceDistance) {
|
|
286
286
|
const currentDistance = car.raceDistance ?? entryRaceDistance - PIT_ENTRY_APPROACH_DISTANCE;
|
|
287
287
|
const remaining = Math.max(0, entryRaceDistance - currentDistance);
|
|
288
|
-
const currentState = nearestTrackState(track, car, car.progress
|
|
288
|
+
const currentState = nearestTrackState(track, car, car.progress);
|
|
289
289
|
const startOffset = currentState.inPitLane ? 0 : currentState.signedOffset ?? 0;
|
|
290
|
-
const entryConnectState = nearestTrackState(track, pitLane.entry.trackConnectPoint, pitLane.entry.trackDistance
|
|
291
|
-
indexMode: 'legacy',
|
|
292
|
-
});
|
|
290
|
+
const entryConnectState = nearestTrackState(track, pitLane.entry.trackConnectPoint, pitLane.entry.trackDistance);
|
|
293
291
|
const targetOffset = entryConnectState.signedOffset ?? 0;
|
|
294
292
|
const points = [routePoint(car, car.heading)];
|
|
295
293
|
const straightDistance = remaining > metersToSimUnits(14)
|