@inventure71/paddockjs 0.2.0 → 1.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/INSTALL_AND_UPDATE.md +165 -0
  3. package/README.md +217 -20
  4. package/docs/architecture.md +84 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +412 -20
  7. package/docs/data_model.md +2 -1
  8. package/docs/index.md +1 -0
  9. package/docs/learnings.md +1 -1
  10. package/docs/rules.md +140 -19
  11. package/docs/system_specs.md +74 -20
  12. package/docs/training.md +206 -0
  13. package/examples/train-basic-policy.mjs +276 -0
  14. package/examples/trainingData.mjs +133 -0
  15. package/package.json +18 -8
  16. package/src/api/PaddockSimulatorController.js +39 -4
  17. package/src/app/BrowserExpertAdapter.js +32 -0
  18. package/src/app/F1SimulatorApp.js +1251 -146
  19. package/src/app/domBindings.js +10 -2
  20. package/src/config/defaultOptions.js +11 -3
  21. package/src/data/championship.js +13 -0
  22. package/src/data/normalizeDrivers.js +7 -2
  23. package/src/environment/actions.js +68 -0
  24. package/src/environment/episode.js +17 -0
  25. package/src/environment/evaluation.js +184 -0
  26. package/src/environment/events.js +17 -0
  27. package/src/environment/index.d.ts +521 -0
  28. package/src/environment/index.js +15 -0
  29. package/src/environment/observationOptions.js +12 -0
  30. package/src/environment/observations.js +284 -0
  31. package/src/environment/options.js +157 -0
  32. package/src/environment/recorder.js +28 -0
  33. package/src/environment/rewards.js +69 -0
  34. package/src/environment/runtime.js +175 -0
  35. package/src/environment/scenarios.js +220 -0
  36. package/src/environment/sensors.js +367 -0
  37. package/src/environment/specs.js +163 -0
  38. package/src/environment/workerProtocol.js +52 -0
  39. package/src/index.d.ts +405 -6
  40. package/src/index.js +27 -2
  41. package/src/rendering/proceduralTrackAsset.js +343 -24
  42. package/src/simulation/collisionGeometry.js +214 -0
  43. package/src/simulation/driverController.js +431 -65
  44. package/src/simulation/raceSimulation.js +2140 -171
  45. package/src/simulation/rules/collisionSteward.js +69 -0
  46. package/src/simulation/rules/penaltyLedger.js +153 -0
  47. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  48. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  49. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  50. package/src/simulation/rulesConfig.js +352 -0
  51. package/src/simulation/trackModel.js +1154 -76
  52. package/src/simulation/units.js +6 -4
  53. package/src/simulation/vehicleGeometry.js +247 -0
  54. package/src/simulation/vehiclePhysics.js +82 -50
  55. package/src/simulation/wheelSurface.js +338 -0
  56. package/src/styles.css +369 -67
  57. package/src/ui/bannerTemplates.js +69 -0
  58. package/src/ui/componentTemplates.js +45 -81
  59. package/src/ui/raceControlStatusBanner.js +39 -0
  60. package/src/ui/shellTemplate.js +1 -1
  61. package/src/ui/templateUtils.js +23 -0
  62. package/AGENTS.md +0 -86
  63. package/src/standalone.js +0 -15
@@ -0,0 +1,521 @@
1
+ export type TireCompound = 'S' | 'M' | 'H';
2
+ export type PaddockPitIntent = 0 | 1 | 2;
3
+ export type PaddockScenarioPreset = 'cornering' | 'off-track-recovery' | 'overtaking-pack' | 'pit-entry';
4
+
5
+ export interface TeamData {
6
+ id?: string;
7
+ name?: string;
8
+ color?: string;
9
+ icon?: string;
10
+ pitCrew?: PaddockPitCrewStats;
11
+ pitCrewStats?: PaddockPitCrewStats;
12
+ }
13
+
14
+ export interface PaddockPitCrewStats {
15
+ speed?: number;
16
+ consistency?: number;
17
+ reliability?: number;
18
+ }
19
+
20
+ export interface SimulatorDriver {
21
+ id: string;
22
+ name: string;
23
+ color: string;
24
+ link?: string;
25
+ icon?: string;
26
+ code?: string;
27
+ timingName?: string;
28
+ tire?: TireCompound;
29
+ raceData?: string[];
30
+ customFields?: Array<{ label: string; value: string }> | Record<string, string>;
31
+ team?: TeamData | null;
32
+ driverNumber?: number;
33
+ }
34
+
35
+ export interface ChampionshipEntryBlueprint {
36
+ driverId: string;
37
+ driverNumber?: number;
38
+ timingName?: string;
39
+ driver?: unknown;
40
+ vehicle?: unknown;
41
+ team?: TeamData;
42
+ }
43
+
44
+ export interface RaceEvent {
45
+ type: string;
46
+ at?: number;
47
+ [key: string]: unknown;
48
+ }
49
+
50
+ export type PaddockRulesetName = 'paddock' | 'custom' | 'grandPrix2025' | 'fia2025';
51
+
52
+ export interface PaddockPenaltyEntry {
53
+ id: string;
54
+ type: string;
55
+ at: number;
56
+ lap: number;
57
+ driverId: string;
58
+ strictness: number;
59
+ status: PaddockPenaltyStatus;
60
+ penaltySeconds: number;
61
+ pendingPenaltySeconds?: number;
62
+ consequences: PaddockPenaltyConsequence[];
63
+ serviceType?: 'driveThrough' | 'stopGo' | null;
64
+ serviceRequired?: boolean;
65
+ serviceServedAt?: number | null;
66
+ appliedAt?: number;
67
+ cancelledAt?: number;
68
+ unserved?: boolean;
69
+ positionDrop?: number;
70
+ gridDrop?: number;
71
+ disqualified?: boolean;
72
+ reason?: string;
73
+ otherCarId?: string;
74
+ aheadDriverId?: string;
75
+ atFaultDriverId?: string;
76
+ sharedFault?: boolean;
77
+ impactSpeedKph?: number;
78
+ impactSpeedThresholdKph?: number;
79
+ [key: string]: unknown;
80
+ }
81
+
82
+ export type PaddockPenaltyConsequence =
83
+ | { type: 'warning' }
84
+ | { type: 'time'; seconds: number }
85
+ | { type: 'driveThrough'; conversionSeconds?: number }
86
+ | { type: 'stopGo'; seconds?: number; conversionSeconds?: number }
87
+ | { type: 'positionDrop'; positions: number }
88
+ | { type: 'gridDrop'; positions: number }
89
+ | { type: 'disqualification' };
90
+
91
+ export type PaddockPenaltyStatus = 'issued' | 'served' | 'applied' | 'cancelled';
92
+
93
+ export interface RaceSnapshot {
94
+ time: number;
95
+ totalLaps: number;
96
+ raceControl: {
97
+ mode: string;
98
+ finished: boolean;
99
+ [key: string]: unknown;
100
+ };
101
+ track: Record<string, unknown>;
102
+ cars: Array<Record<string, unknown>>;
103
+ events: RaceEvent[];
104
+ penalties: PaddockPenaltyEntry[];
105
+ [key: string]: unknown;
106
+ }
107
+
108
+ export interface PaddockAction {
109
+ steering: number;
110
+ throttle: number;
111
+ brake: number;
112
+ pitIntent?: PaddockPitIntent;
113
+ pitCompound?: TireCompound | string;
114
+ pitTargetCompound?: TireCompound | string;
115
+ }
116
+
117
+ export type PaddockActionMap = Record<string, PaddockAction>;
118
+
119
+ export interface PaddockRaceRules {
120
+ ruleset?: PaddockRulesetName;
121
+ profile?: PaddockRulesetName;
122
+ modules?: {
123
+ pitStops?: {
124
+ enabled?: boolean;
125
+ pitLaneSpeedLimitKph?: number;
126
+ defaultStopSeconds?: number;
127
+ variability?: {
128
+ enabled?: boolean;
129
+ perfect?: boolean;
130
+ speedImpactSeconds?: number;
131
+ consistencyJitterSeconds?: number;
132
+ issueChance?: number;
133
+ issueMaxDelaySeconds?: number;
134
+ };
135
+ doubleStacking?: boolean;
136
+ maxConcurrentPitLaneCars?: number;
137
+ minimumPitLaneGapMeters?: number;
138
+ tirePitRequestThresholdPercent?: number;
139
+ tirePitCommitThresholdPercent?: number;
140
+ };
141
+ tireStrategy?: {
142
+ enabled?: boolean;
143
+ compounds?: TireCompound[];
144
+ mandatoryDistinctDryCompounds?: number | null;
145
+ };
146
+ penalties?: {
147
+ enabled?: boolean;
148
+ stewardStrictness?: number;
149
+ trackLimits?: {
150
+ strictness?: number;
151
+ warningsBeforePenalty?: number;
152
+ relaxedMarginMeters?: number;
153
+ timePenaltySeconds?: number;
154
+ consequences?: PaddockPenaltyConsequence[];
155
+ };
156
+ collision?: {
157
+ strictness?: number;
158
+ timePenaltySeconds?: number;
159
+ consequences?: PaddockPenaltyConsequence[];
160
+ minimumSeverity?: number;
161
+ relaxedSeverityMargin?: number;
162
+ minimumImpactSpeedKph?: number;
163
+ relaxedImpactSpeedKph?: number;
164
+ };
165
+ tireRequirement?: {
166
+ strictness?: number;
167
+ timePenaltySeconds?: number;
168
+ consequences?: PaddockPenaltyConsequence[];
169
+ };
170
+ pitLaneSpeeding?: {
171
+ strictness?: number;
172
+ speedLimitKph?: number;
173
+ marginKph?: number;
174
+ relaxedMarginKph?: number;
175
+ timePenaltySeconds?: number;
176
+ consequences?: PaddockPenaltyConsequence[];
177
+ };
178
+ };
179
+ weather?: { enabled?: boolean };
180
+ reliability?: { enabled?: boolean };
181
+ fuelLoad?: { enabled?: boolean };
182
+ };
183
+ drsDetectionSeconds?: number;
184
+ safetyCarSpeed?: number;
185
+ safetyCarLeadDistance?: number;
186
+ safetyCarGap?: number;
187
+ collisionRestitution?: number;
188
+ standingStart?: boolean;
189
+ startLightCount?: number;
190
+ startLightInterval?: number;
191
+ startLightsOutHold?: number;
192
+ }
193
+
194
+ export interface PaddockSensorRayResult {
195
+ angleDegrees: number;
196
+ angleRadians: number;
197
+ lengthMeters: number;
198
+ track: {
199
+ hit: boolean;
200
+ distanceMeters: number;
201
+ kind: 'exit' | 'entry' | null;
202
+ };
203
+ car: {
204
+ hit: boolean;
205
+ distanceMeters: number;
206
+ driverId: string | null;
207
+ relativeSpeedKph: number;
208
+ };
209
+ }
210
+
211
+ export interface PaddockNearbyCarObservation {
212
+ id: string;
213
+ relativeForwardMeters: number;
214
+ relativeRightMeters: number;
215
+ relativeDistanceMeters: number;
216
+ relativeSpeedKph: number;
217
+ relativeHeadingRadians: number;
218
+ ahead: boolean;
219
+ sameLap: boolean;
220
+ }
221
+
222
+ export interface PaddockTrackLookaheadObservation {
223
+ distanceMeters: number;
224
+ curvature: number;
225
+ headingDeltaRadians: number;
226
+ }
227
+
228
+ export interface PaddockDriverObservationObject {
229
+ self: {
230
+ id: string;
231
+ speedKph: number;
232
+ speedMetersPerSecond: number;
233
+ headingRadians: number;
234
+ steeringAngleRadians: number;
235
+ throttle: number;
236
+ brake: number;
237
+ lap: number;
238
+ completedLaps: number;
239
+ lapProgressMeters: number;
240
+ trackOffsetMeters: number;
241
+ trackHeadingErrorRadians: number;
242
+ onTrack: boolean;
243
+ surface: string;
244
+ inPitLane: boolean;
245
+ pitLanePart: 'entry' | 'fast-lane' | 'working-lane' | 'exit' | 'service-box' | 'garage-box' | null;
246
+ pitBoxId: string | null;
247
+ tireEnergy: number | null;
248
+ pitIntent: PaddockPitIntent;
249
+ pitTargetCompound: TireCompound | string | null;
250
+ pitStopStatus: 'pending' | 'entering' | 'queued' | 'servicing' | 'exiting' | 'completed' | null;
251
+ pitStopPhase: 'entry' | 'queue' | 'queue-release' | 'penalty' | 'service' | 'exit' | null;
252
+ pitStopServiceRemainingSeconds: number | null;
253
+ pitStopPenaltyServiceRemainingSeconds: number | null;
254
+ pitStopsCompleted: number;
255
+ };
256
+ race: {
257
+ position: number;
258
+ totalCars: number;
259
+ raceMode: string;
260
+ pitLaneOpen: boolean;
261
+ redFlag: boolean;
262
+ totalLaps: number;
263
+ };
264
+ track: {
265
+ lengthMeters: number;
266
+ widthMeters: number;
267
+ curvature: number;
268
+ lookahead: PaddockTrackLookaheadObservation[];
269
+ };
270
+ rays: PaddockSensorRayResult[];
271
+ nearbyCars: PaddockNearbyCarObservation[];
272
+ events: RaceEvent[];
273
+ }
274
+
275
+ export interface PaddockObservationSchemaEntry {
276
+ name: string;
277
+ unit?: string;
278
+ scale?: string;
279
+ }
280
+
281
+ export interface PaddockDriverObservation {
282
+ object: PaddockDriverObservationObject;
283
+ vector: number[];
284
+ schema: PaddockObservationSchemaEntry[];
285
+ events: RaceEvent[];
286
+ }
287
+
288
+ export interface PaddockEnvironmentOptions {
289
+ drivers: SimulatorDriver[];
290
+ entries?: ChampionshipEntryBlueprint[];
291
+ controlledDrivers: string[];
292
+ seed?: number;
293
+ trackSeed?: number;
294
+ totalLaps?: number;
295
+ frameSkip?: number;
296
+ rules?: PaddockRaceRules;
297
+ actionPolicy?: 'strict' | 'report';
298
+ scenario?: {
299
+ participants?: 'all' | 'controlled-only' | string[];
300
+ nonControlled?: 'ai';
301
+ preset?: PaddockScenarioPreset;
302
+ placements?: Record<string, PaddockScenarioPlacement>;
303
+ traffic?: PaddockScenarioTrafficPlacement[];
304
+ };
305
+ sensors?: {
306
+ rays?: {
307
+ enabled?: boolean;
308
+ anglesDegrees?: number[];
309
+ lengthMeters?: number;
310
+ detectTrack?: boolean;
311
+ detectCars?: boolean;
312
+ };
313
+ nearbyCars?: {
314
+ enabled?: boolean;
315
+ maxCars?: number;
316
+ radiusMeters?: number;
317
+ };
318
+ };
319
+ sensorsByDriver?: Record<string, PaddockEnvironmentOptions['sensors']>;
320
+ observation?: {
321
+ lookaheadMeters?: number[];
322
+ };
323
+ episode?: {
324
+ maxSteps?: number;
325
+ endOnRaceFinish?: boolean;
326
+ };
327
+ reward?: (payload: PaddockRewardContext) => number;
328
+ }
329
+
330
+ export interface PaddockScenarioPlacement {
331
+ distanceMeters?: number;
332
+ startDistanceMeters?: number;
333
+ offsetMeters?: number;
334
+ speedKph?: number;
335
+ headingErrorRadians?: number;
336
+ }
337
+
338
+ export interface PaddockScenarioTrafficPlacement {
339
+ driverId: string;
340
+ relativeTo: string;
341
+ deltaDistanceMeters?: number;
342
+ offsetMeters?: number;
343
+ speedKph?: number;
344
+ headingErrorRadians?: number;
345
+ }
346
+
347
+ export interface PaddockRewardContext {
348
+ driverId: string;
349
+ previous: RaceSnapshot | null;
350
+ current: PaddockDriverObservation;
351
+ action: PaddockAction | undefined;
352
+ events: RaceEvent[];
353
+ state: { snapshot: RaceSnapshot };
354
+ }
355
+
356
+ export interface PaddockProgressRewardWeights {
357
+ progress: number;
358
+ speed: number;
359
+ offTrack: number;
360
+ collision: number;
361
+ steering: number;
362
+ brake: number;
363
+ }
364
+
365
+ export interface PaddockProgressRewardOptions {
366
+ weights?: Partial<PaddockProgressRewardWeights>;
367
+ }
368
+
369
+ export interface PaddockActionSpec {
370
+ version: 1;
371
+ controlledDrivers: string[];
372
+ action: {
373
+ type: 'continuous';
374
+ perDriver: {
375
+ steering: { min: -1; max: 1; unit: 'normalized' };
376
+ throttle: { min: 0; max: 1; unit: 'normalized' };
377
+ brake: { min: 0; max: 1; unit: 'normalized' };
378
+ pitIntent: { values: [0, 1, 2]; unit: 'request'; optional: true };
379
+ pitCompound: { values: string[]; unit: 'compound'; optional: true };
380
+ };
381
+ };
382
+ }
383
+
384
+ export interface PaddockObservationSpec {
385
+ version: 2;
386
+ controlledDrivers: string[];
387
+ object: Record<string, unknown>;
388
+ vector: {
389
+ schema: PaddockObservationSchemaEntry[];
390
+ };
391
+ }
392
+
393
+ export interface PaddockRolloutTransition {
394
+ observation: Record<string, PaddockDriverObservation>;
395
+ action: PaddockActionMap;
396
+ reward: null | Record<string, number>;
397
+ nextObservation: Record<string, PaddockDriverObservation>;
398
+ terminated: boolean;
399
+ truncated: boolean;
400
+ info: PaddockEnvironmentResult['info'];
401
+ }
402
+
403
+ export interface PaddockRolloutRecorder {
404
+ recordStep(
405
+ previousResult: PaddockEnvironmentResult,
406
+ action: PaddockActionMap,
407
+ nextResult: PaddockEnvironmentResult
408
+ ): PaddockRolloutTransition;
409
+ clear(): void;
410
+ toJSON(): PaddockRolloutTransition[];
411
+ }
412
+
413
+ export interface PaddockEvaluationCase {
414
+ name: string;
415
+ seed?: number;
416
+ trackSeed?: number;
417
+ maxSteps?: number;
418
+ scenario?: PaddockEnvironmentOptions['scenario'];
419
+ }
420
+
421
+ export interface PaddockEvaluationDriverMetrics {
422
+ distanceMeters: number;
423
+ lapProgressMeters: number;
424
+ offTrackSteps: number;
425
+ contactCount: number;
426
+ recoverySuccess: boolean;
427
+ passCount: number;
428
+ lapTimeSeconds: number | null;
429
+ }
430
+
431
+ export interface PaddockEvaluationCaseReport {
432
+ name: string;
433
+ seed?: number;
434
+ trackSeed?: number;
435
+ steps: number;
436
+ done: boolean;
437
+ endReason: string | null;
438
+ metrics: Record<string, PaddockEvaluationDriverMetrics>;
439
+ }
440
+
441
+ export interface PaddockEvaluationReport {
442
+ cases: PaddockEvaluationCaseReport[];
443
+ }
444
+
445
+ export type PaddockPolicyLike =
446
+ | ((observation: PaddockDriverObservation, context: Record<string, unknown>) => PaddockAction)
447
+ | { predict(observation: PaddockDriverObservation, context: Record<string, unknown>): PaddockAction };
448
+
449
+ export interface PaddockEnvironmentWorkerMessage {
450
+ id?: string | number | null;
451
+ type: 'reset' | 'step' | 'getActionSpec' | 'getObservationSpec' | 'getObservation' | 'getState' | 'destroy' | string;
452
+ options?: Partial<PaddockEnvironmentOptions>;
453
+ actions?: PaddockActionMap;
454
+ }
455
+
456
+ export interface PaddockEnvironmentWorkerResponse {
457
+ id: string | number | null;
458
+ ok: boolean;
459
+ type: string;
460
+ result?: unknown;
461
+ error?: string;
462
+ }
463
+
464
+ export interface PaddockEnvironmentWorkerProtocol {
465
+ handle(message: PaddockEnvironmentWorkerMessage): PaddockEnvironmentWorkerResponse;
466
+ }
467
+
468
+ export interface PaddockEnvironmentResult {
469
+ observation: Record<string, PaddockDriverObservation>;
470
+ reward: null | Record<string, number>;
471
+ terminated: boolean;
472
+ truncated: boolean;
473
+ done: boolean;
474
+ events: RaceEvent[];
475
+ state: { snapshot: RaceSnapshot };
476
+ info: {
477
+ step: number;
478
+ elapsedSeconds: number;
479
+ seed: number;
480
+ trackSeed: number;
481
+ controlledDrivers: string[];
482
+ actionErrors: string[];
483
+ endReason: string | null;
484
+ };
485
+ }
486
+
487
+ export interface PaddockEnvironment {
488
+ reset(options?: Partial<PaddockEnvironmentOptions>): PaddockEnvironmentResult;
489
+ step(actions: PaddockActionMap): PaddockEnvironmentResult;
490
+ getObservation(): PaddockEnvironmentResult['observation'];
491
+ getState(): PaddockEnvironmentResult['state'];
492
+ getActionSpec(): PaddockActionSpec;
493
+ getObservationSpec(): PaddockObservationSpec;
494
+ destroy(): void;
495
+ }
496
+
497
+ export function createPaddockEnvironment(options: PaddockEnvironmentOptions): PaddockEnvironment;
498
+ export function createProgressReward(options?: PaddockProgressRewardOptions): (context: PaddockRewardContext) => number;
499
+ export function createRolloutRecorder(): PaddockRolloutRecorder;
500
+ export function createRolloutTransition(
501
+ previousResult: PaddockEnvironmentResult,
502
+ action: PaddockActionMap,
503
+ nextResult: PaddockEnvironmentResult
504
+ ): PaddockRolloutTransition;
505
+ export const ENVIRONMENT_SCENARIO_PRESETS: readonly PaddockScenarioPreset[];
506
+ export const DEFAULT_EVALUATION_CASES: readonly PaddockEvaluationCase[];
507
+ export function createEvaluationTracker(initialResult: PaddockEnvironmentResult): {
508
+ update(result: PaddockEnvironmentResult): void;
509
+ finish(): Record<string, PaddockEvaluationDriverMetrics>;
510
+ };
511
+ export function runEnvironmentEvaluation(options: {
512
+ baseOptions: PaddockEnvironmentOptions;
513
+ policy: PaddockPolicyLike;
514
+ cases?: PaddockEvaluationCase[];
515
+ createEnvironment?: (options: PaddockEnvironmentOptions) => PaddockEnvironment;
516
+ }): PaddockEvaluationReport;
517
+ export function createEnvironmentWorkerProtocol(env: PaddockEnvironment): PaddockEnvironmentWorkerProtocol;
518
+ export function handleEnvironmentMessage(
519
+ env: PaddockEnvironment,
520
+ message: PaddockEnvironmentWorkerMessage
521
+ ): PaddockEnvironmentWorkerResponse;
@@ -0,0 +1,15 @@
1
+ export { createPaddockEnvironment } from './runtime.js';
2
+ export { createProgressReward } from './rewards.js';
3
+ export { createRolloutRecorder, createRolloutTransition } from './recorder.js';
4
+ export {
5
+ DEFAULT_EVALUATION_CASES,
6
+ createEvaluationTracker,
7
+ runEnvironmentEvaluation,
8
+ } from './evaluation.js';
9
+ export {
10
+ ENVIRONMENT_SCENARIO_PRESETS,
11
+ } from './scenarios.js';
12
+ export {
13
+ createEnvironmentWorkerProtocol,
14
+ handleEnvironmentMessage,
15
+ } from './workerProtocol.js';
@@ -0,0 +1,12 @@
1
+ export const DEFAULT_VECTOR_LOOKAHEAD_METERS = Object.freeze([20, 50, 100, 150]);
2
+
3
+ export function normalizeLookaheadMeters(value) {
4
+ if (value == null) return [...DEFAULT_VECTOR_LOOKAHEAD_METERS];
5
+ if (!Array.isArray(value)) return [...DEFAULT_VECTOR_LOOKAHEAD_METERS];
6
+
7
+ const distances = value
8
+ .map((item) => Number(item))
9
+ .filter((item) => Number.isFinite(item));
10
+
11
+ return distances.length ? distances : [...DEFAULT_VECTOR_LOOKAHEAD_METERS];
12
+ }