@inventure71/paddockjs 1.0.0 → 2.0.1
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 +58 -0
- package/INSTALL_AND_UPDATE.md +4 -3
- package/README.md +219 -12
- package/docs/architecture.md +126 -45
- package/docs/concepts.md +9 -5
- package/docs/custom_model_controller.md +329 -0
- package/docs/data_contract.md +317 -22
- package/docs/data_model.md +1 -1
- package/docs/index.md +10 -0
- package/docs/learnings.md +22 -0
- package/docs/rules.md +71 -14
- package/docs/sense_contract.md +59 -0
- package/docs/system_specs.md +75 -13
- package/docs/training.md +280 -32
- package/examples/python/README.md +62 -0
- package/examples/python/base_policy_server.py +191 -0
- package/examples/python/requirements.txt +2 -0
- package/package.json +12 -4
- package/src/api/PaddockSimulatorController.js +6 -33
- package/src/app/BrowserExpertAdapter.js +76 -9
- package/src/app/BrowserExpertExternalRenderer.js +303 -0
- package/src/app/F1SimulatorApp.js +291 -1630
- package/src/app/banners/bannerFormatters.js +55 -0
- package/src/app/banners/raceDataBannerController.js +141 -0
- package/src/app/banners/stewardMessageController.js +99 -0
- package/src/app/camera/cameraConstants.js +21 -0
- package/src/app/camera/cameraController.js +265 -0
- package/src/app/camera/cameraGeometry.js +176 -0
- package/src/app/domBindings.js +4 -0
- package/src/app/readouts/carOverviewRenderer.js +109 -0
- package/src/app/readouts/controlStateRenderer.js +37 -0
- package/src/app/readouts/raceStatusRenderer.js +133 -0
- package/src/app/readouts/readoutFormatters.js +58 -0
- package/src/app/readouts/telemetryRenderer.js +113 -0
- package/src/app/readouts/timingTowerRenderer.js +177 -0
- package/src/app/rendering/appAssets.js +38 -0
- package/src/app/rendering/carRenderer.js +286 -0
- package/src/app/rendering/displayUtils.js +22 -0
- package/src/app/rendering/drsTrailRenderer.js +90 -0
- package/src/app/rendering/expertSensorRenderer.js +126 -0
- package/src/app/rendering/pitLaneStatusRenderer.js +50 -0
- package/src/app/rendering/replayGhostRenderer.js +124 -0
- package/src/app/rendering/trackRenderer.js +25 -0
- package/src/app/runtime/frameLoop.js +66 -0
- package/src/app/runtime/runtimeTiming.js +34 -0
- package/src/app/runtime/runtimeVisibility.js +45 -0
- package/src/config/defaultOptions.js +20 -0
- package/src/config/restartOptions.js +34 -0
- package/src/environment/actions.js +13 -5
- package/src/environment/controllerLoop.js +261 -0
- package/src/environment/episode.js +98 -2
- package/src/environment/evaluation.js +54 -32
- package/src/environment/index.d.ts +376 -10
- package/src/environment/index.js +1 -0
- package/src/environment/metrics.js +159 -0
- package/src/environment/observationOptions.js +22 -1
- package/src/environment/observationVector.js +200 -0
- package/src/environment/observations.js +199 -141
- package/src/environment/options.js +37 -5
- package/src/environment/recorder.js +13 -6
- package/src/environment/rewards.js +13 -2
- package/src/environment/runtime.js +272 -23
- package/src/environment/scenarios.js +57 -9
- package/src/environment/sensors/bodySenses.js +23 -0
- package/src/environment/sensors/boundarySenses.js +17 -0
- package/src/environment/sensors/carRays.js +75 -0
- package/src/environment/sensors/contactSenses.js +32 -0
- package/src/environment/sensors/index.js +157 -0
- package/src/environment/sensors/indexedRayBands.js +103 -0
- package/src/environment/sensors/nearbyCars.js +52 -0
- package/src/environment/sensors/opponentRadar.js +75 -0
- package/src/environment/sensors/rayConfig.js +110 -0
- package/src/environment/sensors/rayDefaults.js +6 -0
- package/src/environment/sensors/rayGeometry.js +60 -0
- package/src/environment/sensors/rayGuards.js +19 -0
- package/src/environment/sensors/sensorTargets.js +66 -0
- package/src/environment/sensors/surfaceRays.js +360 -0
- package/src/environment/sensors/trackRays.js +424 -0
- package/src/environment/sensors.js +1 -367
- package/src/environment/specs.js +159 -26
- package/src/environment/workerProtocol.js +13 -1
- package/src/index.d.ts +171 -6
- package/src/index.js +6 -32
- package/src/rendering/proceduralTrackAsset.js +20 -823
- package/src/rendering/renderSnapshot.js +38 -12
- package/src/rendering/track/finishLineRenderer.js +34 -0
- package/src/rendering/track/gridRenderer.js +47 -0
- package/src/rendering/track/offsetStrokeSafety.js +136 -0
- package/src/rendering/track/pitLaneRenderer.js +248 -0
- package/src/rendering/track/trackMaterialRenderer.js +326 -0
- package/src/rendering/track/trackRenderConstants.js +38 -0
- package/src/rendering/track/trackRenderGeometry.js +94 -0
- package/src/simulation/collisionGeometry.js +1 -1
- package/src/simulation/driver/driverControlConstants.js +28 -0
- package/src/simulation/driver/driverController.js +47 -0
- package/src/simulation/driver/driverInput.js +28 -0
- package/src/simulation/driver/driverMath.js +6 -0
- package/src/simulation/driver/driverPersonality.js +25 -0
- package/src/simulation/driver/edgeRecovery.js +48 -0
- package/src/simulation/driver/racingControls.js +399 -0
- package/src/simulation/driver/racingLinePlan.js +166 -0
- package/src/simulation/driver/recoveryDynamics.js +38 -0
- package/src/simulation/driver/rejoinControls.js +213 -0
- package/src/simulation/driver/safetyCarControls.js +35 -0
- package/src/simulation/driver/trafficScan.js +51 -0
- package/src/simulation/driverController.js +4 -619
- package/src/simulation/participants/participantInteractions.js +120 -0
- package/src/simulation/pit/pitFlow.js +239 -0
- package/src/simulation/pit/pitIntent.js +92 -0
- package/src/simulation/pit/pitKinematics.js +24 -0
- package/src/simulation/pit/pitOccupancy.js +47 -0
- package/src/simulation/pit/pitPenaltyService.js +71 -0
- package/src/simulation/pit/pitQueue.js +60 -0
- package/src/simulation/pit/pitRouteMovement.js +167 -0
- package/src/simulation/pit/pitRouting.js +319 -0
- package/src/simulation/pit/pitService.js +43 -0
- package/src/simulation/pit/pitServiceConstants.js +21 -0
- package/src/simulation/pit/pitServiceProfile.js +45 -0
- package/src/simulation/pit/pitSnapshots.js +61 -0
- package/src/simulation/pit/pitState.js +113 -0
- package/src/simulation/pit/pitTireService.js +94 -0
- package/src/simulation/race/classification.js +104 -0
- package/src/simulation/race/gridPenalties.js +37 -0
- package/src/simulation/race/raceControlState.js +38 -0
- package/src/simulation/race/raceDistance.js +32 -0
- package/src/simulation/race/raceFinish.js +65 -0
- package/src/simulation/race/raceLifecycle.js +67 -0
- package/src/simulation/race/raceOrder.js +113 -0
- package/src/simulation/race/raceProgress.js +129 -0
- package/src/simulation/race/raceSetup.js +93 -0
- package/src/simulation/race/raceStep.js +80 -0
- package/src/simulation/race/redFlag.js +15 -0
- package/src/simulation/race/retirements.js +44 -0
- package/src/simulation/race/safetyCar.js +92 -0
- package/src/simulation/race/stalledDnf.js +77 -0
- package/src/simulation/raceSimulation.js +231 -2817
- package/src/simulation/replay/replayGhosts.js +128 -0
- package/src/simulation/rules/moduleConfig.js +72 -0
- package/src/simulation/rules/penaltyConfig.js +99 -0
- package/src/simulation/rules/penaltyEvents.js +18 -0
- package/src/simulation/rules/penaltyStats.js +26 -0
- package/src/simulation/rules/ruleConfigMerge.js +41 -0
- package/src/simulation/rules/ruleDefaults.js +85 -0
- package/src/simulation/rules/rulesReview.js +144 -0
- package/src/simulation/rules/rulesetPresets.js +47 -0
- package/src/simulation/rules/trackLimitsSteward.js +1 -1
- package/src/simulation/rulesConfig.js +5 -323
- package/src/simulation/snapshots/raceSnapshots.js +193 -0
- package/src/simulation/timing/drsTiming.js +87 -0
- package/src/simulation/timing/gapEstimation.js +38 -0
- package/src/simulation/timing/lapTelemetry.js +182 -0
- package/src/simulation/timing/raceTiming.js +15 -0
- package/src/simulation/timing/sectorPerformance.js +60 -0
- package/src/simulation/timing/timingConstants.js +5 -0
- package/src/simulation/timing/timingHistory.js +46 -0
- package/src/simulation/timing/timingLines.js +79 -0
- package/src/simulation/track/centerlineSpline.js +59 -0
- package/src/simulation/track/drsZones.js +67 -0
- package/src/simulation/track/pitLaneAccess.js +103 -0
- package/src/simulation/track/pitLaneLayout.js +395 -0
- package/src/simulation/track/pitLaneState.js +160 -0
- package/src/simulation/track/proceduralCenterline.js +237 -0
- package/src/simulation/track/sampleGeometry.js +49 -0
- package/src/simulation/track/spatialQueries.js +154 -0
- package/src/simulation/track/startStraight.js +141 -0
- package/src/simulation/track/trackConstants.js +109 -0
- package/src/simulation/track/trackGenerationOptions.js +283 -0
- package/src/simulation/track/trackMath.js +241 -0
- package/src/simulation/track/trackModel.js +240 -0
- package/src/simulation/track/trackQueryGrid.js +94 -0
- package/src/simulation/track/trackQueryIndex.js +659 -0
- package/src/simulation/track/trackQueryPitIndex.js +140 -0
- package/src/simulation/track/trackQueryScratch.js +46 -0
- package/src/simulation/track/trackQueryStats.js +28 -0
- package/src/simulation/track/trackSectors.js +19 -0
- package/src/simulation/track/trackStatePolicy.js +24 -0
- package/src/simulation/track/trackValidation.js +98 -0
- package/src/simulation/trackModel.js +1 -1706
- package/src/simulation/vehicle/contactResolution.js +181 -0
- package/src/simulation/vehicle/mainTrackWheelSurface.js +82 -0
- package/src/simulation/vehicle/pitWheelSurface.js +69 -0
- package/src/simulation/vehicle/runoffResponse.js +87 -0
- package/src/simulation/vehicle/surfacePriority.js +28 -0
- package/src/simulation/vehicle/vehicleGeometry.js +247 -0
- package/src/simulation/vehicle/vehicleKinematics.js +37 -0
- package/src/simulation/vehicle/vehiclePhysics.js +364 -0
- package/src/simulation/vehicle/vehicleSnapshots.js +327 -0
- package/src/simulation/vehicle/vehicleState.js +220 -0
- package/src/simulation/vehicle/wheelSurface.js +152 -0
- package/src/simulation/vehicleGeometry.js +1 -247
- package/src/simulation/vehiclePhysics.js +1 -136
- package/src/simulation/warmup/runtimeWarmup.js +224 -0
- package/src/simulation/wheelSurface.js +1 -338
- package/src/styles.css +42 -0
- package/src/ui/cameraControlsTemplate.js +20 -0
- package/src/ui/carOverviewTemplate.js +33 -0
- package/src/ui/componentTemplates.js +14 -360
- package/src/ui/raceCanvasTemplate.js +69 -0
- package/src/ui/raceControlsTemplate.js +36 -0
- package/src/ui/shellTemplate.js +5 -4
- package/src/ui/telemetryDrawerTemplate.js +55 -0
- package/src/ui/telemetryTemplates.js +136 -0
- package/src/ui/timingTowerTemplate.js +34 -0
|
@@ -1,619 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const LANE_OFFSETS = [-8, -5.6, -3.2, 0, 3.2, 5.6, 8].map(metersToSimUnits);
|
|
7
|
-
const EDGE_RECOVERY_MIN_LOOKAHEAD = metersToSimUnits(28);
|
|
8
|
-
const LOOKAHEAD_BASE_DISTANCE = metersToSimUnits(52);
|
|
9
|
-
const LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(115);
|
|
10
|
-
const BRAKING_LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(560);
|
|
11
|
-
const TRAFFIC_GAP_AHEAD = metersToSimUnits(58);
|
|
12
|
-
const TRAFFIC_SIDE_GAP = metersToSimUnits(4.1);
|
|
13
|
-
const TRAFFIC_SIDE_OVERLAP = metersToSimUnits(4.6);
|
|
14
|
-
const TRAFFIC_REAR_WINDOW = metersToSimUnits(42);
|
|
15
|
-
const OVERTAKE_GAP_BASE = metersToSimUnits(26);
|
|
16
|
-
const OVERTAKE_GAP_AGGRESSION = metersToSimUnits(28);
|
|
17
|
-
const OVERTAKE_LATERAL_MIN = metersToSimUnits(2.4);
|
|
18
|
-
const OVERTAKE_LATERAL_REWARD = metersToSimUnits(1.35);
|
|
19
|
-
const PASS_SIDE_BASE = metersToSimUnits(3.4);
|
|
20
|
-
const PASS_SIDE_AGGRESSION = metersToSimUnits(4.6);
|
|
21
|
-
const DEFEND_REAR_GAP = metersToSimUnits(46);
|
|
22
|
-
const DEFEND_MIN_OFFSET = metersToSimUnits(2.4);
|
|
23
|
-
const LANE_EDGE_CLEARANCE_TARGET = metersToSimUnits(0.75);
|
|
24
|
-
const REJOIN_LOOKAHEAD_BASE = metersToSimUnits(56);
|
|
25
|
-
const REJOIN_LOOKAHEAD_MAX = metersToSimUnits(105);
|
|
26
|
-
const SAFETY_CAR_LOOKAHEAD_MIN = metersToSimUnits(24);
|
|
27
|
-
const SAFETY_CAR_LOOKAHEAD_MAX = metersToSimUnits(48);
|
|
28
|
-
const CURVATURE_LOOKAHEAD_SAMPLES = 5;
|
|
29
|
-
const PREVIEW_CURVATURE_CAP = 0.00115;
|
|
30
|
-
const REJOIN_HOLD_FRAMES = 150;
|
|
31
|
-
const OVERTAKE_COMMIT_FRAMES = 150;
|
|
32
|
-
|
|
33
|
-
function angleToPoint(car, target) {
|
|
34
|
-
const angle = Math.atan2(target.y - car.y, target.x - car.x);
|
|
35
|
-
return normalizeAngle(angle - car.heading);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function buildDriverPersonality(driver, index, racecraft, random) {
|
|
39
|
-
const numberSeed = Number(driver.driverNumber ?? index + 1);
|
|
40
|
-
const numberBias = (((Number.isFinite(numberSeed) ? numberSeed : index + 1) % 11) - 5) * 0.014;
|
|
41
|
-
const baseAggression = clamp(
|
|
42
|
-
driver.personality?.aggression ?? 0.36 + racecraft * 0.34 + numberBias + seededRange(random, -0.045, 0.045),
|
|
43
|
-
0.18,
|
|
44
|
-
0.88,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
baseAggression,
|
|
49
|
-
riskTolerance: clamp(
|
|
50
|
-
driver.personality?.riskTolerance ?? baseAggression * 0.72 + racecraft * 0.24 + seededRange(random, -0.04, 0.04),
|
|
51
|
-
0.12,
|
|
52
|
-
0.95,
|
|
53
|
-
),
|
|
54
|
-
patience: clamp(
|
|
55
|
-
driver.personality?.patience ?? 0.72 - baseAggression * 0.36 + racecraft * 0.12 + seededRange(random, -0.05, 0.05),
|
|
56
|
-
0.18,
|
|
57
|
-
0.9,
|
|
58
|
-
),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function createDriverInput() {
|
|
63
|
-
const state = {
|
|
64
|
-
steering: 0,
|
|
65
|
-
throttle: 0,
|
|
66
|
-
brake: 0,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
steer(amount) {
|
|
71
|
-
state.steering = clamp(amount, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
|
|
72
|
-
return this;
|
|
73
|
-
},
|
|
74
|
-
accelerate(amount) {
|
|
75
|
-
state.throttle = clamp(amount, 0, 1);
|
|
76
|
-
return this;
|
|
77
|
-
},
|
|
78
|
-
brake(amount) {
|
|
79
|
-
state.brake = clamp(amount, 0, 1);
|
|
80
|
-
return this;
|
|
81
|
-
},
|
|
82
|
-
controls() {
|
|
83
|
-
return { ...state };
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function decideDriverControls({ car, orderIndex, race }) {
|
|
89
|
-
if (car.manualControls) return car.manualControls;
|
|
90
|
-
|
|
91
|
-
if (car.gridLocked) {
|
|
92
|
-
return createDriverInput().brake(1).controls();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (race.safetyCar.deployed) {
|
|
96
|
-
return decideSafetyCarControls(car, orderIndex, race);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!car.trackState.onTrack) {
|
|
100
|
-
car.rejoinRecoveryFrames = REJOIN_HOLD_FRAMES;
|
|
101
|
-
car.attackCommitmentFrames = 0;
|
|
102
|
-
return decideRejoinControls(car, race);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (shouldContinueRejoinRecovery(car, race)) {
|
|
106
|
-
return decideRejoinControls(car, race);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return decideRacingControls(car, orderIndex, race);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function decideRacingControls(car, orderIndex, race) {
|
|
113
|
-
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
114
|
-
const edgeGuard = calculateTrackEdgeGuard(car, race);
|
|
115
|
-
const baseLookahead = clamp(
|
|
116
|
-
car.speed * (1.62 - aggression * 0.12) + LOOKAHEAD_BASE_DISTANCE - edgeGuard.pressure * metersToSimUnits(18),
|
|
117
|
-
EDGE_RECOVERY_MIN_LOOKAHEAD,
|
|
118
|
-
LOOKAHEAD_MAX_DISTANCE,
|
|
119
|
-
);
|
|
120
|
-
const previewLookahead = clamp(
|
|
121
|
-
car.speed * 3.15 + LOOKAHEAD_BASE_DISTANCE,
|
|
122
|
-
baseLookahead,
|
|
123
|
-
BRAKING_LOOKAHEAD_MAX_DISTANCE,
|
|
124
|
-
);
|
|
125
|
-
const previewCurvature = maxLookaheadCurvature(race.track, car.progress, previewLookahead);
|
|
126
|
-
const cornerPressure = clamp(previewCurvature / 0.001, 0, 1);
|
|
127
|
-
const lookahead = clamp(
|
|
128
|
-
baseLookahead * (1 - cornerPressure * 0.46),
|
|
129
|
-
EDGE_RECOVERY_MIN_LOOKAHEAD,
|
|
130
|
-
LOOKAHEAD_MAX_DISTANCE,
|
|
131
|
-
);
|
|
132
|
-
const targetBase = pointAt(race.track, car.progress + lookahead);
|
|
133
|
-
const curvature = Math.max(
|
|
134
|
-
car.trackState.curvature,
|
|
135
|
-
targetBase.curvature,
|
|
136
|
-
previewCurvature,
|
|
137
|
-
);
|
|
138
|
-
const lanePlan = planRacingLine(car, orderIndex, race);
|
|
139
|
-
const cornerOffsetDamping = clamp(curvature / 0.0012, 0, 0.58);
|
|
140
|
-
const recoveryOffset = edgeGuard.side * edgeGuard.recoveryOffset;
|
|
141
|
-
const racingOffset = calculateRacingLineOffset(car, race, lookahead, curvature, edgeGuard);
|
|
142
|
-
const trafficPressure = lanePlan.attackCommitted || lanePlan.sameLaneAhead || lanePlan.actualLaneAhead || lanePlan.sideRisk ? 1 : 0;
|
|
143
|
-
const racingLineWeight = lanePlan.attackCommitted ? 0.18 : trafficPressure ? 0.52 : 1;
|
|
144
|
-
const cornerOffsetFactor = 1 - cornerOffsetDamping * (lanePlan.attackCommitted ? 0.28 : trafficPressure ? 0.58 : 1);
|
|
145
|
-
const targetOffset = clamp(
|
|
146
|
-
(lanePlan.offset + racingOffset * racingLineWeight) * cornerOffsetFactor * (1 - edgeGuard.pressure * 0.8) +
|
|
147
|
-
recoveryOffset * edgeGuard.pressure * 0.8,
|
|
148
|
-
-edgeGuard.recoveryOffset,
|
|
149
|
-
edgeGuard.recoveryOffset,
|
|
150
|
-
);
|
|
151
|
-
const target = offsetTrackPoint(targetBase, targetOffset);
|
|
152
|
-
const angleError = angleToPoint(car, target);
|
|
153
|
-
const headingError = normalizeAngle(targetBase.heading - car.heading);
|
|
154
|
-
const lateralError = car.trackState?.signedOffset ?? 0;
|
|
155
|
-
const cornerCommitment = clamp(Math.abs(headingError) / 0.9, 0, 0.72);
|
|
156
|
-
const lateralCorrection = -Math.atan2(
|
|
157
|
-
lateralError * (0.85 + edgeGuard.pressure * 0.5),
|
|
158
|
-
Math.max(car.speed, kphToSimSpeed(35)),
|
|
159
|
-
) * (1 - cornerCommitment * 0.62);
|
|
160
|
-
const headingFeedForward = clamp(
|
|
161
|
-
headingError * (0.34 + car.racecraft * 0.18 + edgeGuard.pressure * 0.12),
|
|
162
|
-
-0.42,
|
|
163
|
-
0.42,
|
|
164
|
-
);
|
|
165
|
-
let pathSteer = angleError * (0.72 + car.racecraft * 0.12 + edgeGuard.pressure * 0.42) + headingFeedForward;
|
|
166
|
-
if (edgeGuard.side !== 0 && Math.sign(pathSteer) === edgeGuard.side) {
|
|
167
|
-
pathSteer *= 1 - edgeGuard.pressure * 0.85;
|
|
168
|
-
}
|
|
169
|
-
const edgeRecoverySteer = -edgeGuard.side * clamp(
|
|
170
|
-
(edgeGuard.pressure - 0.22) * 1.18 + edgeGuard.overLimitPressure * 0.55,
|
|
171
|
-
0,
|
|
172
|
-
0.52,
|
|
173
|
-
);
|
|
174
|
-
const steeringLimit = car.trackState.surface === 'kerb'
|
|
175
|
-
? VEHICLE_LIMITS.maxSteer * 0.96
|
|
176
|
-
: VEHICLE_LIMITS.maxSteer;
|
|
177
|
-
const steeringRequest = clamp(pathSteer + lateralCorrection + edgeRecoverySteer, -steeringLimit, steeringLimit);
|
|
178
|
-
const cornerRadiusMeters = simUnitsToMeters(1 / Math.max(curvature, 1e-7));
|
|
179
|
-
const lateralGripBudget = 14.8 + car.racecraft * 3.4 + (car.tireEnergy ?? 100) * 0.011 + aggression * 1.8;
|
|
180
|
-
const cornerTargetKph = clamp(
|
|
181
|
-
Math.sqrt(lateralGripBudget * cornerRadiusMeters) * 3.6 * (1.1 + aggression * 0.08) +
|
|
182
|
-
(car.pace - 1) * 20 + aggression * 10,
|
|
183
|
-
96,
|
|
184
|
-
318,
|
|
185
|
-
);
|
|
186
|
-
const edgePenalty = edgeGuard.pressure > 0.42
|
|
187
|
-
? simUnitsToMeters(Math.max(0, car.trackState.crossTrackError - edgeGuard.softLimit)) *
|
|
188
|
-
(5.8 - aggression * 1.1) + edgeGuard.overLimitPressure * 48
|
|
189
|
-
: edgeGuard.overLimitPressure * 48;
|
|
190
|
-
const steeringPenalty = clamp((Math.abs(angleError) - 0.38) * 24, 0, 12);
|
|
191
|
-
const headingPenalty = clamp((Math.abs(headingError) - 0.48) * 42, 0, 24);
|
|
192
|
-
const recoveryAlignmentPenalty = edgeGuard.pressure > 0.2
|
|
193
|
-
? clamp((Math.abs(headingError) - 0.46) * 64, 0, 38)
|
|
194
|
-
: 0;
|
|
195
|
-
const steeringLoadPenalty = clamp(
|
|
196
|
-
(Math.abs(steeringRequest) / Math.max(steeringLimit, 1e-6) - 0.82) * 42,
|
|
197
|
-
0,
|
|
198
|
-
18,
|
|
199
|
-
) * clamp(Math.abs(headingError) / 0.48, 0, 1);
|
|
200
|
-
const actualLaneLateral = lanePlan.actualLaneAhead
|
|
201
|
-
? Math.abs(lanePlan.actualLaneAhead.signedOffset - (car.trackState?.signedOffset ?? 0))
|
|
202
|
-
: Infinity;
|
|
203
|
-
const plannedPassingOverlap = lanePlan.attackCommitted && lanePlan.sameLaneAhead
|
|
204
|
-
? Math.abs(lanePlan.sameLaneAhead.signedOffset - (car.trackState?.signedOffset ?? 0)) > OVERTAKE_LATERAL_MIN
|
|
205
|
-
: false;
|
|
206
|
-
const trafficPenalty = Math.max(
|
|
207
|
-
lanePlan.sameLaneAhead
|
|
208
|
-
? calculatePlannedTrafficPenalty(lanePlan.sameLaneAhead, plannedPassingOverlap)
|
|
209
|
-
: 0,
|
|
210
|
-
lanePlan.actualLaneAhead && actualLaneLateral < OVERTAKE_LATERAL_MIN
|
|
211
|
-
? calculateActualOverlapPenalty(car, lanePlan.actualLaneAhead)
|
|
212
|
-
: 0,
|
|
213
|
-
lanePlan.sideRisk ? clamp(simUnitsToMeters(TRAFFIC_SIDE_GAP - lanePlan.sideRisk.lateral) * 0.42, 0, 16) : 0,
|
|
214
|
-
) * (1 - aggression * 0.28);
|
|
215
|
-
const minimumDesiredSpeedKph = edgeGuard.pressure > 0.55
|
|
216
|
-
? 34
|
|
217
|
-
: car.trackState.surface === 'kerb' ? 58 : 52;
|
|
218
|
-
const desiredSpeedKph = clamp(
|
|
219
|
-
(car.drsActive ? cornerTargetKph + 22 : cornerTargetKph) -
|
|
220
|
-
edgePenalty -
|
|
221
|
-
steeringPenalty -
|
|
222
|
-
headingPenalty -
|
|
223
|
-
recoveryAlignmentPenalty -
|
|
224
|
-
steeringLoadPenalty -
|
|
225
|
-
trafficPenalty,
|
|
226
|
-
minimumDesiredSpeedKph,
|
|
227
|
-
330,
|
|
228
|
-
);
|
|
229
|
-
const desiredSpeed = clamp(
|
|
230
|
-
kphToSimSpeed(desiredSpeedKph),
|
|
231
|
-
kphToSimSpeed(minimumDesiredSpeedKph),
|
|
232
|
-
VEHICLE_LIMITS.maxSpeed,
|
|
233
|
-
);
|
|
234
|
-
const speedError = desiredSpeed - car.speed;
|
|
235
|
-
const minimumThrottle = edgeGuard.pressure > 0.2 ? 0 : 0.1 + aggression * 0.08;
|
|
236
|
-
const brakeLimit = car.trackState.surface === 'kerb'
|
|
237
|
-
? 0.42
|
|
238
|
-
: 0.72 - aggression * 0.08;
|
|
239
|
-
let brakeAmount = speedError < -kphToSimSpeed(3)
|
|
240
|
-
? clamp(Math.abs(speedError) / kphToSimSpeed(28 + aggression * 11), 0, brakeLimit)
|
|
241
|
-
: 0;
|
|
242
|
-
if (edgeGuard.pressure > 0.46 && car.speed > kphToSimSpeed(48)) {
|
|
243
|
-
brakeAmount = Math.max(brakeAmount, clamp((edgeGuard.pressure - 0.42) * 0.9, 0, brakeLimit));
|
|
244
|
-
}
|
|
245
|
-
const recoveryThrottleScale = edgeGuard.pressure > 0.24
|
|
246
|
-
? 1 - clamp((Math.abs(headingError) - 0.38) / 0.92, 0, 0.82)
|
|
247
|
-
: 1;
|
|
248
|
-
const throttleRequest = speedError > kphToSimSpeed(1)
|
|
249
|
-
? clamp(speedError / kphToSimSpeed(16), minimumThrottle, 1)
|
|
250
|
-
: 0;
|
|
251
|
-
|
|
252
|
-
return createDriverInput()
|
|
253
|
-
.steer(steeringRequest)
|
|
254
|
-
.accelerate(brakeAmount > 0.05 ? 0 : throttleRequest * recoveryThrottleScale)
|
|
255
|
-
.brake(brakeAmount)
|
|
256
|
-
.controls();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function maxLookaheadCurvature(track, progress, lookahead) {
|
|
260
|
-
let maximum = 0;
|
|
261
|
-
let previous = pointAt(track, progress);
|
|
262
|
-
for (let index = 1; index <= CURVATURE_LOOKAHEAD_SAMPLES; index += 1) {
|
|
263
|
-
const sample = pointAt(track, progress + lookahead * (index / CURVATURE_LOOKAHEAD_SAMPLES));
|
|
264
|
-
const segmentDistance = Math.max(1, lookahead / CURVATURE_LOOKAHEAD_SAMPLES);
|
|
265
|
-
const segmentCurvature = Math.abs(normalizeAngle(sample.heading - previous.heading)) / segmentDistance;
|
|
266
|
-
maximum = Math.max(maximum, Math.abs(sample.curvature ?? 0), Math.min(segmentCurvature, PREVIEW_CURVATURE_CAP));
|
|
267
|
-
previous = sample;
|
|
268
|
-
}
|
|
269
|
-
return maximum;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function calculateRacingLineOffset(car, race, lookahead, curvature, edgeGuard) {
|
|
273
|
-
const cornerStrength = clamp((curvature - 0.00012) / 0.00095, 0, 1);
|
|
274
|
-
if (cornerStrength <= 0) return 0;
|
|
275
|
-
|
|
276
|
-
const entry = pointAt(race.track, car.progress - lookahead * 0.35);
|
|
277
|
-
const apex = pointAt(race.track, car.progress + lookahead * 0.55);
|
|
278
|
-
const exit = pointAt(race.track, car.progress + lookahead * 1.25);
|
|
279
|
-
const signedEntry = normalizeAngle(apex.heading - entry.heading);
|
|
280
|
-
const signedExit = normalizeAngle(exit.heading - apex.heading);
|
|
281
|
-
const turnDirection = Math.sign(Math.abs(signedEntry) > Math.abs(signedExit) ? signedEntry : signedExit);
|
|
282
|
-
if (turnDirection === 0) return 0;
|
|
283
|
-
|
|
284
|
-
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
285
|
-
const safeEdge = race.track.width / 2 - VEHICLE_LIMITS.carWidth * (1.15 - aggression * 0.12);
|
|
286
|
-
const apexOffset = turnDirection * safeEdge * (0.72 + aggression * 0.08);
|
|
287
|
-
|
|
288
|
-
return apexOffset * cornerStrength * (1 - edgeGuard.pressure * 0.65);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function calculateTrackEdgeGuard(car, race) {
|
|
292
|
-
const trackLimit = race.track.width / 2;
|
|
293
|
-
const halfCarWidth = VEHICLE_LIMITS.carWidth / 2;
|
|
294
|
-
const wholeCarOutsideLimit = trackLimit + halfCarWidth;
|
|
295
|
-
const softLimit = trackLimit - halfCarWidth * 1.9;
|
|
296
|
-
const recoveryOffset = trackLimit - halfCarWidth * 1.62;
|
|
297
|
-
const crossTrackError = car.trackState?.crossTrackError ?? 0;
|
|
298
|
-
const side = Math.sign(car.trackState?.signedOffset ?? 0);
|
|
299
|
-
const pressure = clamp(
|
|
300
|
-
(crossTrackError - softLimit) / Math.max(1, wholeCarOutsideLimit - softLimit),
|
|
301
|
-
0,
|
|
302
|
-
1,
|
|
303
|
-
);
|
|
304
|
-
const overLimitPressure = clamp(
|
|
305
|
-
(crossTrackError - wholeCarOutsideLimit) / Math.max(1, (race.track.kerbWidth ?? 0) + halfCarWidth),
|
|
306
|
-
0,
|
|
307
|
-
1,
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
softLimit,
|
|
312
|
-
recoveryOffset,
|
|
313
|
-
pressure,
|
|
314
|
-
overLimitPressure,
|
|
315
|
-
side,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function shouldContinueRejoinRecovery(car, race) {
|
|
320
|
-
const frames = Math.max(0, Math.floor(car.rejoinRecoveryFrames ?? 0));
|
|
321
|
-
if (frames <= 0) return false;
|
|
322
|
-
|
|
323
|
-
const lookahead = clamp(car.speed * 0.66 + REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX);
|
|
324
|
-
const targetBase = pointAt(race.track, car.progress + lookahead);
|
|
325
|
-
const headingError = Math.abs(normalizeAngle(targetBase.heading - car.heading));
|
|
326
|
-
const edgeBuffer = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 1.35;
|
|
327
|
-
const stillUnsettled = car.trackState.crossTrackError > edgeBuffer || headingError > 0.34;
|
|
328
|
-
|
|
329
|
-
car.rejoinRecoveryFrames = stillUnsettled ? frames - 1 : 0;
|
|
330
|
-
return stillUnsettled;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function decideRejoinControls(car, race) {
|
|
334
|
-
const lookahead = clamp(car.speed * 0.72 + REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX);
|
|
335
|
-
const distanceFromRoadMeters = simUnitsToMeters(Math.max(0, car.trackState.crossTrackError - race.track.width / 2));
|
|
336
|
-
const targetBase = !car.trackState.onTrack && distanceFromRoadMeters > 2.5
|
|
337
|
-
? pointAt(race.track, car.progress)
|
|
338
|
-
: pointAt(race.track, car.progress + lookahead * 0.78);
|
|
339
|
-
const signedSide = Math.sign(car.trackState?.signedOffset ?? 0);
|
|
340
|
-
const rejoinOffset = signedSide !== 0 && (
|
|
341
|
-
distanceFromRoadMeters > 0.5 ||
|
|
342
|
-
car.trackState.crossTrackError > race.track.width / 2 - VEHICLE_LIMITS.carWidth * 1.6
|
|
343
|
-
)
|
|
344
|
-
? -signedSide * race.track.width * 0.22
|
|
345
|
-
: 0;
|
|
346
|
-
const target = offsetTrackPoint(targetBase, rejoinOffset);
|
|
347
|
-
const angleError = angleToPoint(car, target);
|
|
348
|
-
const surfaceTargetSpeedKph = car.trackState.surface === 'track'
|
|
349
|
-
? 88
|
|
350
|
-
: car.trackState.surface === 'kerb'
|
|
351
|
-
? 72
|
|
352
|
-
: car.trackState.surface === 'gravel'
|
|
353
|
-
? 56
|
|
354
|
-
: car.trackState.surface === 'grass'
|
|
355
|
-
? 48
|
|
356
|
-
: 34;
|
|
357
|
-
const alignment = clamp(1 - Math.abs(angleError) / Math.PI, 0.22, 1);
|
|
358
|
-
const desiredSpeed = kphToSimSpeed(clamp(
|
|
359
|
-
surfaceTargetSpeedKph * (0.62 + alignment * 0.38) - distanceFromRoadMeters * 0.065,
|
|
360
|
-
car.trackState.surface === 'barrier' ? 12 : 24,
|
|
361
|
-
surfaceTargetSpeedKph,
|
|
362
|
-
));
|
|
363
|
-
const speedError = desiredSpeed - car.speed;
|
|
364
|
-
const lowSpeedRecovery = car.speed < kphToSimSpeed(18)
|
|
365
|
-
? car.trackState.surface === 'barrier' ? 0.34 : 0.18
|
|
366
|
-
: 0;
|
|
367
|
-
const throttleLimit = car.trackState.surface === 'barrier'
|
|
368
|
-
? 0.68
|
|
369
|
-
: car.trackState.surface === 'gravel' ? 0.46 : 0.42;
|
|
370
|
-
const brakeAmount = speedError < -kphToSimSpeed(5)
|
|
371
|
-
? clamp(Math.abs(speedError) / kphToSimSpeed(30), 0.04, car.trackState.onTrack ? 0.46 : 0.74)
|
|
372
|
-
: 0;
|
|
373
|
-
|
|
374
|
-
return createDriverInput()
|
|
375
|
-
.steer(angleError * 1.65)
|
|
376
|
-
.accelerate(brakeAmount > 0.05 ? 0 : speedError > 0
|
|
377
|
-
? clamp((speedError / kphToSimSpeed(24)) * alignment + lowSpeedRecovery, 0.08, throttleLimit)
|
|
378
|
-
: lowSpeedRecovery)
|
|
379
|
-
.brake(brakeAmount)
|
|
380
|
-
.controls();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function decideSafetyCarControls(car, orderIndex, race) {
|
|
384
|
-
const queueSlot = race.safetyCar.progress - race.rules.safetyCarLeadDistance - orderIndex * race.rules.safetyCarGap;
|
|
385
|
-
const lookahead = clamp(car.speed * 0.55 + metersToSimUnits(20), SAFETY_CAR_LOOKAHEAD_MIN, SAFETY_CAR_LOOKAHEAD_MAX);
|
|
386
|
-
const targetBase = pointAt(race.track, car.progress + lookahead);
|
|
387
|
-
const target = offsetTrackPoint(targetBase, 0);
|
|
388
|
-
const slotError = queueSlot - car.raceDistance;
|
|
389
|
-
const headingError = normalizeAngle(targetBase.heading - car.heading);
|
|
390
|
-
const lateralError = car.trackState?.signedOffset ?? 0;
|
|
391
|
-
const centerlineCorrection = -Math.atan2(
|
|
392
|
-
lateralError * 1.25,
|
|
393
|
-
Math.max(car.speed, kphToSimSpeed(35)),
|
|
394
|
-
);
|
|
395
|
-
const cornerRadiusMeters = simUnitsToMeters(1 / Math.max(Math.abs(targetBase.curvature), 1e-7));
|
|
396
|
-
const cornerSpeed = kphToSimSpeed(clamp(Math.sqrt(4.2 * cornerRadiusMeters) * 3.6, 45, 158));
|
|
397
|
-
const edgePenalty = clamp(simUnitsToMeters(Math.abs(lateralError)) - simUnitsToMeters(race.track.width / 2) * 0.68, 0, 3) * kphToSimSpeed(18);
|
|
398
|
-
const desiredSpeed = clamp(
|
|
399
|
-
race.rules.safetyCarSpeed + slotError * 0.24 - edgePenalty,
|
|
400
|
-
kphToSimSpeed(22),
|
|
401
|
-
Math.min(race.rules.safetyCarSpeed + kphToSimSpeed(32), cornerSpeed),
|
|
402
|
-
);
|
|
403
|
-
const speedError = desiredSpeed - car.speed;
|
|
404
|
-
|
|
405
|
-
return createDriverInput()
|
|
406
|
-
.steer(angleToPoint(car, target) * 1.18 + headingError * 0.32 + centerlineCorrection)
|
|
407
|
-
.accelerate(speedError > kphToSimSpeed(1) ? clamp(speedError / kphToSimSpeed(16), 0, 0.5) : 0)
|
|
408
|
-
.brake(speedError < -kphToSimSpeed(0.5) ? clamp(Math.abs(speedError) / kphToSimSpeed(14), 0, 1) : 0)
|
|
409
|
-
.controls();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
export function planRacingLine(car, orderIndex, race) {
|
|
413
|
-
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
414
|
-
const riskTolerance = car.personality?.riskTolerance ?? aggression;
|
|
415
|
-
const trackLimit = race.track.width / 2 - VEHICLE_LIMITS.carWidth * clamp(1.55 - aggression * 0.28, 1.26, 1.55);
|
|
416
|
-
const preferred = Math.sin((car.index / Math.max(1, race.cars.length)) * TWO_PI) * metersToSimUnits(1.1);
|
|
417
|
-
const currentOffset = clamp(car.desiredOffset ?? preferred, -trackLimit, trackLimit);
|
|
418
|
-
const ahead = race.orderedCars[orderIndex - 1];
|
|
419
|
-
const traffic = scanNearbyTraffic(car, race);
|
|
420
|
-
const rearThreat = findDefensiveThreat(traffic);
|
|
421
|
-
const attackPlan = planAttackCommitment(car, ahead, traffic, trackLimit, aggression, race);
|
|
422
|
-
|
|
423
|
-
let bestOffset = currentOffset;
|
|
424
|
-
let bestScore = -Infinity;
|
|
425
|
-
|
|
426
|
-
LANE_OFFSETS.forEach((rawOffset) => {
|
|
427
|
-
const offset = clamp(rawOffset, -trackLimit, trackLimit);
|
|
428
|
-
const edgeClearance = trackLimit - Math.abs(offset);
|
|
429
|
-
let score = 80;
|
|
430
|
-
score -= Math.abs(offset - preferred) * 0.18;
|
|
431
|
-
score -= Math.abs(offset - currentOffset) * (0.09 - aggression * 0.025);
|
|
432
|
-
score -= Math.max(0, LANE_EDGE_CLEARANCE_TARGET - edgeClearance) * (0.92 - aggression * 0.42);
|
|
433
|
-
|
|
434
|
-
traffic.forEach((entry) => {
|
|
435
|
-
const lateral = Math.abs(entry.signedOffset - offset);
|
|
436
|
-
if (entry.gap > 0 && entry.gap < TRAFFIC_GAP_AHEAD) {
|
|
437
|
-
const overlapRisk = clamp(TRAFFIC_SIDE_OVERLAP - lateral, 0, TRAFFIC_SIDE_OVERLAP);
|
|
438
|
-
score -= overlapRisk * (TRAFFIC_GAP_AHEAD - entry.gap) * 0.038 * (1 - riskTolerance * 0.34);
|
|
439
|
-
if (entry.gap < OVERTAKE_GAP_BASE + aggression * OVERTAKE_GAP_AGGRESSION && lateral > OVERTAKE_LATERAL_MIN - aggression * metersToSimUnits(4)) {
|
|
440
|
-
score += Math.min(metersToSimUnits(14) + aggression * metersToSimUnits(8.5), lateral - (OVERTAKE_LATERAL_REWARD - aggression * metersToSimUnits(3))) * (0.62 + aggression * 0.82);
|
|
441
|
-
}
|
|
442
|
-
} else if (entry.gap <= 0 && entry.gap > -TRAFFIC_SIDE_OVERLAP) {
|
|
443
|
-
const sideOverlapRisk = clamp(TRAFFIC_SIDE_OVERLAP - lateral, 0, TRAFFIC_SIDE_OVERLAP);
|
|
444
|
-
score -= sideOverlapRisk * (TRAFFIC_SIDE_OVERLAP + entry.gap) * 0.052 * (1 - riskTolerance * 0.22);
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
if (ahead && car.gapAhead < TRAFFIC_GAP_AHEAD) {
|
|
449
|
-
const passSide = attackPlan?.targetOffset ?? clamp(
|
|
450
|
-
(ahead.trackState.signedOffset * -0.65) + (car.index % 2 === 0 ? -1 : 1) * (PASS_SIDE_BASE + aggression * PASS_SIDE_AGGRESSION),
|
|
451
|
-
-trackLimit,
|
|
452
|
-
trackLimit,
|
|
453
|
-
);
|
|
454
|
-
const attackPressure = attackPlan?.pressure ?? 0.45;
|
|
455
|
-
score -= Math.abs(offset - passSide) * (0.12 + aggression * 0.16) * attackPressure;
|
|
456
|
-
if (attackPlan && Math.sign(offset || passSide) === attackPlan.side && Math.abs(offset - ahead.trackState.signedOffset) > OVERTAKE_LATERAL_MIN) {
|
|
457
|
-
score += metersToSimUnits(4 + aggression * 5) * attackPressure;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const defenseIntensity = clamp((aggression - 0.5) / 0.3, 0, 1) *
|
|
462
|
-
clamp((Math.max(riskTolerance, aggression) - 0.42) / 0.4, 0, 1);
|
|
463
|
-
if (rearThreat && defenseIntensity > 0.04) {
|
|
464
|
-
const threatSide = Math.sign(rearThreat.signedOffset || currentOffset || (car.index % 2 === 0 ? 1 : -1));
|
|
465
|
-
const defendOffset = clamp(
|
|
466
|
-
threatSide * Math.max(Math.abs(rearThreat.signedOffset) + metersToSimUnits(0.8), DEFEND_MIN_OFFSET),
|
|
467
|
-
-trackLimit,
|
|
468
|
-
trackLimit,
|
|
469
|
-
);
|
|
470
|
-
const closingPressure = clamp((DEFEND_REAR_GAP + rearThreat.gap) / DEFEND_REAR_GAP, 0, 1);
|
|
471
|
-
score -= Math.abs(offset - defendOffset) * (0.75 + aggression * 0.45) * closingPressure * defenseIntensity;
|
|
472
|
-
if (Math.sign(offset) === threatSide && Math.abs(offset) >= DEFEND_MIN_OFFSET) {
|
|
473
|
-
score += metersToSimUnits(10) * (0.42 + aggression * 0.36) * closingPressure * defenseIntensity;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (score > bestScore) {
|
|
478
|
-
bestScore = score;
|
|
479
|
-
bestOffset = offset;
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
const laneChangeRate = metersToSimUnits(0.95 + car.racecraft * 0.32 + aggression * 0.78);
|
|
484
|
-
car.desiredOffset = currentOffset + clamp(bestOffset - currentOffset, -laneChangeRate, laneChangeRate);
|
|
485
|
-
|
|
486
|
-
return {
|
|
487
|
-
offset: car.desiredOffset,
|
|
488
|
-
sameLaneAhead: findLaneTrafficAhead(traffic, car.desiredOffset, TRAFFIC_GAP_AHEAD),
|
|
489
|
-
actualLaneAhead: findLaneTrafficAhead(traffic, car.trackState?.signedOffset ?? car.desiredOffset, TRAFFIC_GAP_AHEAD),
|
|
490
|
-
sideRisk: findLaneTrafficBeside(traffic, car.desiredOffset),
|
|
491
|
-
attackCommitted: Boolean(attackPlan),
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function calculatePlannedTrafficPenalty(entry, plannedPassingOverlap) {
|
|
496
|
-
return clamp(
|
|
497
|
-
simUnitsToMeters(TRAFFIC_GAP_AHEAD - entry.gap) * (plannedPassingOverlap ? 0.08 : 0.16),
|
|
498
|
-
0,
|
|
499
|
-
plannedPassingOverlap ? 24 : 32,
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function calculateActualOverlapPenalty(car, entry) {
|
|
504
|
-
const gapMeters = simUnitsToMeters(entry.gap);
|
|
505
|
-
const closingKph = Math.max(0, simSpeedToKph(car.speed - (entry.speed ?? car.speed)));
|
|
506
|
-
const urgent = gapMeters < 9
|
|
507
|
-
? (9 - gapMeters) * (2.2 + closingKph * 0.03)
|
|
508
|
-
: 0;
|
|
509
|
-
|
|
510
|
-
return clamp(urgent, 0, 58);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function planAttackCommitment(car, ahead, traffic, trackLimit, aggression, race) {
|
|
514
|
-
const existingFrames = Math.max(0, Math.floor(car.attackCommitmentFrames ?? 0));
|
|
515
|
-
const relativeSpeed = ahead ? car.speed - ahead.speed : 0;
|
|
516
|
-
const closeEnough = ahead &&
|
|
517
|
-
car.gapAhead > 0 &&
|
|
518
|
-
car.gapAhead < OVERTAKE_GAP_BASE + aggression * OVERTAKE_GAP_AGGRESSION + metersToSimUnits(28);
|
|
519
|
-
const speedPressure = clamp(relativeSpeed / kphToSimSpeed(34), -0.4, 1);
|
|
520
|
-
|
|
521
|
-
if (closeEnough && speedPressure > -0.15) {
|
|
522
|
-
const aheadOffset = ahead.trackState?.signedOffset ?? 0;
|
|
523
|
-
const currentSide = Math.sign(car.attackSide ?? 0);
|
|
524
|
-
const blockedCommitment = currentSide &&
|
|
525
|
-
Math.sign(aheadOffset || currentSide) === currentSide &&
|
|
526
|
-
Math.abs(aheadOffset - (car.trackState?.signedOffset ?? 0)) < TRAFFIC_SIDE_GAP &&
|
|
527
|
-
Math.abs(aheadOffset) > trackLimit * 0.55;
|
|
528
|
-
|
|
529
|
-
if (!existingFrames || !currentSide || blockedCommitment) {
|
|
530
|
-
const leftSpace = trackLimit + aheadOffset;
|
|
531
|
-
const rightSpace = trackLimit - aheadOffset;
|
|
532
|
-
const preferredSide = leftSpace > rightSpace ? -1 : 1;
|
|
533
|
-
const freeSide = chooseFreerAttackSide(traffic, preferredSide);
|
|
534
|
-
car.attackSide = freeSide;
|
|
535
|
-
}
|
|
536
|
-
car.attackCommitmentFrames = OVERTAKE_COMMIT_FRAMES;
|
|
537
|
-
} else if (existingFrames > 0) {
|
|
538
|
-
car.attackCommitmentFrames = existingFrames - 1;
|
|
539
|
-
} else {
|
|
540
|
-
car.attackSide = 0;
|
|
541
|
-
car.attackCommitmentFrames = 0;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const side = Math.sign(car.attackSide ?? 0);
|
|
545
|
-
if (!side || !ahead) return null;
|
|
546
|
-
|
|
547
|
-
const targetOffset = clamp(
|
|
548
|
-
(ahead.trackState?.signedOffset ?? 0) + side * (PASS_SIDE_BASE + aggression * PASS_SIDE_AGGRESSION + metersToSimUnits(2.4)),
|
|
549
|
-
-trackLimit,
|
|
550
|
-
trackLimit,
|
|
551
|
-
);
|
|
552
|
-
const pressure = clamp(
|
|
553
|
-
0.48 +
|
|
554
|
-
aggression * 0.34 +
|
|
555
|
-
speedPressure * 0.28 +
|
|
556
|
-
(car.attackCommitmentFrames ?? 0) / OVERTAKE_COMMIT_FRAMES * 0.22,
|
|
557
|
-
0.45,
|
|
558
|
-
1.15,
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
return { side, targetOffset, pressure };
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function chooseFreerAttackSide(traffic, preferredSide) {
|
|
565
|
-
const sideScore = (side) => traffic.reduce((score, entry) => {
|
|
566
|
-
if (entry.gap < -TRAFFIC_REAR_WINDOW || entry.gap > TRAFFIC_GAP_AHEAD) return score;
|
|
567
|
-
const sameSide = Math.sign(entry.signedOffset || side) === side;
|
|
568
|
-
return score - (sameSide ? 1 : 0) * (1 - clamp(Math.abs(entry.gap) / TRAFFIC_GAP_AHEAD, 0, 1));
|
|
569
|
-
}, side === preferredSide ? 0.18 : 0);
|
|
570
|
-
return sideScore(1) >= sideScore(-1) ? 1 : -1;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function scanNearbyTraffic(car, race) {
|
|
574
|
-
return race.cars
|
|
575
|
-
.filter((other) => other !== car)
|
|
576
|
-
.map((other) => ({
|
|
577
|
-
car: other,
|
|
578
|
-
gap: other.raceDistance - car.raceDistance,
|
|
579
|
-
signedOffset: other.trackState?.signedOffset ?? 0,
|
|
580
|
-
speed: other.speed,
|
|
581
|
-
}))
|
|
582
|
-
.filter((entry) => entry.gap > -metersToSimUnits(39) && entry.gap < metersToSimUnits(134));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function findDefensiveThreat(traffic) {
|
|
586
|
-
let closest = null;
|
|
587
|
-
|
|
588
|
-
traffic.forEach((entry) => {
|
|
589
|
-
if (entry.gap >= -VEHICLE_LIMITS.carLength * 1.1 || entry.gap < -DEFEND_REAR_GAP) return;
|
|
590
|
-
if (!closest || entry.gap > closest.gap) closest = entry;
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
return closest;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function findLaneTrafficAhead(traffic, offset, maxDistance) {
|
|
597
|
-
let closest = null;
|
|
598
|
-
|
|
599
|
-
traffic.forEach((entry) => {
|
|
600
|
-
if (entry.gap <= 0 || entry.gap > maxDistance) return;
|
|
601
|
-
if (Math.abs(entry.signedOffset - offset) > TRAFFIC_SIDE_GAP) return;
|
|
602
|
-
if (!closest || entry.gap < closest.gap) closest = entry;
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
return closest;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function findLaneTrafficBeside(traffic, offset) {
|
|
609
|
-
let closest = null;
|
|
610
|
-
|
|
611
|
-
traffic.forEach((entry) => {
|
|
612
|
-
const lateral = Math.abs(entry.signedOffset - offset);
|
|
613
|
-
if (Math.abs(entry.gap) > metersToSimUnits(28) || lateral > TRAFFIC_SIDE_GAP) return;
|
|
614
|
-
const risk = (metersToSimUnits(28) - Math.abs(entry.gap)) + (TRAFFIC_SIDE_GAP - lateral);
|
|
615
|
-
if (!closest || risk > closest.risk) closest = { ...entry, lateral, risk };
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
return closest;
|
|
619
|
-
}
|
|
1
|
+
export { decideDriverControls } from './driver/driverController.js';
|
|
2
|
+
export { createDriverInput } from './driver/driverInput.js';
|
|
3
|
+
export { buildDriverPersonality } from './driver/driverPersonality.js';
|
|
4
|
+
export { planRacingLine } from './driver/racingLinePlan.js';
|