@inventure71/paddockjs 2.0.1 → 4.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 +176 -1
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +84 -512
- package/docs/architecture.md +38 -24
- package/docs/component_inventory.md +72 -0
- package/docs/composable-layouts.md +77 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +6 -6
- package/docs/data-helpers.md +50 -0
- package/docs/data_contract.md +104 -27
- package/docs/getting-started.md +75 -0
- package/docs/headless-environment.md +68 -0
- package/docs/index.md +18 -4
- package/docs/learnings.md +6 -6
- package/docs/rules.md +12 -8
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +71 -21
- package/docs/theming.md +64 -0
- package/docs/training.md +26 -19
- package/docs/troubleshooting.md +76 -0
- package/docs/upgrading-hosts-to-3.md +42 -0
- package/examples/python/README.md +6 -0
- package/examples/python/base_policy_server.py +19 -0
- package/package.json +10 -3
- package/src/api/PaddockSimulatorController.js +150 -24
- package/src/api/runtimeTheme.js +59 -0
- package/src/app/F1SimulatorApp.js +213 -29
- package/src/app/banners/raceDataBannerController.js +71 -36
- package/src/app/banners/stewardMessageController.js +5 -4
- package/src/app/camera/cameraConstants.js +2 -0
- package/src/app/camera/cameraController.js +86 -29
- package/src/app/camera/cameraGeometry.js +12 -1
- package/src/app/domBindings.js +89 -9
- package/src/app/layoutScheduler.js +27 -0
- package/src/app/layoutSupport.js +165 -0
- package/src/app/raceOverlayClearanceSupport.js +158 -0
- package/src/app/readouts/carOverviewRenderer.js +16 -8
- package/src/app/readouts/controlStateRenderer.js +1 -1
- package/src/app/readouts/raceStatusRenderer.js +30 -21
- package/src/app/readouts/readoutFormatters.js +2 -0
- package/src/app/readouts/telemetryRenderer.js +17 -8
- package/src/app/readouts/timingTowerRenderer.js +262 -49
- package/src/app/runtime/frameLoop.js +5 -1
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +58 -38
- package/src/config/themeOptions.js +830 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/data/index.d.ts +212 -0
- package/src/data/index.js +14 -0
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/episode.js +11 -1
- package/src/environment/index.d.ts +1 -2
- package/src/environment/metrics.js +59 -16
- package/src/environment/observationVector.js +50 -33
- package/src/environment/observations.js +89 -20
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +44 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/carRays.js +48 -37
- package/src/environment/sensors/index.js +501 -97
- package/src/environment/sensors/indexedRayBands.js +134 -46
- package/src/environment/sensors/rayBandTrace.js +1171 -0
- package/src/environment/sensors/rayChannels.js +108 -0
- package/src/environment/sensors/rayConfig.js +71 -25
- package/src/environment/sensors/rayGeometry.js +23 -17
- package/src/environment/sensors/sensorTargets.js +67 -1
- package/src/environment/sensors/surfaceRays.js +167 -118
- package/src/environment/sensors/trackRays.js +157 -48
- package/src/index.d.ts +153 -9
- package/src/index.js +52 -3
- package/src/simulation/collisionGeometry.js +260 -96
- package/src/simulation/driver/driverController.js +1 -1
- package/src/simulation/driver/edgeRecovery.js +1 -1
- package/src/simulation/driver/racingControls.js +2 -2
- package/src/simulation/driver/rejoinControls.js +2 -2
- package/src/simulation/pit/pitFlow.js +21 -9
- package/src/simulation/pit/pitKinematics.js +2 -2
- package/src/simulation/pit/pitQueue.js +5 -3
- package/src/simulation/pit/pitRouteMovement.js +9 -4
- package/src/simulation/pit/pitRouting.js +79 -46
- package/src/simulation/pit/pitState.js +1 -0
- package/src/simulation/pit/pitTireService.js +7 -3
- package/src/simulation/race/raceFinish.js +25 -8
- package/src/simulation/race/raceOrder.js +189 -32
- package/src/simulation/race/raceProgress.js +129 -37
- package/src/simulation/race/raceSetup.js +4 -6
- package/src/simulation/race/raceStep.js +26 -14
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/raceSimulation.js +20 -17
- package/src/simulation/rules/collisionSteward.js +40 -23
- package/src/simulation/rules/rulesReview.js +77 -56
- package/src/simulation/snapshots/raceSnapshots.js +37 -0
- package/src/simulation/timing/drsTiming.js +143 -30
- package/src/simulation/timing/gapEstimation.js +25 -3
- package/src/simulation/timing/lapTelemetry.js +138 -40
- package/src/simulation/timing/raceTiming.js +5 -1
- package/src/simulation/timing/sectorPerformance.js +104 -26
- package/src/simulation/timing/timingHistory.js +229 -14
- package/src/simulation/timing/timingLines.js +65 -7
- package/src/simulation/track/pitLaneLayout.js +30 -3
- package/src/simulation/track/pitLaneState.js +218 -19
- package/src/simulation/track/spatialQueries.js +66 -33
- package/src/simulation/track/trackMath.js +91 -24
- package/src/simulation/track/trackModel.js +2 -0
- package/src/simulation/track/trackQueryIndex.js +1468 -189
- package/src/simulation/track/trackQueryPitIndex.js +138 -14
- package/src/simulation/track/trackQueryScratch.js +7 -0
- package/src/simulation/track/trackQueryStats.js +25 -0
- package/src/simulation/track/trackSnapshotJson.js +139 -0
- package/src/simulation/track/trackStatePolicy.js +249 -2
- package/src/simulation/vehicle/contactResolution.js +99 -62
- package/src/simulation/vehicle/mainTrackWheelSurface.js +73 -34
- package/src/simulation/vehicle/pitWheelSurface.js +81 -28
- package/src/simulation/vehicle/runoffResponse.js +54 -29
- package/src/simulation/vehicle/vehicleGeometry.js +245 -51
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +45 -30
- package/src/simulation/vehicle/vehicleSnapshots.js +35 -1
- package/src/simulation/vehicle/vehicleState.js +7 -1
- package/src/simulation/vehicle/wheelSurface.js +807 -92
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1568 -519
- package/src/ui/bannerTemplates.js +38 -28
- package/src/ui/cameraControlsTemplate.js +16 -5
- package/src/ui/carOverviewTemplate.js +12 -5
- package/src/ui/raceCanvasTemplate.js +31 -8
- package/src/ui/raceControlsTemplate.js +27 -10
- package/src/ui/shellTemplate.js +3 -1
- package/src/ui/telemetryDrawerTemplate.js +23 -10
- package/src/ui/telemetryTemplates.js +58 -32
- package/src/ui/templateUtils.js +48 -0
- package/src/ui/timingTowerTemplate.js +29 -12
|
@@ -12,11 +12,21 @@ import { canUseIndexedRecoveryRayApproximation } from './rayGuards.js';
|
|
|
12
12
|
import { pointOnRay } from './rayGeometry.js';
|
|
13
13
|
import { findIndexedTrackBandBoundaries } from './indexedRayBands.js';
|
|
14
14
|
|
|
15
|
-
const LEGAL_SURFACES = new Set(['track', 'kerb', 'pit-entry', 'pit-lane', 'pit-exit', 'pit-box']);
|
|
16
|
-
const SURFACE_CHANNELS = new Set(['kerb', 'illegalSurface']);
|
|
17
|
-
|
|
18
15
|
export function requestedSurfaceChannels(channels = []) {
|
|
19
|
-
|
|
16
|
+
const requested = [];
|
|
17
|
+
let hasKerb = false;
|
|
18
|
+
let hasIllegalSurface = false;
|
|
19
|
+
for (let index = 0; index < channels.length; index += 1) {
|
|
20
|
+
const channel = channels[index];
|
|
21
|
+
if (channel === 'kerb' && !hasKerb) {
|
|
22
|
+
requested.push(channel);
|
|
23
|
+
hasKerb = true;
|
|
24
|
+
} else if (channel === 'illegalSurface' && !hasIllegalSurface) {
|
|
25
|
+
requested.push(channel);
|
|
26
|
+
hasIllegalSurface = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return requested;
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
export function createSurfaceMiss(lengthMeters) {
|
|
@@ -38,45 +48,42 @@ export function estimateSurfaceHits(
|
|
|
38
48
|
{ precision = 'driver', sharedRayQuery = null } = {},
|
|
39
49
|
) {
|
|
40
50
|
const requested = requestedSurfaceChannels(channels);
|
|
41
|
-
const
|
|
51
|
+
const hits = prepareSurfaceHitContainer(sharedRayQuery, ray.lengthMeters);
|
|
42
52
|
if (!requested.length || !Array.isArray(snapshot.track?.samples) || snapshot.track.samples.length === 0) {
|
|
43
|
-
return
|
|
53
|
+
return hits;
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
const originState = context?.precision === precision
|
|
47
57
|
? context.originState
|
|
48
58
|
: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (analyticHits) return { ...misses, ...analyticHits, ...originHits };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const pending = new Set(requested);
|
|
75
|
-
Object.keys(originHits).forEach((channel) => {
|
|
76
|
-
misses[channel] = originHits[channel];
|
|
77
|
-
pending.delete(channel);
|
|
59
|
+
writeSurfaceHitsAtOrigin(hits, requested, originState);
|
|
60
|
+
const indexedHits = estimateIndexedSurfaceHits({
|
|
61
|
+
car,
|
|
62
|
+
track: snapshot.track,
|
|
63
|
+
ray,
|
|
64
|
+
origin,
|
|
65
|
+
vector,
|
|
66
|
+
requested,
|
|
67
|
+
originState,
|
|
68
|
+
precision,
|
|
69
|
+
sharedRayQuery,
|
|
70
|
+
hits,
|
|
71
|
+
});
|
|
72
|
+
if (indexedHits) return indexedHits;
|
|
73
|
+
const analyticHits = estimateAnalyticSurfaceHits({
|
|
74
|
+
car,
|
|
75
|
+
track: snapshot.track,
|
|
76
|
+
ray,
|
|
77
|
+
vector,
|
|
78
|
+
requested,
|
|
79
|
+
originState,
|
|
80
|
+
hits,
|
|
78
81
|
});
|
|
79
|
-
if (
|
|
82
|
+
if (analyticHits) return analyticHits;
|
|
83
|
+
|
|
84
|
+
let pendingKerb = requestedIncludes(requested, 'kerb') && !matchesSurfaceChannel('kerb', originState);
|
|
85
|
+
let pendingIllegalSurface = requestedIncludes(requested, 'illegalSurface') && !matchesSurfaceChannel('illegalSurface', originState);
|
|
86
|
+
if (!pendingKerb && !pendingIllegalSurface) return hits;
|
|
80
87
|
const maxDistance = metersToSimUnits(ray.lengthMeters);
|
|
81
88
|
const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
|
|
82
89
|
let previousDistance = 0;
|
|
@@ -84,60 +91,78 @@ export function estimateSurfaceHits(
|
|
|
84
91
|
|
|
85
92
|
for (let distance = 0; distance <= maxDistance; distance += step) {
|
|
86
93
|
const state = nearestRayTrackState(snapshot.track, car, pointOnRay(origin, vector, distance), car.progress, precision);
|
|
87
|
-
|
|
88
|
-
if (!matchesSurfaceChannel(channel, state)) continue;
|
|
94
|
+
if (pendingKerb && matchesSurfaceChannel('kerb', state)) {
|
|
89
95
|
const hitDistance = previousState
|
|
90
|
-
? refineSurfaceTransition(snapshot.track, car, origin, vector, car.progress, previousDistance, distance,
|
|
96
|
+
? refineSurfaceTransition(snapshot.track, car, origin, vector, car.progress, previousDistance, distance, 'kerb', refineStepsForPrecision(precision))
|
|
91
97
|
: distance;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
distanceMeters: simUnitsToMeters(hitDistance),
|
|
95
|
-
surface: state.surface ?? null,
|
|
96
|
-
};
|
|
97
|
-
pending.delete(channel);
|
|
98
|
+
writeSurfaceHit(hits.kerb, simUnitsToMeters(hitDistance), state.surface ?? null);
|
|
99
|
+
pendingKerb = false;
|
|
98
100
|
}
|
|
99
|
-
if (
|
|
101
|
+
if (pendingIllegalSurface && matchesSurfaceChannel('illegalSurface', state)) {
|
|
102
|
+
const hitDistance = previousState
|
|
103
|
+
? refineSurfaceTransition(snapshot.track, car, origin, vector, car.progress, previousDistance, distance, 'illegalSurface', refineStepsForPrecision(precision))
|
|
104
|
+
: distance;
|
|
105
|
+
writeSurfaceHit(hits.illegalSurface, simUnitsToMeters(hitDistance), state.surface ?? null);
|
|
106
|
+
pendingIllegalSurface = false;
|
|
107
|
+
}
|
|
108
|
+
if (!pendingKerb && !pendingIllegalSurface) break;
|
|
100
109
|
previousDistance = distance;
|
|
101
110
|
previousState = state;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
return
|
|
113
|
+
return hits;
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
function
|
|
108
|
-
const hits =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
surface: originState.surface ?? null,
|
|
115
|
-
};
|
|
116
|
-
});
|
|
116
|
+
function prepareSurfaceHitContainer(cache, lengthMeters) {
|
|
117
|
+
const hits = cache
|
|
118
|
+
? (cache.surfaceHits ?? {})
|
|
119
|
+
: {};
|
|
120
|
+
if (cache) cache.surfaceHits = hits;
|
|
121
|
+
hits.kerb = writeSurfaceMiss(hits.kerb ?? {}, lengthMeters);
|
|
122
|
+
hits.illegalSurface = writeSurfaceMiss(hits.illegalSurface ?? {}, lengthMeters);
|
|
117
123
|
return hits;
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
function
|
|
126
|
+
function writeSurfaceMiss(target, lengthMeters) {
|
|
127
|
+
target.hit = false;
|
|
128
|
+
target.distanceMeters = lengthMeters;
|
|
129
|
+
target.surface = null;
|
|
130
|
+
return target;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function writeSurfaceHit(target, distanceMeters, surface) {
|
|
134
|
+
target.hit = true;
|
|
135
|
+
target.distanceMeters = distanceMeters;
|
|
136
|
+
target.surface = surface ?? null;
|
|
137
|
+
return target;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function writeSurfaceHitsAtOrigin(hits, requested, originState) {
|
|
141
|
+
for (let index = 0; index < requested.length; index += 1) {
|
|
142
|
+
const channel = requested[index];
|
|
143
|
+
if (!matchesSurfaceChannel(channel, originState)) continue;
|
|
144
|
+
writeSurfaceHit(hits[channel], 0, originState.surface ?? null);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function estimateAnalyticSurfaceHits({ car, track, ray, vector, requested, originState, hits }) {
|
|
121
149
|
if (!originState || car.inPitLane || originState.inPitLane) return null;
|
|
122
150
|
if (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState)) return null;
|
|
123
151
|
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
|
|
124
152
|
if (Math.abs(originState.curvature ?? 0) > ANALYTIC_TRACK_RAY_MAX_CURVATURE) return null;
|
|
125
153
|
|
|
126
154
|
const lateral = vector.x * originState.normalX + vector.y * originState.normalY;
|
|
127
|
-
if (Math.abs(lateral) < 0.08) return
|
|
128
|
-
channel,
|
|
129
|
-
createSurfaceMiss(ray.lengthMeters),
|
|
130
|
-
]));
|
|
155
|
+
if (Math.abs(lateral) < 0.08) return hits;
|
|
131
156
|
|
|
132
157
|
const trackHalfWidth = track.width / 2;
|
|
133
158
|
const kerbOuter = trackHalfWidth + (track.kerbWidth ?? 0);
|
|
134
159
|
const maxDistance = metersToSimUnits(ray.lengthMeters);
|
|
135
160
|
const offset = originState.signedOffset ?? car.signedOffset ?? 0;
|
|
136
|
-
const hits = {};
|
|
137
161
|
|
|
138
|
-
requested.
|
|
162
|
+
for (let index = 0; index < requested.length; index += 1) {
|
|
163
|
+
const channel = requested[index];
|
|
139
164
|
if (channel === 'kerb') {
|
|
140
|
-
hits
|
|
165
|
+
hitAbsOffsetBand(hits.kerb, {
|
|
141
166
|
offset,
|
|
142
167
|
lateral,
|
|
143
168
|
minAbsOffset: trackHalfWidth,
|
|
@@ -147,7 +172,7 @@ function estimateAnalyticSurfaceHits({ car, track, ray, vector, requested, origi
|
|
|
147
172
|
surface: 'kerb',
|
|
148
173
|
});
|
|
149
174
|
} else if (channel === 'illegalSurface') {
|
|
150
|
-
hits
|
|
175
|
+
hitAbsOffsetBand(hits.illegalSurface, {
|
|
151
176
|
offset,
|
|
152
177
|
lateral,
|
|
153
178
|
minAbsOffset: kerbOuter,
|
|
@@ -157,15 +182,14 @@ function estimateAnalyticSurfaceHits({ car, track, ray, vector, requested, origi
|
|
|
157
182
|
surface: surfaceBeyondKerb(track, offset, lateral, maxDistance),
|
|
158
183
|
});
|
|
159
184
|
}
|
|
160
|
-
}
|
|
185
|
+
}
|
|
161
186
|
|
|
162
187
|
return hits;
|
|
163
188
|
}
|
|
164
189
|
|
|
165
|
-
function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested, originState, precision, sharedRayQuery }) {
|
|
190
|
+
function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested, originState, precision, sharedRayQuery, hits }) {
|
|
166
191
|
if (!originState || car.inPitLane || originState.inPitLane) return null;
|
|
167
192
|
if (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState)) return null;
|
|
168
|
-
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
|
|
169
193
|
|
|
170
194
|
const trackHalfWidth = track.width / 2;
|
|
171
195
|
const kerbOuter = trackHalfWidth + (track.kerbWidth ?? 0);
|
|
@@ -180,19 +204,27 @@ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested
|
|
|
180
204
|
sharedRayQuery,
|
|
181
205
|
);
|
|
182
206
|
if (!boundaries.available) return null;
|
|
183
|
-
const hits = {};
|
|
184
207
|
|
|
185
|
-
for (
|
|
208
|
+
for (let index = 0; index < requested.length; index += 1) {
|
|
209
|
+
const channel = requested[index];
|
|
186
210
|
if (matchesSurfaceChannel(channel, originState)) {
|
|
187
|
-
hits[channel]
|
|
211
|
+
writeSurfaceHit(hits[channel], 0, originState.surface ?? null);
|
|
188
212
|
continue;
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
const boundaryDistance = channel === 'kerb'
|
|
192
|
-
?
|
|
216
|
+
? minFinite2(boundaries.trackEdgeDistance, boundaries.kerbOuterDistance)
|
|
193
217
|
: boundaries.kerbOuterDistance;
|
|
194
|
-
if (boundaryDistance == null)
|
|
195
|
-
|
|
218
|
+
if (boundaryDistance == null) {
|
|
219
|
+
if (usesMainTrackOnlyRays(car) && channel === 'illegalSurface') {
|
|
220
|
+
writeSurfaceMiss(hits[channel], ray.lengthMeters);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (canSampleRecoverySurface(track, originState, vector)) return null;
|
|
224
|
+
writeSurfaceMiss(hits[channel], ray.lengthMeters);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const hitDistance = usesMainTrackOnlyRays(car) || refineStepsForPrecision(precision) <= 0 || precision === 'debug'
|
|
196
228
|
? boundaryDistance
|
|
197
229
|
: sampledIndexedSurfaceDistance({
|
|
198
230
|
track,
|
|
@@ -206,13 +238,13 @@ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested
|
|
|
206
238
|
precision,
|
|
207
239
|
});
|
|
208
240
|
if (hitDistance == null) return null;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
241
|
+
writeSurfaceHit(
|
|
242
|
+
hits[channel],
|
|
243
|
+
simUnitsToMeters(hitDistance),
|
|
244
|
+
channel === 'kerb'
|
|
213
245
|
? 'kerb'
|
|
214
246
|
: surfaceBeyondKerb(track, originState.signedOffset ?? 0, lateral, metersToSimUnits(ray.lengthMeters)),
|
|
215
|
-
|
|
247
|
+
);
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
return hits;
|
|
@@ -244,35 +276,28 @@ function usesMainTrackOnlyRays(car) {
|
|
|
244
276
|
return car?.interaction?.profile === 'batch-training';
|
|
245
277
|
}
|
|
246
278
|
|
|
247
|
-
function hitAbsOffsetBand({ offset, lateral, minAbsOffset, maxAbsOffset, maxDistance, lengthMeters, surface }) {
|
|
279
|
+
function hitAbsOffsetBand(target, { offset, lateral, minAbsOffset, maxAbsOffset, maxDistance, lengthMeters, surface }) {
|
|
248
280
|
const currentAbs = Math.abs(offset);
|
|
249
281
|
if (currentAbs >= minAbsOffset && currentAbs <= maxAbsOffset) {
|
|
250
|
-
return
|
|
282
|
+
return writeSurfaceHit(target, 0, surface);
|
|
251
283
|
}
|
|
252
284
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
].filter((distance) => Number.isFinite(distance) && distance >= 0 && distance <= maxDistance);
|
|
257
|
-
|
|
285
|
+
let distance = Infinity;
|
|
286
|
+
distance = nearestValidBandDistance(distance, distanceToOffset(minAbsOffset, offset, lateral), offset, lateral, minAbsOffset, maxAbsOffset, maxDistance);
|
|
287
|
+
distance = nearestValidBandDistance(distance, distanceToOffset(-minAbsOffset, offset, lateral), offset, lateral, minAbsOffset, maxAbsOffset, maxDistance);
|
|
258
288
|
if (Number.isFinite(maxAbsOffset)) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
distanceToOffset(maxAbsOffset, offset, lateral),
|
|
262
|
-
distanceToOffset(-maxAbsOffset, offset, lateral),
|
|
263
|
-
].filter((distance) => Number.isFinite(distance) && distance >= 0 && distance <= maxDistance),
|
|
264
|
-
);
|
|
289
|
+
distance = nearestValidBandDistance(distance, distanceToOffset(maxAbsOffset, offset, lateral), offset, lateral, minAbsOffset, maxAbsOffset, maxDistance);
|
|
290
|
+
distance = nearestValidBandDistance(distance, distanceToOffset(-maxAbsOffset, offset, lateral), offset, lateral, minAbsOffset, maxAbsOffset, maxDistance);
|
|
265
291
|
}
|
|
266
292
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return nextAbs >= minAbsOffset - 1e-6 && nextAbs <= maxAbsOffset + 1e-6;
|
|
271
|
-
})
|
|
272
|
-
.sort((a, b) => a - b)[0];
|
|
293
|
+
if (!Number.isFinite(distance)) return writeSurfaceMiss(target, lengthMeters);
|
|
294
|
+
return writeSurfaceHit(target, simUnitsToMeters(distance), surface);
|
|
295
|
+
}
|
|
273
296
|
|
|
274
|
-
|
|
275
|
-
|
|
297
|
+
function nearestValidBandDistance(current, candidate, offset, lateral, minAbsOffset, maxAbsOffset, maxDistance) {
|
|
298
|
+
if (!Number.isFinite(candidate) || candidate < 0 || candidate > maxDistance || candidate >= current) return current;
|
|
299
|
+
const nextAbs = Math.abs(offset + lateral * candidate);
|
|
300
|
+
return nextAbs >= minAbsOffset - 1e-6 && nextAbs <= maxAbsOffset + 1e-6 ? candidate : current;
|
|
276
301
|
}
|
|
277
302
|
|
|
278
303
|
function distanceToOffset(targetOffset, offset, lateral) {
|
|
@@ -281,12 +306,16 @@ function distanceToOffset(targetOffset, offset, lateral) {
|
|
|
281
306
|
|
|
282
307
|
function surfaceBeyondKerb(track, offset, lateral, maxDistance) {
|
|
283
308
|
const gravelOuter = track.width / 2 + (track.kerbWidth ?? 0) + (track.gravelWidth ?? 0);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
309
|
+
const kerbOuter = track.width / 2 + (track.kerbWidth ?? 0);
|
|
310
|
+
const positiveDistance = distanceToOffset(kerbOuter, offset, lateral);
|
|
311
|
+
const negativeDistance = distanceToOffset(-kerbOuter, offset, lateral);
|
|
312
|
+
let hitDistance = Infinity;
|
|
313
|
+
if (Number.isFinite(positiveDistance) && positiveDistance >= 0 && positiveDistance <= maxDistance) {
|
|
314
|
+
hitDistance = positiveDistance;
|
|
315
|
+
}
|
|
316
|
+
if (Number.isFinite(negativeDistance) && negativeDistance >= 0 && negativeDistance <= maxDistance && negativeDistance < hitDistance) {
|
|
317
|
+
hitDistance = negativeDistance;
|
|
318
|
+
}
|
|
290
319
|
const hitAbs = Math.abs(offset + lateral * hitDistance);
|
|
291
320
|
return hitAbs <= gravelOuter ? 'gravel' : 'grass';
|
|
292
321
|
}
|
|
@@ -337,24 +366,44 @@ function refineStepsForPrecision(precision) {
|
|
|
337
366
|
|
|
338
367
|
function matchesSurfaceChannel(channel, state) {
|
|
339
368
|
if (channel === 'kerb') return state.surface === 'kerb';
|
|
340
|
-
if (channel === 'illegalSurface') return !
|
|
369
|
+
if (channel === 'illegalSurface') return !isLegalSurface(state.surface) && state.surface !== 'barrier';
|
|
341
370
|
return false;
|
|
342
371
|
}
|
|
343
372
|
|
|
373
|
+
function requestedIncludes(requested, channel) {
|
|
374
|
+
for (let index = 0; index < requested.length; index += 1) {
|
|
375
|
+
if (requested[index] === channel) return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function isLegalSurface(surface) {
|
|
381
|
+
return surface === 'track' ||
|
|
382
|
+
surface === 'kerb' ||
|
|
383
|
+
surface === 'pit-entry' ||
|
|
384
|
+
surface === 'pit-lane' ||
|
|
385
|
+
surface === 'pit-exit' ||
|
|
386
|
+
surface === 'pit-box';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function canSampleRecoverySurface(track, originState, vector) {
|
|
390
|
+
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
|
|
391
|
+
const kerbOuter = track.width / 2 + (track.kerbWidth ?? 0);
|
|
392
|
+
const offset = originState.signedOffset ?? 0;
|
|
393
|
+
if (Math.abs(offset) <= kerbOuter) return false;
|
|
394
|
+
const lateral = vector.x * originState.normalX + vector.y * originState.normalY;
|
|
395
|
+
if (Math.abs(lateral) < 0.05) return false;
|
|
396
|
+
return offset > 0 ? lateral < 0 : lateral > 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
344
399
|
function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
|
|
345
|
-
const useBatchTrainingIndexMode = usesMainTrackOnlyRays(car);
|
|
346
400
|
return nearestTrackState(track, point, progressHint, {
|
|
347
401
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
348
|
-
indexMode: usesSampledDriverContract(car, precision) ? 'legacy' : useBatchTrainingIndexMode ? 'sample' : undefined,
|
|
349
|
-
hintMaxDistance: useBatchTrainingIndexMode ? Infinity : undefined,
|
|
350
402
|
});
|
|
351
403
|
}
|
|
352
404
|
|
|
353
|
-
function
|
|
354
|
-
return
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
function minFinite(...values) {
|
|
358
|
-
const minimum = Math.min(...values.filter((value) => Number.isFinite(value)));
|
|
359
|
-
return Number.isFinite(minimum) ? minimum : null;
|
|
405
|
+
function minFinite2(first, second) {
|
|
406
|
+
if (!Number.isFinite(first)) return Number.isFinite(second) ? second : null;
|
|
407
|
+
if (!Number.isFinite(second)) return first;
|
|
408
|
+
return first <= second ? first : second;
|
|
360
409
|
}
|