@inventure71/paddockjs 4.1.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 +76 -0
- package/INSTALL_AND_UPDATE.md +31 -5
- package/README.md +34 -3
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +111 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +32 -16
- 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/getting-started.md +3 -0
- package/docs/grid_walk.md +80 -0
- package/docs/index.md +5 -2
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/loading.md +141 -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 +40 -12
- package/docs/testing-reliability-guide.md +67 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +23 -15
- 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 +12 -195
- 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 +35 -91
- 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 +43 -202
- 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 +12 -20
- 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 +16 -5
- package/src/simulation/driver/edgeRecovery.js +2 -3
- package/src/simulation/driver/racingControls.js +10 -396
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +54 -20
- package/src/simulation/driver/rejoinControls.js +59 -112
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +136 -27
- package/src/simulation/participants/participantInteractions.js +24 -15
- 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 +20 -11
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +5 -9
- 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/pitLaneSpeedingSteward.js +1 -1
- package/src/simulation/rules/rulesReview.js +20 -16
- package/src/simulation/rules/trackLimitsSteward.js +2 -2
- 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 +14 -6
- 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 +76 -31
- 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 +12 -240
- 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 +23 -9
- 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 +85 -103
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +42 -196
- package/src/simulation/vehicle/vehicleSnapshots.js +18 -6
- package/src/simulation/vehicle/vehicleState.js +5 -3
- 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/raceCanvasTemplate.js +3 -2
- 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
- package/examples/train-basic-policy.mjs +0 -276
- package/examples/trainingData.mjs +0 -133
|
@@ -7,13 +7,13 @@ import { querySegmentNeighborhoodProjection, querySegmentNeighborhoodProjections
|
|
|
7
7
|
import {
|
|
8
8
|
ANALYTIC_TRACK_RAY_MAX_CURVATURE,
|
|
9
9
|
DRIVER_RAY_REFINE_STEPS,
|
|
10
|
-
PIT_CONNECTOR_RAY_FALLBACK_METERS,
|
|
11
10
|
TRACK_RAY_REFINE_STEPS,
|
|
12
11
|
TRACK_RAY_STEP_METERS,
|
|
13
12
|
} from './rayDefaults.js';
|
|
14
13
|
import { findIndexedRayBoundaryDistances } from './indexedRayBands.js';
|
|
15
14
|
import { createRayChannelFlags, isLegalRaySurface, writeRayChannelFlags } from './rayChannels.js';
|
|
16
15
|
import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
|
|
16
|
+
import { isNearPitConnector } from './pitConnectorProximity.js';
|
|
17
17
|
|
|
18
18
|
const BOUNDARY_VALIDATION_EPSILON_METERS = 0.25;
|
|
19
19
|
const LOCAL_SEGMENT_RECOVERY_BATCH_SIZE = 16;
|
|
@@ -67,7 +67,6 @@ export function traceIndexedRayBands({
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const trackHalfWidth = track.width / 2;
|
|
70
|
-
const kerbOuter = trackHalfWidth + (track.kerbWidth ?? 0);
|
|
71
70
|
const boundaries = findIndexedSurfaceBoundaries(
|
|
72
71
|
track,
|
|
73
72
|
origin,
|
|
@@ -321,15 +320,6 @@ function directBoundaryFallbackReason({
|
|
|
321
320
|
) {
|
|
322
321
|
return 'kerb-recovery';
|
|
323
322
|
}
|
|
324
|
-
if (
|
|
325
|
-
channels.hasIllegalSurface &&
|
|
326
|
-
originState.surface === 'barrier' &&
|
|
327
|
-
boundaries.runoffOuterDistance == null &&
|
|
328
|
-
Math.abs(originState.signedOffset) > kerbOuter &&
|
|
329
|
-
!barrierOriginCanStayDirect
|
|
330
|
-
) {
|
|
331
|
-
return 'illegal-surface-recovery';
|
|
332
|
-
}
|
|
333
323
|
return null;
|
|
334
324
|
}
|
|
335
325
|
|
|
@@ -1090,14 +1080,6 @@ function isInsideTrackBorder(state, track, includePitLane = false) {
|
|
|
1090
1080
|
return Math.abs(state?.crossTrackError ?? Infinity) <= track.width / 2;
|
|
1091
1081
|
}
|
|
1092
1082
|
|
|
1093
|
-
function createTrackMiss(lengthMeters) {
|
|
1094
|
-
return writeTrackMiss({}, lengthMeters);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
function createSurfaceMiss(lengthMeters) {
|
|
1098
|
-
return writeSurfaceMiss({}, lengthMeters);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
1083
|
function writeTrackMiss(target, lengthMeters) {
|
|
1102
1084
|
target.hit = false;
|
|
1103
1085
|
target.distanceMeters = lengthMeters;
|
|
@@ -1119,24 +1101,6 @@ function writeSurfaceHit(target, distanceMeters, surface) {
|
|
|
1119
1101
|
return target;
|
|
1120
1102
|
}
|
|
1121
1103
|
|
|
1122
|
-
function isNearPitConnector(track, state) {
|
|
1123
|
-
const pitLane = track.pitLane;
|
|
1124
|
-
if (!pitLane?.enabled || !Number.isFinite(state?.distance)) return false;
|
|
1125
|
-
const window = metersToSimUnits(PIT_CONNECTOR_RAY_FALLBACK_METERS);
|
|
1126
|
-
const entryDistance = pitLane.entry?.trackDistance ?? pitLane.entry?.distanceFromStart;
|
|
1127
|
-
const exitDistance = pitLane.exit?.trackDistance ?? pitLane.exit?.distanceFromStart;
|
|
1128
|
-
return wrappedTrackDistance(state.distance, entryDistance, track.length) <= window ||
|
|
1129
|
-
wrappedTrackDistance(state.distance, exitDistance, track.length) <= window;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
function wrappedTrackDistance(first, second, totalLength) {
|
|
1133
|
-
if (!Number.isFinite(first) || !Number.isFinite(second) || !Number.isFinite(totalLength) || totalLength <= 0) {
|
|
1134
|
-
return Infinity;
|
|
1135
|
-
}
|
|
1136
|
-
const delta = Math.abs(first - second);
|
|
1137
|
-
return Math.min(delta, totalLength - delta);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
1104
|
function minFinite(...values) {
|
|
1141
1105
|
let minimum = Infinity;
|
|
1142
1106
|
for (let index = 0; index < values.length; index += 1) {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createCarRayMiss } from './carRays.js';
|
|
2
|
+
import { createTrackMiss, createTrackRayContext } from './trackRays.js';
|
|
3
|
+
import { canUseBatchTrainingRayApproximation } from './rayGuards.js';
|
|
4
|
+
import { degreesToRadians } from './rayGeometry.js';
|
|
5
|
+
import { createRayChannelContext, estimateCarHitForRay, estimateRayBands } from './rayQuery.js';
|
|
6
|
+
import { appendRayVectorValues, writeRayVectorValues, writeRichRay } from './rayOutputs.js';
|
|
7
|
+
import { getFilteredCarTargets, getNormalizedRayOptions, getRayOrigin, getRayScratch, getScratchRayVector, getSharedRayQuery, prepareScratchArray } from './rayScratch.js';
|
|
8
|
+
|
|
9
|
+
export function buildRaySensors(car, snapshot, rayOptions = {}, batchContext = null) {
|
|
10
|
+
return buildRayBatch(car, snapshot, rayOptions, batchContext, 'rich');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildRaySensorVectorValues(car, snapshot, rayOptions = {}, batchContext = null) {
|
|
14
|
+
return buildRayBatch(car, snapshot, rayOptions, batchContext, 'vector');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function appendRaySensorVectorValues(target, car, snapshot, rayOptions = {}, batchContext = null) {
|
|
18
|
+
return buildRayBatch(car, snapshot, rayOptions, batchContext, 'append', target);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function buildRayBatch(car, snapshot, rayOptions, batchContext, output, target = null) {
|
|
22
|
+
const scratch = getRayScratch(batchContext);
|
|
23
|
+
const normalized = getNormalizedRayOptions(rayOptions, scratch);
|
|
24
|
+
if (!normalized.enabled) return target ?? [];
|
|
25
|
+
const channelContext = createRayChannelContext(normalized);
|
|
26
|
+
const inactive = Boolean(car?.destroyed || car?.outOfRace);
|
|
27
|
+
const origin = inactive ? null : getRayOrigin(car, scratch);
|
|
28
|
+
const trackContext = !inactive && channelContext.hasRoadOrSurface
|
|
29
|
+
? createTrackRayContext(car, snapshot, origin, normalized.precision)
|
|
30
|
+
: null;
|
|
31
|
+
const carTargets = !inactive && channelContext.hasCar
|
|
32
|
+
? getFilteredCarTargets(car, snapshot, batchContext, scratch)
|
|
33
|
+
: [];
|
|
34
|
+
const skipEmptyCarQuery = output === 'rich' && canUseFastBatchTrainingRays(car, snapshot, trackContext) && carTargets.length === 0;
|
|
35
|
+
const values = target ?? (output === 'rich'
|
|
36
|
+
? prepareScratchArray(scratch, 'rays', 'rayPool', normalized.rays.length, () => ({})) ?? normalized.rays.map(() => ({}))
|
|
37
|
+
: prepareScratchArray(scratch, 'rayVectorValues', 'rayVectorValuePool', normalized.rays.length, () => []) ?? normalized.rays.map(() => []));
|
|
38
|
+
|
|
39
|
+
for (let index = 0; index < normalized.rays.length; index += 1) {
|
|
40
|
+
const ray = normalized.rays[index];
|
|
41
|
+
const angleRadians = degreesToRadians(ray.angleDegrees);
|
|
42
|
+
const vector = inactive ? null : getScratchRayVector(scratch, index, car, angleRadians);
|
|
43
|
+
const sharedRayQuery = inactive ? null : getSharedRayQuery(scratch, index);
|
|
44
|
+
const { roadEdge, surfaceHits } = inactive
|
|
45
|
+
? { roadEdge: createTrackMiss(ray.lengthMeters), surfaceHits: {} }
|
|
46
|
+
: estimateRayBands({
|
|
47
|
+
car,
|
|
48
|
+
snapshot,
|
|
49
|
+
ray,
|
|
50
|
+
angleDegrees: ray.angleDegrees,
|
|
51
|
+
origin,
|
|
52
|
+
vector,
|
|
53
|
+
normalized,
|
|
54
|
+
channelContext,
|
|
55
|
+
trackContext,
|
|
56
|
+
sharedRayQuery,
|
|
57
|
+
scratch,
|
|
58
|
+
});
|
|
59
|
+
const carHit = !inactive && channelContext.hasCar && !skipEmptyCarQuery
|
|
60
|
+
? estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch)
|
|
61
|
+
: createCarRayMiss(ray.lengthMeters);
|
|
62
|
+
if (output === 'rich') {
|
|
63
|
+
writeRichRay(values[index], ray, angleRadians, roadEdge, carHit, surfaceHits, channelContext.channels);
|
|
64
|
+
} else if (output === 'vector') {
|
|
65
|
+
writeRayVectorValues(values[index], ray.lengthMeters, roadEdge, carHit, surfaceHits, channelContext.surfaceChannels);
|
|
66
|
+
} else {
|
|
67
|
+
appendRayVectorValues(values, ray.lengthMeters, roadEdge, carHit, surfaceHits, channelContext.surfaceChannels);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function canUseFastBatchTrainingRays(car, snapshot, trackContext) {
|
|
74
|
+
return car?.interaction?.profile === 'batch-training' &&
|
|
75
|
+
!car.inPitLane &&
|
|
76
|
+
!trackContext?.originState?.inPitLane &&
|
|
77
|
+
Array.isArray(snapshot.track?.samples) &&
|
|
78
|
+
snapshot.track.samples.length > 0 &&
|
|
79
|
+
trackContext?.originState &&
|
|
80
|
+
canUseBatchTrainingRayApproximation(snapshot.track, trackContext.originState);
|
|
81
|
+
}
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_RAY_ANGLES_DEGREES } from './rayDefaults.js';
|
|
|
2
2
|
import { RAY_CHANNELS } from './rayChannels.js';
|
|
3
3
|
|
|
4
4
|
const NORMALIZED_RAY_OPTIONS = Symbol('normalizedRayOptions');
|
|
5
|
+
const NORMALIZED_RAY_INPUTS = new WeakMap();
|
|
5
6
|
|
|
6
7
|
export { RAY_CHANNELS } from './rayChannels.js';
|
|
7
8
|
|
|
@@ -63,9 +64,34 @@ export function normalizeRayOptions(rayOptions = {}) {
|
|
|
63
64
|
value: true,
|
|
64
65
|
enumerable: false,
|
|
65
66
|
});
|
|
67
|
+
NORMALIZED_RAY_INPUTS.set(normalized, copyRayOptionsInput(rayOptions));
|
|
66
68
|
return normalized;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
export function mergeRayOptionOverrides(baseRays = {}, overrides = {}) {
|
|
72
|
+
const baseInput = NORMALIZED_RAY_INPUTS.get(baseRays) ?? baseRays;
|
|
73
|
+
const overrideInput = NORMALIZED_RAY_INPUTS.get(overrides) ?? overrides;
|
|
74
|
+
const requested = Object.fromEntries(Object.entries(overrideInput).filter(([, value]) => value !== undefined));
|
|
75
|
+
const merged = { ...baseInput, ...requested };
|
|
76
|
+
if (Object.hasOwn(requested, 'lengthMeters') && !Object.hasOwn(requested, 'defaultLengthMeters')) {
|
|
77
|
+
delete merged.defaultLengthMeters;
|
|
78
|
+
}
|
|
79
|
+
if (Object.hasOwn(requested, 'rays')) return merged;
|
|
80
|
+
if (Object.hasOwn(requested, 'layout') || Object.hasOwn(requested, 'anglesDegrees')) {
|
|
81
|
+
delete merged.rays;
|
|
82
|
+
if (Object.hasOwn(requested, 'anglesDegrees') && !Object.hasOwn(requested, 'layout')) delete merged.layout;
|
|
83
|
+
}
|
|
84
|
+
return merged;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function copyRayOptionsInput(source) {
|
|
88
|
+
const input = { ...source };
|
|
89
|
+
if (Array.isArray(source.rays)) input.rays = source.rays.map((ray) => typeof ray === 'object' && ray ? { ...ray } : ray);
|
|
90
|
+
if (Array.isArray(source.anglesDegrees)) input.anglesDegrees = [...source.anglesDegrees];
|
|
91
|
+
if (Array.isArray(source.channels)) input.channels = [...source.channels];
|
|
92
|
+
return input;
|
|
93
|
+
}
|
|
94
|
+
|
|
69
95
|
function normalizeRays(rayOptions, defaultLengthMeters) {
|
|
70
96
|
const configuredRays = Array.isArray(rayOptions.rays)
|
|
71
97
|
? rayOptions.rays
|
|
@@ -20,10 +20,6 @@ export function pointOnRay(origin, ray, distance) {
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function intersectAxisAlignedBoxRay(origin, ray, halfLength, halfWidth) {
|
|
24
|
-
return intersectAxisAlignedBoxRayScalars(origin.x, origin.y, ray.x, ray.y, halfLength, halfWidth);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
23
|
export function intersectAxisAlignedBoxRayScalars(originX, originY, rayX, rayY, halfLength, halfWidth) {
|
|
28
24
|
let tMin = -Infinity;
|
|
29
25
|
let tMax = Infinity;
|
|
@@ -50,10 +46,6 @@ export function intersectAxisAlignedBoxRayScalars(originX, originY, rayX, rayY,
|
|
|
50
46
|
return Math.max(0, tMin);
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
export function dot(a, b) {
|
|
54
|
-
return a.x * b.x + a.y * b.y;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
49
|
export function degreesToRadians(degrees) {
|
|
58
50
|
return (degrees * Math.PI) / 180;
|
|
59
51
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { metersToSimUnits } from '../../simulation/units.js';
|
|
2
2
|
|
|
3
|
-
export function canUseLocalStripRayApproximation(track, state) {
|
|
4
|
-
if (!track || !state || state.inPitLane) return false;
|
|
5
|
-
const kerbOuter = track.width / 2 + (track.kerbWidth ?? 0);
|
|
6
|
-
return Number.isFinite(state.crossTrackError) && state.crossTrackError <= kerbOuter;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
3
|
export function canUseIndexedRecoveryRayApproximation(track, state) {
|
|
10
4
|
if (!track || !state || state.inPitLane) return false;
|
|
11
5
|
const recoveryBand = track.width / 2 +
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createSurfaceMiss } from './surfaceRays.js';
|
|
2
|
+
|
|
3
|
+
export function writeRichRay(target, ray, angleRadians, roadEdge, carHit, surfaceHits, channels) {
|
|
4
|
+
target.id = ray.id;
|
|
5
|
+
target.angleDegrees = ray.angleDegrees;
|
|
6
|
+
target.angleRadians = angleRadians;
|
|
7
|
+
target.lengthMeters = ray.lengthMeters;
|
|
8
|
+
target.roadEdge = writeTrackRayHit(target.roadEdge ?? {}, roadEdge);
|
|
9
|
+
target.track = target.roadEdge;
|
|
10
|
+
target.kerb = channels.hasKerb
|
|
11
|
+
? writeSurfaceRayHit(target.kerb ?? {}, surfaceHits.kerb ?? createSurfaceMiss(ray.lengthMeters))
|
|
12
|
+
: writeSurfaceRayHit(target.kerb ?? {}, createSurfaceMiss(ray.lengthMeters));
|
|
13
|
+
target.illegalSurface = channels.hasIllegalSurface
|
|
14
|
+
? writeSurfaceRayHit(target.illegalSurface ?? {}, surfaceHits.illegalSurface ?? createSurfaceMiss(ray.lengthMeters))
|
|
15
|
+
: writeSurfaceRayHit(target.illegalSurface ?? {}, createSurfaceMiss(ray.lengthMeters));
|
|
16
|
+
target.car = writeCarRayHit(target.car ?? {}, carHit);
|
|
17
|
+
return target;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeTrackRayHit(target, source) {
|
|
21
|
+
target.hit = Boolean(source?.hit);
|
|
22
|
+
target.distanceMeters = source?.distanceMeters;
|
|
23
|
+
target.kind = source?.kind ?? null;
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function writeSurfaceRayHit(target, source) {
|
|
28
|
+
target.hit = Boolean(source?.hit);
|
|
29
|
+
target.distanceMeters = source?.distanceMeters;
|
|
30
|
+
target.surface = source?.surface ?? null;
|
|
31
|
+
return target;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeCarRayHit(target, source) {
|
|
35
|
+
target.hit = Boolean(source?.hit);
|
|
36
|
+
target.distanceMeters = source?.distanceMeters;
|
|
37
|
+
target.driverId = source?.driverId ?? null;
|
|
38
|
+
target.targetId = source?.targetId ?? null;
|
|
39
|
+
target.targetType = source?.targetType ?? null;
|
|
40
|
+
target.relativeSpeedKph = source?.relativeSpeedKph ?? 0;
|
|
41
|
+
return target;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function writeRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
|
|
45
|
+
values.length = 0;
|
|
46
|
+
appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels);
|
|
47
|
+
return values;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function appendRayVectorValues(values, lengthMeters, roadEdge, carHit, surfaceHits, surfaceChannels) {
|
|
51
|
+
values.push(
|
|
52
|
+
ratio(roadEdge.distanceMeters, lengthMeters),
|
|
53
|
+
roadEdge.hit ? 1 : 0,
|
|
54
|
+
roadEdge.kind === 'exit' ? 1 : 0,
|
|
55
|
+
roadEdge.kind === 'entry' ? 1 : 0,
|
|
56
|
+
ratio(carHit.distanceMeters, lengthMeters),
|
|
57
|
+
carHit.hit ? 1 : 0,
|
|
58
|
+
carHit.relativeSpeedKph / 200,
|
|
59
|
+
carHit.targetType === 'replayGhost' ? 1 : 0,
|
|
60
|
+
);
|
|
61
|
+
for (let index = 0; index < surfaceChannels.length; index += 1) {
|
|
62
|
+
const channel = surfaceChannels[index];
|
|
63
|
+
const hit = surfaceHits[channel] ?? createSurfaceMiss(lengthMeters);
|
|
64
|
+
values.push(
|
|
65
|
+
ratio(hit.distanceMeters, lengthMeters),
|
|
66
|
+
hit.hit ? 1 : 0,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return values;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ratio(value, max) {
|
|
73
|
+
const finite = Number.isFinite(value) ? value : max;
|
|
74
|
+
return Math.max(0, Math.min(1, finite / Math.max(1e-9, max)));
|
|
75
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { estimateCarHit } from './carRays.js';
|
|
2
|
+
import { createTrackMiss, estimateTrackHit } from './trackRays.js';
|
|
3
|
+
import { estimateSurfaceHits } from './surfaceRays.js';
|
|
4
|
+
import { traceIndexedRayBands } from './rayBandTrace.js';
|
|
5
|
+
import { createRayChannelFlags, writeRayChannelFlags } from './rayChannels.js';
|
|
6
|
+
import { getCarRayStats, getRayBandTraceStats } from './rayScratch.js';
|
|
7
|
+
import { MODEL_RAY_SURFACE_CHANNELS } from '../observationSchema.js';
|
|
8
|
+
import { pitOverrideAllowedForCar } from '../../simulation/track/trackStatePolicy.js';
|
|
9
|
+
|
|
10
|
+
const CHANNEL_CONTEXT = Symbol('rayChannelContext');
|
|
11
|
+
|
|
12
|
+
export function estimateCarHitForRay(car, snapshot, ray, origin, vector, carTargets, sharedRayQuery, scratch) {
|
|
13
|
+
return estimateCarHit(car, snapshot, ray.angleDegrees, ray.lengthMeters, origin, carTargets, {
|
|
14
|
+
rayVector: vector,
|
|
15
|
+
resultTarget: sharedRayQuery?.carHitResult,
|
|
16
|
+
stats: getCarRayStats(scratch),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createRayChannelContext(normalized) {
|
|
21
|
+
if (normalized?.[CHANNEL_CONTEXT]) return normalized[CHANNEL_CONTEXT];
|
|
22
|
+
const channels = writeRayChannelFlags(createRayChannelFlags(), normalized.channels);
|
|
23
|
+
const context = {
|
|
24
|
+
channels,
|
|
25
|
+
surfaceChannels: MODEL_RAY_SURFACE_CHANNELS.filter((channel) => channels.has(channel)),
|
|
26
|
+
hasRoadEdge: channels.hasRoadEdge,
|
|
27
|
+
hasCar: channels.hasCar,
|
|
28
|
+
hasRoadOrSurface: channels.hasRoadEdge || channels.surfaceChannels.length > 0,
|
|
29
|
+
};
|
|
30
|
+
if (normalized) {
|
|
31
|
+
Object.defineProperty(normalized, CHANNEL_CONTEXT, {
|
|
32
|
+
value: context,
|
|
33
|
+
enumerable: false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return context;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function estimateRayBands({
|
|
40
|
+
car,
|
|
41
|
+
snapshot,
|
|
42
|
+
ray,
|
|
43
|
+
angleDegrees,
|
|
44
|
+
origin,
|
|
45
|
+
vector,
|
|
46
|
+
normalized,
|
|
47
|
+
channelContext,
|
|
48
|
+
trackContext,
|
|
49
|
+
sharedRayQuery,
|
|
50
|
+
scratch,
|
|
51
|
+
}) {
|
|
52
|
+
const stats = getRayBandTraceStats(scratch);
|
|
53
|
+
const canUseRayBandTrace = channelContext.hasRoadOrSurface &&
|
|
54
|
+
trackContext?.originState;
|
|
55
|
+
if (canUseRayBandTrace) {
|
|
56
|
+
const direct = traceIndexedRayBands({
|
|
57
|
+
track: snapshot.track,
|
|
58
|
+
origin,
|
|
59
|
+
vector,
|
|
60
|
+
lengthMeters: ray.lengthMeters,
|
|
61
|
+
originState: trackContext.originState,
|
|
62
|
+
channels: channelContext.channels,
|
|
63
|
+
precision: normalized.precision,
|
|
64
|
+
sharedRayQuery,
|
|
65
|
+
stats,
|
|
66
|
+
validateBoundaries: car?.interaction?.profile !== 'batch-training',
|
|
67
|
+
allowPitOverride: car?.interaction?.profile === 'batch-training'
|
|
68
|
+
? false
|
|
69
|
+
: pitOverrideAllowedForCar(car),
|
|
70
|
+
mainTrackOnly: car?.interaction?.profile === 'batch-training',
|
|
71
|
+
});
|
|
72
|
+
if (direct.available) {
|
|
73
|
+
if (stats) {
|
|
74
|
+
if (direct.path === 'sampled') stats.sampledPathCount += 1;
|
|
75
|
+
else stats.directPathCount += 1;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
roadEdge: direct.roadEdge,
|
|
79
|
+
surfaceHits: {
|
|
80
|
+
kerb: direct.kerb,
|
|
81
|
+
illegalSurface: direct.illegalSurface,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (stats) stats.fallbackCount += 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
roadEdge: channelContext.hasRoadEdge
|
|
90
|
+
? estimateTrackHit(
|
|
91
|
+
car,
|
|
92
|
+
snapshot,
|
|
93
|
+
angleDegrees,
|
|
94
|
+
ray.lengthMeters,
|
|
95
|
+
trackContext,
|
|
96
|
+
{ precision: normalized.precision, sharedRayQuery },
|
|
97
|
+
)
|
|
98
|
+
: createTrackMiss(ray.lengthMeters),
|
|
99
|
+
surfaceHits: estimateSurfaceHits(
|
|
100
|
+
car,
|
|
101
|
+
snapshot,
|
|
102
|
+
ray,
|
|
103
|
+
origin,
|
|
104
|
+
vector,
|
|
105
|
+
channelContext.surfaceChannels,
|
|
106
|
+
trackContext,
|
|
107
|
+
{ precision: normalized.precision, sharedRayQuery },
|
|
108
|
+
),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { normalizeRayOptions } from './rayConfig.js';
|
|
2
|
+
import { getCarRayOrigin } from './rayGeometry.js';
|
|
3
|
+
import { isSelfCarTarget, rayDetectableTargetsForSnapshot } from './sensorTargets.js';
|
|
4
|
+
|
|
5
|
+
export function createRayBatchContext(snapshot, { scratch = false } = {}) {
|
|
6
|
+
const contextScratch = scratch === true
|
|
7
|
+
? {}
|
|
8
|
+
: scratch && typeof scratch === 'object'
|
|
9
|
+
? scratch
|
|
10
|
+
: null;
|
|
11
|
+
const context = {
|
|
12
|
+
rayTargets: rayDetectableTargetsForSnapshot(snapshot, contextScratch),
|
|
13
|
+
};
|
|
14
|
+
if (contextScratch) context.scratch = contextScratch;
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getRayScratch(batchContext) {
|
|
19
|
+
return batchContext?.scratch ?? null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getNormalizedRayOptions(rayOptions, scratch) {
|
|
23
|
+
if (!scratch || !rayOptions || typeof rayOptions !== 'object') return normalizeRayOptions(rayOptions);
|
|
24
|
+
if (rayOptions?.rays && rayOptions?.channels && rayOptions?.anglesDegrees) {
|
|
25
|
+
return normalizeRayOptions(rayOptions);
|
|
26
|
+
}
|
|
27
|
+
const cache = scratch.normalizedRayOptionsBySource ?? new WeakMap();
|
|
28
|
+
scratch.normalizedRayOptionsBySource = cache;
|
|
29
|
+
const cached = cache.get(rayOptions);
|
|
30
|
+
if (cached) return cached;
|
|
31
|
+
const normalized = normalizeRayOptions(rayOptions);
|
|
32
|
+
cache.set(rayOptions, normalized);
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getRayOrigin(car, scratch) {
|
|
37
|
+
if (!scratch) return getCarRayOrigin(car);
|
|
38
|
+
const origin = scratch.origin ?? {};
|
|
39
|
+
scratch.origin = origin;
|
|
40
|
+
origin.x = car.x;
|
|
41
|
+
origin.y = car.y;
|
|
42
|
+
return origin;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getFilteredCarTargets(car, snapshot, batchContext, scratch) {
|
|
46
|
+
const targets = batchContext?.rayTargets ?? rayDetectableTargetsForSnapshot(snapshot);
|
|
47
|
+
if (!scratch) return targets.filter((target) => !isSelfCarTarget(car, target));
|
|
48
|
+
const filtered = scratch.carTargets ?? [];
|
|
49
|
+
scratch.carTargets = filtered;
|
|
50
|
+
let count = 0;
|
|
51
|
+
for (let index = 0; index < targets.length; index += 1) {
|
|
52
|
+
const target = targets[index];
|
|
53
|
+
if (isSelfCarTarget(car, target)) continue;
|
|
54
|
+
filtered[count] = target;
|
|
55
|
+
count += 1;
|
|
56
|
+
}
|
|
57
|
+
filtered.length = count;
|
|
58
|
+
return filtered;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function prepareScratchArray(scratch, arrayKey, poolKey, count, createItem) {
|
|
62
|
+
if (!scratch) return null;
|
|
63
|
+
const values = scratch[arrayKey] ?? [];
|
|
64
|
+
const pool = scratch[poolKey] ?? [];
|
|
65
|
+
scratch[arrayKey] = values;
|
|
66
|
+
scratch[poolKey] = pool;
|
|
67
|
+
values.length = count;
|
|
68
|
+
for (let index = 0; index < count; index += 1) {
|
|
69
|
+
const item = pool[index] ?? createItem();
|
|
70
|
+
pool[index] = item;
|
|
71
|
+
values[index] = item;
|
|
72
|
+
}
|
|
73
|
+
return values;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getScratchRayVector(scratch, index, car, angleRadians) {
|
|
77
|
+
if (!scratch) {
|
|
78
|
+
return {
|
|
79
|
+
x: Math.cos((car.heading ?? 0) + angleRadians),
|
|
80
|
+
y: Math.sin((car.heading ?? 0) + angleRadians),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const vectors = scratch.rayVectors ?? [];
|
|
84
|
+
scratch.rayVectors = vectors;
|
|
85
|
+
const vector = vectors[index] ?? {};
|
|
86
|
+
vectors[index] = vector;
|
|
87
|
+
vector.x = Math.cos((car.heading ?? 0) + angleRadians);
|
|
88
|
+
vector.y = Math.sin((car.heading ?? 0) + angleRadians);
|
|
89
|
+
return vector;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getSharedRayQuery(scratch, index) {
|
|
93
|
+
if (!scratch) return {};
|
|
94
|
+
const queries = scratch.sharedRayQueries ?? [];
|
|
95
|
+
scratch.sharedRayQueries = queries;
|
|
96
|
+
const query = queries[index] ?? {};
|
|
97
|
+
queries[index] = query;
|
|
98
|
+
query.carHitResult = query.carHitResult ?? {};
|
|
99
|
+
query.surfaceBoundaries = null;
|
|
100
|
+
query.trackBandBoundaries = null;
|
|
101
|
+
query.boundaryStateCache = null;
|
|
102
|
+
query.sampleStateCache = null;
|
|
103
|
+
return query;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getCarRayStats(scratch) {
|
|
107
|
+
if (!scratch) return null;
|
|
108
|
+
const stats = scratch.carRayStats ?? {
|
|
109
|
+
callerVectorCount: 0,
|
|
110
|
+
computedVectorCount: 0,
|
|
111
|
+
resultTargetCount: 0,
|
|
112
|
+
};
|
|
113
|
+
scratch.carRayStats = stats;
|
|
114
|
+
return stats;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getRayBandTraceStats(scratch) {
|
|
118
|
+
if (!scratch) return null;
|
|
119
|
+
const stats = scratch.rayBandTraceStats ?? {
|
|
120
|
+
directPathCount: 0,
|
|
121
|
+
sampledPathCount: 0,
|
|
122
|
+
fallbackCount: 0,
|
|
123
|
+
channelSetAllocations: 0,
|
|
124
|
+
};
|
|
125
|
+
scratch.rayBandTraceStats = stats;
|
|
126
|
+
return stats;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function clearRayBatchContextScratch(scratch) {
|
|
130
|
+
if (!scratch) return;
|
|
131
|
+
const arrayKeys = [
|
|
132
|
+
'rayTargets',
|
|
133
|
+
'rayTargetPool',
|
|
134
|
+
'carTargets',
|
|
135
|
+
'rayVectors',
|
|
136
|
+
'sharedRayQueries',
|
|
137
|
+
'rays',
|
|
138
|
+
'rayPool',
|
|
139
|
+
'rayVectorValues',
|
|
140
|
+
'rayVectorValuePool',
|
|
141
|
+
];
|
|
142
|
+
for (let index = 0; index < arrayKeys.length; index += 1) {
|
|
143
|
+
const value = scratch[arrayKeys[index]];
|
|
144
|
+
if (Array.isArray(value)) value.length = 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -10,10 +10,6 @@ export function rayDetectableTargets(self, snapshot) {
|
|
|
10
10
|
return rayDetectableTargetsForSnapshot(snapshot).filter((target) => !isSelfCarTarget(self, target));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function nearbyDetectableTargets(self, snapshot) {
|
|
14
|
-
return nearbyDetectableTargetsForSnapshot(snapshot).filter((target) => !isSelfCarTarget(self, target));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
export function rayDetectableTargetsForSnapshot(snapshot, scratch = null) {
|
|
18
14
|
if (scratch) {
|
|
19
15
|
return writeRayDetectableTargetsForSnapshot(snapshot, scratch);
|
|
@@ -31,6 +27,18 @@ export function nearbyDetectableTargetsForSnapshot(snapshot) {
|
|
|
31
27
|
];
|
|
32
28
|
}
|
|
33
29
|
|
|
30
|
+
// Shared only within one observation build, so mutable snapshots cannot leave
|
|
31
|
+
// stale positions, detectability flags, or replay entities in a later result.
|
|
32
|
+
export function createNearbyBatchContext(snapshot) {
|
|
33
|
+
const targetsById = new Map();
|
|
34
|
+
for (const car of snapshot.cars ?? []) targetsById.set(`car:${car.id}`, car);
|
|
35
|
+
for (const ghost of snapshot.replayGhosts ?? []) targetsById.set(`replayGhost:${ghost.id}`, ghost);
|
|
36
|
+
return {
|
|
37
|
+
nearbyTargets: nearbyDetectableTargetsForSnapshot(snapshot),
|
|
38
|
+
targetsById,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
function detectableCars(snapshot, predicate) {
|
|
35
43
|
return (snapshot.cars ?? [])
|
|
36
44
|
.filter((car) => predicate(car))
|