@inventure71/paddockjs 0.3.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/INSTALL_AND_UPDATE.md +3 -1
  3. package/README.md +142 -9
  4. package/docs/architecture.md +49 -15
  5. package/docs/concepts.md +20 -3
  6. package/docs/data_contract.md +185 -27
  7. package/docs/data_model.md +1 -1
  8. package/docs/learnings.md +1 -1
  9. package/docs/rules.md +140 -19
  10. package/docs/system_specs.md +39 -16
  11. package/docs/training.md +103 -1
  12. package/examples/train-basic-policy.mjs +5 -6
  13. package/examples/trainingData.mjs +133 -0
  14. package/package.json +5 -2
  15. package/src/api/PaddockSimulatorController.js +32 -2
  16. package/src/app/F1SimulatorApp.js +1101 -128
  17. package/src/app/domBindings.js +10 -1
  18. package/src/config/defaultOptions.js +4 -4
  19. package/src/environment/actions.js +25 -2
  20. package/src/environment/evaluation.js +184 -0
  21. package/src/environment/index.d.ts +281 -1
  22. package/src/environment/index.js +13 -0
  23. package/src/environment/observationOptions.js +12 -0
  24. package/src/environment/observations.js +189 -17
  25. package/src/environment/options.js +7 -0
  26. package/src/environment/recorder.js +28 -0
  27. package/src/environment/runtime.js +41 -6
  28. package/src/environment/scenarios.js +220 -0
  29. package/src/environment/sensors.js +145 -35
  30. package/src/environment/specs.js +92 -8
  31. package/src/environment/workerProtocol.js +52 -0
  32. package/src/index.d.ts +366 -3
  33. package/src/index.js +21 -0
  34. package/src/rendering/proceduralTrackAsset.js +336 -23
  35. package/src/simulation/collisionGeometry.js +214 -0
  36. package/src/simulation/driverController.js +431 -65
  37. package/src/simulation/raceSimulation.js +2132 -171
  38. package/src/simulation/rules/collisionSteward.js +69 -0
  39. package/src/simulation/rules/penaltyLedger.js +153 -0
  40. package/src/simulation/rules/pitLaneSpeedingSteward.js +71 -0
  41. package/src/simulation/rules/tireRequirementSteward.js +29 -0
  42. package/src/simulation/rules/trackLimitsSteward.js +85 -0
  43. package/src/simulation/rulesConfig.js +352 -0
  44. package/src/simulation/trackModel.js +1154 -76
  45. package/src/simulation/units.js +6 -4
  46. package/src/simulation/vehicleGeometry.js +247 -0
  47. package/src/simulation/vehiclePhysics.js +82 -50
  48. package/src/simulation/wheelSurface.js +338 -0
  49. package/src/styles.css +300 -42
  50. package/src/ui/bannerTemplates.js +69 -0
  51. package/src/ui/componentTemplates.js +36 -96
  52. package/src/ui/raceControlStatusBanner.js +39 -0
  53. package/src/ui/shellTemplate.js +1 -1
  54. package/src/ui/templateUtils.js +23 -0
@@ -1,9 +1,11 @@
1
- import { VEHICLE_LIMITS } from './vehiclePhysics.js';
2
-
3
1
  export const REAL_F1_CAR_LENGTH_METERS = 5.63;
2
+ export const REAL_F1_CAR_WIDTH_METERS = 1.9;
3
+ export const REAL_F1_WHEELBASE_METERS = 3.6;
4
4
  export const TARGET_F1_TOP_SPEED_KPH = 330;
5
- export const SIM_UNITS_PER_METER = (VEHICLE_LIMITS.maxSpeed * 3.6) / TARGET_F1_TOP_SPEED_KPH;
6
- export const VISUAL_CAR_LENGTH_METERS = VEHICLE_LIMITS.carLength / SIM_UNITS_PER_METER;
5
+ export const SIM_UNITS_PER_METER = 12;
6
+ export const TOP_SPEED_SIM_UNITS_PER_SECOND = (TARGET_F1_TOP_SPEED_KPH / 3.6) * SIM_UNITS_PER_METER;
7
+ export const VISUAL_CAR_LENGTH_METERS = REAL_F1_CAR_LENGTH_METERS;
8
+ export const VISUAL_CAR_WIDTH_METERS = REAL_F1_CAR_WIDTH_METERS;
7
9
 
8
10
  export function metersToSimUnits(meters) {
9
11
  return meters * SIM_UNITS_PER_METER;
@@ -0,0 +1,247 @@
1
+ import { normalizeAngle } from './simMath.js';
2
+ import {
3
+ REAL_F1_CAR_LENGTH_METERS,
4
+ REAL_F1_CAR_WIDTH_METERS,
5
+ REAL_F1_WHEELBASE_METERS,
6
+ metersToSimUnits,
7
+ } from './units.js';
8
+
9
+ export const VEHICLE_GEOMETRY = {
10
+ visualLength: metersToSimUnits(REAL_F1_CAR_LENGTH_METERS),
11
+ visualWidth: metersToSimUnits(REAL_F1_CAR_WIDTH_METERS),
12
+ bodyLength: metersToSimUnits(4.65),
13
+ bodyWidth: metersToSimUnits(1.24),
14
+ wheelLength: metersToSimUnits(0.72),
15
+ wheelWidth: metersToSimUnits(0.38),
16
+ wheelLongitudinalOffset: metersToSimUnits(REAL_F1_WHEELBASE_METERS / 2),
17
+ wheelLateralOffset: metersToSimUnits((REAL_F1_CAR_WIDTH_METERS - 0.38) / 2),
18
+ };
19
+
20
+ const WHEEL_SPECS = [
21
+ { id: 'front-left', longitudinal: 1, lateral: -1 },
22
+ { id: 'front-right', longitudinal: 1, lateral: 1 },
23
+ { id: 'rear-left', longitudinal: -1, lateral: -1 },
24
+ { id: 'rear-right', longitudinal: -1, lateral: 1 },
25
+ ];
26
+
27
+ function finiteOr(value, fallback) {
28
+ return Number.isFinite(value) ? value : fallback;
29
+ }
30
+
31
+ function vehiclePose(car, { previous = false } = {}) {
32
+ const x = previous ? finiteOr(car.previousX, car.x) : car.x;
33
+ const y = previous ? finiteOr(car.previousY, car.y) : car.y;
34
+ const heading = previous ? finiteOr(car.previousHeading, car.heading) : car.heading;
35
+
36
+ return {
37
+ x: finiteOr(x, 0),
38
+ y: finiteOr(y, 0),
39
+ heading: normalizeAngle(finiteOr(heading, 0)),
40
+ };
41
+ }
42
+
43
+ export function vehicleAxes(heading) {
44
+ const cos = Math.cos(heading);
45
+ const sin = Math.sin(heading);
46
+ return {
47
+ forward: { x: cos, y: sin },
48
+ right: { x: -sin, y: cos },
49
+ };
50
+ }
51
+
52
+ function offsetPoint(center, axes, longitudinal, lateral) {
53
+ return {
54
+ x: center.x + axes.forward.x * longitudinal + axes.right.x * lateral,
55
+ y: center.y + axes.forward.y * longitudinal + axes.right.y * lateral,
56
+ };
57
+ }
58
+
59
+ export function createOrientedRect({ id, type, center, heading, length, width }) {
60
+ const axes = vehicleAxes(heading);
61
+ const halfLength = length / 2;
62
+ const halfWidth = width / 2;
63
+ const corners = [
64
+ offsetPoint(center, axes, halfLength, halfWidth),
65
+ offsetPoint(center, axes, halfLength, -halfWidth),
66
+ offsetPoint(center, axes, -halfLength, -halfWidth),
67
+ offsetPoint(center, axes, -halfLength, halfWidth),
68
+ ];
69
+
70
+ return {
71
+ id,
72
+ type,
73
+ center,
74
+ heading,
75
+ length,
76
+ width,
77
+ halfLength,
78
+ halfWidth,
79
+ forward: axes.forward,
80
+ right: axes.right,
81
+ corners,
82
+ };
83
+ }
84
+
85
+ export function createVehicleGeometry(car, options = {}) {
86
+ const pose = vehiclePose(car, options);
87
+ const axes = vehicleAxes(pose.heading);
88
+ const center = { x: pose.x, y: pose.y };
89
+ const body = createOrientedRect({
90
+ id: 'body',
91
+ type: 'body',
92
+ center,
93
+ heading: pose.heading,
94
+ length: VEHICLE_GEOMETRY.bodyLength,
95
+ width: VEHICLE_GEOMETRY.bodyWidth,
96
+ });
97
+ const wheels = WHEEL_SPECS.map((spec) => {
98
+ const wheelCenter = offsetPoint(
99
+ center,
100
+ axes,
101
+ spec.longitudinal * VEHICLE_GEOMETRY.wheelLongitudinalOffset,
102
+ spec.lateral * VEHICLE_GEOMETRY.wheelLateralOffset,
103
+ );
104
+ return createOrientedRect({
105
+ id: spec.id,
106
+ type: 'wheel',
107
+ center: wheelCenter,
108
+ heading: pose.heading,
109
+ length: VEHICLE_GEOMETRY.wheelLength,
110
+ width: VEHICLE_GEOMETRY.wheelWidth,
111
+ });
112
+ });
113
+
114
+ return {
115
+ body,
116
+ wheels,
117
+ contactPatches: wheels,
118
+ shapes: [body, ...wheels],
119
+ };
120
+ }
121
+
122
+ function geometryPoseSignature(pose) {
123
+ return [
124
+ pose.x,
125
+ pose.y,
126
+ pose.heading,
127
+ pose.previousX,
128
+ pose.previousY,
129
+ pose.previousHeading,
130
+ ].join(':');
131
+ }
132
+
133
+ function geometryPose(car) {
134
+ const x = finiteOr(car.x, 0);
135
+ const y = finiteOr(car.y, 0);
136
+ const heading = normalizeAngle(finiteOr(car.heading, 0));
137
+ return {
138
+ x,
139
+ y,
140
+ heading,
141
+ previousX: finiteOr(car.previousX, x),
142
+ previousY: finiteOr(car.previousY, y),
143
+ previousHeading: normalizeAngle(finiteOr(car.previousHeading, heading)),
144
+ };
145
+ }
146
+
147
+ function geometryStateMatches(car, state) {
148
+ const pose = state?.pose;
149
+ if (!pose) return false;
150
+ const x = finiteOr(car.x, 0);
151
+ const y = finiteOr(car.y, 0);
152
+ const heading = normalizeAngle(finiteOr(car.heading, 0));
153
+ return (
154
+ pose.x === x &&
155
+ pose.y === y &&
156
+ pose.heading === heading &&
157
+ pose.previousX === finiteOr(car.previousX, x) &&
158
+ pose.previousY === finiteOr(car.previousY, y) &&
159
+ pose.previousHeading === normalizeAngle(finiteOr(car.previousHeading, heading))
160
+ );
161
+ }
162
+
163
+ export function createVehicleShapeAabb(shape) {
164
+ let minX = Infinity;
165
+ let maxX = -Infinity;
166
+ let minY = Infinity;
167
+ let maxY = -Infinity;
168
+ for (let index = 0; index < shape.corners.length; index += 1) {
169
+ const corner = shape.corners[index];
170
+ if (corner.x < minX) minX = corner.x;
171
+ if (corner.x > maxX) maxX = corner.x;
172
+ if (corner.y < minY) minY = corner.y;
173
+ if (corner.y > maxY) maxY = corner.y;
174
+ }
175
+ return {
176
+ minX,
177
+ maxX,
178
+ minY,
179
+ maxY,
180
+ };
181
+ }
182
+
183
+ export function mergeAabbs(aabbs) {
184
+ let minX = Infinity;
185
+ let maxX = -Infinity;
186
+ let minY = Infinity;
187
+ let maxY = -Infinity;
188
+ for (let index = 0; index < aabbs.length; index += 1) {
189
+ const aabb = aabbs[index];
190
+ if (aabb.minX < minX) minX = aabb.minX;
191
+ if (aabb.maxX > maxX) maxX = aabb.maxX;
192
+ if (aabb.minY < minY) minY = aabb.minY;
193
+ if (aabb.maxY > maxY) maxY = aabb.maxY;
194
+ }
195
+ return { minX, maxX, minY, maxY };
196
+ }
197
+
198
+ export function createVehicleGeometryState(car) {
199
+ const pose = geometryPose(car);
200
+ const current = createVehicleGeometry(car);
201
+ const previous = createVehicleGeometry(car, { previous: true });
202
+ const bodyAabb = createVehicleShapeAabb(current.body);
203
+ const previousBodyAabb = createVehicleShapeAabb(previous.body);
204
+ const sweptBodyAabb = mergeAabbs([previousBodyAabb, bodyAabb]);
205
+
206
+ return {
207
+ pose,
208
+ signature: geometryPoseSignature(pose),
209
+ current,
210
+ previous,
211
+ body: current.body,
212
+ wheels: current.wheels,
213
+ contactPatches: current.contactPatches,
214
+ bodyAabb,
215
+ previousBodyAabb,
216
+ sweptBodyAabb,
217
+ };
218
+ }
219
+
220
+ export function getVehicleGeometryState(car) {
221
+ if (geometryStateMatches(car, car.geometryState)) return car.geometryState;
222
+ car.geometryState = createVehicleGeometryState(car);
223
+ return car.geometryState;
224
+ }
225
+
226
+ export function createVehicleAabb(car, options = {}) {
227
+ const geometry = createVehicleGeometry(car, options);
228
+ return mergeAabbs(geometry.shapes.map(createVehicleShapeAabb));
229
+ }
230
+
231
+ export function interpolateVehiclePose(car, amount) {
232
+ const previousX = finiteOr(car.previousX, car.x);
233
+ const previousY = finiteOr(car.previousY, car.y);
234
+ const previousHeading = finiteOr(car.previousHeading, car.heading);
235
+ const headingDelta = normalizeAngle(finiteOr(car.heading, 0) - previousHeading);
236
+
237
+ return {
238
+ ...car,
239
+ x: previousX + (finiteOr(car.x, previousX) - previousX) * amount,
240
+ y: previousY + (finiteOr(car.y, previousY) - previousY) * amount,
241
+ heading: normalizeAngle(previousHeading + headingDelta * amount),
242
+ };
243
+ }
244
+
245
+ export function getCarCorners(car) {
246
+ return createVehicleGeometry(car).body.corners;
247
+ }
@@ -1,32 +1,75 @@
1
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
+ simUnitsToMeters,
7
+ metersToSimUnits,
8
+ } from './units.js';
9
+ import { VEHICLE_GEOMETRY, getCarCorners } from './vehicleGeometry.js';
2
10
 
3
11
  const G = 9.80665;
4
12
 
5
13
  export const VEHICLE_LIMITS = {
6
- wheelbase: 3.65,
14
+ wheelbase: metersToSimUnits(REAL_F1_WHEELBASE_METERS),
7
15
  maxSteer: 0.56,
8
16
  steerRate: 2.35,
9
- maxSpeed: 192,
10
- carLength: 66,
11
- carWidth: 21,
17
+ maxSpeed: TOP_SPEED_SIM_UNITS_PER_SECOND,
18
+ carLength: VEHICLE_GEOMETRY.visualLength,
19
+ carWidth: VEHICLE_GEOMETRY.visualWidth,
12
20
  };
13
21
 
14
22
  const SURFACE_MODEL = {
15
23
  track: { grip: 1, drag: 0, rollingResistance: 0 },
24
+ 'pit-entry': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
25
+ 'pit-lane': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
26
+ 'pit-exit': { grip: 0.96, drag: 0.04, rollingResistance: 0.012 },
27
+ 'pit-box': { grip: 0.94, drag: 0.08, rollingResistance: 0.025 },
16
28
  kerb: { grip: 0.92, drag: 0.12, rollingResistance: 0.045 },
17
- gravel: { grip: 0.43, drag: 2.6, rollingResistance: 0.48 },
29
+ gravel: { grip: 0.43, drag: 4.0, rollingResistance: 0.68 },
18
30
  grass: { grip: 0.34, drag: 1.9, rollingResistance: 0.36 },
19
31
  barrier: { grip: 0.18, drag: 9, rollingResistance: 1.2 },
20
32
  };
33
+ const WHEEL_DRAG_YAW_GAIN = 0.08;
34
+ const MAX_WHEEL_DRAG_YAW_RATE = 0.22;
21
35
 
22
36
  function accelerationLimit(speed, surfaceGrip) {
23
37
  const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
24
- return surfaceGrip * (17 * (1 - speedRatio ** 2.1) + 1.2);
38
+ return SIM_UNITS_PER_METER * surfaceGrip * (17 * (1 - speedRatio ** 2.1) + 1.2);
25
39
  }
26
40
 
27
41
  function brakingLimit(speed, surfaceGrip) {
28
42
  const speedRatio = clamp(speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
29
- return surfaceGrip * (20 + speedRatio * 14);
43
+ return SIM_UNITS_PER_METER * surfaceGrip * (20 + speedRatio * 14);
44
+ }
45
+
46
+ export function tirePerformanceFactor(tireEnergy = 100) {
47
+ const normalized = clamp((Number(tireEnergy) || 1) / 100, 0.01, 1);
48
+ return clamp(0.45 + 0.55 * normalized ** 0.72, 0.45, 1);
49
+ }
50
+
51
+ function surfaceResistance(surfaceName) {
52
+ const surface = SURFACE_MODEL[surfaceName] ?? SURFACE_MODEL.track;
53
+ return surface.drag * 0.14 + surface.rollingResistance * 2.4 + (1 - surface.grip) * 0.65;
54
+ }
55
+
56
+ function sideWheelResistance(wheels, side) {
57
+ const sideWheels = wheels.filter((wheel) => wheel.id?.endsWith(`-${side}`));
58
+ if (!sideWheels.length) return 0;
59
+ return sideWheels.reduce((total, wheel) => total + surfaceResistance(wheel.surface), 0) / sideWheels.length;
60
+ }
61
+
62
+ function wheelDragYawRate(car) {
63
+ const wheels = Array.isArray(car.wheelStates) ? car.wheelStates : [];
64
+ if (!wheels.length) return 0;
65
+ const leftResistance = sideWheelResistance(wheels, 'left');
66
+ const rightResistance = sideWheelResistance(wheels, 'right');
67
+ const speedFactor = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
68
+ return clamp(
69
+ (rightResistance - leftResistance) * speedFactor * WHEEL_DRAG_YAW_GAIN,
70
+ -MAX_WHEEL_DRAG_YAW_RATE,
71
+ MAX_WHEEL_DRAG_YAW_RATE,
72
+ );
30
73
  }
31
74
 
32
75
  export function integrateVehiclePhysics(car, controls, dt) {
@@ -41,64 +84,53 @@ export function integrateVehiclePhysics(car, controls, dt) {
41
84
  const throttle = clamp(controls.throttle ?? 0, 0, 1);
42
85
  const brake = clamp(controls.brake ?? 0, 0, 1);
43
86
  const surface = SURFACE_MODEL[car.trackState?.surface] ?? SURFACE_MODEL.track;
44
- const surfaceGrip = surface.grip;
87
+ const tireFactor = tirePerformanceFactor(car.tireEnergy ?? 100);
88
+ const surfaceGrip = surface.grip * tireFactor;
45
89
  const dragMultiplier = car.drsActive ? 0.42 : 1;
46
90
  const speedRatio = clamp(car.speed / VEHICLE_LIMITS.maxSpeed, 0, 1);
47
- const engineForce = throttle * car.powerNewtons * Math.max(0.12, 1 - speedRatio ** 1.15);
91
+ const engineForce = throttle *
92
+ car.powerNewtons *
93
+ (car.drsActive ? 1.08 : 1) *
94
+ Math.max(0.12, 1 - speedRatio ** 1.15);
48
95
  const brakeForce = brake * car.brakeNewtons;
49
- const dragForce = (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) * car.speed * car.speed;
96
+ const speedMetersPerSecondBefore = simUnitsToMeters(car.speed);
97
+ const dragForce = (car.dragCoefficient * dragMultiplier * 0.12 + surface.drag) *
98
+ speedMetersPerSecondBefore * speedMetersPerSecondBefore;
50
99
  const rollingForce = surface.rollingResistance * car.mass * G;
51
- const driveAcceleration = clamp(engineForce / car.mass, 0, accelerationLimit(car.speed, surfaceGrip));
52
- const brakeDeceleration = clamp(brakeForce / car.mass, 0, brakingLimit(car.speed, surfaceGrip));
53
- const dragDeceleration = (dragForce + rollingForce) / car.mass;
54
- const acceleration = driveAcceleration - brakeDeceleration - dragDeceleration;
100
+ const driveAcceleration = clamp((engineForce / car.mass) * SIM_UNITS_PER_METER, 0, accelerationLimit(car.speed, surfaceGrip));
101
+ const drsAcceleration = car.drsActive ? driveAcceleration * 0.06 : 0;
102
+ const brakeDeceleration = clamp((brakeForce / car.mass) * SIM_UNITS_PER_METER, 0, brakingLimit(car.speed, surfaceGrip));
103
+ const dragDeceleration = ((dragForce + rollingForce) / car.mass) * SIM_UNITS_PER_METER;
104
+ const acceleration = driveAcceleration + drsAcceleration - brakeDeceleration - dragDeceleration;
55
105
 
56
106
  car.speed = clamp(car.speed + acceleration * dt, 0, VEHICLE_LIMITS.maxSpeed);
57
107
 
58
- const rawYawRate = car.speed / VEHICLE_LIMITS.wheelbase * Math.tan(car.steeringAngle);
59
- const downforceGrip = car.downforceCoefficient * car.speed * car.speed / car.mass;
60
- const tyreConditionGrip = clamp(0.82 + (car.tireEnergy ?? 100) / 560, 0.82, 1);
61
- const maxYawRate = ((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip) / Math.max(car.speed, 8);
62
- car.yawRate = clamp(rawYawRate, -maxYawRate, maxYawRate);
108
+ const speedMetersPerSecond = simUnitsToMeters(car.speed);
109
+ const rawYawRate = (speedMetersPerSecond / REAL_F1_WHEELBASE_METERS) *
110
+ Math.tan(car.steeringAngle) *
111
+ tireFactor;
112
+ const downforceGrip = car.downforceCoefficient * speedMetersPerSecond * speedMetersPerSecond / car.mass;
113
+ const tyreConditionGrip = tireFactor;
114
+ const maxYawRate = ((car.tireGrip * tyreConditionGrip * G + downforceGrip) * surfaceGrip) /
115
+ Math.max(speedMetersPerSecond, 1);
116
+ const steeringYawRate = clamp(rawYawRate, -maxYawRate, maxYawRate);
117
+ const requestedWheelDragYawRate = wheelDragYawRate(car);
118
+ car.yawRate = clamp(steeringYawRate + requestedWheelDragYawRate, -maxYawRate, maxYawRate);
119
+ car.wheelDragYawRate = car.yawRate - steeringYawRate;
63
120
  car.turnRadius = Math.abs(car.yawRate) < 0.001 ? Infinity : car.speed / Math.abs(car.yawRate);
64
121
  car.heading = normalizeAngle(car.heading + car.yawRate * dt);
65
122
  car.x += Math.cos(car.heading) * car.speed * dt;
66
123
  car.y += Math.sin(car.heading) * car.speed * dt;
67
124
  car.throttle = throttle;
68
125
  car.brake = brake;
69
- car.lateralAcceleration = car.speed * car.yawRate;
70
- car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(car.yawRate / rawYawRate);
126
+ car.lateralAcceleration = speedMetersPerSecond * car.yawRate;
127
+ car.steerSaturation = rawYawRate === 0 ? 0 : Math.abs(steeringYawRate / rawYawRate);
71
128
  const tyreLoad = Math.abs(car.lateralAcceleration) / G;
72
- const wearRate = 0.035 + tyreLoad * 0.11 + brake * 0.07 + throttle * 0.025;
73
- car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 38, 100);
129
+ const tireCare = clamp(Number(car.tireCare) || 1, 0.45, 1.8);
130
+ const wearRate = (0.035 + tyreLoad * 0.11 + brake * 0.07 + throttle * 0.025) / tireCare;
131
+ car.tireEnergy = clamp((car.tireEnergy ?? 100) - wearRate * dt, 1, 100);
74
132
 
75
133
  return car;
76
134
  }
77
135
 
78
- export function getCarCorners(car) {
79
- const halfLength = VEHICLE_LIMITS.carLength / 2;
80
- const halfWidth = VEHICLE_LIMITS.carWidth / 2;
81
- const cos = Math.cos(car.heading);
82
- const sin = Math.sin(car.heading);
83
- const forward = { x: cos, y: sin };
84
- const right = { x: -sin, y: cos };
85
-
86
- return [
87
- {
88
- x: car.x + forward.x * halfLength + right.x * halfWidth,
89
- y: car.y + forward.y * halfLength + right.y * halfWidth,
90
- },
91
- {
92
- x: car.x + forward.x * halfLength - right.x * halfWidth,
93
- y: car.y + forward.y * halfLength - right.y * halfWidth,
94
- },
95
- {
96
- x: car.x - forward.x * halfLength - right.x * halfWidth,
97
- y: car.y - forward.y * halfLength - right.y * halfWidth,
98
- },
99
- {
100
- x: car.x - forward.x * halfLength + right.x * halfWidth,
101
- y: car.y - forward.y * halfLength + right.y * halfWidth,
102
- },
103
- ];
104
- }
136
+ export { getCarCorners };