@inventure71/paddockjs 1.0.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 +52 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +194 -12
- package/docs/architecture.md +119 -44
- package/docs/concepts.md +4 -2
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +299 -17
- package/docs/index.md +1 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +37 -10
- package/docs/system_specs.md +23 -10
- package/docs/training.md +272 -30
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +4 -0
- package/src/app/BrowserExpertAdapter.js +367 -9
- package/src/app/F1SimulatorApp.js +305 -1603
- 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 +4 -0
- 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 +20 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +246 -0
- package/src/environment/episode.js +86 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +361 -9
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observations.js +325 -40
- package/src/environment/options.js +37 -5
- package/src/environment/runtime.js +222 -21
- package/src/environment/scenarios.js +37 -8
- 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 -367
- package/src/environment/specs.js +100 -5
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +169 -6
- package/src/index.js +5 -0
- package/src/rendering/proceduralTrackAsset.js +20 -823
- 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 +1 -1
- 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 -619
- 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 +230 -2817
- package/src/simulation/replay/replayGhosts.js +128 -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/penaltyStats.js +26 -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/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- 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 -1706
- 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 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -1,27 +1,74 @@
|
|
|
1
|
-
import { metersToSimUnits,
|
|
1
|
+
import { metersToSimUnits, simUnitsToMeters } from '../simulation/units.js';
|
|
2
2
|
import { normalizeAngle } from '../simulation/simMath.js';
|
|
3
|
-
import { pointAt } from '../simulation/trackModel.js';
|
|
3
|
+
import { pointAt } from '../simulation/track/trackModel.js';
|
|
4
4
|
import { normalizeLookaheadMeters } from './observationOptions.js';
|
|
5
|
-
import {
|
|
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, buildRaySensors, createRayBatchContext, normalizeRayOptions } from './sensors.js';
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
const EMPTY_EVENTS = Object.freeze([]);
|
|
12
|
+
const PHYSICAL_RAY_SURFACE_CHANNELS = Object.freeze(['kerb', 'illegalSurface']);
|
|
13
|
+
|
|
14
|
+
export function buildEnvironmentObservation({ snapshot, options, events = [], controlledDrivers = options.controlledDrivers }) {
|
|
8
15
|
const carsById = new Map(snapshot.cars.map((entry) => [entry.id, entry]));
|
|
9
|
-
const eventsByDriver = groupEventsByDriver(events,
|
|
10
|
-
|
|
16
|
+
const eventsByDriver = groupEventsByDriver(events, controlledDrivers);
|
|
17
|
+
const defaultSensors = defaultSensorOptions(options);
|
|
18
|
+
const hasSensorOverrides = Object.keys(options.sensorsByDriver ?? {}).length > 0;
|
|
19
|
+
let rayBatchContext = null;
|
|
20
|
+
const getRayBatchContext = () => {
|
|
21
|
+
rayBatchContext ??= createRayBatchContext(snapshot);
|
|
22
|
+
return rayBatchContext;
|
|
23
|
+
};
|
|
24
|
+
const observations = {};
|
|
25
|
+
controlledDrivers.forEach((driverId) => {
|
|
11
26
|
const car = carsById.get(driverId);
|
|
12
|
-
if (!car)
|
|
27
|
+
if (!car) {
|
|
28
|
+
observations[driverId] = formatObservation(emptyObservation(driverId), options.observation);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const driverEvents = eventsByDriver?.get(driverId) ?? EMPTY_EVENTS;
|
|
33
|
+
const sensors = effectiveSensorOptions(options, car.id, defaultSensors, hasSensorOverrides);
|
|
34
|
+
const output = options.observation?.output ?? 'full';
|
|
35
|
+
const includeSchema = options.observation?.includeSchema !== false;
|
|
36
|
+
const wantsObject = output === 'full' || output === 'object';
|
|
37
|
+
const wantsVector = output === 'full' || output === 'vector';
|
|
38
|
+
const object = wantsObject
|
|
39
|
+
? buildDriverObservationObject(car, snapshot, options, driverEvents, sensors, getRayBatchContext)
|
|
40
|
+
: null;
|
|
41
|
+
const { vector, schema } = wantsVector
|
|
42
|
+
? object
|
|
43
|
+
? buildDriverVector(object, sensors, {
|
|
44
|
+
includeSchema,
|
|
45
|
+
vectorType: options.observation?.vectorType,
|
|
46
|
+
})
|
|
47
|
+
: includeSchema
|
|
48
|
+
? buildDriverVector(buildDriverObservationObject(car, snapshot, options, driverEvents, sensors, getRayBatchContext), sensors, {
|
|
49
|
+
includeSchema,
|
|
50
|
+
vectorType: options.observation?.vectorType,
|
|
51
|
+
})
|
|
52
|
+
: buildDriverVectorDirect(car, snapshot, options, driverEvents, sensors, getRayBatchContext)
|
|
53
|
+
: { vector: undefined, schema: [] };
|
|
54
|
+
observations[driverId] = formatObservation({ object, vector, schema, events: driverEvents }, options.observation);
|
|
55
|
+
});
|
|
56
|
+
return observations;
|
|
57
|
+
}
|
|
13
58
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
59
|
+
function formatObservation(observation, options = {}) {
|
|
60
|
+
const output = options.output ?? 'full';
|
|
61
|
+
const includeSchema = options.includeSchema !== false;
|
|
62
|
+
const formatted = { events: observation.events ?? [] };
|
|
63
|
+
if ((output === 'full' || output === 'object') && observation.object) formatted.object = observation.object;
|
|
64
|
+
if ((output === 'full' || output === 'vector') && observation.vector) formatted.vector = observation.vector;
|
|
65
|
+
if (includeSchema) formatted.schema = observation.schema;
|
|
66
|
+
return formatted;
|
|
20
67
|
}
|
|
21
68
|
|
|
22
69
|
function groupEventsByDriver(events, controlledDrivers) {
|
|
70
|
+
if (!events.length) return null;
|
|
23
71
|
const byDriver = new Map(controlledDrivers.map((driverId) => [driverId, []]));
|
|
24
|
-
if (!events.length) return byDriver;
|
|
25
72
|
const controlledSet = new Set(controlledDrivers);
|
|
26
73
|
events.forEach((event) => {
|
|
27
74
|
const driverIds = new Set([
|
|
@@ -37,22 +84,28 @@ function groupEventsByDriver(events, controlledDrivers) {
|
|
|
37
84
|
return byDriver;
|
|
38
85
|
}
|
|
39
86
|
|
|
40
|
-
function buildDriverObservationObject(car, snapshot, options, events, sensors) {
|
|
87
|
+
function buildDriverObservationObject(car, snapshot, options, events, sensors, getRayBatchContext = null) {
|
|
88
|
+
const sensorCar = withEnvironmentControlFlag(car, options);
|
|
89
|
+
const onTrack = isCarLegallyOnTrack(car);
|
|
90
|
+
const body = buildBodySenses(car);
|
|
91
|
+
const trackHeadingError = car.trackHeadingError ?? estimateTrackHeadingError(car, snapshot);
|
|
92
|
+
const trackRelation = {
|
|
93
|
+
...buildBoundarySenses(car, snapshot, onTrack),
|
|
94
|
+
headingErrorRadians: trackHeadingError,
|
|
95
|
+
};
|
|
96
|
+
const nearbyCars = sensors.nearbyCars.enabled
|
|
97
|
+
? enrichOpponentRadar(car, buildNearbyCars(car, snapshot, sensors.nearbyCars), snapshot)
|
|
98
|
+
: [];
|
|
41
99
|
return {
|
|
100
|
+
profile: options.observation?.profile ?? 'default',
|
|
42
101
|
self: {
|
|
43
|
-
|
|
44
|
-
speedKph: car.speedKph,
|
|
45
|
-
speedMetersPerSecond: simSpeedToMetersPerSecond(car.speed ?? 0),
|
|
46
|
-
headingRadians: car.heading,
|
|
47
|
-
steeringAngleRadians: car.steeringAngle ?? 0,
|
|
48
|
-
throttle: car.throttle ?? 0,
|
|
49
|
-
brake: car.brake ?? 0,
|
|
102
|
+
...body,
|
|
50
103
|
lap: car.lap,
|
|
51
104
|
completedLaps: car.lapTelemetry?.completedLaps ?? 0,
|
|
52
105
|
lapProgressMeters: simUnitsToMeters(car.progress ?? 0),
|
|
53
|
-
trackOffsetMeters:
|
|
54
|
-
trackHeadingErrorRadians:
|
|
55
|
-
onTrack
|
|
106
|
+
trackOffsetMeters: trackRelation.lateralOffsetMeters,
|
|
107
|
+
trackHeadingErrorRadians: trackHeadingError,
|
|
108
|
+
onTrack,
|
|
56
109
|
surface: car.surface ?? 'track',
|
|
57
110
|
inPitLane: Boolean(car.inPitLane),
|
|
58
111
|
pitLanePart: car.pitLanePart ?? null,
|
|
@@ -66,6 +119,8 @@ function buildDriverObservationObject(car, snapshot, options, events, sensors) {
|
|
|
66
119
|
pitStopPenaltyServiceRemainingSeconds: car.pitStop?.penaltyServiceRemainingSeconds ?? null,
|
|
67
120
|
pitStopsCompleted: car.pitStop?.stopsCompleted ?? 0,
|
|
68
121
|
},
|
|
122
|
+
trackRelation,
|
|
123
|
+
contactPatches: buildContactPatchSenses(car),
|
|
69
124
|
race: {
|
|
70
125
|
position: car.rank,
|
|
71
126
|
totalCars: snapshot.cars.length,
|
|
@@ -80,8 +135,8 @@ function buildDriverObservationObject(car, snapshot, options, events, sensors) {
|
|
|
80
135
|
curvature: car.trackState?.curvature ?? pointAt(snapshot.track, car.progress ?? 0).curvature ?? 0,
|
|
81
136
|
lookahead: buildTrackLookahead(car, snapshot, options),
|
|
82
137
|
},
|
|
83
|
-
rays: sensors.rays.enabled ? buildRaySensors(
|
|
84
|
-
nearbyCars
|
|
138
|
+
rays: sensors.rays.enabled ? buildRaySensors(sensorCar, snapshot, sensors.rays, rayBatchContextForSensors(sensors, getRayBatchContext)) : [],
|
|
139
|
+
nearbyCars,
|
|
85
140
|
events,
|
|
86
141
|
};
|
|
87
142
|
}
|
|
@@ -92,7 +147,10 @@ function estimateTrackHeadingError(car, snapshot) {
|
|
|
92
147
|
}
|
|
93
148
|
|
|
94
149
|
function buildTrackLookahead(car, snapshot, options) {
|
|
95
|
-
const distances =
|
|
150
|
+
const distances = Array.isArray(options.observation?.lookaheadMeters)
|
|
151
|
+
? options.observation.lookaheadMeters
|
|
152
|
+
: normalizeLookaheadMeters(options.observation?.lookaheadMeters);
|
|
153
|
+
if (!distances.length) return [];
|
|
96
154
|
const base = pointAt(snapshot.track, car.progress ?? 0);
|
|
97
155
|
return distances.map((distanceMeters) => {
|
|
98
156
|
const sample = pointAt(snapshot.track, (car.progress ?? 0) + metersToSimUnits(distanceMeters));
|
|
@@ -112,13 +170,19 @@ function isCarLegallyOnTrack(car) {
|
|
|
112
170
|
return ['track', 'kerb', 'pit-entry', 'pit-lane', 'pit-exit', 'pit-box'].includes(car.surface ?? 'track');
|
|
113
171
|
}
|
|
114
172
|
|
|
115
|
-
function buildDriverVector(object, sensors) {
|
|
116
|
-
const
|
|
173
|
+
function buildDriverVector(object, sensors, { includeSchema = true, vectorType = 'array' } = {}) {
|
|
174
|
+
const includePhysicalDriverSenses = object.profile === 'physical-driver';
|
|
175
|
+
const schema = includeSchema ? [
|
|
117
176
|
{ name: 'self.speedKph', unit: 'kph', scale: 'fixed:400' },
|
|
118
177
|
{ name: 'self.speedMetersPerSecond', unit: 'm/s', scale: 'fixed:120' },
|
|
119
178
|
{ name: 'self.steeringAngleRadians', unit: 'rad', scale: 'fixed:pi' },
|
|
120
179
|
{ name: 'self.throttle', scale: '0..1' },
|
|
121
180
|
{ name: 'self.brake', scale: '0..1' },
|
|
181
|
+
{ name: 'self.lateralG', scale: 'fixed:8g' },
|
|
182
|
+
{ name: 'self.longitudinalG', scale: 'fixed:6g' },
|
|
183
|
+
{ name: 'self.gripUsage', scale: '0..2' },
|
|
184
|
+
{ name: 'self.slipAngleRadians', unit: 'rad', scale: 'fixed:pi' },
|
|
185
|
+
{ name: 'self.tractionLimited', scale: 'boolean' },
|
|
122
186
|
{ name: 'self.lapProgressRatio', scale: '0..1' },
|
|
123
187
|
{ name: 'self.trackOffsetMeters', unit: 'm', scale: 'fixed:meters' },
|
|
124
188
|
{ name: 'self.trackHeadingErrorRadians', unit: 'rad', scale: 'fixed:pi' },
|
|
@@ -133,13 +197,18 @@ function buildDriverVector(object, sensors) {
|
|
|
133
197
|
{ name: 'race.redFlag', scale: 'boolean' },
|
|
134
198
|
{ name: 'race.pitLaneOpen', scale: 'boolean' },
|
|
135
199
|
{ name: 'track.curvature', scale: 'track-curvature' },
|
|
136
|
-
];
|
|
200
|
+
] : null;
|
|
137
201
|
const vector = [
|
|
138
202
|
object.self.speedKph / 400,
|
|
139
203
|
object.self.speedMetersPerSecond / 120,
|
|
140
204
|
object.self.steeringAngleRadians / Math.PI,
|
|
141
205
|
object.self.throttle,
|
|
142
206
|
object.self.brake,
|
|
207
|
+
object.self.lateralG / 8,
|
|
208
|
+
object.self.longitudinalG / 6,
|
|
209
|
+
object.self.gripUsage / 2,
|
|
210
|
+
object.self.slipAngleRadians / Math.PI,
|
|
211
|
+
object.self.tractionLimited ? 1 : 0,
|
|
143
212
|
normalizeLapProgress(object),
|
|
144
213
|
object.self.trackOffsetMeters,
|
|
145
214
|
object.self.trackHeadingErrorRadians / Math.PI,
|
|
@@ -155,15 +224,43 @@ function buildDriverVector(object, sensors) {
|
|
|
155
224
|
object.race.pitLaneOpen ? 1 : 0,
|
|
156
225
|
object.track.curvature ?? 0,
|
|
157
226
|
];
|
|
227
|
+
if (includePhysicalDriverSenses) {
|
|
228
|
+
pushSchema(schema,
|
|
229
|
+
{ name: 'self.yawRateRadiansPerSecond', unit: 'rad/s', scale: 'fixed:pi' },
|
|
230
|
+
{ name: 'trackRelation.leftBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
|
|
231
|
+
{ name: 'trackRelation.rightBoundaryMeters', unit: 'm', scale: 'fixed:meters' },
|
|
232
|
+
{ name: 'trackRelation.legalWidthMeters', unit: 'm', scale: 'fixed:meters' },
|
|
233
|
+
);
|
|
234
|
+
vector.push(
|
|
235
|
+
object.self.yawRateRadiansPerSecond / Math.PI,
|
|
236
|
+
object.trackRelation.leftBoundaryMeters,
|
|
237
|
+
object.trackRelation.rightBoundaryMeters,
|
|
238
|
+
object.trackRelation.legalWidthMeters,
|
|
239
|
+
);
|
|
240
|
+
object.contactPatches.forEach((patch, index) => {
|
|
241
|
+
pushSchema(schema,
|
|
242
|
+
{ name: `contactPatches[${index}].present`, scale: 'boolean' },
|
|
243
|
+
{ name: `contactPatches[${index}].surfaceCode`, scale: 'surface-code' },
|
|
244
|
+
{ name: `contactPatches[${index}].onLegalSurface`, scale: 'boolean' },
|
|
245
|
+
{ name: `contactPatches[${index}].signedOffsetMeters`, unit: 'm', scale: 'fixed:meters' },
|
|
246
|
+
);
|
|
247
|
+
vector.push(
|
|
248
|
+
patch.present ? 1 : 0,
|
|
249
|
+
patch.surfaceCode / 5,
|
|
250
|
+
patch.onLegalSurface ? 1 : 0,
|
|
251
|
+
patch.signedOffsetMeters,
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
158
255
|
object.track.lookahead.forEach((sample, index) => {
|
|
159
|
-
schema
|
|
256
|
+
pushSchema(schema,
|
|
160
257
|
{ name: `track.lookahead[${index}].curvature`, scale: 'track-curvature' },
|
|
161
258
|
{ name: `track.lookahead[${index}].headingDeltaRadians`, unit: 'rad', scale: 'fixed:pi' },
|
|
162
259
|
);
|
|
163
260
|
vector.push(sample.curvature ?? 0, (sample.headingDeltaRadians ?? 0) / Math.PI);
|
|
164
261
|
});
|
|
165
262
|
object.rays.forEach((ray, index) => {
|
|
166
|
-
schema
|
|
263
|
+
pushSchema(schema,
|
|
167
264
|
{ name: `rays[${index}].track.distanceRatio`, scale: '0..1' },
|
|
168
265
|
{ name: `rays[${index}].track.hit`, scale: 'boolean' },
|
|
169
266
|
{ name: `rays[${index}].track.kindExit`, scale: 'boolean' },
|
|
@@ -181,12 +278,24 @@ function buildDriverVector(object, sensors) {
|
|
|
181
278
|
ray.car.hit ? 1 : 0,
|
|
182
279
|
ray.car.relativeSpeedKph / 200,
|
|
183
280
|
);
|
|
281
|
+
if (includePhysicalDriverSenses) {
|
|
282
|
+
PHYSICAL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
283
|
+
pushSchema(schema,
|
|
284
|
+
{ name: `rays[${index}].${channel}.distanceRatio`, scale: '0..1' },
|
|
285
|
+
{ name: `rays[${index}].${channel}.hit`, scale: 'boolean' },
|
|
286
|
+
);
|
|
287
|
+
vector.push(
|
|
288
|
+
ratio(ray[channel]?.distanceMeters ?? ray.lengthMeters, ray.lengthMeters),
|
|
289
|
+
ray[channel]?.hit ? 1 : 0,
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
184
293
|
});
|
|
185
294
|
const nearbyLimit = sensors.nearbyCars.enabled ? (sensors.nearbyCars.maxCars ?? object.nearbyCars.length) : 0;
|
|
186
295
|
const nearbyRadius = sensors.nearbyCars.radiusMeters ?? 150;
|
|
187
296
|
for (let index = 0; index < nearbyLimit; index += 1) {
|
|
188
297
|
const nearby = object.nearbyCars[index] ?? null;
|
|
189
|
-
schema
|
|
298
|
+
pushSchema(schema,
|
|
190
299
|
{ name: `nearbyCars[${index}].present`, scale: 'boolean' },
|
|
191
300
|
{ name: `nearbyCars[${index}].relativeForwardRatio`, scale: '-1..1' },
|
|
192
301
|
{ name: `nearbyCars[${index}].relativeRightRatio`, scale: '-1..1' },
|
|
@@ -206,8 +315,146 @@ function buildDriverVector(object, sensors) {
|
|
|
206
315
|
nearby?.ahead ? 1 : 0,
|
|
207
316
|
nearby?.sameLap ? 1 : 0,
|
|
208
317
|
);
|
|
318
|
+
if (includePhysicalDriverSenses) {
|
|
319
|
+
pushSchema(schema,
|
|
320
|
+
{ name: `nearbyCars[${index}].behind`, scale: 'boolean' },
|
|
321
|
+
{ name: `nearbyCars[${index}].closingRateMetersPerSecond`, unit: 'm/s', scale: 'fixed:100' },
|
|
322
|
+
{ name: `nearbyCars[${index}].timeToContactSeconds`, unit: 's', scale: 'fixed:10' },
|
|
323
|
+
{ name: `nearbyCars[${index}].leftOverlap`, scale: 'boolean' },
|
|
324
|
+
{ name: `nearbyCars[${index}].rightOverlap`, scale: 'boolean' },
|
|
325
|
+
);
|
|
326
|
+
vector.push(
|
|
327
|
+
nearby?.behind ? 1 : 0,
|
|
328
|
+
(nearby?.closingRateMetersPerSecond ?? 0) / 100,
|
|
329
|
+
ratio(nearby?.timeToContactSeconds ?? 10, 10),
|
|
330
|
+
nearby?.leftOverlap ? 1 : 0,
|
|
331
|
+
nearby?.rightOverlap ? 1 : 0,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
209
334
|
}
|
|
210
|
-
return { vector, schema };
|
|
335
|
+
return { vector: finalizeVector(vector, vectorType), schema: schema ?? [] };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function buildDriverVectorDirect(car, snapshot, options, events, sensors, getRayBatchContext = null) {
|
|
339
|
+
const profile = options.observation?.profile ?? 'default';
|
|
340
|
+
const includePhysicalDriverSenses = profile === 'physical-driver';
|
|
341
|
+
const onTrack = isCarLegallyOnTrack(car);
|
|
342
|
+
const body = buildBodySenses(car);
|
|
343
|
+
const trackHeadingError = car.trackHeadingError ?? estimateTrackHeadingError(car, snapshot);
|
|
344
|
+
const sensorCar = withEnvironmentControlFlag(car, options);
|
|
345
|
+
const trackRelation = {
|
|
346
|
+
...buildBoundarySenses(car, snapshot, onTrack),
|
|
347
|
+
headingErrorRadians: trackHeadingError,
|
|
348
|
+
};
|
|
349
|
+
const contactPatches = includePhysicalDriverSenses ? buildContactPatchSenses(car) : [];
|
|
350
|
+
const rays = sensors.rays.enabled ? buildRaySensors(sensorCar, snapshot, sensors.rays, rayBatchContextForSensors(sensors, getRayBatchContext)) : [];
|
|
351
|
+
const nearbyCars = sensors.nearbyCars.enabled
|
|
352
|
+
? enrichOpponentRadar(car, buildNearbyCars(car, snapshot, sensors.nearbyCars), snapshot)
|
|
353
|
+
: [];
|
|
354
|
+
const trackLengthMeters = simUnitsToMeters(snapshot.track.length ?? 0);
|
|
355
|
+
const trackCurvature = car.trackState?.curvature ?? pointAt(snapshot.track, car.progress ?? 0).curvature ?? 0;
|
|
356
|
+
const vector = [
|
|
357
|
+
body.speedKph / 400,
|
|
358
|
+
body.speedMetersPerSecond / 120,
|
|
359
|
+
body.steeringAngleRadians / Math.PI,
|
|
360
|
+
body.throttle,
|
|
361
|
+
body.brake,
|
|
362
|
+
body.lateralG / 8,
|
|
363
|
+
body.longitudinalG / 6,
|
|
364
|
+
body.gripUsage / 2,
|
|
365
|
+
body.slipAngleRadians / Math.PI,
|
|
366
|
+
body.tractionLimited ? 1 : 0,
|
|
367
|
+
ratio(simUnitsToMeters(car.progress ?? 0), trackLengthMeters || 1),
|
|
368
|
+
trackRelation.lateralOffsetMeters,
|
|
369
|
+
trackHeadingError / Math.PI,
|
|
370
|
+
onTrack ? 1 : 0,
|
|
371
|
+
car.inPitLane ? 1 : 0,
|
|
372
|
+
(car.tireEnergy ?? 0) / 100,
|
|
373
|
+
(car.pitIntent ?? car.pitStop?.intent ?? 0) / 2,
|
|
374
|
+
car.pitStop?.status && car.pitStop.status !== 'pending' && car.pitStop.status !== 'completed' ? 1 : 0,
|
|
375
|
+
snapshot.cars.length <= 1 ? 0 : ((car.rank ?? 1) - 1) / Math.max(1, snapshot.cars.length - 1),
|
|
376
|
+
snapshot.raceControl.mode === 'green' ? 1 : 0,
|
|
377
|
+
snapshot.raceControl.mode === 'safety-car' ? 1 : 0,
|
|
378
|
+
snapshot.raceControl.redFlag ? 1 : 0,
|
|
379
|
+
snapshot.raceControl.pitLaneOpen ? 1 : 0,
|
|
380
|
+
trackCurvature,
|
|
381
|
+
];
|
|
382
|
+
if (includePhysicalDriverSenses) {
|
|
383
|
+
vector.push(
|
|
384
|
+
body.yawRateRadiansPerSecond / Math.PI,
|
|
385
|
+
trackRelation.leftBoundaryMeters,
|
|
386
|
+
trackRelation.rightBoundaryMeters,
|
|
387
|
+
trackRelation.legalWidthMeters,
|
|
388
|
+
);
|
|
389
|
+
contactPatches.forEach((patch) => {
|
|
390
|
+
vector.push(
|
|
391
|
+
patch.present ? 1 : 0,
|
|
392
|
+
patch.surfaceCode / 5,
|
|
393
|
+
patch.onLegalSurface ? 1 : 0,
|
|
394
|
+
patch.signedOffsetMeters,
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
buildTrackLookahead(car, snapshot, options).forEach((sample) => {
|
|
399
|
+
vector.push(sample.curvature ?? 0, (sample.headingDeltaRadians ?? 0) / Math.PI);
|
|
400
|
+
});
|
|
401
|
+
rays.forEach((ray) => {
|
|
402
|
+
vector.push(
|
|
403
|
+
ratio(ray.track.distanceMeters, ray.lengthMeters),
|
|
404
|
+
ray.track.hit ? 1 : 0,
|
|
405
|
+
ray.track.kind === 'exit' ? 1 : 0,
|
|
406
|
+
ray.track.kind === 'entry' ? 1 : 0,
|
|
407
|
+
ratio(ray.car.distanceMeters, ray.lengthMeters),
|
|
408
|
+
ray.car.hit ? 1 : 0,
|
|
409
|
+
ray.car.relativeSpeedKph / 200,
|
|
410
|
+
);
|
|
411
|
+
if (includePhysicalDriverSenses) {
|
|
412
|
+
PHYSICAL_RAY_SURFACE_CHANNELS.forEach((channel) => {
|
|
413
|
+
vector.push(
|
|
414
|
+
ratio(ray[channel]?.distanceMeters ?? ray.lengthMeters, ray.lengthMeters),
|
|
415
|
+
ray[channel]?.hit ? 1 : 0,
|
|
416
|
+
);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
const nearbyLimit = sensors.nearbyCars.enabled ? (sensors.nearbyCars.maxCars ?? nearbyCars.length) : 0;
|
|
421
|
+
const nearbyRadius = sensors.nearbyCars.radiusMeters ?? 150;
|
|
422
|
+
for (let index = 0; index < nearbyLimit; index += 1) {
|
|
423
|
+
const nearby = nearbyCars[index] ?? null;
|
|
424
|
+
vector.push(
|
|
425
|
+
nearby ? 1 : 0,
|
|
426
|
+
clampRatio((nearby?.relativeForwardMeters ?? 0) / nearbyRadius),
|
|
427
|
+
clampRatio((nearby?.relativeRightMeters ?? 0) / nearbyRadius),
|
|
428
|
+
ratio(nearby?.relativeDistanceMeters ?? nearbyRadius, nearbyRadius),
|
|
429
|
+
(nearby?.relativeSpeedKph ?? 0) / 200,
|
|
430
|
+
(nearby?.relativeHeadingRadians ?? 0) / Math.PI,
|
|
431
|
+
nearby?.ahead ? 1 : 0,
|
|
432
|
+
nearby?.sameLap ? 1 : 0,
|
|
433
|
+
);
|
|
434
|
+
if (includePhysicalDriverSenses) {
|
|
435
|
+
vector.push(
|
|
436
|
+
nearby?.behind ? 1 : 0,
|
|
437
|
+
(nearby?.closingRateMetersPerSecond ?? 0) / 100,
|
|
438
|
+
ratio(nearby?.timeToContactSeconds ?? 10, 10),
|
|
439
|
+
nearby?.leftOverlap ? 1 : 0,
|
|
440
|
+
nearby?.rightOverlap ? 1 : 0,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return { vector: finalizeVector(vector, options.observation?.vectorType), schema: [] };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function rayBatchContextForSensors(sensors, getRayBatchContext) {
|
|
448
|
+
if (!getRayBatchContext || !sensors.rays?.channels?.includes('car')) return null;
|
|
449
|
+
return getRayBatchContext();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function finalizeVector(vector, vectorType = 'array') {
|
|
453
|
+
return vectorType === 'float32' ? Float32Array.from(vector) : vector;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function pushSchema(schema, ...entries) {
|
|
457
|
+
if (schema) schema.push(...entries);
|
|
211
458
|
}
|
|
212
459
|
|
|
213
460
|
function normalizeLapProgress(object) {
|
|
@@ -215,8 +462,10 @@ function normalizeLapProgress(object) {
|
|
|
215
462
|
}
|
|
216
463
|
|
|
217
464
|
function normalizeRacePosition(object) {
|
|
218
|
-
const
|
|
219
|
-
|
|
465
|
+
const totalCars = Number(object.race.totalCars);
|
|
466
|
+
const position = Number(object.race.position);
|
|
467
|
+
if (!Number.isFinite(totalCars) || totalCars <= 1 || !Number.isFinite(position)) return 0;
|
|
468
|
+
return ratio(position - 1, totalCars - 1);
|
|
220
469
|
}
|
|
221
470
|
|
|
222
471
|
function ratio(value, max) {
|
|
@@ -228,12 +477,20 @@ function clampRatio(value) {
|
|
|
228
477
|
return Math.max(-1, Math.min(1, Number.isFinite(value) ? value : 0));
|
|
229
478
|
}
|
|
230
479
|
|
|
231
|
-
function
|
|
480
|
+
function defaultSensorOptions(options) {
|
|
481
|
+
return {
|
|
482
|
+
rays: normalizeRayOptions(options.sensors.rays),
|
|
483
|
+
nearbyCars: options.sensors.nearbyCars,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function effectiveSensorOptions(options, driverId, defaultSensors = defaultSensorOptions(options), hasSensorOverrides = true) {
|
|
488
|
+
if (!hasSensorOverrides || !options.sensorsByDriver?.[driverId]) return defaultSensors;
|
|
232
489
|
return {
|
|
233
|
-
rays: {
|
|
490
|
+
rays: normalizeRayOptions({
|
|
234
491
|
...options.sensors.rays,
|
|
235
492
|
...(options.sensorsByDriver?.[driverId]?.rays ?? {}),
|
|
236
|
-
},
|
|
493
|
+
}),
|
|
237
494
|
nearbyCars: {
|
|
238
495
|
...options.sensors.nearbyCars,
|
|
239
496
|
...(options.sensorsByDriver?.[driverId]?.nearbyCars ?? {}),
|
|
@@ -249,9 +506,18 @@ function emptyObservation(driverId) {
|
|
|
249
506
|
speedKph: 0,
|
|
250
507
|
speedMetersPerSecond: 0,
|
|
251
508
|
headingRadians: 0,
|
|
509
|
+
yawRateRadiansPerSecond: 0,
|
|
252
510
|
steeringAngleRadians: 0,
|
|
253
511
|
throttle: 0,
|
|
254
512
|
brake: 0,
|
|
513
|
+
lateralG: 0,
|
|
514
|
+
longitudinalG: 0,
|
|
515
|
+
gripUsage: 0,
|
|
516
|
+
slipAngleRadians: 0,
|
|
517
|
+
tractionLimited: false,
|
|
518
|
+
stabilityState: 'stable',
|
|
519
|
+
destroyed: false,
|
|
520
|
+
destroyReason: null,
|
|
255
521
|
lap: 0,
|
|
256
522
|
completedLaps: 0,
|
|
257
523
|
lapProgressMeters: 0,
|
|
@@ -271,6 +537,16 @@ function emptyObservation(driverId) {
|
|
|
271
537
|
pitStopPenaltyServiceRemainingSeconds: null,
|
|
272
538
|
pitStopsCompleted: 0,
|
|
273
539
|
},
|
|
540
|
+
trackRelation: {
|
|
541
|
+
lateralOffsetMeters: 0,
|
|
542
|
+
headingErrorRadians: 0,
|
|
543
|
+
legalWidthMeters: 0,
|
|
544
|
+
leftBoundaryMeters: 0,
|
|
545
|
+
rightBoundaryMeters: 0,
|
|
546
|
+
onLegalSurface: false,
|
|
547
|
+
surface: 'missing',
|
|
548
|
+
},
|
|
549
|
+
contactPatches: [],
|
|
274
550
|
race: { position: 0, totalCars: 0, raceMode: 'missing', pitLaneOpen: false, redFlag: false, totalLaps: 0 },
|
|
275
551
|
track: { lengthMeters: 0, widthMeters: 0, curvature: 0, lookahead: [] },
|
|
276
552
|
rays: [],
|
|
@@ -282,3 +558,12 @@ function emptyObservation(driverId) {
|
|
|
282
558
|
events: [],
|
|
283
559
|
};
|
|
284
560
|
}
|
|
561
|
+
|
|
562
|
+
function withEnvironmentControlFlag(car, options) {
|
|
563
|
+
if (!car || car.environmentControlled) return car;
|
|
564
|
+
if (!Array.isArray(options?.controlledDrivers) || !options.controlledDrivers.includes(car.id)) return car;
|
|
565
|
+
return {
|
|
566
|
+
...car,
|
|
567
|
+
environmentControlled: true,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { CHAMPIONSHIP_ENTRY_BLUEPRINTS } from '../data/championship.js';
|
|
2
2
|
import { normalizeSimulatorDrivers } from '../data/normalizeDrivers.js';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizePhysicsMode } from '../simulation/vehicle/vehiclePhysics.js';
|
|
4
|
+
import { normalizeWarmupOptions } from '../simulation/warmup/runtimeWarmup.js';
|
|
5
|
+
import { normalizeObservationOptions } from './observationOptions.js';
|
|
4
6
|
import { resolveScenarioPlacementConfig } from './scenarios.js';
|
|
7
|
+
import { normalizeRayOptions } from './sensors.js';
|
|
5
8
|
|
|
6
9
|
const DEFAULT_SEED = 1971;
|
|
7
10
|
const DEFAULT_FRAME_SKIP = 1;
|
|
8
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']);
|
|
9
14
|
|
|
10
15
|
const DEFAULT_SENSOR_OPTIONS = {
|
|
11
16
|
rays: {
|
|
@@ -37,6 +42,7 @@ export function resolveEnvironmentOptions(options = {}) {
|
|
|
37
42
|
...options,
|
|
38
43
|
seed,
|
|
39
44
|
trackSeed,
|
|
45
|
+
trackGeneration: options.trackGeneration ?? {},
|
|
40
46
|
totalLaps: options.totalLaps,
|
|
41
47
|
drivers: normalizedDrivers,
|
|
42
48
|
};
|
|
@@ -60,16 +66,19 @@ export function resolveEnvironmentOptions(options = {}) {
|
|
|
60
66
|
}
|
|
61
67
|
});
|
|
62
68
|
|
|
69
|
+
const externalRenderer = normalizeExternalRenderer(options.externalRenderer);
|
|
70
|
+
|
|
63
71
|
return {
|
|
64
72
|
...resolved,
|
|
73
|
+
physicsMode: normalizePhysicsMode(options.physicsMode),
|
|
74
|
+
warmup: normalizeWarmupOptions(options.warmup, 'environment'),
|
|
65
75
|
drivers: participantDrivers,
|
|
66
76
|
controlledDrivers,
|
|
67
77
|
frameSkip: normalizePositiveInteger(options.frameSkip, DEFAULT_FRAME_SKIP, 'frameSkip'),
|
|
68
78
|
actionPolicy: options.actionPolicy === 'report' ? 'report' : 'strict',
|
|
69
79
|
scenario,
|
|
70
80
|
observation: {
|
|
71
|
-
...(options.observation ?? {}),
|
|
72
|
-
lookaheadMeters: normalizeLookaheadMeters(options.observation?.lookaheadMeters),
|
|
81
|
+
...normalizeObservationOptions(options.observation ?? {}),
|
|
73
82
|
},
|
|
74
83
|
sensors: mergeSensorOptions(options.sensors),
|
|
75
84
|
sensorsByDriver: options.sensorsByDriver ?? {},
|
|
@@ -77,7 +86,21 @@ export function resolveEnvironmentOptions(options = {}) {
|
|
|
77
86
|
maxSteps: normalizePositiveInteger(options.episode?.maxSteps, DEFAULT_MAX_STEPS, 'episode.maxSteps'),
|
|
78
87
|
endOnRaceFinish: options.episode?.endOnRaceFinish !== false,
|
|
79
88
|
},
|
|
89
|
+
result: normalizeResultOptions(options.result),
|
|
80
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,
|
|
81
104
|
};
|
|
82
105
|
}
|
|
83
106
|
|
|
@@ -145,13 +168,22 @@ function normalizePositiveInteger(value, fallback, label) {
|
|
|
145
168
|
|
|
146
169
|
function mergeSensorOptions(sensors = {}) {
|
|
147
170
|
return {
|
|
148
|
-
rays: {
|
|
171
|
+
rays: normalizeRayOptions({
|
|
149
172
|
...DEFAULT_SENSOR_OPTIONS.rays,
|
|
150
173
|
...(sensors.rays ?? {}),
|
|
151
|
-
},
|
|
174
|
+
}),
|
|
152
175
|
nearbyCars: {
|
|
153
176
|
...DEFAULT_SENSOR_OPTIONS.nearbyCars,
|
|
154
177
|
...(sensors.nearbyCars ?? {}),
|
|
155
178
|
},
|
|
156
179
|
};
|
|
157
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
|
+
}
|