@inventure71/paddockjs 0.3.0 → 2.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 +91 -0
- package/INSTALL_AND_UPDATE.md +5 -2
- package/README.md +328 -13
- package/docs/architecture.md +150 -41
- package/docs/concepts.md +22 -3
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +474 -34
- package/docs/data_model.md +1 -1
- package/docs/index.md +1 -0
- package/docs/learnings.md +23 -1
- package/docs/rules.md +169 -21
- package/docs/system_specs.md +56 -20
- package/docs/training.md +369 -25
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/examples/train-basic-policy.mjs +5 -6
- package/examples/trainingData.mjs +133 -0
- package/package.json +15 -4
- package/src/api/PaddockSimulatorController.js +36 -2
- package/src/app/BrowserExpertAdapter.js +367 -9
- package/src/app/F1SimulatorApp.js +615 -940
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +14 -1
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +24 -4
- package/src/environment/actions.js +38 -7
- package/src/environment/controllerLoop.js +246 -0
- package/src/environment/episode.js +86 -2
- package/src/environment/evaluation.js +206 -0
- package/src/environment/index.d.ts +638 -6
- package/src/environment/index.js +14 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +33 -0
- package/src/environment/observations.js +500 -43
- package/src/environment/options.js +41 -2
- package/src/environment/recorder.js +28 -0
- package/src/environment/runtime.js +260 -24
- package/src/environment/scenarios.js +249 -0
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +65 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +60 -0
- package/src/environment/sensors/surfaceRays.js +296 -0
- package/src/environment/sensors/trackRays.js +261 -0
- package/src/environment/sensors.js +1 -257
- package/src/environment/specs.js +188 -9
- package/src/environment/workerProtocol.js +64 -0
- package/src/index.d.ts +533 -7
- package/src/index.js +26 -0
- package/src/rendering/proceduralTrackAsset.js +20 -510
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +214 -0
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -253
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +234 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +55 -0
- package/src/simulation/pit/pitRouteMovement.js +160 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +41 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +92 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +71 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +124 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +86 -0
- package/src/simulation/race/stalledDnf.js +86 -0
- package/src/simulation/raceSimulation.js +386 -1012
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/collisionSteward.js +69 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyLedger.js +153 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +141 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/tireRequirementSteward.js +29 -0
- package/src/simulation/rules/trackLimitsSteward.js +85 -0
- package/src/simulation/rulesConfig.js +34 -0
- package/src/simulation/snapshots/raceSnapshots.js +192 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryIndex.js +945 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -628
- package/src/simulation/units.js +6 -4
- package/src/simulation/vehicle/contactResolution.js +116 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +88 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehiclePhysics.js +363 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +316 -0
- package/src/simulation/vehicle/vehicleState.js +186 -0
- package/src/simulation/vehicle/wheelSurface.js +150 -0
- package/src/simulation/vehicleGeometry.js +1 -0
- package/src/simulation/vehiclePhysics.js +1 -104
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -0
- package/src/styles.css +342 -42
- package/src/ui/bannerTemplates.js +69 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +20 -426
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlStatusBanner.js +39 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +6 -5
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/templateUtils.js +23 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { CHAMPIONSHIP_ENTRY_BLUEPRINTS } from '../data/championship.js';
|
|
2
2
|
import { normalizeSimulatorDrivers } from '../data/normalizeDrivers.js';
|
|
3
|
+
import { normalizePhysicsMode } from '../simulation/vehicle/vehiclePhysics.js';
|
|
4
|
+
import { normalizeWarmupOptions } from '../simulation/warmup/runtimeWarmup.js';
|
|
5
|
+
import { normalizeObservationOptions } from './observationOptions.js';
|
|
6
|
+
import { resolveScenarioPlacementConfig } from './scenarios.js';
|
|
7
|
+
import { normalizeRayOptions } from './sensors.js';
|
|
3
8
|
|
|
4
9
|
const DEFAULT_SEED = 1971;
|
|
5
10
|
const DEFAULT_FRAME_SKIP = 1;
|
|
6
11
|
const DEFAULT_MAX_STEPS = 10000;
|
|
12
|
+
const RESULT_STATE_OUTPUTS = new Set(['full', 'minimal', 'none']);
|
|
13
|
+
const RESET_OBSERVATION_SCOPES = new Set(['all', 'reset']);
|
|
7
14
|
|
|
8
15
|
const DEFAULT_SENSOR_OPTIONS = {
|
|
9
16
|
rays: {
|
|
@@ -35,6 +42,7 @@ export function resolveEnvironmentOptions(options = {}) {
|
|
|
35
42
|
...options,
|
|
36
43
|
seed,
|
|
37
44
|
trackSeed,
|
|
45
|
+
trackGeneration: options.trackGeneration ?? {},
|
|
38
46
|
totalLaps: options.totalLaps,
|
|
39
47
|
drivers: normalizedDrivers,
|
|
40
48
|
};
|
|
@@ -58,20 +66,41 @@ export function resolveEnvironmentOptions(options = {}) {
|
|
|
58
66
|
}
|
|
59
67
|
});
|
|
60
68
|
|
|
69
|
+
const externalRenderer = normalizeExternalRenderer(options.externalRenderer);
|
|
70
|
+
|
|
61
71
|
return {
|
|
62
72
|
...resolved,
|
|
73
|
+
physicsMode: normalizePhysicsMode(options.physicsMode),
|
|
74
|
+
warmup: normalizeWarmupOptions(options.warmup, 'environment'),
|
|
63
75
|
drivers: participantDrivers,
|
|
64
76
|
controlledDrivers,
|
|
65
77
|
frameSkip: normalizePositiveInteger(options.frameSkip, DEFAULT_FRAME_SKIP, 'frameSkip'),
|
|
66
78
|
actionPolicy: options.actionPolicy === 'report' ? 'report' : 'strict',
|
|
67
79
|
scenario,
|
|
80
|
+
observation: {
|
|
81
|
+
...normalizeObservationOptions(options.observation ?? {}),
|
|
82
|
+
},
|
|
68
83
|
sensors: mergeSensorOptions(options.sensors),
|
|
69
84
|
sensorsByDriver: options.sensorsByDriver ?? {},
|
|
70
85
|
episode: {
|
|
71
86
|
maxSteps: normalizePositiveInteger(options.episode?.maxSteps, DEFAULT_MAX_STEPS, 'episode.maxSteps'),
|
|
72
87
|
endOnRaceFinish: options.episode?.endOnRaceFinish !== false,
|
|
73
88
|
},
|
|
89
|
+
result: normalizeResultOptions(options.result),
|
|
74
90
|
reward: typeof options.reward === 'function' ? options.reward : null,
|
|
91
|
+
externalRenderer,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function normalizeResultOptions(result = {}) {
|
|
96
|
+
const stateOutput = RESULT_STATE_OUTPUTS.has(result.stateOutput) ? result.stateOutput : 'full';
|
|
97
|
+
const resetDriversObservationScope = RESET_OBSERVATION_SCOPES.has(result.resetDriversObservationScope)
|
|
98
|
+
? result.resetDriversObservationScope
|
|
99
|
+
: 'all';
|
|
100
|
+
return {
|
|
101
|
+
...result,
|
|
102
|
+
stateOutput,
|
|
103
|
+
resetDriversObservationScope,
|
|
75
104
|
};
|
|
76
105
|
}
|
|
77
106
|
|
|
@@ -118,6 +147,7 @@ function resolveScenario(scenario = {}, controlledDrivers, driverIds) {
|
|
|
118
147
|
return {
|
|
119
148
|
participants,
|
|
120
149
|
nonControlled: 'ai',
|
|
150
|
+
...resolveScenarioPlacementConfig(scenario, driverIds),
|
|
121
151
|
};
|
|
122
152
|
}
|
|
123
153
|
|
|
@@ -138,13 +168,22 @@ function normalizePositiveInteger(value, fallback, label) {
|
|
|
138
168
|
|
|
139
169
|
function mergeSensorOptions(sensors = {}) {
|
|
140
170
|
return {
|
|
141
|
-
rays: {
|
|
171
|
+
rays: normalizeRayOptions({
|
|
142
172
|
...DEFAULT_SENSOR_OPTIONS.rays,
|
|
143
173
|
...(sensors.rays ?? {}),
|
|
144
|
-
},
|
|
174
|
+
}),
|
|
145
175
|
nearbyCars: {
|
|
146
176
|
...DEFAULT_SENSOR_OPTIONS.nearbyCars,
|
|
147
177
|
...(sensors.nearbyCars ?? {}),
|
|
148
178
|
},
|
|
149
179
|
};
|
|
150
180
|
}
|
|
181
|
+
|
|
182
|
+
function normalizeExternalRenderer(value) {
|
|
183
|
+
if (value == null) return null;
|
|
184
|
+
if (typeof value === 'function') return { onFrame: value };
|
|
185
|
+
if (typeof value === 'object' && typeof value.onFrame === 'function') {
|
|
186
|
+
return { onFrame: value.onFrame };
|
|
187
|
+
}
|
|
188
|
+
throw new Error('PaddockJS environment externalRenderer must be a function or an object with onFrame(frame).');
|
|
189
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function createRolloutRecorder() {
|
|
2
|
+
const transitions = [];
|
|
3
|
+
return {
|
|
4
|
+
recordStep(previousResult, action, nextResult) {
|
|
5
|
+
const transition = createRolloutTransition(previousResult, action, nextResult);
|
|
6
|
+
transitions.push(transition);
|
|
7
|
+
return transition;
|
|
8
|
+
},
|
|
9
|
+
clear() {
|
|
10
|
+
transitions.length = 0;
|
|
11
|
+
},
|
|
12
|
+
toJSON() {
|
|
13
|
+
return transitions.map((entry) => ({ ...entry }));
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createRolloutTransition(previousResult, action, nextResult) {
|
|
19
|
+
return {
|
|
20
|
+
observation: previousResult.observation,
|
|
21
|
+
action,
|
|
22
|
+
reward: nextResult.reward,
|
|
23
|
+
nextObservation: nextResult.observation,
|
|
24
|
+
terminated: nextResult.terminated,
|
|
25
|
+
truncated: nextResult.truncated,
|
|
26
|
+
info: nextResult.info,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import { createRaceSimulation, FIXED_STEP } from '../simulation/raceSimulation.js';
|
|
2
|
-
import {
|
|
2
|
+
import { disableWarmupOptions, runWarmupWithGuard } from '../simulation/warmup/runtimeWarmup.js';
|
|
3
|
+
import { handleActionError, resolveActionMap } from './actions.js';
|
|
3
4
|
import { collectStepEvents } from './events.js';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
advanceDriverEpisodes,
|
|
7
|
+
buildDriverEpisodeInfo,
|
|
8
|
+
createEpisodeState,
|
|
9
|
+
evaluateEpisode,
|
|
10
|
+
initializeDriverEpisodes,
|
|
11
|
+
markDestroyedDriverEpisodes,
|
|
12
|
+
resetDriverEpisodes,
|
|
13
|
+
} from './episode.js';
|
|
14
|
+
import { buildDriverMetrics } from './metrics.js';
|
|
5
15
|
import { buildEnvironmentObservation } from './observations.js';
|
|
6
16
|
import { resolveEnvironmentOptions } from './options.js';
|
|
17
|
+
import { applyEnvironmentPlacements, applyEnvironmentScenario, normalizeEnvironmentPlacements } from './scenarios.js';
|
|
7
18
|
import { buildActionSpec, buildObservationSpec } from './specs.js';
|
|
8
19
|
|
|
9
20
|
export function createPaddockEnvironment(options = {}) {
|
|
10
21
|
let resolvedOptions = resolveEnvironmentOptions(options);
|
|
11
|
-
let sim =
|
|
22
|
+
let sim = createSimulationWithEnvironmentScenario(resolvedOptions);
|
|
12
23
|
return createEnvironmentRuntime({
|
|
13
24
|
getSimulation: () => sim,
|
|
14
25
|
setSimulation(nextSim) {
|
|
15
26
|
sim = nextSim;
|
|
16
27
|
},
|
|
17
28
|
createSimulation(nextOptions) {
|
|
18
|
-
return
|
|
29
|
+
return createSimulationWithEnvironmentScenario(nextOptions);
|
|
19
30
|
},
|
|
20
31
|
getOptions: () => resolvedOptions,
|
|
21
32
|
setOptions(nextOptions) {
|
|
@@ -26,20 +37,52 @@ export function createPaddockEnvironment(options = {}) {
|
|
|
26
37
|
});
|
|
27
38
|
}
|
|
28
39
|
|
|
40
|
+
function createSimulationWithEnvironmentScenario(options) {
|
|
41
|
+
const simulationOptions = {
|
|
42
|
+
...options,
|
|
43
|
+
trackQueryIndex: shouldUseTrackQueryIndex(options),
|
|
44
|
+
};
|
|
45
|
+
runWarmupWithGuard({
|
|
46
|
+
options: simulationOptions,
|
|
47
|
+
surface: 'environment',
|
|
48
|
+
execute: ({ warmup }) => {
|
|
49
|
+
const warmupSimulation = createRaceSimulation(disableWarmupOptions(simulationOptions, warmup.surface));
|
|
50
|
+
applyEnvironmentScenario(warmupSimulation, options);
|
|
51
|
+
for (let index = 0; index < warmup.steps; index += 1) warmupSimulation.step(FIXED_STEP);
|
|
52
|
+
const warmupSnapshot = warmupSimulation.snapshotObservation?.() ?? warmupSimulation.snapshot();
|
|
53
|
+
buildEnvironmentObservation({
|
|
54
|
+
snapshot: warmupSnapshot,
|
|
55
|
+
options,
|
|
56
|
+
events: [],
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const sim = createRaceSimulation(disableWarmupOptions(simulationOptions, 'environment'));
|
|
61
|
+
applyEnvironmentScenario(sim, options);
|
|
62
|
+
return sim;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function shouldUseTrackQueryIndex(options) {
|
|
66
|
+
return options.trackQueryIndex !== false;
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
export function createEnvironmentRuntime(host) {
|
|
30
70
|
const episodeState = createEpisodeState();
|
|
31
71
|
|
|
72
|
+
initializeControlledPitIntent(host);
|
|
73
|
+
initializeDriverEpisodes(episodeState, host.getOptions().controlledDrivers);
|
|
74
|
+
|
|
32
75
|
function reset(nextOptions = {}) {
|
|
33
|
-
const options = resolveEnvironmentOptions(
|
|
34
|
-
...host.getOptions(),
|
|
35
|
-
...nextOptions,
|
|
36
|
-
});
|
|
76
|
+
const options = resolveEnvironmentOptions(mergeEnvironmentResetOptions(host.getOptions(), nextOptions));
|
|
37
77
|
host.setOptions(options);
|
|
38
78
|
host.setSimulation(host.createSimulation(options));
|
|
79
|
+
initializeControlledPitIntent(host);
|
|
39
80
|
episodeState.step = 0;
|
|
40
81
|
episodeState.previousSnapshot = null;
|
|
82
|
+
initializeDriverEpisodes(episodeState, options.controlledDrivers);
|
|
41
83
|
const result = buildResult({ host, episodeState, events: [], actionErrors: [] });
|
|
42
84
|
episodeState.lastResult = result;
|
|
85
|
+
emitExternalRenderFrame(host, result, { source: 'reset' });
|
|
43
86
|
host.afterReset(result);
|
|
44
87
|
return result;
|
|
45
88
|
}
|
|
@@ -47,23 +90,39 @@ export function createEnvironmentRuntime(host) {
|
|
|
47
90
|
function step(actions = {}) {
|
|
48
91
|
const options = host.getOptions();
|
|
49
92
|
const sim = host.getSimulation();
|
|
50
|
-
const { controlsByDriver, errors } = resolveActionMap(actions, options.controlledDrivers, {
|
|
93
|
+
const { controlsByDriver, pitIntentByDriver, errors } = resolveActionMap(actions, options.controlledDrivers, {
|
|
51
94
|
policy: options.actionPolicy,
|
|
52
95
|
});
|
|
53
96
|
|
|
97
|
+
if (options.actionPolicy === 'report') {
|
|
98
|
+
options.controlledDrivers.forEach((driverId) => {
|
|
99
|
+
if (!Object.hasOwn(controlsByDriver, driverId)) sim.clearCarControls?.(driverId);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
54
102
|
Object.entries(controlsByDriver).forEach(([driverId, controls]) => {
|
|
55
103
|
sim.setCarControls(driverId, controls);
|
|
56
104
|
});
|
|
105
|
+
Object.entries(pitIntentByDriver).forEach(([driverId, pitIntent]) => {
|
|
106
|
+
const applied = Boolean(sim.setPitIntent?.(driverId, pitIntent));
|
|
107
|
+
if (!applied && !isNoopPitIntent(pitIntent)) {
|
|
108
|
+
handleActionError(`Pit intent could not be applied for controlled driver: ${driverId}`, options.actionPolicy, errors);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
57
111
|
|
|
58
|
-
episodeState.previousSnapshot = sim.
|
|
112
|
+
episodeState.previousSnapshot = episodeState.lastRewardSnapshot ?? snapshotForResult(sim, options, options.result.stateOutput);
|
|
59
113
|
const stepEvents = [];
|
|
60
114
|
for (let index = 0; index < options.frameSkip; index += 1) {
|
|
61
115
|
sim.step(FIXED_STEP);
|
|
62
|
-
|
|
116
|
+
const events = typeof sim.consumeStepEvents === 'function'
|
|
117
|
+
? sim.consumeStepEvents()
|
|
118
|
+
: sim.events;
|
|
119
|
+
stepEvents.push(...collectStepEvents(events));
|
|
63
120
|
}
|
|
64
121
|
episodeState.step += 1;
|
|
122
|
+
advanceDriverEpisodes(episodeState, options.controlledDrivers);
|
|
65
123
|
const result = buildResult({ host, episodeState, events: stepEvents, actionErrors: errors, actions });
|
|
66
124
|
episodeState.lastResult = result;
|
|
125
|
+
emitExternalRenderFrame(host, result, { source: 'step', actions });
|
|
67
126
|
host.afterStep(result);
|
|
68
127
|
return result;
|
|
69
128
|
}
|
|
@@ -73,8 +132,49 @@ export function createEnvironmentRuntime(host) {
|
|
|
73
132
|
buildResult({ host, episodeState, events: [], actionErrors: [] }).observation;
|
|
74
133
|
}
|
|
75
134
|
|
|
76
|
-
function getState() {
|
|
77
|
-
|
|
135
|
+
function getState({ output = 'full' } = {}) {
|
|
136
|
+
const sim = host.getSimulation();
|
|
137
|
+
if (output === 'none') return null;
|
|
138
|
+
if (output === 'minimal') return { snapshot: sim.snapshotObservation?.() ?? sim.snapshot() };
|
|
139
|
+
return { snapshot: sim.snapshot() };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function resetDrivers(placements = {}, resultOptions = {}) {
|
|
143
|
+
const options = host.getOptions();
|
|
144
|
+
const sim = host.getSimulation();
|
|
145
|
+
const driverIds = new Set(options.controlledDrivers);
|
|
146
|
+
const normalizedPlacements = normalizeEnvironmentPlacements(placements, driverIds);
|
|
147
|
+
const snapshot = sim.snapshotObservation?.() ?? sim.snapshot();
|
|
148
|
+
const carsById = new Map(snapshot.cars.map((car) => [car.id, car]));
|
|
149
|
+
applyEnvironmentPlacements(sim, snapshot.track, carsById, normalizedPlacements);
|
|
150
|
+
Object.keys(normalizedPlacements).forEach((driverId) => {
|
|
151
|
+
sim.clearCarControls?.(driverId);
|
|
152
|
+
sim.setAutomaticPitIntentEnabled?.(driverId, false);
|
|
153
|
+
sim.setPitIntent?.(driverId, 0);
|
|
154
|
+
const car = sim.cars?.find?.((item) => item.id === driverId);
|
|
155
|
+
if (car) sim.applyRunoffResponse?.(car);
|
|
156
|
+
});
|
|
157
|
+
resetDriverEpisodes(episodeState, Object.keys(normalizedPlacements));
|
|
158
|
+
episodeState.previousSnapshot = null;
|
|
159
|
+
const resetDriverIds = Object.keys(normalizedPlacements);
|
|
160
|
+
const observationScope = resultOptions.observationScope ??
|
|
161
|
+
resultOptions.resetDriversObservationScope ??
|
|
162
|
+
options.result.resetDriversObservationScope;
|
|
163
|
+
const result = buildResult({
|
|
164
|
+
host,
|
|
165
|
+
episodeState,
|
|
166
|
+
events: [],
|
|
167
|
+
actionErrors: [],
|
|
168
|
+
controlledDrivers: observationScope === 'reset' ? resetDriverIds : options.controlledDrivers,
|
|
169
|
+
stateOutput: resultOptions.stateOutput,
|
|
170
|
+
});
|
|
171
|
+
episodeState.lastResult = result;
|
|
172
|
+
emitExternalRenderFrame(host, result, {
|
|
173
|
+
source: 'resetDrivers',
|
|
174
|
+
resetDriverIds,
|
|
175
|
+
observationScope,
|
|
176
|
+
});
|
|
177
|
+
return result;
|
|
78
178
|
}
|
|
79
179
|
|
|
80
180
|
function getActionSpec() {
|
|
@@ -88,53 +188,189 @@ export function createEnvironmentRuntime(host) {
|
|
|
88
188
|
function destroy() {
|
|
89
189
|
episodeState.lastResult = null;
|
|
90
190
|
episodeState.previousSnapshot = null;
|
|
191
|
+
episodeState.lastObservationSnapshot = null;
|
|
192
|
+
episodeState.lastRewardSnapshot = null;
|
|
193
|
+
episodeState.drivers?.clear?.();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { reset, step, resetDrivers, getObservation, getState, getActionSpec, getObservationSpec, destroy };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function mergeEnvironmentResetOptions(currentOptions, nextOptions = {}) {
|
|
200
|
+
return mergePlainObjects(currentOptions, nextOptions);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const REPLACE_OBJECT_PATHS = new Set([
|
|
204
|
+
'scenario.placements',
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
function mergePlainObjects(base, overrides, path = []) {
|
|
208
|
+
if (!isPlainObject(base) || !isPlainObject(overrides)) return overrides;
|
|
209
|
+
const merged = { ...base };
|
|
210
|
+
Object.entries(overrides).forEach(([key, value]) => {
|
|
211
|
+
const nextPath = [...path, key];
|
|
212
|
+
merged[key] = isPlainObject(value) &&
|
|
213
|
+
isPlainObject(base[key]) &&
|
|
214
|
+
!REPLACE_OBJECT_PATHS.has(nextPath.join('.'))
|
|
215
|
+
? mergePlainObjects(base[key], value, nextPath)
|
|
216
|
+
: value;
|
|
217
|
+
});
|
|
218
|
+
return merged;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isPlainObject(value) {
|
|
222
|
+
return value != null &&
|
|
223
|
+
typeof value === 'object' &&
|
|
224
|
+
!Array.isArray(value) &&
|
|
225
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isNoopPitIntent(pitIntent) {
|
|
229
|
+
if (pitIntent && typeof pitIntent === 'object') {
|
|
230
|
+
return Number(pitIntent.intent ?? pitIntent.pitIntent) === 0;
|
|
91
231
|
}
|
|
232
|
+
return Number(pitIntent) === 0;
|
|
233
|
+
}
|
|
92
234
|
|
|
93
|
-
|
|
235
|
+
function initializeControlledPitIntent(host) {
|
|
236
|
+
const sim = host.getSimulation?.();
|
|
237
|
+
const options = host.getOptions?.();
|
|
238
|
+
options?.controlledDrivers?.forEach?.((driverId) => {
|
|
239
|
+
sim?.setAutomaticPitIntentEnabled?.(driverId, false);
|
|
240
|
+
sim?.setPitIntent?.(driverId, 0);
|
|
241
|
+
const car = sim?.cars?.find?.((item) => item.id === driverId);
|
|
242
|
+
if (car) car.environmentControlled = true;
|
|
243
|
+
});
|
|
94
244
|
}
|
|
95
245
|
|
|
96
|
-
function buildResult({
|
|
246
|
+
function buildResult({
|
|
247
|
+
host,
|
|
248
|
+
episodeState,
|
|
249
|
+
events,
|
|
250
|
+
actionErrors,
|
|
251
|
+
actions = {},
|
|
252
|
+
controlledDrivers = null,
|
|
253
|
+
stateOutput = null,
|
|
254
|
+
}) {
|
|
97
255
|
const options = host.getOptions();
|
|
98
|
-
const
|
|
256
|
+
const sim = host.getSimulation();
|
|
257
|
+
const resultDrivers = controlledDrivers ?? options.controlledDrivers;
|
|
258
|
+
const resolvedStateOutput = stateOutput ?? options.result.stateOutput;
|
|
259
|
+
const observationSnapshot = snapshotForResult(sim, options, resolvedStateOutput);
|
|
260
|
+
markDestroyedDriverEpisodes(episodeState, options.controlledDrivers, observationSnapshot);
|
|
99
261
|
const observation = buildEnvironmentObservation({
|
|
100
|
-
snapshot,
|
|
262
|
+
snapshot: observationSnapshot,
|
|
101
263
|
previousSnapshot: episodeState.previousSnapshot,
|
|
102
264
|
options,
|
|
103
265
|
events,
|
|
266
|
+
controlledDrivers: resultDrivers,
|
|
267
|
+
});
|
|
268
|
+
const episode = evaluateEpisode(observationSnapshot, options, episodeState, resultDrivers);
|
|
269
|
+
const metrics = buildDriverMetrics({
|
|
270
|
+
snapshot: observationSnapshot,
|
|
271
|
+
previousSnapshot: episodeState.previousSnapshot,
|
|
272
|
+
options: { ...options, controlledDrivers: resultDrivers },
|
|
273
|
+
events,
|
|
274
|
+
});
|
|
275
|
+
const rewardEpisodeInfo = buildDriverEpisodeInfo(episodeState, {
|
|
276
|
+
...options,
|
|
277
|
+
controlledDrivers: resultDrivers,
|
|
278
|
+
}, episode);
|
|
279
|
+
const driverEpisodeInfo = buildDriverEpisodeInfo(episodeState, options, episode);
|
|
280
|
+
const { state, rewardSnapshot } = buildResultState(sim, observationSnapshot, resolvedStateOutput);
|
|
281
|
+
const reward = computeReward({
|
|
282
|
+
options: { ...options, controlledDrivers: resultDrivers },
|
|
283
|
+
observation,
|
|
284
|
+
events,
|
|
285
|
+
snapshot: rewardSnapshot,
|
|
286
|
+
actions,
|
|
287
|
+
previousSnapshot: episodeState.previousSnapshot,
|
|
288
|
+
metrics,
|
|
289
|
+
driverEpisodeInfo: rewardEpisodeInfo,
|
|
104
290
|
});
|
|
105
|
-
|
|
106
|
-
|
|
291
|
+
episodeState.lastObservationSnapshot = observationSnapshot;
|
|
292
|
+
episodeState.lastRewardSnapshot = rewardSnapshot;
|
|
107
293
|
return {
|
|
108
294
|
observation,
|
|
109
295
|
reward,
|
|
296
|
+
metrics,
|
|
110
297
|
terminated: episode.terminated,
|
|
111
298
|
truncated: episode.truncated,
|
|
112
299
|
done: episode.terminated || episode.truncated,
|
|
113
300
|
events,
|
|
114
|
-
state
|
|
301
|
+
state,
|
|
115
302
|
info: {
|
|
116
303
|
step: episodeState.step,
|
|
117
|
-
elapsedSeconds:
|
|
304
|
+
elapsedSeconds: observationSnapshot.time,
|
|
118
305
|
seed: options.seed,
|
|
119
306
|
trackSeed: options.trackSeed,
|
|
120
307
|
controlledDrivers: [...options.controlledDrivers],
|
|
121
308
|
actionErrors,
|
|
122
309
|
endReason: episode.endReason,
|
|
310
|
+
drivers: driverEpisodeInfo,
|
|
123
311
|
},
|
|
124
312
|
};
|
|
125
313
|
}
|
|
126
314
|
|
|
127
|
-
function
|
|
315
|
+
function buildResultState(sim, observationSnapshot, stateOutput) {
|
|
316
|
+
if (stateOutput === 'none') return { state: null, rewardSnapshot: observationSnapshot };
|
|
317
|
+
if (stateOutput === 'minimal') return { state: { snapshot: observationSnapshot }, rewardSnapshot: observationSnapshot };
|
|
318
|
+
const snapshot = sim.snapshot();
|
|
319
|
+
return { state: { snapshot }, rewardSnapshot: snapshot };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function snapshotForResult(sim, options, stateOutput) {
|
|
323
|
+
if (canUseTrainingSnapshot(options, stateOutput)) {
|
|
324
|
+
return sim.snapshotTraining?.() ?? sim.snapshotObservation?.() ?? sim.snapshot();
|
|
325
|
+
}
|
|
326
|
+
return sim.snapshotObservation?.() ?? sim.snapshot();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function canUseTrainingSnapshot(options, stateOutput) {
|
|
330
|
+
return stateOutput === 'none' &&
|
|
331
|
+
options.observation?.output === 'vector' &&
|
|
332
|
+
options.observation?.includeSchema === false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function computeReward({ options, observation, events, snapshot, actions, previousSnapshot, metrics, driverEpisodeInfo }) {
|
|
128
336
|
if (!options.reward) return null;
|
|
129
337
|
return Object.fromEntries(options.controlledDrivers.map((driverId) => [
|
|
130
338
|
driverId,
|
|
131
|
-
|
|
339
|
+
normalizeRewardValue(options.reward({
|
|
132
340
|
driverId,
|
|
133
341
|
previous: previousSnapshot,
|
|
134
342
|
current: observation[driverId],
|
|
135
343
|
action: actions?.[driverId],
|
|
136
344
|
events: observation[driverId]?.events ?? events,
|
|
137
345
|
state: { snapshot },
|
|
138
|
-
|
|
346
|
+
metrics: metrics?.[driverId] ?? null,
|
|
347
|
+
episode: driverEpisodeInfo?.[driverId] ?? null,
|
|
348
|
+
})),
|
|
139
349
|
]));
|
|
140
350
|
}
|
|
351
|
+
|
|
352
|
+
function emitExternalRenderFrame(host, result, meta = {}) {
|
|
353
|
+
const options = host.getOptions?.();
|
|
354
|
+
const onFrame = options?.externalRenderer?.onFrame;
|
|
355
|
+
if (typeof onFrame !== 'function') return;
|
|
356
|
+
try {
|
|
357
|
+
onFrame({
|
|
358
|
+
snapshot: result?.state?.snapshot ??
|
|
359
|
+
host.getSimulation?.()?.snapshotObservation?.() ??
|
|
360
|
+
host.getSimulation?.()?.snapshot?.(),
|
|
361
|
+
observation: result?.observation ?? {},
|
|
362
|
+
meta: {
|
|
363
|
+
...meta,
|
|
364
|
+
step: result?.info?.step ?? null,
|
|
365
|
+
elapsedSeconds: result?.info?.elapsedSeconds ?? null,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
} catch {
|
|
369
|
+
// External rendering hooks are observer-only and must not break stepping.
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function normalizeRewardValue(value) {
|
|
374
|
+
const reward = Number(value ?? 0);
|
|
375
|
+
return Number.isFinite(reward) ? reward : 0;
|
|
376
|
+
}
|