@inventure71/paddockjs 2.0.1 → 3.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 +31 -1
- package/INSTALL_AND_UPDATE.md +10 -1
- package/README.md +65 -15
- package/docs/architecture.md +17 -13
- package/docs/component_inventory.md +72 -0
- package/docs/concepts.md +2 -2
- package/docs/custom_model_controller.md +2 -2
- package/docs/data_contract.md +96 -21
- package/docs/index.md +2 -1
- package/docs/learnings.md +6 -6
- package/docs/rules.md +7 -7
- package/docs/sense_contract.md +1 -1
- package/docs/system_specs.md +25 -18
- package/docs/training.md +13 -12
- package/package.json +1 -1
- package/src/api/PaddockSimulatorController.js +74 -4
- package/src/app/F1SimulatorApp.js +191 -28
- 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 +62 -10
- package/src/app/runtime/runtimeVisibility.js +66 -5
- package/src/config/cssValues.js +16 -0
- package/src/config/defaultOptions.js +51 -38
- package/src/config/themeOptions.js +825 -0
- package/src/config/timingGapMode.js +13 -0
- package/src/config/urlOptions.js +25 -0
- package/src/data/championship.js +14 -9
- package/src/environment/controllerLoop.js +17 -3
- package/src/environment/index.d.ts +1 -2
- package/src/environment/options.js +1 -1
- package/src/environment/runtime.js +3 -11
- package/src/environment/scenarios.js +1 -1
- package/src/environment/sensors/surfaceRays.js +41 -33
- package/src/environment/sensors/trackRays.js +44 -49
- package/src/index.d.ts +110 -9
- package/src/index.js +25 -1
- 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/pitKinematics.js +2 -2
- package/src/simulation/pit/pitRouting.js +2 -4
- package/src/simulation/race/raceSetup.js +2 -6
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/safetyCar.js +1 -1
- package/src/simulation/race/stalledDnf.js +1 -1
- package/src/simulation/timing/lapTelemetry.js +28 -9
- package/src/simulation/track/pitLaneLayout.js +7 -3
- package/src/simulation/track/spatialQueries.js +5 -28
- package/src/simulation/track/trackQueryIndex.js +180 -135
- package/src/simulation/track/trackStatePolicy.js +0 -1
- package/src/simulation/vehicle/contactResolution.js +3 -3
- package/src/simulation/vehicle/vehicleKinematics.js +2 -2
- package/src/simulation/vehicle/vehiclePhysics.js +3 -3
- package/src/simulation/vehicle/vehicleSnapshots.js +12 -1
- package/src/simulation/vehicle/vehicleState.js +1 -1
- package/src/simulation/warmup/runtimeWarmup.js +0 -1
- package/src/styles.css +1185 -136
- 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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const TIMING_GAP_MODES = new Set(['interval', 'leader']);
|
|
2
|
+
|
|
3
|
+
export function normalizeTimingGapMode(value) {
|
|
4
|
+
return value === 'leader' ? 'leader' : 'interval';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getTimingGapModeLabel(mode) {
|
|
8
|
+
return normalizeTimingGapMode(mode) === 'leader' ? 'Gap' : 'Int';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getNextTimingGapMode(mode) {
|
|
12
|
+
return normalizeTimingGapMode(mode) === 'leader' ? 'interval' : 'leader';
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const SAFE_ABSOLUTE_URL_PROTOCOLS = new Set(['http:', 'https:']);
|
|
2
|
+
|
|
3
|
+
export function normalizePublicUrlOption(value, fallback) {
|
|
4
|
+
const fallbackValue = typeof fallback === 'string' && fallback.trim() !== ''
|
|
5
|
+
? fallback.trim()
|
|
6
|
+
: '';
|
|
7
|
+
if (typeof value !== 'string') return fallbackValue;
|
|
8
|
+
|
|
9
|
+
const candidate = value.trim();
|
|
10
|
+
if (candidate === '') return fallbackValue;
|
|
11
|
+
if (/[\u0000-\u001F\u007F]/.test(candidate)) return fallbackValue;
|
|
12
|
+
if (candidate.includes('\\')) return fallbackValue;
|
|
13
|
+
if (candidate.startsWith('#')) return candidate;
|
|
14
|
+
if (candidate.startsWith('//')) return fallbackValue;
|
|
15
|
+
|
|
16
|
+
const schemeMatch = /^[A-Za-z][A-Za-z0-9+.-]*:/.exec(candidate);
|
|
17
|
+
if (!schemeMatch) return candidate;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(candidate);
|
|
21
|
+
return SAFE_ABSOLUTE_URL_PROTOCOLS.has(url.protocol) ? candidate : fallbackValue;
|
|
22
|
+
} catch {
|
|
23
|
+
return fallbackValue;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/data/championship.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DriverData } from './driverData.js';
|
|
2
2
|
import { DEMO_PROJECT_DRIVERS } from './demoDrivers.js';
|
|
3
3
|
import { VehicleData } from './vehicleData.js';
|
|
4
|
+
import { DEFAULT_DARK_THEME_TOKENS, normalizeCssColorToken } from '../config/themeOptions.js';
|
|
4
5
|
|
|
5
6
|
export { DriverData, DRIVER_STAT_DEFINITIONS } from './driverData.js';
|
|
6
7
|
export { VehicleData, VEHICLE_STAT_DEFINITIONS } from './vehicleData.js';
|
|
@@ -187,7 +188,7 @@ function normalizeTeam(team, driver, timingCode) {
|
|
|
187
188
|
...source,
|
|
188
189
|
id,
|
|
189
190
|
name,
|
|
190
|
-
color: source.color ?? driver.color,
|
|
191
|
+
color: normalizeCssColorToken(source.color ?? driver.color, driver.color),
|
|
191
192
|
icon: source.icon ?? normalizeTeamIcon(source.name ?? source.id ?? timingCode ?? driver.name),
|
|
192
193
|
};
|
|
193
194
|
}
|
|
@@ -201,22 +202,26 @@ export function buildChampionshipDriverGrid(drivers = DEMO_PROJECT_DRIVERS, entr
|
|
|
201
202
|
const usedTimingCodes = new Set();
|
|
202
203
|
|
|
203
204
|
return drivers.map((driver, index) => {
|
|
205
|
+
const safeDriver = {
|
|
206
|
+
...driver,
|
|
207
|
+
color: normalizeCssColorToken(driver.color, DEFAULT_DARK_THEME_TOKENS.primary),
|
|
208
|
+
};
|
|
204
209
|
const entry = {
|
|
205
|
-
...(blueprintByDriverId.get(
|
|
206
|
-
...(entryByDriverId.get(
|
|
210
|
+
...(blueprintByDriverId.get(safeDriver.id) ?? {}),
|
|
211
|
+
...(entryByDriverId.get(safeDriver.id) ?? {}),
|
|
207
212
|
};
|
|
208
|
-
const timingCode = pickUniqueTimingCode(entry.timingName ??
|
|
209
|
-
const driverNumber = entry.driverNumber ??
|
|
210
|
-
const driverArgs = buildDriverConstructorArgs({ driverId:
|
|
211
|
-
const vehicleArgs = buildVehicleConstructorArgs({ driverId:
|
|
213
|
+
const timingCode = pickUniqueTimingCode(entry.timingName ?? safeDriver.timingName ?? safeDriver.name ?? safeDriver.code, usedTimingCodes);
|
|
214
|
+
const driverNumber = entry.driverNumber ?? safeDriver.driverNumber ?? index + 1;
|
|
215
|
+
const driverArgs = buildDriverConstructorArgs({ driverId: safeDriver.id, ...entry });
|
|
216
|
+
const vehicleArgs = buildVehicleConstructorArgs({ driverId: safeDriver.id, ...entry });
|
|
212
217
|
|
|
213
218
|
return {
|
|
214
|
-
...
|
|
219
|
+
...safeDriver,
|
|
215
220
|
code: timingCode,
|
|
216
221
|
timingCode,
|
|
217
222
|
raceName: timingCode,
|
|
218
223
|
driverNumber,
|
|
219
|
-
team: normalizeTeam(entry.team,
|
|
224
|
+
team: normalizeTeam(entry.team, safeDriver, timingCode),
|
|
220
225
|
pace: driverArgs.pace,
|
|
221
226
|
racecraft: driverArgs.racecraft,
|
|
222
227
|
consistency: driverArgs.consistency,
|
|
@@ -168,8 +168,7 @@ export function createPaddockDriverControllerLoop({
|
|
|
168
168
|
|
|
169
169
|
function stop() {
|
|
170
170
|
running = false;
|
|
171
|
-
|
|
172
|
-
else if (typeof scheduled === 'number' && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(scheduled);
|
|
171
|
+
cancelScheduled(scheduled);
|
|
173
172
|
scheduled = null;
|
|
174
173
|
}
|
|
175
174
|
|
|
@@ -250,9 +249,24 @@ function now() {
|
|
|
250
249
|
: Date.now();
|
|
251
250
|
}
|
|
252
251
|
|
|
252
|
+
function cancelScheduled(handle) {
|
|
253
|
+
if (handle == null) return;
|
|
254
|
+
if (typeof handle.cancel === 'function') {
|
|
255
|
+
handle.cancel();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (typeof handle === 'number') {
|
|
259
|
+
if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);
|
|
260
|
+
if (typeof clearTimeout === 'function') clearTimeout(handle);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
function defaultScheduler() {
|
|
254
265
|
if (typeof requestAnimationFrame === 'function') {
|
|
255
|
-
return (callback) =>
|
|
266
|
+
return (callback) => {
|
|
267
|
+
const id = requestAnimationFrame(callback);
|
|
268
|
+
return { cancel: () => cancelAnimationFrame(id) };
|
|
269
|
+
};
|
|
256
270
|
}
|
|
257
271
|
return (callback) => {
|
|
258
272
|
const id = setTimeout(callback, 0);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type TireCompound = 'S' | 'M' | 'H';
|
|
2
|
-
export type PaddockPhysicsMode = 'arcade' | '
|
|
2
|
+
export type PaddockPhysicsMode = 'arcade' | 'advanced';
|
|
3
3
|
export type PaddockProceduralTrackProfile = 'race' | 'training-short' | 'training-medium' | 'training-technical';
|
|
4
4
|
|
|
5
5
|
export interface PaddockProceduralTrackOptions {
|
|
@@ -478,7 +478,6 @@ export interface PaddockEnvironmentOptions {
|
|
|
478
478
|
seed?: number;
|
|
479
479
|
trackSeed?: number;
|
|
480
480
|
trackGeneration?: PaddockProceduralTrackOptions;
|
|
481
|
-
trackQueryIndex?: boolean;
|
|
482
481
|
totalLaps?: number;
|
|
483
482
|
frameSkip?: number;
|
|
484
483
|
physicsMode?: PaddockPhysicsMode;
|
|
@@ -8,7 +8,7 @@ import { normalizeRayOptions } from './sensors.js';
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_SEED = 1971;
|
|
10
10
|
const DEFAULT_FRAME_SKIP = 1;
|
|
11
|
-
const DEFAULT_MAX_STEPS =
|
|
11
|
+
const DEFAULT_MAX_STEPS = 1_000_000;
|
|
12
12
|
const RESULT_STATE_OUTPUTS = new Set(['full', 'minimal', 'none']);
|
|
13
13
|
const RESET_OBSERVATION_SCOPES = new Set(['all', 'reset']);
|
|
14
14
|
|
|
@@ -38,15 +38,11 @@ export function createPaddockEnvironment(options = {}) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function createSimulationWithEnvironmentScenario(options) {
|
|
41
|
-
const simulationOptions = {
|
|
42
|
-
...options,
|
|
43
|
-
trackQueryIndex: shouldUseTrackQueryIndex(options),
|
|
44
|
-
};
|
|
45
41
|
runWarmupWithGuard({
|
|
46
|
-
options
|
|
42
|
+
options,
|
|
47
43
|
surface: 'environment',
|
|
48
44
|
execute: ({ warmup }) => {
|
|
49
|
-
const warmupSimulation = createRaceSimulation(disableWarmupOptions(
|
|
45
|
+
const warmupSimulation = createRaceSimulation(disableWarmupOptions(options, warmup.surface));
|
|
50
46
|
initializeControlledPitIntentForSimulation(warmupSimulation, options);
|
|
51
47
|
applyEnvironmentScenario(warmupSimulation, options);
|
|
52
48
|
applyControlledRunoffResponse(warmupSimulation, options);
|
|
@@ -59,17 +55,13 @@ function createSimulationWithEnvironmentScenario(options) {
|
|
|
59
55
|
});
|
|
60
56
|
},
|
|
61
57
|
});
|
|
62
|
-
const sim = createRaceSimulation(disableWarmupOptions(
|
|
58
|
+
const sim = createRaceSimulation(disableWarmupOptions(options, 'environment'));
|
|
63
59
|
initializeControlledPitIntentForSimulation(sim, options);
|
|
64
60
|
applyEnvironmentScenario(sim, options);
|
|
65
61
|
applyControlledRunoffResponse(sim, options);
|
|
66
62
|
return sim;
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
function shouldUseTrackQueryIndex(options) {
|
|
70
|
-
return options.trackQueryIndex !== false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
65
|
export function createEnvironmentRuntime(host) {
|
|
74
66
|
const episodeState = createEpisodeState();
|
|
75
67
|
|
|
@@ -233,7 +233,7 @@ function placementToCarState(track, currentCar, placement, { physicsMode = 'arca
|
|
|
233
233
|
};
|
|
234
234
|
if (speed != null) {
|
|
235
235
|
partial.speed = speed;
|
|
236
|
-
if (physicsMode === '
|
|
236
|
+
if (physicsMode === 'advanced') {
|
|
237
237
|
partial.velocityX = Math.cos(heading) * speed;
|
|
238
238
|
partial.velocityY = Math.sin(heading) * speed;
|
|
239
239
|
} else {
|
|
@@ -47,29 +47,27 @@ export function estimateSurfaceHits(
|
|
|
47
47
|
? context.originState
|
|
48
48
|
: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
|
|
49
49
|
const originHits = surfaceHitsAtOrigin(requested, originState);
|
|
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
|
-
}
|
|
50
|
+
const indexedHits = estimateIndexedSurfaceHits({
|
|
51
|
+
car,
|
|
52
|
+
track: snapshot.track,
|
|
53
|
+
ray,
|
|
54
|
+
origin,
|
|
55
|
+
vector,
|
|
56
|
+
requested,
|
|
57
|
+
originState,
|
|
58
|
+
precision,
|
|
59
|
+
sharedRayQuery,
|
|
60
|
+
});
|
|
61
|
+
if (indexedHits) return { ...misses, ...indexedHits, ...originHits };
|
|
62
|
+
const analyticHits = estimateAnalyticSurfaceHits({
|
|
63
|
+
car,
|
|
64
|
+
track: snapshot.track,
|
|
65
|
+
ray,
|
|
66
|
+
vector,
|
|
67
|
+
requested,
|
|
68
|
+
originState,
|
|
69
|
+
});
|
|
70
|
+
if (analyticHits) return { ...misses, ...analyticHits, ...originHits };
|
|
73
71
|
|
|
74
72
|
const pending = new Set(requested);
|
|
75
73
|
Object.keys(originHits).forEach((channel) => {
|
|
@@ -165,7 +163,6 @@ function estimateAnalyticSurfaceHits({ car, track, ray, vector, requested, origi
|
|
|
165
163
|
function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested, originState, precision, sharedRayQuery }) {
|
|
166
164
|
if (!originState || car.inPitLane || originState.inPitLane) return null;
|
|
167
165
|
if (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState)) return null;
|
|
168
|
-
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
|
|
169
166
|
|
|
170
167
|
const trackHalfWidth = track.width / 2;
|
|
171
168
|
const kerbOuter = trackHalfWidth + (track.kerbWidth ?? 0);
|
|
@@ -191,8 +188,16 @@ function estimateIndexedSurfaceHits({ car, track, ray, origin, vector, requested
|
|
|
191
188
|
const boundaryDistance = channel === 'kerb'
|
|
192
189
|
? minFinite(boundaries.trackEdgeDistance, boundaries.kerbOuterDistance)
|
|
193
190
|
: boundaries.kerbOuterDistance;
|
|
194
|
-
if (boundaryDistance == null)
|
|
195
|
-
|
|
191
|
+
if (boundaryDistance == null) {
|
|
192
|
+
if (usesMainTrackOnlyRays(car) && channel === 'illegalSurface') {
|
|
193
|
+
hits[channel] = createSurfaceMiss(ray.lengthMeters);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (canSampleRecoverySurface(track, originState, vector)) return null;
|
|
197
|
+
hits[channel] = createSurfaceMiss(ray.lengthMeters);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const hitDistance = usesMainTrackOnlyRays(car) || refineStepsForPrecision(precision) <= 0 || precision === 'debug'
|
|
196
201
|
? boundaryDistance
|
|
197
202
|
: sampledIndexedSurfaceDistance({
|
|
198
203
|
track,
|
|
@@ -341,19 +346,22 @@ function matchesSurfaceChannel(channel, state) {
|
|
|
341
346
|
return false;
|
|
342
347
|
}
|
|
343
348
|
|
|
349
|
+
function canSampleRecoverySurface(track, originState, vector) {
|
|
350
|
+
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
|
|
351
|
+
const kerbOuter = track.width / 2 + (track.kerbWidth ?? 0);
|
|
352
|
+
const offset = originState.signedOffset ?? 0;
|
|
353
|
+
if (Math.abs(offset) <= kerbOuter) return false;
|
|
354
|
+
const lateral = vector.x * originState.normalX + vector.y * originState.normalY;
|
|
355
|
+
if (Math.abs(lateral) < 0.05) return false;
|
|
356
|
+
return offset > 0 ? lateral < 0 : lateral > 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
344
359
|
function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
|
|
345
|
-
const useBatchTrainingIndexMode = usesMainTrackOnlyRays(car);
|
|
346
360
|
return nearestTrackState(track, point, progressHint, {
|
|
347
361
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
348
|
-
indexMode: usesSampledDriverContract(car, precision) ? 'legacy' : useBatchTrainingIndexMode ? 'sample' : undefined,
|
|
349
|
-
hintMaxDistance: useBatchTrainingIndexMode ? Infinity : undefined,
|
|
350
362
|
});
|
|
351
363
|
}
|
|
352
364
|
|
|
353
|
-
function usesSampledDriverContract(car, precision) {
|
|
354
|
-
return precision === 'driver' && !usesMainTrackOnlyRays(car);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
365
|
function minFinite(...values) {
|
|
358
366
|
const minimum = Math.min(...values.filter((value) => Number.isFinite(value)));
|
|
359
367
|
return Number.isFinite(minimum) ? minimum : null;
|
|
@@ -43,6 +43,18 @@ export function estimateTrackHit(
|
|
|
43
43
|
? context.originState
|
|
44
44
|
: nearestRayTrackState(snapshot.track, car, origin, car.progress, precision);
|
|
45
45
|
const includePitLane = Boolean(car.inPitLane || car.pitLanePart || originState.inPitLane);
|
|
46
|
+
const indexedHit = estimateIndexedTrackHit({
|
|
47
|
+
car,
|
|
48
|
+
track: snapshot.track,
|
|
49
|
+
origin,
|
|
50
|
+
originState,
|
|
51
|
+
ray,
|
|
52
|
+
lengthMeters,
|
|
53
|
+
includePitLane,
|
|
54
|
+
precision,
|
|
55
|
+
sharedRayQuery,
|
|
56
|
+
});
|
|
57
|
+
if (indexedHit) return indexedHit;
|
|
46
58
|
const directionalAnalyticHit = canUseDirectionalAnalyticTrackHit({ car, track: snapshot.track, originState, ray, includePitLane })
|
|
47
59
|
? estimateAnalyticMainTrackHit({
|
|
48
60
|
car,
|
|
@@ -55,20 +67,6 @@ export function estimateTrackHit(
|
|
|
55
67
|
})
|
|
56
68
|
: null;
|
|
57
69
|
if (directionalAnalyticHit) return directionalAnalyticHit;
|
|
58
|
-
if (!usesSampledDriverContract(car, precision)) {
|
|
59
|
-
const indexedHit = estimateIndexedTrackHit({
|
|
60
|
-
car,
|
|
61
|
-
track: snapshot.track,
|
|
62
|
-
origin,
|
|
63
|
-
originState,
|
|
64
|
-
ray,
|
|
65
|
-
lengthMeters,
|
|
66
|
-
includePitLane,
|
|
67
|
-
precision,
|
|
68
|
-
sharedRayQuery,
|
|
69
|
-
});
|
|
70
|
-
if (indexedHit) return indexedHit;
|
|
71
|
-
}
|
|
72
70
|
const analyticHit = estimateAnalyticMainTrackHit({
|
|
73
71
|
car,
|
|
74
72
|
track: snapshot.track,
|
|
@@ -168,7 +166,6 @@ function canUseDirectionalAnalyticTrackHit({ car, track, originState, ray, inclu
|
|
|
168
166
|
|
|
169
167
|
function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthMeters, includePitLane, precision, sharedRayQuery }) {
|
|
170
168
|
if (includePitLane || (!usesMainTrackOnlyRays(car) && isNearPitConnector(track, originState))) return null;
|
|
171
|
-
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return null;
|
|
172
169
|
const trackHalfWidth = track.width / 2;
|
|
173
170
|
const inside = Math.abs(originState.signedOffset ?? 0) <= trackHalfWidth;
|
|
174
171
|
const boundaries = findIndexedTrackBandBoundaries(
|
|
@@ -181,21 +178,28 @@ function estimateIndexedTrackHit({ car, track, origin, originState, ray, lengthM
|
|
|
181
178
|
sharedRayQuery,
|
|
182
179
|
);
|
|
183
180
|
if (!boundaries.available) return null;
|
|
184
|
-
if (boundaries.trackEdgeDistance == null)
|
|
181
|
+
if (boundaries.trackEdgeDistance == null) {
|
|
182
|
+
if (usesMainTrackOnlyRays(car)) return createTrackMiss(lengthMeters);
|
|
183
|
+
return canSampleRecoveryTransition(track, originState, ray)
|
|
184
|
+
? null
|
|
185
|
+
: createTrackMiss(lengthMeters);
|
|
186
|
+
}
|
|
185
187
|
const kind = inside ? 'exit' : 'entry';
|
|
186
|
-
const hitDistance =
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
188
|
+
const hitDistance = usesMainTrackOnlyRays(car)
|
|
189
|
+
? boundaries.trackEdgeDistance
|
|
190
|
+
: validatedIndexedTransitionDistance({
|
|
191
|
+
track,
|
|
192
|
+
car,
|
|
193
|
+
origin,
|
|
194
|
+
ray,
|
|
195
|
+
progressHint: car.progress,
|
|
196
|
+
distance: boundaries.trackEdgeDistance,
|
|
197
|
+
maxDistance: metersToSimUnits(lengthMeters),
|
|
198
|
+
kind,
|
|
199
|
+
includePitLane,
|
|
200
|
+
precision,
|
|
201
|
+
originInside: inside,
|
|
202
|
+
});
|
|
199
203
|
if (hitDistance == null) return null;
|
|
200
204
|
return {
|
|
201
205
|
hit: true,
|
|
@@ -287,18 +291,11 @@ function refineTrackTransitionDistance(
|
|
|
287
291
|
}
|
|
288
292
|
|
|
289
293
|
function nearestRayTrackState(track, car, point, progressHint, precision = 'debug') {
|
|
290
|
-
const useBatchTrainingIndexMode = usesMainTrackOnlyRays(car);
|
|
291
294
|
return nearestTrackState(track, point, progressHint, {
|
|
292
295
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
293
|
-
indexMode: usesSampledDriverContract(car, precision) ? 'legacy' : useBatchTrainingIndexMode ? 'sample' : undefined,
|
|
294
|
-
hintMaxDistance: useBatchTrainingIndexMode ? Infinity : undefined,
|
|
295
296
|
});
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
function usesSampledDriverContract(car, precision) {
|
|
299
|
-
return precision === 'driver' && !usesMainTrackOnlyRays(car);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
299
|
function refineStepsForPrecision(precision) {
|
|
303
300
|
return precision === 'debug' ? TRACK_RAY_REFINE_STEPS : DRIVER_RAY_REFINE_STEPS;
|
|
304
301
|
}
|
|
@@ -308,6 +305,16 @@ function isInsideTrackBorder(state, track, includePitLane = true) {
|
|
|
308
305
|
return state.crossTrackError <= track.width / 2;
|
|
309
306
|
}
|
|
310
307
|
|
|
308
|
+
function canSampleRecoveryTransition(track, originState, ray) {
|
|
309
|
+
if (!canUseIndexedRecoveryRayApproximation(track, originState)) return false;
|
|
310
|
+
const trackHalfWidth = track.width / 2;
|
|
311
|
+
const offset = originState.signedOffset ?? 0;
|
|
312
|
+
if (Math.abs(offset) <= trackHalfWidth) return false;
|
|
313
|
+
const lateral = ray.x * originState.normalX + ray.y * originState.normalY;
|
|
314
|
+
if (Math.abs(lateral) < 0.05) return false;
|
|
315
|
+
return offset > 0 ? lateral < 0 : lateral > 0;
|
|
316
|
+
}
|
|
317
|
+
|
|
311
318
|
function validatedIndexedTransitionDistance({
|
|
312
319
|
track,
|
|
313
320
|
car,
|
|
@@ -323,19 +330,8 @@ function validatedIndexedTransitionDistance({
|
|
|
323
330
|
}) {
|
|
324
331
|
const step = metersToSimUnits(TRACK_RAY_STEP_METERS);
|
|
325
332
|
const refineSteps = refineStepsForPrecision(precision);
|
|
326
|
-
if (refineSteps <= 0 && usesMainTrackOnlyRays(car)) return distance;
|
|
327
333
|
if (refineSteps <= 0) {
|
|
328
|
-
return
|
|
329
|
-
track,
|
|
330
|
-
car,
|
|
331
|
-
origin,
|
|
332
|
-
ray,
|
|
333
|
-
progressHint,
|
|
334
|
-
distance,
|
|
335
|
-
maxDistance,
|
|
336
|
-
kind,
|
|
337
|
-
includePitLane,
|
|
338
|
-
});
|
|
334
|
+
return distance;
|
|
339
335
|
}
|
|
340
336
|
|
|
341
337
|
const windows = [step * 1.5, step * 8];
|
|
@@ -418,7 +414,6 @@ function refineValidatedTransitionDistance(track, car, origin, ray, progressHint
|
|
|
418
414
|
function validatedInsideTrackBorder(track, car, origin, ray, distance, progressHint, includePitLane) {
|
|
419
415
|
const state = nearestTrackState(track, pointOnRay(origin, ray, distance), progressHint, {
|
|
420
416
|
allowPitOverride: pitOverrideAllowedForCar(car),
|
|
421
|
-
indexMode: 'legacy',
|
|
422
417
|
});
|
|
423
418
|
return isInsideTrackBorder(state, track, includePitLane);
|
|
424
419
|
}
|