@inventure71/paddockjs 4.2.0 → 4.3.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 +34 -0
- package/INSTALL_AND_UPDATE.md +23 -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 +80 -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 +36 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -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 +99 -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,5 @@
|
|
|
1
|
+
import { cancelScheduled, defaultScheduler, now } from './controllerScheduler.js';
|
|
2
|
+
|
|
1
3
|
export function createPaddockDriverControllerLoop({
|
|
2
4
|
runtime,
|
|
3
5
|
controller,
|
|
@@ -28,6 +30,8 @@ export function createPaddockDriverControllerLoop({
|
|
|
28
30
|
let previousActions = {};
|
|
29
31
|
let lastDecisionMs = 0;
|
|
30
32
|
let lastError = null;
|
|
33
|
+
let generation = 0;
|
|
34
|
+
let generationController = new AbortController();
|
|
31
35
|
|
|
32
36
|
async function ensureSpecs() {
|
|
33
37
|
if (!actionSpec) actionSpec = runtime.getActionSpec();
|
|
@@ -39,14 +43,24 @@ export function createPaddockDriverControllerLoop({
|
|
|
39
43
|
observationSpec = runtime.getObservationSpec();
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
async function runControllerHook(name, context) {
|
|
47
|
+
try {
|
|
48
|
+
return await controller[name]?.(context);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (context.signal.aborted) return;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
async function ensureInitialized(context) {
|
|
43
56
|
if (initialized) return;
|
|
44
|
-
await
|
|
45
|
-
initialized = true;
|
|
57
|
+
await runControllerHook('init', context);
|
|
58
|
+
if (!context.signal.aborted) initialized = true;
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
async function resetRuntime(options = {}, { stopPlayback = true } = {}) {
|
|
49
62
|
if (stopPlayback) stop();
|
|
63
|
+
const resetGeneration = generation;
|
|
50
64
|
result = runtime.reset(options);
|
|
51
65
|
previousResult = null;
|
|
52
66
|
heldActions = null;
|
|
@@ -57,9 +71,11 @@ export function createPaddockDriverControllerLoop({
|
|
|
57
71
|
previousActions = {};
|
|
58
72
|
lastError = null;
|
|
59
73
|
await refreshSpecs();
|
|
74
|
+
if (generation !== resetGeneration) return result;
|
|
60
75
|
const context = buildContext({ resetDriverIds: controlledDrivers() });
|
|
61
76
|
await ensureInitialized(context);
|
|
62
|
-
|
|
77
|
+
if (generation !== resetGeneration) return result;
|
|
78
|
+
await runControllerHook('reset', context);
|
|
63
79
|
return result;
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -75,6 +91,9 @@ export function createPaddockDriverControllerLoop({
|
|
|
75
91
|
if (typeof runtime.resetDrivers !== 'function') {
|
|
76
92
|
throw new Error('The supplied Paddock runtime does not support resetDrivers().');
|
|
77
93
|
}
|
|
94
|
+
const resumePlayback = running;
|
|
95
|
+
stop();
|
|
96
|
+
const resetGeneration = generation;
|
|
78
97
|
const resetDriverIds = Object.keys(placements);
|
|
79
98
|
previousResult = result;
|
|
80
99
|
result = runtime.resetDrivers(placements, resultOptions);
|
|
@@ -89,29 +108,39 @@ export function createPaddockDriverControllerLoop({
|
|
|
89
108
|
delete previousActions[driverId];
|
|
90
109
|
});
|
|
91
110
|
await ensureSpecs();
|
|
111
|
+
if (generation !== resetGeneration) return result;
|
|
92
112
|
const context = buildContext({ resetDriverIds });
|
|
93
113
|
await ensureInitialized(context);
|
|
94
|
-
|
|
114
|
+
if (generation !== resetGeneration) return result;
|
|
115
|
+
await runControllerHook('reset', context);
|
|
116
|
+
if (resumePlayback && generation === resetGeneration) start();
|
|
95
117
|
return result;
|
|
96
118
|
}
|
|
97
119
|
|
|
98
|
-
async function beginDecision() {
|
|
120
|
+
async function beginDecision(expectedGeneration) {
|
|
99
121
|
await ensureResult();
|
|
122
|
+
if (generation !== expectedGeneration) return false;
|
|
100
123
|
const context = buildContext();
|
|
101
124
|
await ensureInitialized(context);
|
|
125
|
+
if (generation !== expectedGeneration) return false;
|
|
102
126
|
const startedAt = now();
|
|
103
|
-
const actions = await
|
|
127
|
+
const actions = await runControllerHook('decideBatch', context);
|
|
128
|
+
if (generation !== expectedGeneration) return false;
|
|
104
129
|
lastDecisionMs = now() - startedAt;
|
|
105
130
|
heldActions = actions && typeof actions === 'object' ? actions : {};
|
|
106
131
|
heldFramesRemaining = repeat;
|
|
107
132
|
policyStep += 1;
|
|
133
|
+
return true;
|
|
108
134
|
}
|
|
109
135
|
|
|
110
|
-
async function stepFrame() {
|
|
136
|
+
async function stepFrame(expectedGeneration = generation) {
|
|
111
137
|
await ensureResult();
|
|
138
|
+
if (generation !== expectedGeneration) return result;
|
|
112
139
|
if (!heldActions || heldFramesRemaining <= 0) {
|
|
113
|
-
await beginDecision();
|
|
140
|
+
const decisionReady = await beginDecision(expectedGeneration);
|
|
141
|
+
if (!decisionReady) return result;
|
|
114
142
|
}
|
|
143
|
+
if (generation !== expectedGeneration) return result;
|
|
115
144
|
const actionIndex = repeat - heldFramesRemaining;
|
|
116
145
|
const actions = heldActions;
|
|
117
146
|
const previousActionsForStep = previousActions;
|
|
@@ -121,7 +150,8 @@ export function createPaddockDriverControllerLoop({
|
|
|
121
150
|
heldFramesRemaining -= 1;
|
|
122
151
|
runtimeStep = Number(result?.info?.step ?? runtimeStep + 1);
|
|
123
152
|
previousActions = actions;
|
|
124
|
-
|
|
153
|
+
if (generation !== expectedGeneration) return result;
|
|
154
|
+
await runControllerHook('onStep', buildContext({
|
|
125
155
|
actionIndex,
|
|
126
156
|
actions,
|
|
127
157
|
previousActions: previousActionsForStep,
|
|
@@ -133,8 +163,10 @@ export function createPaddockDriverControllerLoop({
|
|
|
133
163
|
async function step() {
|
|
134
164
|
let latest = result;
|
|
135
165
|
const targetPolicyStep = policyStep + 1;
|
|
166
|
+
const expectedGeneration = generation;
|
|
136
167
|
while (policyStep < targetPolicyStep || heldFramesRemaining > 0) {
|
|
137
|
-
latest = await stepFrame();
|
|
168
|
+
latest = await stepFrame(expectedGeneration);
|
|
169
|
+
if (generation !== expectedGeneration) break;
|
|
138
170
|
if (latest?.done) break;
|
|
139
171
|
if (policyStep >= targetPolicyStep && heldFramesRemaining <= 0) break;
|
|
140
172
|
}
|
|
@@ -145,18 +177,21 @@ export function createPaddockDriverControllerLoop({
|
|
|
145
177
|
if (running) return;
|
|
146
178
|
lastError = null;
|
|
147
179
|
running = true;
|
|
180
|
+
const expectedGeneration = generation;
|
|
148
181
|
const schedule = scheduler ?? defaultScheduler();
|
|
149
182
|
const tick = async () => {
|
|
150
|
-
if (!running) return;
|
|
183
|
+
if (!running || generation !== expectedGeneration) return;
|
|
151
184
|
scheduled = null;
|
|
152
185
|
try {
|
|
153
|
-
await stepFrame();
|
|
186
|
+
await stepFrame(expectedGeneration);
|
|
154
187
|
} catch (error) {
|
|
188
|
+
if (generation !== expectedGeneration) return;
|
|
155
189
|
lastError = error;
|
|
156
190
|
running = false;
|
|
157
191
|
scheduled = null;
|
|
158
192
|
return;
|
|
159
193
|
}
|
|
194
|
+
if (generation !== expectedGeneration) return;
|
|
160
195
|
if (!running || result?.done) {
|
|
161
196
|
running = false;
|
|
162
197
|
return;
|
|
@@ -167,9 +202,17 @@ export function createPaddockDriverControllerLoop({
|
|
|
167
202
|
}
|
|
168
203
|
|
|
169
204
|
function stop() {
|
|
205
|
+
const previousController = generationController;
|
|
206
|
+
const previousSchedule = scheduled;
|
|
170
207
|
running = false;
|
|
171
|
-
|
|
208
|
+
generation += 1;
|
|
209
|
+
generationController = new AbortController();
|
|
172
210
|
scheduled = null;
|
|
211
|
+
try {
|
|
212
|
+
cancelScheduled(previousSchedule);
|
|
213
|
+
} finally {
|
|
214
|
+
previousController.abort();
|
|
215
|
+
}
|
|
173
216
|
}
|
|
174
217
|
|
|
175
218
|
function controlledDrivers() {
|
|
@@ -184,6 +227,7 @@ export function createPaddockDriverControllerLoop({
|
|
|
184
227
|
};
|
|
185
228
|
return {
|
|
186
229
|
runtime,
|
|
230
|
+
signal: generationController.signal,
|
|
187
231
|
mode,
|
|
188
232
|
actionRepeat: repeat,
|
|
189
233
|
controlledDrivers: drivers,
|
|
@@ -242,34 +286,3 @@ export function createPaddockDriverControllerLoop({
|
|
|
242
286
|
},
|
|
243
287
|
};
|
|
244
288
|
}
|
|
245
|
-
|
|
246
|
-
function now() {
|
|
247
|
-
return typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
248
|
-
? performance.now()
|
|
249
|
-
: Date.now();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function cancelScheduled(handle) {
|
|
253
|
-
if (handle == null) return;
|
|
254
|
-
if (typeof handle.cancel === 'function') {
|
|
255
|
-
handle.cancel();
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (typeof handle === 'number') {
|
|
259
|
-
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
|
|
260
|
-
if (typeof clearTimeout === 'function') clearTimeout(handle);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function defaultScheduler() {
|
|
265
|
-
if (typeof requestAnimationFrame === 'function') {
|
|
266
|
-
return (callback) => {
|
|
267
|
-
const id = requestAnimationFrame(callback);
|
|
268
|
-
return { cancel: () => cancelAnimationFrame(id) };
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
return (callback) => {
|
|
272
|
-
const id = setTimeout(callback, 0);
|
|
273
|
-
return { cancel: () => clearTimeout(id) };
|
|
274
|
-
};
|
|
275
|
-
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function now() {
|
|
2
|
+
return typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
3
|
+
? performance.now()
|
|
4
|
+
: Date.now();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function cancelScheduled(handle) {
|
|
8
|
+
if (handle == null) return;
|
|
9
|
+
if (typeof handle.cancel === 'function') {
|
|
10
|
+
handle.cancel();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (typeof handle === 'number') {
|
|
14
|
+
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
|
|
15
|
+
if (typeof clearTimeout === 'function') clearTimeout(handle);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultScheduler() {
|
|
20
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
21
|
+
return (callback) => {
|
|
22
|
+
const id = requestAnimationFrame(callback);
|
|
23
|
+
return { cancel: () => cancelAnimationFrame(id) };
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return (callback) => {
|
|
27
|
+
const id = setTimeout(callback, 0);
|
|
28
|
+
return { cancel: () => clearTimeout(id) };
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { metersToSimUnits, simUnitsToMeters } from '../simulation/units.js';
|
|
2
|
+
import { normalizeAngle } from '../simulation/simMath.js';
|
|
3
|
+
import { pointAt } from '../simulation/track/trackModel.js';
|
|
4
|
+
import { normalizeLookaheadMeters } from './observationOptions.js';
|
|
5
|
+
import { buildBodySenses } from './sensors/bodySenses.js';
|
|
6
|
+
import { buildBoundarySenses } from './sensors/boundarySenses.js';
|
|
7
|
+
import { buildContactPatchSenses } from './sensors/contactSenses.js';
|
|
8
|
+
import { enrichOpponentRadar } from './sensors/opponentRadar.js';
|
|
9
|
+
import { buildNearbyCars, appendRaySensorVectorValues, buildRaySensors } from './sensors.js';
|
|
10
|
+
import { buildObservationVector } from './observationVector.js';
|
|
11
|
+
|
|
12
|
+
export function buildDriverObservation(car, snapshot, options, events, sensors, sensorContexts) {
|
|
13
|
+
const output = options.observation?.output ?? 'full';
|
|
14
|
+
const includeSchema = options.observation?.includeSchema !== false;
|
|
15
|
+
const wantsObject = output === 'full' || output === 'object';
|
|
16
|
+
const wantsVector = output === 'full' || output === 'vector';
|
|
17
|
+
const object = wantsObject
|
|
18
|
+
? buildDriverObservationObject(car, snapshot, options, events, sensors, sensorContexts)
|
|
19
|
+
: null;
|
|
20
|
+
let vector;
|
|
21
|
+
let schema = [];
|
|
22
|
+
if (wantsVector) {
|
|
23
|
+
const encoded = !object && !includeSchema
|
|
24
|
+
? buildDriverVectorDirect(car, snapshot, options, sensors, sensorContexts)
|
|
25
|
+
: buildObservationVector(
|
|
26
|
+
object ?? buildDriverObservationObject(car, snapshot, options, events, sensors, sensorContexts),
|
|
27
|
+
sensors,
|
|
28
|
+
{ includeSchema, vectorType: options.observation?.vectorType },
|
|
29
|
+
);
|
|
30
|
+
vector = encoded.vector;
|
|
31
|
+
schema = encoded.schema;
|
|
32
|
+
}
|
|
33
|
+
return { object, vector, schema, events };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildDriverNearbyCars(car, snapshot, sensors, sensorContexts) {
|
|
37
|
+
if (!sensors.nearbyCars.enabled) return [];
|
|
38
|
+
const context = sensorContexts?.getNearbyBatchContext();
|
|
39
|
+
return enrichOpponentRadar(car, buildNearbyCars(car, snapshot, sensors.nearbyCars, context), snapshot, context);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildDriverObservationObject(car, snapshot, options, events, sensors, sensorContexts = null) {
|
|
43
|
+
const sensorCar = withEnvironmentControlFlag(car, options);
|
|
44
|
+
const onTrack = isCarLegallyOnTrack(car);
|
|
45
|
+
const body = buildBodySenses(car);
|
|
46
|
+
const trackHeadingError = car.trackHeadingError ?? estimateTrackHeadingError(car, snapshot);
|
|
47
|
+
const trackRelation = {
|
|
48
|
+
...buildBoundarySenses(car, snapshot, onTrack),
|
|
49
|
+
headingErrorRadians: trackHeadingError,
|
|
50
|
+
};
|
|
51
|
+
const nearbyCars = buildDriverNearbyCars(car, snapshot, sensors, sensorContexts);
|
|
52
|
+
return {
|
|
53
|
+
profile: options.observation?.profile ?? 'default',
|
|
54
|
+
self: {
|
|
55
|
+
...body,
|
|
56
|
+
lap: car.lap,
|
|
57
|
+
completedLaps: car.lapTelemetry?.completedLaps ?? 0,
|
|
58
|
+
lapProgressMeters: simUnitsToMeters(car.progress ?? 0),
|
|
59
|
+
trackOffsetMeters: trackRelation.lateralOffsetMeters,
|
|
60
|
+
trackHeadingErrorRadians: trackHeadingError,
|
|
61
|
+
onTrack,
|
|
62
|
+
surface: car.surface ?? 'track',
|
|
63
|
+
inPitLane: Boolean(car.inPitLane),
|
|
64
|
+
pitLanePart: car.pitLanePart ?? null,
|
|
65
|
+
pitBoxId: car.pitBoxId ?? null,
|
|
66
|
+
tireEnergy: car.tireEnergy ?? null,
|
|
67
|
+
pitIntent: car.pitIntent ?? car.pitStop?.intent ?? 0,
|
|
68
|
+
pitTargetCompound: car.pitStop?.targetTire ?? null,
|
|
69
|
+
pitStopStatus: car.pitStop?.status ?? null,
|
|
70
|
+
pitStopPhase: car.pitStop?.phase ?? null,
|
|
71
|
+
pitStopServiceRemainingSeconds: car.pitStop?.serviceRemainingSeconds ?? null,
|
|
72
|
+
pitStopPenaltyServiceRemainingSeconds: car.pitStop?.penaltyServiceRemainingSeconds ?? null,
|
|
73
|
+
pitStopsCompleted: car.pitStop?.stopsCompleted ?? 0,
|
|
74
|
+
},
|
|
75
|
+
trackRelation,
|
|
76
|
+
contactPatches: buildContactPatchSenses(car),
|
|
77
|
+
race: {
|
|
78
|
+
position: car.rank,
|
|
79
|
+
totalCars: snapshot.cars.length,
|
|
80
|
+
raceMode: snapshot.raceControl.mode,
|
|
81
|
+
pitLaneOpen: Boolean(snapshot.raceControl.pitLaneOpen),
|
|
82
|
+
redFlag: Boolean(snapshot.raceControl.redFlag),
|
|
83
|
+
totalLaps: snapshot.totalLaps,
|
|
84
|
+
},
|
|
85
|
+
track: {
|
|
86
|
+
lengthMeters: simUnitsToMeters(snapshot.track.length ?? 0),
|
|
87
|
+
widthMeters: simUnitsToMeters(snapshot.track.width ?? 0),
|
|
88
|
+
curvature: car.trackState?.curvature ?? pointAt(snapshot.track, car.progress ?? 0).curvature ?? 0,
|
|
89
|
+
lookahead: buildTrackLookahead(car, snapshot, options),
|
|
90
|
+
},
|
|
91
|
+
rays: sensors.rays.enabled ? buildRaySensors(sensorCar, snapshot, sensors.rays, rayBatchContextForSensors(sensors, sensorContexts?.getRayBatchContext)) : [],
|
|
92
|
+
nearbyCars,
|
|
93
|
+
events,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function estimateTrackHeadingError(car, snapshot) {
|
|
98
|
+
const base = pointAt(snapshot.track, car.progress ?? 0);
|
|
99
|
+
return normalizeAngle((car.heading ?? base.heading) - base.heading);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildTrackLookahead(car, snapshot, options) {
|
|
103
|
+
const distances = Array.isArray(options.observation?.lookaheadMeters)
|
|
104
|
+
? options.observation.lookaheadMeters
|
|
105
|
+
: normalizeLookaheadMeters(options.observation?.lookaheadMeters);
|
|
106
|
+
if (!distances.length) return [];
|
|
107
|
+
const base = pointAt(snapshot.track, car.progress ?? 0);
|
|
108
|
+
return distances.map((distanceMeters) => {
|
|
109
|
+
const sample = pointAt(snapshot.track, (car.progress ?? 0) + metersToSimUnits(distanceMeters));
|
|
110
|
+
return {
|
|
111
|
+
distanceMeters,
|
|
112
|
+
curvature: sample.curvature ?? 0,
|
|
113
|
+
headingDeltaRadians: normalizeAngle(sample.heading - base.heading),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isCarLegallyOnTrack(car) {
|
|
119
|
+
if (Array.isArray(car.wheels) && car.wheels.length > 0) {
|
|
120
|
+
return car.wheels.every((wheel) => wheel.onTrack || wheel.inPitLane);
|
|
121
|
+
}
|
|
122
|
+
if (car.inPitLane) return true;
|
|
123
|
+
return ['track', 'kerb', 'pit-entry', 'pit-lane', 'pit-exit', 'pit-box'].includes(car.surface ?? 'track');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildDriverVectorDirect(car, snapshot, options, sensors, sensorContexts = null) {
|
|
127
|
+
const profile = options.observation?.profile ?? 'default';
|
|
128
|
+
const includePhysicalDriverSenses = profile === 'physical-driver';
|
|
129
|
+
const onTrack = isCarLegallyOnTrack(car);
|
|
130
|
+
const body = buildBodySenses(car);
|
|
131
|
+
const trackHeadingError = car.trackHeadingError ?? estimateTrackHeadingError(car, snapshot);
|
|
132
|
+
const sensorCar = withEnvironmentControlFlag(car, options);
|
|
133
|
+
const trackRelation = {
|
|
134
|
+
...buildBoundarySenses(car, snapshot, onTrack),
|
|
135
|
+
headingErrorRadians: trackHeadingError,
|
|
136
|
+
};
|
|
137
|
+
const contactPatches = includePhysicalDriverSenses ? buildContactPatchSenses(car) : [];
|
|
138
|
+
const appendRayVectorValues = sensors.rays.enabled
|
|
139
|
+
? (vector) => appendRaySensorVectorValues(
|
|
140
|
+
vector,
|
|
141
|
+
sensorCar,
|
|
142
|
+
snapshot,
|
|
143
|
+
sensors.rays,
|
|
144
|
+
rayBatchContextForSensors(sensors, sensorContexts?.getRayBatchContext, { scratch: true }),
|
|
145
|
+
)
|
|
146
|
+
: null;
|
|
147
|
+
const nearbyCars = buildDriverNearbyCars(car, snapshot, sensors, sensorContexts);
|
|
148
|
+
const trackLengthMeters = simUnitsToMeters(snapshot.track.length ?? 0);
|
|
149
|
+
const trackCurvature = car.trackState?.curvature ?? pointAt(snapshot.track, car.progress ?? 0).curvature ?? 0;
|
|
150
|
+
const source = {
|
|
151
|
+
profile,
|
|
152
|
+
self: {
|
|
153
|
+
...body,
|
|
154
|
+
lapProgressMeters: simUnitsToMeters(car.progress ?? 0),
|
|
155
|
+
trackOffsetMeters: trackRelation.lateralOffsetMeters,
|
|
156
|
+
trackHeadingErrorRadians: trackHeadingError,
|
|
157
|
+
onTrack,
|
|
158
|
+
inPitLane: Boolean(car.inPitLane),
|
|
159
|
+
tireEnergy: car.tireEnergy ?? null,
|
|
160
|
+
pitIntent: car.pitIntent ?? car.pitStop?.intent ?? 0,
|
|
161
|
+
pitStopStatus: car.pitStop?.status ?? null,
|
|
162
|
+
},
|
|
163
|
+
trackRelation,
|
|
164
|
+
contactPatches,
|
|
165
|
+
race: {
|
|
166
|
+
position: car.rank,
|
|
167
|
+
totalCars: snapshot.cars.length,
|
|
168
|
+
raceMode: snapshot.raceControl.mode,
|
|
169
|
+
pitLaneOpen: Boolean(snapshot.raceControl.pitLaneOpen),
|
|
170
|
+
redFlag: Boolean(snapshot.raceControl.redFlag),
|
|
171
|
+
},
|
|
172
|
+
track: {
|
|
173
|
+
lengthMeters: trackLengthMeters,
|
|
174
|
+
curvature: trackCurvature,
|
|
175
|
+
lookahead: buildTrackLookahead(car, snapshot, options),
|
|
176
|
+
},
|
|
177
|
+
rays: [],
|
|
178
|
+
appendRayVectorValues,
|
|
179
|
+
nearbyCars,
|
|
180
|
+
};
|
|
181
|
+
return buildObservationVector(source, sensors, {
|
|
182
|
+
includeSchema: false,
|
|
183
|
+
vectorType: options.observation?.vectorType,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function rayBatchContextForSensors(sensors, getRayBatchContext, { scratch = false } = {}) {
|
|
188
|
+
if (!getRayBatchContext || !sensors.rays?.channels?.includes('car')) return null;
|
|
189
|
+
return getRayBatchContext({ scratch });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function emptyObservation(driverId) {
|
|
193
|
+
return {
|
|
194
|
+
object: {
|
|
195
|
+
self: {
|
|
196
|
+
id: driverId,
|
|
197
|
+
speedKph: 0,
|
|
198
|
+
speedMetersPerSecond: 0,
|
|
199
|
+
headingRadians: 0,
|
|
200
|
+
yawRateRadiansPerSecond: 0,
|
|
201
|
+
steeringAngleRadians: 0,
|
|
202
|
+
throttle: 0,
|
|
203
|
+
brake: 0,
|
|
204
|
+
lateralG: 0,
|
|
205
|
+
longitudinalG: 0,
|
|
206
|
+
gripUsage: 0,
|
|
207
|
+
slipAngleRadians: 0,
|
|
208
|
+
tractionLimited: false,
|
|
209
|
+
stabilityState: 'stable',
|
|
210
|
+
destroyed: false,
|
|
211
|
+
destroyReason: null,
|
|
212
|
+
lap: 0,
|
|
213
|
+
completedLaps: 0,
|
|
214
|
+
lapProgressMeters: 0,
|
|
215
|
+
trackOffsetMeters: 0,
|
|
216
|
+
trackHeadingErrorRadians: 0,
|
|
217
|
+
onTrack: false,
|
|
218
|
+
surface: 'missing',
|
|
219
|
+
inPitLane: false,
|
|
220
|
+
pitLanePart: null,
|
|
221
|
+
pitBoxId: null,
|
|
222
|
+
tireEnergy: null,
|
|
223
|
+
pitIntent: 0,
|
|
224
|
+
pitTargetCompound: null,
|
|
225
|
+
pitStopStatus: null,
|
|
226
|
+
pitStopPhase: null,
|
|
227
|
+
pitStopServiceRemainingSeconds: null,
|
|
228
|
+
pitStopPenaltyServiceRemainingSeconds: null,
|
|
229
|
+
pitStopsCompleted: 0,
|
|
230
|
+
},
|
|
231
|
+
trackRelation: {
|
|
232
|
+
lateralOffsetMeters: 0,
|
|
233
|
+
headingErrorRadians: 0,
|
|
234
|
+
legalWidthMeters: 0,
|
|
235
|
+
leftBoundaryMeters: 0,
|
|
236
|
+
rightBoundaryMeters: 0,
|
|
237
|
+
onLegalSurface: false,
|
|
238
|
+
surface: 'missing',
|
|
239
|
+
},
|
|
240
|
+
contactPatches: [],
|
|
241
|
+
race: { position: 0, totalCars: 0, raceMode: 'missing', pitLaneOpen: false, redFlag: false, totalLaps: 0 },
|
|
242
|
+
track: { lengthMeters: 0, widthMeters: 0, curvature: 0, lookahead: [] },
|
|
243
|
+
rays: [],
|
|
244
|
+
nearbyCars: [],
|
|
245
|
+
events: [],
|
|
246
|
+
},
|
|
247
|
+
vector: [],
|
|
248
|
+
schema: [],
|
|
249
|
+
events: [],
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function withEnvironmentControlFlag(car, options) {
|
|
254
|
+
if (!car || car.environmentControlled) return car;
|
|
255
|
+
if (!Array.isArray(options?.controlledDrivers) || !options.controlledDrivers.includes(car.id)) return car;
|
|
256
|
+
return {
|
|
257
|
+
...car,
|
|
258
|
+
environmentControlled: true,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function initializeControlledPitIntent(host) {
|
|
2
|
+
const sim = host.getSimulation?.();
|
|
3
|
+
const options = host.getOptions?.();
|
|
4
|
+
initializeControlledPitIntentForSimulation(sim, options);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function initializeControlledPitIntentForSimulation(sim, options) {
|
|
8
|
+
options?.controlledDrivers?.forEach?.((driverId) => {
|
|
9
|
+
sim?.setAutomaticPitIntentEnabled?.(driverId, false);
|
|
10
|
+
sim?.setPitIntent?.(driverId, 0);
|
|
11
|
+
const car = sim?.cars?.find?.((item) => item.id === driverId);
|
|
12
|
+
if (car) car.environmentControlled = true;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resetEnvironmentPitStop(car) {
|
|
17
|
+
const stop = car?.pitStop;
|
|
18
|
+
if (!stop) return;
|
|
19
|
+
stop.status = 'pending';
|
|
20
|
+
stop.phase = null;
|
|
21
|
+
stop.intent = 0;
|
|
22
|
+
stop.queueingForService = false;
|
|
23
|
+
stop.route = null;
|
|
24
|
+
stop.routeProgress = 0;
|
|
25
|
+
stop.routeStartRaceDistance = null;
|
|
26
|
+
stop.routeEndRaceDistance = null;
|
|
27
|
+
stop.serviceRemaining = 0;
|
|
28
|
+
stop.penaltyServiceRemaining = 0;
|
|
29
|
+
stop.penaltyServiceTotal = 0;
|
|
30
|
+
stop.servingPenaltyIds = [];
|
|
31
|
+
stop.serviceProfile = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function applyControlledRunoffResponse(sim, options) {
|
|
35
|
+
options?.controlledDrivers?.forEach?.((driverId) => {
|
|
36
|
+
const car = sim?.cars?.find?.((item) => item.id === driverId);
|
|
37
|
+
if (car) sim?.applyRunoffResponse?.(car);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export function resetControlledDriverState(sim, driverIds) {
|
|
43
|
+
driverIds.forEach((driverId) => {
|
|
44
|
+
sim.clearCarControls?.(driverId);
|
|
45
|
+
sim.setAutomaticPitIntentEnabled?.(driverId, false);
|
|
46
|
+
const car = sim.cars?.find?.((item) => item.id === driverId);
|
|
47
|
+
resetEnvironmentPitStop(car);
|
|
48
|
+
sim.setPitIntent?.(driverId, 0);
|
|
49
|
+
if (car) sim.applyRunoffResponse?.(car);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -92,7 +92,6 @@ export function evaluateEpisode(snapshot, options, episodeState, controlledDrive
|
|
|
92
92
|
if (!state.truncated && state.episodeStep < options.episode.maxSteps) allTruncated = false;
|
|
93
93
|
if (state.truncated || state.episodeStep >= options.episode.maxSteps) anyTruncated = true;
|
|
94
94
|
if (!state.terminated && !state.truncated && state.episodeStep < options.episode.maxSteps) allEnded = false;
|
|
95
|
-
if (!allTerminated && !allTruncated && !allEnded) break;
|
|
96
95
|
}
|
|
97
96
|
if (allTerminated) {
|
|
98
97
|
return { terminated: true, truncated: false, endReason: controlledDrivers.length === 1 ? firstTerminatedReason : 'all-drivers-terminated' };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { createEvaluationTracker } from './evaluationTracker.js';
|
|
2
|
+
export { createEvaluationTracker } from './evaluationTracker.js';
|
|
3
3
|
import { createPaddockEnvironment } from './runtime.js';
|
|
4
4
|
|
|
5
5
|
export const DEFAULT_EVALUATION_CASES = Object.freeze([
|
|
@@ -37,6 +37,7 @@ export function runEnvironmentEvaluation({
|
|
|
37
37
|
baseOptions,
|
|
38
38
|
policy,
|
|
39
39
|
cases = DEFAULT_EVALUATION_CASES,
|
|
40
|
+
lapScope = 'since-start',
|
|
40
41
|
createEnvironment = createPaddockEnvironment,
|
|
41
42
|
} = {}) {
|
|
42
43
|
if (!baseOptions || typeof baseOptions !== 'object') {
|
|
@@ -52,6 +53,7 @@ export function runEnvironmentEvaluation({
|
|
|
52
53
|
policy,
|
|
53
54
|
evaluationCase,
|
|
54
55
|
createEnvironment,
|
|
56
|
+
lapScope,
|
|
55
57
|
})),
|
|
56
58
|
};
|
|
57
59
|
}
|
|
@@ -61,89 +63,7 @@ function isEvaluationPolicy(policy) {
|
|
|
61
63
|
(policy != null && typeof policy.predict === 'function');
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
if (!initialResult?.state?.snapshot) {
|
|
66
|
-
throw new Error('PaddockJS environment evaluation tracker requires result.state.snapshot; use result.stateOutput "minimal" or "full".');
|
|
67
|
-
}
|
|
68
|
-
const initialCars = carsById(initialResult.state.snapshot);
|
|
69
|
-
const metrics = Object.fromEntries(initialResult.info.controlledDrivers.map((driverId) => {
|
|
70
|
-
const car = initialCars.get(driverId);
|
|
71
|
-
const offTrack = isEnvironmentCarOffTrack(car);
|
|
72
|
-
return [driverId, {
|
|
73
|
-
distanceMeters: 0,
|
|
74
|
-
lapProgressMeters: lapProgressMetersFromSnapshotCar(car),
|
|
75
|
-
offTrackSteps: offTrack ? 1 : 0,
|
|
76
|
-
contactCount: 0,
|
|
77
|
-
recoverySuccess: false,
|
|
78
|
-
passCount: 0,
|
|
79
|
-
lapTimeSeconds: null,
|
|
80
|
-
startedOffTrack: offTrack,
|
|
81
|
-
startPosition: car?.rank ?? null,
|
|
82
|
-
bestPosition: car?.rank ?? null,
|
|
83
|
-
startDistanceMeters: cumulativeDistanceMetersFromSnapshotCar(car),
|
|
84
|
-
}];
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
update(result) {
|
|
89
|
-
const currentCars = carsById(result.state.snapshot);
|
|
90
|
-
result.info.controlledDrivers.forEach((driverId) => {
|
|
91
|
-
const car = currentCars.get(driverId);
|
|
92
|
-
const entry = metrics[driverId];
|
|
93
|
-
if (!car || !entry) return;
|
|
94
|
-
entry.distanceMeters = Math.max(0, cumulativeDistanceMetersFromSnapshotCar(car) - entry.startDistanceMeters);
|
|
95
|
-
entry.lapProgressMeters = lapProgressMetersFromSnapshotCar(car);
|
|
96
|
-
const offTrack = isEnvironmentCarOffTrack(car);
|
|
97
|
-
if (offTrack) entry.offTrackSteps += 1;
|
|
98
|
-
if (entry.startedOffTrack && !offTrack) entry.recoverySuccess = true;
|
|
99
|
-
if (Number.isFinite(car.rank)) {
|
|
100
|
-
entry.bestPosition = entry.bestPosition == null ? car.rank : Math.min(entry.bestPosition, car.rank);
|
|
101
|
-
entry.passCount = entry.startPosition == null ? 0 : Math.max(0, entry.startPosition - entry.bestPosition);
|
|
102
|
-
}
|
|
103
|
-
const completedLaps = car.lapTelemetry?.completedLaps ?? 0;
|
|
104
|
-
if (completedLaps > 0 && entry.lapTimeSeconds == null) {
|
|
105
|
-
entry.lapTimeSeconds = result.info.elapsedSeconds;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
result.events.forEach((event) => {
|
|
109
|
-
if (!isEnvironmentContactEvent(event)) return;
|
|
110
|
-
Object.keys(metrics).forEach((driverId) => {
|
|
111
|
-
if (event.driverId === driverId || event.carId === driverId || event.otherCarId === driverId || event.driverIds?.includes?.(driverId)) {
|
|
112
|
-
metrics[driverId].contactCount += 1;
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
},
|
|
117
|
-
finish() {
|
|
118
|
-
return Object.fromEntries(Object.entries(metrics).map(([driverId, entry]) => [
|
|
119
|
-
driverId,
|
|
120
|
-
{
|
|
121
|
-
distanceMeters: entry.distanceMeters,
|
|
122
|
-
lapProgressMeters: entry.lapProgressMeters,
|
|
123
|
-
offTrackSteps: entry.offTrackSteps,
|
|
124
|
-
contactCount: entry.contactCount,
|
|
125
|
-
recoverySuccess: entry.recoverySuccess,
|
|
126
|
-
passCount: entry.passCount,
|
|
127
|
-
lapTimeSeconds: entry.lapTimeSeconds,
|
|
128
|
-
},
|
|
129
|
-
]));
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function cumulativeDistanceMetersFromSnapshotCar(car) {
|
|
135
|
-
const distance = Number(car?.distanceMeters ?? car?.raceDistanceMeters);
|
|
136
|
-
if (Number.isFinite(distance)) return distance;
|
|
137
|
-
return simUnitsToMeters(car?.raceDistance ?? 0);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function lapProgressMetersFromSnapshotCar(car) {
|
|
141
|
-
const progressMeters = Number(car?.lapProgressMeters ?? car?.progressMeters);
|
|
142
|
-
if (Number.isFinite(progressMeters)) return progressMeters;
|
|
143
|
-
return simUnitsToMeters(car?.progress ?? 0);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function runEvaluationCase({ baseOptions, policy, evaluationCase, createEnvironment }) {
|
|
66
|
+
function runEvaluationCase({ baseOptions, policy, evaluationCase, createEnvironment, lapScope }) {
|
|
147
67
|
const env = createEnvironment({
|
|
148
68
|
...baseOptions,
|
|
149
69
|
result: {
|
|
@@ -159,7 +79,7 @@ function runEvaluationCase({ baseOptions, policy, evaluationCase, createEnvironm
|
|
|
159
79
|
});
|
|
160
80
|
try {
|
|
161
81
|
let result = env.reset();
|
|
162
|
-
const tracker = createEvaluationTracker(result);
|
|
82
|
+
const tracker = createEvaluationTracker(result, { lapScope });
|
|
163
83
|
const maxSteps = Math.max(1, Math.floor(evaluationCase.maxSteps ?? baseOptions.episode?.maxSteps ?? 1000));
|
|
164
84
|
let steps = 0;
|
|
165
85
|
while (!result.done && steps < maxSteps) {
|
|
@@ -200,7 +120,3 @@ function predictAction(policy, observation, context) {
|
|
|
200
120
|
: policy.predict(observation, context);
|
|
201
121
|
return action ?? { steering: 0, throttle: 0, brake: 1 };
|
|
202
122
|
}
|
|
203
|
-
|
|
204
|
-
function carsById(snapshot) {
|
|
205
|
-
return new Map(snapshot.cars.map((car) => [car.id, car]));
|
|
206
|
-
}
|