@adatechnology/game-vehicles-driving 0.0.0-stage → 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,477 @@
1
+ // src/arcade.ts
2
+ var defaultArcadeSpec = {
3
+ length: 4.3,
4
+ width: 1.9,
5
+ accel: 5.5,
6
+ brakeAgainstMotion: 12,
7
+ reverseAccel: 3,
8
+ drag: 0.5,
9
+ handbrakeDrag: 3,
10
+ maxForward: 17,
11
+ maxReverse: -5,
12
+ steerMax: 0.55,
13
+ steerResponse: 5,
14
+ wheelbase: 2.6,
15
+ engineNoiseInterval: 0.5,
16
+ engineNoiseBase: 25,
17
+ engineNoiseSpeedFactor: 2,
18
+ hornNoiseRadius: 120,
19
+ crash: { minSpeed: 0.5, damageSpeed: 4, damage: 9, shakeDivisor: 20, hurtSpeed: 9, hurtFactor: 1.2, bounce: -0.25, noiseRadius: 25 },
20
+ runOver: { knockSpeed: 3, killSpeed: 8, knockDamage: 2.5, stunSeconds: 1.4, throwFactor: 0.8, sideKick: 2, knockSlow: 0.93, killVehicleDamage: 3, holdDrag: 2.5, margin: 0.3 }
21
+ };
22
+ function vehicleCorners(spec, x, z, yaw) {
23
+ const c = Math.cos(yaw);
24
+ const s = Math.sin(yaw);
25
+ const hl = spec.length / 2;
26
+ const hw = spec.width / 2;
27
+ const out = [];
28
+ for (const [a, b] of [[hl, hw], [hl, -hw], [-hl, hw], [-hl, -hw], [hl, 0], [-hl, 0], [0, hw], [0, -hw]]) out.push([x + a * c + b * s, z - a * s + b * c]);
29
+ return out;
30
+ }
31
+ var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
32
+ var lerp = (a, b, t) => a + (b - a) * t;
33
+ function createArcadeDriver(options = {}) {
34
+ const spec = {
35
+ ...defaultArcadeSpec,
36
+ ...options,
37
+ crash: { ...defaultArcadeSpec.crash, ...options.crash || {} },
38
+ runOver: { ...defaultArcadeSpec.runOver, ...options.runOver || {} }
39
+ };
40
+ const state = { x: 0, z: 0, yaw: 0, v: 0, steer: 0, noiseTimer: 0 };
41
+ const step = (input, dt, world) => {
42
+ const events = [];
43
+ const f = (input.throttle > 0 ? 1 : 0) - (input.brake > 0 ? 1 : 0);
44
+ if (f > 0) state.v += (state.v < 0 ? spec.brakeAgainstMotion : spec.accel) * dt * Math.min(1, input.throttle);
45
+ else if (f < 0) state.v -= (state.v > 0 ? spec.brakeAgainstMotion : spec.reverseAccel) * dt * Math.min(1, input.brake);
46
+ else state.v *= Math.exp(-spec.drag * dt);
47
+ if (input.handbrake) state.v *= Math.exp(-spec.handbrakeDrag * dt);
48
+ state.v = clamp(state.v, spec.maxReverse, spec.maxForward);
49
+ state.steer = lerp(state.steer, clamp(input.steer, -1, 1) * spec.steerMax, Math.min(1, dt * spec.steerResponse));
50
+ const nyaw = state.yaw + state.steer * state.v / spec.wheelbase * dt;
51
+ const fx = Math.cos(nyaw);
52
+ const fz = -Math.sin(nyaw);
53
+ const nx = state.x + fx * state.v * dt;
54
+ const nz = state.z + fz * state.v * dt;
55
+ if (!world.blocked(nx, nz, nyaw)) {
56
+ state.x = nx;
57
+ state.z = nz;
58
+ state.yaw = nyaw;
59
+ } else if (Math.abs(state.v) > spec.crash.minSpeed) {
60
+ const sp2 = Math.abs(state.v);
61
+ if (sp2 > spec.crash.damageSpeed) {
62
+ events.push({ type: "crash", speed: sp2, damage: sp2 * spec.crash.damage, shake: sp2 / spec.crash.shakeDivisor, hurtDriver: sp2 > spec.crash.hurtSpeed ? sp2 * spec.crash.hurtFactor : 0 });
63
+ events.push({ type: "noise", radius: spec.crash.noiseRadius, x: state.x, z: state.z, source: "crash" });
64
+ }
65
+ state.v *= spec.crash.bounce;
66
+ }
67
+ const sp = Math.abs(state.v);
68
+ const ro = spec.runOver;
69
+ const bodies = world.bodies ? world.bodies() : [];
70
+ for (let i = 0; i < bodies.length; i++) {
71
+ const body = bodies[i];
72
+ if (!body) continue;
73
+ const dx = body.x - state.x;
74
+ const dz = body.z - state.z;
75
+ const a = dx * fx + dz * fz;
76
+ const b = -dx * fz + dz * fx;
77
+ if (Math.abs(a) > spec.length / 2 + ro.margin || Math.abs(b) > spec.width / 2 + ro.margin) continue;
78
+ if (sp > ro.killSpeed) events.push({ type: "run_over", outcome: "kill", index: i, speed: sp, vehicleDamage: ro.killVehicleDamage });
79
+ else if (sp > ro.knockSpeed) {
80
+ events.push({
81
+ type: "run_over",
82
+ outcome: "knock",
83
+ index: i,
84
+ speed: sp,
85
+ damage: sp * sp * ro.knockDamage,
86
+ stunSeconds: ro.stunSeconds,
87
+ vx: fx * sp * ro.throwFactor + (b > 0 ? -fz : fz) * ro.sideKick,
88
+ vz: fz * sp * ro.throwFactor + (b > 0 ? fx : -fx) * ro.sideKick,
89
+ slow: ro.knockSlow
90
+ });
91
+ state.v *= ro.knockSlow;
92
+ } else {
93
+ state.v *= Math.exp(-ro.holdDrag * dt);
94
+ events.push({ type: "run_over", outcome: "push", index: i, speed: sp, dx: (b > 0 ? -fz : fz) * dt, dz: (b > 0 ? fx : -fx) * dt });
95
+ }
96
+ }
97
+ state.noiseTimer -= dt;
98
+ if (state.noiseTimer <= 0) {
99
+ state.noiseTimer = spec.engineNoiseInterval;
100
+ events.push({ type: "noise", radius: spec.engineNoiseBase + sp * spec.engineNoiseSpeedFactor, x: state.x, z: state.z, source: "engine" });
101
+ }
102
+ if (input.horn) {
103
+ events.push({ type: "noise", radius: spec.hornNoiseRadius, x: state.x, z: state.z, source: "horn" });
104
+ events.push({ type: "horn" });
105
+ }
106
+ return { state, events };
107
+ };
108
+ return {
109
+ spec,
110
+ state,
111
+ step,
112
+ place(x, z, yaw) {
113
+ state.x = x;
114
+ state.z = z;
115
+ state.yaw = yaw;
116
+ state.v = 0;
117
+ state.steer = 0;
118
+ }
119
+ };
120
+ }
121
+
122
+ // src/run-over.ts
123
+ function runOver(speed, rules = {}) {
124
+ const { knockSpeed, killSpeed } = { ...defaultArcadeSpec.runOver, ...rules };
125
+ const s = Math.abs(speed);
126
+ return s > killSpeed ? "kill" : s > knockSpeed ? "knock" : "push";
127
+ }
128
+
129
+ // src/semi/fuel.ts
130
+ var defaultFuelModel = { idleRate: 3e-4, fullRate: 35e-4, loadFactor: 0.5, damagedEngineExtra: 0.4 };
131
+ function fuelRate(model, rpmFraction, throttle, loadRatio, engineHealth) {
132
+ const base = model.idleRate + model.fullRate * Math.max(0, Math.min(1, rpmFraction)) * Math.max(0, Math.min(1, throttle));
133
+ return base * (1 + model.loadFactor * Math.max(0, loadRatio)) * (1 + model.damagedEngineExtra * (1 - Math.max(0, Math.min(1, engineHealth))));
134
+ }
135
+
136
+ // src/semi/surfaces.ts
137
+ import { applyPatches, mergeById, removeIds } from "@adatechnology/game-contracts";
138
+ var defaultSurfaces = [
139
+ { id: "dry_asphalt", grip: 1 },
140
+ { id: "wet_asphalt", grip: 0.75, aquaplaningKmh: 90, aquaplaningFactor: 0.4, aquaplaningBand: 30 },
141
+ { id: "dirt", grip: 0.65 },
142
+ { id: "grass", grip: 0.55 },
143
+ { id: "mud", grip: 0.4, bogs: true },
144
+ /** Blood and bodies after a run-over; a driver applies it for a few seconds with `contaminate`. */
145
+ { id: "blood", grip: 0.5 }
146
+ ];
147
+ function createSurfaces(options = {}) {
148
+ const entries = mergeById(defaultSurfaces, options.packs?.flatMap((pack) => pack.surfaces ?? []));
149
+ applyPatches(entries, options.patch);
150
+ removeIds(entries, options.remove);
151
+ const fallback = entries.get("dry_asphalt") ?? { id: "dry_asphalt", grip: 1 };
152
+ return {
153
+ get: (id) => entries.get(id) ?? fallback,
154
+ get surfaces() {
155
+ return [...entries.values()];
156
+ }
157
+ };
158
+ }
159
+ function surfaceGrip(surface, speedMetresPerSecond) {
160
+ if (surface.aquaplaningKmh == null) return surface.grip;
161
+ const kmh = Math.abs(speedMetresPerSecond) * 3.6;
162
+ const band = surface.aquaplaningBand ?? 30;
163
+ const t = Math.max(0, Math.min(1, (kmh - surface.aquaplaningKmh) / band));
164
+ return surface.grip * (1 - (1 - (surface.aquaplaningFactor ?? 0.4)) * t);
165
+ }
166
+
167
+ // src/semi/driver.ts
168
+ var G = 9.81;
169
+ var clamp2 = (x, a, b) => Math.max(a, Math.min(b, x));
170
+ var MAX_SUBSTEP = 1 / 240;
171
+ var FLAT_BELOW = 0.2;
172
+ var STALL_HEALTH = 0.3;
173
+ var defaultSemiSpec = {
174
+ mass: 1300,
175
+ centreHeight: 0.5,
176
+ wheels: [
177
+ { x: 1.3, z: -0.75, steer: true },
178
+ { x: 1.3, z: 0.75, steer: true },
179
+ { x: -1.3, z: -0.75, drive: true, handbrake: true },
180
+ { x: -1.3, z: 0.75, drive: true, handbrake: true }
181
+ ],
182
+ wheelRadius: 0.32,
183
+ fourWheelDrive: false,
184
+ drag: 0.4,
185
+ rolling: 0.015,
186
+ bogResistance: 0.3,
187
+ maxSteer: 0.55,
188
+ brakeForce: 14e3,
189
+ engine: { idleRpm: 900, peakRpm: 4200, redlineRpm: 6500, peakTorque: 220 },
190
+ gears: [3.6, 2.2, 1.5, 1.1, 0.85],
191
+ reverseRatio: 3.4,
192
+ finalDrive: 3.7,
193
+ efficiency: 0.85,
194
+ upshiftFraction: 0.85,
195
+ downshiftFraction: 0.35,
196
+ suspension: { restLength: 0.4, spring: 32e3, damper: 3200 },
197
+ tyre: { friction: 1.05, corneringStiffness: 9 },
198
+ tank: { capacity: 45 }
199
+ };
200
+ var torqueAt = (spec, rpm) => {
201
+ const e = spec.engine;
202
+ if (rpm >= e.redlineRpm) return 0;
203
+ const t = rpm <= e.peakRpm ? 0.7 + 0.3 * clamp2((rpm - e.idleRpm) / (e.peakRpm - e.idleRpm), 0, 1) : 1 - 0.4 * ((rpm - e.peakRpm) / (e.redlineRpm - e.peakRpm));
204
+ return e.peakTorque * t;
205
+ };
206
+ function createSemiSimDriver(specification = {}, options = {}) {
207
+ const spec = { ...defaultSemiSpec, ...specification };
208
+ const surfaces = options.surfaces ?? createSurfaces();
209
+ const fuelModel = { ...defaultFuelModel, ...spec.tank };
210
+ const wheelbase = Math.max(...spec.wheels.map((w) => w.x)) - Math.min(...spec.wheels.map((w) => w.x));
211
+ const track = Math.max(...spec.wheels.map((w) => w.z)) - Math.min(...spec.wheels.map((w) => w.z));
212
+ const baseMass = spec.mass;
213
+ let cargo = 0;
214
+ let contamination = null;
215
+ const n = spec.wheels.length;
216
+ const state = {
217
+ x: 0,
218
+ z: 0,
219
+ yaw: 0,
220
+ u: 0,
221
+ w: 0,
222
+ yawRate: 0,
223
+ height: spec.suspension.restLength + spec.wheelRadius - spec.mass * G / (specification.wheels ? specification.wheels.length : defaultSemiSpec.wheels.length) / spec.suspension.spring,
224
+ heightRate: 0,
225
+ pitch: 0,
226
+ pitchRate: 0,
227
+ roll: 0,
228
+ rollRate: 0,
229
+ rpm: spec.engine.idleRpm,
230
+ gear: 1,
231
+ engineOn: true,
232
+ bogged: false,
233
+ fuel: spec.tank.capacity,
234
+ loads: new Array(n).fill(baseMass * G / n),
235
+ slip: new Array(n).fill(0),
236
+ steerAngle: 0
237
+ };
238
+ const driver = {
239
+ spec,
240
+ state,
241
+ condition: {},
242
+ setCargo(kg) {
243
+ cargo = Math.max(0, kg);
244
+ },
245
+ contaminate(surfaceId, seconds) {
246
+ contamination = { id: surfaceId, left: seconds };
247
+ },
248
+ refuel(litres) {
249
+ state.fuel = Math.min(spec.tank.capacity, state.fuel + litres);
250
+ },
251
+ place(x, z, yaw) {
252
+ state.x = x;
253
+ state.z = z;
254
+ state.yaw = yaw;
255
+ state.u = state.w = state.yawRate = 0;
256
+ },
257
+ step(input, dt, world = {}) {
258
+ const events = [];
259
+ const steps = Math.max(1, Math.ceil(dt / MAX_SUBSTEP));
260
+ const h = dt / steps;
261
+ for (let s = 0; s < steps; s++) substep(input, h, world, events);
262
+ return { state, events };
263
+ }
264
+ };
265
+ const stallWatch = { throttleTime: 0 };
266
+ function substep(input, dt, world, events) {
267
+ const cond = driver.condition;
268
+ const mass = baseMass + cargo;
269
+ const engineHealth = clamp2(cond.engine ?? 1, 0, 1);
270
+ const suspensionHealth = clamp2(cond.suspension ?? 1, 0, 1);
271
+ const inertia = (spec.yawInertia ?? baseMass * (wheelbase * wheelbase + track * track) / 12) * (mass / baseMass);
272
+ const pitchInertia = mass * (wheelbase * wheelbase + 1) / 12;
273
+ const rollInertia = mass * (track * track + 1) / 12;
274
+ if (contamination) {
275
+ contamination.left -= dt;
276
+ if (contamination.left <= 0) contamination = null;
277
+ }
278
+ const cs = Math.cos(state.yaw);
279
+ const sn = Math.sin(state.yaw);
280
+ const target = clamp2(input.steer, -1, 1) * spec.maxSteer;
281
+ state.steerAngle += (target - state.steerAngle) * Math.min(1, dt * 8);
282
+ let gear = state.gear;
283
+ if (input.gear != null) gear = input.gear;
284
+ else if (state.u < 0.5 && input.brake > 0 && input.throttle <= 0) gear = -1;
285
+ else if (gear <= 0 && !(gear === -1 && state.u < 0.5 && input.throttle <= 0)) gear = 1;
286
+ const ratioOf = (g) => g < 0 ? spec.reverseRatio : g === 0 ? 0 : spec.gears[g - 1];
287
+ const driveWheels = spec.wheels.map((w, i) => ({ w, i })).filter(({ w }) => spec.fourWheelDrive ? true : w.drive);
288
+ let driveSpeed = 0;
289
+ for (const { w } of driveWheels) driveSpeed += state.u + state.yawRate * w.z;
290
+ driveSpeed /= Math.max(1, driveWheels.length);
291
+ const wheelRpm = Math.abs(driveSpeed) / spec.wheelRadius * (60 / (2 * Math.PI));
292
+ if (input.gear == null && gear > 0) {
293
+ if (wheelRpm * ratioOf(gear) * spec.finalDrive > spec.engine.redlineRpm * spec.upshiftFraction && gear < spec.gears.length) gear++;
294
+ else if (gear > 1 && wheelRpm * ratioOf(gear) * spec.finalDrive < spec.engine.redlineRpm * spec.downshiftFraction) gear--;
295
+ }
296
+ if (gear !== state.gear) {
297
+ state.gear = gear;
298
+ events.push({ type: "shift", gear });
299
+ }
300
+ const reversing = state.gear < 0;
301
+ const accelInput = reversing ? input.brake : input.throttle;
302
+ const brakeInput = clamp2(reversing ? input.throttle : input.brake, 0, 1);
303
+ const ratio = ratioOf(state.gear) * spec.finalDrive;
304
+ state.rpm = clamp2(wheelRpm * ratio, spec.engine.idleRpm, spec.engine.redlineRpm + 200);
305
+ if (!state.engineOn || state.fuel <= 0) state.rpm = 0;
306
+ let throttle = clamp2(accelInput, 0, 1);
307
+ if (engineHealth < STALL_HEALTH && throttle > 0.8) {
308
+ stallWatch.throttleTime += dt;
309
+ if (state.pitch > 0.06 && stallWatch.throttleTime > 0.5 && state.engineOn) {
310
+ state.engineOn = false;
311
+ events.push({ type: "stall" });
312
+ }
313
+ throttle *= 0.5;
314
+ } else stallWatch.throttleTime = 0;
315
+ if (!state.engineOn && accelInput > 0 && Math.abs(state.u) < 0.5 && engineHealth >= STALL_HEALTH && state.fuel > 0) state.engineOn = true;
316
+ if (state.fuel <= 0 && state.engineOn) {
317
+ state.engineOn = false;
318
+ events.push({ type: "out_of_fuel" });
319
+ }
320
+ const engineTorque = state.engineOn ? torqueAt(spec, state.rpm) * throttle * (0.55 + 0.45 * engineHealth) : 0;
321
+ const driveForceTotal = state.gear === 0 ? 0 : (reversing ? -1 : 1) * engineTorque * ratio * spec.efficiency / spec.wheelRadius;
322
+ if (state.engineOn) {
323
+ const burned = fuelRate(fuelModel, state.rpm / spec.engine.redlineRpm, throttle, cargo / baseMass, engineHealth) * dt;
324
+ state.fuel = Math.max(0, state.fuel - burned);
325
+ }
326
+ const ride = spec.suspension.restLength;
327
+ let sumN = 0;
328
+ let sumNA = 0;
329
+ let sumNB = 0;
330
+ const wheelState = [];
331
+ let bogging = false;
332
+ for (let i = 0; i < n; i++) {
333
+ const w = spec.wheels[i];
334
+ const wx = state.x + w.x * cs + w.z * sn;
335
+ const wz = state.z - w.x * sn + w.z * cs;
336
+ const ground = world.ground ? world.ground(wx, wz) : {};
337
+ const surface = surfaces.get(contamination && contamination.left > 0 && surfaces.get(contamination.id).grip < surfaces.get(ground.surface ?? "dry_asphalt").grip ? contamination.id : ground.surface ?? "dry_asphalt");
338
+ const top = state.height + w.x * state.pitch - w.z * state.roll;
339
+ const length = top - (ground.height ?? 0) - spec.wheelRadius;
340
+ const compression = ride - length;
341
+ const rate = -(state.heightRate + w.x * state.pitchRate - w.z * state.rollRate);
342
+ const k = spec.suspension.spring * (0.5 + 0.5 * suspensionHealth);
343
+ const c = spec.suspension.damper * (0.5 + 0.5 * suspensionHealth);
344
+ const load = Math.max(0, k * compression + c * rate);
345
+ const tyreHealth = cond.tyres ? clamp2(cond.tyres[i] ?? 1, 0, 1) : 1;
346
+ const flat = tyreHealth < FLAT_BELOW;
347
+ const bog = !!surface.bogs && !spec.fourWheelDrive;
348
+ if (bog) bogging = true;
349
+ const grip = surfaceGrip(surface, state.u) * (flat ? 0.5 : 1);
350
+ wheelState.push({ n: load, grip, surface: surface.id, bog, flat });
351
+ sumN += load;
352
+ sumNA += load * w.x;
353
+ sumNB += load * w.z;
354
+ }
355
+ let fFwd = 0;
356
+ let fRight = 0;
357
+ let torque = 0;
358
+ let pitchTorque = 0;
359
+ let rollTorque = 0;
360
+ const handbrake = !!input.handbrake;
361
+ const loadSum = Math.max(1, wheelState.reduce((a, b) => a + b.n, 0));
362
+ const drivenCount = Math.max(1, driveWheels.length);
363
+ for (let i = 0; i < n; i++) {
364
+ const w = spec.wheels[i];
365
+ const ws = wheelState[i];
366
+ const delta = w.steer ? state.steerAngle : 0;
367
+ const cd = Math.cos(delta);
368
+ const sd = Math.sin(delta);
369
+ const vxw = state.u + state.yawRate * w.z;
370
+ const vyw = state.w - state.yawRate * w.x;
371
+ const vl = vxw * cd - vyw * sd;
372
+ const vc = vxw * sd + vyw * cd;
373
+ const capacity = spec.tyre.friction * ws.grip * ws.n;
374
+ let fl = 0;
375
+ if (driveWheels.some((d) => d.i === i)) fl += driveForceTotal / drivenCount;
376
+ const locked = handbrake && w.handbrake;
377
+ const brakeShare = spec.brakeForce * brakeInput * ws.n / loadSum;
378
+ const moving = Math.abs(vl) > 0.05;
379
+ if (locked) fl += moving ? -Math.sign(vl) * capacity : 0;
380
+ else if (brakeShare > 0 && moving) fl += -Math.sign(vl) * brakeShare;
381
+ const rolling = ws.bog && accelInput > 0 ? spec.bogResistance : spec.rolling * (ws.flat ? 6 : 1);
382
+ const alpha = Math.atan2(vc, Math.abs(vl) + 0.5);
383
+ let fc = -Math.tanh(spec.tyre.corneringStiffness * alpha) * capacity;
384
+ if (locked) fc *= 0.6;
385
+ const total = Math.hypot(fl, fc);
386
+ let used = 1;
387
+ if (total > capacity && total > 0) used = capacity / total;
388
+ fl *= used;
389
+ fc *= used;
390
+ if (moving) fl += -Math.sign(vl) * rolling * ws.n;
391
+ const slipRatio = capacity > 0 ? clamp2(total / capacity, 0, 1) : 0;
392
+ state.slip[i] = slipRatio;
393
+ if (ws.surface === "wet_asphalt" && Math.abs(state.u) * 3.6 > (surfaces.get(ws.surface).aquaplaningKmh ?? 1e9) && slipRatio >= 0.99) events.push({ type: "aquaplaning", wheel: i });
394
+ const fx = fl * cd + fc * sd;
395
+ const fy = -fl * sd + fc * cd;
396
+ fFwd += fx;
397
+ fRight += fy;
398
+ torque += w.z * fx - w.x * fy;
399
+ pitchTorque += fx * spec.centreHeight;
400
+ rollTorque += -fy * spec.centreHeight;
401
+ }
402
+ const bogNow = bogging && Math.abs(state.u) < 1 && accelInput > 0;
403
+ if (bogNow && !state.bogged) events.push({ type: "bogged" });
404
+ state.bogged = bogNow;
405
+ fFwd += -spec.drag * state.u * Math.abs(state.u);
406
+ fRight += -spec.drag * state.w * Math.abs(state.w);
407
+ const du = fFwd / mass - state.yawRate * state.w;
408
+ const dw = fRight / mass + state.yawRate * state.u;
409
+ state.u += du * dt;
410
+ state.w += dw * dt;
411
+ if (Math.abs(state.u) < 0.02 && accelInput <= 0) state.u = 0;
412
+ state.yawRate += torque / inertia * dt;
413
+ state.yaw += state.yawRate * dt;
414
+ const csn = Math.cos(state.yaw);
415
+ const snn = Math.sin(state.yaw);
416
+ state.x += (state.u * csn + state.w * snn) * dt;
417
+ state.z += (-state.u * snn + state.w * csn) * dt;
418
+ const ay = (sumN - mass * G) / mass;
419
+ state.heightRate += ay * dt;
420
+ state.height += state.heightRate * dt;
421
+ state.pitchRate += (sumNA + pitchTorque) / pitchInertia * dt;
422
+ state.pitch += state.pitchRate * dt;
423
+ state.rollRate += (-sumNB + rollTorque) / rollInertia * dt;
424
+ state.roll += state.rollRate * dt;
425
+ for (let i = 0; i < n; i++) state.loads[i] = wheelState[i].n;
426
+ }
427
+ return driver;
428
+ }
429
+
430
+ // src/semi/impact.ts
431
+ var impactEnergy = (vehicleMass, relativeSpeed) => 0.5 * vehicleMass * relativeSpeed * relativeSpeed;
432
+ var bodyDamage = (energy, bumperMultiplier, zoneFactor) => energy * bumperMultiplier * zoneFactor;
433
+ var vehicleDamageFromImpact = (energy, frontArmor, factor = 0.04) => energy * factor * (1 - frontArmor);
434
+ var speedLoss = (bodyMass, vehicleMass, speed) => bodyMass / (vehicleMass + bodyMass) * speed;
435
+
436
+ // src/semi/from-stats.ts
437
+ function semiSpecFromStats(stats, body, base = {}) {
438
+ const spec = { ...defaultSemiSpec, ...base };
439
+ const half = body.wheelbase / 2;
440
+ const side = body.track / 2;
441
+ const driveAll = !!body.fourWheelDrive;
442
+ const wheels = [
443
+ { x: half, z: -side, steer: true, drive: driveAll },
444
+ { x: half, z: side, steer: true, drive: driveAll },
445
+ { x: -half, z: -side, drive: true, handbrake: true },
446
+ { x: -half, z: side, drive: true, handbrake: true }
447
+ ];
448
+ const peakTorque = body.mass * (0.06 + 0.015 * stats.acceleration);
449
+ const engine = { ...spec.engine, peakTorque };
450
+ const top = stats.maxSpeedKmh / 3.6;
451
+ const lastRatio = engine.redlineRpm * 0.95 * 2 * Math.PI * spec.wheelRadius / (60 * top * spec.finalDrive);
452
+ const gears = [...spec.gears.slice(0, -1).map((g, i, all) => Math.max(g, lastRatio * 1.25 ** (all.length - i))), lastRatio];
453
+ const torqueAtTop = peakTorque * 0.76;
454
+ const thrust = torqueAtTop * lastRatio * spec.finalDrive * spec.efficiency / spec.wheelRadius;
455
+ const rollingForce = spec.rolling * body.mass * 9.81;
456
+ const drag = Math.max(0.01, (thrust - rollingForce) / (top * top));
457
+ return { ...spec, mass: body.mass, wheels, fourWheelDrive: driveAll, engine, gears, drag, tyre: { ...spec.tyre, friction: 0.8 + 0.03 * stats.handling } };
458
+ }
459
+ export {
460
+ bodyDamage,
461
+ createArcadeDriver,
462
+ createSemiSimDriver,
463
+ createSurfaces,
464
+ defaultArcadeSpec,
465
+ defaultFuelModel,
466
+ defaultSemiSpec,
467
+ defaultSurfaces,
468
+ fuelRate,
469
+ impactEnergy,
470
+ runOver,
471
+ semiSpecFromStats,
472
+ speedLoss,
473
+ surfaceGrip,
474
+ vehicleCorners,
475
+ vehicleDamageFromImpact
476
+ };
477
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/arcade.ts","../src/run-over.ts","../src/semi/fuel.ts","../src/semi/surfaces.ts","../src/semi/driver.ts","../src/semi/impact.ts","../src/semi/from-stats.ts"],"sourcesContent":["import type { DriveInput, Driver, DriverState, DrivingEvent, DrivingWorld, StepResult } from './types'\n\nexport interface RunOverRules {\n /** Above this speed (m/s) a body is knocked down. */\n knockSpeed: number\n /** Above this speed a body is killed. */\n killSpeed: number\n /** Knock damage is `speed² * knockDamage`. */\n knockDamage: number\n /** Seconds a knocked body is stunned. */\n stunSeconds: number\n /** Fraction of the vehicle speed given to a knocked body. */\n throwFactor: number\n /** Sideways speed added to a knocked body. */\n sideKick: number\n /** The vehicle keeps this fraction of its speed after knocking a body. */\n knockSlow: number\n /** Damage the vehicle takes for each body killed. */\n killVehicleDamage: number\n /** Below `knockSpeed` the vehicle slows by `exp(-holdDrag * dt)` for each body in the way. */\n holdDrag: number\n /** Metres added around the vehicle's footprint when testing a body. */\n margin: number\n}\n\nexport interface CrashRules {\n /** Below this speed an impact is only a bump (no event, but the vehicle still bounces). */\n minSpeed: number\n /** Above this speed the impact damages the vehicle by `speed * damage`. */\n damageSpeed: number\n damage: number\n /** Camera shake is `speed / shakeDivisor`. */\n shakeDivisor: number\n /** Above this speed the driver is hurt by `speed * hurtFactor`. */\n hurtSpeed: number\n hurtFactor: number\n /** The speed after an impact is `v * bounce`. */\n bounce: number\n noiseRadius: number\n}\n\n/** The numbers of the game's drivable car (G 70188-70290). Every one is an option. */\nexport interface ArcadeSpec {\n length: number\n width: number\n accel: number\n /** Deceleration when accelerating against a reverse move, or braking against a forward move. */\n brakeAgainstMotion: number\n reverseAccel: number\n /** Coasting drag: `v *= exp(-drag * dt)`. */\n drag: number\n handbrakeDrag: number\n maxForward: number\n maxReverse: number\n /** Largest steering angle, radians. */\n steerMax: number\n /** The steering angle follows its target by `min(1, steerResponse * dt)`. */\n steerResponse: number\n /** Distance used in `yaw += steer * v / wheelbase * dt`. */\n wheelbase: number\n engineNoiseInterval: number\n engineNoiseBase: number\n engineNoiseSpeedFactor: number\n hornNoiseRadius: number\n crash: CrashRules\n runOver: RunOverRules\n}\n\nexport const defaultArcadeSpec: ArcadeSpec = {\n length: 4.3,\n width: 1.9,\n accel: 5.5,\n brakeAgainstMotion: 12,\n reverseAccel: 3,\n drag: 0.5,\n handbrakeDrag: 3,\n maxForward: 17,\n maxReverse: -5,\n steerMax: 0.55,\n steerResponse: 5,\n wheelbase: 2.6,\n engineNoiseInterval: 0.5,\n engineNoiseBase: 25,\n engineNoiseSpeedFactor: 2,\n hornNoiseRadius: 120,\n crash: { minSpeed: 0.5, damageSpeed: 4, damage: 9, shakeDivisor: 20, hurtSpeed: 9, hurtFactor: 1.2, bounce: -0.25, noiseRadius: 25 },\n runOver: { knockSpeed: 3, killSpeed: 8, knockDamage: 2.5, stunSeconds: 1.4, throwFactor: 0.8, sideKick: 2, knockSlow: 0.93, killVehicleDamage: 3, holdDrag: 2.5, margin: 0.3 },\n}\n\nexport type ArcadeOptions = Partial<Omit<ArcadeSpec, 'crash' | 'runOver'>> & { crash?: Partial<CrashRules>; runOver?: Partial<RunOverRules> }\n\n/** The eight points the game tests for a wall: the four corners, the middle of the front and rear, and the middle of both sides. */\nexport function vehicleCorners(spec: Pick<ArcadeSpec, 'length' | 'width'>, x: number, z: number, yaw: number): Array<[number, number]> {\n const c = Math.cos(yaw)\n const s = Math.sin(yaw)\n const hl = spec.length / 2\n const hw = spec.width / 2\n const out: Array<[number, number]> = []\n for (const [a, b] of [[hl, hw], [hl, -hw], [-hl, hw], [-hl, -hw], [hl, 0], [-hl, 0], [0, hw], [0, -hw]] as const) out.push([x + a * c + b * s, z - a * s + b * c])\n return out\n}\n\nconst clamp = (x: number, a: number, b: number): number => Math.max(a, Math.min(b, x))\nconst lerp = (a: number, b: number, t: number): number => a + (b - a) * t\n\n/**\n * The arcade driver: the game's car, extracted. A pure step function over `DriverState`: no three.js, no game. Port of `carUpdate`\n * (jogo.js 70233-70290) with the same order of operations, so a scripted run gives the same positions and events.\n */\nexport function createArcadeDriver(options: ArcadeOptions = {}): Driver & { readonly spec: ArcadeSpec } {\n const spec: ArcadeSpec = {\n ...defaultArcadeSpec,\n ...options,\n crash: { ...defaultArcadeSpec.crash, ...(options.crash || {}) },\n runOver: { ...defaultArcadeSpec.runOver, ...(options.runOver || {}) },\n }\n const state: DriverState = { x: 0, z: 0, yaw: 0, v: 0, steer: 0, noiseTimer: 0 }\n\n const step = (input: DriveInput, dt: number, world: DrivingWorld): StepResult => {\n const events: DrivingEvent[] = []\n const f = (input.throttle > 0 ? 1 : 0) - (input.brake > 0 ? 1 : 0)\n if (f > 0) state.v += (state.v < 0 ? spec.brakeAgainstMotion : spec.accel) * dt * Math.min(1, input.throttle)\n else if (f < 0) state.v -= (state.v > 0 ? spec.brakeAgainstMotion : spec.reverseAccel) * dt * Math.min(1, input.brake)\n else state.v *= Math.exp(-spec.drag * dt)\n if (input.handbrake) state.v *= Math.exp(-spec.handbrakeDrag * dt)\n state.v = clamp(state.v, spec.maxReverse, spec.maxForward)\n state.steer = lerp(state.steer, clamp(input.steer, -1, 1) * spec.steerMax, Math.min(1, dt * spec.steerResponse))\n const nyaw = state.yaw + ((state.steer * state.v) / spec.wheelbase) * dt\n const fx = Math.cos(nyaw)\n const fz = -Math.sin(nyaw)\n const nx = state.x + fx * state.v * dt\n const nz = state.z + fz * state.v * dt\n if (!world.blocked(nx, nz, nyaw)) {\n state.x = nx\n state.z = nz\n state.yaw = nyaw\n } else if (Math.abs(state.v) > spec.crash.minSpeed) {\n const sp = Math.abs(state.v)\n if (sp > spec.crash.damageSpeed) {\n events.push({ type: 'crash', speed: sp, damage: sp * spec.crash.damage, shake: sp / spec.crash.shakeDivisor, hurtDriver: sp > spec.crash.hurtSpeed ? sp * spec.crash.hurtFactor : 0 })\n events.push({ type: 'noise', radius: spec.crash.noiseRadius, x: state.x, z: state.z, source: 'crash' })\n }\n state.v *= spec.crash.bounce\n }\n // run-over: speed is read once, before the loop, as the game does\n const sp = Math.abs(state.v)\n const ro = spec.runOver\n const bodies = world.bodies ? world.bodies() : []\n for (let i = 0; i < bodies.length; i++) {\n const body = bodies[i]\n if (!body) continue\n const dx = body.x - state.x\n const dz = body.z - state.z\n const a = dx * fx + dz * fz\n const b = -dx * fz + dz * fx\n if (Math.abs(a) > spec.length / 2 + ro.margin || Math.abs(b) > spec.width / 2 + ro.margin) continue\n if (sp > ro.killSpeed) events.push({ type: 'run_over', outcome: 'kill', index: i, speed: sp, vehicleDamage: ro.killVehicleDamage })\n else if (sp > ro.knockSpeed) {\n events.push({\n type: 'run_over',\n outcome: 'knock',\n index: i,\n speed: sp,\n damage: sp * sp * ro.knockDamage,\n stunSeconds: ro.stunSeconds,\n vx: fx * sp * ro.throwFactor + (b > 0 ? -fz : fz) * ro.sideKick,\n vz: fz * sp * ro.throwFactor + (b > 0 ? fx : -fx) * ro.sideKick,\n slow: ro.knockSlow,\n })\n state.v *= ro.knockSlow\n } else {\n state.v *= Math.exp(-ro.holdDrag * dt)\n events.push({ type: 'run_over', outcome: 'push', index: i, speed: sp, dx: (b > 0 ? -fz : fz) * dt, dz: (b > 0 ? fx : -fx) * dt })\n }\n }\n state.noiseTimer -= dt\n if (state.noiseTimer <= 0) {\n state.noiseTimer = spec.engineNoiseInterval\n events.push({ type: 'noise', radius: spec.engineNoiseBase + sp * spec.engineNoiseSpeedFactor, x: state.x, z: state.z, source: 'engine' })\n }\n if (input.horn) {\n events.push({ type: 'noise', radius: spec.hornNoiseRadius, x: state.x, z: state.z, source: 'horn' })\n events.push({ type: 'horn' })\n }\n return { state, events }\n }\n\n return {\n spec,\n state,\n step,\n place(x, z, yaw) {\n state.x = x\n state.z = z\n state.yaw = yaw\n state.v = 0\n state.steer = 0\n },\n }\n}\n","import type { RunOverRules } from './arcade'\nimport { defaultArcadeSpec } from './arcade'\n\nexport type RunOverOutcome = 'push' | 'knock' | 'kill'\n\n/** What a vehicle moving at `speed` (m/s) does to a body it touches: pushes it (and is held), knocks it down, or kills it. */\nexport function runOver(speed: number, rules: Partial<RunOverRules> = {}): RunOverOutcome {\n const { knockSpeed, killSpeed } = { ...defaultArcadeSpec.runOver, ...rules }\n const s = Math.abs(speed)\n return s > killSpeed ? 'kill' : s > knockSpeed ? 'knock' : 'push'\n}\n","export interface FuelModel {\n /** Litres per second at idle. */\n idleRate: number\n /** Extra litres per second at full throttle and redline. */\n fullRate: number\n /** Extra consumption per unit of cargo mass over the vehicle's own mass (0.5 = +50% when the load equals the vehicle). */\n loadFactor: number\n /** Extra consumption of a wrecked engine (0.4 = up to +40% at zero health). */\n damagedEngineExtra: number\n}\n\nexport const defaultFuelModel: FuelModel = { idleRate: 0.0003, fullRate: 0.0035, loadFactor: 0.5, damagedEngineExtra: 0.4 }\n\n/**\n * Litres per second: it depends on the RPM, the throttle, the weight of the cargo and the state of the engine\n * (Frota document, section 04: \"a damaged engine spends up to 40% more\").\n */\nexport function fuelRate(model: FuelModel, rpmFraction: number, throttle: number, loadRatio: number, engineHealth: number): number {\n const base = model.idleRate + model.fullRate * Math.max(0, Math.min(1, rpmFraction)) * Math.max(0, Math.min(1, throttle))\n return base * (1 + model.loadFactor * Math.max(0, loadRatio)) * (1 + model.damagedEngineExtra * (1 - Math.max(0, Math.min(1, engineHealth))))\n}\n","import { applyPatches, mergeById, removeIds } from '@adatechnology/game-contracts'\n\n/** A kind of ground the tyres run on. */\nexport interface Surface {\n id: string\n /** Multiplier of the tyre grip (dry asphalt = 1). */\n grip: number\n /** Above this speed (km/h) the tyres float: grip drops linearly to `grip * aquaplaningFactor` over `aquaplaningBand` km/h. */\n aquaplaningKmh?: number\n aquaplaningFactor?: number\n aquaplaningBand?: number\n /** A vehicle without four-wheel drive sinks: its rolling resistance becomes the spec's `bogResistance`. */\n bogs?: boolean\n}\n\n/** The table of the Frota document, section 03 (Surfaces). Every number is a default a game can replace. */\nexport const defaultSurfaces: readonly Surface[] = [\n { id: 'dry_asphalt', grip: 1 },\n { id: 'wet_asphalt', grip: 0.75, aquaplaningKmh: 90, aquaplaningFactor: 0.4, aquaplaningBand: 30 },\n { id: 'dirt', grip: 0.65 },\n { id: 'grass', grip: 0.55 },\n { id: 'mud', grip: 0.4, bogs: true },\n /** Blood and bodies after a run-over; a driver applies it for a few seconds with `contaminate`. */\n { id: 'blood', grip: 0.5 },\n]\n\nexport interface SurfaceOptions {\n packs?: ReadonlyArray<{ surfaces?: readonly Surface[] }>\n patch?: Readonly<Record<string, Partial<Surface>>>\n remove?: readonly string[]\n}\n\nexport interface SurfaceRegistry {\n get(id: string): Surface\n readonly surfaces: readonly Surface[]\n}\n\n/** The registry of surfaces: the document's table, plus packs, patches and removals by id. An unknown id falls back to dry asphalt. */\nexport function createSurfaces(options: SurfaceOptions = {}): SurfaceRegistry {\n const entries = mergeById(defaultSurfaces, options.packs?.flatMap((pack) => pack.surfaces ?? []))\n applyPatches(entries, options.patch)\n removeIds(entries, options.remove)\n const fallback = entries.get('dry_asphalt') ?? ({ id: 'dry_asphalt', grip: 1 } as Surface)\n return {\n get: (id) => entries.get(id) ?? fallback,\n get surfaces() {\n return [...entries.values()]\n },\n }\n}\n\n/** Grip of a surface at a speed, with aquaplaning. */\nexport function surfaceGrip(surface: Surface, speedMetresPerSecond: number): number {\n if (surface.aquaplaningKmh == null) return surface.grip\n const kmh = Math.abs(speedMetresPerSecond) * 3.6\n const band = surface.aquaplaningBand ?? 30\n const t = Math.max(0, Math.min(1, (kmh - surface.aquaplaningKmh) / band))\n return surface.grip * (1 - (1 - (surface.aquaplaningFactor ?? 0.4)) * t)\n}\n","import type { Driver } from '../types'\nimport { fuelRate, defaultFuelModel } from './fuel'\nimport type { FuelModel } from './fuel'\nimport { createSurfaces, surfaceGrip } from './surfaces'\nimport type { SurfaceRegistry } from './surfaces'\nimport type { SemiEvent, SemiInput, SemiSimSpec, SemiState, SemiWorld, VehicleCondition } from './types'\n\nconst G = 9.81\nconst clamp = (x: number, a: number, b: number): number => Math.max(a, Math.min(b, x))\nconst MAX_SUBSTEP = 1 / 240\nconst FLAT_BELOW = 0.2\nconst STALL_HEALTH = 0.3\n\n/** A mid-size passenger car, so a game can start from numbers that drive like a car. Every field can be replaced. */\nexport const defaultSemiSpec: SemiSimSpec = {\n mass: 1300,\n centreHeight: 0.5,\n wheels: [\n { x: 1.3, z: -0.75, steer: true },\n { x: 1.3, z: 0.75, steer: true },\n { x: -1.3, z: -0.75, drive: true, handbrake: true },\n { x: -1.3, z: 0.75, drive: true, handbrake: true },\n ],\n wheelRadius: 0.32,\n fourWheelDrive: false,\n drag: 0.4,\n rolling: 0.015,\n bogResistance: 0.3,\n maxSteer: 0.55,\n brakeForce: 14000,\n engine: { idleRpm: 900, peakRpm: 4200, redlineRpm: 6500, peakTorque: 220 },\n gears: [3.6, 2.2, 1.5, 1.1, 0.85],\n reverseRatio: 3.4,\n finalDrive: 3.7,\n efficiency: 0.85,\n upshiftFraction: 0.85,\n downshiftFraction: 0.35,\n suspension: { restLength: 0.4, spring: 32000, damper: 3200 },\n tyre: { friction: 1.05, corneringStiffness: 9 },\n tank: { capacity: 45 },\n}\n\nexport interface SemiSimOptions {\n surfaces?: SurfaceRegistry\n}\n\nexport interface SemiDriver extends Omit<Driver, 'step' | 'state'> {\n readonly spec: SemiSimSpec\n readonly state: SemiState\n condition: VehicleCondition\n /** Sets the load carried, kilograms; the mass, the wheel loads and the fuel use follow. */\n setCargo(kg: number): void\n /** A surface that overrides the ground under the wheels for `seconds` (blood after a run-over). */\n contaminate(surfaceId: string, seconds: number): void\n /** Fills the tank (litres). */\n refuel(litres: number): void\n step(input: SemiInput, dt: number, world?: SemiWorld): { state: SemiState; events: SemiEvent[] }\n}\n\nconst torqueAt = (spec: SemiSimSpec, rpm: number): number => {\n const e = spec.engine\n if (rpm >= e.redlineRpm) return 0\n const t = rpm <= e.peakRpm ? 0.7 + 0.3 * clamp((rpm - e.idleRpm) / (e.peakRpm - e.idleRpm), 0, 1) : 1 - 0.4 * ((rpm - e.peakRpm) / (e.redlineRpm - e.peakRpm))\n return e.peakTorque * t\n}\n\n/**\n * The semi-simulation driver (Frota document, section 03): a single rigid body with a low centre of mass, a spring and damper on each wheel\n * measured against the ground under it, separate longitudinal and lateral tyre forces inside a friction circle, an automatic gearbox with a\n * simulated RPM, a handbrake that locks the rear wheels, surfaces with their grip, and damage that changes how it drives. No team code exists\n * for it: the numbers are the document's and the tests check them.\n */\nexport function createSemiSimDriver(specification: Partial<SemiSimSpec> = {}, options: SemiSimOptions = {}): SemiDriver {\n const spec: SemiSimSpec = { ...defaultSemiSpec, ...specification }\n const surfaces = options.surfaces ?? createSurfaces()\n const fuelModel: FuelModel = { ...defaultFuelModel, ...spec.tank }\n const wheelbase = Math.max(...spec.wheels.map((w) => w.x)) - Math.min(...spec.wheels.map((w) => w.x))\n const track = Math.max(...spec.wheels.map((w) => w.z)) - Math.min(...spec.wheels.map((w) => w.z))\n const baseMass = spec.mass\n let cargo = 0\n let contamination: { id: string; left: number } | null = null\n const n = spec.wheels.length\n const state: SemiState = {\n x: 0,\n z: 0,\n yaw: 0,\n u: 0,\n w: 0,\n yawRate: 0,\n height: spec.suspension.restLength + spec.wheelRadius - (spec.mass * G) / (specification.wheels ? specification.wheels.length : defaultSemiSpec.wheels.length) / spec.suspension.spring,\n heightRate: 0,\n pitch: 0,\n pitchRate: 0,\n roll: 0,\n rollRate: 0,\n rpm: spec.engine.idleRpm,\n gear: 1,\n engineOn: true,\n bogged: false,\n fuel: spec.tank.capacity,\n loads: new Array<number>(n).fill((baseMass * G) / n),\n slip: new Array<number>(n).fill(0),\n steerAngle: 0,\n }\n const driver: SemiDriver = {\n spec,\n state,\n condition: {},\n setCargo(kg) {\n cargo = Math.max(0, kg)\n },\n contaminate(surfaceId, seconds) {\n contamination = { id: surfaceId, left: seconds }\n },\n refuel(litres) {\n state.fuel = Math.min(spec.tank.capacity, state.fuel + litres)\n },\n place(x, z, yaw) {\n state.x = x\n state.z = z\n state.yaw = yaw\n state.u = state.w = state.yawRate = 0\n },\n step(input, dt, world = {}) {\n const events: SemiEvent[] = []\n const steps = Math.max(1, Math.ceil(dt / MAX_SUBSTEP))\n const h = dt / steps\n for (let s = 0; s < steps; s++) substep(input, h, world, events)\n return { state, events }\n },\n }\n\n const stallWatch = { throttleTime: 0 }\n\n function substep(input: SemiInput, dt: number, world: SemiWorld, events: SemiEvent[]): void {\n const cond = driver.condition\n const mass = baseMass + cargo\n const engineHealth = clamp(cond.engine ?? 1, 0, 1)\n const suspensionHealth = clamp(cond.suspension ?? 1, 0, 1)\n const inertia = (spec.yawInertia ?? (baseMass * (wheelbase * wheelbase + track * track)) / 12) * (mass / baseMass)\n const pitchInertia = (mass * (wheelbase * wheelbase + 1)) / 12\n const rollInertia = (mass * (track * track + 1)) / 12\n if (contamination) {\n contamination.left -= dt\n if (contamination.left <= 0) contamination = null\n }\n const cs = Math.cos(state.yaw)\n const sn = Math.sin(state.yaw)\n // steering: the front wheels follow the input, slower when fast (the wheel angle limit shrinks a little)\n const target = clamp(input.steer, -1, 1) * spec.maxSteer\n state.steerAngle += (target - state.steerAngle) * Math.min(1, dt * 8)\n // gearbox: automatic by default. From a stop, the brake reverses and the throttle goes forward again.\n let gear = state.gear\n if (input.gear != null) gear = input.gear\n else if (state.u < 0.5 && input.brake > 0 && input.throttle <= 0) gear = -1\n else if (gear <= 0 && !(gear === -1 && state.u < 0.5 && input.throttle <= 0)) gear = 1\n const ratioOf = (g: number): number => (g < 0 ? spec.reverseRatio : g === 0 ? 0 : (spec.gears[g - 1] as number))\n const driveWheels = spec.wheels.map((w, i) => ({ w, i })).filter(({ w }) => (spec.fourWheelDrive ? true : w.drive))\n // the wheel speed that drives the engine: mean longitudinal speed of the driven wheels / radius\n let driveSpeed = 0\n for (const { w } of driveWheels) driveSpeed += state.u + state.yawRate * w.z\n driveSpeed /= Math.max(1, driveWheels.length)\n const wheelRpm = (Math.abs(driveSpeed) / spec.wheelRadius) * (60 / (2 * Math.PI))\n if (input.gear == null && gear > 0) {\n if (wheelRpm * ratioOf(gear) * spec.finalDrive > spec.engine.redlineRpm * spec.upshiftFraction && gear < spec.gears.length) gear++\n else if (gear > 1 && wheelRpm * ratioOf(gear) * spec.finalDrive < spec.engine.redlineRpm * spec.downshiftFraction) gear--\n }\n if (gear !== state.gear) {\n state.gear = gear\n events.push({ type: 'shift', gear })\n }\n const reversing = state.gear < 0\n const accelInput = reversing ? input.brake : input.throttle\n const brakeInput = clamp(reversing ? input.throttle : input.brake, 0, 1)\n const ratio = ratioOf(state.gear) * spec.finalDrive\n state.rpm = clamp(wheelRpm * ratio, spec.engine.idleRpm, spec.engine.redlineRpm + 200)\n if (!state.engineOn || state.fuel <= 0) state.rpm = 0\n // engine torque: a wrecked engine fails to deliver at full throttle, and under 30% it can die uphill\n let throttle = clamp(accelInput, 0, 1)\n if (engineHealth < STALL_HEALTH && throttle > 0.8) {\n stallWatch.throttleTime += dt\n if (state.pitch > 0.06 && stallWatch.throttleTime > 0.5 && state.engineOn) {\n state.engineOn = false\n events.push({ type: 'stall' })\n }\n throttle *= 0.5\n } else stallWatch.throttleTime = 0\n if (!state.engineOn && accelInput > 0 && Math.abs(state.u) < 0.5 && engineHealth >= STALL_HEALTH && state.fuel > 0) state.engineOn = true\n if (state.fuel <= 0 && state.engineOn) {\n state.engineOn = false\n events.push({ type: 'out_of_fuel' })\n }\n const engineTorque = state.engineOn ? torqueAt(spec, state.rpm) * throttle * (0.55 + 0.45 * engineHealth) : 0\n const driveForceTotal = state.gear === 0 ? 0 : ((reversing ? -1 : 1) * engineTorque * ratio * spec.efficiency) / spec.wheelRadius\n // fuel\n if (state.engineOn) {\n const burned = fuelRate(fuelModel, state.rpm / spec.engine.redlineRpm, throttle, cargo / baseMass, engineHealth) * dt\n state.fuel = Math.max(0, state.fuel - burned)\n }\n\n // ground under each wheel, and the suspension\n const ride = spec.suspension.restLength\n let sumN = 0\n let sumNA = 0\n let sumNB = 0\n const wheelState: Array<{ n: number; grip: number; surface: string; bog: boolean; flat: boolean }> = []\n let bogging = false\n for (let i = 0; i < n; i++) {\n const w = spec.wheels[i] as (typeof spec.wheels)[number]\n const wx = state.x + w.x * cs + w.z * sn\n const wz = state.z - w.x * sn + w.z * cs\n const ground = world.ground ? world.ground(wx, wz) : {}\n const surface = surfaces.get(contamination && contamination.left > 0 && surfaces.get(contamination.id).grip < surfaces.get(ground.surface ?? 'dry_asphalt').grip ? contamination.id : (ground.surface ?? 'dry_asphalt'))\n const top = state.height + w.x * state.pitch - w.z * state.roll\n const length = top - (ground.height ?? 0) - spec.wheelRadius\n const compression = ride - length\n const rate = -(state.heightRate + w.x * state.pitchRate - w.z * state.rollRate)\n const k = spec.suspension.spring * (0.5 + 0.5 * suspensionHealth)\n const c = spec.suspension.damper * (0.5 + 0.5 * suspensionHealth)\n const load = Math.max(0, k * compression + c * rate)\n const tyreHealth = cond.tyres ? clamp(cond.tyres[i] ?? 1, 0, 1) : 1\n const flat = tyreHealth < FLAT_BELOW\n const bog = !!surface.bogs && !spec.fourWheelDrive\n if (bog) bogging = true\n const grip = surfaceGrip(surface, state.u) * (flat ? 0.5 : 1)\n wheelState.push({ n: load, grip, surface: surface.id, bog, flat })\n sumN += load\n sumNA += load * w.x\n sumNB += load * w.z\n }\n // forces on the ground, per wheel\n let fFwd = 0\n let fRight = 0\n let torque = 0\n let pitchTorque = 0\n let rollTorque = 0\n const handbrake = !!input.handbrake\n const loadSum = Math.max(1, wheelState.reduce((a, b) => a + b.n, 0))\n const drivenCount = Math.max(1, driveWheels.length)\n for (let i = 0; i < n; i++) {\n const w = spec.wheels[i] as (typeof spec.wheels)[number]\n const ws = wheelState[i] as (typeof wheelState)[number]\n const delta = w.steer ? state.steerAngle : 0\n const cd = Math.cos(delta)\n const sd = Math.sin(delta)\n const vxw = state.u + state.yawRate * w.z\n const vyw = state.w - state.yawRate * w.x\n const vl = vxw * cd - vyw * sd\n const vc = vxw * sd + vyw * cd\n const capacity = spec.tyre.friction * ws.grip * ws.n\n // longitudinal request: drive, brake, rolling and handbrake lock\n let fl = 0\n if (driveWheels.some((d) => d.i === i)) fl += driveForceTotal / drivenCount\n const locked = handbrake && w.handbrake\n const brakeShare = (spec.brakeForce * brakeInput * ws.n) / loadSum\n const moving = Math.abs(vl) > 0.05\n if (locked) fl += moving ? -Math.sign(vl) * capacity : 0\n else if (brakeShare > 0 && moving) fl += -Math.sign(vl) * brakeShare\n const rolling = ws.bog && accelInput > 0 ? spec.bogResistance : spec.rolling * (ws.flat ? 6 : 1)\n // lateral: slip angle into a saturating curve, separate from the longitudinal one\n const alpha = Math.atan2(vc, Math.abs(vl) + 0.5)\n let fc = -Math.tanh((spec.tyre.corneringStiffness * alpha)) * capacity\n if (locked) fc *= 0.6\n // friction circle\n const total = Math.hypot(fl, fc)\n let used = 1\n if (total > capacity && total > 0) used = capacity / total\n fl *= used\n fc *= used\n // rolling resistance is the ground's, not the tyre's: it is not limited by the friction circle\n if (moving) fl += -Math.sign(vl) * rolling * ws.n\n const slipRatio = capacity > 0 ? clamp(total / capacity, 0, 1) : 0\n state.slip[i] = slipRatio\n if (ws.surface === 'wet_asphalt' && Math.abs(state.u) * 3.6 > (surfaces.get(ws.surface).aquaplaningKmh ?? 1e9) && slipRatio >= 0.99) events.push({ type: 'aquaplaning', wheel: i })\n // to the body\n const fx = fl * cd + fc * sd\n const fy = -fl * sd + fc * cd\n fFwd += fx\n fRight += fy\n torque += w.z * fx - w.x * fy\n pitchTorque += fx * spec.centreHeight\n rollTorque += -fy * spec.centreHeight\n }\n const bogNow = bogging && Math.abs(state.u) < 1 && accelInput > 0\n if (bogNow && !state.bogged) events.push({ type: 'bogged' })\n state.bogged = bogNow\n // air drag\n fFwd += -spec.drag * state.u * Math.abs(state.u)\n fRight += -spec.drag * state.w * Math.abs(state.w)\n // planar integration in the body frame\n const du = fFwd / mass - state.yawRate * state.w\n const dw = fRight / mass + state.yawRate * state.u\n state.u += du * dt\n state.w += dw * dt\n // a stopped vehicle does not creep: the sticking friction holds it\n if (Math.abs(state.u) < 0.02 && accelInput <= 0) state.u = 0\n state.yawRate += (torque / inertia) * dt\n state.yaw += state.yawRate * dt\n const csn = Math.cos(state.yaw)\n const snn = Math.sin(state.yaw)\n state.x += (state.u * csn + state.w * snn) * dt\n state.z += (-state.u * snn + state.w * csn) * dt\n // vertical, pitch and roll from the wheel loads\n const ay = (sumN - mass * G) / mass\n state.heightRate += ay * dt\n state.height += state.heightRate * dt\n state.pitchRate += ((sumNA + pitchTorque) / pitchInertia) * dt\n state.pitch += state.pitchRate * dt\n state.rollRate += ((-sumNB + rollTorque) / rollInertia) * dt\n state.roll += state.rollRate * dt\n for (let i = 0; i < n; i++) state.loads[i] = (wheelState[i] as (typeof wheelState)[number]).n\n }\n\n return driver\n}\n","/** Kinetic energy of a vehicle against a body: `0.5 * mass * speed²` (joules, with kg and m/s). Frota document, section 08. */\nexport const impactEnergy = (vehicleMass: number, relativeSpeed: number): number => 0.5 * vehicleMass * relativeSpeed * relativeSpeed\n\n/** Damage to the body that was hit: `energy * bumperMultiplier * zoneFactor`. The zone factors (head, torso, legs) belong to the game. */\nexport const bodyDamage = (energy: number, bumperMultiplier: number, zoneFactor: number): number => energy * bumperMultiplier * zoneFactor\n\n/** Damage to the vehicle: `energy * 0.04 * (1 - frontArmor)`, with the armour from 0 to 1. */\nexport const vehicleDamageFromImpact = (energy: number, frontArmor: number, factor = 0.04): number => energy * factor * (1 - frontArmor)\n\n/** Speed the vehicle loses: `bodyMass / (vehicleMass + bodyMass) * speed`. */\nexport const speedLoss = (bodyMass: number, vehicleMass: number, speed: number): number => (bodyMass / (vehicleMass + bodyMass)) * speed\n","import { defaultSemiSpec } from './driver'\nimport type { SemiSimSpec } from './types'\n\n/** The design ratings of a vehicle (the shape of `fleetStats` in `game-vehicles-fleet`; any object with these fields works). */\nexport interface DesignStats {\n /** 0-10. */\n acceleration: number\n /** 0-10. */\n handling: number\n maxSpeedKmh: number\n}\n\nexport interface BodyDimensions {\n mass: number\n wheelbase: number\n track: number\n fourWheelDrive?: boolean\n}\n\n/**\n * A semi-simulation spec for a two-axle vehicle from its design ratings: the grip follows the handling, the torque follows the acceleration,\n * and the air drag is set so that the top gear tops out at the design top speed.\n */\nexport function semiSpecFromStats(stats: DesignStats, body: BodyDimensions, base: Partial<SemiSimSpec> = {}): SemiSimSpec {\n const spec: SemiSimSpec = { ...defaultSemiSpec, ...base }\n const half = body.wheelbase / 2\n const side = body.track / 2\n const driveAll = !!body.fourWheelDrive\n const wheels = [\n { x: half, z: -side, steer: true, drive: driveAll },\n { x: half, z: side, steer: true, drive: driveAll },\n { x: -half, z: -side, drive: true, handbrake: true },\n { x: -half, z: side, drive: true, handbrake: true },\n ]\n const peakTorque = body.mass * (0.06 + 0.015 * stats.acceleration)\n const engine = { ...spec.engine, peakTorque }\n const top = stats.maxSpeedKmh / 3.6\n // the last gear puts the redline at the top speed\n const lastRatio = (engine.redlineRpm * 0.95 * 2 * Math.PI * spec.wheelRadius) / (60 * top * spec.finalDrive)\n const gears = [...spec.gears.slice(0, -1).map((g, i, all) => Math.max(g, lastRatio * (1.25 ** (all.length - i)))), lastRatio]\n const torqueAtTop = peakTorque * 0.76\n const thrust = (torqueAtTop * lastRatio * spec.finalDrive * spec.efficiency) / spec.wheelRadius\n const rollingForce = spec.rolling * body.mass * 9.81\n const drag = Math.max(0.01, (thrust - rollingForce) / (top * top))\n return { ...spec, mass: body.mass, wheels, fourWheelDrive: driveAll, engine, gears, drag, tyre: { ...spec.tyre, friction: 0.8 + 0.03 * stats.handling } }\n}\n"],"mappings":"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","names":["sp","clamp"]}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@adatechnology/game-vehicles-driving",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "license": "MIT",
5
+ "description": "Driving for vehicles, without three.js and without a game: an arcade driver (the game's car: acceleration, braking, handbrake, steering, corner collision, crash damage, noise, run-over of bodies) and a semi-simulation driver (suspension, tyre friction, gearbox, surfaces, damage coupling, consumption). Knows nothing about any game.",
6
+ "keywords": [
7
+ "game",
8
+ "vehicles",
9
+ "driving",
10
+ "arcade",
11
+ "physics",
12
+ "car"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Andersonfrfilho/adatechnology-game-kit.git",
17
+ "directory": "packages/game-vehicles-driving"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "module": "dist/index.js",
25
+ "types": "dist/index.d.ts",
26
+ "unpkg": "dist/index.global.js",
27
+ "jsdelivr": "dist/index.global.js",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ },
34
+ "./global": "./dist/index.global.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "MIGRATION.md"
39
+ ],
40
+ "devDependencies": {
41
+ "@types/node": "^22.10.7",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "^5.7.3",
44
+ "vitest": "^3.2.4",
45
+ "@adatechnology/golden-tool": "0.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@adatechnology/game-contracts": "0.13.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "typecheck": "tsc -p tsconfig.json --noEmit",
53
+ "test": "vitest run"
54
+ }
6
55
  }