@inventure71/paddockjs 4.2.0 → 4.3.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 +40 -0
- package/INSTALL_AND_UPDATE.md +24 -4
- package/README.md +31 -2
- package/docs/advanced_ai_pace.md +131 -0
- package/docs/advanced_physics_rebuild.md +166 -0
- package/docs/architecture.md +109 -30
- package/docs/cleanup_coverage.md +284 -0
- package/docs/component_inventory.md +31 -15
- package/docs/composable-layouts.md +2 -0
- package/docs/concepts.md +12 -1
- package/docs/custom_model_controller.md +1 -1
- package/docs/data_contract.md +33 -10
- package/docs/data_model.md +4 -2
- package/docs/efficiency_cleanup_2026-10-13.md +80 -0
- package/docs/grid_walk.md +84 -0
- package/docs/index.md +1 -0
- package/docs/keyboard_driving_fix.md +118 -0
- package/docs/limit_driving_2026-10-14.md +88 -0
- package/docs/maintainability_audit_2026-09-08.md +146 -0
- package/docs/racing_ai_improvements.md +145 -0
- package/docs/rules.md +43 -13
- package/docs/sense_contract.md +4 -1
- package/docs/system_specs.md +38 -11
- package/docs/testing-reliability-guide.md +5 -0
- package/docs/v11_improvements.md +108 -0
- package/package.json +24 -13
- package/src/api/MountedSimulatorComponents.js +101 -0
- package/src/api/PaddockSimulatorController.js +56 -81
- package/src/app/BrowserExpertAdapter.js +0 -6
- package/src/app/BrowserExpertExternalRenderer.js +72 -50
- package/src/app/ExternalTrackSnapshot.js +50 -0
- package/src/app/F1SimulatorApp.js +242 -297
- package/src/app/HostLifecycleEmitter.js +93 -0
- package/src/app/banners/raceDataBannerController.js +9 -4
- package/src/app/camera/cameraConstants.js +1 -1
- package/src/app/camera/cameraController.js +10 -13
- package/src/app/camera/cameraGeometry.js +1 -1
- package/src/app/controls/SimulatorViewControls.js +45 -0
- package/src/app/controls/workbenchBindings.js +64 -0
- package/src/app/controls/workbenchDisclosure.js +41 -0
- package/src/app/domBindings.js +21 -13
- package/src/app/gridWalk/GridWalkController.js +180 -0
- package/src/app/gridWalk/gridWalkCamera.js +88 -0
- package/src/app/gridWalk/gridWalkControls.js +24 -0
- package/src/app/gridWalk/gridWalkCrew.js +246 -0
- package/src/app/gridWalk/gridWalkLabels.js +104 -0
- package/src/app/gridWalk/gridWalkLayout.js +46 -0
- package/src/app/gridWalk/gridWalkNavigation.js +106 -0
- package/src/app/gridWalk/gridWalkProjectBanner.js +43 -0
- package/src/app/gridWalk/gridWalkSession.js +50 -0
- package/src/app/layoutSupport.js +2 -0
- package/src/app/raceOverlayClearanceSupport.js +21 -7
- package/src/app/readouts/carOverviewRenderer.js +9 -4
- package/src/app/readouts/readoutFormatters.js +1 -8
- package/src/app/readouts/telemetryRenderer.js +1 -3
- package/src/app/readouts/timingTowerRenderer.js +48 -50
- package/src/app/rendering/appAssets.js +8 -10
- package/src/app/rendering/carRenderer.js +27 -79
- package/src/app/rendering/displayUtils.js +0 -6
- package/src/app/rendering/expertSensorRenderer.js +1 -1
- package/src/app/rendering/replayGhostRenderer.js +4 -1
- package/src/app/rendering/trackRenderer.js +2 -2
- package/src/app/runtime/frameLoop.js +18 -17
- package/src/app/runtime/pixiStartupLifecycle.js +94 -0
- package/src/app/runtime/runtimeTiming.js +4 -1
- package/src/app/runtime/runtimeVisibility.js +7 -2
- package/src/config/cssValues.js +1 -1
- package/src/config/defaultOptions.js +12 -173
- package/src/config/restartOptions.js +1 -0
- package/src/config/runtimeSeed.js +10 -0
- package/src/config/simulatorDefaults.js +106 -0
- package/src/config/telemetryModules.js +20 -0
- package/src/config/theme/cssTokens.js +85 -0
- package/src/config/theme/themeCss.js +72 -0
- package/src/config/theme/themeInputs.js +155 -0
- package/src/config/theme/themeResolver.js +172 -0
- package/src/config/theme/themeSelectors.js +69 -0
- package/src/config/theme/themeTokens.js +257 -0
- package/src/config/theme/themeUtils.js +15 -0
- package/src/config/themeOptions.js +14 -830
- package/src/config/timingGapMode.js +0 -2
- package/src/config/uiOptions.js +53 -0
- package/src/data/championship.js +16 -158
- package/src/data/championshipDefaults.js +87 -0
- package/src/data/driverData.js +10 -20
- package/src/data/entryIdentity.js +67 -0
- package/src/data/entryPresentation.js +23 -0
- package/src/data/entryTypes.d.ts +160 -0
- package/src/data/index.d.ts +13 -259
- package/src/data/ratingScale.js +14 -0
- package/src/data/teamTypes.d.ts +23 -0
- package/src/data/trackTypes.d.ts +101 -0
- package/src/data/vehicleData.js +12 -23
- package/src/environment/actions.js +3 -2
- package/src/environment/controllerLoop.js +57 -44
- package/src/environment/controllerScheduler.js +30 -0
- package/src/environment/driverObservations.js +260 -0
- package/src/environment/driverReset.js +51 -0
- package/src/environment/episode.js +0 -1
- package/src/environment/evaluation.js +6 -90
- package/src/environment/evaluationTracker.js +100 -0
- package/src/environment/events.js +23 -0
- package/src/environment/index.d.ts +21 -140
- package/src/environment/metrics.js +3 -17
- package/src/environment/observationSchema.js +104 -0
- package/src/environment/observationVector.js +16 -115
- package/src/environment/observations.js +16 -316
- package/src/environment/options.js +5 -24
- package/src/environment/renderFrames.js +21 -0
- package/src/environment/resetOptions.js +41 -0
- package/src/environment/results.js +125 -0
- package/src/environment/rewards.js +10 -16
- package/src/environment/runtime.js +17 -300
- package/src/environment/scenarioOptions.js +90 -0
- package/src/environment/scenarioPlacements.js +70 -0
- package/src/environment/scenarioPresets.js +78 -0
- package/src/environment/scenarios.js +5 -236
- package/src/environment/scratch.js +18 -0
- package/src/environment/sensorOptions.js +20 -0
- package/src/environment/sensors/index.js +2 -556
- package/src/environment/sensors/indexedRayBands.js +0 -18
- package/src/environment/sensors/nearbyCars.js +18 -15
- package/src/environment/sensors/opponentRadar.js +3 -2
- package/src/environment/sensors/pitConnectorProximity.js +20 -0
- package/src/environment/sensors/rayBandTrace.js +1 -37
- package/src/environment/sensors/rayBatch.js +81 -0
- package/src/environment/sensors/rayConfig.js +26 -0
- package/src/environment/sensors/rayGeometry.js +0 -8
- package/src/environment/sensors/rayGuards.js +0 -6
- package/src/environment/sensors/rayOutputs.js +75 -0
- package/src/environment/sensors/rayQuery.js +110 -0
- package/src/environment/sensors/rayScratch.js +146 -0
- package/src/environment/sensors/sensorTargets.js +12 -4
- package/src/environment/sensors/surfaceRays.js +6 -61
- package/src/environment/sensors/trackRays.js +7 -80
- package/src/environment/simulationSetup.js +30 -0
- package/src/environment/specs.js +15 -109
- package/src/index.d.ts +34 -259
- package/src/index.js +28 -6
- package/src/placeholder/index.js +1 -8
- package/src/rendering/displayLifecycle.js +6 -0
- package/src/rendering/proceduralTrackAsset.js +1 -1
- package/src/rendering/renderSnapshot.js +3 -0
- package/src/rendering/track/finishLineRenderer.js +1 -1
- package/src/rendering/track/gridRenderer.js +1 -1
- package/src/rendering/track/offsetStrokeSafety.js +2 -2
- package/src/rendering/track/pitLaneRenderer.js +1 -1
- package/src/rendering/track/trackMaterialRenderer.js +4 -4
- package/src/rendering/track/trackRenderConstants.js +0 -1
- package/src/rendering/track/trackRenderGeometry.js +1 -7
- package/src/simulation/collisionGeometry.js +5 -11
- package/src/simulation/driver/advancedPathControls.js +53 -0
- package/src/simulation/driver/advancedRacePace.js +112 -0
- package/src/simulation/driver/advancedRacingControls.js +61 -0
- package/src/simulation/driver/advancedRacingEnvelope.js +255 -0
- package/src/simulation/driver/advancedRacingSteering.js +76 -0
- package/src/simulation/driver/advancedRacingTiming.js +16 -0
- package/src/simulation/driver/arcadeRacePace.js +83 -0
- package/src/simulation/driver/arcadeRacingControls.js +111 -0
- package/src/simulation/driver/arcadeRacingEnvelope.js +63 -0
- package/src/simulation/driver/driverControlConstants.js +0 -2
- package/src/simulation/driver/driverController.js +14 -3
- package/src/simulation/driver/edgeRecovery.js +0 -1
- package/src/simulation/driver/racingControls.js +10 -406
- package/src/simulation/driver/racingDriverProfile.js +48 -0
- package/src/simulation/driver/racingLinePlan.js +48 -15
- package/src/simulation/driver/rejoinControls.js +58 -111
- package/src/simulation/driver/safetyCarControls.js +12 -0
- package/src/simulation/driver/trafficScan.js +89 -6
- package/src/simulation/pit/pitCarPlacement.js +29 -0
- package/src/simulation/pit/pitFlow.js +8 -30
- package/src/simulation/pit/pitIntent.js +4 -3
- package/src/simulation/pit/pitOccupancy.js +2 -2
- package/src/simulation/pit/pitQueue.js +2 -18
- package/src/simulation/pit/pitRouteMovement.js +1 -36
- package/src/simulation/pit/pitRouting.js +5 -81
- package/src/simulation/pit/pitService.js +3 -32
- package/src/simulation/pit/pitServiceConstants.js +1 -0
- package/src/simulation/pit/pitServiceProgress.js +34 -0
- package/src/simulation/pit/pitSnapshots.js +8 -4
- package/src/simulation/pit/pitState.js +1 -5
- package/src/simulation/race/classification.js +1 -8
- package/src/simulation/race/raceFinish.js +3 -8
- package/src/simulation/race/raceOrder.js +2 -13
- package/src/simulation/race/raceProgress.js +6 -20
- package/src/simulation/race/raceSetup.js +1 -1
- package/src/simulation/race/raceStep.js +1 -1
- package/src/simulation/race/redFlag.js +2 -4
- package/src/simulation/race/safetyCar.js +3 -8
- package/src/simulation/race/stalledDnf.js +2 -4
- package/src/simulation/raceSimulation.js +6 -38
- package/src/simulation/replay/replayGhosts.js +44 -32
- package/src/simulation/rules/collisionSteward.js +0 -14
- package/src/simulation/rules/penaltyActions.js +37 -0
- package/src/simulation/rules/penaltyLedger.js +8 -1
- package/src/simulation/rules/rulesReview.js +1 -7
- package/src/simulation/simMath.js +4 -0
- package/src/simulation/snapshots/raceSnapshots.js +30 -11
- package/src/simulation/timing/drsState.js +8 -0
- package/src/simulation/timing/drsTiming.js +2 -4
- package/src/simulation/timing/lapTelemetry.js +7 -4
- package/src/simulation/timing/sectorPerformance.js +3 -3
- package/src/simulation/timing/timingLines.js +12 -6
- package/src/simulation/track/pitLaneAccess.js +2 -2
- package/src/simulation/track/pitLaneLayout.js +4 -135
- package/src/simulation/track/pitLaneModel.js +70 -0
- package/src/simulation/track/pitLaneState.js +0 -1
- package/src/simulation/track/pitServiceLayout.js +121 -0
- package/src/simulation/track/proceduralTrack.js +77 -0
- package/src/simulation/track/spatialQueries.js +1 -25
- package/src/simulation/track/trackBuilder.js +54 -0
- package/src/simulation/track/trackConstants.js +0 -22
- package/src/simulation/track/trackMath.js +4 -29
- package/src/simulation/track/trackModel.js +4 -241
- package/src/simulation/track/trackModelCache.js +105 -0
- package/src/simulation/track/trackNearestQueries.js +1218 -0
- package/src/simulation/track/trackQueryConstants.js +3 -0
- package/src/simulation/track/trackQueryConstruction.js +368 -0
- package/src/simulation/track/trackQueryIndex.js +12 -1933
- package/src/simulation/track/trackQuerySegments.js +174 -0
- package/src/simulation/track/trackQueryStats.js +12 -0
- package/src/simulation/track/trackRayQueries.js +165 -0
- package/src/simulation/vehicle/advancedContactResponse.js +89 -0
- package/src/simulation/vehicle/advancedTireForces.js +92 -0
- package/src/simulation/vehicle/advancedVehicleModel.js +65 -0
- package/src/simulation/vehicle/advancedVehiclePhysics.js +127 -0
- package/src/simulation/vehicle/contactResolution.js +20 -6
- package/src/simulation/vehicle/mainTrackWheelSurface.js +2 -13
- package/src/simulation/vehicle/pitWheelSurface.js +0 -12
- package/src/simulation/vehicle/runoffResponse.js +2 -4
- package/src/simulation/vehicle/sampledWheelSurface.js +266 -0
- package/src/simulation/vehicle/vehicleGeometry.js +0 -56
- package/src/simulation/vehicle/vehicleKinematics.js +10 -0
- package/src/simulation/vehicle/vehiclePhysics.js +35 -194
- package/src/simulation/vehicle/vehicleSnapshots.js +13 -4
- package/src/simulation/vehicle/vehicleState.js +2 -1
- package/src/simulation/vehicle/wheelSurface.js +13 -432
- package/src/simulation/vehicle/wheelSurfaceCache.js +55 -0
- package/src/simulation/vehicle/wheelSurfaceSummary.js +101 -0
- package/src/styles.css +173 -19
- package/src/ui/bannerTemplates.js +4 -2
- package/src/ui/cameraControlsTemplate.js +4 -2
- package/src/ui/gridWalkTemplate.js +29 -0
- package/src/ui/htmlEscaping.js +8 -0
- package/src/ui/shellTemplate.js +3 -0
- package/src/ui/telemetryDrawerTemplate.js +4 -2
- package/src/ui/telemetryTemplates.js +3 -22
- package/src/ui/templateUtils.js +2 -8
- package/src/ui/viewControlsTemplate.js +21 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { clamp, normalizeAngle } from '../simMath.js';
|
|
2
|
+
import { kphToSimSpeed, metersToSimUnits, simUnitsToMeters } from '../units.js';
|
|
3
|
+
import { pointAtInto } from '../track/trackModel.js';
|
|
4
|
+
import { VEHICLE_LIMITS, tirePerformanceFactor, arcadeTrackCornerSpeedLimit, arcadeSurfaceGrip } from '../vehicle/vehiclePhysics.js';
|
|
5
|
+
import { mainTrackCenterOffset } from './trafficScan.js';
|
|
6
|
+
import { createDriverInput } from './driverInput.js';
|
|
7
|
+
import { angleToPoint } from './driverMath.js';
|
|
8
|
+
import { calculateTrackEdgeGuard } from './edgeRecovery.js';
|
|
9
|
+
import { calculateRacingTrafficPenalty, planRacingLine } from './racingLinePlan.js';
|
|
10
|
+
import { arcadePreviewLookahead, arcadePreviewSpeed, arcadeRacingLookahead, calculateRacingLineOffset, maxLookaheadCurvature } from './arcadeRacePace.js';
|
|
11
|
+
import { arcadeRacingSpeedControls, arcadeLongitudinalLimits } from './arcadeRacingEnvelope.js';
|
|
12
|
+
import { racingDriverProfile } from './racingDriverProfile.js';
|
|
13
|
+
|
|
14
|
+
const TARGET_BASE_SCRATCH = { x: 0, y: 0, heading: 0, normalX: 0, normalY: 0, curvature: 0, distance: 0 };
|
|
15
|
+
const TARGET_POINT_SCRATCH = { x: 0, y: 0 };
|
|
16
|
+
|
|
17
|
+
export function decideArcadeRacingControls(car, orderIndex, race) {
|
|
18
|
+
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
19
|
+
const profile = racingDriverProfile(car);
|
|
20
|
+
const edgeGuard = calculateTrackEdgeGuard(car, race);
|
|
21
|
+
const previewLookahead = arcadePreviewLookahead(car.speed);
|
|
22
|
+
const previewCurvature = maxLookaheadCurvature(race.track, car.progress, previewLookahead);
|
|
23
|
+
const lookahead = arcadeRacingLookahead(car.speed);
|
|
24
|
+
const targetBase = pointAtInto(race.track, car.progress + lookahead, TARGET_BASE_SCRATCH);
|
|
25
|
+
const curvature = Math.max(car.trackState.curvature, targetBase.curvature, previewCurvature);
|
|
26
|
+
const lanePlan = planRacingLine(car, orderIndex, race);
|
|
27
|
+
const cornerOffsetDamping = clamp(curvature / 0.0012, 0, 0.58);
|
|
28
|
+
const recoveryOffset = edgeGuard.side * edgeGuard.recoveryOffset;
|
|
29
|
+
const racingOffset = calculateRacingLineOffset(car, race, lookahead, curvature, edgeGuard);
|
|
30
|
+
const trafficPressure = lanePlan.attackCommitted || lanePlan.sameLaneAhead || lanePlan.actualLaneAhead || lanePlan.sideRisk ? 1 : 0;
|
|
31
|
+
const racingLineWeight = lanePlan.attackCommitted ? 0.18 : trafficPressure ? 0.52 : 1;
|
|
32
|
+
const cornerOffsetFactor = 1 - cornerOffsetDamping * (lanePlan.attackCommitted ? 0.28 : trafficPressure ? 0.58 : 1);
|
|
33
|
+
const plannedTargetOffset = clamp(
|
|
34
|
+
(lanePlan.offset + racingOffset * racingLineWeight + metersToSimUnits(profile.lineErrorMeters)) * cornerOffsetFactor * (1 - edgeGuard.pressure * 0.8) +
|
|
35
|
+
recoveryOffset * edgeGuard.pressure * 0.8,
|
|
36
|
+
-edgeGuard.recoveryOffset,
|
|
37
|
+
edgeGuard.recoveryOffset,
|
|
38
|
+
);
|
|
39
|
+
// A committed passing lane must unwind with the corner. Moving laterally
|
|
40
|
+
// while the current turn consumes most grip sends a safe speed target off
|
|
41
|
+
// the road. Edge recovery retains priority over holding the current line.
|
|
42
|
+
const cornerHold = trafficPressure && edgeGuard.pressure < 0.2
|
|
43
|
+
? clamp((car.gripUsage ?? 0) / 0.7, 0, 1) ** 2 : 0;
|
|
44
|
+
const currentOffset = mainTrackCenterOffset(car);
|
|
45
|
+
const targetOffset = clamp(
|
|
46
|
+
currentOffset + (plannedTargetOffset - currentOffset) * (1 - cornerHold),
|
|
47
|
+
-edgeGuard.recoveryOffset,
|
|
48
|
+
edgeGuard.recoveryOffset,
|
|
49
|
+
);
|
|
50
|
+
TARGET_POINT_SCRATCH.x = targetBase.x + targetBase.normalX * targetOffset;
|
|
51
|
+
TARGET_POINT_SCRATCH.y = targetBase.y + targetBase.normalY * targetOffset;
|
|
52
|
+
const angleError = angleToPoint(car, TARGET_POINT_SCRATCH);
|
|
53
|
+
const headingError = normalizeAngle(targetBase.heading - car.heading);
|
|
54
|
+
const steeringLimit = car.trackState.surface === 'kerb' ? VEHICLE_LIMITS.maxSteer * 0.96 : VEHICLE_LIMITS.maxSteer;
|
|
55
|
+
// Invert the arcade bicycle yaw law for the curvature to the chosen lane point.
|
|
56
|
+
// Tire wear scales yaw response in the integrator, so it also scales this inverse.
|
|
57
|
+
const pursuitCurve = 2 * Math.sin(angleError) / Math.max(
|
|
58
|
+
Math.hypot(TARGET_POINT_SCRATCH.x - car.x, TARGET_POINT_SCRATCH.y - car.y),
|
|
59
|
+
metersToSimUnits(1),
|
|
60
|
+
);
|
|
61
|
+
const steeringRequest = clamp(
|
|
62
|
+
Math.atan(VEHICLE_LIMITS.wheelbase * pursuitCurve / tirePerformanceFactor(car.tireEnergy ?? 100)),
|
|
63
|
+
-steeringLimit,
|
|
64
|
+
steeringLimit,
|
|
65
|
+
);
|
|
66
|
+
const cornerTargetKph = arcadePreviewSpeed(car, race.track, previewLookahead, aggression, profile);
|
|
67
|
+
const edgePenalty = edgeGuard.pressure > 0.42
|
|
68
|
+
? simUnitsToMeters(Math.max(0, car.trackState.crossTrackError - edgeGuard.softLimit)) *
|
|
69
|
+
(5.8 - aggression * 1.1) + edgeGuard.overLimitPressure * 48
|
|
70
|
+
: edgeGuard.overLimitPressure * 48;
|
|
71
|
+
const steeringPenalty = clamp((Math.abs(angleError) - 0.38) * 24, 0, 12);
|
|
72
|
+
const headingPenalty = clamp((Math.abs(headingError) - 0.48) * 42, 0, 24);
|
|
73
|
+
const recoveryAlignmentPenalty = edgeGuard.pressure > 0.2
|
|
74
|
+
? clamp((Math.abs(headingError) - 0.46) * 64, 0, 38)
|
|
75
|
+
: 0;
|
|
76
|
+
const steeringLoadPenalty = clamp(
|
|
77
|
+
(Math.abs(steeringRequest) / Math.max(steeringLimit, 1e-6) - 0.82) * 42,
|
|
78
|
+
0,
|
|
79
|
+
18,
|
|
80
|
+
) * clamp(Math.abs(headingError) / 0.48, 0, 1);
|
|
81
|
+
const trafficPenalty = calculateRacingTrafficPenalty(car, lanePlan) * (1 - aggression * 0.28);
|
|
82
|
+
const minimumDesiredSpeedKph = edgeGuard.pressure > 0.55 ? 34 : car.trackState.surface === 'kerb' ? 58 : 52;
|
|
83
|
+
const desiredSpeedKph = clamp(
|
|
84
|
+
cornerTargetKph - edgePenalty - steeringPenalty - headingPenalty - recoveryAlignmentPenalty - steeringLoadPenalty - trafficPenalty,
|
|
85
|
+
minimumDesiredSpeedKph,
|
|
86
|
+
330,
|
|
87
|
+
);
|
|
88
|
+
// Corner capacity and physical traffic take precedence over maneuvering
|
|
89
|
+
// floors: a stopped leader may require the car to stop until the pass clears.
|
|
90
|
+
const desiredSpeed = Math.min(
|
|
91
|
+
metersToSimUnits(lanePlan.followingSpeed),
|
|
92
|
+
kphToSimSpeed(cornerTargetKph),
|
|
93
|
+
metersToSimUnits(arcadeTrackCornerSpeedLimit(car, pursuitCurve * metersToSimUnits(1), profile.lateralShare * arcadeSurfaceGrip(car))),
|
|
94
|
+
clamp(kphToSimSpeed(desiredSpeedKph), kphToSimSpeed(minimumDesiredSpeedKph), VEHICLE_LIMITS.maxSpeed),
|
|
95
|
+
);
|
|
96
|
+
const pedals = arcadeRacingSpeedControls(car, simUnitsToMeters(desiredSpeed), profile);
|
|
97
|
+
const brakeLimit = arcadeLongitudinalLimits(car).brakeDeceleration * car.mass / car.brakeNewtons;
|
|
98
|
+
let brakeAmount = pedals.brake;
|
|
99
|
+
if (edgeGuard.pressure > 0.46 && car.speed > kphToSimSpeed(48)) {
|
|
100
|
+
brakeAmount = Math.max(brakeAmount, clamp((edgeGuard.pressure - 0.42) * 0.9, 0, brakeLimit));
|
|
101
|
+
}
|
|
102
|
+
const recoveryThrottleScale = edgeGuard.pressure > 0.24
|
|
103
|
+
? 1 - clamp((Math.abs(headingError) - 0.38) / 0.92, 0, 0.82)
|
|
104
|
+
: 1;
|
|
105
|
+
|
|
106
|
+
return createDriverInput()
|
|
107
|
+
.steer(steeringRequest)
|
|
108
|
+
.accelerate(brakeAmount > 0 ? 0 : pedals.throttle * recoveryThrottleScale)
|
|
109
|
+
.brake(brakeAmount)
|
|
110
|
+
.controls();
|
|
111
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { clamp } from '../simMath.js';
|
|
2
|
+
import { metersToSimUnits, simUnitsToMeters } from '../units.js';
|
|
3
|
+
import {
|
|
4
|
+
arcadeAccelerationLimit,
|
|
5
|
+
arcadeBrakingLimit,
|
|
6
|
+
arcadeResistanceForce,
|
|
7
|
+
arcadeSurfaceGrip,
|
|
8
|
+
tirePerformanceFactor,
|
|
9
|
+
VEHICLE_LIMITS,
|
|
10
|
+
} from '../vehicle/vehiclePhysics.js';
|
|
11
|
+
import { racingDriverProfile } from './racingDriverProfile.js';
|
|
12
|
+
|
|
13
|
+
const SPEED_RESPONSE_SECONDS = 0.0625;
|
|
14
|
+
|
|
15
|
+
function brakeDecelerationAt(car, speedSim, grip) {
|
|
16
|
+
return Math.min(car.brakeNewtons / car.mass, simUnitsToMeters(arcadeBrakingLimit(speedSim, grip)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// All accelerations/speeds here are SI. Arcade has independent longitudinal
|
|
20
|
+
// and yaw limits, so this envelope does not add a fictional friction circle.
|
|
21
|
+
export function arcadeLongitudinalLimits(car, speedMps = simUnitsToMeters(car.speed)) {
|
|
22
|
+
const speed = clamp(speedMps, 0, simUnitsToMeters(VEHICLE_LIMITS.maxSpeed));
|
|
23
|
+
const speedSim = metersToSimUnits(speed);
|
|
24
|
+
const speedRatio = clamp(speedSim / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
25
|
+
const grip = arcadeSurfaceGrip(car) * tirePerformanceFactor(car.tireEnergy ?? 100);
|
|
26
|
+
const engineForce = car.powerNewtons * (car.drsActive ? 1.08 : 1) *
|
|
27
|
+
Math.max(0.12, 1 - speedRatio ** 1.15);
|
|
28
|
+
const drsBonus = car.drsActive ? 1.06 : 1;
|
|
29
|
+
return {
|
|
30
|
+
speed,
|
|
31
|
+
engineForce,
|
|
32
|
+
drsBonus,
|
|
33
|
+
driveAcceleration: Math.min(engineForce / car.mass, simUnitsToMeters(arcadeAccelerationLimit(speedSim, grip))) * drsBonus,
|
|
34
|
+
brakeDeceleration: brakeDecelerationAt(car, speedSim, grip),
|
|
35
|
+
resistanceDeceleration: arcadeResistanceForce(car, speed) / car.mass,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function arcadeBrakingDeceleration(car, speedMps, profile = racingDriverProfile(car)) {
|
|
40
|
+
const speed = clamp(speedMps, 0, simUnitsToMeters(VEHICLE_LIMITS.maxSpeed));
|
|
41
|
+
const grip = arcadeSurfaceGrip(car) * tirePerformanceFactor(car.tireEnergy ?? 100);
|
|
42
|
+
// Preview needs only braking, not engine taper or acceleration capacity.
|
|
43
|
+
return brakeDecelerationAt(car, metersToSimUnits(speed), grip) * clamp(profile.longitudinalShare, 0, 1) +
|
|
44
|
+
arcadeResistanceForce(car, speed) / car.mass;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function arcadeRacingSpeedControls(car, targetSpeedMps, profile = racingDriverProfile(car)) {
|
|
48
|
+
const limits = arcadeLongitudinalLimits(car);
|
|
49
|
+
const target = clamp(targetSpeedMps, 0, simUnitsToMeters(VEHICLE_LIMITS.maxSpeed));
|
|
50
|
+
const share = clamp(profile.longitudinalShare, 0, 1);
|
|
51
|
+
// A single signed force request includes only resistance needed to hold a
|
|
52
|
+
// moving target. Stopping must not request throttle to overcome rolling drag.
|
|
53
|
+
const demand = car.mass * ((target - limits.speed) / SPEED_RESPONSE_SECONDS +
|
|
54
|
+
(target > 0 ? limits.resistanceDeceleration : 0));
|
|
55
|
+
const fullEngineForce = Math.max(limits.engineForce * limits.drsBonus, 1e-9);
|
|
56
|
+
const fullBrakeForce = Math.max(car.brakeNewtons, 1e-9);
|
|
57
|
+
return {
|
|
58
|
+
throttle: clamp(demand / fullEngineForce, 0,
|
|
59
|
+
Math.min(1, limits.driveAcceleration * car.mass * share / fullEngineForce)),
|
|
60
|
+
brake: clamp(-demand / fullBrakeForce, 0,
|
|
61
|
+
Math.min(1, limits.brakeDeceleration * car.mass * share / fullBrakeForce)),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { metersToSimUnits } from '../units.js';
|
|
2
2
|
|
|
3
3
|
export const LANE_OFFSETS = [-8, -5.6, -3.2, 0, 3.2, 5.6, 8].map(metersToSimUnits);
|
|
4
|
-
export const EDGE_RECOVERY_MIN_LOOKAHEAD = metersToSimUnits(28);
|
|
5
4
|
export const LOOKAHEAD_BASE_DISTANCE = metersToSimUnits(52);
|
|
6
|
-
export const LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(115);
|
|
7
5
|
export const BRAKING_LOOKAHEAD_MAX_DISTANCE = metersToSimUnits(560);
|
|
8
6
|
export const TRAFFIC_GAP_AHEAD = metersToSimUnits(58);
|
|
9
7
|
export const TRAFFIC_SIDE_GAP = metersToSimUnits(4.1);
|
|
@@ -5,6 +5,7 @@ import { decideRejoinControls } from './rejoinControls.js';
|
|
|
5
5
|
import { shouldContinueRejoinRecovery } from './edgeRecovery.js';
|
|
6
6
|
import { decideSafetyCarControls } from './safetyCarControls.js';
|
|
7
7
|
import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
|
|
8
|
+
import { normalizeAngle } from '../simMath.js';
|
|
8
9
|
|
|
9
10
|
export function decideDriverControls({ car, orderIndex, race }) {
|
|
10
11
|
if (car.manualControls) return car.manualControls;
|
|
@@ -23,6 +24,15 @@ export function decideDriverControls({ car, orderIndex, race }) {
|
|
|
23
24
|
return decideRejoinControls(car, race);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
// Forward path pursuit is ambiguous when the car faces away from the road.
|
|
28
|
+
// Let the existing recovery controller turn it around before resuming pace.
|
|
29
|
+
if (!isSimulatorPhysicsMode(race.physicsMode) &&
|
|
30
|
+
Math.abs(normalizeAngle(car.heading - car.trackState.heading)) > Math.PI / 2) {
|
|
31
|
+
car.rejoinRecoveryFrames = REJOIN_HOLD_FRAMES;
|
|
32
|
+
car.attackCommitmentFrames = 0;
|
|
33
|
+
return decideRejoinControls(car, race);
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
if (shouldStabilizeSimulatorCar(car, race)) {
|
|
27
37
|
car.rejoinRecoveryFrames = Math.max(car.rejoinRecoveryFrames ?? 0, Math.floor(REJOIN_HOLD_FRAMES * 0.45));
|
|
28
38
|
car.attackCommitmentFrames = 0;
|
|
@@ -38,10 +48,11 @@ export function decideDriverControls({ car, orderIndex, race }) {
|
|
|
38
48
|
|
|
39
49
|
function shouldStabilizeSimulatorCar(car, race) {
|
|
40
50
|
if (!isSimulatorPhysicsMode(race.physicsMode)) return false;
|
|
41
|
-
const gripUsage = car.gripUsage ?? 0;
|
|
42
51
|
const slipAngle = Math.abs(car.slipAngleRadians ?? 0);
|
|
43
52
|
const edgeDistance = race.track.width / 2 - VEHICLE_LIMITS.carWidth * 2.25;
|
|
44
53
|
const nearEdge = (car.trackState?.crossTrackError ?? 0) > edgeDistance;
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
// Tire force demand can exceed capacity during a straight launch. Recovery
|
|
55
|
+
// responds to lost directional stability, not longitudinal demand alone.
|
|
56
|
+
const unstable = car.stabilityState === 'spin-risk' || slipAngle > 0.24;
|
|
57
|
+
return unstable && (nearEdge || slipAngle > 0.34);
|
|
47
58
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { clamp, normalizeAngle } from '../simMath.js';
|
|
2
|
-
import { metersToSimUnits } from '../units.js';
|
|
3
2
|
import { pointAt } from '../track/trackModel.js';
|
|
4
3
|
import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
|
|
5
4
|
import { REJOIN_LOOKAHEAD_BASE, REJOIN_LOOKAHEAD_MAX } from './driverControlConstants.js';
|
|
@@ -1,409 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { VEHICLE_LIMITS, isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
|
|
5
|
-
import { BRAKING_LOOKAHEAD_MAX_DISTANCE, CURVATURE_LOOKAHEAD_SAMPLES, EDGE_RECOVERY_MIN_LOOKAHEAD, LOOKAHEAD_BASE_DISTANCE, LOOKAHEAD_MAX_DISTANCE, OVERTAKE_LATERAL_MIN, PREVIEW_CURVATURE_CAP, TRAFFIC_GAP_AHEAD, TRAFFIC_SIDE_GAP } from './driverControlConstants.js';
|
|
6
|
-
import { createDriverInput } from './driverInput.js';
|
|
7
|
-
import { angleToPoint } from './driverMath.js';
|
|
8
|
-
import { calculateTrackEdgeGuard } from './edgeRecovery.js';
|
|
9
|
-
import { calculateActualOverlapPenalty, calculatePlannedTrafficPenalty, planRacingLine } from './racingLinePlan.js';
|
|
10
|
-
import { analyzeTrackEdgeMotion } from './recoveryDynamics.js';
|
|
1
|
+
import { isSimulatorPhysicsMode } from '../vehicle/vehiclePhysics.js';
|
|
2
|
+
import { decideArcadeRacingControls } from './arcadeRacingControls.js';
|
|
3
|
+
import { decideAdvancedRacingControls } from './advancedRacingControls.js';
|
|
11
4
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return decideArcadeRacingControls(car, orderIndex, race);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const ARCADE_CONTROL_PROFILE = {
|
|
20
|
-
simulatorMode: false,
|
|
21
|
-
baseLookaheadSpeedFactor: (aggression) => 1.62 - aggression * 0.12,
|
|
22
|
-
previewLookaheadSpeedFactor: 3.15,
|
|
23
|
-
edgeLookaheadPenaltyMeters: 18,
|
|
24
|
-
cornerPressureCurvature: 0.001,
|
|
25
|
-
edgeLineDamping: 0.8,
|
|
26
|
-
edgeRecoveryWeight: 0.8,
|
|
27
|
-
headingFeedForwardBase: (carEntry, edgeGuard) => 0.34 + carEntry.racecraft * 0.18 + edgeGuard.pressure * 0.12,
|
|
28
|
-
headingFeedForwardLimit: 0.42,
|
|
29
|
-
pathSteerGain: (carEntry, edgeGuard) => 0.72 + carEntry.racecraft * 0.12 + edgeGuard.pressure * 0.42,
|
|
30
|
-
edgeRecoveryStart: 0.22,
|
|
31
|
-
edgeRecoveryGain: 1.18,
|
|
32
|
-
edgeRecoveryOverLimitGain: 0.55,
|
|
33
|
-
edgeRecoveryLimit: 0.52,
|
|
34
|
-
steeringLimitScale: () => 1,
|
|
35
|
-
gripBudget: (carEntry, aggression) => 14.8 + carEntry.racecraft * 3.4 + (carEntry.tireEnergy ?? 100) * 0.011 + aggression * 1.8,
|
|
36
|
-
cornerSpeedMultiplier: (aggression) => 1.12 + aggression * 0.08,
|
|
37
|
-
paceBonus: (carEntry) => (carEntry.pace - 1) * 20,
|
|
38
|
-
aggressionSpeedBonus: (aggression) => aggression * 10,
|
|
39
|
-
minCornerSpeedKph: 96,
|
|
40
|
-
maxCornerSpeedKph: 318,
|
|
41
|
-
edgePenaltyMetersScale: (aggression) => 5.8 - aggression * 1.1,
|
|
42
|
-
edgePenaltyPressure: 48,
|
|
43
|
-
edgePressureSpeedPenalty: 0,
|
|
44
|
-
edgeOutwardSpeedPenalty: 0,
|
|
45
|
-
edgeHeadingOutwardPenalty: 0,
|
|
46
|
-
steeringPenaltyStart: 0.38,
|
|
47
|
-
steeringPenaltyGain: 24,
|
|
48
|
-
steeringPenaltyMax: 12,
|
|
49
|
-
headingPenaltyStart: 0.48,
|
|
50
|
-
headingPenaltyGain: 42,
|
|
51
|
-
headingPenaltyMax: 24,
|
|
52
|
-
recoveryHeadingStart: 0.46,
|
|
53
|
-
recoveryHeadingGain: 64,
|
|
54
|
-
recoveryHeadingMax: 38,
|
|
55
|
-
steeringLoadStart: 0.82,
|
|
56
|
-
steeringLoadMax: 18,
|
|
57
|
-
steeringLoadHeadingScale: 0.48,
|
|
58
|
-
loadPenalty: () => 0,
|
|
59
|
-
minimumSpeeds: { edge: 34, kerb: 58, track: 52 },
|
|
60
|
-
desiredSpeedMaxKph: 330,
|
|
61
|
-
minimumThrottle: (aggression) => 0.1 + aggression * 0.08,
|
|
62
|
-
brakeLimits: { kerb: (aggression) => 0.42, track: (aggression) => 0.72 - aggression * 0.08 },
|
|
63
|
-
brakeResponseKph: (aggression) => 28 + aggression * 11,
|
|
64
|
-
extraGripBrake: () => 0,
|
|
65
|
-
edgeBrakeLimitScale: 1,
|
|
66
|
-
edgeSlideBrakeStart: 1.1,
|
|
67
|
-
edgeSlideBrakeGain: 0,
|
|
68
|
-
edgeSlideVelocityBrakeGain: 0,
|
|
69
|
-
edgeSlideHeadingBrake: 0,
|
|
70
|
-
edgeSlideBrakeLimitScale: 0,
|
|
71
|
-
edgeSlideThrottleDamping: 0,
|
|
72
|
-
recoveryHeadingThrottleStart: 0.38,
|
|
73
|
-
throttleResponseKph: 16,
|
|
74
|
-
throttleScale: () => 1,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
export function decideArcadeRacingControls(car, orderIndex, race) {
|
|
78
|
-
return decideRacingControlsForMode(car, orderIndex, race, ARCADE_CONTROL_PROFILE);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const SIMULATOR_CONTROL_PROFILE = {
|
|
82
|
-
simulatorMode: true,
|
|
83
|
-
baseLookaheadSpeedFactor: (aggression) => 2.45 - aggression * 0.06,
|
|
84
|
-
previewLookaheadSpeedFactor: 6.15,
|
|
85
|
-
edgeLookaheadPenaltyMeters: 34,
|
|
86
|
-
cornerPressureCurvature: 0.00072,
|
|
87
|
-
edgeLineDamping: 1.55,
|
|
88
|
-
edgeRecoveryWeight: 1.45,
|
|
89
|
-
headingFeedForwardBase: (carEntry, edgeGuard) => 0.2 + carEntry.racecraft * 0.08 + edgeGuard.pressure * 0.07,
|
|
90
|
-
headingFeedForwardLimit: 0.25,
|
|
91
|
-
pathSteerGain: (carEntry, edgeGuard) => 0.46 + carEntry.racecraft * 0.06 + edgeGuard.pressure * 0.18,
|
|
92
|
-
edgeRecoveryStart: 0.04,
|
|
93
|
-
edgeRecoveryGain: 3.1,
|
|
94
|
-
edgeRecoveryOverLimitGain: 1.15,
|
|
95
|
-
edgeRecoveryLimit: 0.78,
|
|
96
|
-
steeringLimitScale: (carEntry, speedRatio, gripUsage) => clamp(1 - speedRatio * 0.55 - gripUsage * 0.14, 0.26, 1),
|
|
97
|
-
gripBudget: (carEntry, aggression) => 10.6 + carEntry.racecraft * 1.55 + (carEntry.tireEnergy ?? 100) * 0.006 + aggression * 0.55,
|
|
98
|
-
cornerSpeedMultiplier: (aggression) => 0.93 + aggression * 0.035,
|
|
99
|
-
paceBonus: (carEntry) => (carEntry.pace - 1) * 12,
|
|
100
|
-
aggressionSpeedBonus: (aggression) => aggression * 4,
|
|
101
|
-
minCornerSpeedKph: 58,
|
|
102
|
-
maxCornerSpeedKph: 292,
|
|
103
|
-
edgePenaltyMetersScale: (aggression) => 10.5 - aggression * 0.8,
|
|
104
|
-
edgePenaltyPressure: 92,
|
|
105
|
-
edgePressureSpeedPenalty: 32,
|
|
106
|
-
edgeOutwardSpeedPenalty: 30,
|
|
107
|
-
edgeHeadingOutwardPenalty: 22,
|
|
108
|
-
steeringPenaltyStart: 0.27,
|
|
109
|
-
steeringPenaltyGain: 42,
|
|
110
|
-
steeringPenaltyMax: 28,
|
|
111
|
-
headingPenaltyStart: 0.32,
|
|
112
|
-
headingPenaltyGain: 62,
|
|
113
|
-
headingPenaltyMax: 42,
|
|
114
|
-
recoveryHeadingStart: 0.28,
|
|
115
|
-
recoveryHeadingGain: 86,
|
|
116
|
-
recoveryHeadingMax: 58,
|
|
117
|
-
steeringLoadStart: 0.58,
|
|
118
|
-
steeringLoadMax: 36,
|
|
119
|
-
steeringLoadHeadingScale: 0.32,
|
|
120
|
-
loadPenalty: (entry, gripUsage, slipAngle, cornerPressure, outwardDriftAngle) =>
|
|
121
|
-
clamp((gripUsage - 0.52) * 92, 0, 54) +
|
|
122
|
-
clamp(slipAngle * 130, 0, 42) +
|
|
123
|
-
cornerPressure * 18 +
|
|
124
|
-
clamp(outwardDriftAngle * 330, 0, 62),
|
|
125
|
-
minimumSpeeds: { edge: 44, kerb: 58, track: 76 },
|
|
126
|
-
desiredSpeedMaxKph: 302,
|
|
127
|
-
minimumThrottle: (aggression) => 0.03 + aggression * 0.03,
|
|
128
|
-
brakeLimits: { kerb: (aggression) => 0.36, track: (aggression) => 0.9 - aggression * 0.03 },
|
|
129
|
-
brakeResponseKph: (aggression) => 15 + aggression * 5,
|
|
130
|
-
extraGripBrake: (gripUsage, slipAngle, brakeLimit) => clamp((gripUsage - 0.68) * 0.42 + slipAngle * 0.5, 0, brakeLimit * 0.6),
|
|
131
|
-
edgeBrakeLimitScale: 1.08,
|
|
132
|
-
edgeSlideBrakeStart: 0.22,
|
|
133
|
-
edgeSlideBrakeGain: 0.78,
|
|
134
|
-
edgeSlideVelocityBrakeGain: 0.12,
|
|
135
|
-
edgeSlideHeadingBrake: 0.1,
|
|
136
|
-
edgeSlideBrakeLimitScale: 0.86,
|
|
137
|
-
edgeSlideThrottleDamping: 0.9,
|
|
138
|
-
recoveryHeadingThrottleStart: 0.3,
|
|
139
|
-
throttleResponseKph: 18,
|
|
140
|
-
throttleScale: (steeringLoad, gripUsage, slipAngle, edgeGuard) => clamp(
|
|
141
|
-
1 - steeringLoad * 0.42 - Math.max(0, gripUsage - 0.58) * 0.58 - slipAngle * 1.25 - edgeGuard.pressure * 0.42,
|
|
142
|
-
0,
|
|
143
|
-
1,
|
|
144
|
-
),
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export function decideSimulatorRacingControls(car, orderIndex, race) {
|
|
148
|
-
return decideRacingControlsForMode(car, orderIndex, race, SIMULATOR_CONTROL_PROFILE);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function decideRacingControlsForMode(car, orderIndex, race, profile) {
|
|
152
|
-
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
153
|
-
const simulatorMode = profile.simulatorMode;
|
|
154
|
-
const edgeGuard = calculateTrackEdgeGuard(car, race);
|
|
155
|
-
const edgeMotion = analyzeTrackEdgeMotion(car, race);
|
|
156
|
-
const gripUsage = Number.isFinite(car.gripUsage) ? car.gripUsage : 0;
|
|
157
|
-
const slipAngle = Math.abs(car.slipAngleRadians ?? 0);
|
|
158
|
-
const baseLookahead = clamp(
|
|
159
|
-
car.speed * profile.baseLookaheadSpeedFactor(aggression) +
|
|
160
|
-
LOOKAHEAD_BASE_DISTANCE -
|
|
161
|
-
edgeGuard.pressure * metersToSimUnits(profile.edgeLookaheadPenaltyMeters),
|
|
162
|
-
EDGE_RECOVERY_MIN_LOOKAHEAD,
|
|
163
|
-
LOOKAHEAD_MAX_DISTANCE,
|
|
164
|
-
);
|
|
165
|
-
const previewLookahead = clamp(
|
|
166
|
-
car.speed * profile.previewLookaheadSpeedFactor + LOOKAHEAD_BASE_DISTANCE,
|
|
167
|
-
baseLookahead,
|
|
168
|
-
BRAKING_LOOKAHEAD_MAX_DISTANCE,
|
|
169
|
-
);
|
|
170
|
-
const previewCurvature = maxLookaheadCurvature(race.track, car.progress, previewLookahead);
|
|
171
|
-
const cornerPressure = clamp(previewCurvature / profile.cornerPressureCurvature, 0, 1);
|
|
172
|
-
const lookahead = clamp(
|
|
173
|
-
baseLookahead * (1 - cornerPressure * 0.46),
|
|
174
|
-
EDGE_RECOVERY_MIN_LOOKAHEAD,
|
|
175
|
-
LOOKAHEAD_MAX_DISTANCE,
|
|
176
|
-
);
|
|
177
|
-
const targetBase = pointAtInto(race.track, car.progress + lookahead, TARGET_BASE_SCRATCH);
|
|
178
|
-
const curvature = Math.max(
|
|
179
|
-
car.trackState.curvature,
|
|
180
|
-
targetBase.curvature,
|
|
181
|
-
previewCurvature,
|
|
182
|
-
);
|
|
183
|
-
const lanePlan = planRacingLine(car, orderIndex, race);
|
|
184
|
-
const cornerOffsetDamping = clamp(curvature / 0.0012, 0, 0.58);
|
|
185
|
-
const recoveryOffset = edgeGuard.side * edgeGuard.recoveryOffset;
|
|
186
|
-
const racingOffset = calculateRacingLineOffset(car, race, lookahead, curvature, edgeGuard);
|
|
187
|
-
const trafficPressure = lanePlan.attackCommitted || lanePlan.sameLaneAhead || lanePlan.actualLaneAhead || lanePlan.sideRisk ? 1 : 0;
|
|
188
|
-
const racingLineWeight = lanePlan.attackCommitted ? 0.18 : trafficPressure ? 0.52 : 1;
|
|
189
|
-
const cornerOffsetFactor = 1 - cornerOffsetDamping * (lanePlan.attackCommitted ? 0.28 : trafficPressure ? 0.58 : 1);
|
|
190
|
-
const targetOffset = clamp(
|
|
191
|
-
(lanePlan.offset + racingOffset * racingLineWeight) * cornerOffsetFactor * (1 - edgeGuard.pressure * profile.edgeLineDamping) +
|
|
192
|
-
recoveryOffset * edgeGuard.pressure * profile.edgeRecoveryWeight,
|
|
193
|
-
-edgeGuard.recoveryOffset,
|
|
194
|
-
edgeGuard.recoveryOffset,
|
|
195
|
-
);
|
|
196
|
-
TARGET_POINT_SCRATCH.x = targetBase.x + targetBase.normalX * targetOffset;
|
|
197
|
-
TARGET_POINT_SCRATCH.y = targetBase.y + targetBase.normalY * targetOffset;
|
|
198
|
-
const angleError = angleToPoint(car, TARGET_POINT_SCRATCH);
|
|
199
|
-
const headingError = normalizeAngle(targetBase.heading - car.heading);
|
|
200
|
-
const lateralError = car.trackState?.signedOffset ?? 0;
|
|
201
|
-
const outwardDriftAngle = simulatorMode &&
|
|
202
|
-
Math.sign(lateralError) !== 0 &&
|
|
203
|
-
Math.sign(normalizeAngle(car.heading - (car.trackState?.heading ?? targetBase.heading))) === Math.sign(lateralError)
|
|
204
|
-
? Math.abs(normalizeAngle(car.heading - (car.trackState?.heading ?? targetBase.heading)))
|
|
205
|
-
: 0;
|
|
206
|
-
const cornerCommitment = clamp(Math.abs(headingError) / 0.9, 0, 0.72);
|
|
207
|
-
const lateralCorrection = -Math.atan2(
|
|
208
|
-
lateralError * (0.85 + edgeGuard.pressure * 0.5),
|
|
209
|
-
Math.max(car.speed, kphToSimSpeed(35)),
|
|
210
|
-
) * (1 - cornerCommitment * (simulatorMode ? 0.78 : 0.62));
|
|
211
|
-
const headingFeedForward = clamp(
|
|
212
|
-
headingError * profile.headingFeedForwardBase(car, edgeGuard),
|
|
213
|
-
-profile.headingFeedForwardLimit,
|
|
214
|
-
profile.headingFeedForwardLimit,
|
|
215
|
-
);
|
|
216
|
-
let pathSteer = angleError * profile.pathSteerGain(car, edgeGuard) + headingFeedForward;
|
|
217
|
-
if (edgeGuard.side !== 0 && Math.sign(pathSteer) === edgeGuard.side) {
|
|
218
|
-
pathSteer *= 1 - edgeGuard.pressure * 0.85;
|
|
219
|
-
}
|
|
220
|
-
const edgeRecoverySteer = -edgeGuard.side * clamp(
|
|
221
|
-
(edgeGuard.pressure - profile.edgeRecoveryStart) * profile.edgeRecoveryGain +
|
|
222
|
-
edgeGuard.overLimitPressure * profile.edgeRecoveryOverLimitGain +
|
|
223
|
-
(simulatorMode ? outwardDriftAngle * clamp(Math.abs(lateralError) / (race.track.width * 0.26), 0, 1) * 1.8 : 0),
|
|
224
|
-
0,
|
|
225
|
-
profile.edgeRecoveryLimit,
|
|
226
|
-
);
|
|
227
|
-
const speedRatio = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
228
|
-
const simulatorSteeringLimit = VEHICLE_LIMITS.maxSteer *
|
|
229
|
-
profile.steeringLimitScale(car, speedRatio, gripUsage);
|
|
230
|
-
const steeringLimit = simulatorMode
|
|
231
|
-
? (car.trackState.surface === 'kerb' ? simulatorSteeringLimit * 0.82 : simulatorSteeringLimit)
|
|
232
|
-
: car.trackState.surface === 'kerb'
|
|
233
|
-
? VEHICLE_LIMITS.maxSteer * 0.96
|
|
234
|
-
: VEHICLE_LIMITS.maxSteer;
|
|
235
|
-
let steeringRequest = clamp(pathSteer + lateralCorrection + edgeRecoverySteer, -steeringLimit, steeringLimit);
|
|
236
|
-
if (simulatorMode && edgeGuard.pressure > 0.62 && Math.sign(steeringRequest) === edgeGuard.side) {
|
|
237
|
-
steeringRequest = clamp(
|
|
238
|
-
-edgeGuard.side * Math.max(Math.abs(steeringRequest), steeringLimit * 0.55),
|
|
239
|
-
-steeringLimit,
|
|
240
|
-
steeringLimit,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
const cornerRadiusMeters = simUnitsToMeters(1 / Math.max(curvature, 1e-7));
|
|
244
|
-
const lateralGripBudget = profile.gripBudget(car, aggression);
|
|
245
|
-
const cornerTargetKph = clamp(
|
|
246
|
-
Math.sqrt(lateralGripBudget * cornerRadiusMeters) * 3.6 *
|
|
247
|
-
profile.cornerSpeedMultiplier(aggression) +
|
|
248
|
-
profile.paceBonus(car) + profile.aggressionSpeedBonus(aggression),
|
|
249
|
-
profile.minCornerSpeedKph,
|
|
250
|
-
profile.maxCornerSpeedKph,
|
|
251
|
-
);
|
|
252
|
-
const edgePenalty = edgeGuard.pressure > 0.42
|
|
253
|
-
? simUnitsToMeters(Math.max(0, car.trackState.crossTrackError - edgeGuard.softLimit)) *
|
|
254
|
-
profile.edgePenaltyMetersScale(aggression) +
|
|
255
|
-
edgeGuard.overLimitPressure * profile.edgePenaltyPressure
|
|
256
|
-
: edgeGuard.overLimitPressure * profile.edgePenaltyPressure +
|
|
257
|
-
edgeGuard.pressure * profile.edgePressureSpeedPenalty;
|
|
258
|
-
const edgeMotionPenalty =
|
|
259
|
-
edgeMotion.outwardSpeedMps * profile.edgeOutwardSpeedPenalty * clamp(edgeGuard.pressure, 0, 1) +
|
|
260
|
-
(edgeMotion.headingOutward ? profile.edgeHeadingOutwardPenalty * clamp(edgeGuard.pressure, 0, 1) : 0);
|
|
261
|
-
const steeringPenalty = clamp((Math.abs(angleError) - profile.steeringPenaltyStart) * profile.steeringPenaltyGain, 0, profile.steeringPenaltyMax);
|
|
262
|
-
const headingPenalty = clamp((Math.abs(headingError) - profile.headingPenaltyStart) * profile.headingPenaltyGain, 0, profile.headingPenaltyMax);
|
|
263
|
-
const recoveryAlignmentPenalty = edgeGuard.pressure > 0.2
|
|
264
|
-
? clamp((Math.abs(headingError) - profile.recoveryHeadingStart) * profile.recoveryHeadingGain, 0, profile.recoveryHeadingMax)
|
|
265
|
-
: 0;
|
|
266
|
-
const steeringLoadPenalty = clamp(
|
|
267
|
-
(Math.abs(steeringRequest) / Math.max(steeringLimit, 1e-6) - profile.steeringLoadStart) * 42,
|
|
268
|
-
0,
|
|
269
|
-
profile.steeringLoadMax,
|
|
270
|
-
) * clamp(Math.abs(headingError) / profile.steeringLoadHeadingScale, 0, 1);
|
|
271
|
-
const simulatorLoadPenalty = profile.loadPenalty(car, gripUsage, slipAngle, cornerPressure, outwardDriftAngle);
|
|
272
|
-
const actualLaneLateral = lanePlan.actualLaneAhead
|
|
273
|
-
? Math.abs(lanePlan.actualLaneAhead.signedOffset - (car.trackState?.signedOffset ?? 0))
|
|
274
|
-
: Infinity;
|
|
275
|
-
const plannedPassingOverlap = lanePlan.attackCommitted && lanePlan.sameLaneAhead
|
|
276
|
-
? Math.abs(lanePlan.sameLaneAhead.signedOffset - (car.trackState?.signedOffset ?? 0)) > OVERTAKE_LATERAL_MIN
|
|
277
|
-
: false;
|
|
278
|
-
const trafficPenalty = Math.max(
|
|
279
|
-
lanePlan.sameLaneAhead
|
|
280
|
-
? calculatePlannedTrafficPenalty(lanePlan.sameLaneAhead, plannedPassingOverlap)
|
|
281
|
-
: 0,
|
|
282
|
-
lanePlan.actualLaneAhead && actualLaneLateral < OVERTAKE_LATERAL_MIN
|
|
283
|
-
? calculateActualOverlapPenalty(car, lanePlan.actualLaneAhead)
|
|
284
|
-
: 0,
|
|
285
|
-
lanePlan.sideRisk ? clamp(simUnitsToMeters(TRAFFIC_SIDE_GAP - lanePlan.sideRisk.lateral) * 0.78, 0, 30) : 0,
|
|
286
|
-
) * (1 - aggression * 0.28);
|
|
287
|
-
const minimumDesiredSpeedKph = edgeGuard.pressure > 0.55
|
|
288
|
-
? profile.minimumSpeeds.edge
|
|
289
|
-
: car.trackState.surface === 'kerb' ? profile.minimumSpeeds.kerb : profile.minimumSpeeds.track;
|
|
290
|
-
const desiredSpeedKph = clamp(
|
|
291
|
-
(car.drsActive ? cornerTargetKph + 22 : cornerTargetKph) -
|
|
292
|
-
edgePenalty -
|
|
293
|
-
edgeMotionPenalty -
|
|
294
|
-
steeringPenalty -
|
|
295
|
-
headingPenalty -
|
|
296
|
-
recoveryAlignmentPenalty -
|
|
297
|
-
steeringLoadPenalty -
|
|
298
|
-
simulatorLoadPenalty -
|
|
299
|
-
trafficPenalty,
|
|
300
|
-
minimumDesiredSpeedKph,
|
|
301
|
-
profile.desiredSpeedMaxKph,
|
|
302
|
-
);
|
|
303
|
-
const desiredSpeed = clamp(
|
|
304
|
-
kphToSimSpeed(desiredSpeedKph),
|
|
305
|
-
kphToSimSpeed(minimumDesiredSpeedKph),
|
|
306
|
-
VEHICLE_LIMITS.maxSpeed,
|
|
307
|
-
);
|
|
308
|
-
const speedError = desiredSpeed - car.speed;
|
|
309
|
-
const minimumThrottle = edgeGuard.pressure > 0.2 ? 0 : profile.minimumThrottle(aggression);
|
|
310
|
-
const brakeLimit = simulatorMode
|
|
311
|
-
? car.trackState.surface === 'kerb'
|
|
312
|
-
? profile.brakeLimits.kerb(aggression)
|
|
313
|
-
: profile.brakeLimits.track(aggression)
|
|
314
|
-
: car.trackState.surface === 'kerb'
|
|
315
|
-
? profile.brakeLimits.kerb(aggression)
|
|
316
|
-
: profile.brakeLimits.track(aggression);
|
|
317
|
-
let brakeAmount = speedError < -kphToSimSpeed(3)
|
|
318
|
-
? clamp(Math.abs(speedError) / kphToSimSpeed(profile.brakeResponseKph(aggression)), 0, brakeLimit)
|
|
319
|
-
: 0;
|
|
320
|
-
const simulatorBrakeRisk = cornerPressure > 0.15 ||
|
|
321
|
-
edgeGuard.pressure > 0.2 ||
|
|
322
|
-
outwardDriftAngle > 0.03 ||
|
|
323
|
-
car.speed > kphToSimSpeed(132) ||
|
|
324
|
-
speedError < kphToSimSpeed(8);
|
|
325
|
-
if (simulatorMode && simulatorBrakeRisk && (gripUsage > 0.9 || slipAngle > 0.12)) {
|
|
326
|
-
brakeAmount = Math.max(brakeAmount, profile.extraGripBrake(gripUsage, slipAngle, brakeLimit));
|
|
327
|
-
}
|
|
328
|
-
if (simulatorMode && car.speed < kphToSimSpeed(58) && edgeGuard.pressure < 0.75) {
|
|
329
|
-
brakeAmount *= 0.25;
|
|
330
|
-
}
|
|
331
|
-
if (edgeGuard.pressure > 0.46 && car.speed > kphToSimSpeed(48)) {
|
|
332
|
-
brakeAmount = Math.max(brakeAmount, clamp((edgeGuard.pressure - 0.42) * 0.9 * profile.edgeBrakeLimitScale, 0, brakeLimit));
|
|
333
|
-
}
|
|
334
|
-
if (edgeGuard.pressure > profile.edgeSlideBrakeStart && edgeMotion.outwardSpeedMps > 0.25) {
|
|
335
|
-
brakeAmount = Math.max(
|
|
336
|
-
brakeAmount,
|
|
337
|
-
clamp(
|
|
338
|
-
(edgeGuard.pressure - profile.edgeSlideBrakeStart) * profile.edgeSlideBrakeGain +
|
|
339
|
-
edgeMotion.outwardSpeedMps * profile.edgeSlideVelocityBrakeGain +
|
|
340
|
-
(edgeMotion.headingOutward ? profile.edgeSlideHeadingBrake : 0),
|
|
341
|
-
0,
|
|
342
|
-
brakeLimit * profile.edgeSlideBrakeLimitScale,
|
|
343
|
-
),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
const recoveryThrottleScale = edgeGuard.pressure > 0.24
|
|
347
|
-
? 1 - clamp((Math.abs(headingError) - profile.recoveryHeadingThrottleStart) / 0.92, 0, 0.82)
|
|
348
|
-
: 1;
|
|
349
|
-
const throttleRequest = speedError > kphToSimSpeed(1)
|
|
350
|
-
? clamp(speedError / kphToSimSpeed(profile.throttleResponseKph), minimumThrottle, 1)
|
|
351
|
-
: 0;
|
|
352
|
-
const steeringLoad = Math.abs(steeringRequest) / Math.max(steeringLimit, 1e-6);
|
|
353
|
-
const simulatorThrottleScale = profile.throttleScale(steeringLoad, gripUsage, slipAngle, edgeGuard);
|
|
354
|
-
const edgeSlideThrottleScale = 1 - clamp(
|
|
355
|
-
(
|
|
356
|
-
Math.max(0, edgeGuard.pressure - 0.18) +
|
|
357
|
-
edgeMotion.outwardSpeedMps * 0.16 +
|
|
358
|
-
(edgeMotion.headingOutward ? 0.18 : 0)
|
|
359
|
-
) * profile.edgeSlideThrottleDamping,
|
|
360
|
-
0,
|
|
361
|
-
0.92,
|
|
362
|
-
);
|
|
5
|
+
export { decideArcadeRacingControls } from './arcadeRacingControls.js';
|
|
6
|
+
export { decideAdvancedRacingControls as decideSimulatorRacingControls } from './advancedRacingControls.js';
|
|
7
|
+
export { calculateRacingLineOffset, maxLookaheadCurvature } from './arcadeRacePace.js';
|
|
363
8
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
.controls();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const LOOKAHEAD_SAMPLE_SCRATCH = { heading: 0, curvature: 0 };
|
|
372
|
-
const TARGET_BASE_SCRATCH = { x: 0, y: 0, heading: 0, normalX: 0, normalY: 0, curvature: 0, distance: 0 };
|
|
373
|
-
const TARGET_POINT_SCRATCH = { x: 0, y: 0 };
|
|
374
|
-
|
|
375
|
-
export function maxLookaheadCurvature(track, progress, lookahead) {
|
|
376
|
-
let maximum = 0;
|
|
377
|
-
const sample = LOOKAHEAD_SAMPLE_SCRATCH;
|
|
378
|
-
let previousHeading = sampleHeadingCurvatureAtInto(track, progress, sample).heading;
|
|
379
|
-
const segmentDistance = Math.max(1, lookahead / CURVATURE_LOOKAHEAD_SAMPLES);
|
|
380
|
-
for (let index = 1; index <= CURVATURE_LOOKAHEAD_SAMPLES; index += 1) {
|
|
381
|
-
sampleHeadingCurvatureAtInto(track, progress + lookahead * (index / CURVATURE_LOOKAHEAD_SAMPLES), sample);
|
|
382
|
-
const segmentCurvature = Math.abs(normalizeAngle(sample.heading - previousHeading)) / segmentDistance;
|
|
383
|
-
maximum = Math.max(maximum, Math.abs(sample.curvature ?? 0), Math.min(segmentCurvature, PREVIEW_CURVATURE_CAP));
|
|
384
|
-
previousHeading = sample.heading;
|
|
385
|
-
}
|
|
386
|
-
return maximum;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export function calculateRacingLineOffset(car, race, lookahead, curvature, edgeGuard) {
|
|
390
|
-
const cornerStrength = clamp((curvature - 0.00012) / 0.00095, 0, 1);
|
|
391
|
-
if (cornerStrength <= 0) return 0;
|
|
392
|
-
|
|
393
|
-
const entryHeading = sampleHeadingAt(race.track, car.progress - lookahead * 0.35);
|
|
394
|
-
const apexHeading = sampleHeadingAt(race.track, car.progress + lookahead * 0.55);
|
|
395
|
-
const exitHeading = sampleHeadingAt(race.track, car.progress + lookahead * 1.25);
|
|
396
|
-
const signedEntry = normalizeAngle(apexHeading - entryHeading);
|
|
397
|
-
const signedExit = normalizeAngle(exitHeading - apexHeading);
|
|
398
|
-
const turnDirection = Math.sign(Math.abs(signedEntry) > Math.abs(signedExit) ? signedEntry : signedExit);
|
|
399
|
-
if (turnDirection === 0) return 0;
|
|
400
|
-
|
|
401
|
-
const aggression = car.aggression ?? car.personality?.baseAggression ?? 0.5;
|
|
402
|
-
const simulatorMode = isSimulatorPhysicsMode(race.physicsMode);
|
|
403
|
-
const safeEdge = race.track.width / 2 - VEHICLE_LIMITS.carWidth *
|
|
404
|
-
(simulatorMode ? 2.05 - aggression * 0.08 : 1.15 - aggression * 0.12);
|
|
405
|
-
const apexOffset = turnDirection * safeEdge *
|
|
406
|
-
(simulatorMode ? 0.42 + aggression * 0.05 : 0.72 + aggression * 0.08);
|
|
407
|
-
|
|
408
|
-
return apexOffset * cornerStrength * (1 - edgeGuard.pressure * (simulatorMode ? 0.82 : 0.65));
|
|
9
|
+
export function decideRacingControls(car, orderIndex, race) {
|
|
10
|
+
return isSimulatorPhysicsMode(race.physicsMode)
|
|
11
|
+
? decideAdvancedRacingControls(car, orderIndex, race)
|
|
12
|
+
: decideArcadeRacingControls(car, orderIndex, race);
|
|
409
13
|
}
|