@inventure71/paddockjs 4.2.0 → 4.3.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 +40 -0
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +31 -2
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +109 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +31 -15
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/grid_walk.md +84 -0
- package/docs/index.md +1 -0
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +38 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +24 -13
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +106 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +13 -259
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +21 -140
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +34 -259
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +5 -11
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +14 -3
- package/src/simulation/driver/edgeRecovery.js +0 -1
- package/src/simulation/driver/racingControls.js +10 -406
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +48 -15
- package/src/simulation/driver/rejoinControls.js +58 -111
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +89 -6
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +3 -8
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/rulesReview.js +1 -7
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +3 -3
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +1 -25
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +4 -241
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +20 -6
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +0 -56
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +35 -194
- package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
- package/src/simulation/vehicle/vehicleState.js +2 -1
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearCurrentDrsState } from '../timing/drsState.js';
|
|
1
2
|
import { markCarDnf } from './retirements.js';
|
|
2
3
|
import { simSpeedToKph } from '../units.js';
|
|
3
4
|
import { freezeVehicleMotion } from '../vehicle/vehicleKinematics.js';
|
|
@@ -66,10 +67,7 @@ export function freezeRetiredCar(car, { physicsMode = 'arcade' } = {}) {
|
|
|
66
67
|
stabilityState: 'destroyed',
|
|
67
68
|
});
|
|
68
69
|
car.canAttack = false;
|
|
69
|
-
car
|
|
70
|
-
car.drsEligible = false;
|
|
71
|
-
car.drsZoneId = null;
|
|
72
|
-
car.drsZoneEnabled = false;
|
|
70
|
+
clearCurrentDrsState(car);
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
function resetStallTimer(car) {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyUnservedServicePenalty,
|
|
3
|
-
cancelPenaltyRecord,
|
|
4
|
-
createPenaltyEvent,
|
|
5
|
-
createPenaltyRecord,
|
|
6
|
-
servePenaltyRecord,
|
|
7
3
|
} from './rules/penaltyLedger.js';
|
|
8
|
-
import {
|
|
4
|
+
import { recordPenaltyForSimulation, servePenaltyForSimulation, cancelPenaltyForSimulation } from './rules/penaltyActions.js';
|
|
9
5
|
import { buildPenaltyStatsByDriver, getPenaltyStats } from './rules/penaltyStats.js';
|
|
10
6
|
import {
|
|
11
7
|
reviewCollisionForSimulation,
|
|
@@ -93,7 +89,6 @@ import { applyGridDropForSimulation } from './race/gridPenalties.js';
|
|
|
93
89
|
import { initializeRaceSimulation, normalizePitIntentForRace } from './race/raceSetup.js';
|
|
94
90
|
import { runRaceStep } from './race/raceStep.js';
|
|
95
91
|
import {
|
|
96
|
-
createVehicleSnapshotDependencies,
|
|
97
92
|
getRaceWinnerSnapshot as getRaceWinnerSnapshotForSimulation,
|
|
98
93
|
snapshotRace,
|
|
99
94
|
snapshotRaceObservation,
|
|
@@ -209,7 +204,7 @@ export class F1RaceSimulation {
|
|
|
209
204
|
}
|
|
210
205
|
|
|
211
206
|
isCarInActivePitStop(car) {
|
|
212
|
-
return isCarInActivePitStopForSimulation(
|
|
207
|
+
return isCarInActivePitStopForSimulation(car);
|
|
213
208
|
}
|
|
214
209
|
|
|
215
210
|
schedulePitStopAtNextEntry(car, stop) {
|
|
@@ -249,7 +244,7 @@ export class F1RaceSimulation {
|
|
|
249
244
|
}
|
|
250
245
|
|
|
251
246
|
isPitServiceAreaOccupied(candidate, box) {
|
|
252
|
-
return isPitServiceAreaOccupiedForSimulation(
|
|
247
|
+
return isPitServiceAreaOccupiedForSimulation(candidate, box);
|
|
253
248
|
}
|
|
254
249
|
|
|
255
250
|
isPitServiceQueueOccupied(car, box) {
|
|
@@ -317,38 +312,15 @@ export class F1RaceSimulation {
|
|
|
317
312
|
}
|
|
318
313
|
|
|
319
314
|
recordPenalty(penalty) {
|
|
320
|
-
|
|
321
|
-
const entry = createPenaltyRecord({
|
|
322
|
-
sequence: this.nextPenaltyId,
|
|
323
|
-
time: this.time,
|
|
324
|
-
lap: this.computeLap(car?.raceDistance ?? 0),
|
|
325
|
-
penalty,
|
|
326
|
-
});
|
|
327
|
-
this.nextPenaltyId += 1;
|
|
328
|
-
this.penalties.push(entry);
|
|
329
|
-
if (entry.gridDrop > 0 && this.raceControl.mode === 'pre-start') {
|
|
330
|
-
this.applyGridDrop(entry.driverId, entry.gridDrop);
|
|
331
|
-
}
|
|
332
|
-
this.events.unshift(createPenaltyEvent(entry));
|
|
333
|
-
return entry;
|
|
315
|
+
return recordPenaltyForSimulation(this, penalty);
|
|
334
316
|
}
|
|
335
317
|
|
|
336
318
|
servePenalty(penaltyId) {
|
|
337
|
-
|
|
338
|
-
const result = servePenaltyRecord(penalty, this.time);
|
|
339
|
-
if (result) {
|
|
340
|
-
this.events.unshift(createPenaltyServedEvent(result, this.time));
|
|
341
|
-
}
|
|
342
|
-
return result;
|
|
319
|
+
return servePenaltyForSimulation(this, penaltyId);
|
|
343
320
|
}
|
|
344
321
|
|
|
345
322
|
cancelPenalty(penaltyId) {
|
|
346
|
-
|
|
347
|
-
const result = cancelPenaltyRecord(penalty, this.time);
|
|
348
|
-
if (result) {
|
|
349
|
-
this.events.unshift(createPenaltyCancelledEvent(result, this.time));
|
|
350
|
-
}
|
|
351
|
-
return result;
|
|
323
|
+
return cancelPenaltyForSimulation(this, penaltyId);
|
|
352
324
|
}
|
|
353
325
|
|
|
354
326
|
getDriverPenaltySeconds(driverId) {
|
|
@@ -389,10 +361,6 @@ export class F1RaceSimulation {
|
|
|
389
361
|
reviewPitLaneSpeedingForSimulation(this);
|
|
390
362
|
}
|
|
391
363
|
|
|
392
|
-
vehicleSnapshotDependencies() {
|
|
393
|
-
return createVehicleSnapshotDependencies(this);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
364
|
snapshot() {
|
|
397
365
|
return snapshotRace(this);
|
|
398
366
|
}
|
|
@@ -11,25 +11,17 @@ export function normalizeReplayGhosts(replayGhosts = []) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function updateReplayGhosts(replayGhosts = [], timeSeconds = 0) {
|
|
14
|
-
replayGhosts.
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
x: sample.x,
|
|
26
|
-
y: sample.y,
|
|
27
|
-
heading: sample.heading,
|
|
28
|
-
speedKph: sample.speedKph,
|
|
29
|
-
progressMeters: sample.progressMeters,
|
|
30
|
-
timeSeconds,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
14
|
+
for (let index = 0; index < replayGhosts.length; index += 1) {
|
|
15
|
+
const ghost = replayGhosts[index];
|
|
16
|
+
const previousX = ghost.x;
|
|
17
|
+
const previousY = ghost.y;
|
|
18
|
+
const previousHeading = ghost.heading;
|
|
19
|
+
interpolateTrajectoryInto(ghost, ghost.trajectory, timeSeconds);
|
|
20
|
+
ghost.previousX = Number.isFinite(previousX) ? previousX : ghost.x;
|
|
21
|
+
ghost.previousY = Number.isFinite(previousY) ? previousY : ghost.y;
|
|
22
|
+
ghost.previousHeading = Number.isFinite(previousHeading) ? previousHeading : ghost.heading;
|
|
23
|
+
ghost.timeSeconds = timeSeconds;
|
|
24
|
+
}
|
|
33
25
|
}
|
|
34
26
|
|
|
35
27
|
export function serializeReplayGhosts(replayGhosts = []) {
|
|
@@ -57,7 +49,7 @@ function normalizeReplayGhost(ghost, index) {
|
|
|
57
49
|
const trajectory = normalizeTrajectory(ghost.trajectory);
|
|
58
50
|
if (trajectory.length === 0) return null;
|
|
59
51
|
const id = String(ghost.id ?? `replay-ghost-${index + 1}`);
|
|
60
|
-
const initial =
|
|
52
|
+
const initial = interpolateTrajectoryInto({}, trajectory, 0);
|
|
61
53
|
return {
|
|
62
54
|
id,
|
|
63
55
|
label: String(ghost.label ?? id),
|
|
@@ -103,23 +95,43 @@ function normalizeTrajectory(trajectory) {
|
|
|
103
95
|
.sort((a, b) => a.timeSeconds - b.timeSeconds);
|
|
104
96
|
}
|
|
105
97
|
|
|
106
|
-
function
|
|
107
|
-
if (trajectory.length === 1 || timeSeconds <= trajectory[0].timeSeconds)
|
|
98
|
+
function interpolateTrajectoryInto(target, trajectory, timeSeconds) {
|
|
99
|
+
if (trajectory.length === 1 || timeSeconds <= trajectory[0].timeSeconds) {
|
|
100
|
+
return writeTrajectorySample(target, trajectory[0]);
|
|
101
|
+
}
|
|
108
102
|
const last = trajectory[trajectory.length - 1];
|
|
109
|
-
if (timeSeconds >= last.timeSeconds) return
|
|
110
|
-
const nextIndex = trajectory
|
|
103
|
+
if (timeSeconds >= last.timeSeconds) return writeTrajectorySample(target, last);
|
|
104
|
+
const nextIndex = lowerBoundTrajectoryTime(trajectory, timeSeconds);
|
|
111
105
|
const previous = trajectory[Math.max(0, nextIndex - 1)];
|
|
112
106
|
const next = trajectory[nextIndex];
|
|
113
107
|
const span = next.timeSeconds - previous.timeSeconds || 1;
|
|
114
108
|
const amount = clamp((timeSeconds - previous.timeSeconds) / span, 0, 1);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
109
|
+
target.x = lerp(previous.x, next.x, amount);
|
|
110
|
+
target.y = lerp(previous.y, next.y, amount);
|
|
111
|
+
target.heading = previous.heading + normalizeAngle(next.heading - previous.heading) * amount;
|
|
112
|
+
target.speedKph = lerp(previous.speedKph, next.speedKph, amount);
|
|
113
|
+
target.progressMeters = lerp(previous.progressMeters, next.progressMeters, amount);
|
|
114
|
+
return target;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function lowerBoundTrajectoryTime(trajectory, timeSeconds) {
|
|
118
|
+
let low = 0;
|
|
119
|
+
let high = trajectory.length;
|
|
120
|
+
while (low < high) {
|
|
121
|
+
const middle = Math.floor((low + high) / 2);
|
|
122
|
+
if (trajectory[middle].timeSeconds < timeSeconds) low = middle + 1;
|
|
123
|
+
else high = middle;
|
|
124
|
+
}
|
|
125
|
+
return low;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeTrajectorySample(target, sample) {
|
|
129
|
+
target.x = sample.x;
|
|
130
|
+
target.y = sample.y;
|
|
131
|
+
target.heading = sample.heading;
|
|
132
|
+
target.speedKph = sample.speedKph;
|
|
133
|
+
target.progressMeters = sample.progressMeters;
|
|
134
|
+
return target;
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
function finiteNumber(value) {
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import { simSpeedToKph } from '../units.js';
|
|
2
2
|
|
|
3
|
-
export function calculateCollisionPenalties({ first, second, collision = null, rule }) {
|
|
4
|
-
const penalties = [];
|
|
5
|
-
emitCollisionPenalties({
|
|
6
|
-
first,
|
|
7
|
-
second,
|
|
8
|
-
collision,
|
|
9
|
-
rule,
|
|
10
|
-
emit: (penalty) => {
|
|
11
|
-
penalties.push(penalty);
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
return penalties;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
3
|
export function emitCollisionPenalties({ first, second, collision = null, rule, emit, emitContext = null }) {
|
|
18
4
|
if (!rule || typeof emit !== 'function') return 0;
|
|
19
5
|
const severity = collision?.depth ?? 0;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cancelPenaltyRecord, createPenaltyEvent, createPenaltyRecord, servePenaltyRecord } from './penaltyLedger.js';
|
|
2
|
+
import { createPenaltyCancelledEvent, createPenaltyServedEvent } from './penaltyEvents.js';
|
|
3
|
+
|
|
4
|
+
export function recordPenaltyForSimulation(sim, penalty) {
|
|
5
|
+
const car = sim.cars.find((item) => item.id === penalty.driverId);
|
|
6
|
+
const entry = createPenaltyRecord({
|
|
7
|
+
sequence: sim.nextPenaltyId,
|
|
8
|
+
time: sim.time,
|
|
9
|
+
lap: sim.computeLap(car?.raceDistance ?? 0),
|
|
10
|
+
penalty,
|
|
11
|
+
});
|
|
12
|
+
sim.nextPenaltyId += 1;
|
|
13
|
+
sim.penalties.push(entry);
|
|
14
|
+
if (entry.gridDrop > 0 && sim.raceControl.mode === 'pre-start') {
|
|
15
|
+
sim.applyGridDrop(entry.driverId, entry.gridDrop);
|
|
16
|
+
}
|
|
17
|
+
sim.events.unshift(createPenaltyEvent(entry));
|
|
18
|
+
return entry;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function servePenaltyForSimulation(sim, penaltyId) {
|
|
22
|
+
const penalty = sim.penalties.find((entry) => entry.id === penaltyId);
|
|
23
|
+
const result = servePenaltyRecord(penalty, sim.time);
|
|
24
|
+
if (result) {
|
|
25
|
+
sim.events.unshift(createPenaltyServedEvent(result, sim.time));
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function cancelPenaltyForSimulation(sim, penaltyId) {
|
|
31
|
+
const penalty = sim.penalties.find((entry) => entry.id === penaltyId);
|
|
32
|
+
const result = cancelPenaltyRecord(penalty, sim.time);
|
|
33
|
+
if (result) {
|
|
34
|
+
sim.events.unshift(createPenaltyCancelledEvent(result, sim.time));
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
@@ -48,7 +48,14 @@ export function createPenaltyEvent(entry) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function serializePenalty(penalty) {
|
|
51
|
-
return {
|
|
51
|
+
return {
|
|
52
|
+
...penalty,
|
|
53
|
+
...(Array.isArray(penalty.consequences) ? {
|
|
54
|
+
consequences: penalty.consequences.map((consequence) => ({ ...consequence })),
|
|
55
|
+
} : {}),
|
|
56
|
+
...(Array.isArray(penalty.usedCompounds) ? { usedCompounds: [...penalty.usedCompounds] } : {}),
|
|
57
|
+
...(Array.isArray(penalty.wheelOffsets) ? { wheelOffsets: [...penalty.wheelOffsets] } : {}),
|
|
58
|
+
};
|
|
52
59
|
}
|
|
53
60
|
|
|
54
61
|
export function servePenaltyRecord(penalty, time) {
|
|
@@ -5,6 +5,7 @@ import { calculatePitLaneSpeedingReview } from './pitLaneSpeedingSteward.js';
|
|
|
5
5
|
import { getPenaltyRule } from '../rulesConfig.js';
|
|
6
6
|
import { simSpeedToKph } from '../units.js';
|
|
7
7
|
import { VEHICLE_LIMITS } from '../vehicle/vehiclePhysics.js';
|
|
8
|
+
import { progressDelta } from '../race/raceDistance.js';
|
|
8
9
|
|
|
9
10
|
function velocityComponentX(car) {
|
|
10
11
|
return Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)
|
|
@@ -18,13 +19,6 @@ function velocityComponentY(car) {
|
|
|
18
19
|
: Math.sin(car.heading) * car.speed;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
function progressDelta(a, b, trackLength) {
|
|
22
|
-
let delta = a - b;
|
|
23
|
-
if (delta < -trackLength / 2) delta += trackLength;
|
|
24
|
-
if (delta > trackLength / 2) delta -= trackLength;
|
|
25
|
-
return delta;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
function prepareCollisionStewardContext(scratch = null) {
|
|
29
23
|
const target = scratch
|
|
30
24
|
? (scratch.collisionStewardContext ??= { sharedFaultDriverIds: [] })
|
|
@@ -38,12 +38,8 @@ export function createVehicleSnapshotDependencies(sim) {
|
|
|
38
38
|
normalizePitIntent,
|
|
39
39
|
PIT_INTENT_NONE,
|
|
40
40
|
serializeLapTelemetry,
|
|
41
|
-
serializeObservationPitStop
|
|
42
|
-
|
|
43
|
-
normalizePitIntent,
|
|
44
|
-
PIT_INTENT_NONE,
|
|
45
|
-
),
|
|
46
|
-
serializePitStop: (pitStop) => serializePitStop(pitStop, normalizePitIntent, PIT_INTENT_NONE),
|
|
41
|
+
serializeObservationPitStop,
|
|
42
|
+
serializePitStop,
|
|
47
43
|
serializeRenderPitStop,
|
|
48
44
|
simSpeedToKph,
|
|
49
45
|
simUnitsToMeters,
|
|
@@ -220,11 +216,34 @@ function carsForSnapshot(sim, ordered) {
|
|
|
220
216
|
return [...ranked, ...unranked];
|
|
221
217
|
}
|
|
222
218
|
|
|
219
|
+
const RENDER_CAR_ORDER_SCRATCH = new WeakMap();
|
|
220
|
+
|
|
223
221
|
function renderCarsForSnapshotInto(targetCars, sim, ordered, dependencies) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
222
|
+
let scratch = RENDER_CAR_ORDER_SCRATCH.get(targetCars);
|
|
223
|
+
if (!scratch) {
|
|
224
|
+
scratch = { orderedIds: new Set(), unranked: [] };
|
|
225
|
+
RENDER_CAR_ORDER_SCRATCH.set(targetCars, scratch);
|
|
226
|
+
}
|
|
227
|
+
const { orderedIds, unranked } = scratch;
|
|
228
|
+
orderedIds.clear();
|
|
229
|
+
unranked.length = 0;
|
|
230
|
+
let count = 0;
|
|
231
|
+
for (const car of ordered) {
|
|
232
|
+
orderedIds.add(car.id);
|
|
233
|
+
targetCars[count] = serializeRenderCarInto(targetCars[count] ?? {}, car, dependencies);
|
|
234
|
+
count += 1;
|
|
235
|
+
}
|
|
236
|
+
for (const car of sim.cars) {
|
|
237
|
+
if (!orderedIds.has(car.id)) unranked.push(car);
|
|
238
|
+
}
|
|
239
|
+
unranked.sort((a, b) => a.index - b.index);
|
|
240
|
+
for (const car of unranked) {
|
|
241
|
+
targetCars[count] = serializeRenderCarInto(targetCars[count] ?? {}, car, dependencies);
|
|
242
|
+
count += 1;
|
|
243
|
+
}
|
|
244
|
+
targetCars.length = count;
|
|
245
|
+
// Scratch owns ordering only; do not retain live participants between frames.
|
|
246
|
+
orderedIds.clear();
|
|
247
|
+
unranked.length = 0;
|
|
229
248
|
return targetCars;
|
|
230
249
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Suppress the current latch without erasing passage history or next-zone hints.
|
|
2
|
+
// External placement uses the separate full reset in vehicleState.js.
|
|
3
|
+
export function clearCurrentDrsState(car) {
|
|
4
|
+
car.drsActive = false;
|
|
5
|
+
car.drsEligible = false;
|
|
6
|
+
car.drsZoneId = null;
|
|
7
|
+
car.drsZoneEnabled = false;
|
|
8
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearCurrentDrsState } from './drsState.js';
|
|
1
2
|
import { wrapDistance } from '../simMath.js';
|
|
2
3
|
|
|
3
4
|
function wrapProgress(value, length) {
|
|
@@ -106,10 +107,7 @@ function updateDrsLatchCore(
|
|
|
106
107
|
runtimeBenchmarkStats = null,
|
|
107
108
|
) {
|
|
108
109
|
if (safetyCarDeployed || car.finished) {
|
|
109
|
-
car
|
|
110
|
-
car.drsActive = false;
|
|
111
|
-
car.drsZoneId = null;
|
|
112
|
-
car.drsZoneEnabled = false;
|
|
110
|
+
clearCurrentDrsState(car);
|
|
113
111
|
car._drsZoneRef = null;
|
|
114
112
|
return;
|
|
115
113
|
}
|
|
@@ -25,7 +25,7 @@ function writeLapTelemetryPosition(target, track, raceDistance, totalLaps = Infi
|
|
|
25
25
|
const sectorIndex = Math.min(TELEMETRY_SECTOR_COUNT - 1, Math.floor(lapProgress / sectorLength));
|
|
26
26
|
const sectorStart = sectorIndex * sectorLength;
|
|
27
27
|
|
|
28
|
-
target.completedLaps = Math.floor(positiveDistance / track.length);
|
|
28
|
+
target.completedLaps = Math.min(Math.floor(positiveDistance / track.length), totalLaps);
|
|
29
29
|
target.currentLap = Math.max(1, lapIndex + 1);
|
|
30
30
|
target.currentSector = sectorIndex + 1;
|
|
31
31
|
target.currentSectorProgress = clamp((lapProgress - sectorStart) / sectorLength, 0, 1);
|
|
@@ -143,7 +143,10 @@ export function updateLapTelemetry(car, previousRaceDistance, currentTime, track
|
|
|
143
143
|
telemetry.lastSectors = copySectorTimes(telemetry.lastSectors, telemetry.currentSectors);
|
|
144
144
|
clearSectorTimes(telemetry.currentSectors);
|
|
145
145
|
telemetry.currentLapStartedAt = crossingTime;
|
|
146
|
-
telemetry.completedLaps = Math.
|
|
146
|
+
telemetry.completedLaps = Math.min(
|
|
147
|
+
totalLaps,
|
|
148
|
+
Math.max(telemetry.completedLaps + 1, Math.floor(boundaryDistance / track.length)),
|
|
149
|
+
);
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
telemetry.currentSectorStartedAt = crossingTime;
|
|
@@ -207,7 +210,7 @@ function syncInProgressLapTelemetry(telemetry, currentTime, currentRaceDistance,
|
|
|
207
210
|
const sectorProgress = ensureSectorTimesArray(telemetry.sectorProgress);
|
|
208
211
|
const liveSectors = ensureSectorTimesArray(telemetry.liveSectors);
|
|
209
212
|
|
|
210
|
-
telemetry.completedLaps = Math.floor(positiveDistance / track.length);
|
|
213
|
+
telemetry.completedLaps = Math.min(Math.floor(positiveDistance / track.length), totalLaps);
|
|
211
214
|
telemetry.currentLap = Math.max(1, lapIndex + 1);
|
|
212
215
|
telemetry.currentSector = activeIndex + 1;
|
|
213
216
|
telemetry.currentSectorProgress = clamp((lapProgress - sectorStart) / sectorLength, 0, 1);
|
|
@@ -234,7 +237,7 @@ function syncLapTelemetryPosition(telemetry, currentTime, currentRaceDistance, t
|
|
|
234
237
|
preserveCompletedSectors,
|
|
235
238
|
});
|
|
236
239
|
telemetry.sectorProgress = sectorProgress;
|
|
237
|
-
telemetry.completedLaps = Math.
|
|
240
|
+
telemetry.completedLaps = Math.min(telemetry.completedLaps, totalLaps);
|
|
238
241
|
telemetry.currentLapTime = Math.max(0, currentTime - telemetry.currentLapStartedAt);
|
|
239
242
|
telemetry.currentSectorElapsed = Math.max(0, currentTime - telemetry.currentSectorStartedAt);
|
|
240
243
|
syncLiveSectorTelemetry(telemetry, { preserveCompletedSectors });
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { finiteOrNull } from '../simMath.js';
|
|
2
|
+
export { finiteOrNull } from '../simMath.js';
|
|
3
|
+
|
|
1
4
|
import { TELEMETRY_SECTOR_COUNT } from './timingConstants.js';
|
|
2
5
|
|
|
3
6
|
const OVERALL_BEST_0 = '_sectorPerformanceOverallBest0';
|
|
@@ -108,9 +111,6 @@ export function updateSectorPerformance(cars, stats = null) {
|
|
|
108
111
|
return true;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
export function finiteOrNull(value) {
|
|
112
|
-
return Number.isFinite(value) ? value : null;
|
|
113
|
-
}
|
|
114
114
|
|
|
115
115
|
function classifySectorPerformance(value, personalBest, overallBest) {
|
|
116
116
|
if (!Number.isFinite(value)) return null;
|
|
@@ -76,7 +76,13 @@ export function createTimingLines(track) {
|
|
|
76
76
|
export function getTimingLineNumber(track, raceDistance) {
|
|
77
77
|
const spacing = track.timingLines?.spacing;
|
|
78
78
|
if (!Number.isFinite(spacing) || spacing <= 0 || !Number.isFinite(raceDistance)) return null;
|
|
79
|
-
|
|
79
|
+
const distance = Math.max(0, raceDistance);
|
|
80
|
+
let lineNumber = Math.floor(distance / spacing);
|
|
81
|
+
// Division can round onto a line whose stored product is still ahead of
|
|
82
|
+
// the car. Use the same representable boundary for lookup and recording.
|
|
83
|
+
if (lineNumber > 0 && lineNumber * spacing > distance) lineNumber -= 1;
|
|
84
|
+
else if ((lineNumber + 1) * spacing <= distance) lineNumber += 1;
|
|
85
|
+
return lineNumber;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
export function resetTimingLineCrossings(car, currentTime) {
|
|
@@ -105,7 +111,7 @@ export function recordTimingLineCrossings(car, previousRaceDistance, currentTime
|
|
|
105
111
|
? latestLine - (track.timingLines.count * TIMING_LINE_HISTORY_LAPS)
|
|
106
112
|
: null;
|
|
107
113
|
|
|
108
|
-
if (
|
|
114
|
+
if (travelled === 0) {
|
|
109
115
|
car.timingLineLastUpdatedAt = currentTime;
|
|
110
116
|
if (!Number.isFinite(cutoff) || cutoff <= 0 || cutoff === car.timingLinePruneCutoff) return;
|
|
111
117
|
pruneTimingLineCrossings(car, cutoff);
|
|
@@ -113,12 +119,12 @@ export function recordTimingLineCrossings(car, previousRaceDistance, currentTime
|
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
if (travelled >
|
|
117
|
-
const firstLine =
|
|
118
|
-
const lastLine =
|
|
122
|
+
if (travelled > 0 && travelled < track.length / 2) {
|
|
123
|
+
const firstLine = getTimingLineNumber(track, previousDistance) + 1;
|
|
124
|
+
const lastLine = getTimingLineNumber(track, currentDistance);
|
|
119
125
|
for (let lineNumber = firstLine; lineNumber <= lastLine; lineNumber += 1) {
|
|
120
126
|
const lineDistance = lineNumber * spacing;
|
|
121
|
-
if (lineDistance <= previousDistance
|
|
127
|
+
if (lineDistance <= previousDistance || lineDistance > currentDistance) continue;
|
|
122
128
|
const ratio = clamp((lineDistance - previousDistance) / travelled, 0, 1);
|
|
123
129
|
setTimingLineCrossing(car, lineNumber, previousTime + ratio * elapsed);
|
|
124
130
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { wrapDistance } from '../simMath.js';
|
|
2
|
+
import { PIT_ACCESS_SAMPLE_STEPS, PIT_ACCESS_SEARCH_STEP, PIT_ACCESS_TRACK_OVERLAP } from './trackConstants.js';
|
|
3
3
|
import { angleBetweenVectors, headingVector, normalizeVector, sampleCubicBezier, signedLateralOffsetToPoint } from './trackMath.js';
|
|
4
4
|
import { offsetTrackPoint, pointAt } from './spatialQueries.js';
|
|
5
5
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { clamp } from '../simMath.js';
|
|
2
2
|
import { metersToSimUnits } from '../units.js';
|
|
3
|
-
import { WORLD, PIT_BOX_COUNT, PIT_BOX_DEPTH,
|
|
3
|
+
import { WORLD, PIT_BOX_COUNT, PIT_BOX_DEPTH, PIT_BOX_TO_LANE_GAP, PIT_ENTRY_SEARCH_AFTER, PIT_ENTRY_SEARCH_BEFORE, PIT_EXIT_SEARCH_AFTER, PIT_EXIT_SEARCH_BEFORE, PIT_LANE_EDGE_GAP, PIT_LANE_ENTRY_BUFFER, PIT_LANE_EXIT_BUFFER, PIT_LANE_FINISH_RATIO, PIT_LANE_OFFSET_SEARCH_STEP, PIT_LANE_WIDTH, PIT_SERVICE_AREA_DEPTH, PIT_TEAM_COUNT, PIT_BOXES_PER_TEAM, PIT_WORKING_LANE_GAP, PIT_WORKING_LANE_WIDTH, PIT_ACCESS_MAX_LENGTH, PIT_ACCESS_MIN_LENGTH, PIT_ACCESS_TANGENT_RATIO, PIT_TRACK_CLEARANCE_MARGIN } from './trackConstants.js';
|
|
4
4
|
import { clonePoint, distance, headingVector, interpolatePitPoint, nearestPointOnPolyline, normalizeVector, pointWorldClearance, projectPitPoint, createPointBounds } from './trackMath.js';
|
|
5
5
|
import { findPitAccessConnection, createPitAccessRoadCenterline } from './pitLaneAccess.js';
|
|
6
6
|
import { pointAt } from './spatialQueries.js';
|
|
7
|
+
import { createPitBoxes, getPitBoxRunLength } from './pitServiceLayout.js';
|
|
8
|
+
|
|
9
|
+
export { getPitBoxRunLength, createPitBoxCorners, createPitRectangleCorners, createPitBoxes } from './pitServiceLayout.js';
|
|
7
10
|
|
|
8
11
|
export function createStartStraightBasis(track) {
|
|
9
12
|
const finish = pointAt(track, 0);
|
|
@@ -20,20 +23,6 @@ export function createStartStraightBasis(track) {
|
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
export function getPitBoxRunLength({
|
|
24
|
-
teamCount = PIT_TEAM_COUNT,
|
|
25
|
-
boxesPerTeam = PIT_BOXES_PER_TEAM,
|
|
26
|
-
boxLength = PIT_BOX_LENGTH,
|
|
27
|
-
pairGap = PIT_BOX_PAIR_GAP,
|
|
28
|
-
teamGap = PIT_TEAM_GAP,
|
|
29
|
-
} = {}) {
|
|
30
|
-
const boxCount = teamCount * boxesPerTeam;
|
|
31
|
-
return (
|
|
32
|
-
boxCount * boxLength +
|
|
33
|
-
teamCount * Math.max(0, boxesPerTeam - 1) * pairGap +
|
|
34
|
-
Math.max(0, teamCount - 1) * teamGap
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
26
|
|
|
38
27
|
export function createPitLaneLayout() {
|
|
39
28
|
const runLength = getPitBoxRunLength();
|
|
@@ -130,128 +119,8 @@ export function choosePitLanePlacement(track, baseLaneOffset, layout) {
|
|
|
130
119
|
return candidates.sort((left, right) => right.score - left.score)[0];
|
|
131
120
|
}
|
|
132
121
|
|
|
133
|
-
export function createPitBoxCorners(center, forward, serviceNormal) {
|
|
134
|
-
const halfLength = PIT_BOX_LENGTH / 2;
|
|
135
|
-
const halfDepth = PIT_BOX_DEPTH / 2;
|
|
136
|
-
return [
|
|
137
|
-
{
|
|
138
|
-
x: center.x + forward.x * halfLength + serviceNormal.x * halfDepth,
|
|
139
|
-
y: center.y + forward.y * halfLength + serviceNormal.y * halfDepth,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
x: center.x - forward.x * halfLength + serviceNormal.x * halfDepth,
|
|
143
|
-
y: center.y - forward.y * halfLength + serviceNormal.y * halfDepth,
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
x: center.x - forward.x * halfLength - serviceNormal.x * halfDepth,
|
|
147
|
-
y: center.y - forward.y * halfLength - serviceNormal.y * halfDepth,
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
x: center.x + forward.x * halfLength - serviceNormal.x * halfDepth,
|
|
151
|
-
y: center.y + forward.y * halfLength - serviceNormal.y * halfDepth,
|
|
152
|
-
},
|
|
153
|
-
];
|
|
154
|
-
}
|
|
155
122
|
|
|
156
|
-
export function createPitRectangleCorners(center, forward, serviceNormal, length, depth) {
|
|
157
|
-
const halfLength = length / 2;
|
|
158
|
-
const halfDepth = depth / 2;
|
|
159
|
-
return [
|
|
160
|
-
{
|
|
161
|
-
x: center.x + forward.x * halfLength + serviceNormal.x * halfDepth,
|
|
162
|
-
y: center.y + forward.y * halfLength + serviceNormal.y * halfDepth,
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
x: center.x - forward.x * halfLength + serviceNormal.x * halfDepth,
|
|
166
|
-
y: center.y - forward.y * halfLength + serviceNormal.y * halfDepth,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
x: center.x - forward.x * halfLength - serviceNormal.x * halfDepth,
|
|
170
|
-
y: center.y - forward.y * halfLength - serviceNormal.y * halfDepth,
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
x: center.x + forward.x * halfLength - serviceNormal.x * halfDepth,
|
|
174
|
-
y: center.y + forward.y * halfLength - serviceNormal.y * halfDepth,
|
|
175
|
-
},
|
|
176
|
-
];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function createPitBoxes({ laneStart, laneForward, laneLength, serviceNormal }) {
|
|
180
|
-
const runLength = getPitBoxRunLength();
|
|
181
|
-
let cursor = Math.max(PIT_SERVICE_QUEUE_GAP + PIT_BOX_LENGTH / 2, (laneLength - runLength) / 2);
|
|
182
|
-
const workingLaneOffset = PIT_LANE_WIDTH / 2 + PIT_WORKING_LANE_GAP + PIT_WORKING_LANE_WIDTH / 2;
|
|
183
|
-
const boxLateral = workingLaneOffset + PIT_WORKING_LANE_WIDTH / 2 + PIT_BOX_TO_LANE_GAP + PIT_BOX_DEPTH / 2;
|
|
184
|
-
const boxes = [];
|
|
185
|
-
const serviceAreas = [];
|
|
186
|
-
|
|
187
|
-
for (let index = 0; index < PIT_BOX_COUNT; index += 1) {
|
|
188
|
-
const distanceAlongLane = cursor + PIT_BOX_LENGTH / 2;
|
|
189
|
-
const laneTarget = interpolatePitPoint(laneStart, laneForward, distanceAlongLane);
|
|
190
|
-
const center = {
|
|
191
|
-
x: laneTarget.x + serviceNormal.x * boxLateral,
|
|
192
|
-
y: laneTarget.y + serviceNormal.y * boxLateral,
|
|
193
|
-
};
|
|
194
|
-
const teamIndex = Math.floor(index / PIT_BOXES_PER_TEAM);
|
|
195
|
-
const teamBoxIndex = index % PIT_BOXES_PER_TEAM;
|
|
196
123
|
|
|
197
|
-
boxes.push({
|
|
198
|
-
id: `team-${teamIndex + 1}-box-${teamBoxIndex + 1}`,
|
|
199
|
-
index,
|
|
200
|
-
teamIndex,
|
|
201
|
-
teamBoxIndex,
|
|
202
|
-
distanceAlongLane,
|
|
203
|
-
laneTarget,
|
|
204
|
-
center,
|
|
205
|
-
length: PIT_BOX_LENGTH,
|
|
206
|
-
depth: PIT_BOX_DEPTH,
|
|
207
|
-
corners: createPitBoxCorners(center, laneForward, serviceNormal),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
if (teamBoxIndex === PIT_BOXES_PER_TEAM - 1) {
|
|
211
|
-
const firstTeamBox = boxes[index - (PIT_BOXES_PER_TEAM - 1)];
|
|
212
|
-
const serviceDistance = (firstTeamBox.distanceAlongLane + distanceAlongLane) / 2;
|
|
213
|
-
const queueDistance = Math.max(PIT_SERVICE_QUEUE_GAP / 2, serviceDistance - PIT_SERVICE_QUEUE_GAP);
|
|
214
|
-
const serviceCenter = projectPitPoint(
|
|
215
|
-
laneStart,
|
|
216
|
-
laneForward,
|
|
217
|
-
serviceNormal,
|
|
218
|
-
serviceDistance,
|
|
219
|
-
workingLaneOffset,
|
|
220
|
-
);
|
|
221
|
-
const queuePoint = projectPitPoint(
|
|
222
|
-
laneStart,
|
|
223
|
-
laneForward,
|
|
224
|
-
serviceNormal,
|
|
225
|
-
queueDistance,
|
|
226
|
-
workingLaneOffset,
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
serviceAreas.push({
|
|
230
|
-
id: `team-${teamIndex + 1}-service`,
|
|
231
|
-
index: teamIndex,
|
|
232
|
-
teamIndex,
|
|
233
|
-
distanceAlongLane: serviceDistance,
|
|
234
|
-
queueDistanceAlongLane: queueDistance,
|
|
235
|
-
laneTarget: serviceCenter,
|
|
236
|
-
center: serviceCenter,
|
|
237
|
-
queuePoint,
|
|
238
|
-
length: PIT_SERVICE_AREA_LENGTH,
|
|
239
|
-
depth: PIT_SERVICE_AREA_DEPTH,
|
|
240
|
-
corners: createPitRectangleCorners(serviceCenter, laneForward, serviceNormal, PIT_SERVICE_AREA_LENGTH, PIT_SERVICE_AREA_DEPTH),
|
|
241
|
-
queueCorners: createPitRectangleCorners(queuePoint, laneForward, serviceNormal, PIT_SERVICE_AREA_LENGTH * 0.72, PIT_SERVICE_AREA_DEPTH),
|
|
242
|
-
garageBoxIds: boxes
|
|
243
|
-
.slice(index - (PIT_BOXES_PER_TEAM - 1), index + 1)
|
|
244
|
-
.map((box) => box.id),
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
cursor += PIT_BOX_LENGTH;
|
|
249
|
-
if (teamBoxIndex === 0) cursor += PIT_BOX_PAIR_GAP;
|
|
250
|
-
else if (teamIndex < PIT_TEAM_COUNT - 1) cursor += PIT_TEAM_GAP;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return { boxes, serviceAreas, workingLaneOffset };
|
|
254
|
-
}
|
|
255
124
|
|
|
256
125
|
export function createPitLaneBounds(pitLane) {
|
|
257
126
|
const points = [
|