@adatechnology/game-vehicles-driving 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/index.d.ts +405 -0
- package/dist/index.global.js +529 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @adatechnology/game-vehicles-driving
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Driving for vehicles, without three.js and without a game. The arcade driver is the car of the game, extracted: a pure step over a state, with every number an option.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createArcadeDriver, vehicleCorners } from '@adatechnology/game-vehicles-driving'
|
|
7
|
+
|
|
8
|
+
const driver = createArcadeDriver({ maxForward: 20 })
|
|
9
|
+
driver.place(0, 0, 0)
|
|
10
|
+
const world = {
|
|
11
|
+
blocked: (x, z, yaw) => vehicleCorners(driver.spec, x, z, yaw).some(([a, b]) => isSolid(a, b)),
|
|
12
|
+
bodies: () => zombies.map((z) => (z.alive ? z : undefined)),
|
|
13
|
+
}
|
|
14
|
+
const { state, events } = driver.step({ throttle: 1, brake: 0, steer: 0, handbrake: false }, dt, world)
|
|
15
|
+
// events: crash, noise (crash, engine, horn), run_over (kill, knock, push), horn
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The driver never touches a game: the host applies damage, shakes the camera, kills the bodies and plays the sounds from the events. Tested against the game's own `carUpdate` cut from `jogo.js` (positions and events frame by frame).
|
|
19
|
+
|
|
20
|
+
## Semi-simulation
|
|
21
|
+
|
|
22
|
+
`createSemiSimDriver(spec)` is the driving the Frota document asks for: a single rigid body with a low centre of mass, a spring and damper on each wheel measured against the ground under it (`world.ground(x, z)`), longitudinal and lateral tyre forces apart inside a friction circle, an automatic gearbox with a simulated RPM, a handbrake that locks the rear wheels, surfaces (`dry_asphalt` 1.0, `wet_asphalt` 0.75 with aquaplaning above 90 km/h, `dirt` 0.65, `grass` 0.55, `mud` 0.4 that bogs a vehicle without four-wheel drive, `blood` 0.5) and damage that changes how it drives (`driver.condition`: a flat tyre pulls to its side, a damaged suspension rolls more, an engine under 30% fails at full throttle and can die uphill). Fuel use follows the RPM, the cargo and the engine (up to +40% when wrecked). `semiSpecFromStats` builds a spec from the design ratings; `impactEnergy`, `bodyDamage`, `vehicleDamageFromImpact` and `speedLoss` are the run-over formulas of section 08. There is no team code for it: the document's numbers are the tests, and no golden is claimed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/** What the player (or an AI) asks of the vehicle in one step. */
|
|
2
|
+
interface DriveInput {
|
|
3
|
+
/** 0..1, accelerates (and brakes hard against a reverse move). */
|
|
4
|
+
throttle: number;
|
|
5
|
+
/** 0..1, brakes hard against a forward move, reverses from a stop. Throttle and brake together cancel. */
|
|
6
|
+
brake: number;
|
|
7
|
+
/** -1..1, positive steers to the left (the game's A key), as the yaw grows counter-clockwise seen from above. */
|
|
8
|
+
steer: number;
|
|
9
|
+
handbrake: boolean;
|
|
10
|
+
/** The horn this step. */
|
|
11
|
+
horn?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** The position and motion of the vehicle. Yaw 0 points to +x; the forward vector is `(cos yaw, -sin yaw)` on (x, z). */
|
|
14
|
+
interface DriverState {
|
|
15
|
+
x: number;
|
|
16
|
+
z: number;
|
|
17
|
+
yaw: number;
|
|
18
|
+
/** Signed speed along the forward vector, m/s. */
|
|
19
|
+
v: number;
|
|
20
|
+
/** Current steering angle, radians (smoothed toward the input). */
|
|
21
|
+
steer: number;
|
|
22
|
+
/** Seconds until the engine noise is next emitted. */
|
|
23
|
+
noiseTimer: number;
|
|
24
|
+
}
|
|
25
|
+
/** A body the vehicle can run over. */
|
|
26
|
+
interface DrivingBody {
|
|
27
|
+
x: number;
|
|
28
|
+
z: number;
|
|
29
|
+
}
|
|
30
|
+
/** What the vehicle drives through. All hooks are optional except `blocked`. */
|
|
31
|
+
interface DrivingWorld {
|
|
32
|
+
/** True when a vehicle at (x, z) with this yaw overlaps something solid. `vehicleCorners` lists the points the game tests. */
|
|
33
|
+
blocked(x: number, z: number, yaw: number): boolean;
|
|
34
|
+
/** The bodies that can be run over right now, indexed as the host indexes them; a dead or absent one is `undefined`. */
|
|
35
|
+
bodies?(): ReadonlyArray<DrivingBody | undefined>;
|
|
36
|
+
}
|
|
37
|
+
type DrivingEvent =
|
|
38
|
+
/** A hard hit (above `crash.minSpeed`): the host damages the vehicle by `damage`, shakes the camera, hurts the driver when `hurtDriver` is above 0. */
|
|
39
|
+
{
|
|
40
|
+
type: 'crash';
|
|
41
|
+
speed: number;
|
|
42
|
+
damage: number;
|
|
43
|
+
shake: number;
|
|
44
|
+
hurtDriver: number;
|
|
45
|
+
}
|
|
46
|
+
/** A noise the horde can hear. */
|
|
47
|
+
| {
|
|
48
|
+
type: 'noise';
|
|
49
|
+
radius: number;
|
|
50
|
+
x: number;
|
|
51
|
+
z: number;
|
|
52
|
+
source: 'crash' | 'engine' | 'horn';
|
|
53
|
+
}
|
|
54
|
+
/** A body hit above `runOver.killSpeed`: dies, limbs come off; the vehicle takes `vehicleDamage`. */
|
|
55
|
+
| {
|
|
56
|
+
type: 'run_over';
|
|
57
|
+
outcome: 'kill';
|
|
58
|
+
index: number;
|
|
59
|
+
speed: number;
|
|
60
|
+
vehicleDamage: number;
|
|
61
|
+
}
|
|
62
|
+
/** A body hit between `knockSpeed` and `killSpeed`: it loses `damage`, is stunned and thrown with (`vx`, `vz`); the vehicle slows by `slow`. */
|
|
63
|
+
| {
|
|
64
|
+
type: 'run_over';
|
|
65
|
+
outcome: 'knock';
|
|
66
|
+
index: number;
|
|
67
|
+
speed: number;
|
|
68
|
+
damage: number;
|
|
69
|
+
stunSeconds: number;
|
|
70
|
+
vx: number;
|
|
71
|
+
vz: number;
|
|
72
|
+
slow: number;
|
|
73
|
+
}
|
|
74
|
+
/** A body hit below `knockSpeed`: the horde holds the vehicle (it slows) and the body is pushed aside by (`dx`, `dz`). */
|
|
75
|
+
| {
|
|
76
|
+
type: 'run_over';
|
|
77
|
+
outcome: 'push';
|
|
78
|
+
index: number;
|
|
79
|
+
speed: number;
|
|
80
|
+
dx: number;
|
|
81
|
+
dz: number;
|
|
82
|
+
} | {
|
|
83
|
+
type: 'horn';
|
|
84
|
+
};
|
|
85
|
+
interface StepResult {
|
|
86
|
+
state: DriverState;
|
|
87
|
+
events: DrivingEvent[];
|
|
88
|
+
}
|
|
89
|
+
/** A driver turns inputs into motion. */
|
|
90
|
+
interface Driver {
|
|
91
|
+
readonly state: DriverState;
|
|
92
|
+
step(input: DriveInput, dt: number, world: DrivingWorld): StepResult;
|
|
93
|
+
/** Puts the vehicle somewhere, standing still. */
|
|
94
|
+
place(x: number, z: number, yaw: number): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface RunOverRules {
|
|
98
|
+
/** Above this speed (m/s) a body is knocked down. */
|
|
99
|
+
knockSpeed: number;
|
|
100
|
+
/** Above this speed a body is killed. */
|
|
101
|
+
killSpeed: number;
|
|
102
|
+
/** Knock damage is `speed² * knockDamage`. */
|
|
103
|
+
knockDamage: number;
|
|
104
|
+
/** Seconds a knocked body is stunned. */
|
|
105
|
+
stunSeconds: number;
|
|
106
|
+
/** Fraction of the vehicle speed given to a knocked body. */
|
|
107
|
+
throwFactor: number;
|
|
108
|
+
/** Sideways speed added to a knocked body. */
|
|
109
|
+
sideKick: number;
|
|
110
|
+
/** The vehicle keeps this fraction of its speed after knocking a body. */
|
|
111
|
+
knockSlow: number;
|
|
112
|
+
/** Damage the vehicle takes for each body killed. */
|
|
113
|
+
killVehicleDamage: number;
|
|
114
|
+
/** Below `knockSpeed` the vehicle slows by `exp(-holdDrag * dt)` for each body in the way. */
|
|
115
|
+
holdDrag: number;
|
|
116
|
+
/** Metres added around the vehicle's footprint when testing a body. */
|
|
117
|
+
margin: number;
|
|
118
|
+
}
|
|
119
|
+
interface CrashRules {
|
|
120
|
+
/** Below this speed an impact is only a bump (no event, but the vehicle still bounces). */
|
|
121
|
+
minSpeed: number;
|
|
122
|
+
/** Above this speed the impact damages the vehicle by `speed * damage`. */
|
|
123
|
+
damageSpeed: number;
|
|
124
|
+
damage: number;
|
|
125
|
+
/** Camera shake is `speed / shakeDivisor`. */
|
|
126
|
+
shakeDivisor: number;
|
|
127
|
+
/** Above this speed the driver is hurt by `speed * hurtFactor`. */
|
|
128
|
+
hurtSpeed: number;
|
|
129
|
+
hurtFactor: number;
|
|
130
|
+
/** The speed after an impact is `v * bounce`. */
|
|
131
|
+
bounce: number;
|
|
132
|
+
noiseRadius: number;
|
|
133
|
+
}
|
|
134
|
+
/** The numbers of the game's drivable car (G 70188-70290). Every one is an option. */
|
|
135
|
+
interface ArcadeSpec {
|
|
136
|
+
length: number;
|
|
137
|
+
width: number;
|
|
138
|
+
accel: number;
|
|
139
|
+
/** Deceleration when accelerating against a reverse move, or braking against a forward move. */
|
|
140
|
+
brakeAgainstMotion: number;
|
|
141
|
+
reverseAccel: number;
|
|
142
|
+
/** Coasting drag: `v *= exp(-drag * dt)`. */
|
|
143
|
+
drag: number;
|
|
144
|
+
handbrakeDrag: number;
|
|
145
|
+
maxForward: number;
|
|
146
|
+
maxReverse: number;
|
|
147
|
+
/** Largest steering angle, radians. */
|
|
148
|
+
steerMax: number;
|
|
149
|
+
/** The steering angle follows its target by `min(1, steerResponse * dt)`. */
|
|
150
|
+
steerResponse: number;
|
|
151
|
+
/** Distance used in `yaw += steer * v / wheelbase * dt`. */
|
|
152
|
+
wheelbase: number;
|
|
153
|
+
engineNoiseInterval: number;
|
|
154
|
+
engineNoiseBase: number;
|
|
155
|
+
engineNoiseSpeedFactor: number;
|
|
156
|
+
hornNoiseRadius: number;
|
|
157
|
+
crash: CrashRules;
|
|
158
|
+
runOver: RunOverRules;
|
|
159
|
+
}
|
|
160
|
+
declare const defaultArcadeSpec: ArcadeSpec;
|
|
161
|
+
type ArcadeOptions = Partial<Omit<ArcadeSpec, 'crash' | 'runOver'>> & {
|
|
162
|
+
crash?: Partial<CrashRules>;
|
|
163
|
+
runOver?: Partial<RunOverRules>;
|
|
164
|
+
};
|
|
165
|
+
/** 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. */
|
|
166
|
+
declare function vehicleCorners(spec: Pick<ArcadeSpec, 'length' | 'width'>, x: number, z: number, yaw: number): Array<[number, number]>;
|
|
167
|
+
/**
|
|
168
|
+
* The arcade driver: the game's car, extracted. A pure step function over `DriverState`: no three.js, no game. Port of `carUpdate`
|
|
169
|
+
* (jogo.js 70233-70290) with the same order of operations, so a scripted run gives the same positions and events.
|
|
170
|
+
*/
|
|
171
|
+
declare function createArcadeDriver(options?: ArcadeOptions): Driver & {
|
|
172
|
+
readonly spec: ArcadeSpec;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
type RunOverOutcome = 'push' | 'knock' | 'kill';
|
|
176
|
+
/** 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. */
|
|
177
|
+
declare function runOver(speed: number, rules?: Partial<RunOverRules>): RunOverOutcome;
|
|
178
|
+
|
|
179
|
+
/** A kind of ground the tyres run on. */
|
|
180
|
+
interface Surface {
|
|
181
|
+
id: string;
|
|
182
|
+
/** Multiplier of the tyre grip (dry asphalt = 1). */
|
|
183
|
+
grip: number;
|
|
184
|
+
/** Above this speed (km/h) the tyres float: grip drops linearly to `grip * aquaplaningFactor` over `aquaplaningBand` km/h. */
|
|
185
|
+
aquaplaningKmh?: number;
|
|
186
|
+
aquaplaningFactor?: number;
|
|
187
|
+
aquaplaningBand?: number;
|
|
188
|
+
/** A vehicle without four-wheel drive sinks: its rolling resistance becomes the spec's `bogResistance`. */
|
|
189
|
+
bogs?: boolean;
|
|
190
|
+
}
|
|
191
|
+
/** The table of the Frota document, section 03 (Surfaces). Every number is a default a game can replace. */
|
|
192
|
+
declare const defaultSurfaces: readonly Surface[];
|
|
193
|
+
interface SurfaceOptions {
|
|
194
|
+
packs?: ReadonlyArray<{
|
|
195
|
+
surfaces?: readonly Surface[];
|
|
196
|
+
}>;
|
|
197
|
+
patch?: Readonly<Record<string, Partial<Surface>>>;
|
|
198
|
+
remove?: readonly string[];
|
|
199
|
+
}
|
|
200
|
+
interface SurfaceRegistry {
|
|
201
|
+
get(id: string): Surface;
|
|
202
|
+
readonly surfaces: readonly Surface[];
|
|
203
|
+
}
|
|
204
|
+
/** The registry of surfaces: the document's table, plus packs, patches and removals by id. An unknown id falls back to dry asphalt. */
|
|
205
|
+
declare function createSurfaces(options?: SurfaceOptions): SurfaceRegistry;
|
|
206
|
+
/** Grip of a surface at a speed, with aquaplaning. */
|
|
207
|
+
declare function surfaceGrip(surface: Surface, speedMetresPerSecond: number): number;
|
|
208
|
+
|
|
209
|
+
interface FuelModel {
|
|
210
|
+
/** Litres per second at idle. */
|
|
211
|
+
idleRate: number;
|
|
212
|
+
/** Extra litres per second at full throttle and redline. */
|
|
213
|
+
fullRate: number;
|
|
214
|
+
/** Extra consumption per unit of cargo mass over the vehicle's own mass (0.5 = +50% when the load equals the vehicle). */
|
|
215
|
+
loadFactor: number;
|
|
216
|
+
/** Extra consumption of a wrecked engine (0.4 = up to +40% at zero health). */
|
|
217
|
+
damagedEngineExtra: number;
|
|
218
|
+
}
|
|
219
|
+
declare const defaultFuelModel: FuelModel;
|
|
220
|
+
/**
|
|
221
|
+
* Litres per second: it depends on the RPM, the throttle, the weight of the cargo and the state of the engine
|
|
222
|
+
* (Frota document, section 04: "a damaged engine spends up to 40% more").
|
|
223
|
+
*/
|
|
224
|
+
declare function fuelRate(model: FuelModel, rpmFraction: number, throttle: number, loadRatio: number, engineHealth: number): number;
|
|
225
|
+
|
|
226
|
+
interface SemiWheel {
|
|
227
|
+
/** Metres ahead of the centre of mass (negative behind). */
|
|
228
|
+
x: number;
|
|
229
|
+
/** Metres to the right of the centre line. */
|
|
230
|
+
z: number;
|
|
231
|
+
steer?: boolean;
|
|
232
|
+
drive?: boolean;
|
|
233
|
+
/** The handbrake locks it. */
|
|
234
|
+
handbrake?: boolean;
|
|
235
|
+
}
|
|
236
|
+
interface SemiSimSpec {
|
|
237
|
+
/** Kilograms. */
|
|
238
|
+
mass: number;
|
|
239
|
+
/** Yaw moment of inertia, kg·m². Default `mass * (wheelbase² + track²) / 12`. */
|
|
240
|
+
yawInertia?: number;
|
|
241
|
+
/** Height of the centre of mass above the ground, metres. Lower = less roll and pitch. */
|
|
242
|
+
centreHeight: number;
|
|
243
|
+
wheels: readonly SemiWheel[];
|
|
244
|
+
wheelRadius: number;
|
|
245
|
+
fourWheelDrive: boolean;
|
|
246
|
+
/** Air drag: `0.5 * 1.2 * Cd * A`, in kg/m. */
|
|
247
|
+
drag: number;
|
|
248
|
+
/** Rolling resistance coefficient on hard ground. */
|
|
249
|
+
rolling: number;
|
|
250
|
+
/** Rolling resistance of a vehicle that sinks on a `bogs` surface without four-wheel drive, while it is trying to move. */
|
|
251
|
+
bogResistance: number;
|
|
252
|
+
maxSteer: number;
|
|
253
|
+
/** Total brake force, newtons, split by the wheel loads. */
|
|
254
|
+
brakeForce: number;
|
|
255
|
+
engine: {
|
|
256
|
+
idleRpm: number;
|
|
257
|
+
peakRpm: number;
|
|
258
|
+
redlineRpm: number;
|
|
259
|
+
peakTorque: number;
|
|
260
|
+
};
|
|
261
|
+
/** Gear ratios, first to last. */
|
|
262
|
+
gears: readonly number[];
|
|
263
|
+
reverseRatio: number;
|
|
264
|
+
finalDrive: number;
|
|
265
|
+
/** Share of the engine torque that reaches the ground. */
|
|
266
|
+
efficiency: number;
|
|
267
|
+
/** Upshift above this fraction of the redline; downshift under `downshiftRpm` fraction. */
|
|
268
|
+
upshiftFraction: number;
|
|
269
|
+
downshiftFraction: number;
|
|
270
|
+
suspension: {
|
|
271
|
+
restLength: number;
|
|
272
|
+
spring: number;
|
|
273
|
+
damper: number;
|
|
274
|
+
};
|
|
275
|
+
tyre: {
|
|
276
|
+
/** Peak friction coefficient on dry asphalt. */
|
|
277
|
+
friction: number;
|
|
278
|
+
/** Lateral force per radian of slip, as a multiple of the wheel load, before saturating. */
|
|
279
|
+
corneringStiffness: number;
|
|
280
|
+
};
|
|
281
|
+
tank: {
|
|
282
|
+
capacity: number;
|
|
283
|
+
} & Partial<FuelModel>;
|
|
284
|
+
}
|
|
285
|
+
/** What is wrong with the vehicle, each from 0 (gone) to 1 (new). The host fills it from `kit.zoneOverall` or from a destructible. */
|
|
286
|
+
interface VehicleCondition {
|
|
287
|
+
/** Health of each wheel in the order of `spec.wheels`. Under 0.2 the tyre is flat. */
|
|
288
|
+
tyres?: readonly number[];
|
|
289
|
+
engine?: number;
|
|
290
|
+
suspension?: number;
|
|
291
|
+
}
|
|
292
|
+
interface SemiInput extends DriveInput {
|
|
293
|
+
/** Forces a gear (1-based, 0 = neutral, -1 = reverse). Absent = automatic. */
|
|
294
|
+
gear?: number;
|
|
295
|
+
}
|
|
296
|
+
interface SemiGround {
|
|
297
|
+
/** Height of the ground under the point, metres. Default 0. */
|
|
298
|
+
height?: number;
|
|
299
|
+
/** Id of the surface. Default `dry_asphalt`. */
|
|
300
|
+
surface?: string;
|
|
301
|
+
}
|
|
302
|
+
interface SemiWorld {
|
|
303
|
+
ground?(x: number, z: number): SemiGround;
|
|
304
|
+
}
|
|
305
|
+
interface SemiState {
|
|
306
|
+
x: number;
|
|
307
|
+
z: number;
|
|
308
|
+
yaw: number;
|
|
309
|
+
/** Body-frame speeds: forward and to the right, m/s. */
|
|
310
|
+
u: number;
|
|
311
|
+
w: number;
|
|
312
|
+
/** Yaw rate, rad/s, positive turning left. */
|
|
313
|
+
yawRate: number;
|
|
314
|
+
/** Ride height of the centre, pitch (nose up) and roll (right side down). */
|
|
315
|
+
height: number;
|
|
316
|
+
heightRate: number;
|
|
317
|
+
pitch: number;
|
|
318
|
+
pitchRate: number;
|
|
319
|
+
roll: number;
|
|
320
|
+
rollRate: number;
|
|
321
|
+
rpm: number;
|
|
322
|
+
/** Gear in use: 0 neutral, -1 reverse. */
|
|
323
|
+
gear: number;
|
|
324
|
+
engineOn: boolean;
|
|
325
|
+
/** Stuck: a 2WD vehicle on a bogging surface. */
|
|
326
|
+
bogged: boolean;
|
|
327
|
+
fuel: number;
|
|
328
|
+
/** Load on each wheel, newtons. */
|
|
329
|
+
loads: number[];
|
|
330
|
+
/** Slip of each wheel (1 = sliding). */
|
|
331
|
+
slip: number[];
|
|
332
|
+
steerAngle: number;
|
|
333
|
+
}
|
|
334
|
+
type SemiEvent = {
|
|
335
|
+
type: 'stall';
|
|
336
|
+
} | {
|
|
337
|
+
type: 'bogged';
|
|
338
|
+
} | {
|
|
339
|
+
type: 'aquaplaning';
|
|
340
|
+
wheel: number;
|
|
341
|
+
} | {
|
|
342
|
+
type: 'shift';
|
|
343
|
+
gear: number;
|
|
344
|
+
} | {
|
|
345
|
+
type: 'out_of_fuel';
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/** A mid-size passenger car, so a game can start from numbers that drive like a car. Every field can be replaced. */
|
|
349
|
+
declare const defaultSemiSpec: SemiSimSpec;
|
|
350
|
+
interface SemiSimOptions {
|
|
351
|
+
surfaces?: SurfaceRegistry;
|
|
352
|
+
}
|
|
353
|
+
interface SemiDriver extends Omit<Driver, 'step' | 'state'> {
|
|
354
|
+
readonly spec: SemiSimSpec;
|
|
355
|
+
readonly state: SemiState;
|
|
356
|
+
condition: VehicleCondition;
|
|
357
|
+
/** Sets the load carried, kilograms; the mass, the wheel loads and the fuel use follow. */
|
|
358
|
+
setCargo(kg: number): void;
|
|
359
|
+
/** A surface that overrides the ground under the wheels for `seconds` (blood after a run-over). */
|
|
360
|
+
contaminate(surfaceId: string, seconds: number): void;
|
|
361
|
+
/** Fills the tank (litres). */
|
|
362
|
+
refuel(litres: number): void;
|
|
363
|
+
step(input: SemiInput, dt: number, world?: SemiWorld): {
|
|
364
|
+
state: SemiState;
|
|
365
|
+
events: SemiEvent[];
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 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
|
|
370
|
+
* measured against the ground under it, separate longitudinal and lateral tyre forces inside a friction circle, an automatic gearbox with a
|
|
371
|
+
* simulated RPM, a handbrake that locks the rear wheels, surfaces with their grip, and damage that changes how it drives. No team code exists
|
|
372
|
+
* for it: the numbers are the document's and the tests check them.
|
|
373
|
+
*/
|
|
374
|
+
declare function createSemiSimDriver(specification?: Partial<SemiSimSpec>, options?: SemiSimOptions): SemiDriver;
|
|
375
|
+
|
|
376
|
+
/** Kinetic energy of a vehicle against a body: `0.5 * mass * speed²` (joules, with kg and m/s). Frota document, section 08. */
|
|
377
|
+
declare const impactEnergy: (vehicleMass: number, relativeSpeed: number) => number;
|
|
378
|
+
/** Damage to the body that was hit: `energy * bumperMultiplier * zoneFactor`. The zone factors (head, torso, legs) belong to the game. */
|
|
379
|
+
declare const bodyDamage: (energy: number, bumperMultiplier: number, zoneFactor: number) => number;
|
|
380
|
+
/** Damage to the vehicle: `energy * 0.04 * (1 - frontArmor)`, with the armour from 0 to 1. */
|
|
381
|
+
declare const vehicleDamageFromImpact: (energy: number, frontArmor: number, factor?: number) => number;
|
|
382
|
+
/** Speed the vehicle loses: `bodyMass / (vehicleMass + bodyMass) * speed`. */
|
|
383
|
+
declare const speedLoss: (bodyMass: number, vehicleMass: number, speed: number) => number;
|
|
384
|
+
|
|
385
|
+
/** The design ratings of a vehicle (the shape of `fleetStats` in `game-vehicles-fleet`; any object with these fields works). */
|
|
386
|
+
interface DesignStats {
|
|
387
|
+
/** 0-10. */
|
|
388
|
+
acceleration: number;
|
|
389
|
+
/** 0-10. */
|
|
390
|
+
handling: number;
|
|
391
|
+
maxSpeedKmh: number;
|
|
392
|
+
}
|
|
393
|
+
interface BodyDimensions {
|
|
394
|
+
mass: number;
|
|
395
|
+
wheelbase: number;
|
|
396
|
+
track: number;
|
|
397
|
+
fourWheelDrive?: boolean;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* A semi-simulation spec for a two-axle vehicle from its design ratings: the grip follows the handling, the torque follows the acceleration,
|
|
401
|
+
* and the air drag is set so that the top gear tops out at the design top speed.
|
|
402
|
+
*/
|
|
403
|
+
declare function semiSpecFromStats(stats: DesignStats, body: BodyDimensions, base?: Partial<SemiSimSpec>): SemiSimSpec;
|
|
404
|
+
|
|
405
|
+
export { type ArcadeOptions, type ArcadeSpec, type BodyDimensions, type CrashRules, type DesignStats, type DriveInput, type Driver, type DriverState, type DrivingBody, type DrivingEvent, type DrivingWorld, type FuelModel, type RunOverOutcome, type RunOverRules, type SemiDriver, type SemiEvent, type SemiGround, type SemiInput, type SemiSimOptions, type SemiSimSpec, type SemiState, type SemiWheel, type SemiWorld, type StepResult, type Surface, type SurfaceOptions, type SurfaceRegistry, type VehicleCondition, bodyDamage, createArcadeDriver, createSemiSimDriver, createSurfaces, defaultArcadeSpec, defaultFuelModel, defaultSemiSpec, defaultSurfaces, fuelRate, impactEnergy, runOver, semiSpecFromStats, speedLoss, surfaceGrip, vehicleCorners, vehicleDamageFromImpact };
|