@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
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function freezeVehicleMotion(car, {
|
|
2
|
+
brake = 1,
|
|
3
|
+
clearManualControls = false,
|
|
4
|
+
physicsMode = 'simulator',
|
|
5
|
+
stabilityState = car.stabilityState ?? 'stable',
|
|
6
|
+
} = {}) {
|
|
7
|
+
car.speed = 0;
|
|
8
|
+
if (physicsMode === 'simulator') {
|
|
9
|
+
car.velocityX = 0;
|
|
10
|
+
car.velocityY = 0;
|
|
11
|
+
} else {
|
|
12
|
+
car.velocityX = null;
|
|
13
|
+
car.velocityY = null;
|
|
14
|
+
}
|
|
15
|
+
car.throttle = 0;
|
|
16
|
+
car.brake = brake;
|
|
17
|
+
car.appliedControls = { steering: 0, throttle: 0, brake };
|
|
18
|
+
if (clearManualControls) car.manualControls = null;
|
|
19
|
+
car.steeringAngle = 0;
|
|
20
|
+
car.yawRate = 0;
|
|
21
|
+
car.turnRadius = Infinity;
|
|
22
|
+
car.longitudinalAcceleration = 0;
|
|
23
|
+
car.lateralAcceleration = 0;
|
|
24
|
+
car.longitudinalG = 0;
|
|
25
|
+
car.lateralG = 0;
|
|
26
|
+
car.gripUsage = 0;
|
|
27
|
+
car.slipAngleRadians = 0;
|
|
28
|
+
car.tractionLimited = false;
|
|
29
|
+
car.stabilityState = stabilityState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function syncVehicleVelocityToHeading(car) {
|
|
33
|
+
const speed = Number.isFinite(car.speed) ? car.speed : 0;
|
|
34
|
+
const heading = Number.isFinite(car.heading) ? car.heading : 0;
|
|
35
|
+
car.velocityX = Math.cos(heading) * speed;
|
|
36
|
+
car.velocityY = Math.sin(heading) * speed;
|
|
37
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { clamp, normalizeAngle } from '../simMath.js';
|
|
2
|
+
import {
|
|
3
|
+
REAL_F1_WHEELBASE_METERS,
|
|
4
|
+
SIM_UNITS_PER_METER,
|
|
5
|
+
TOP_SPEED_SIM_UNITS_PER_SECOND,
|
|
6
|
+
metersPerSecondToSimSpeed,
|
|
7
|
+
simUnitsToMeters,
|
|
8
|
+
metersToSimUnits,
|
|
9
|
+
} from '../units.js';
|
|
10
|
+
import { VEHICLE_GEOMETRY, getCarCorners, vehicleAxes } from './vehicleGeometry.js';
|
|
11
|
+
|
|
12
|
+
const G = 9.80665;
|
|
13
|
+
|
|
14
|
+
export const PHYSICS_MODES = ['arcade', 'simulator'];
|
|
15
|
+
export const DEFAULT_PHYSICS_MODE = 'arcade';
|
|
16
|
+
|
|
17
|
+
export const VEHICLE_LIMITS = {
|
|
18
|
+
wheelbase: metersToSimUnits(REAL_F1_WHEELBASE_METERS),
|
|
19
|
+
maxSteer: 0.56,
|
|
20
|
+
steerRate: 2.35,
|
|
21
|
+
maxSpeed: TOP_SPEED_SIM_UNITS_PER_SECOND,
|
|
22
|
+
carLength: VEHICLE_GEOMETRY.visualLength,
|
|
23
|
+
carWidth: VEHICLE_GEOMETRY.visualWidth,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const SURFACE_MODEL = {
|
|
27
|
+
track: { grip: 1, drag: 0, rollingResistance: 0 },
|
|
28
|
+
'pit-entry': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
|
|
29
|
+
'pit-lane': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
|
|
30
|
+
'pit-exit': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
|
|
31
|
+
'pit-box': { grip: 0.94, drag: 0.08, rollingResistance: 0.025 },
|
|
32
|
+
kerb: { grip: 0.92, drag: 0.12, rollingResistance: 0.045 },
|
|
33
|
+
gravel: { grip: 0.43, drag: 4.0, rollingResistance: 0.68 },
|
|
34
|
+
grass: { grip: 0.34, drag: 1.9, rollingResistance: 0.36 },
|
|
35
|
+
barrier: { grip: 0.18, drag: 9, rollingResistance: 1.2 },
|
|
36
|
+
};
|
|
37
|
+
const WHEEL_DRAG_YAW_GAIN = 0.08;
|
|
38
|
+
const MAX_WHEEL_DRAG_YAW_RATE = 0.22;
|
|
39
|
+
|
|
40
|
+
const SIMULATOR_SURFACE_MODEL = {
|
|
41
|
+
track: { grip: 1, drag: 0, rollingResistance: 0.008, scrub: 0 },
|
|
42
|
+
'pit-entry': { grip: 0.92, drag: 0.06, rollingResistance: 0.018, scrub: 1.2 },
|
|
43
|
+
'pit-lane': { grip: 0.9, drag: 0.08, rollingResistance: 0.02, scrub: 1.4 },
|
|
44
|
+
'pit-exit': { grip: 0.92, drag: 0.06, rollingResistance: 0.018, scrub: 1.2 },
|
|
45
|
+
'pit-box': { grip: 0.82, drag: 0.12, rollingResistance: 0.035, scrub: 2.5 },
|
|
46
|
+
kerb: { grip: 0.78, drag: 0.42, rollingResistance: 0.09, scrub: 8.5 },
|
|
47
|
+
gravel: { grip: 0.36, drag: 5.4, rollingResistance: 0.24, scrub: 18 },
|
|
48
|
+
grass: { grip: 0.3, drag: 3.4, rollingResistance: 0.2, scrub: 15 },
|
|
49
|
+
barrier: { grip: 0.08, drag: 15, rollingResistance: 2.4, scrub: 30 },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function normalizePhysicsMode(value) {
|
|
53
|
+
return value === 'simulator' ? 'simulator' : DEFAULT_PHYSICS_MODE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function accelerationLimit(speed, surfaceGrip) {
|
|
57
|
+
const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
58
|
+
return SIM_UNITS_PER_METER * surfaceGrip * (17 * (1 - speedRatio ** 2.1) + 1.2);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function brakingLimit(speed, surfaceGrip) {
|
|
62
|
+
const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
63
|
+
return SIM_UNITS_PER_METER * surfaceGrip * (20 + speedRatio * 14);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function tirePerformanceFactor(tireEnergy = 100) {
|
|
67
|
+
const normalized = clamp((Number(tireEnergy) || 1) / 100, 0.01, 1);
|
|
68
|
+
return clamp(0.45 + 0.55 * normalized ** 0.72, 0.45, 1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function surfaceResistance(surfaceName, model = SURFACE_MODEL) {
|
|
72
|
+
const surface = model[surfaceName] ?? model.track;
|
|
73
|
+
return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sideWheelResistance(wheels, side, model = SURFACE_MODEL) {
|
|
77
|
+
const sideWheels = wheels.filter((wheel) => wheel.id?.endsWith(`-${side}`));
|
|
78
|
+
if (!sideWheels.length) return 0;
|
|
79
|
+
return sideWheels.reduce((total, wheel) => total + surfaceResistance(wheel.surface, model), 0) / sideWheels.length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function wheelDragYawRate(car, model = SURFACE_MODEL) {
|
|
83
|
+
const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
|
|
84
|
+
if (!wheels.length) return 0;
|
|
85
|
+
const leftResistance = sideWheelResistance(wheels, 'left', model);
|
|
86
|
+
const rightResistance = sideWheelResistance(wheels, 'right', model);
|
|
87
|
+
const speedFactor = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
88
|
+
return clamp(
|
|
89
|
+
(rightResistance - leftResistance) * speedFactor * WHEEL_DRAG_YAW_GAIN,
|
|
90
|
+
-MAX_WHEEL_DRAG_YAW_RATE,
|
|
91
|
+
MAX_WHEEL_DRAG_YAW_RATE,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function setPhysicsTelemetry(car, {
|
|
96
|
+
lateralAcceleration = 0,
|
|
97
|
+
longitudinalAcceleration = 0,
|
|
98
|
+
gripUsage = 0,
|
|
99
|
+
slipAngleRadians = 0,
|
|
100
|
+
tractionLimited = false,
|
|
101
|
+
stabilityState = 'stable',
|
|
102
|
+
} = {}) {
|
|
103
|
+
car.lateralAcceleration = lateralAcceleration;
|
|
104
|
+
car.longitudinalAcceleration = longitudinalAcceleration;
|
|
105
|
+
car.lateralG = lateralAcceleration / G;
|
|
106
|
+
car.longitudinalG = longitudinalAcceleration / G;
|
|
107
|
+
car.gripUsage = Number.isFinite(gripUsage) ? gripUsage : 0;
|
|
108
|
+
car.slipAngleRadians = Number.isFinite(slipAngleRadians) ? slipAngleRadians : 0;
|
|
109
|
+
car.tractionLimited = Boolean(tractionLimited);
|
|
110
|
+
car.stabilityState = stabilityState;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function integrateVehiclePhysics(car, controls, dt, options = {}) {
|
|
114
|
+
if (normalizePhysicsMode(options.physicsMode) === 'simulator') {
|
|
115
|
+
return integrateSimulatorVehiclePhysics(car, controls, dt, options);
|
|
116
|
+
}
|
|
117
|
+
return integrateArcadeVehiclePhysics(car, controls, dt, options);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function integrateArcadeVehiclePhysics(car, controls, dt, options = {}) {
|
|
121
|
+
const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
|
|
122
|
+
const steerDelta = clamp(
|
|
123
|
+
steeringTarget - car.steeringAngle,
|
|
124
|
+
-VEHICLE_LIMITS.steerRate * dt,
|
|
125
|
+
VEHICLE_LIMITS.steerRate * dt,
|
|
126
|
+
);
|
|
127
|
+
car.steeringAngle += steerDelta;
|
|
128
|
+
|
|
129
|
+
const throttle = clamp(controls.throttle ?? 0, 0, 1);
|
|
130
|
+
const brake = clamp(controls.brake ?? 0, 0, 1);
|
|
131
|
+
const surface = SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track;
|
|
132
|
+
const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
|
|
133
|
+
const surfaceGrip = surface.grip * tireFactor;
|
|
134
|
+
const dragMultiplier = car.drsActive ? 0.42 : 1;
|
|
135
|
+
const speedRatio = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
136
|
+
const engineForce = throttle *
|
|
137
|
+
car.powerNewtons *
|
|
138
|
+
(car.drsActive ? 1.08 : 1) *
|
|
139
|
+
Math.max(0.12, 1 - speedRatio ** 1.15);
|
|
140
|
+
const brakeForce = brake * car.brakeNewtons;
|
|
141
|
+
const speedMetersPerSecondBefore = simUnitsToMeters(car.speed);
|
|
142
|
+
const dragForce = (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) *
|
|
143
|
+
speedMetersPerSecondBefore * speedMetersPerSecondBefore;
|
|
144
|
+
const rollingForce = surface.rollingResistance * car.mass * G;
|
|
145
|
+
const driveAcceleration = clamp((engineForce / car.mass) * SIM_UNITS_PER_METER, 0, accelerationLimit(car.speed, surfaceGrip));
|
|
146
|
+
const drsAcceleration = car.drsActive ? driveAcceleration * 0.06 : 0;
|
|
147
|
+
const brakeDeceleration = clamp((brakeForce / car.mass) * SIM_UNITS_PER_METER, 0, brakingLimit(car.speed, surfaceGrip));
|
|
148
|
+
const dragDeceleration = ((dragForce + rollingForce) / car.mass) * SIM_UNITS_PER_METER;
|
|
149
|
+
const acceleration = driveAcceleration + drsAcceleration - brakeDeceleration - dragDeceleration;
|
|
150
|
+
|
|
151
|
+
car.speed = clamp(car.speed + acceleration * dt, 0, VEHICLE_LIMITS.maxSpeed);
|
|
152
|
+
|
|
153
|
+
const speedMetersPerSecond = simUnitsToMeters(car.speed);
|
|
154
|
+
const rawYawRate = (speedMetersPerSecond / REAL_F1_WHEELBASE_METERS) *
|
|
155
|
+
Math.tan(car.steeringAngle) *
|
|
156
|
+
tireFactor;
|
|
157
|
+
const downforceGrip = car.downforceCoefficient * speedMetersPerSecond * speedMetersPerSecond / car.mass;
|
|
158
|
+
const tyreConditionGrip = tireFactor;
|
|
159
|
+
const maxYawRate = ((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip) /
|
|
160
|
+
Math.max(speedMetersPerSecond, 1);
|
|
161
|
+
const steeringYawRate = clamp(rawYawRate, -maxYawRate, maxYawRate);
|
|
162
|
+
const requestedWheelDragYawRate = wheelDragYawRate(car);
|
|
163
|
+
car.yawRate = clamp(steeringYawRate + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
|
|
164
|
+
car.wheelDragYawRate = car.yawRate - steeringYawRate;
|
|
165
|
+
car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
|
|
166
|
+
car.heading = normalizeAngle(car.heading + car.yawRate * dt);
|
|
167
|
+
car.x += Math.cos(car.heading) * car.speed * dt;
|
|
168
|
+
car.y += Math.sin(car.heading) * car.speed * dt;
|
|
169
|
+
car.throttle = throttle;
|
|
170
|
+
car.brake = brake;
|
|
171
|
+
car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
|
|
172
|
+
setPhysicsTelemetry(car, {
|
|
173
|
+
lateralAcceleration: speedMetersPerSecond * car.yawRate,
|
|
174
|
+
longitudinalAcceleration: acceleration / SIM_UNITS_PER_METER,
|
|
175
|
+
gripUsage: Math.min(1, Math.abs(speedMetersPerSecond * car.yawRate) /
|
|
176
|
+
Math.max((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip, 1)),
|
|
177
|
+
tractionLimited: Math.abs(rawYawRate) > Math.abs(steeringYawRate) + 1e-6,
|
|
178
|
+
stabilityState: Math.abs(rawYawRate) > Math.abs(steeringYawRate) + 1e-6 ? 'understeer' : 'stable',
|
|
179
|
+
});
|
|
180
|
+
if (options.tireDegradationEnabled !== false) {
|
|
181
|
+
const tyreLoad = Math.abs(car.lateralAcceleration) / G;
|
|
182
|
+
const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
|
|
183
|
+
const wearRate = (0.035 + tyreLoad * 0.11 + brake * 0.07 + throttle * 0.025) / tireCare;
|
|
184
|
+
car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return car;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function simulatorSurface(surfaceName) {
|
|
191
|
+
return SIMULATOR_SURFACE_MODEL[surfaceName] ?? SIMULATOR_SURFACE_MODEL.track;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function aggregateSimulatorSurface(car) {
|
|
195
|
+
const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
|
|
196
|
+
if (!wheels.length) return simulatorSurface(car.trackState?.surface ?? 'track');
|
|
197
|
+
const total = wheels.reduce((sum, wheel) => {
|
|
198
|
+
const surface = simulatorSurface(wheel.surface);
|
|
199
|
+
return {
|
|
200
|
+
grip: sum.grip + surface.grip,
|
|
201
|
+
drag: sum.drag + surface.drag,
|
|
202
|
+
rollingResistance: sum.rollingResistance + surface.rollingResistance,
|
|
203
|
+
scrub: sum.scrub + surface.scrub,
|
|
204
|
+
};
|
|
205
|
+
}, { grip: 0, drag: 0, rollingResistance: 0, scrub: 0 });
|
|
206
|
+
return {
|
|
207
|
+
grip: total.grip / wheels.length,
|
|
208
|
+
drag: total.drag / wheels.length,
|
|
209
|
+
rollingResistance: total.rollingResistance / wheels.length,
|
|
210
|
+
scrub: total.scrub / wheels.length,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }) {
|
|
215
|
+
if (gripUsage > 1.32 || Math.abs(slipAngle) > 0.42) return 'spin-risk';
|
|
216
|
+
if (gripUsage > 1.02) return brake > 0.2 && throttle < 0.2 ? 'oversteer' : 'understeer';
|
|
217
|
+
if (surfaceName === 'kerb' && gripUsage > 0.72) return 'oversteer';
|
|
218
|
+
if ((surfaceName === 'gravel' || surfaceName === 'grass') && gripUsage > 0.5) return 'understeer';
|
|
219
|
+
return 'stable';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ensureSimulatorVelocity(car) {
|
|
223
|
+
if (Number.isFinite(car.velocityX) && Number.isFinite(car.velocityY)) return;
|
|
224
|
+
const speed = clamp(Number(car.speed) || 0, 0, VEHICLE_LIMITS.maxSpeed);
|
|
225
|
+
car.velocityX = Math.cos(car.heading ?? 0) * speed;
|
|
226
|
+
car.velocityY = Math.sin(car.heading ?? 0) * speed;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function limitSimulatorSpeed(car) {
|
|
230
|
+
const speed = Math.hypot(car.velocityX, car.velocityY);
|
|
231
|
+
if (speed <= VEHICLE_LIMITS.maxSpeed || speed <= 0) return speed;
|
|
232
|
+
const scale = VEHICLE_LIMITS.maxSpeed / speed;
|
|
233
|
+
car.velocityX *= scale;
|
|
234
|
+
car.velocityY *= scale;
|
|
235
|
+
return VEHICLE_LIMITS.maxSpeed;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function integrateSimulatorVehiclePhysics(car, controls, dt, options = {}) {
|
|
239
|
+
ensureSimulatorVelocity(car);
|
|
240
|
+
const steeringTarget = clamp(controls.steering ?? 0, -VEHICLE_LIMITS.maxSteer, VEHICLE_LIMITS.maxSteer);
|
|
241
|
+
const steerDelta = clamp(
|
|
242
|
+
steeringTarget - car.steeringAngle,
|
|
243
|
+
-VEHICLE_LIMITS.steerRate * dt,
|
|
244
|
+
VEHICLE_LIMITS.steerRate * dt,
|
|
245
|
+
);
|
|
246
|
+
car.steeringAngle += steerDelta;
|
|
247
|
+
|
|
248
|
+
const throttle = clamp(controls.throttle ?? 0, 0, 1);
|
|
249
|
+
const brake = clamp(controls.brake ?? 0, 0, 1);
|
|
250
|
+
const surfaceName = car.trackState?.surface ?? 'track';
|
|
251
|
+
const surface = aggregateSimulatorSurface(car);
|
|
252
|
+
const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
|
|
253
|
+
const surfaceGrip = surface.grip * tireFactor;
|
|
254
|
+
const speedBefore = Math.hypot(car.velocityX, car.velocityY);
|
|
255
|
+
const speedBeforeMps = simUnitsToMeters(speedBefore);
|
|
256
|
+
const speedRatio = clamp(speedBefore / VEHICLE_LIMITS.maxSpeed, 0, 1);
|
|
257
|
+
const downforceGrip = car.downforceCoefficient * speedBeforeMps * speedBeforeMps / car.mass;
|
|
258
|
+
const totalGripAcceleration = Math.max(0.1, (car.tireGrip * tireFactor * G + downforceGrip) * surfaceGrip);
|
|
259
|
+
const axes = vehicleAxes(car.heading ?? 0);
|
|
260
|
+
const forwardVelocity = car.velocityX * axes.forward.x + car.velocityY * axes.forward.y;
|
|
261
|
+
const lateralVelocity = car.velocityX * axes.right.x + car.velocityY * axes.right.y;
|
|
262
|
+
const forwardSpeedMps = simUnitsToMeters(forwardVelocity);
|
|
263
|
+
const lateralSpeedMps = simUnitsToMeters(lateralVelocity);
|
|
264
|
+
const currentSlipAngle = Math.atan2(lateralSpeedMps, Math.max(Math.abs(forwardSpeedMps), 0.1));
|
|
265
|
+
const engineForce = throttle *
|
|
266
|
+
car.powerNewtons *
|
|
267
|
+
(car.drsActive ? 1.07 : 1) *
|
|
268
|
+
Math.max(0.08, 1 - speedRatio ** 1.22);
|
|
269
|
+
const brakeForce = brake * car.brakeNewtons;
|
|
270
|
+
const driveAcceleration = Math.min(
|
|
271
|
+
engineForce / car.mass,
|
|
272
|
+
accelerationLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
|
|
273
|
+
);
|
|
274
|
+
const brakeDeceleration = Math.min(
|
|
275
|
+
brakeForce / car.mass,
|
|
276
|
+
brakingLimit(car.speed, surfaceGrip) / SIM_UNITS_PER_METER,
|
|
277
|
+
);
|
|
278
|
+
const dragMultiplier = car.drsActive ? 0.46 : 1;
|
|
279
|
+
const dragAcceleration = (
|
|
280
|
+
(car.dragCoefficient * dragMultiplier * 0.28 + surface.drag * 0.018) *
|
|
281
|
+
speedBeforeMps * speedBeforeMps
|
|
282
|
+
) / car.mass;
|
|
283
|
+
const rollingAcceleration = surface.rollingResistance * G;
|
|
284
|
+
const normalizedSteer = Math.abs(car.steeringAngle) / VEHICLE_LIMITS.maxSteer;
|
|
285
|
+
const speedSensitiveSteer = 1 / (1 + (Math.abs(forwardSpeedMps) / 58) ** 1.85 * 2.35);
|
|
286
|
+
const effectiveSteeringAngle = car.steeringAngle * clamp(speedSensitiveSteer, 0.16, 1);
|
|
287
|
+
const rawYawRate = Math.abs(forwardSpeedMps) <= 0.05
|
|
288
|
+
? 0
|
|
289
|
+
: (forwardSpeedMps / REAL_F1_WHEELBASE_METERS) * Math.tan(effectiveSteeringAngle);
|
|
290
|
+
const sideSlipDamping = 4.2 + surfaceGrip * 2.1 + speedRatio * 1.6;
|
|
291
|
+
const desiredLateralAcceleration = forwardSpeedMps * rawYawRate - lateralSpeedMps * sideSlipDamping;
|
|
292
|
+
const longitudinalDemand = Math.max(driveAcceleration, brakeDeceleration);
|
|
293
|
+
const longitudinalUsage = clamp(longitudinalDemand / Math.max(totalGripAcceleration * 0.52, 1), 0, 0.94);
|
|
294
|
+
const lateralCapacity = totalGripAcceleration * Math.sqrt(Math.max(0.08, 1 - longitudinalUsage ** 2));
|
|
295
|
+
const desiredLateralAbs = Math.abs(desiredLateralAcceleration);
|
|
296
|
+
const lateralUsage = desiredLateralAbs / Math.max(lateralCapacity, 0.1);
|
|
297
|
+
const gripUsage = Math.sqrt(lateralUsage ** 2 + longitudinalUsage ** 2);
|
|
298
|
+
const tractionLimited = gripUsage > 1 || longitudinalUsage > 0.9;
|
|
299
|
+
const actualLateralAcceleration = clamp(desiredLateralAcceleration, -lateralCapacity, lateralCapacity);
|
|
300
|
+
const steeringYawRate = Math.abs(forwardSpeedMps) <= 0.05 ? 0 : actualLateralAcceleration / Math.max(Math.abs(forwardSpeedMps), 0.1);
|
|
301
|
+
const steeringScrubAcceleration = normalizedSteer ** 1.55 *
|
|
302
|
+
speedRatio ** 1.05 *
|
|
303
|
+
(42 + surface.scrub + Math.max(0, gripUsage - 0.55) * 18);
|
|
304
|
+
const tractionPowerScale = tractionLimited
|
|
305
|
+
? clamp(1 - (gripUsage - 1) * 0.55 - longitudinalUsage * 0.18, 0.12, 1)
|
|
306
|
+
: 1;
|
|
307
|
+
const brakingDirection = forwardSpeedMps >= -0.2 ? 1 : -1;
|
|
308
|
+
const scrubDirection = forwardSpeedMps >= 0 ? 1 : -1;
|
|
309
|
+
const longitudinalAcceleration =
|
|
310
|
+
driveAcceleration * tractionPowerScale -
|
|
311
|
+
brakeDeceleration * brakingDirection -
|
|
312
|
+
dragAcceleration * Math.sign(forwardSpeedMps || 1) -
|
|
313
|
+
rollingAcceleration * Math.sign(forwardSpeedMps || 1) -
|
|
314
|
+
steeringScrubAcceleration * scrubDirection;
|
|
315
|
+
|
|
316
|
+
const requestedWheelDragYawRate = wheelDragYawRate(car, SIMULATOR_SURFACE_MODEL) * (surfaceName === 'kerb' ? 1.6 : 1);
|
|
317
|
+
const maxYawRate = speedBeforeMps <= 0.05 ? 0 : totalGripAcceleration / Math.max(speedBeforeMps, 0.1);
|
|
318
|
+
const slipSteeringAuthority = clamp(1 - Math.abs(currentSlipAngle) * 1.8, 0.35, 1);
|
|
319
|
+
const yawTarget = clamp(steeringYawRate * slipSteeringAuthority + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
|
|
320
|
+
const yawResponse = 1.6 + surfaceGrip * 0.9 + speedRatio * 0.5;
|
|
321
|
+
const yawAccelerationCap = speedRatio < 0.18 ? 6.2 : 3.2;
|
|
322
|
+
const maxYawDelta = Math.min(Math.max(0.08, maxYawRate * yawResponse), yawAccelerationCap) * dt;
|
|
323
|
+
car.yawRate = clamp(
|
|
324
|
+
(car.yawRate ?? 0) + clamp(yawTarget - (car.yawRate ?? 0), -maxYawDelta, maxYawDelta),
|
|
325
|
+
-maxYawRate,
|
|
326
|
+
maxYawRate,
|
|
327
|
+
);
|
|
328
|
+
car.wheelDragYawRate = requestedWheelDragYawRate;
|
|
329
|
+
car.heading = normalizeAngle(car.heading + car.yawRate * dt);
|
|
330
|
+
|
|
331
|
+
const accelerationX = axes.forward.x * longitudinalAcceleration + axes.right.x * actualLateralAcceleration;
|
|
332
|
+
const accelerationY = axes.forward.y * longitudinalAcceleration + axes.right.y * actualLateralAcceleration;
|
|
333
|
+
car.velocityX += metersToSimUnits(accelerationX) * dt;
|
|
334
|
+
car.velocityY += metersToSimUnits(accelerationY) * dt;
|
|
335
|
+
car.speed = limitSimulatorSpeed(car);
|
|
336
|
+
car.x += car.velocityX * dt;
|
|
337
|
+
car.y += car.velocityY * dt;
|
|
338
|
+
car.throttle = throttle;
|
|
339
|
+
car.brake = brake;
|
|
340
|
+
car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
|
|
341
|
+
car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
|
|
342
|
+
const velocityHeading = car.speed <= 0.01 ? car.heading : Math.atan2(car.velocityY, car.velocityX);
|
|
343
|
+
const slipAngle = normalizeAngle(velocityHeading - car.heading);
|
|
344
|
+
setPhysicsTelemetry(car, {
|
|
345
|
+
lateralAcceleration: actualLateralAcceleration,
|
|
346
|
+
longitudinalAcceleration,
|
|
347
|
+
gripUsage,
|
|
348
|
+
slipAngleRadians: slipAngle,
|
|
349
|
+
tractionLimited,
|
|
350
|
+
stabilityState: simulatorStabilityState({ gripUsage, slipAngle, surfaceName, brake, throttle }),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (options.tireDegradationEnabled !== false) {
|
|
354
|
+
const tyreLoad = Math.abs(car.lateralAcceleration) / G;
|
|
355
|
+
const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
|
|
356
|
+
const heatPenalty = Math.max(0, gripUsage - 0.72) * 0.24 + normalizedSteer * speedRatio * 0.08;
|
|
357
|
+
const wearRate = (0.045 + tyreLoad * 0.16 + brake * 0.09 + throttle * 0.035 + heatPenalty) / tireCare;
|
|
358
|
+
car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return car;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export { getCarCorners };
|