@adatechnology/game-vehicles-three 0.6.4 → 0.8.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.
@@ -23,6 +23,8 @@ var GameVehiclesThree = (() => {
23
23
  __export(index_exports, {
24
24
  BIKE_ENGINE: () => BIKE_ENGINE,
25
25
  CAR_ENGINE: () => CAR_ENGINE,
26
+ CAR_RECORDED_RPM: () => CAR_RECORDED_RPM,
27
+ CAR_START_RUN_STOP: () => CAR_START_RUN_STOP,
26
28
  CRASH_SPEEDS: () => CRASH_SPEEDS,
27
29
  DASHBOARD_LOOK: () => DASHBOARD_LOOK,
28
30
  DEFAULT_TEST_VEHICLES: () => DEFAULT_TEST_VEHICLES,
@@ -31,6 +33,7 @@ var GameVehiclesThree = (() => {
31
33
  ENGINE_PROFILES: () => ENGINE_PROFILES,
32
34
  LAMP_RULES: () => LAMP_RULES,
33
35
  LITE_CLASSES: () => LITE_CLASSES,
36
+ MIST_RULES: () => MIST_RULES,
34
37
  PAGE_ITEM_ALIASES: () => PAGE_ITEM_ALIASES,
35
38
  PAGE_SCENARIO_ALIASES: () => PAGE_SCENARIO_ALIASES,
36
39
  PROVING_AREA_ID: () => PROVING_AREA_ID,
@@ -41,20 +44,26 @@ var GameVehiclesThree = (() => {
41
44
  VEHICLE_LOD_DISTANCE: () => VEHICLE_LOD_DISTANCE,
42
45
  VEHICLE_LOOPS: () => VEHICLE_LOOPS,
43
46
  VEHICLE_LOOP_FALLOFF: () => VEHICLE_LOOP_FALLOFF,
47
+ VEHICLE_PRELOAD_SOUNDS: () => VEHICLE_PRELOAD_SOUNDS,
48
+ VEHICLE_SAMPLE_ALIASES: () => VEHICLE_SAMPLE_ALIASES,
44
49
  VEHICLE_SCENARIO_IDS: () => VEHICLE_SCENARIO_IDS,
45
50
  VEHICLE_SOUNDS: () => VEHICLE_SOUNDS,
46
51
  VEHICLE_SOUND_BANK: () => VEHICLE_SOUND_BANK,
47
52
  classColor: () => classColor,
48
53
  classify: () => classify,
49
54
  createCabinSmoke: () => createCabinSmoke,
55
+ createEngineBudget: () => createEngineBudget,
50
56
  createGearGuess: () => createGearGuess,
51
57
  createProvingModule: () => createProvingModule,
58
+ createVehicleBody: () => createVehicleBody,
52
59
  createVehicleDashboard: () => createVehicleDashboard,
53
60
  createVehicleLamps: () => createVehicleLamps,
54
61
  createVehicleLod: () => createVehicleLod,
55
62
  createVehicleSound: () => createVehicleSound,
56
63
  createVehiclesTestModule: () => createVehiclesTestModule,
57
64
  createVehiclesThree: () => createVehiclesThree,
65
+ createWheelRoll: () => createWheelRoll,
66
+ createWindshieldMist: () => createWindshieldMist,
58
67
  defaultEffectIds: () => defaultEffectIds,
59
68
  defaultSoundIds: () => defaultSoundIds,
60
69
  engineMix: () => engineMix,
@@ -70,6 +79,10 @@ var GameVehiclesThree = (() => {
70
79
 
71
80
  // ../game-contracts/dist/index.js
72
81
  var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
82
+ var LAUNCHED = "launched";
83
+ function launchBody(root, velocity) {
84
+ root.userData[LAUNCHED] = [velocity[0], velocity[1], velocity[2]];
85
+ }
73
86
  var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
74
87
  var DEFAULT_IGNITION_RULES = {
75
88
  fillCurve: [
@@ -3861,9 +3874,9 @@ var GameVehiclesThree = (() => {
3861
3874
  let x = bx + (Hd[0] - bx) * v;
3862
3875
  const y = by + (Hd[1] - by) * v;
3863
3876
  let z = bz + (Hd[2] - bz) * v;
3864
- const chord = Math.hypot(C[0] - T[0], C[1] - T[1]) * (1 - v);
3877
+ const chord2 = Math.hypot(C[0] - T[0], C[1] - T[1]) * (1 - v);
3865
3878
  x -= roach * u * Math.sin(v * PI) * (u > 0.7 ? 1 : u / 0.7);
3866
- z += cam * chord * 4 * u * (1 - u) * (0.6 + 0.4 * (1 - v));
3879
+ z += cam * chord2 * 4 * u * (1 - u) * (0.6 + 0.4 * (1 - v));
3867
3880
  pos.push(x, y, z);
3868
3881
  }
3869
3882
  }
@@ -5092,12 +5105,24 @@ var GameVehiclesThree = (() => {
5092
5105
  }
5093
5106
  );
5094
5107
  }
5108
+ var WIPER_SWEEP = 1.5;
5109
+ function tagWiper(params) {
5110
+ var _a;
5111
+ const tag = { arm: params.index, sweep: WIPER_SWEEP, spindle: [params.spindle.x, params.spindle.y, params.spindle.z], axis: [params.normal.x, params.normal.y, params.normal.z] };
5112
+ (_a = params.context).wipers ?? (_a.wipers = []);
5113
+ for (const part of params.parts) params.context.wipers.push({ part, tag });
5114
+ }
5115
+ function markSunroof(pane, slide) {
5116
+ pane.name = "sunroof";
5117
+ pane.userData.sunroof = { slide };
5118
+ return pane;
5119
+ }
5095
5120
  function installCarsExtra(fx) {
5096
5121
  const kit = fx.kit;
5097
5122
  const THREE = kit.THREE;
5098
5123
  const trl = fx.t;
5099
5124
  const lbl = fx.label;
5100
- const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G2 = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp3 = kit.clamp, smooth2 = kit.smooth;
5125
+ const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G2 = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp6 = kit.clamp, smooth2 = kit.smooth;
5101
5126
  const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
5102
5127
  const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
5103
5128
  const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
@@ -5111,7 +5136,7 @@ var GameVehiclesThree = (() => {
5111
5136
  const a = (y - o.ys) / (o.belt - o.ys);
5112
5137
  return o.kt * a * a;
5113
5138
  }
5114
- const b = clamp3((o.ys - y) / (o.ys - o.yb), 0, 1.25);
5139
+ const b = clamp6((o.ys - y) / (o.ys - o.yb), 0, 1.25);
5115
5140
  return o.kb * b * b;
5116
5141
  }
5117
5142
  const f = function(x, y, z) {
@@ -5191,13 +5216,13 @@ var GameVehiclesThree = (() => {
5191
5216
  });
5192
5217
  }
5193
5218
  function roofAt(gh, x) {
5194
- const rR = gh.roofR || gh.roof, t = clamp3((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
5219
+ const rR = gh.roofR || gh.roof, t = clamp6((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
5195
5220
  return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
5196
5221
  }
5197
5222
  function windows(gh) {
5198
5223
  const inn = 0.07, rise = gh.rise || 0;
5199
5224
  function ybAt(x) {
5200
- return gh.belt + 0.035 + rise * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
5225
+ return gh.belt + 0.035 + rise * clamp6((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
5201
5226
  }
5202
5227
  function lx(x0, x1, y, top) {
5203
5228
  return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
@@ -5400,7 +5425,7 @@ var GameVehiclesThree = (() => {
5400
5425
  }
5401
5426
  const G22 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
5402
5427
  G22.at = function(u, y) {
5403
- const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
5428
+ const fi = clamp6(u, 0, 1) * nu, fj = clamp6((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
5404
5429
  function L(M) {
5405
5430
  return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
5406
5431
  }
@@ -5588,7 +5613,7 @@ var GameVehiclesThree = (() => {
5588
5613
  off: 3e-3,
5589
5614
  nv: 4
5590
5615
  });
5591
- skin(c, Gd, o.refl || REFL, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 });
5616
+ const reflector = skin(c, Gd, o.refl || REFL, { u0: pu, u1: 1 - pu, yb, yt, off: 6e-3, nv: 4 });
5592
5617
  (o.proj || []).forEach(function(q) {
5593
5618
  const r = sh(q[0]), y = (r[0] + r[1]) / 2 + (q[1] || 0), s = Gd.at(q[0], y), rad = q[2] || Math.min(0.034, (r[1] - r[0]) * 0.4);
5594
5619
  const cup = onSurf(c.cylinder(rad * 1.35, rad * 1.2, 0.012, LH, 0, 0, 0), s, 8e-3);
@@ -5619,7 +5644,7 @@ var GameVehiclesThree = (() => {
5619
5644
  lens.push(ln);
5620
5645
  if (!inner.length) inner.push(ln);
5621
5646
  const cs = Gd.at(0.5, (rg[0] + rg[1]) / 2).p;
5622
- regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner, lens, cs);
5647
+ Object.assign(regLight(c, "headlight", o.label || (sd > 0 ? trl("labels.right_headlight") : trl("labels.left_headlight")), inner, lens, cs), { reflector: [reflector] });
5623
5648
  return Gd;
5624
5649
  };
5625
5650
  K.tail = function(sd, o) {
@@ -5980,7 +6005,7 @@ var GameVehiclesThree = (() => {
5980
6005
  c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
5981
6006
  if (s.clad)
5982
6007
  s.arches.forEach(function(a) {
5983
- const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
6008
+ const a0 = Math.asin(clamp6((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
5984
6009
  tor.rotation.z = a0;
5985
6010
  tor.scale.z = 0.8;
5986
6011
  });
@@ -6005,12 +6030,18 @@ var GameVehiclesThree = (() => {
6005
6030
  (s.wiper === 1 ? [[0.12, 0.66, -0.5]] : [
6006
6031
  [-0.3, 0.42, -0.36],
6007
6032
  [0.2, 0.42, -0.36]
6008
- ]).forEach(function(q) {
6033
+ ]).forEach(function(q, index) {
6009
6034
  const p0 = new V3(gh.fb, gh.belt, q[0]).addScaledVector(wd, 0.07).addScaledVector(wn, 0.035), p1 = p0.clone().addScaledVector(wd, q[1] * 0.42);
6010
6035
  p1.z += q[2];
6011
- c.rod(p0, p1, 9e-3, MT.trim);
6012
- c.cylinder(0.018, 0.018, 0.03, MT.trim, p0.x, p0.y, p0.z);
6036
+ const blade = c.rod(p0, p1, 9e-3, MT.trim);
6037
+ const cap = c.cylinder(0.018, 0.018, 0.03, MT.trim, p0.x, p0.y, p0.z);
6038
+ tagWiper({ context: c, parts: [blade, cap], spindle: p0, normal: wn, index });
6013
6039
  });
6040
+ if (s.sunroof) {
6041
+ const sx = gh.rt + (gh.ft - gh.rt) * 0.62, sl = 0.62, sw = 2 * (halfAt(gh.Wg, T, gh.roof) - 0.24), sy = roofAt(gh, sx), tilt = Math.atan2(roofAt(gh, sx + 0.3) - roofAt(gh, sx - 0.3), 0.6);
6042
+ c.roundBox(sl, 8e-3, sw, 0.01, PLASTIC, sx, sy + 4e-3, 0, { rot: [0, 0, tilt] }).castShadow = false;
6043
+ markSunroof(c.roundBox(sl + 0.03, 0.012, sw + 0.03, 0.01, MT.tint, sx, sy + 0.013, 0, { rot: [0, 0, tilt] }), sl * 0.85);
6044
+ }
6014
6045
  if (s.fin !== false)
6015
6046
  c.sphere(0.1, s.roofCol ? c.paint(s.roofCol) : GLOSS, gh.rt + 0.2, roofAt(gh, gh.rt + 0.2) + 0.045, 0, 1, 0.45, 0.28, {
6016
6047
  tl: PI / 2
@@ -6111,7 +6142,7 @@ var GameVehiclesThree = (() => {
6111
6142
  c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
6112
6143
  }
6113
6144
  function sm(t) {
6114
- t = clamp3(t, 0, 1);
6145
+ t = clamp6(t, 0, 1);
6115
6146
  return t * t * (3 - 2 * t);
6116
6147
  }
6117
6148
  function sideGrid(S, sd, o) {
@@ -6128,7 +6159,7 @@ var GameVehiclesThree = (() => {
6128
6159
  }
6129
6160
  const G22 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
6130
6161
  G22.at = function(u, y) {
6131
- const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
6162
+ const fi = clamp6(u, 0, 1) * nu, fj = clamp6((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
6132
6163
  function L(M) {
6133
6164
  return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
6134
6165
  }
@@ -6986,6 +7017,7 @@ var GameVehiclesThree = (() => {
6986
7017
  function(c) {
6987
7018
  const hex = "#8c2b2b";
6988
7019
  car(c, {
7020
+ sunroof: fx.hasSunroof("turbo_hatch"),
6989
7021
  W: 1.751,
6990
7022
  col: hex,
6991
7023
  shelf: 0.42,
@@ -7263,6 +7295,7 @@ var GameVehiclesThree = (() => {
7263
7295
  function(c) {
7264
7296
  const hex = "#5a5f66";
7265
7297
  car(c, {
7298
+ sunroof: fx.hasSunroof("fastback_sedan"),
7266
7299
  W: 1.799,
7267
7300
  col: hex,
7268
7301
  bottomY: 0.2,
@@ -7557,6 +7590,8 @@ var GameVehiclesThree = (() => {
7557
7590
  fx.register(
7558
7591
  {
7559
7592
  key: "classic_six_sedan",
7593
+ // glass headlight lenses, as on cars of its time: they shatter (a modern car's are polycarbonate)
7594
+ headlightLens: "glass",
7560
7595
  group: "cars",
7561
7596
  name: trl("vehicles.classic_six_sedan.name"),
7562
7597
  inspired: trl("vehicles.classic_six_sedan.inspired"),
@@ -7858,7 +7893,7 @@ var GameVehiclesThree = (() => {
7858
7893
  const g = K.grid(1, 0, { z1: 0.66, y0: 0.27, y1: 0.61, du: 0.03, dv: 0.03 }), zt = 0.6, zb = 0.43, yB0 = 0.3, yT = 0.585;
7859
7894
  const gyb = function(u) {
7860
7895
  const z = Math.abs(g.zu(u));
7861
- return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) * clamp3((z - zb) / (zt - zb), 0, 1);
7896
+ return z <= zb ? yB0 : yB0 + (yT - 0.02 - yB0) * clamp6((z - zb) / (zt - zb), 0, 1);
7862
7897
  };
7863
7898
  K.skin(g, grilleMat("mesh"), { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT, off: 0.022, tile: 0.05, nv: 6 });
7864
7899
  outline(c2, g, { u0: g.uz(-zt), u1: g.uz(zt), yb: gyb, yt: yT }, 0.012, LH, 0.024);
@@ -8667,7 +8702,7 @@ var GameVehiclesThree = (() => {
8667
8702
  lightsY: 0.82
8668
8703
  });
8669
8704
  const tx = -1.25, len2 = 6.2, ty = 2.08, sy = 0.82, sz = 1.16;
8670
- function hz(y) {
8705
+ function hz2(y) {
8671
8706
  const d = (y - ty) / sy;
8672
8707
  return sz * Math.sqrt(Math.max(0, 1 - d * d));
8673
8708
  }
@@ -8721,12 +8756,12 @@ var GameVehiclesThree = (() => {
8721
8756
  [1, -1].forEach(function(s) {
8722
8757
  c.cylinder(0.12, 0.12, 2.3, ALU, 0.25, 1.36, s * 1, "x");
8723
8758
  c.cylinder(0.1, 0.1, 0.02, MT.dark, -0.91, 1.36, s * 1, "x");
8724
- c.roundBox(0.42, 0.32, 0.012, 0.01, CM("#d9771c"), tx + 1.6, ty - 0.25, s * (hz(ty - 0.1) + 6e-3));
8725
- c.text("33", 0.36, 0.13, tx + 1.6, ty - 0.18, s * (hz(ty - 0.1) + 0.014), s > 0 ? "+z" : "-z", { fg: "#111", size: 0.85 });
8726
- c.text("1203", 0.36, 0.13, tx + 1.6, ty - 0.32, s * (hz(ty - 0.1) + 0.014), s > 0 ? "+z" : "-z", { fg: "#111", size: 0.85 });
8727
- const dia = c.roundBox(0.3, 0.3, 0.012, 0.01, CM("#c3271c"), tx + 2.15, ty - 0.25, s * (hz(ty - 0.05) + 6e-3));
8759
+ c.roundBox(0.42, 0.32, 0.012, 0.01, CM("#d9771c"), tx + 1.6, ty - 0.25, s * (hz2(ty - 0.1) + 6e-3));
8760
+ c.text("33", 0.36, 0.13, tx + 1.6, ty - 0.18, s * (hz2(ty - 0.1) + 0.014), s > 0 ? "+z" : "-z", { fg: "#111", size: 0.85 });
8761
+ c.text("1203", 0.36, 0.13, tx + 1.6, ty - 0.32, s * (hz2(ty - 0.1) + 0.014), s > 0 ? "+z" : "-z", { fg: "#111", size: 0.85 });
8762
+ const dia = c.roundBox(0.3, 0.3, 0.012, 0.01, CM("#c3271c"), tx + 2.15, ty - 0.25, s * (hz2(ty - 0.05) + 6e-3));
8728
8763
  dia.rotation.z = PI / 4;
8729
- c.text("3", 0.18, 0.18, tx + 2.15, ty - 0.27, s * (hz(ty - 0.05) + 0.014), s > 0 ? "+z" : "-z", { fg: "#fff", size: 0.9 });
8764
+ c.text("3", 0.18, 0.18, tx + 2.15, ty - 0.27, s * (hz2(ty - 0.05) + 0.014), s > 0 ? "+z" : "-z", { fg: "#fff", size: 0.9 });
8730
8765
  c.text(trl("texts.flammable"), 1.12, 0.3, tx - 0.625, ty + 0.12, s * (sz + 0.012), s > 0 ? "+z" : "-z", { fg: "#b3261e" });
8731
8766
  for (let k = 0; k < 11; k++)
8732
8767
  c.roundBox(
@@ -8737,7 +8772,7 @@ var GameVehiclesThree = (() => {
8737
8772
  k % 2 ? REFL_W : REFL_R,
8738
8773
  tx - 2.8 + k * 0.55,
8739
8774
  ty - sy * 0.72,
8740
- s * (hz(ty - sy * 0.62) + 6e-3)
8775
+ s * (hz2(ty - sy * 0.62) + 6e-3)
8741
8776
  );
8742
8777
  c.roundBox(0.42, 0.42, 0.25, 0.04, CM("#b3261e"), 2.2, 1, s * (W / 2 - 0.15));
8743
8778
  });
@@ -9494,8 +9529,10 @@ var GameVehiclesThree = (() => {
9494
9529
  c.cylinder(0.36, 0.38, 0.1, plate, -1.3, 3.13, 0);
9495
9530
  c.cylinder(0.3, 0.3, 0.05, MT.steel, -1.3, 3.2, 0);
9496
9531
  c.roundBox(0.2, 0.08, 1.3, 0.03, MT.trim, bx0 - 0.25, 3.13, 0);
9497
- [-0.45, 0, 0.45].forEach(function(z) {
9498
- c.roundBox(0.14, 0.08, 0.3, 0.03, MT.amber, bx0 - 0.25, 3.2, z);
9532
+ [-0.45, 0, 0.45].forEach(function(z, i) {
9533
+ const lamp = c.roundBox(0.14, 0.08, 0.3, 0.03, MT.amber, bx0 - 0.25, 3.2, z);
9534
+ lamp.userData.lamp = "beacon";
9535
+ lamp.userData.beaconSide = i % 2;
9499
9536
  });
9500
9537
  c.rod(new V3(-2.5, 3.08, 0.6), new V3(-2.5, 3.6, 0.6), 8e-3, MT.trim);
9501
9538
  c.rod(new V3(-2.8, 3.08, -0.6), new V3(-2.8, 3.4, -0.6), 0.01, MT.trim);
@@ -11753,7 +11790,7 @@ var GameVehiclesThree = (() => {
11753
11790
  m.castShadow = true;
11754
11791
  m.receiveShadow = false;
11755
11792
  for (let k = 0; k < 14; k++) {
11756
- const hx = x0 + 0.2 + (x1 - x0 - 0.4) * kit.hash(k, 1, 7), hz = (kit.hash(k, 2, 7) - 0.5) * w * 0.85, n = 3 + Math.floor(kit.hash(k, 4, 7) * 3);
11793
+ const hx = x0 + 0.2 + (x1 - x0 - 0.4) * kit.hash(k, 1, 7), hz2 = (kit.hash(k, 2, 7) - 0.5) * w * 0.85, n = 3 + Math.floor(kit.hash(k, 4, 7) * 3);
11757
11794
  for (let q = 0; q < n; q++) {
11758
11795
  const r = 0.07 + kit.hash(k, q, 11) * 0.07, geo = new THREE.IcosahedronGeometry(r, 1), p = geo.attributes.position;
11759
11796
  for (let vi = 0; vi < p.count; vi++) {
@@ -11761,7 +11798,7 @@ var GameVehiclesThree = (() => {
11761
11798
  p.setXYZ(vi, p.getX(vi) * f, p.getY(vi) * f * 0.8, p.getZ(vi) * f);
11762
11799
  }
11763
11800
  geo.computeVertexNormals();
11764
- const ang = q / n * PI * 2 + k, tf = c.add(geo, (k + q) % 2 ? NET : NET2, hx + Math.cos(ang) * 0.1, prof(hz) + 0.1 + r * 0.5, hz + Math.sin(ang) * 0.1);
11801
+ const ang = q / n * PI * 2 + k, tf = c.add(geo, (k + q) % 2 ? NET : NET2, hx + Math.cos(ang) * 0.1, prof(hz2) + 0.1 + r * 0.5, hz2 + Math.sin(ang) * 0.1);
11765
11802
  tf.rotation.set(kit.hash(k, q, 1) * 3, kit.hash(k, q, 2) * 3, 0);
11766
11803
  }
11767
11804
  }
@@ -13644,7 +13681,7 @@ var GameVehiclesThree = (() => {
13644
13681
  }
13645
13682
  };
13646
13683
  }
13647
- function militaryRotor(c, x, y, z, n, len, chord, mat, o) {
13684
+ function militaryRotor(c, x, y, z, n, len, chord2, mat, o) {
13648
13685
  o = o || {};
13649
13686
  const g = new THREE.Group();
13650
13687
  g.position.set(x, y, z);
@@ -13652,7 +13689,7 @@ var GameVehiclesThree = (() => {
13652
13689
  for (let i = 0; i < n; i++) {
13653
13690
  const piv = new THREE.Group();
13654
13691
  piv.rotation[o.axis === "z" ? "z" : "y"] = i / n * PI * 2 + (o.phase || 0);
13655
- const geo = G2.roundedBox(len, o.th || 0.06, chord, 0.02);
13692
+ const geo = G2.roundedBox(len, o.th || 0.06, chord2, 0.02);
13656
13693
  geo.translate(len / 2 + (o.hub || 0.3), 0, 0);
13657
13694
  if (o.droop) {
13658
13695
  const p = geo.attributes.position;
@@ -13667,7 +13704,7 @@ var GameVehiclesThree = (() => {
13667
13704
  if (o.axis === "z") b.rotation.x = PI / 2;
13668
13705
  piv.add(b);
13669
13706
  if (o.tip) {
13670
- const tp = new THREE.Mesh(G2.roundedBox(0.25, (o.th || 0.06) + 5e-3, chord + 5e-3, 0.01), o.tip);
13707
+ const tp = new THREE.Mesh(G2.roundedBox(0.25, (o.th || 0.06) + 5e-3, chord2 + 5e-3, 0.01), o.tip);
13671
13708
  tp.position.set(len + (o.hub || 0.3) - 0.12, -(o.droop || 0), 0);
13672
13709
  if (o.axis === "z") tp.rotation.x = PI / 2;
13673
13710
  piv.add(tp);
@@ -14017,7 +14054,7 @@ var GameVehiclesThree = (() => {
14017
14054
  const THREE = kit.THREE;
14018
14055
  const trl = fx.t;
14019
14056
  const lbl = fx.label;
14020
- const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G2 = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp3 = kit.clamp, smooth2 = kit.smooth;
14057
+ const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, G2 = kit.geo, profX = kit.profX, halfAt = kit.halfAt, clamp6 = kit.clamp, smooth2 = kit.smooth;
14021
14058
  const GLOSS = CM("#07080a", { roughness: 0.18, metalness: 0.5 });
14022
14059
  const ALLOY = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 });
14023
14060
  const ALLOYD = CM("#3a3e42", { metalness: 0.6, roughness: 0.35 });
@@ -14036,7 +14073,7 @@ var GameVehiclesThree = (() => {
14036
14073
  const a = (y - o.ys) / (o.belt - o.ys);
14037
14074
  return o.kt * a * a;
14038
14075
  }
14039
- const b = clamp3((o.ys - y) / (o.ys - o.yb), 0, 1.25);
14076
+ const b = clamp6((o.ys - y) / (o.ys - o.yb), 0, 1.25);
14040
14077
  return o.kb * b * b;
14041
14078
  }
14042
14079
  const f = function(x, y, z) {
@@ -14216,13 +14253,13 @@ var GameVehiclesThree = (() => {
14216
14253
  return L;
14217
14254
  }
14218
14255
  function roofAt(gh, x) {
14219
- const rR = gh.roofR || gh.roof, t = clamp3((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
14256
+ const rR = gh.roofR || gh.roof, t = clamp6((x - gh.rt) / (gh.ft - gh.rt), 0, 1);
14220
14257
  return rR + (gh.roof - rR) * t + (gh.crown || 0) * 4 * t * (1 - t);
14221
14258
  }
14222
14259
  function windows(gh) {
14223
14260
  const inn = 0.07, rise = gh.rise || 0;
14224
14261
  function ybAt(x) {
14225
- return gh.belt + 0.035 + rise * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
14262
+ return gh.belt + 0.035 + rise * clamp6((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
14226
14263
  }
14227
14264
  function lx(x0, x1, y, top) {
14228
14265
  return x0 + (x1 - x0) * (y - gh.belt) / (top - gh.belt);
@@ -14433,7 +14470,7 @@ var GameVehiclesThree = (() => {
14433
14470
  }
14434
14471
  const G22 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: tot * (full ? 2 : 1), E, sd };
14435
14472
  G22.at = function(u, y) {
14436
- const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
14473
+ const fi = clamp6(u, 0, 1) * nu, fj = clamp6((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
14437
14474
  function L(M) {
14438
14475
  return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
14439
14476
  }
@@ -14994,7 +15031,7 @@ var GameVehiclesThree = (() => {
14994
15031
  c.roundBox(x1 - x0, 0.08, 0.035, 0.015, MT.trim, (x0 + x1) / 2, by + 0.05, sd * (hw - 8e-3));
14995
15032
  if (s.clad)
14996
15033
  s.arches.forEach(function(a) {
14997
- const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
15034
+ const a0 = Math.asin(clamp6((by - a[1]) / a[2], -1, 1)), tor = c.add(new THREE.TorusGeometry(a[2] + 0.02, 0.035, 6, 26, PI - 2 * a0), MT.trim, a[0], a[1], sd * (hw - 0.01));
14998
15035
  tor.rotation.z = a0;
14999
15036
  tor.scale.z = 0.8;
15000
15037
  });
@@ -15012,8 +15049,11 @@ var GameVehiclesThree = (() => {
15012
15049
  );
15013
15050
  });
15014
15051
  const wy = gh.belt + 0.06, wx = gh.fb - 0.03;
15015
- [-0.3, 0.2].forEach(function(z) {
15016
- c.rod(new V3(wx + 0.02, wy, z + 0.02), new V3(wx - 0.13, wy + 0.13, z - 0.36), 9e-3, MT.trim);
15052
+ [-0.3, 0.2].forEach(function(z, index) {
15053
+ const spindle = new V3(wx + 0.02, wy, z + 0.02);
15054
+ const blade = c.rod(spindle, new V3(wx - 0.13, wy + 0.13, z - 0.36), 9e-3, MT.trim);
15055
+ const along = new V3(gh.ft - gh.fb, gh.roof - gh.belt, 0).normalize();
15056
+ tagWiper({ context: c, parts: [blade], spindle, normal: new V3(along.y, -along.x, 0), index });
15017
15057
  });
15018
15058
  if (s.fin !== false)
15019
15059
  c.sphere(0.1, s.roofCol ? c.paint(s.roofCol) : GLOSS, gh.rt + 0.2, roofAt(gh, gh.rt + 0.2) + 0.045, 0, 1, 0.45, 0.28, {
@@ -15115,7 +15155,7 @@ var GameVehiclesThree = (() => {
15115
15155
  c.cylinder(0.06, 0.06, 0.012, MT.head, x + 0.055, y, z, "x");
15116
15156
  }
15117
15157
  function sm(t) {
15118
- t = clamp3(t, 0, 1);
15158
+ t = clamp6(t, 0, 1);
15119
15159
  return t * t * (3 - 2 * t);
15120
15160
  }
15121
15161
  function sideGrid(S, sd, o) {
@@ -15132,7 +15172,7 @@ var GameVehiclesThree = (() => {
15132
15172
  }
15133
15173
  const G22 = { P, N, nu, nv, y0: o.y0, y1: o.y1, len: Math.abs(o.x1 - o.x0) };
15134
15174
  G22.at = function(u, y) {
15135
- const fi = clamp3(u, 0, 1) * nu, fj = clamp3((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
15175
+ const fi = clamp6(u, 0, 1) * nu, fj = clamp6((y - o.y0) / (o.y1 - o.y0), 0, 1) * nv, i0 = Math.min(nu - 1, Math.floor(fi)), j0 = Math.min(nv - 1, Math.floor(fj)), a = fi - i0, b = fj - j0;
15136
15176
  function L(M) {
15137
15177
  return M[i0][j0].clone().multiplyScalar((1 - a) * (1 - b)).addScaledVector(M[i0 + 1][j0], a * (1 - b)).addScaledVector(M[i0][j0 + 1], (1 - a) * b).addScaledVector(M[i0 + 1][j0 + 1], a * b);
15138
15178
  }
@@ -15293,7 +15333,7 @@ var GameVehiclesThree = (() => {
15293
15333
  return function(c, I) {
15294
15334
  const gh = I.gh, col = c.meta.color;
15295
15335
  function yb(x) {
15296
- return gh.belt + 0.035 + (gh.rise || 0) * clamp3((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
15336
+ return gh.belt + 0.035 + (gh.rise || 0) * clamp6((gh.fb - x) / (gh.fb - gh.rb), 0, 1);
15297
15337
  }
15298
15338
  ;
15299
15339
  [1, -1].forEach(function(sd) {
@@ -16534,6 +16574,10 @@ var GameVehiclesThree = (() => {
16534
16574
  }
16535
16575
  }
16536
16576
  });
16577
+ beacon[1].userData.lamp = "beacon";
16578
+ beacon[1].userData.beaconSide = 0;
16579
+ beacon[2].userData.lamp = "beacon";
16580
+ beacon[2].userData.beaconSide = 1;
16537
16581
  c2.meta.top = top + 0.15;
16538
16582
  c2.meta.roof.y = Math.max(c2.meta.roof.y, top + 0.15);
16539
16583
  const fx2 = I.fxB + 0.12, push = [];
@@ -17836,7 +17880,9 @@ var GameVehiclesThree = (() => {
17836
17880
  c.roundBox(0.5, 0.018, 6e-3, 2e-3, c.paint("#f2c21a"), -0.7, 0.825, z);
17837
17881
  c.text("PRF", 0.2, 0.08, -0.7, 0.73, z + s * 2e-3, f, { fg: "#1f3f8f", size: 0.85 });
17838
17882
  c.text(trl("texts.federal_highway_police"), 0.3, 0.035, 0.24, 1, s * 0.3, f, { fg: "#f4f2ea", w: 1024, h: 120, size: 0.8 });
17839
- c.roundBox(0.05, 0.05, 0.04, 0.012, s > 0 ? MT.blue : MT.tail, 0.62, 0.95, s * 0.3, { def: true });
17883
+ const flasher = c.roundBox(0.05, 0.05, 0.04, 0.012, s > 0 ? MT.blue : MT.tail, 0.62, 0.95, s * 0.3, { def: true });
17884
+ flasher.userData.lamp = "beacon";
17885
+ flasher.userData.beaconSide = s > 0 ? 1 : 0;
17840
17886
  });
17841
17887
  c.cylinder(0.04, 0.045, 0.06, MT.trim, 0.66, 0.66, 0.36, "x");
17842
17888
  }
@@ -18089,6 +18135,8 @@ var GameVehiclesThree = (() => {
18089
18135
  fx.register(
18090
18136
  {
18091
18137
  key: "beetle",
18138
+ // glass headlight lenses, as on cars of its time: they shatter (a modern car's are polycarbonate)
18139
+ headlightLens: "glass",
18092
18140
  group: "cars",
18093
18141
  name: trl("vehicles.beetle.name"),
18094
18142
  inspired: trl("vehicles.beetle.inspired"),
@@ -18164,7 +18212,7 @@ var GameVehiclesThree = (() => {
18164
18212
  return n.y > 0.3 && ce.x > 0.9 && ce.y > 0.6;
18165
18213
  },
18166
18214
  sdf: function(x, y, z) {
18167
- const t = clamp3((x - 1.17) / 0.5, 0, 1);
18215
+ const t = clamp6((x - 1.17) / 0.5, 0, 1);
18168
18216
  return Math.max(0.94 - x, x - 1.69, Math.abs(z) - (0.44 - 0.16 * t * t));
18169
18217
  }
18170
18218
  });
@@ -18868,8 +18916,8 @@ var GameVehiclesThree = (() => {
18868
18916
  ];
18869
18917
  body(c, RB, col);
18870
18918
  [1, -1].forEach(function(s) {
18871
- const x = -0.3, y = 0.6, hz = loftHalf(RB, x, y) + 4e-3;
18872
- c.text(trl("texts.bis_125"), 0.2, 0.05, x, y, s * hz, s > 0 ? "+z" : "-z", { fg: "#f2f0ea", size: 0.85 });
18919
+ const x = -0.3, y = 0.6, hz2 = loftHalf(RB, x, y) + 4e-3;
18920
+ c.text(trl("texts.bis_125"), 0.2, 0.05, x, y, s * hz2, s > 0 ? "+z" : "-z", { fg: "#f2f0ea", size: 0.85 });
18873
18921
  body(
18874
18922
  c,
18875
18923
  [
@@ -22162,7 +22210,7 @@ var GameVehiclesThree = (() => {
22162
22210
  const THREE = kit.THREE;
22163
22211
  const trl = fx.t;
22164
22212
  const lbl = fx.label;
22165
- const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, clamp3 = kit.clamp, smooth2 = kit.smooth;
22213
+ const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, clamp6 = kit.clamp, smooth2 = kit.smooth;
22166
22214
  const CLAD = CM("#1a1b1d", { roughness: 0.82 }), LINER = CM("#1c1d1f", { roughness: 0.92 }), RIMDARK = CM("#2a2d30", { metalness: 0.6, roughness: 0.4 });
22167
22215
  const RIMSIL = CM("#b9bec2", { metalness: 0.75, roughness: 0.3 }), RIMGUN = CM("#55595d", { metalness: 0.7, roughness: 0.35 });
22168
22216
  function axles(WB, oF, L, dF, dR) {
@@ -22193,16 +22241,16 @@ var GameVehiclesThree = (() => {
22193
22241
  const m = c.meta, W = m.width, by = o.bottom != null ? o.bottom : m.clearance, top = o.top != null ? o.top : m.belt;
22194
22242
  const Wg = m.glasshouse && m.glasshouse.Wg || W, front = m.front, rear = m.rear;
22195
22243
  const tuck = o.tuck == null ? 0.045 : o.tuck, peak = o.peak || 0.45;
22196
- const B = o.sh != null ? o.sh : clamp3(1 - (Wg + 0.06) / W, 0, 0.06);
22244
+ const B = o.sh != null ? o.sh : clamp6(1 - (Wg + 0.06) / W, 0, 0.06);
22197
22245
  const Rf = o.Rf || 0.55, Rr = o.Rr || 0.42, pf = o.pf == null ? 0.08 : o.pf, pr = o.pr == null ? 0.05 : o.pr;
22198
22246
  const bowF = o.bowF == null ? 0.07 : o.bowF, bowR = o.bowR == null ? 0.04 : o.bowR, Rb = o.Rb || 0.35, hw = W / 2;
22199
22247
  const crown = o.crown == null ? 0.045 : o.crown, xc = o.crownX != null ? o.crownX : m.glasshouse ? m.glasshouse.fb : front - 1, y0 = by + 0.55 * (top - by);
22200
22248
  function sc(y) {
22201
- const t = clamp3((y - by) / (top - by), 0, 1);
22249
+ const t = clamp6((y - by) / (top - by), 0, 1);
22202
22250
  return t < peak ? 1 - tuck * Math.pow(1 - t / peak, 2) : 1 - B * Math.pow((t - peak) / (1 - peak), 2);
22203
22251
  }
22204
22252
  function F(v2) {
22205
- const a = smooth2((v2.x - (front - Rf)) / Rf), b = smooth2((rear + Rr - v2.x) / Rr), zz = clamp3(v2.z / hw, -1, 1) * clamp3(v2.z / hw, -1, 1);
22253
+ const a = smooth2((v2.x - (front - Rf)) / Rf), b = smooth2((rear + Rr - v2.x) / Rr), zz = clamp6(v2.z / hw, -1, 1) * clamp6(v2.z / hw, -1, 1);
22206
22254
  const bf = smooth2((v2.x - (front - Rb)) / Rb), br = smooth2((rear + Rb - v2.x) / Rb);
22207
22255
  const cr = crown * (1 - zz) * smooth2((v2.y - y0) / (top - y0)) * smooth2((v2.x - xc) / 0.3);
22208
22256
  v2.x += -bowF * zz * bf + bowR * zz * br;
@@ -22279,7 +22327,7 @@ var GameVehiclesThree = (() => {
22279
22327
  o = o || {};
22280
22328
  const wd = o.w || 0.075, dp = o.d || 0.045, mat = o.mat || CLAD, out = [];
22281
22329
  arches.forEach(function(a) {
22282
- const a0 = Math.asin(clamp3((by - a[1]) / a[2], -1, 1)) - 0.02, r0 = a[2] - (o.inner == null ? 0.055 : o.inner), r1 = a[2] + wd;
22330
+ const a0 = Math.asin(clamp6((by - a[1]) / a[2], -1, 1)) - 0.02, r0 = a[2] - (o.inner == null ? 0.055 : o.inner), r1 = a[2] + wd;
22283
22331
  const sh = new THREE.Shape();
22284
22332
  if (o.trap) {
22285
22333
  const yb0 = Math.sin(a0) * r1, xb0 = Math.cos(a0) * r1, top = r1 * 0.93, xt = r1 * 0.52;
@@ -22629,7 +22677,7 @@ var GameVehiclesThree = (() => {
22629
22677
  function roof(c, drop, x0, x1) {
22630
22678
  const m = c.meta, b = m.glasshouse.belt, h = m.top - b;
22631
22679
  function dy(x, y) {
22632
- const t = clamp3((y - b) / h, 0, 1);
22680
+ const t = clamp6((y - b) / h, 0, 1);
22633
22681
  return t <= 0 ? 0 : drop * smooth2((x0 - x) / (x0 - x1)) * t;
22634
22682
  }
22635
22683
  c.root.updateMatrixWorld(true);
@@ -23116,17 +23164,17 @@ var GameVehiclesThree = (() => {
23116
23164
  }
23117
23165
  beltline(c2, -0.35, 0.035, col);
23118
23166
  pillarC(c2, -1.12, [-1.36, 1.25]);
23119
- const E = glasshouse(c2), hz = function(y) {
23167
+ const E = glasshouse(c2), hz2 = function(y) {
23120
23168
  return kit.halfAt(m.glasshouse.Wg, m.tumble, y) + 0.03;
23121
23169
  };
23122
23170
  [1, -1].forEach(function(sd) {
23123
23171
  c2.tube(
23124
23172
  [
23125
- [m.glasshouse.fb - 0.08, E.yb - 0.03, sd * hz(E.yb)],
23126
- [-0.35, E.yb - 0.03, sd * hz(E.yb)],
23127
- [-1.45, E.yb + 0.01, sd * hz(E.yb)],
23128
- [E.back(E.yb + 0.15) + 0.05, E.yb + 0.15, sd * hz(E.yb + 0.15)],
23129
- [E.back(E.top - 0.05) + 0.06, E.top - 0.05, sd * hz(E.top - 0.05)]
23173
+ [m.glasshouse.fb - 0.08, E.yb - 0.03, sd * hz2(E.yb)],
23174
+ [-0.35, E.yb - 0.03, sd * hz2(E.yb)],
23175
+ [-1.45, E.yb + 0.01, sd * hz2(E.yb)],
23176
+ [E.back(E.yb + 0.15) + 0.05, E.yb + 0.15, sd * hz2(E.yb + 0.15)],
23177
+ [E.back(E.top - 0.05) + 0.06, E.top - 0.05, sd * hz2(E.top - 0.05)]
23130
23178
  ],
23131
23179
  9e-3,
23132
23180
  MT.chrome,
@@ -23823,8 +23871,8 @@ var GameVehiclesThree = (() => {
23823
23871
  setOnHit(pt, function(c2, p0) {
23824
23872
  const o = p0.obj;
23825
23873
  if (o && o.userData.hung && o.parent && o.parent !== c2.vehicle) {
23826
- o.parent.rotation.x = clamp3(o.parent.rotation.x, -0.22, 0.22);
23827
- o.parent.rotation.y = clamp3(o.parent.rotation.y, -0.08, 0.08);
23874
+ o.parent.rotation.x = clamp6(o.parent.rotation.x, -0.22, 0.22);
23875
+ o.parent.rotation.y = clamp6(o.parent.rotation.y, -0.08, 0.08);
23828
23876
  }
23829
23877
  });
23830
23878
  });
@@ -24365,18 +24413,19 @@ var GameVehiclesThree = (() => {
24365
24413
  c2.roundBox(0.03, 0.12, 1.9, 0.01, rd, m.rear - 8e-3, 1.1, 0);
24366
24414
  const beaconBar = [c2.roundBox(0.3, 0.08, 1.5, 0.03, MT.trim, 0.6, m.top + 0.04, 0)];
24367
24415
  [-0.5, -0.17, 0.17, 0.5].forEach(function(z, i) {
24368
- beaconBar.push(
24369
- c2.roundBox(
24370
- 0.24,
24371
- 0.1,
24372
- 0.3,
24373
- 0.04,
24374
- i % 2 ? MT.tail : CM("#f2f2f2", { emissive: new THREE.Color("#ffffff"), emissiveIntensity: 0.6 }),
24375
- 0.6,
24376
- m.top + 0.13,
24377
- z
24378
- )
24416
+ const lamp = c2.roundBox(
24417
+ 0.24,
24418
+ 0.1,
24419
+ 0.3,
24420
+ 0.04,
24421
+ i % 2 ? MT.tail : CM("#f2f2f2", { emissive: new THREE.Color("#ffffff"), emissiveIntensity: 0.6 }),
24422
+ 0.6,
24423
+ m.top + 0.13,
24424
+ z
24379
24425
  );
24426
+ lamp.userData.lamp = "beacon";
24427
+ lamp.userData.beaconSide = z < 0 ? 0 : 1;
24428
+ beaconBar.push(lamp);
24380
24429
  });
24381
24430
  beaconBar.push(
24382
24431
  c2.roundBox(
@@ -24475,7 +24524,7 @@ var GameVehiclesThree = (() => {
24475
24524
  const shared2 = fx.shared;
24476
24525
  const trl = fx.t;
24477
24526
  const lbl = fx.label;
24478
- const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G2 = kit.geo, DS = THREE.DoubleSide, clamp3 = kit.clamp;
24527
+ const V3 = kit.V3, PI = Math.PI, MT = kit.materials, CM = kit.makeMaterial, _paintCol = kit.paintColor, G2 = kit.geo, DS = THREE.DoubleSide, clamp6 = kit.clamp;
24479
24528
  function colorize(geo, fn) {
24480
24529
  const p = geo.attributes.position, col = new Float32Array(p.count * 3);
24481
24530
  for (let i = 0; i < p.count; i++) {
@@ -24491,7 +24540,7 @@ var GameVehiclesThree = (() => {
24491
24540
  return a + (b - a) * t;
24492
24541
  }
24493
24542
  function sstep(a, b, x) {
24494
- const t = clamp3((x - a) / (b - a), 0, 1);
24543
+ const t = clamp6((x - a) / (b - a), 0, 1);
24495
24544
  return t * t * (3 - 2 * t);
24496
24545
  }
24497
24546
  function tab(T) {
@@ -24568,7 +24617,7 @@ var GameVehiclesThree = (() => {
24568
24617
  o,
24569
24618
  sec,
24570
24619
  t: function(x) {
24571
- return clamp3((x - o.x0) / o.L, 0, 1);
24620
+ return clamp6((x - o.x0) / o.L, 0, 1);
24572
24621
  },
24573
24622
  /* half beam of the hull at height y (inside, so floors and decks do not leak) */
24574
24623
  half: function(x, y) {
@@ -24742,7 +24791,7 @@ var GameVehiclesThree = (() => {
24742
24791
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
24743
24792
  const a = poly[i], b = poly[j];
24744
24793
  if (a[1] > y !== b[1] > y && x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0]) inside = !inside;
24745
- const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t = clamp3((wx * vx + wy * vy) / (vx * vx + vy * vy), 0, 1);
24794
+ const vx = b[0] - a[0], vy = b[1] - a[1], wx = x - a[0], wy = y - a[1], t = clamp6((wx * vx + wy * vy) / (vx * vx + vy * vy), 0, 1);
24746
24795
  d = Math.min(d, Math.hypot(wx - vx * t, wy - vy * t));
24747
24796
  }
24748
24797
  return inside ? -d : d;
@@ -27290,6 +27339,7 @@ var GameVehiclesThree = (() => {
27290
27339
  shared: shared2,
27291
27340
  t,
27292
27341
  label: (key) => ({ key, label: t("labels." + key) }),
27342
+ hasSunroof: (key) => !!options.sunroofs?.includes(key),
27293
27343
  register(spec, build) {
27294
27344
  if (!groups.has(spec.group) || only && !only.has(spec.key) || skip.has(spec.key)) return;
27295
27345
  kit.register({ ...spec, seedKey: legacyVehicleSeeds[spec.key] }, build);
@@ -27864,7 +27914,8 @@ var GameVehiclesThree = (() => {
27864
27914
  const tmp = new THREE.Box3();
27865
27915
  let pruned = 0;
27866
27916
  c.vehicle.updateMatrixWorld(true);
27867
- c.vehicle.traverse((o) => {
27917
+ c.vehicle.traverse((node) => {
27918
+ const o = node;
27868
27919
  if (!o.isMesh || o.isInstancedMesh || o.isSkinnedMesh || !o.geometry || !o.geometry.attributes.position) return;
27869
27920
  for (let p = o; p; p = p.parent) if (p.visible === false) return;
27870
27921
  if (o.userData && (o.userData.ghost || o.userData.skipLite)) return;
@@ -27967,7 +28018,8 @@ var GameVehiclesThree = (() => {
27967
28018
  function count(root) {
27968
28019
  let triangles = 0;
27969
28020
  let drawCalls = 0;
27970
- root.traverse((o) => {
28021
+ root.traverse((node) => {
28022
+ const o = node;
27971
28023
  if (!o.isMesh || !o.geometry) return;
27972
28024
  for (let p = o; p; p = p.parent) if (p.visible === false) return;
27973
28025
  const g = o.geometry;
@@ -27982,7 +28034,8 @@ var GameVehiclesThree = (() => {
27982
28034
  let best = null;
27983
28035
  let bestCount = -1;
27984
28036
  const counts = /* @__PURE__ */ new Map();
27985
- c.vehicle.traverse((o) => {
28037
+ c.vehicle.traverse((node) => {
28038
+ const o = node;
27986
28039
  if (!o.isMesh || !o.geometry) return;
27987
28040
  const mats = Array.isArray(o.material) ? o.material : [o.material];
27988
28041
  const g = o.geometry;
@@ -28027,7 +28080,8 @@ var GameVehiclesThree = (() => {
28027
28080
  const bounds = new THREE.Box3();
28028
28081
  const piece = new THREE.Box3();
28029
28082
  const turned = new THREE.Matrix4();
28030
- c.vehicle.traverse((m) => {
28083
+ c.vehicle.traverse((node) => {
28084
+ const m = node;
28031
28085
  if (!m.isMesh || !m.geometry || m.userData.ghost) return;
28032
28086
  for (let p = m; p; p = p.parent) if (p.visible === false) return;
28033
28087
  m.geometry.computeBoundingBox();
@@ -28056,7 +28110,8 @@ var GameVehiclesThree = (() => {
28056
28110
  if (color2 && primary) {
28057
28111
  const repainted = primary.clone();
28058
28112
  repainted.color.set(color2);
28059
- c.vehicle.traverse((m) => {
28113
+ c.vehicle.traverse((node) => {
28114
+ const m = node;
28060
28115
  if (!m.isMesh) return;
28061
28116
  if (Array.isArray(m.material)) m.material = m.material.map((x) => x === primary ? repainted : x);
28062
28117
  else if (m.material === primary) m.material = repainted;
@@ -28092,7 +28147,8 @@ var GameVehiclesThree = (() => {
28092
28147
  context: quality === "high" ? c : null,
28093
28148
  driverEye,
28094
28149
  dispose() {
28095
- group.traverse((m) => {
28150
+ group.traverse((node) => {
28151
+ const m = node;
28096
28152
  if (m.geometry) m.geometry.dispose();
28097
28153
  });
28098
28154
  }
@@ -28114,7 +28170,7 @@ var GameVehiclesThree = (() => {
28114
28170
  }
28115
28171
  }
28116
28172
 
28117
- // ../game-shapes/dist/chunk-M5N2SHNL.js
28173
+ // ../game-shapes/dist/chunk-FZQTELDQ.js
28118
28174
  function canonicalJson(value) {
28119
28175
  if (value === null) return "null";
28120
28176
  if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
@@ -28990,17 +29046,19 @@ var GameVehiclesThree = (() => {
28990
29046
  if (p.prevZ) p.prevZ.nextZ = p.nextZ;
28991
29047
  if (p.nextZ) p.nextZ.prevZ = p.prevZ;
28992
29048
  }
28993
- function Node(i, x, y) {
28994
- this.i = i;
28995
- this.x = x;
28996
- this.y = y;
28997
- this.prev = null;
28998
- this.next = null;
28999
- this.z = 0;
29000
- this.prevZ = null;
29001
- this.nextZ = null;
29002
- this.steiner = false;
29003
- }
29049
+ var Node = class {
29050
+ constructor(i, x, y) {
29051
+ this.i = i;
29052
+ this.x = x;
29053
+ this.y = y;
29054
+ this.prev = null;
29055
+ this.next = null;
29056
+ this.z = 0;
29057
+ this.prevZ = null;
29058
+ this.nextZ = null;
29059
+ this.steiner = false;
29060
+ }
29061
+ };
29004
29062
  function signedArea(data, start2, end, dim) {
29005
29063
  let sum = 0;
29006
29064
  for (let i = start2, j = end - dim; i < end; i += dim) {
@@ -29267,8 +29325,8 @@ var GameVehiclesThree = (() => {
29267
29325
  const face = faces[i];
29268
29326
  f3(face[2] + offset, face[1] + offset, face[0] + offset);
29269
29327
  }
29270
- const layer = steps + bevelSegments * 2;
29271
- offset = vlen * layer;
29328
+ const layer2 = steps + bevelSegments * 2;
29329
+ offset = vlen * layer2;
29272
29330
  for (let i = 0; i < flen; i++) {
29273
29331
  const face = faces[i];
29274
29332
  f3(face[0] + offset, face[1] + offset, face[2] + offset);
@@ -30151,10 +30209,11 @@ var GameVehiclesThree = (() => {
30151
30209
  return out;
30152
30210
  }
30153
30211
  function assignDeep(target, source) {
30212
+ const t = target;
30154
30213
  Object.keys(source || {}).forEach(function(k) {
30155
30214
  const v = source[k];
30156
- if (v && typeof v === "object" && !Array.isArray(v)) target[k] = assignDeep(target[k] && typeof target[k] === "object" ? target[k] : {}, v);
30157
- else if (v !== void 0) target[k] = v;
30215
+ if (v && typeof v === "object" && !Array.isArray(v)) t[k] = assignDeep(t[k] && typeof t[k] === "object" ? t[k] : {}, v);
30216
+ else if (v !== void 0) t[k] = v;
30158
30217
  });
30159
30218
  return target;
30160
30219
  }
@@ -30421,30 +30480,30 @@ var GameVehiclesThree = (() => {
30421
30480
  function makeFlatMaterial(color2, o) {
30422
30481
  return makeMaterial(color2, Object.assign({ flatShading: true, roughness: 0.92 }, o || {}));
30423
30482
  }
30424
- const DOUBLE_SIDE = THREE.DoubleSide;
30483
+ const DOUBLE_SIDE2 = THREE.DoubleSide;
30425
30484
  function toColor(hex) {
30426
30485
  return new THREE.Color(hex);
30427
30486
  }
30428
30487
  const MATERIALS = {
30429
30488
  trim: makeMaterial("#17191b", { roughness: 0.7 }),
30430
- rubber: makeMaterial("#111111", { roughness: 0.95, side: DOUBLE_SIDE }),
30489
+ rubber: makeMaterial("#111111", { roughness: 0.95, side: DOUBLE_SIDE2 }),
30431
30490
  chrome: makeMaterial("#b8bcbf", { metalness: 0.9, roughness: 0.28 }),
30432
30491
  steel: makeMaterial("#6c7175", { metalness: 0.7, roughness: 0.45 }),
30433
- glass: makeMaterial("#2a3c44", { roughness: 0.05, metalness: 0.25, transparent: true, opacity: 0.4, depthWrite: false, side: DOUBLE_SIDE }),
30434
- tint: makeMaterial("#121a1e", { roughness: 0.05, metalness: 0.3, transparent: true, opacity: 0.62, depthWrite: false, side: DOUBLE_SIDE }),
30492
+ glass: makeMaterial("#2a3c44", { roughness: 0.05, metalness: 0.25, transparent: true, opacity: 0.4, depthWrite: false, side: DOUBLE_SIDE2 }),
30493
+ tint: makeMaterial("#121a1e", { roughness: 0.05, metalness: 0.3, transparent: true, opacity: 0.62, depthWrite: false, side: DOUBLE_SIDE2 }),
30435
30494
  /* rear glass: darker, so the interior does not look like an open bed seen from behind */
30436
- rearTint: makeMaterial("#0c1114", { roughness: 0.04, metalness: 0.45, transparent: true, opacity: 0.84, depthWrite: false, side: DOUBLE_SIDE }),
30437
- glassDark: makeMaterial("#0c1317", { roughness: 0.06, metalness: 0.75, side: DOUBLE_SIDE }),
30438
- armorGlass: makeMaterial("#1d302d", { roughness: 0.08, metalness: 0.4, transparent: true, opacity: 0.6, depthWrite: false, side: DOUBLE_SIDE }),
30439
- glassEdge: makeMaterial("#a9bcc1", { roughness: 0.25, metalness: 0.1, transparent: true, opacity: 0.6, depthWrite: false, side: DOUBLE_SIDE }),
30495
+ rearTint: makeMaterial("#0c1114", { roughness: 0.04, metalness: 0.45, transparent: true, opacity: 0.84, depthWrite: false, side: DOUBLE_SIDE2 }),
30496
+ glassDark: makeMaterial("#0c1317", { roughness: 0.06, metalness: 0.75, side: DOUBLE_SIDE2 }),
30497
+ armorGlass: makeMaterial("#1d302d", { roughness: 0.08, metalness: 0.4, transparent: true, opacity: 0.6, depthWrite: false, side: DOUBLE_SIDE2 }),
30498
+ glassEdge: makeMaterial("#a9bcc1", { roughness: 0.25, metalness: 0.1, transparent: true, opacity: 0.6, depthWrite: false, side: DOUBLE_SIDE2 }),
30440
30499
  lens: makeMaterial("#eef3f5", { roughness: 0.03, metalness: 0.1, transparent: true, opacity: 0.16, depthWrite: false }),
30441
30500
  lensRed: makeMaterial("#c0281c", { roughness: 0.04, metalness: 0.1, transparent: true, opacity: 0.42, depthWrite: false }),
30442
30501
  lensAmber: makeMaterial("#d88a2a", { roughness: 0.04, transparent: true, opacity: 0.4, depthWrite: false }),
30443
30502
  housing: makeMaterial("#111214", { roughness: 0.45, metalness: 0.2 }),
30444
- reflector: makeMaterial("#dfe3e6", { metalness: 0.9, roughness: 0.12, side: DOUBLE_SIDE }),
30445
- reflectorLit: makeMaterial("#e8e2cc", { metalness: 0.6, roughness: 0.18, emissive: toColor("#cfc19a"), emissiveIntensity: 1.6, side: DOUBLE_SIDE }),
30446
- hole: makeMaterial("#040506", { roughness: 1, side: DOUBLE_SIDE }),
30447
- dark: makeMaterial("#0a0b0c", { roughness: 1, side: DOUBLE_SIDE }),
30503
+ reflector: makeMaterial("#dfe3e6", { metalness: 0.9, roughness: 0.12, side: DOUBLE_SIDE2 }),
30504
+ reflectorLit: makeMaterial("#e8e2cc", { metalness: 0.6, roughness: 0.18, emissive: toColor("#cfc19a"), emissiveIntensity: 1.6, side: DOUBLE_SIDE2 }),
30505
+ hole: makeMaterial("#040506", { roughness: 1, side: DOUBLE_SIDE2 }),
30506
+ dark: makeMaterial("#0a0b0c", { roughness: 1, side: DOUBLE_SIDE2 }),
30448
30507
  head: makeMaterial("#efe6c9", { emissive: toColor("#f0dcaa"), emissiveIntensity: 1.7, roughness: 0.2 }),
30449
30508
  led: makeMaterial("#e6f2ff", { emissive: toColor("#cfe6ff"), emissiveIntensity: 1.6, roughness: 0.2 }),
30450
30509
  tail: makeMaterial("#8a1a14", { emissive: toColor("#7a0d08"), emissiveIntensity: 0.8, roughness: 0.25 }),
@@ -30457,14 +30516,14 @@ var GameVehiclesThree = (() => {
30457
30516
  wood: makeMaterial("#6b4a2e", { roughness: 0.9 }),
30458
30517
  leather: makeMaterial("#4a2f20", { roughness: 0.7 }),
30459
30518
  seat: makeMaterial("#1e1f22", { roughness: 0.85 }),
30460
- alu: makeMaterial("#8e959a", { metalness: 0.6, roughness: 0.42, side: DOUBLE_SIDE }),
30519
+ alu: makeMaterial("#8e959a", { metalness: 0.6, roughness: 0.42, side: DOUBLE_SIDE2 }),
30461
30520
  carpet: makeMaterial("#141517", { roughness: 1 }),
30462
30521
  plastic: makeMaterial("#1f2023", { roughness: 0.75 }),
30463
- headliner: makeMaterial("#6f6c66", { roughness: 0.95, side: DOUBLE_SIDE }),
30522
+ headliner: makeMaterial("#6f6c66", { roughness: 0.95, side: DOUBLE_SIDE2 }),
30464
30523
  gauge: makeMaterial("#0d1512", { emissive: toColor("#4fbf8a"), emissiveIntensity: 0.35, roughness: 0.4 }),
30465
30524
  screen: makeMaterial("#0b1220", { emissive: toColor("#3d6cb0"), emissiveIntensity: 0.4, roughness: 0.2 }),
30466
- bay: makeMaterial("#2c3033", { roughness: 0.8, metalness: 0.2, emissive: toColor("#0c0e10"), emissiveIntensity: 1, side: DOUBLE_SIDE }),
30467
- hoodLiner: makeMaterial("#1c1d1e", { roughness: 0.95, side: DOUBLE_SIDE }),
30525
+ bay: makeMaterial("#2c3033", { roughness: 0.8, metalness: 0.2, emissive: toColor("#0c0e10"), emissiveIntensity: 1, side: DOUBLE_SIDE2 }),
30526
+ hoodLiner: makeMaterial("#1c1d1e", { roughness: 0.95, side: DOUBLE_SIDE2 }),
30468
30527
  block: makeMaterial("#a3abb0", { metalness: 0.55, roughness: 0.38, emissive: toColor("#1a1d1f"), emissiveIntensity: 1 }),
30469
30528
  cover: makeMaterial("#3c4044", { metalness: 0.4, roughness: 0.35, emissive: toColor("#101213"), emissiveIntensity: 1 }),
30470
30529
  radiator: makeMaterial("#3a3f42", { metalness: 0.5, roughness: 0.55 }),
@@ -30531,7 +30590,9 @@ var GameVehiclesThree = (() => {
30531
30590
  return t;
30532
30591
  }
30533
30592
  kit.textTexture = textTexture;
30534
- Object.assign(I, { RUST, cache: cache2, studioEnvironment, makeMaterial, makeFlatMaterial, DOUBLE_SIDE, toColor, MATERIALS, paintColor, texCache, textTexture });
30593
+ const shared2 = { RUST, cache: cache2, studioEnvironment, makeMaterial, makeFlatMaterial, DOUBLE_SIDE: DOUBLE_SIDE2, toColor, MATERIALS, paintColor, texCache, textTexture };
30594
+ Object.assign(I, shared2);
30595
+ return shared2;
30535
30596
  }
30536
30597
  var TRI_BLOCK = 32;
30537
30598
  var cache = /* @__PURE__ */ new WeakMap();
@@ -30684,7 +30745,8 @@ var GameVehiclesThree = (() => {
30684
30745
  }
30685
30746
  return s;
30686
30747
  }
30687
- function indexable(THREE, mesh2) {
30748
+ function indexable(THREE, object2) {
30749
+ const mesh2 = object2;
30688
30750
  const geo = mesh2.geometry;
30689
30751
  if (mesh2.raycast !== THREE.Mesh.prototype.raycast) return false;
30690
30752
  if (!mesh2.isMesh || mesh2.isSkinnedMesh || mesh2.isInstancedMesh || mesh2.isBatchedMesh || !geo || !geo.isBufferGeometry) return false;
@@ -30771,7 +30833,7 @@ var GameVehiclesThree = (() => {
30771
30833
  const nr = g.attributes.normal;
30772
30834
  const hx = w / 2 - r;
30773
30835
  const hy = h / 2 - r;
30774
- const hz = d / 2 - r;
30836
+ const hz2 = d / 2 - r;
30775
30837
  function map(v, hi) {
30776
30838
  const i = Math.round((v + 0.5) * n);
30777
30839
  return i <= nb ? -hi - r * (nb - i) / nb : hi + r * (i - nb - 1) / nb;
@@ -30780,8 +30842,8 @@ var GameVehiclesThree = (() => {
30780
30842
  const c = new V3();
30781
30843
  const dir = new V3();
30782
30844
  for (let i = 0; i < p.count; i++) {
30783
- q.set(map(p.getX(i), hx), map(p.getY(i), hy), map(p.getZ(i), hz));
30784
- c.set(clamp(q.x, -hx, hx), clamp(q.y, -hy, hy), clamp(q.z, -hz, hz));
30845
+ q.set(map(p.getX(i), hx), map(p.getY(i), hy), map(p.getZ(i), hz2));
30846
+ c.set(clamp(q.x, -hx, hx), clamp(q.y, -hy, hy), clamp(q.z, -hz2, hz2));
30785
30847
  dir.subVectors(q, c);
30786
30848
  if (dir.lengthSq() > 1e-12) {
30787
30849
  dir.normalize();
@@ -31281,21 +31343,23 @@ var GameVehiclesThree = (() => {
31281
31343
  let wb = 0;
31282
31344
  segs.forEach(function(seg) {
31283
31345
  if (typeof seg[0] === "number") {
31346
+ const run = seg;
31284
31347
  for (let a = 0; a < names.length; a++) {
31285
31348
  const size = sizes[a];
31286
31349
  if (!idx) {
31287
- dst[a].set(arrs[a].subarray(seg[0] * 3 * size, seg[1] * 3 * size), w[a]);
31288
- w[a] += (seg[1] - seg[0]) * 3 * size;
31350
+ dst[a].set(arrs[a].subarray(run[0] * 3 * size, run[1] * 3 * size), w[a]);
31351
+ w[a] += (run[1] - run[0]) * 3 * size;
31289
31352
  continue;
31290
31353
  }
31291
- for (let f = seg[0] * 3; f < seg[1] * 3; f++) for (let q = 0; q < size; q++) dst[a][w[a]++] = arrs[a][idx[f] * size + q];
31354
+ for (let f = run[0] * 3; f < run[1] * 3; f++) for (let q = 0; q < size; q++) dst[a][w[a]++] = arrs[a][idx[f] * size + q];
31292
31355
  }
31293
31356
  if (boxesOut2 && ti) {
31294
- boxesOut2.set(ti.boxes.subarray(seg[0] * 6, seg[1] * 6), wb);
31295
- wb += (seg[1] - seg[0]) * 6;
31357
+ boxesOut2.set(ti.boxes.subarray(run[0] * 6, run[1] * 6), wb);
31358
+ wb += (run[1] - run[0]) * 6;
31296
31359
  }
31297
31360
  return;
31298
31361
  }
31362
+ ;
31299
31363
  seg.forEach(function(t) {
31300
31364
  if (boxesOut2) {
31301
31365
  for (let k = 0; k < 3; k++) {
@@ -31489,14 +31553,16 @@ var GameVehiclesThree = (() => {
31489
31553
  }
31490
31554
  };
31491
31555
  };
31492
- Object.assign(I, { roundedBoxGeometry, taper, subdivide, crease, creaseTask, refreshBounds, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject });
31556
+ const shared2 = { roundedBoxGeometry, taper, subdivide, crease, creaseTask, refreshBounds, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject };
31557
+ Object.assign(I, shared2);
31558
+ return shared2;
31493
31559
  }
31494
31560
  function installContext(kit) {
31495
31561
  const THREE = kit.THREE;
31496
31562
  const V3 = THREE.Vector3;
31497
31563
  const PI = Math.PI;
31498
31564
  const I = kit._;
31499
- const { makeMaterial, DOUBLE_SIDE, MATERIALS, crease, mergeGeometries, paintColor, roundedBoxGeometry, subdivide, taper, textTexture } = I;
31565
+ const { makeMaterial, DOUBLE_SIDE: DOUBLE_SIDE2, MATERIALS, crease, mergeGeometries, paintColor, roundedBoxGeometry, subdivide, taper, textTexture } = I;
31500
31566
  function resolveLabel(l) {
31501
31567
  if (l && typeof l === "object") return { key: l.key, label: l.label != null ? l.label : kit.t("parts." + l.key) };
31502
31568
  return { label: l };
@@ -31546,7 +31612,7 @@ var GameVehiclesThree = (() => {
31546
31612
  o = Object.assign({}, o || {});
31547
31613
  const matte = o.matte != null ? o.matte : this.meta.matte;
31548
31614
  delete o.matte;
31549
- const base = matte ? { roughness: this.state === "damaged" ? 0.92 : 0.82, metalness: 0.08, side: DOUBLE_SIDE } : { roughness: this.state === "damaged" ? 0.75 : 0.42, metalness: 0.3, side: DOUBLE_SIDE };
31615
+ const base = matte ? { roughness: this.state === "damaged" ? 0.92 : 0.82, metalness: 0.08, side: DOUBLE_SIDE2 } : { roughness: this.state === "damaged" ? 0.75 : 0.42, metalness: 0.3, side: DOUBLE_SIDE2 };
31550
31616
  const pm = makeMaterial(paintColor(hex, this.state), Object.assign(base, o));
31551
31617
  this.paints.add(pm);
31552
31618
  return pm;
@@ -31741,6 +31807,7 @@ var GameVehiclesThree = (() => {
31741
31807
  }
31742
31808
  kit.BuildContext = BuildContext;
31743
31809
  I.BuildContext = BuildContext;
31810
+ return { resolveLabel, BuildContext };
31744
31811
  }
31745
31812
  function installWarp(kit) {
31746
31813
  const THREE = kit.THREE;
@@ -31934,7 +32001,9 @@ var GameVehiclesThree = (() => {
31934
32001
  kit.applyWarp = applyWarp;
31935
32002
  kit.geo.resample = resample;
31936
32003
  kit.geo.smoothLoft = smoothLoft;
31937
- Object.assign(I, { makeWarp, applyWarp, resample, smoothLoft });
32004
+ const shared2 = { makeWarp, applyWarp, resample, smoothLoft };
32005
+ Object.assign(I, shared2);
32006
+ return shared2;
31938
32007
  }
31939
32008
  function installBody(kit) {
31940
32009
  const THREE = kit.THREE;
@@ -31998,23 +32067,24 @@ var GameVehiclesThree = (() => {
31998
32067
  o.arches.forEach(function(a) {
31999
32068
  const a0 = Math.asin(clamp((o.bottomY - a[1]) / a[2], -1, 1));
32000
32069
  const rr = a[2] - 0.01;
32070
+ const archInner = o.archInner;
32001
32071
  if (o.archInner == null) {
32002
32072
  const wl = c.add(new THREE.CylinderGeometry(rr, rr, o.W - 0.12, 20, 1, true, a0 + PI / 2, PI - 2 * a0), MATERIALS.dark, a[0], a[1], 0);
32003
32073
  wl.rotation.x = PI / 2;
32004
32074
  wl.castShadow = false;
32005
32075
  } else
32006
32076
  [1, -1].forEach(function(s) {
32007
- const len = o.W / 2 - 0.06 - o.archInner;
32077
+ const len = o.W / 2 - 0.06 - archInner;
32008
32078
  const w2 = c.add(
32009
32079
  new THREE.CylinderGeometry(rr, rr, len, 20, 1, true, a0 + PI / 2, PI - 2 * a0),
32010
32080
  MATERIALS.dark,
32011
32081
  a[0],
32012
32082
  a[1],
32013
- s * (o.archInner + len / 2)
32083
+ s * (archInner + len / 2)
32014
32084
  );
32015
32085
  w2.rotation.x = PI / 2;
32016
32086
  w2.castShadow = false;
32017
- const cap = c.add(new THREE.CircleGeometry(rr, 20, a0, PI - 2 * a0), MATERIALS.dark, a[0], a[1], s * o.archInner);
32087
+ const cap = c.add(new THREE.CircleGeometry(rr, 20, a0, PI - 2 * a0), MATERIALS.dark, a[0], a[1], s * archInner);
32018
32088
  cap.castShadow = false;
32019
32089
  });
32020
32090
  });
@@ -32091,8 +32161,12 @@ var GameVehiclesThree = (() => {
32091
32161
  if (!q.isMesh || !q.visible || q.userData.glass || q.userData.decal || q.isSprite) return;
32092
32162
  if (o.all || dset.has(q) || c.paints && c.paints.has(q.material)) cands.push(q);
32093
32163
  });
32094
- const d = (dir.isVector3 ? dir.clone() : new V3(dir[0], dir[1], dir[2])).normalize();
32095
- const p0 = org.isVector3 ? org.clone() : new V3(org[0], org[1], org[2]);
32164
+ const dv = dir;
32165
+ const da = dir;
32166
+ const d = (dv.isVector3 ? dv.clone() : new V3(da[0], da[1], da[2])).normalize();
32167
+ const ov = org;
32168
+ const oa = org;
32169
+ const p0 = ov.isVector3 ? ov.clone() : new V3(oa[0], oa[1], oa[2]);
32096
32170
  _sr.set(p0.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
32097
32171
  _sr.near = 0;
32098
32172
  _sr.far = o.max || 20;
@@ -32109,14 +32183,17 @@ var GameVehiclesThree = (() => {
32109
32183
  const h = kit.surfaceAt(c, new V3(x0, y, z || 0), new V3(-sg, 0, 0));
32110
32184
  return h ? h.p.x : items ? profX(items, y, front !== false) : null;
32111
32185
  };
32112
- Object.assign(I, { halfAt, shell, autoWindows, profX, _sr });
32186
+ const shared2 = { halfAt, shell, autoWindows, profX, _sr };
32187
+ Object.assign(I, shared2);
32188
+ return shared2;
32113
32189
  }
32114
32190
  function installGlass(kit) {
32115
32191
  const THREE = kit.THREE;
32116
32192
  const V3 = THREE.Vector3;
32117
32193
  const I = kit._;
32118
32194
  const { MATERIALS, crease, cutGeometry, pathShape, subdivide, tumble, uvProject } = I;
32119
- I.paneSeals = /* @__PURE__ */ new WeakMap();
32195
+ const paneSeals = /* @__PURE__ */ new WeakMap();
32196
+ I.paneSeals = paneSeals;
32120
32197
  kit.addGlass = function(c, geo, label, type, o) {
32121
32198
  o = o || {};
32122
32199
  if (!geo.attributes.uv || o.reuv) o.aspect = uvProject(geo, o.uvAxes);
@@ -32213,7 +32290,7 @@ var GameVehiclesThree = (() => {
32213
32290
  };
32214
32291
  kit.quadDef = function(pb, pt, Wg, T, which, bev, o) {
32215
32292
  o = o || {};
32216
- bev = bev == null ? 0.05 : bev;
32293
+ const bv = bev == null ? 0.05 : bev;
32217
32294
  const dx = pt[0] - pb[0];
32218
32295
  const dy = pt[1] - pb[1];
32219
32296
  const L = Math.hypot(dx, dy);
@@ -32222,13 +32299,13 @@ var GameVehiclesThree = (() => {
32222
32299
  const nx = which === "front" ? uy : -uy;
32223
32300
  const ny = which === "front" ? -ux : ux;
32224
32301
  const ins = o.inset || 0.09;
32225
- const off = bev + 6e-3;
32302
+ const off = bv + 6e-3;
32226
32303
  const sd = o.side || 0.1;
32227
32304
  const b = [pb[0] + ux * ins + nx * off, pb[1] + uy * ins + ny * off];
32228
32305
  const t = [pt[0] - ux * ins * 1.3 + nx * off, pt[1] - uy * ins * 1.3 + ny * off];
32229
32306
  const hb = I.halfAt(Wg, T, b[1]) - sd;
32230
32307
  const ht = I.halfAt(Wg, T, t[1]) - sd;
32231
- return { pb, pt, L, u: [ux, uy], n: [nx, ny], ins, b, t, hb, ht, which, bev, Wg, T };
32308
+ return { pb, pt, L, u: [ux, uy], n: [nx, ny], ins, b, t, hb, ht, which, bev: bv, Wg, T };
32232
32309
  };
32233
32310
  kit.glassQuad = function(c, q, label, o) {
32234
32311
  o = o || {};
@@ -32282,7 +32359,9 @@ var GameVehiclesThree = (() => {
32282
32359
  c.quads.push(q);
32283
32360
  return m;
32284
32361
  };
32285
- Object.assign(I, {});
32362
+ const shared2 = { paneSeals };
32363
+ Object.assign(I, shared2);
32364
+ return shared2;
32286
32365
  }
32287
32366
  function installLights(kit) {
32288
32367
  const THREE = kit.THREE;
@@ -32617,7 +32696,9 @@ var GameVehiclesThree = (() => {
32617
32696
  if (o.emblem !== false) kit.emblem(c, 0.05, st === "closed" ? h * 0.12 : 0, 0, Math.min(0.05, h * 0.3), g);
32618
32697
  return g;
32619
32698
  };
32620
- Object.assign(I, { lampGroup, gm, wedgeY, haloTex, glowTexture, haloMats, halo, lampKit });
32699
+ const shared2 = { lampGroup, gm, wedgeY, haloTex, glowTexture, haloMats, halo, lampKit };
32700
+ Object.assign(I, shared2);
32701
+ return shared2;
32621
32702
  }
32622
32703
  function installInterior(kit) {
32623
32704
  const THREE = kit.THREE;
@@ -32867,7 +32948,9 @@ var GameVehiclesThree = (() => {
32867
32948
  c.roundBox(o.x0 - o.x1 + 1, 0.03, zi * 2, 0.01, MATERIALS.carpet, (o.x0 + o.x1) / 2, o.floor, 0);
32868
32949
  return n;
32869
32950
  };
32870
- Object.assign(I, { consoleBox });
32951
+ const shared2 = { consoleBox };
32952
+ Object.assign(I, shared2);
32953
+ return shared2;
32871
32954
  }
32872
32955
  var HOOD_LIFT = 8e-3;
32873
32956
  function installHood(kit) {
@@ -33260,7 +33343,9 @@ var GameVehiclesThree = (() => {
33260
33343
  };
33261
33344
  return out;
33262
33345
  };
33263
- Object.assign(I, {});
33346
+ const shared2 = {};
33347
+ Object.assign(I, shared2);
33348
+ return shared2;
33264
33349
  }
33265
33350
  function installCar(kit) {
33266
33351
  const THREE = kit.THREE;
@@ -33504,13 +33589,13 @@ var GameVehiclesThree = (() => {
33504
33589
  );
33505
33590
  const hy = hl.y || s.headY || s.noseY - 0.06;
33506
33591
  const hx = I.profX(it, hy, true);
33507
- const hz = W / 2 - hl.in - hl.w / 2;
33592
+ const hz2 = W / 2 - hl.in - hl.w / 2;
33508
33593
  [1, -1].forEach(function(sd) {
33509
33594
  kit.headlight(c, {
33510
33595
  style: hl.style,
33511
33596
  x: hx + 0.02 + (hl.dx || 0),
33512
33597
  y: hy,
33513
- z: sd * hz,
33598
+ z: sd * hz2,
33514
33599
  w: hl.w,
33515
33600
  h: hl.h,
33516
33601
  side: sd,
@@ -33518,7 +33603,7 @@ var GameVehiclesThree = (() => {
33518
33603
  mat: hl.mat
33519
33604
  });
33520
33605
  });
33521
- if (hl.style === "ledbar") kit.lightBar(c, hx + 0.03, hy + hl.h * 0.3, -hz + hl.w / 2, hz - hl.w / 2, 0.018, 1);
33606
+ if (hl.style === "ledbar") kit.lightBar(c, hx + 0.03, hy + hl.h * 0.3, -hz2 + hl.w / 2, hz2 - hl.w / 2, 0.018, 1);
33522
33607
  if (s.grille !== false && !s.noGrille) {
33523
33608
  const gr = Object.assign({ style: "bars", w: W * 0.44, h: Math.max(0.08, hy - bumperY - 0.12) }, s.grille || {});
33524
33609
  const gy = gr.y || (bumperY + hy) / 2 + 0.02;
@@ -33616,7 +33701,9 @@ var GameVehiclesThree = (() => {
33616
33701
  });
33617
33702
  return out;
33618
33703
  };
33619
- Object.assign(I, {});
33704
+ const shared2 = {};
33705
+ Object.assign(I, shared2);
33706
+ return shared2;
33620
33707
  }
33621
33708
  function installTrucks(kit) {
33622
33709
  const THREE = kit.THREE;
@@ -33759,7 +33846,9 @@ var GameVehiclesThree = (() => {
33759
33846
  m.rear = rear;
33760
33847
  m.length = L;
33761
33848
  };
33762
- Object.assign(I, {});
33849
+ const shared2 = {};
33850
+ Object.assign(I, shared2);
33851
+ return shared2;
33763
33852
  }
33764
33853
  function installWheels(kit) {
33765
33854
  const THREE = kit.THREE;
@@ -33896,7 +33985,9 @@ var GameVehiclesThree = (() => {
33896
33985
  c.part("track", kit.t("parts.track", { s: z > 0 ? 1 : -1 }), tm, new V3((x0 + x1) / 2, 0.3, z), 1.4);
33897
33986
  return { mesh: tm, wheels: gs, sprocket: spr, idler: idl };
33898
33987
  };
33899
- Object.assign(I, {});
33988
+ const shared2 = {};
33989
+ Object.assign(I, shared2);
33990
+ return shared2;
33900
33991
  }
33901
33992
  var ROUTED_PARTICLE_TYPES = { smoke: "particle_puff", dust: "particle_puff", fire: "particle_glow" };
33902
33993
  var PUFF_LOOKS = {
@@ -34321,7 +34412,7 @@ var GameVehiclesThree = (() => {
34321
34412
  }
34322
34413
  });
34323
34414
  }
34324
- Object.assign(I, {
34415
+ const shared2 = {
34325
34416
  PARTICLE_LIMIT,
34326
34417
  PARTICLE_GEOMETRIES,
34327
34418
  PARTICLE_MATERIALS,
@@ -34341,7 +34432,9 @@ var GameVehiclesThree = (() => {
34341
34432
  burst,
34342
34433
  exhaustLook,
34343
34434
  tickEmit
34344
- });
34435
+ };
34436
+ Object.assign(I, shared2);
34437
+ return shared2;
34345
34438
  }
34346
34439
  var PUDDLE_COLORS = {
34347
34440
  coolant: 3123279,
@@ -34356,7 +34449,7 @@ var GameVehiclesThree = (() => {
34356
34449
  const V3 = THREE.Vector3;
34357
34450
  const PI = Math.PI;
34358
34451
  const I = kit._;
34359
- const { DOUBLE_SIDE } = I;
34452
+ const { DOUBLE_SIDE: DOUBLE_SIDE2 } = I;
34360
34453
  kit.fluidTypes = {};
34361
34454
  kit.defineFluid = function(key, def) {
34362
34455
  const d = Object.assign({ dryTime: 600, roughFresh: 0.12, roughDry: 0.92, metalFresh: 0.15, metalDry: 0, wipeable: false }, def || {});
@@ -34377,7 +34470,7 @@ var GameVehiclesThree = (() => {
34377
34470
  color: fdef.freshC.clone(),
34378
34471
  roughness: fdef.roughFresh,
34379
34472
  metalness: fdef.metalFresh,
34380
- side: DOUBLE_SIDE,
34473
+ side: DOUBLE_SIDE2,
34381
34474
  polygonOffset: true,
34382
34475
  polygonOffsetFactor: -2,
34383
34476
  polygonOffsetUnits: -4
@@ -34635,7 +34728,7 @@ var GameVehiclesThree = (() => {
34635
34728
  metalness: 0.7,
34636
34729
  transparent: true,
34637
34730
  opacity: 0,
34638
- side: DOUBLE_SIDE,
34731
+ side: DOUBLE_SIDE2,
34639
34732
  depthWrite: false
34640
34733
  })
34641
34734
  );
@@ -34649,7 +34742,7 @@ var GameVehiclesThree = (() => {
34649
34742
  } else if (kind === "gasoline") {
34650
34743
  const haze = new THREE.Mesh(
34651
34744
  puddleGeometry(),
34652
- new THREE.MeshBasicMaterial({ color: 10465982, transparent: true, opacity: 0.035, depthWrite: false, side: DOUBLE_SIDE })
34745
+ new THREE.MeshBasicMaterial({ color: 10465982, transparent: true, opacity: 0.035, depthWrite: false, side: DOUBLE_SIDE2 })
34653
34746
  );
34654
34747
  haze.position.set(q.x, 0.22, q.z);
34655
34748
  haze.raycast = function() {
@@ -34813,7 +34906,9 @@ var GameVehiclesThree = (() => {
34813
34906
  }
34814
34907
  return n;
34815
34908
  };
34816
- Object.assign(I, { _fr, buildDrip, DOWNHILL, PN, puddleGeometry, rimGeometry, puddle, shapePuddle, updateFluids });
34909
+ const shared2 = { _fr, buildDrip, DOWNHILL, PN, puddleGeometry, rimGeometry, puddle, shapePuddle, updateFluids };
34910
+ Object.assign(I, shared2);
34911
+ return shared2;
34817
34912
  }
34818
34913
  function crackedGlassMaterial(THREE, texture) {
34819
34914
  return new THREE.MeshStandardMaterial({
@@ -35135,8 +35230,20 @@ var GameVehiclesThree = (() => {
35135
35230
  if (!L || L.broken) return;
35136
35231
  L.broken = true;
35137
35232
  L.lens.forEach(function(m) {
35138
- m.visible = false;
35233
+ const rim = L.lensMaterial ? glassRim(m.geometry, c.rand) : null;
35234
+ if (!rim) {
35235
+ m.visible = false;
35236
+ return;
35237
+ }
35238
+ m.geometry.dispose();
35239
+ m.geometry = rim;
35240
+ m.material = MATERIALS.glassEdge;
35241
+ m.userData.lensRim = true;
35139
35242
  });
35243
+ if (L.lensMaterial)
35244
+ (L.reflector || []).forEach(function(m) {
35245
+ m.material = MATERIALS.broken;
35246
+ });
35140
35247
  L.inner.forEach(function(m) {
35141
35248
  m.material = MATERIALS.bulbBroken;
35142
35249
  });
@@ -35144,18 +35251,47 @@ var GameVehiclesThree = (() => {
35144
35251
  h.visible = false;
35145
35252
  });
35146
35253
  const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
35147
- I.burst(c, "lens", c.toEffects(p), 10, { dir: c.dirToEffects(dir.clone().negate()), speed: 1.4, variant: L.tint === "red" ? "red" : "clear" });
35148
- I.burst(c, "plastic", c.toEffects(p), 2, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
35254
+ const glass = L.lensMaterial !== "plastic";
35255
+ I.burst(c, "lens", c.toEffects(p), glass ? 10 : 5, { dir: c.dirToEffects(dir.clone().negate()), speed: glass ? 1.4 : 1.1, variant: L.tint === "red" ? "red" : "clear" });
35256
+ I.burst(c, "plastic", c.toEffects(p), glass ? 2 : 5, { dir: c.dirToEffects(dir.clone().negate()), speed: 1 });
35149
35257
  }
35150
35258
  kit.breakLight = breakLight;
35151
- Object.assign(I, { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight });
35259
+ const crazedCache = /* @__PURE__ */ new Map();
35260
+ const CRAZED_OPACITY = 0.6;
35261
+ function crazed(material) {
35262
+ const known = crazedCache.get(material);
35263
+ if (known) return known;
35264
+ const m = I.crackLens(material).clone();
35265
+ m.opacity = Math.max(m.opacity ?? 1, CRAZED_OPACITY);
35266
+ m.transparent = true;
35267
+ m.userData.shared = true;
35268
+ crazedCache.set(material, m);
35269
+ return m;
35270
+ }
35271
+ function crackLight(c, L, dir, pierced) {
35272
+ if (!L || L.broken) return;
35273
+ if (!L.cracked) {
35274
+ L.cracked = true;
35275
+ for (const m of L.lens) if (m.material && !Array.isArray(m.material)) m.material = crazed(m.material);
35276
+ }
35277
+ const p = L.group ? L.group.position.clone() : L.inner[0] ? L.inner[0].position.clone() : new V3();
35278
+ I.burst(c, "lens", c.toEffects(p), 3, { dir: c.dirToEffects(dir.clone().negate()), speed: 0.9, variant: L.tint === "red" ? "red" : "clear" });
35279
+ if (!pierced || L.dark) return;
35280
+ L.dark = true;
35281
+ for (const m of L.inner) m.material = MATERIALS.bulbBroken;
35282
+ for (const h of L.halo ?? []) h.visible = false;
35283
+ }
35284
+ kit.crackLight = crackLight;
35285
+ const shared2 = { glassUV, drawWeb, crackGlass, glassRim, shatterGlass, hitGlass, breakLight, crackLight };
35286
+ Object.assign(I, shared2);
35287
+ return shared2;
35152
35288
  }
35153
35289
  function installImpact(kit) {
35154
35290
  const THREE = kit.THREE;
35155
35291
  const V3 = THREE.Vector3;
35156
35292
  const PI = Math.PI;
35157
35293
  const I = kit._;
35158
- const { makeMaterial, DOUBLE_SIDE, crease, paintColor } = I;
35294
+ const { makeMaterial, DOUBLE_SIDE: DOUBLE_SIDE2, crease, paintColor } = I;
35159
35295
  function dentField(v, H) {
35160
35296
  const d = v.distanceTo(H.p);
35161
35297
  if (d >= H.R) return 0;
@@ -35504,7 +35640,7 @@ var GameVehiclesThree = (() => {
35504
35640
  I.burst(c, "scrap", pf, Math.round(force * 5), {
35505
35641
  dir: df,
35506
35642
  speed: 1.4,
35507
- mat: makeMaterial(paintColor(m.color || "#777", c.state), { roughness: 0.5, metalness: 0.3, side: DOUBLE_SIDE })
35643
+ mat: makeMaterial(paintColor(m.color || "#777", c.state), { roughness: 0.5, metalness: 0.3, side: DOUBLE_SIDE2 })
35508
35644
  });
35509
35645
  I.burst(c, "plastic", pf, 2, { dir: df, speed: 1.2 });
35510
35646
  }
@@ -35620,7 +35756,7 @@ var GameVehiclesThree = (() => {
35620
35756
  return hit;
35621
35757
  }
35622
35758
  kit.zoneImpact = zoneImpact;
35623
- Object.assign(I, {
35759
+ const shared2 = {
35624
35760
  dentField,
35625
35761
  hangBumper,
35626
35762
  tankFuel,
@@ -35636,14 +35772,16 @@ var GameVehiclesThree = (() => {
35636
35772
  _pr,
35637
35773
  probe,
35638
35774
  zoneImpact
35639
- });
35775
+ };
35776
+ Object.assign(I, shared2);
35777
+ return shared2;
35640
35778
  }
35641
35779
  function installExplosion(kit) {
35642
35780
  const THREE = kit.THREE;
35643
35781
  const V3 = THREE.Vector3;
35644
35782
  const PI = Math.PI;
35645
35783
  const I = kit._;
35646
- const { makeMaterial, DOUBLE_SIDE, MATERIALS, paintColor, textTexture } = I;
35784
+ const { makeMaterial, DOUBLE_SIDE: DOUBLE_SIDE2, MATERIALS, paintColor, textTexture } = I;
35647
35785
  const BURNT = ["#1d1a17", "#26211c", "#171514", "#2e2620"].map(function(h, i) {
35648
35786
  return makeMaterial(h, { roughness: 1, metalness: i === 3 ? 0.35 : 0.15 });
35649
35787
  });
@@ -35654,7 +35792,7 @@ var GameVehiclesThree = (() => {
35654
35792
  transparent: true,
35655
35793
  opacity: op,
35656
35794
  depthWrite: false,
35657
- side: DOUBLE_SIDE,
35795
+ side: DOUBLE_SIDE2,
35658
35796
  fog: false,
35659
35797
  blending: add2 ? THREE.AdditiveBlending : THREE.NormalBlending
35660
35798
  });
@@ -35842,7 +35980,7 @@ var GameVehiclesThree = (() => {
35842
35980
  I.burst(c, "scrap", cen, 14, {
35843
35981
  speed: 5,
35844
35982
  spread: 1.5,
35845
- mat: makeMaterial(paintColor(m.color || "#777", c.state), { roughness: 0.5, metalness: 0.3, side: DOUBLE_SIDE })
35983
+ mat: makeMaterial(paintColor(m.color || "#777", c.state), { roughness: 0.5, metalness: 0.3, side: DOUBLE_SIDE2 })
35846
35984
  });
35847
35985
  I.burst(c, "plastic", cen, 8, { speed: 4 });
35848
35986
  c.glass.slice().forEach(function(gm2) {
@@ -36019,7 +36157,9 @@ var GameVehiclesThree = (() => {
36019
36157
  }
36020
36158
  }
36021
36159
  }
36022
- Object.assign(I, { BURNT, BURNT_RUB, boomMat, enginePos, fling, burnAll, bigBoomFx, detonate, tickBoom, tickFlying });
36160
+ const shared2 = { BURNT, BURNT_RUB, boomMat, enginePos, fling, burnAll, bigBoomFx, detonate, tickBoom, tickFlying };
36161
+ Object.assign(I, shared2);
36162
+ return shared2;
36023
36163
  }
36024
36164
  function installRollover(kit) {
36025
36165
  const THREE = kit.THREE;
@@ -36219,7 +36359,8 @@ var GameVehiclesThree = (() => {
36219
36359
  c.parts.forEach(function(p) {
36220
36360
  if (p.kind !== "rotor" && p.kind !== "tail_rotor" || !p.obj) return;
36221
36361
  const main = p.kind === "rotor";
36222
- p.obj.traverse(function(o) {
36362
+ p.obj.traverse(function(node) {
36363
+ const o = node;
36223
36364
  if (!o.isMesh || !o.visible) return;
36224
36365
  const bb = new THREE.Box3().setFromObject(o).applyMatrix4(inv);
36225
36366
  if (!main && bb.min.y >= -0.01) return;
@@ -36291,7 +36432,9 @@ var GameVehiclesThree = (() => {
36291
36432
  if (p >= 1) finishRoll(c);
36292
36433
  }
36293
36434
  kit.rollContact = rollContact;
36294
- Object.assign(I, { rollMeshes, convexHull, rollContact, exactMinY, finishRoll, tickRoll });
36435
+ const shared2 = { rollMeshes, convexHull, rollContact, exactMinY, finishRoll, tickRoll };
36436
+ Object.assign(I, shared2);
36437
+ return shared2;
36295
36438
  }
36296
36439
  function installTick(kit) {
36297
36440
  const I = kit._;
@@ -36327,7 +36470,9 @@ var GameVehiclesThree = (() => {
36327
36470
  c.hood.target = open ? c.hood.open : 0;
36328
36471
  return true;
36329
36472
  };
36330
- Object.assign(I, {});
36473
+ const shared2 = {};
36474
+ Object.assign(I, shared2);
36475
+ return shared2;
36331
36476
  }
36332
36477
  function installArmor(kit) {
36333
36478
  const THREE = kit.THREE;
@@ -36577,7 +36722,9 @@ var GameVehiclesThree = (() => {
36577
36722
  }
36578
36723
  }
36579
36724
  kit.applyArmor = applyArmor;
36580
- Object.assign(I, { STEEL_MATERIAL, PLATE_MATERIALS, jerryCan, applyArmor });
36725
+ const shared2 = { STEEL_MATERIAL, PLATE_MATERIALS, jerryCan, applyArmor };
36726
+ Object.assign(I, shared2);
36727
+ return shared2;
36581
36728
  }
36582
36729
  function installZoneCore(kit) {
36583
36730
  const THREE = kit.THREE;
@@ -36594,8 +36741,8 @@ var GameVehiclesThree = (() => {
36594
36741
  return { id: b.id, name: kit.t("bands." + b.id), min: b.min, health: b.health, color: b.color };
36595
36742
  });
36596
36743
  function bandOf(h) {
36597
- h = Math.round(clamp(h == null ? 100 : h, 0, 100));
36598
- for (let i = 0; i < BANDS.length; i++) if (h >= BANDS[i].min) return i;
36744
+ const v = Math.round(clamp(h == null ? 100 : h, 0, 100));
36745
+ for (let i = 0; i < BANDS.length; i++) if (v >= BANDS[i].min) return i;
36599
36746
  return 4;
36600
36747
  }
36601
36748
  kit.bands = BANDS;
@@ -36621,8 +36768,12 @@ var GameVehiclesThree = (() => {
36621
36768
  z0: Math.min(b[4], b[5]),
36622
36769
  z1: Math.max(b[4], b[5])
36623
36770
  };
36624
- if (b.isBox3) return { x0: b.min.x, x1: b.max.x, y0: b.min.y, y1: b.max.y, z0: b.min.z, z1: b.max.z };
36625
- return { x0: b.x0, x1: b.x1, y0: b.y0, y1: b.y1, z0: b.z0, z1: b.z1 };
36771
+ if (b.isBox3) {
36772
+ const box2 = b;
36773
+ return { x0: box2.min.x, x1: box2.max.x, y0: box2.min.y, y1: box2.max.y, z0: box2.min.z, z1: box2.max.z };
36774
+ }
36775
+ const r = b;
36776
+ return { x0: r.x0, x1: r.x1, y0: r.y0, y1: r.y1, z0: r.z0, z1: r.z1 };
36626
36777
  }
36627
36778
  kit.normalizeBox = normalizeBox;
36628
36779
  function boxLo(B, a) {
@@ -36659,7 +36810,8 @@ var GameVehiclesThree = (() => {
36659
36810
  const bb = new THREE.Box3();
36660
36811
  const tmp = new THREE.Box3();
36661
36812
  const M = new THREE.Matrix4();
36662
- o.traverse(function(x) {
36813
+ o.traverse(function(node) {
36814
+ const x = node;
36663
36815
  if (!x.isMesh || !x.geometry || !x.geometry.attributes.position || x.userData.decal) return;
36664
36816
  if (!x.geometry.boundingBox) x.geometry.computeBoundingBox();
36665
36817
  tmp.copy(x.geometry.boundingBox).applyMatrix4(M.multiplyMatrices(inv, x.matrixWorld));
@@ -36725,7 +36877,7 @@ var GameVehiclesThree = (() => {
36725
36877
  for (let q = o; q && q !== c.vehicle; q = q.parent) if (q.visible === false) return false;
36726
36878
  return !!o;
36727
36879
  }
36728
- Object.assign(I, {
36880
+ const shared2 = {
36729
36881
  BANDS,
36730
36882
  bandOf,
36731
36883
  ZONE_WEIGHTS,
@@ -36748,16 +36900,18 @@ var GameVehiclesThree = (() => {
36748
36900
  lightBoxOf,
36749
36901
  hideObj,
36750
36902
  shown
36751
- });
36903
+ };
36904
+ Object.assign(I, shared2);
36905
+ return shared2;
36752
36906
  }
36753
36907
  function installZoneDecals(kit) {
36754
36908
  const THREE = kit.THREE;
36755
36909
  const V3 = THREE.Vector3;
36756
36910
  const PI = Math.PI;
36757
36911
  const I = kit._;
36758
- const { makeMaterial, DOUBLE_SIDE } = I;
36912
+ const { makeMaterial, DOUBLE_SIDE: DOUBLE_SIDE2 } = I;
36759
36913
  function dmat(hex, o) {
36760
- return makeMaterial(hex, Object.assign({ polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -3, side: DOUBLE_SIDE }, o || {}));
36914
+ return makeMaterial(hex, Object.assign({ polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -3, side: DOUBLE_SIDE2 }, o || {}));
36761
36915
  }
36762
36916
  const DAMAGE_MATERIALS = {
36763
36917
  /* without an environment map, high-metalness metal turns black and chipped paint looks like a bullet hole: light, barely metallic metal */
@@ -36775,7 +36929,7 @@ var GameVehiclesThree = (() => {
36775
36929
  transparent: true,
36776
36930
  opacity: 0.62,
36777
36931
  depthWrite: false,
36778
- side: DOUBLE_SIDE,
36932
+ side: DOUBLE_SIDE2,
36779
36933
  polygonOffset: true,
36780
36934
  polygonOffsetFactor: -2,
36781
36935
  polygonOffsetUnits: -3
@@ -37031,7 +37185,10 @@ var GameVehiclesThree = (() => {
37031
37185
  narrowTo(rc, q);
37032
37186
  }
37033
37187
  const hits = rc.intersectObjects(cands, false);
37034
- for (let i = 0; i < hits.length; i++) if (hits[i].object.proxyOf) hits[i].object = hits[i].object.proxyOf;
37188
+ for (let i = 0; i < hits.length; i++) {
37189
+ const proxyOf = hits[i].object.proxyOf;
37190
+ if (proxyOf) hits[i].object = proxyOf;
37191
+ }
37035
37192
  return hits;
37036
37193
  }
37037
37194
  function zoneCands(c, z, inv, glassToo) {
@@ -37232,7 +37389,7 @@ var GameVehiclesThree = (() => {
37232
37389
  for (i = 0; i < (spec.tear || 0); i++) if (s = nx()) tears.push(tearDecal(c, s.p, s.n, 0.07 + R() * 0.06, R, s.obj));
37233
37390
  return S;
37234
37391
  }
37235
- Object.assign(I, {
37392
+ const shared2 = {
37236
37393
  dmat,
37237
37394
  DAMAGE_MATERIALS,
37238
37395
  _zq,
@@ -37253,7 +37410,9 @@ var GameVehiclesThree = (() => {
37253
37410
  ribbonGeo,
37254
37411
  scratchDecal,
37255
37412
  zoneDecals
37256
- });
37413
+ };
37414
+ Object.assign(I, shared2);
37415
+ return shared2;
37257
37416
  }
37258
37417
  function installZoneDent(kit) {
37259
37418
  const THREE = kit.THREE;
@@ -37609,7 +37768,9 @@ var GameVehiclesThree = (() => {
37609
37768
  });
37610
37769
  return piv;
37611
37770
  }
37612
- Object.assign(I, { sm, subdivIn, dentTargets, zoneDent, cutPanel, pivotOf });
37771
+ const shared2 = { sm, subdivIn, dentTargets, zoneDent, cutPanel, pivotOf };
37772
+ Object.assign(I, shared2);
37773
+ return shared2;
37613
37774
  }
37614
37775
  function installDamagePanels(kit) {
37615
37776
  const THREE = kit.THREE;
@@ -37960,7 +38121,9 @@ var GameVehiclesThree = (() => {
37960
38121
  const sgn = new V3().crossVectors(axisV, tangent).dot(outv) > 0 ? 1 : -1;
37961
38122
  piv.quaternion.setFromAxisAngle(axisV, sgn * (0.6 + R() * 0.3));
37962
38123
  }
37963
- Object.assign(I, { panelBand, roofOff, tailOff, followersIn, windowsOver, doorGap, mirrorHang, doorHang, lidHang, flapHang });
38124
+ const shared2 = { panelBand, roofOff, tailOff, followersIn, windowsOver, doorGap, mirrorHang, doorHang, lidHang, flapHang };
38125
+ Object.assign(I, shared2);
38126
+ return shared2;
37964
38127
  }
37965
38128
  function installDamageParts(kit) {
37966
38129
  const THREE = kit.THREE;
@@ -38074,7 +38237,8 @@ var GameVehiclesThree = (() => {
38074
38237
  }
38075
38238
  function tireMeshes(w) {
38076
38239
  const out = [];
38077
- w.traverse(function(o) {
38240
+ w.traverse(function(node) {
38241
+ const o = node;
38078
38242
  if (o.isMesh && o.material === MATERIALS.rubber) out.push(o);
38079
38243
  });
38080
38244
  return out;
@@ -38133,7 +38297,8 @@ var GameVehiclesThree = (() => {
38133
38297
  c.zoneDrops.push({ x: w.position.x, z: w.position.z, d: r * 0.24 });
38134
38298
  } else if (b === 4) {
38135
38299
  let rr = 0;
38136
- w.traverse(function(o) {
38300
+ w.traverse(function(node) {
38301
+ const o = node;
38137
38302
  if (!o.isMesh || o.material === MATERIALS.rubber) return;
38138
38303
  if (!o.geometry.boundingSphere) o.geometry.computeBoundingSphere();
38139
38304
  const cc = o.geometry.boundingSphere.center.clone().applyMatrix4(o.matrix);
@@ -38341,7 +38506,8 @@ var GameVehiclesThree = (() => {
38341
38506
  const xf = H.x0 + (H.x1 - H.x0) * (0.5 + R() * 0.1);
38342
38507
  const k = 0.55 + R() * 0.2;
38343
38508
  const v = new V3();
38344
- H.piv.children.forEach(function(msh) {
38509
+ H.piv.children.forEach(function(node) {
38510
+ const msh = node;
38345
38511
  if (!msh.isMesh) return;
38346
38512
  msh.updateMatrix();
38347
38513
  const M = msh.matrix;
@@ -38534,7 +38700,7 @@ var GameVehiclesThree = (() => {
38534
38700
  });
38535
38701
  void K;
38536
38702
  }
38537
- Object.assign(I, {
38703
+ const shared2 = {
38538
38704
  glassPt,
38539
38705
  glassIn,
38540
38706
  glassBand,
@@ -38552,7 +38718,9 @@ var GameVehiclesThree = (() => {
38552
38718
  bumperBand,
38553
38719
  partBand,
38554
38720
  bendAft
38555
- });
38721
+ };
38722
+ Object.assign(I, shared2);
38723
+ return shared2;
38556
38724
  }
38557
38725
  function installZoneDerive(kit) {
38558
38726
  const THREE = kit.THREE;
@@ -38573,7 +38741,8 @@ var GameVehiclesThree = (() => {
38573
38741
  }
38574
38742
  function resolveObjs(c, list2, z) {
38575
38743
  const out = [];
38576
- (list2 || []).forEach(function(o) {
38744
+ (list2 || []).forEach(function(item) {
38745
+ const o = item;
38577
38746
  if (!o) return;
38578
38747
  if (typeof o === "string") {
38579
38748
  c.parts.forEach(function(p) {
@@ -38822,7 +38991,9 @@ var GameVehiclesThree = (() => {
38822
38991
  return null;
38823
38992
  }
38824
38993
  kit.zoneOwner = ownerOf;
38825
- Object.assign(I, { parseDir, resolveObjs, deriveZones, carMap, zonesOf, ownerOf });
38994
+ const shared2 = { parseDir, resolveObjs, deriveZones, carMap, zonesOf, ownerOf };
38995
+ Object.assign(I, shared2);
38996
+ return shared2;
38826
38997
  }
38827
38998
  var REAR_GLASS_KEYS = ["rear_window", "window_rear", "quarter_glass"];
38828
38999
  function installZoneAuto(kit) {
@@ -39615,7 +39786,9 @@ var GameVehiclesThree = (() => {
39615
39786
  if (!wheelsDone) wheelZones(false);
39616
39787
  return Z;
39617
39788
  }
39618
- Object.assign(I, { autoZones });
39789
+ const shared2 = { autoZones };
39790
+ Object.assign(I, shared2);
39791
+ return shared2;
39619
39792
  }
39620
39793
  function installZoneApply(kit) {
39621
39794
  const THREE = kit.THREE;
@@ -39824,7 +39997,9 @@ var GameVehiclesThree = (() => {
39824
39997
  c.rand = R0;
39825
39998
  return c.zoneState;
39826
39999
  };
39827
- Object.assign(I, { fadePaint, abandonFx, burntFx, settleDrops });
40000
+ const shared2 = { fadePaint, abandonFx, burntFx, settleDrops };
40001
+ Object.assign(I, shared2);
40002
+ return shared2;
39828
40003
  }
39829
40004
  function installScenarios(kit) {
39830
40005
  const THREE = kit.THREE;
@@ -39979,7 +40154,9 @@ var GameVehiclesThree = (() => {
39979
40154
  }
39980
40155
  return { p, dir };
39981
40156
  };
39982
- Object.assign(I, { BODY_TYPES });
40157
+ const shared2 = { BODY_TYPES };
40158
+ Object.assign(I, shared2);
40159
+ return shared2;
39983
40160
  }
39984
40161
  var SVGNS = "http://www.w3.org/2000/svg";
39985
40162
  var BAND_TEXT = ["#10140f", "#1c1606", "#1f1206", "#fff3ef", "#f1f2f2"];
@@ -40134,7 +40311,8 @@ var GameVehiclesThree = (() => {
40134
40311
  }
40135
40312
  g.addEventListener("click", pick);
40136
40313
  g.addEventListener("keydown", function(e) {
40137
- if (e.key === "Enter" || e.key === " ") {
40314
+ const key = e.key;
40315
+ if (key === "Enter" || key === " ") {
40138
40316
  e.preventDefault();
40139
40317
  pick();
40140
40318
  }
@@ -40147,7 +40325,7 @@ var GameVehiclesThree = (() => {
40147
40325
  return kit.bands.map(function(B, i) {
40148
40326
  const b = el("button", null, B.name);
40149
40327
  b.type = "button";
40150
- b.dataset.b = i;
40328
+ b.dataset.b = String(i);
40151
40329
  b.style.setProperty("--band", B.color);
40152
40330
  b.addEventListener("click", function() {
40153
40331
  if (onPick) onPick(B.health, i);
@@ -40369,6 +40547,7 @@ var GameVehiclesThree = (() => {
40369
40547
  hash,
40370
40548
  clamp,
40371
40549
  smooth,
40550
+ // filled by the modules below, each with the helpers it shares
40372
40551
  _: {}
40373
40552
  };
40374
40553
  kit.fuels = {};
@@ -40446,8 +40625,8 @@ var GameVehiclesThree = (() => {
40446
40625
  fn.apply(kit, args);
40447
40626
  });
40448
40627
  };
40449
- kit.configure = function(o) {
40450
- o = o || {};
40628
+ kit.configure = function(cfg) {
40629
+ const o = cfg || {};
40451
40630
  if (o.bands)
40452
40631
  o.bands.forEach(function(b, i) {
40453
40632
  if (kit.bands[i] && b) Object.assign(kit.bands[i], b);
@@ -40483,7 +40662,8 @@ var GameVehiclesThree = (() => {
40483
40662
  }
40484
40663
  ;
40485
40664
  ["impactStain", "defaultSplat", "defaultGroup", "damagedScenario"].forEach(function(k) {
40486
- if (o[k] !== void 0) kit.config[k] = o[k];
40665
+ const v = o[k];
40666
+ if (v !== void 0) kit.config[k] = v;
40487
40667
  });
40488
40668
  return kit;
40489
40669
  };
@@ -40658,6 +40838,8 @@ var GameVehiclesThree = (() => {
40658
40838
  heli: ["battery", "alternator", "gearbox"]
40659
40839
  };
40660
40840
  var HITCH_CHANCE = { car: 30, truck: 60, bus: 0, tractor: 100, quad: 40, bike: 0, tank: 0, boat: 0, plane: 0, heli: 0 };
40841
+ var GLASS_LENS_HP = 320;
40842
+ var PLASTIC_LENS_HP = 900;
40661
40843
  var STEEL_KG_M2 = 7;
40662
40844
  var ALU_KG_M2 = 3.2;
40663
40845
  var kindOf = (c) => {
@@ -40815,7 +40997,9 @@ var GameVehiclesThree = (() => {
40815
40997
  continue;
40816
40998
  }
40817
40999
  if (role === "light") {
40818
- add2({ id, role, name: label, material: "glass", box: box2, mass: 1.2 + 4 * area3, hp: 320, zoneId: id, visual: true, tags: ["light", ...tags] });
41000
+ const lens = /^head/.test(id) ? spec.headlightLens ?? "polycarbonate" : "acrylic";
41001
+ const glassLens = lens === "glass";
41002
+ add2({ id, role, name: label, material: glassLens ? "glass" : "plastic", box: box2, mass: 1.2 + 4 * area3, hp: glassLens ? GLASS_LENS_HP : PLASTIC_LENS_HP, zoneId: id, visual: true, tags: ["light", `lens_${lens}`, ...tags] });
40819
41003
  continue;
40820
41004
  }
40821
41005
  if (role === "bumper") {
@@ -41269,20 +41453,185 @@ var GameVehiclesThree = (() => {
41269
41453
  mesh2.geometry.dispose();
41270
41454
  mesh2.geometry = g;
41271
41455
  }
41456
+ var STRIP_SMOOTH_DEG = 180;
41457
+ var SKIN_BAND = 0.05;
41458
+ var BODY_FACING = 0.5;
41459
+ var FIELDS = 4;
41460
+ var SEAM_THIN = 0.03;
41461
+ var SEAM_AT_EDGE = 0.04;
41462
+ var SEAM_TALL = 0.4;
41463
+ var SLIVER_AREA = 1e-10;
41464
+ function cutStripOverDoor(vehicleKit, params) {
41465
+ const kit = vehicleKit;
41466
+ const strip = params.strip;
41467
+ const toVehicle = params.toVehicle;
41468
+ const { x0, x1 } = params;
41469
+ const THREE = kit.THREE;
41470
+ const at = new THREE.Vector3();
41471
+ const along = (x, y, z) => at.set(x, y, z).applyMatrix4(toVehicle).x;
41472
+ const piece = (tests) => {
41473
+ const mesh2 = new THREE.Mesh(strip.geometry.clone(), strip.material);
41474
+ kit.cutMesh(mesh2, tests, STRIP_SMOOTH_DEG, {});
41475
+ return mesh2;
41476
+ };
41477
+ const over = piece([{ sdf: (x, y, z) => along(x, y, z) - x0 }, { sdf: (x, y, z) => x1 - along(x, y, z) }]);
41478
+ const after = piece([{ sdf: (x, y, z) => along(x, y, z) - x1 }]);
41479
+ kit.cutMesh(strip, [{ sdf: (x, y, z) => x0 - along(x, y, z) }], STRIP_SMOOTH_DEG, {});
41480
+ const joined = joinTriangles(THREE, strip.geometry, after.geometry);
41481
+ strip.geometry.dispose();
41482
+ after.geometry.dispose();
41483
+ strip.geometry = joined;
41484
+ return over;
41485
+ }
41486
+ function joinTriangles(THREE, first, second) {
41487
+ const a = first.index ? first.toNonIndexed() : first;
41488
+ const b = second.index ? second.toNonIndexed() : second;
41489
+ const flags = [...solidTriangles(THREE, a), ...solidTriangles(THREE, b)];
41490
+ const both = new THREE.BufferGeometry();
41491
+ for (const name of Object.keys(a.attributes)) {
41492
+ const p = a.getAttribute(name);
41493
+ const q = b.getAttribute(name);
41494
+ if (!q || q.itemSize !== p.itemSize) continue;
41495
+ const array = new Float32Array(p.array.length + q.array.length);
41496
+ array.set(p.array, 0);
41497
+ array.set(q.array, p.array.length);
41498
+ both.setAttribute(name, new THREE.Float32BufferAttribute(array, p.itemSize));
41499
+ }
41500
+ if (a !== first) a.dispose();
41501
+ if (b !== second) b.dispose();
41502
+ const out = pickTriangles(THREE, both, flags, true) ?? new THREE.BufferGeometry();
41503
+ both.dispose();
41504
+ return out;
41505
+ }
41506
+ function solidTriangles(THREE, geometry) {
41507
+ const pos = geometry.getAttribute("position");
41508
+ const triangle = new THREE.Triangle();
41509
+ const flags = [];
41510
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
41511
+ triangle.a.fromBufferAttribute(pos, t * 3);
41512
+ triangle.b.fromBufferAttribute(pos, t * 3 + 1);
41513
+ triangle.c.fromBufferAttribute(pos, t * 3 + 2);
41514
+ flags.push(triangle.getArea() > SLIVER_AREA);
41515
+ }
41516
+ return flags;
41517
+ }
41518
+ function splitDoorSkin(three, meshes, toVehicleOf, side2, depth) {
41519
+ const THREE = three;
41520
+ const pieces = meshes;
41521
+ const toVehicle = toVehicleOf;
41522
+ const v = new THREE.Vector3();
41523
+ const corners = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
41524
+ const triangle = new THREE.Triangle();
41525
+ const centres = pieces.map((mesh2) => {
41526
+ const geometry = mesh2.geometry.index ? mesh2.geometry.toNonIndexed() : mesh2.geometry;
41527
+ const matrix = toVehicle(mesh2);
41528
+ const pos = geometry.getAttribute("position");
41529
+ const out = new Float32Array(pos.count / 3 * FIELDS);
41530
+ for (let t = 0; t * 3 + 2 < pos.count; t++) {
41531
+ let y = 0;
41532
+ let across = 0;
41533
+ for (let k = 0; k < 3; k++) {
41534
+ corners[k].fromBufferAttribute(pos, t * 3 + k).applyMatrix4(matrix);
41535
+ y += corners[k].y / 3;
41536
+ across += side2 * corners[k].z / 3;
41537
+ }
41538
+ out[t * FIELDS] = y;
41539
+ out[t * FIELDS + 1] = across;
41540
+ triangle.set(corners[0], corners[1], corners[2]).getNormal(v);
41541
+ out[t * FIELDS + 2] = v.y;
41542
+ out[t * FIELDS + 3] = v.x;
41543
+ }
41544
+ return { geometry, out };
41545
+ });
41546
+ const outer = /* @__PURE__ */ new Map();
41547
+ for (const { out } of centres)
41548
+ for (let t = 0; t * FIELDS < out.length; t++) {
41549
+ const band = Math.floor(out[t * FIELDS] / SKIN_BAND);
41550
+ outer.set(band, Math.max(outer.get(band) ?? -Infinity, out[t * FIELDS + 1]));
41551
+ }
41552
+ const surfaceAt = (y) => {
41553
+ const band = Math.floor(y / SKIN_BAND);
41554
+ return Math.max(outer.get(band - 1) ?? -Infinity, outer.get(band) ?? -Infinity, outer.get(band + 1) ?? -Infinity);
41555
+ };
41556
+ return centres.map(({ geometry, out }, i) => {
41557
+ const near = [];
41558
+ for (let t = 0; t * FIELDS < out.length; t++) {
41559
+ const f = t * FIELDS;
41560
+ const body = out[f + 1] < surfaceAt(out[f]) - depth && (out[f + 2] < -BODY_FACING || Math.abs(out[f + 3]) > BODY_FACING);
41561
+ near.push(!body);
41562
+ }
41563
+ const result = { skin: pickTriangles(THREE, geometry, near, true), deep: pickTriangles(THREE, geometry, near, false) };
41564
+ if (geometry !== pieces[i].geometry) geometry.dispose();
41565
+ return result;
41566
+ });
41567
+ }
41568
+ function leaveDeepSkinOnBody(three, meshes, params) {
41569
+ const THREE = three;
41570
+ const pieces = meshes;
41571
+ const toVehicle = params.toVehicle;
41572
+ const { side: side2, depth } = params;
41573
+ const split = splitDoorSkin(THREE, pieces, (mesh2) => new THREE.Matrix4().multiplyMatrices(toVehicle, mesh2.matrixWorld), side2, depth);
41574
+ split.forEach(({ skin, deep }, i) => {
41575
+ const piece = pieces[i];
41576
+ if (!deep || !skin || !piece.parent) return;
41577
+ const body = new THREE.Mesh(deep, piece.material);
41578
+ body.position.copy(piece.position);
41579
+ body.quaternion.copy(piece.quaternion);
41580
+ body.scale.copy(piece.scale);
41581
+ body.castShadow = piece.castShadow;
41582
+ body.receiveShadow = piece.receiveShadow;
41583
+ piece.parent.add(body);
41584
+ piece.geometry.dispose();
41585
+ piece.geometry = skin;
41586
+ });
41587
+ }
41588
+ function pickTriangles(THREE, geometry, flags, wanted) {
41589
+ const count2 = flags.filter((flag) => flag === wanted).length;
41590
+ if (!count2) return null;
41591
+ const out = new THREE.BufferGeometry();
41592
+ for (const name of Object.keys(geometry.attributes)) {
41593
+ const attribute = geometry.getAttribute(name);
41594
+ const size = attribute.itemSize;
41595
+ const source = attribute.array;
41596
+ const array = new Float32Array(count2 * 3 * size);
41597
+ let w = 0;
41598
+ flags.forEach((flag, t) => {
41599
+ if (flag !== wanted) return;
41600
+ array.set(source.subarray(t * 3 * size, (t + 1) * 3 * size), w);
41601
+ w += 3 * size;
41602
+ });
41603
+ out.setAttribute(name, new THREE.Float32BufferAttribute(array, size));
41604
+ }
41605
+ out.computeBoundingBox();
41606
+ out.computeBoundingSphere();
41607
+ return out;
41608
+ }
41609
+ function isDoorSeam(three, params) {
41610
+ const THREE = three;
41611
+ const box2 = params.box;
41612
+ const { zone, side: side2, dark } = params;
41613
+ if (!dark || box2.isEmpty()) return false;
41614
+ const size = box2.getSize(new THREE.Vector3());
41615
+ const centre = box2.getCenter(new THREE.Vector3());
41616
+ if (Math.sign(centre.z) !== Math.sign(side2) || size.x > SEAM_THIN || size.z > SEAM_THIN || size.y < (zone.y1 - zone.y0) * SEAM_TALL) return false;
41617
+ if (centre.y < zone.y0 || centre.y > zone.y1) return false;
41618
+ return Math.abs(centre.x - zone.x0) < SEAM_AT_EDGE || Math.abs(centre.x - zone.x1) < SEAM_AT_EDGE;
41619
+ }
41272
41620
  var HOLE_DECAL_SCALE = 2.4;
41273
41621
  var DOOR_DEPTH = 0.07;
41622
+ var DOOR_SKIN_DEPTH = 0.09;
41623
+ var DOOR_FLOOR_DEPTH = 0.2;
41274
41624
  var DOOR_EDGE_PIECES = 8;
41275
41625
  var DOOR_EDGE_REACH = 0.08;
41276
41626
  var DOOR_CARD_COLOR = "#3b3e42";
41277
41627
  var DOOR_LINER_COLOR = "#5a5e64";
41278
41628
  var DOOR_LINER_DARK = 0.15;
41279
- var DOOR_STRIP_THIN = 0.08;
41629
+ var DOOR_STRIP_THIN = 0.13;
41280
41630
  var DOOR_STRIP_NEAR = 0.04;
41281
41631
  var DOOR_STRIP_TALL = 0.22;
41282
- var STRIP_SMOOTH_DEG = 180;
41283
- var DOOR_STRIP_VERTICES = 2e4;
41632
+ var DOOR_STRIP_VERTICES = 6e4;
41284
41633
  var DOOR_SILL = 0.25;
41285
- var DOOR_STRIP_OVER = 0.3;
41634
+ var DOOR_STRIP_OVER = 0.05;
41286
41635
  var DOOR_TOP_OVER_PANE = 0.03;
41287
41636
  var HOLE_TEXTURE_SIZE = 64;
41288
41637
  var LAMINATED_HP = 1e3;
@@ -41386,6 +41735,8 @@ var GameVehiclesThree = (() => {
41386
41735
  __publicField(this, "syncPlates", /* @__PURE__ */ new Map());
41387
41736
  __publicField(this, "skins", /* @__PURE__ */ new Map());
41388
41737
  __publicField(this, "doorTops", /* @__PURE__ */ new Map());
41738
+ __publicField(this, "seams", /* @__PURE__ */ new Map());
41739
+ __publicField(this, "openNow", /* @__PURE__ */ new Set());
41389
41740
  __publicField(this, "lightsOn", true);
41390
41741
  __publicField(this, "warming");
41391
41742
  __publicField(this, "tornThisFrame", false);
@@ -41562,6 +41913,11 @@ var GameVehiclesThree = (() => {
41562
41913
  }
41563
41914
  panel.visible = open && !this.skins.get(id);
41564
41915
  recess.visible = open && !this.skins.get(id);
41916
+ if (open) this.openNow.add(id);
41917
+ else this.openNow.delete(id);
41918
+ this.seams.forEach((doors, seam) => {
41919
+ if (doors.has(id)) seam.visible = ![...doors].some((door) => this.openNow.has(door));
41920
+ });
41565
41921
  };
41566
41922
  const euler = piv.rotation;
41567
41923
  const quaternion = piv.quaternion;
@@ -41604,11 +41960,13 @@ var GameVehiclesThree = (() => {
41604
41960
  const pcs = I.cutPanel(this.c, z, rect, true, dirty, { anyN: true }).map((x) => x.mesh).filter((m) => !!m);
41605
41961
  this.mend(dirty);
41606
41962
  if (pcs.length) {
41963
+ leaveDeepSkinOnBody(this.THREE, pcs, { toVehicle: inv, side: z.side < 0 ? -1 : 1, depth: DOOR_SKIN_DEPTH });
41607
41964
  const panes = I.windowsOver(this.c, z, inv);
41608
41965
  const seals = panes.map((g) => I.paneSeals.get(g)).filter((s) => !!s);
41609
41966
  const mirrors = this.mirrorsOn(z);
41610
41967
  const frames = this.framesAround(panes, inv);
41611
- objs = [...pcs, ...I.followersIn(this.c, z, inv), ...panes, ...seals, ...mirrors, ...frames];
41968
+ const followers = I.followersIn(this.c, z, inv).filter((o) => !this.takeSeam(o, id, z, inv) && !this.inFloor(o, piv, z, inv));
41969
+ objs = [...pcs, ...followers, ...panes, ...seals, ...mirrors, ...frames];
41612
41970
  }
41613
41971
  } else {
41614
41972
  const pcs = this.cutZone(z);
@@ -41636,6 +41994,25 @@ var GameVehiclesThree = (() => {
41636
41994
  this.c.root.updateMatrixWorld(true);
41637
41995
  return objs.length > 0;
41638
41996
  }
41997
+ /** A thin part at the foot of the opening, well inside the skin (a step, a sill trim, a rail on the floor), is the floor's: it does not swing with the door. */
41998
+ inFloor(o, piv, z, inv) {
41999
+ const side2 = z.side < 0 ? -1 : 1;
42000
+ const centre = this.I.localBox(this.c, o, inv).getCenter(new this.THREE.Vector3());
42001
+ const skin = side2 * (piv.position.z - side2 * 0.016);
42002
+ return centre.y < z.box.y0 + DOOR_SILL && side2 * centre.z < skin - DOOR_FLOOR_DEPTH;
42003
+ }
42004
+ /** A shut line drawn at the door's edge stays on the body, hidden while a door it marks stands open; answers whether `o` is one. */
42005
+ takeSeam(o, id, z, inv) {
42006
+ const B = z.box;
42007
+ const box2 = this.I.localBox(this.c, o, inv);
42008
+ const color2 = o.material?.color;
42009
+ const dark = !!color2 && color2.getHSL({ h: 0, s: 0, l: 0 }).l < DOOR_LINER_DARK;
42010
+ if (!o.isMesh || !isDoorSeam(this.THREE, { box: box2, zone: { x0: B.x0, x1: B.x1, y0: B.y0, y1: B.y1 }, side: z.side, dark })) return false;
42011
+ const doors = this.seams.get(o) ?? /* @__PURE__ */ new Set();
42012
+ doors.add(id);
42013
+ this.seams.set(o, doors);
42014
+ return true;
42015
+ }
41639
42016
  /** A door has edges: a band that follows the skin all round closes it to its card and hides where the cut followed the triangles of the shell; the same band, in the same paint as on a real car, lines the opening. */
41640
42017
  doorEdges(piv, z, y1) {
41641
42018
  const THREE = this.THREE;
@@ -41657,13 +42034,15 @@ var GameVehiclesThree = (() => {
41657
42034
  const at = this.surface("z", side2, B, [[x, y]]);
41658
42035
  return at != null && Math.abs(at - flat) < DOOR_EDGE_REACH ? at : flat;
41659
42036
  });
41660
- const band = (lift) => {
42037
+ const band = (lift, path, closed) => {
41661
42038
  const position = [];
41662
42039
  const index = [];
41663
- loop.forEach(([x, y], i) => {
41664
- position.push(x, y, heights[i] + side2 * lift, x, y, heights[i] + side2 * (lift - DOOR_DEPTH));
42040
+ path.forEach((at, i) => {
42041
+ const [x, y] = loop[at];
42042
+ position.push(x, y, heights[at] + side2 * lift, x, y, heights[at] + side2 * (lift - DOOR_DEPTH));
42043
+ if (!closed && i === path.length - 1) return;
41665
42044
  const a = i * 2;
41666
- const b = (i + 1) % loop.length * 2;
42045
+ const b = (i + 1) % path.length * 2;
41667
42046
  index.push(a, a + 1, b, b, a + 1, b + 1);
41668
42047
  });
41669
42048
  const g = new THREE.BufferGeometry();
@@ -41691,8 +42070,11 @@ var GameVehiclesThree = (() => {
41691
42070
  card.castShadow = false;
41692
42071
  this.c.vehicle.add(card);
41693
42072
  piv.attach(card);
41694
- const edges = new THREE.Mesh(band(2e-3), paint);
41695
- const lining = new THREE.Mesh(band(2e-3), paint);
42073
+ const all = loop.map((_, i) => i);
42074
+ const top = 2 * DOOR_EDGE_PIECES;
42075
+ const sides = [...all.slice(3 * DOOR_EDGE_PIECES), ...all.slice(0, top + 1)];
42076
+ const edges = new THREE.Mesh(band(2e-3, all, true), paint);
42077
+ const lining = new THREE.Mesh(band(2e-3, sides, false), paint);
41696
42078
  for (const m of [edges, lining]) {
41697
42079
  m.userData.noFollow = true;
41698
42080
  m.castShadow = false;
@@ -41749,26 +42131,14 @@ var GameVehiclesThree = (() => {
41749
42131
  const box2 = this.I.localBox(this.c, o, inv);
41750
42132
  const size = box2.getSize(new THREE.Vector3());
41751
42133
  const mid = box2.getCenter(new THREE.Vector3());
41752
- if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box2.max.x, x1) - Math.max(box2.min.x, x0) < (x1 - x0) * DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
42134
+ if (Math.sign(mid.z) !== Math.sign(z.side) || size.z > DOOR_STRIP_THIN || size.y > DOOR_STRIP_TALL || Math.min(box2.max.x, x1) - Math.max(box2.min.x, x0) < DOOR_STRIP_OVER || mid.y < y0 || mid.y > y1) continue;
41753
42135
  const toVehicle = new THREE.Matrix4().multiplyMatrices(inv, o.matrixWorld);
41754
- const v = new THREE.Vector3();
41755
- const sdf = (x, y, zz) => {
41756
- v.set(x, y, zz).applyMatrix4(toVehicle);
41757
- return Math.max(x0 - v.x, v.x - x1);
41758
- };
41759
- const over = new THREE.Mesh(o.geometry.clone(), o.material);
42136
+ const over = cutStripOverDoor(this.kit, { strip: o, toVehicle, x0, x1 });
41760
42137
  over.position.copy(o.position);
41761
42138
  over.quaternion.copy(o.quaternion);
41762
42139
  over.scale.copy(o.scale);
41763
42140
  this.c.vehicle.add(over);
41764
42141
  over.updateMatrixWorld(true);
41765
- const overToVehicle = new THREE.Matrix4().multiplyMatrices(inv, over.matrixWorld);
41766
- const w = new THREE.Vector3();
41767
- this.kit.cutMesh(over, [{ sdf: (x, y, zz) => {
41768
- w.set(x, y, zz).applyMatrix4(overToVehicle);
41769
- return -Math.max(x0 - w.x, w.x - x1);
41770
- } }], STRIP_SMOOTH_DEG, {});
41771
- this.kit.cutMesh(o, [{ sdf }], STRIP_SMOOTH_DEG, {});
41772
42142
  over.userData.noFollow = true;
41773
42143
  piv.attach(over);
41774
42144
  }
@@ -42349,11 +42719,18 @@ var GameVehiclesThree = (() => {
42349
42719
  }
42350
42720
  }
42351
42721
  }
42722
+ /** The lens of a light is holed or cracked and stays; `pierced`: a round went through it to the bulb, and the lamp goes dark. */
42723
+ crackLight(p, pierced) {
42724
+ const z = this.zone(p);
42725
+ if (!z) return;
42726
+ for (const L of z.lights) this.kit.crackLight(this.c, L, new this.THREE.Vector3(p.box.center[0] > 0 ? -1 : 1, 0, 0), pierced);
42727
+ }
42352
42728
  /** The lens of a light breaks and its unit stays (damaged) or goes (destroyed). */
42353
42729
  breakLight(p, gone) {
42354
42730
  const z = this.zone(p);
42355
42731
  if (!z) return;
42356
42732
  for (const L of z.lights) {
42733
+ L.lensMaterial = p.material;
42357
42734
  this.kit.breakLight(this.c, L, new this.THREE.Vector3(p.box.center[0] > 0 ? -1 : 1, 0, 0));
42358
42735
  if (gone) {
42359
42736
  if (L.group) L.group.visible = false;
@@ -42995,6 +43372,7 @@ var GameVehiclesThree = (() => {
42995
43372
  wear(p, amount, out);
42996
43373
  syncZone(p);
42997
43374
  if (p.hp <= 0) lose(p, "broken", out, cause);
43375
+ else if (p.def.role === "light") vis?.crackLight(p.def, dtype === "ballistic");
42998
43376
  if (p.def.role === "tyre" && !p.flat && p.hp < p.def.hp * 0.6 && !p.lost) {
42999
43377
  p.flat = true;
43000
43378
  vis?.tyreFlat(p.def, 0.24);
@@ -43807,7 +44185,7 @@ var GameVehiclesThree = (() => {
43807
44185
  if (!list2.length) return 1;
43808
44186
  return Math.min(...list2.map((p) => p.lost || !attached(p.def.id) ? 0 : p.hp / p.def.hp));
43809
44187
  };
43810
- const tyres = [...ps.values()].filter((p) => p.def.role === "tyre").map((p) => ({ x: p.def.box.center[0], z: p.def.box.center[2], health: tyreHealth(p) }));
44188
+ const tyres = [...ps.values()].filter((p) => p.def.role === "tyre").map((p) => ({ x: p.def.box.center[0], z: p.def.box.center[2], health: tyreHealth(p), rim: p.lost }));
43811
44189
  const status = computeStatus();
43812
44190
  const brakeSystem = [...ps.values()].find((p) => p.def.role === "brakes" && p.def.contains);
43813
44191
  const fluidLeft = brakeSystem ? clamp((brakeSystem.liters ?? 0) / brakeSystem.def.contains.liters / BRAKE_FLUID_NEEDED, 0, 1) : 1;
@@ -44035,7 +44413,7 @@ var GameVehiclesThree = (() => {
44035
44413
  };
44036
44414
  }
44037
44415
 
44038
- // ../game-audio/dist/chunk-BXUGSOPY.js
44416
+ // ../game-audio/dist/chunk-UUWKSG4J.js
44039
44417
  var DISTANCE_CURVES = {
44040
44418
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
44041
44419
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -44558,6 +44936,7 @@ var GameVehiclesThree = (() => {
44558
44936
  burst: 20,
44559
44937
  burstSpacing: 0.06
44560
44938
  };
44939
+ var CATALOG = new Map([...SFX_CATALOG, ...SFX_IMPACTS].map((entry) => [entry.id, entry]));
44561
44940
  var PT = {
44562
44941
  group: "Som",
44563
44942
  name: "Painel de som (arquivos e C1\u2013C10)",
@@ -44632,9 +45011,9 @@ var GameVehiclesThree = (() => {
44632
45011
  strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
44633
45012
  states: { file: "A", synth: "S", missing: "F" },
44634
45013
  criteria: { C1: "C1 Sincronia", C2: "C2 Certo p/ a\xE7\xE3o", C3: "C3 Dist\xE2ncia", C4: "C4 Posi\xE7\xE3o", C5: "C5 Varia\xE7\xE3o", C6: "C6 Volume", C7: "C7 Intensidade", C8: "C8 Cauda", C9: "C9 Rajada", C10: "C10 Escuta" },
44635
- metrics: { source: "1 \xB7 Fonte do que tocou", chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo" },
45014
+ metrics: { source: "1 \xB7 Fonte do que tocou", chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo", unlicensed: "1 \xB7 Arquivo sem licen\xE7a" },
44636
45015
  run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
44637
- words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
45016
+ words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono", local_file: "arquivo do pacote local", sdk_file: "grava\xE7\xE3o do SDK", synth: "s\xEDntese do SDK" },
44638
45017
  sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
44639
45018
  probe: "Fonte do som",
44640
45019
  wall: "Parede (oclus\xE3o)",
@@ -44705,9 +45084,9 @@ var GameVehiclesThree = (() => {
44705
45084
  environments: { indoor: "indoor", outdoor: "outdoor" },
44706
45085
  strengths: { light: "light", medium: "medium", heavy: "heavy" },
44707
45086
  criteria: { C1: "C1 Sync", C2: "C2 Right sound", C3: "C3 Distance", C4: "C4 Position", C5: "C5 Variation", C6: "C6 Level", C7: "C7 Strength", C8: "C8 Tail", C9: "C9 Burst", C10: "C10 Listening" },
44708
- metrics: { source: "1 \xB7 Source of what played", chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue" },
45087
+ metrics: { source: "1 \xB7 Source of what played", chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue", unlicensed: "1 \xB7 File without licence" },
44709
45088
  run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
44710
- words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
45089
+ words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner", local_file: "local pack file", sdk_file: "SDK recording", synth: "SDK synthesis" },
44711
45090
  sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
44712
45091
  probe: "Sound source",
44713
45092
  wall: "Wall (occlusion)",
@@ -44861,9 +45240,137 @@ var GameVehiclesThree = (() => {
44861
45240
  duration: 0.5,
44862
45241
  layers: [{ source: { kind: "osc", wave: "sine", frequency: { set: 880 } }, repeat: { count: 3, spacing: 0.7 }, envelope: [[0, 0.06, "set"], [0.5, 1e-4, "exp", "fixed"]] }],
44863
45242
  falloff: { model: "none" }
45243
+ },
45244
+ {
45245
+ // the parking sensor: a short high beep, repeated by the cabin faster as the obstacle comes close
45246
+ id: "parking_beep",
45247
+ bus: "world",
45248
+ duration: 0.09,
45249
+ layers: [{ source: { kind: "osc", wave: "sine", frequency: { set: 2300 } }, envelope: [[0, 0.07, "set"], [0.08, 1e-4, "exp", "fixed"]] }],
45250
+ falloff: { model: "none" }
45251
+ },
45252
+ {
45253
+ // an emergency siren's wail: a tone rising and falling slowly (one sweep about every 3 s), played again by the cabin as it ends
45254
+ id: "siren_wail",
45255
+ bus: "world",
45256
+ order: "siren",
45257
+ duration: 3,
45258
+ tail: 0.05,
45259
+ layers: [
45260
+ {
45261
+ source: { kind: "osc", wave: "sawtooth", frequency: { set: 1050 } },
45262
+ modulators: [{ target: "frequency", wave: "sine", rate: 0.33, depth: 420 }],
45263
+ filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: 2400 } }] },
45264
+ envelope: [[0, 1e-4, "set", "fixed"], [0.08, 0.07, "linear"], [2.95, 0.07, "set"], [3, 1e-4, "exp", "fixed"]]
45265
+ }
45266
+ ],
45267
+ falloff: { model: "none" }
45268
+ },
45269
+ {
45270
+ // the yelp: the same tone swept fast, for a junction
45271
+ id: "siren_yelp",
45272
+ bus: "world",
45273
+ order: "siren",
45274
+ duration: 2,
45275
+ tail: 0.05,
45276
+ layers: [
45277
+ {
45278
+ source: { kind: "osc", wave: "sawtooth", frequency: { set: 1100 } },
45279
+ modulators: [{ target: "frequency", wave: "triangle", rate: 4, depth: 380 }],
45280
+ filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: 2600 } }] },
45281
+ envelope: [[0, 1e-4, "set", "fixed"], [0.05, 0.07, "linear"], [1.95, 0.07, "set"], [2, 1e-4, "exp", "fixed"]]
45282
+ }
45283
+ ],
45284
+ falloff: { model: "none" }
45285
+ },
45286
+ {
45287
+ // the wires under the column touching: a crackle of bright noise
45288
+ id: "hotwire_spark",
45289
+ bus: "world",
45290
+ duration: 0.12,
45291
+ layers: [
45292
+ {
45293
+ source: { kind: "noise", offset: "random" },
45294
+ filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3200 } }] },
45295
+ modulators: [{ target: "gain", wave: "square", rate: 60, depth: 0.1, base: 0.12 }],
45296
+ envelope: [[0, 0.2, "set"], [0.12, 1e-4, "exp", "fixed"]]
45297
+ }
45298
+ ],
45299
+ falloff: { model: "none" }
45300
+ },
45301
+ {
45302
+ // the glovebox latch: a small plastic click and the lid dropping
45303
+ id: "glovebox_latch",
45304
+ bus: "world",
45305
+ duration: 0.14,
45306
+ layers: [
45307
+ { source: { kind: "noise", offset: "random" }, filters: { mode: "serial", bank: [{ type: "bandpass", frequency: { set: 1800 }, q: 2 }] }, envelope: [[0, 0.16, "set"], [0.03, 1e-4, "exp", "fixed"]] },
45308
+ { source: { kind: "osc", wave: "triangle", frequency: { set: 240 } }, envelope: [[0, 1e-4, "set", "fixed"], [0.06, 0.08, "linear"], [0.14, 1e-4, "exp", "fixed"]] }
45309
+ ],
45310
+ falloff: { model: "none" }
44864
45311
  }
44865
45312
  ];
44866
45313
  var CABIN_BANK = { id: "vehicle_cabin", sounds: CABIN_SOUNDS, loops: [] };
45314
+ var hz = (midi) => 440 * 2 ** ((midi - 69) / 12);
45315
+ var layer = (note, beat, wave, level) => {
45316
+ const [at, midi, beats] = note;
45317
+ const length = beats * beat;
45318
+ return {
45319
+ at: at * beat,
45320
+ duration: length,
45321
+ source: { kind: "osc", wave, frequency: { set: hz(midi) } },
45322
+ envelope: [[0, 1e-4, "set"], [0.01, level, "linear"], [Math.max(0.02, length * 0.9), level * 0.35, "linear"], [length, 1e-4, "exp", "fixed"]]
45323
+ };
45324
+ };
45325
+ function radioTrack(spec) {
45326
+ const beat = 60 / spec.bpm;
45327
+ const recipes = Array.from({ length: spec.bars }, (_, bar) => {
45328
+ const inBar = (notes) => notes.filter((n) => n[0] >= bar * 4 && n[0] < bar * 4 + 4).map((n) => [n[0] - bar * 4, n[1], n[2]]);
45329
+ const layers2 = [
45330
+ ...inBar(spec.bass).map((n) => layer(n, beat, "triangle", 0.16)),
45331
+ ...inBar(spec.chords).map((n) => layer(n, beat, "sine", 0.05)),
45332
+ ...inBar(spec.melody).map((n) => layer(n, beat, spec.wave, 0.07))
45333
+ ];
45334
+ return { id: `${spec.id}_${bar}`, bus: "world", duration: 4 * beat, layers: layers2, falloff: { model: "none" } };
45335
+ });
45336
+ return { song: { id: spec.id, barSeconds: 4 * beat, bars: recipes.map((r) => r.id) }, recipes };
45337
+ }
45338
+ var chord = (bar, root, minor = false) => [[bar * 4, root, 4], [bar * 4, root + (minor ? 3 : 4), 4], [bar * 4, root + 7, 4]];
45339
+ var bassLine = (roots, pattern) => roots.flatMap((root, bar) => pattern.map((b) => [bar * 4 + b, root - 12, 0.9]));
45340
+ var TRACKS = [
45341
+ // a bright pop loop: I–V–vi–IV in C
45342
+ radioTrack({
45343
+ id: "radio_track_pop",
45344
+ bpm: 112,
45345
+ bars: 4,
45346
+ wave: "square",
45347
+ bass: bassLine([48, 55, 57, 53], [0, 1.5, 2, 3]),
45348
+ chords: [...chord(0, 60), ...chord(1, 55), ...chord(2, 57, true), ...chord(3, 53)],
45349
+ melody: [[0, 76, 1], [1, 74, 0.5], [1.5, 72, 1.5], [4, 74, 1], [5, 76, 1], [6, 79, 2], [8, 76, 1], [9, 74, 1], [10, 72, 2], [12, 72, 1], [13, 74, 1], [14, 72, 2]]
45350
+ }),
45351
+ // a syncopated loop in A minor, the bass on the off-beats
45352
+ radioTrack({
45353
+ id: "radio_track_groove",
45354
+ bpm: 100,
45355
+ bars: 4,
45356
+ wave: "triangle",
45357
+ bass: bassLine([57, 62, 64, 57], [0, 0.75, 1.5, 2.5, 3.25]),
45358
+ chords: [...chord(0, 57, true), ...chord(1, 62, true), ...chord(2, 64), ...chord(3, 57, true)],
45359
+ melody: [[0.5, 72, 0.5], [1.25, 76, 0.75], [2.5, 74, 1], [4.5, 77, 0.5], [5.25, 74, 0.75], [6.5, 72, 1.5], [8.5, 71, 0.5], [9.25, 74, 0.75], [10.5, 76, 1.5], [12.5, 72, 0.5], [13.25, 69, 2]]
45360
+ }),
45361
+ // a slow blues-like loop in F with a swung melody
45362
+ radioTrack({
45363
+ id: "radio_track_slow",
45364
+ bpm: 76,
45365
+ bars: 4,
45366
+ wave: "sine",
45367
+ bass: bassLine([53, 58, 53, 60], [0, 2]),
45368
+ chords: [...chord(0, 65), ...chord(1, 58), ...chord(2, 65), ...chord(3, 60)],
45369
+ melody: [[0, 72, 1.5], [1.67, 75, 0.33], [2, 77, 2], [4, 74, 1], [5, 72, 1], [6, 70, 2], [8, 72, 1.5], [9.67, 69, 0.33], [10, 65, 2], [12, 67, 1], [13, 70, 1], [14, 72, 2]]
45370
+ })
45371
+ ];
45372
+ var RADIO_TRACKS = TRACKS.map((t) => t.song);
45373
+ var RADIO_BANK = { id: "vehicle_radio", sounds: TRACKS.flatMap((t) => t.recipes), loops: [] };
44867
45374
  var FOLEY_FALLOFF = {
44868
45375
  model: "inverse_square",
44869
45376
  k: 0.02,
@@ -45470,11 +45977,11 @@ var GameVehiclesThree = (() => {
45470
45977
  ...s.splash ? [sceneNoise({ type: "bandpass", f: s.splash.f * take.f, f2: s.splash.f2 * take.f, q: 1.4, d: s.splash.d * take.t * weight ** 0.4, vol: s.splash.vol * gait.vol * take.v * weight ** 0.5 })] : []
45471
45978
  ];
45472
45979
  }
45473
- var quieter = (level, layers2) => layers2.map((layer) => {
45474
- const gain = layer.gain ?? 1;
45475
- if (typeof gain === "number") return { ...layer, gain: gain * level };
45476
- if (Array.isArray(gain)) return { ...layer, gain: [gain[0] * level, gain[1] * level] };
45477
- return layer;
45980
+ var quieter = (level, layers2) => layers2.map((layer2) => {
45981
+ const gain = layer2.gain ?? 1;
45982
+ if (typeof gain === "number") return { ...layer2, gain: gain * level };
45983
+ if (Array.isArray(gain)) return { ...layer2, gain: [gain[0] * level, gain[1] * level] };
45984
+ return layer2;
45478
45985
  });
45479
45986
  var recipe = (id, layers2, falloff, bus = "world") => ({ id, bus, pan: true, layers: layers2, falloff, sources: ["sdk"] });
45480
45987
  var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
@@ -45490,7 +45997,7 @@ var GameVehiclesThree = (() => {
45490
45997
  (take) => quieter(LANDING_LEVEL, [
45491
45998
  // both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
45492
45999
  ...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
45493
- ...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer) => ({ ...layer, at: (typeof layer.at === "number" ? layer.at : 0) + 0.018 * take.t })),
46000
+ ...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer2) => ({ ...layer2, at: (typeof layer2.at === "number" ? layer2.at : 0) + 0.018 * take.t })),
45494
46001
  sceneTone({ f: 60 * take.f, f2: 38, d: 0.14 * LANDINGS[landing2].weight * take.t, vol: 0.22 * LANDINGS[landing2].weight ** 0.7 * take.v, delay: 0.02 }),
45495
46002
  sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
45496
46003
  ]),
@@ -45536,7 +46043,7 @@ var GameVehiclesThree = (() => {
45536
46043
  fall_damage: (take) => [
45537
46044
  ...BODY.body_fall(take),
45538
46045
  sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
45539
- ...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer) => ({ ...layer, at: 0.08 }))
46046
+ ...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer2) => ({ ...layer2, at: 0.08 }))
45540
46047
  ],
45541
46048
  player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
45542
46049
  player_breath_run: (take) => [
@@ -45594,6 +46101,109 @@ var GameVehiclesThree = (() => {
45594
46101
  };
45595
46102
  var BODY_FOLEY_SET_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
45596
46103
 
46104
+ // src/audio/engine-mix.ts
46105
+ var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
46106
+ var firingHz = (profile, rpm) => rpm / 60 * (profile.cylinders / 2);
46107
+ var rpmShare = (profile, rpm) => clamp01((rpm - profile.idleRpm) / (profile.redlineRpm - profile.idleRpm));
46108
+ var layerWeight = (centre, width, n) => {
46109
+ const d = Math.abs(n - centre) / width;
46110
+ return d >= 1 ? 0 : Math.cos(d * Math.PI / 2);
46111
+ };
46112
+ function engineMix(profile, input) {
46113
+ const throttle = clamp01(input.throttle);
46114
+ const on = input.rpm > 0;
46115
+ const n = rpmShare(profile, input.rpm);
46116
+ const level = on ? profile.closedThrottle + (1 - profile.closedThrottle) * throttle : 0;
46117
+ const open = 0.5 * n + 0.5 * throttle;
46118
+ const hz2 = firingHz(profile, input.rpm);
46119
+ const tones = profile.layers.map((layer2) => ({
46120
+ sound: layer2.sound,
46121
+ volume: layer2.gain * level * layerWeight(layer2.centre, layer2.width, n),
46122
+ frequency: hz2 * layer2.harmonic,
46123
+ cutoff: layer2.cutoff[0] + (layer2.cutoff[1] - layer2.cutoff[0]) * open
46124
+ }));
46125
+ const load = profile.load;
46126
+ return [
46127
+ ...tones,
46128
+ { sound: load.sound, volume: on ? load.gain * (0.15 + 0.85 * throttle) * (0.35 + 0.65 * n) : 0, cutoff: load.cutoff[0] + (load.cutoff[1] - load.cutoff[0]) * open }
46129
+ ];
46130
+ }
46131
+ function createGearGuess(profile) {
46132
+ let gear = 0;
46133
+ return {
46134
+ rpm(speed, throttle) {
46135
+ const v = Math.abs(speed);
46136
+ const ratios = profile.rpmPerMps;
46137
+ if (v < 0.3) {
46138
+ gear = 0;
46139
+ return profile.idleRpm + (profile.redlineRpm - profile.idleRpm) * 0.12 * clamp01(throttle);
46140
+ }
46141
+ const at = (g) => v * ratios[g];
46142
+ while (gear < ratios.length - 1 && at(gear) > profile.redlineRpm * 0.85) gear++;
46143
+ while (gear > 0 && at(gear) < profile.redlineRpm * 0.4) gear--;
46144
+ return Math.max(profile.idleRpm, Math.min(profile.redlineRpm, at(gear)));
46145
+ },
46146
+ get gear() {
46147
+ return gear;
46148
+ }
46149
+ };
46150
+ }
46151
+
46152
+ // src/audio/profiles.ts
46153
+ var DEFAULT_VEHICLE_SOUND_KEYS = {
46154
+ start: "engine_start",
46155
+ stop: "engine_stop",
46156
+ fail: "engine_fail",
46157
+ crashLight: "crash_light",
46158
+ crashMedium: "crash_medium",
46159
+ crashHeavy: "crash_heavy",
46160
+ skidAsphalt: "tire_skid_asphalt",
46161
+ skidDirt: "tire_skid_dirt",
46162
+ roll: "tire_roll",
46163
+ brakeSqueal: "brake_squeal",
46164
+ hitBody: "vehicle_hit_pedestrian",
46165
+ siren: "siren",
46166
+ rotor: "helicopter_rotor_loop"
46167
+ };
46168
+ var CRASH_SPEEDS = { medium: 6, heavy: 14 };
46169
+ var layers = (id, cutoffs, gains) => [
46170
+ { sound: `engine_${id}_low`, centre: 0, width: 0.5, harmonic: 1, gain: gains[0], cutoff: cutoffs[0] },
46171
+ { sound: `engine_${id}_mid`, centre: 0.45, width: 0.5, harmonic: 1, gain: gains[1], cutoff: cutoffs[1] },
46172
+ { sound: `engine_${id}_high`, centre: 1, width: 0.6, harmonic: 2, gain: gains[2], cutoff: cutoffs[2] }
46173
+ ];
46174
+ var CAR_ENGINE = {
46175
+ id: "car",
46176
+ cylinders: 4,
46177
+ idleRpm: 800,
46178
+ redlineRpm: 6500,
46179
+ // a 5-speed hatch: 1st tops at ~13 m/s, 5th at ~50 m/s
46180
+ rpmPerMps: [500, 300, 205, 155, 125],
46181
+ layers: layers("car", [[220, 520], [420, 1300], [900, 2600]], [0.11, 0.075, 0.04]),
46182
+ load: { sound: "engine_car_load", gain: 0.05, cutoff: [350, 1600] },
46183
+ closedThrottle: 0.55
46184
+ };
46185
+ var TRUCK_ENGINE = {
46186
+ id: "truck",
46187
+ cylinders: 6,
46188
+ idleRpm: 650,
46189
+ redlineRpm: 2600,
46190
+ rpmPerMps: [420, 260, 170, 120, 90, 72],
46191
+ layers: layers("truck", [[160, 380], [300, 800], [600, 1500]], [0.13, 0.08, 0.035]),
46192
+ load: { sound: "engine_truck_load", gain: 0.06, cutoff: [250, 900] },
46193
+ closedThrottle: 0.6
46194
+ };
46195
+ var BIKE_ENGINE = {
46196
+ id: "bike",
46197
+ cylinders: 4,
46198
+ idleRpm: 1300,
46199
+ redlineRpm: 12e3,
46200
+ rpmPerMps: [900, 640, 500, 410, 350, 310],
46201
+ layers: layers("bike", [[350, 900], [700, 2200], [1500, 4200]], [0.08, 0.07, 0.05]),
46202
+ load: { sound: "engine_bike_load", gain: 0.045, cutoff: [600, 2600] },
46203
+ closedThrottle: 0.5
46204
+ };
46205
+ var ENGINE_PROFILES = { car: CAR_ENGINE, truck: TRUCK_ENGINE, bike: BIKE_ENGINE };
46206
+
45597
46207
  // src/audio/bank.ts
45598
46208
  var VEHICLE_LOOP_FALLOFF = { model: "inverse_square", k: 4e-3, cutoffK: 0.01 };
45599
46209
  var SDK = "sdk: vehicle sounds (sons/checklist-sons.md 5.6)";
@@ -45632,7 +46242,16 @@ var GameVehiclesThree = (() => {
45632
46242
  ...engineLoops("bike", 600, 1400, 2800, 1200),
45633
46243
  noiseLoop("tire_skid_asphalt", "bandpass", 1900, 5, "rubber squealing on asphalt: a narrow band of noise near 2 kHz"),
45634
46244
  noiseLoop("tire_skid_dirt", "lowpass", 700, 0.8, "tyres sliding on dirt and gravel: a dull, wide hiss"),
45635
- noiseLoop("tire_roll", "lowpass", 400, 0.7, "road noise of the tyres rolling, brighter with the speed")
46245
+ noiseLoop("tire_roll", "lowpass", 400, 0.7, "road noise of the tyres rolling, brighter with the speed"),
46246
+ // a pad on a disc: a thin, nearly pure whistle near 3.4 kHz (a narrow band of noise, so it wavers as a real squeal does)
46247
+ // a siren: a square through a low-pass, its pitch swept by the vehicle every frame (wail: a slow rise and fall; yelp: a fast one)
46248
+ toneLoop("siren", "square", "lowpass", 2600, 0.8, "emergency siren, the sweep driven by the vehicle"),
46249
+ // the blades: a low rumble chopped at the blade-pass rate (≈ 18 Hz for a light helicopter's main rotor)
46250
+ {
46251
+ ...noiseLoop("helicopter_rotor_loop", "lowpass", 320, 0.9, "helicopter rotor: low rumble chopped by the blades"),
46252
+ modulators: [{ target: "gain", wave: "sine", rate: 18, depth: 0.45, base: 0.55 }]
46253
+ },
46254
+ noiseLoop("brake_squeal", "bandpass", 3400, 28, "brake pads squealing on the discs as the car comes to a stop")
45636
46255
  ];
45637
46256
  var recipe2 = (id, layers2, line) => ({ ...sceneRecipe(id, layers2, line), sources: [SDK, line] });
45638
46257
  var crank = (count2, spacing) => [
@@ -45676,6 +46295,17 @@ var GameVehiclesThree = (() => {
45676
46295
  ],
45677
46296
  "crash above 50 km/h: metal folding, the glass going, debris falling"
45678
46297
  ),
46298
+ // a body hit by a car: the dull slap of the bumper on a person, the body folding over the bonnet, at speed a crack of the windscreen
46299
+ recipe2(
46300
+ "vehicle_hit_pedestrian",
46301
+ [
46302
+ sceneTone({ f: 95, f2: 50, d: 0.14, vol: 0.9 }),
46303
+ sceneNoise({ type: "lowpass", f: 600, d: 0.12, vol: 0.8 }),
46304
+ sceneNoise({ type: "bandpass", f: 280, q: 1.2, d: 0.18, vol: 0.55, delay: 0.09 }),
46305
+ sceneNoise({ type: "highpass", f: 2600, d: 0.05, vol: 0.18, delay: 0.1 })
46306
+ ],
46307
+ "a person hit by a car: the bumper slap, the body on the bonnet"
46308
+ ),
45679
46309
  // the game's horn (two squares at 415 and 523 Hz, jogo L70272) heard from where the car is; a truck's air horn lower and longer
45680
46310
  recipe2("horn_car", [sceneTone({ wave: "square", f: 415, d: 0.6, vol: 0.09 }), sceneTone({ wave: "square", f: 523, d: 0.6, vol: 0.09 })], "car horn: the game chord, placed and panned"),
45681
46311
  recipe2("horn_truck", [sceneTone({ wave: "sawtooth", f: 220, d: 1.1, vol: 0.1 }), sceneTone({ wave: "sawtooth", f: 277, d: 1.1, vol: 0.08 }), sceneNoise({ type: "bandpass", f: 900, q: 2, d: 1.1, vol: 0.05 })], "truck air horn: low reedy chord"),
@@ -45685,107 +46315,65 @@ var GameVehiclesThree = (() => {
45685
46315
  recipe2("car_trunk_close", [sceneTone({ f: 90, f2: 55, d: 0.15, vol: 0.65 }), sceneNoise({ type: "bandpass", f: 500, q: 1, d: 0.12, vol: 0.5 }), ...latch(0.01, 0.28), sceneNoise({ type: "bandpass", f: 1800, q: 3, d: 0.04, vol: 0.1, delay: 0.06 })], "trunk or hatch shut: a lighter thump, the latch and a small rattle"),
45686
46316
  recipe2("car_hood_close", [sceneTone({ f: 70, f2: 40, d: 0.22, vol: 0.8 }), sceneNoise({ type: "bandpass", f: 850, q: 1.4, d: 0.2, vol: 0.6, wet: 0.3 }), sceneTone({ f: 900, f2: 860, d: 0.18, vol: 0.05 }), ...latch(0.015, 0.3)], "hood slammed: a big sheet of metal ringing a little, the latch")
45687
46317
  ];
45688
- var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS };
45689
-
45690
- // src/audio/engine-mix.ts
45691
- var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
45692
- var firingHz = (profile, rpm) => rpm / 60 * (profile.cylinders / 2);
45693
- var rpmShare = (profile, rpm) => clamp01((rpm - profile.idleRpm) / (profile.redlineRpm - profile.idleRpm));
45694
- var layerWeight = (centre, width, n) => {
45695
- const d = Math.abs(n - centre) / width;
45696
- return d >= 1 ? 0 : Math.cos(d * Math.PI / 2);
46318
+ var CAR_RECORDED_RPM = { idle: 1e3, high: 2520 };
46319
+ var CAR_START_RUN_STOP = {
46320
+ /** The take the times below were measured on. */
46321
+ take: "_01.",
46322
+ /** The starter and the engine catching (at 0.58 s) and flaring; the loops take over at the crank time of the vehicle. */
46323
+ start: { offset: 0, duration: 1.3 },
46324
+ /** The ignition off: the engine running down to the end of the file. */
46325
+ stop: { offset: 9.96, duration: 0.64 }
45697
46326
  };
45698
- function engineMix(profile, input) {
45699
- const throttle = clamp01(input.throttle);
45700
- const on = input.rpm > 0;
45701
- const n = rpmShare(profile, input.rpm);
45702
- const level = on ? profile.closedThrottle + (1 - profile.closedThrottle) * throttle : 0;
45703
- const open = 0.5 * n + 0.5 * throttle;
45704
- const hz = firingHz(profile, input.rpm);
45705
- const tones = profile.layers.map((layer) => ({
45706
- sound: layer.sound,
45707
- volume: layer.gain * level * layerWeight(layer.centre, layer.width, n),
45708
- frequency: hz * layer.harmonic,
45709
- cutoff: layer.cutoff[0] + (layer.cutoff[1] - layer.cutoff[0]) * open
45710
- }));
45711
- const load = profile.load;
45712
- return [
45713
- ...tones,
45714
- { sound: load.sound, volume: on ? load.gain * (0.15 + 0.85 * throttle) * (0.35 + 0.65 * n) : 0, cutoff: load.cutoff[0] + (load.cutoff[1] - load.cutoff[0]) * open }
45715
- ];
45716
- }
45717
- function createGearGuess(profile) {
45718
- let gear = 0;
46327
+ var carLayer = (id, file, rpm, harmonic, offset) => ({
46328
+ id,
46329
+ file,
46330
+ loop: true,
46331
+ // the layer's oscillator plays the firing frequency times its harmonic: the file sounds at that frequency for the RPM it was recorded at
46332
+ baseFrequency: firingHz(CAR_ENGINE, rpm) * harmonic,
46333
+ falloff: VEHICLE_LOOP_FALLOFF,
46334
+ ...offset === void 0 ? {} : { offset }
46335
+ });
46336
+ var VEHICLE_SAMPLE_ALIASES = [
46337
+ carLayer("engine_car_low", "engine_loop_car_idle", CAR_RECORDED_RPM.idle, 1),
46338
+ carLayer("engine_car_mid", "engine_loop_car_high", CAR_RECORDED_RPM.high, 1),
46339
+ // the same file as the mid layer, read from two seconds in, so the two do not sum in phase where they cross
46340
+ carLayer("engine_car_high", "engine_loop_car_high", CAR_RECORDED_RPM.high, 2, 2),
46341
+ // the cut points are of take _01 (10.6 s); _02 (21.9 s) starts already running and idles at 9.96 s, so it is left out of both
46342
+ { id: "engine_start", file: "engine_start_run_stop", only: CAR_START_RUN_STOP.take, ...CAR_START_RUN_STOP.start, falloff: VEHICLE_LOOP_FALLOFF },
46343
+ { id: "engine_stop", file: "engine_start_run_stop", only: CAR_START_RUN_STOP.take, ...CAR_START_RUN_STOP.stop, falloff: VEHICLE_LOOP_FALLOFF },
46344
+ { id: "car_door_close", file: "car_door_shut" },
46345
+ { id: "car_trunk_close", file: "car_trunk_shut" }
46346
+ ];
46347
+ var VEHICLE_PRELOAD_SOUNDS = [...VEHICLE_SAMPLE_ALIASES.map((alias) => alias.id), "car_door_open"];
46348
+ var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS, sampleAliases: VEHICLE_SAMPLE_ALIASES };
46349
+
46350
+ // src/audio/engine-budget.ts
46351
+ var distanceSq = (a, b) => (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2;
46352
+ function createEngineBudget(options) {
46353
+ const max = Math.max(1, options.max ?? 6);
46354
+ const places = /* @__PURE__ */ new Map();
45719
46355
  return {
45720
- rpm(speed, throttle) {
45721
- const v = Math.abs(speed);
45722
- const ratios = profile.rpmPerMps;
45723
- if (v < 0.3) {
45724
- gear = 0;
45725
- return profile.idleRpm + (profile.redlineRpm - profile.idleRpm) * 0.12 * clamp01(throttle);
46356
+ admit(vehicle, position) {
46357
+ const own = distanceSq(position, options.listener());
46358
+ places.set(vehicle, own);
46359
+ if (places.size <= max) return true;
46360
+ let nearer = 0;
46361
+ for (const [other, d] of places) {
46362
+ if (other === vehicle) continue;
46363
+ if (d < own) nearer++;
46364
+ if (nearer >= max) return false;
45726
46365
  }
45727
- const at = (g) => v * ratios[g];
45728
- while (gear < ratios.length - 1 && at(gear) > profile.redlineRpm * 0.85) gear++;
45729
- while (gear > 0 && at(gear) < profile.redlineRpm * 0.4) gear--;
45730
- return Math.max(profile.idleRpm, Math.min(profile.redlineRpm, at(gear)));
46366
+ return true;
45731
46367
  },
45732
- get gear() {
45733
- return gear;
46368
+ release(vehicle) {
46369
+ places.delete(vehicle);
46370
+ },
46371
+ get size() {
46372
+ return places.size;
45734
46373
  }
45735
46374
  };
45736
46375
  }
45737
46376
 
45738
- // src/audio/profiles.ts
45739
- var DEFAULT_VEHICLE_SOUND_KEYS = {
45740
- start: "engine_start",
45741
- stop: "engine_stop",
45742
- fail: "engine_fail",
45743
- crashLight: "crash_light",
45744
- crashMedium: "crash_medium",
45745
- crashHeavy: "crash_heavy",
45746
- skidAsphalt: "tire_skid_asphalt",
45747
- skidDirt: "tire_skid_dirt",
45748
- roll: "tire_roll"
45749
- };
45750
- var CRASH_SPEEDS = { medium: 6, heavy: 14 };
45751
- var layers = (id, cutoffs, gains) => [
45752
- { sound: `engine_${id}_low`, centre: 0, width: 0.5, harmonic: 1, gain: gains[0], cutoff: cutoffs[0] },
45753
- { sound: `engine_${id}_mid`, centre: 0.45, width: 0.5, harmonic: 1, gain: gains[1], cutoff: cutoffs[1] },
45754
- { sound: `engine_${id}_high`, centre: 1, width: 0.6, harmonic: 2, gain: gains[2], cutoff: cutoffs[2] }
45755
- ];
45756
- var CAR_ENGINE = {
45757
- id: "car",
45758
- cylinders: 4,
45759
- idleRpm: 800,
45760
- redlineRpm: 6500,
45761
- // a 5-speed hatch: 1st tops at ~13 m/s, 5th at ~50 m/s
45762
- rpmPerMps: [500, 300, 205, 155, 125],
45763
- layers: layers("car", [[220, 520], [420, 1300], [900, 2600]], [0.11, 0.075, 0.04]),
45764
- load: { sound: "engine_car_load", gain: 0.05, cutoff: [350, 1600] },
45765
- closedThrottle: 0.55
45766
- };
45767
- var TRUCK_ENGINE = {
45768
- id: "truck",
45769
- cylinders: 6,
45770
- idleRpm: 650,
45771
- redlineRpm: 2600,
45772
- rpmPerMps: [420, 260, 170, 120, 90, 72],
45773
- layers: layers("truck", [[160, 380], [300, 800], [600, 1500]], [0.13, 0.08, 0.035]),
45774
- load: { sound: "engine_truck_load", gain: 0.06, cutoff: [250, 900] },
45775
- closedThrottle: 0.6
45776
- };
45777
- var BIKE_ENGINE = {
45778
- id: "bike",
45779
- cylinders: 4,
45780
- idleRpm: 1300,
45781
- redlineRpm: 12e3,
45782
- rpmPerMps: [900, 640, 500, 410, 350, 310],
45783
- layers: layers("bike", [[350, 900], [700, 2200], [1500, 4200]], [0.08, 0.07, 0.05]),
45784
- load: { sound: "engine_bike_load", gain: 0.045, cutoff: [600, 2600] },
45785
- closedThrottle: 0.5
45786
- };
45787
- var ENGINE_PROFILES = { car: CAR_ENGINE, truck: TRUCK_ENGINE, bike: BIKE_ENGINE };
45788
-
45789
46377
  // src/audio/vehicle-sound.ts
45790
46378
  var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
45791
46379
  function createVehicleSound(audio, options = {}) {
@@ -45794,8 +46382,10 @@ var GameVehiclesThree = (() => {
45794
46382
  const crankSeconds = options.crankSeconds ?? 0.75;
45795
46383
  const gears = createGearGuess(profile);
45796
46384
  const loops = /* @__PURE__ */ new Map();
46385
+ const budget = options.budget;
46386
+ const self = {};
45797
46387
  const levels = {};
45798
- const state = { running: false, cranking: 0, catches: false, flare: 0, rpm: 0 };
46388
+ const state = { running: false, cranking: 0, catches: false, flare: 0, rpm: 0, sirenPhase: 0 };
45799
46389
  const loopOf = (sound) => {
45800
46390
  let handle = loops.get(sound);
45801
46391
  if (!handle && audio?.loop) {
@@ -45829,6 +46419,11 @@ var GameVehiclesThree = (() => {
45829
46419
  play(kind === "heavy" ? keys.crashHeavy : kind === "medium" ? keys.crashMedium : keys.crashLight, position, Math.min(1, 0.35 + v / 20));
45830
46420
  return kind;
45831
46421
  },
46422
+ hit(speed, position, sound) {
46423
+ const v = Math.abs(speed);
46424
+ if (v < 0.5) return;
46425
+ play(sound ?? keys.hitBody, position, Math.min(1, 0.3 + v / 15));
46426
+ },
45832
46427
  update(frame) {
45833
46428
  if (state.cranking > 0) {
45834
46429
  state.cranking -= frame.dt;
@@ -45842,20 +46437,33 @@ var GameVehiclesThree = (() => {
45842
46437
  const driven = frame.rpm ?? gears.rpm(frame.speed, throttle);
45843
46438
  const rpm = state.running ? Math.max(driven, profile.idleRpm * (1 + 1.2 * state.flare * state.flare)) : 0;
45844
46439
  state.rpm = rpm;
45845
- for (const layer of engineMix(profile, { rpm, throttle: Math.max(throttle, state.flare * 0.6) })) {
45846
- setLoop(layer.sound, layer.volume, frame.position, { ...layer.frequency === void 0 ? {} : { frequency: layer.frequency }, cutoff: layer.cutoff });
46440
+ const heard = budget ? budget.admit(self, frame.position) ? 1 : 0 : 1;
46441
+ for (const layer2 of engineMix(profile, { rpm, throttle: Math.max(throttle, state.flare * 0.6) })) {
46442
+ setLoop(layer2.sound, heard * layer2.volume, frame.position, { ...layer2.frequency === void 0 ? {} : { frequency: layer2.frequency }, cutoff: layer2.cutoff });
45847
46443
  }
45848
46444
  const speed = Math.abs(frame.speed);
45849
46445
  const slide = clamp012(((frame.slip ?? 0) - 0.15) / 0.5) * clamp012(speed / 4);
45850
46446
  const dirt = frame.surface === "dirt";
45851
- setLoop(keys.skidAsphalt, dirt ? 0 : 0.16 * slide, frame.position, { cutoff: 1700 + 600 * slide });
45852
- setLoop(keys.skidDirt, dirt ? 0.2 * slide : 0, frame.position, { cutoff: 500 + 500 * slide });
45853
- setLoop(keys.roll, 0.06 * clamp012(speed / 25), frame.position, { cutoff: 250 + 45 * Math.min(speed, 40) });
46447
+ setLoop(keys.skidAsphalt, heard * (dirt ? 0 : 0.16 * slide), frame.position, { cutoff: 1700 + 600 * slide });
46448
+ setLoop(keys.skidDirt, heard * (dirt ? 0.2 * slide : 0), frame.position, { cutoff: 500 + 500 * slide });
46449
+ setLoop(keys.roll, heard * 0.06 * clamp012(speed / 25), frame.position, { cutoff: 250 + 45 * Math.min(speed, 40) });
46450
+ const squeal = clamp012(((frame.brake ?? 0) - 0.3) / 0.4) * clamp012(speed / 0.8) * clamp012((5 - speed) / 2) * (slide > 0 ? 0 : 1);
46451
+ setLoop(keys.brakeSqueal, heard * 0.05 * squeal, frame.position, { cutoff: 3200 + 400 * squeal });
46452
+ const siren = frame.siren ?? "off";
46453
+ if (siren !== "off" || loops.has(keys.siren)) {
46454
+ const period = siren === "yelp" ? 0.25 : 4;
46455
+ state.sirenPhase = (state.sirenPhase + frame.dt / period) % 1;
46456
+ const tri = state.sirenPhase < 0.5 ? state.sirenPhase * 2 : 2 - state.sirenPhase * 2;
46457
+ setLoop(keys.siren, heard * (siren === "off" ? 0 : 0.07), frame.position, { frequency: 650 + 850 * tri, cutoff: 2600 });
46458
+ }
46459
+ const rotor = clamp012(frame.rotor ?? 0);
46460
+ setLoop(keys.rotor, heard * 0.12 * rotor, frame.position, { cutoff: 180 + 220 * rotor });
45854
46461
  },
45855
46462
  get reading() {
45856
46463
  return { running: state.running, rpm: state.rpm, levels: { ...levels } };
45857
46464
  },
45858
46465
  dispose() {
46466
+ budget?.release(self);
45859
46467
  for (const handle of loops.values()) handle.stop();
45860
46468
  loops.clear();
45861
46469
  }
@@ -46245,7 +46853,15 @@ var GameVehiclesThree = (() => {
46245
46853
  var clamp22 = (x, a, b) => Math.max(a, Math.min(b, x));
46246
46854
  var MAX_SUBSTEP = 1 / 240;
46247
46855
  var FLAT_BELOW = 0.2;
46856
+ var RIM_RADIUS_SHARE = 0.7;
46857
+ var RIM_FRICTION = 0.4;
46858
+ var RIM_ROLLING = 0.25;
46859
+ var FLAT_SAG = 0.05;
46860
+ var FLAT_ROLLING = 6;
46248
46861
  var STALL_HEALTH = 0.3;
46862
+ var CLUTCH_BITE = 0.7;
46863
+ var STALL_RPM = 0.5;
46864
+ var STALL_GAS = 0.35;
46249
46865
  var defaultSemiSpec = {
46250
46866
  mass: 1300,
46251
46867
  centreHeight: 0.5,
@@ -46386,7 +47002,14 @@ var GameVehiclesThree = (() => {
46386
47002
  const accelInput = reversing ? input.brake : input.throttle;
46387
47003
  const brakeInput = clamp22(reversing ? input.throttle : input.brake, 0, 1);
46388
47004
  const ratio = ratioOf(state.gear) * spec.finalDrive;
46389
- state.rpm = clamp22(wheelRpm * ratio, spec.engine.idleRpm, spec.engine.redlineRpm + 200);
47005
+ const engaged = input.clutch == null || state.gear === 0 ? 1 : 1 - clamp22(input.clutch, 0, 1);
47006
+ const coupled = wheelRpm * ratio;
47007
+ const free = spec.engine.idleRpm + clamp22(accelInput, 0, 1) * (spec.engine.redlineRpm * 0.7 - spec.engine.idleRpm);
47008
+ state.rpm = clamp22(coupled * engaged + Math.max(coupled, free) * (1 - engaged), spec.engine.idleRpm, spec.engine.redlineRpm + 200);
47009
+ if (input.clutch != null && state.gear !== 0 && state.engineOn && engaged > CLUTCH_BITE && coupled < spec.engine.idleRpm * STALL_RPM && accelInput < STALL_GAS) {
47010
+ state.engineOn = false;
47011
+ events.push({ type: "stall" });
47012
+ }
46390
47013
  if (!state.engineOn || state.fuel <= 0) state.rpm = 0;
46391
47014
  let throttle = clamp22(accelInput, 0, 1);
46392
47015
  if (engineHealth < STALL_HEALTH && throttle > 0.8) {
@@ -46397,12 +47020,13 @@ var GameVehiclesThree = (() => {
46397
47020
  }
46398
47021
  throttle *= 0.5;
46399
47022
  } else stallWatch.throttleTime = 0;
46400
- if (!state.engineOn && accelInput > 0 && Math.abs(state.u) < 0.5 && engineHealth >= STALL_HEALTH && state.fuel > 0) state.engineOn = true;
47023
+ const starting = input.start ?? (accelInput > 0 && Math.abs(state.u) < 0.5);
47024
+ if (!state.engineOn && starting && engineHealth >= STALL_HEALTH && state.fuel > 0) state.engineOn = true;
46401
47025
  if (state.fuel <= 0 && state.engineOn) {
46402
47026
  state.engineOn = false;
46403
47027
  events.push({ type: "out_of_fuel" });
46404
47028
  }
46405
- const engineTorque = state.engineOn ? torqueAt(spec, state.rpm) * throttle * (0.55 + 0.45 * engineHealth) : 0;
47029
+ const engineTorque = state.engineOn ? torqueAt(spec, state.rpm) * throttle * (0.55 + 0.45 * engineHealth) * clamp22(input.powerLimit ?? 1, 0, 1) * engaged : 0;
46406
47030
  const driveForceTotal = state.gear === 0 ? 0 : (reversing ? -1 : 1) * engineTorque * ratio * spec.efficiency / spec.wheelRadius;
46407
47031
  if (state.engineOn) {
46408
47032
  const burned = fuelRate(fuelModel, state.rpm / spec.engine.redlineRpm, throttle, cargo / baseMass, engineHealth) * dt;
@@ -46420,19 +47044,21 @@ var GameVehiclesThree = (() => {
46420
47044
  const wz = state.z - w.x * sn + w.z * cs;
46421
47045
  const ground = world.ground ? world.ground(wx, wz) : {};
46422
47046
  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");
47047
+ const tyreHealth = cond.tyres ? clamp22(cond.tyres[i] ?? 1, 0, 1) : 1;
47048
+ const rim = !!cond.rims?.[i];
47049
+ const flat = rim || tyreHealth < FLAT_BELOW;
47050
+ const radius = rim ? spec.rimRadius ?? spec.wheelRadius * RIM_RADIUS_SHARE : flat ? spec.wheelRadius - FLAT_SAG : spec.wheelRadius;
46423
47051
  const top = state.height + w.x * state.pitch - w.z * state.roll;
46424
- const length = top - (ground.height ?? 0) - spec.wheelRadius;
47052
+ const length = top - (ground.height ?? 0) - radius;
46425
47053
  const compression = ride - length;
46426
47054
  const rate = -(state.heightRate + w.x * state.pitchRate - w.z * state.rollRate);
46427
47055
  const k = spec.suspension.spring * (0.5 + 0.5 * suspensionHealth);
46428
47056
  const c = spec.suspension.damper * (0.5 + 0.5 * suspensionHealth);
46429
47057
  const load = Math.max(0, k * compression + c * rate);
46430
- const tyreHealth = cond.tyres ? clamp22(cond.tyres[i] ?? 1, 0, 1) : 1;
46431
- const flat = tyreHealth < FLAT_BELOW;
46432
47058
  const bog = !!surface.bogs && !spec.fourWheelDrive;
46433
47059
  if (bog) bogging = true;
46434
- const grip = surfaceGrip(surface, state.u) * (flat ? 0.5 : 1);
46435
- wheelState.push({ n: load, grip, surface: surface.id, bog, flat });
47060
+ const grip = surfaceGrip(surface, state.u) * (rim ? RIM_FRICTION / spec.tyre.friction : flat ? 0.5 : 1);
47061
+ wheelState.push({ n: load, grip, surface: surface.id, bog, flat, rim });
46436
47062
  sumN += load;
46437
47063
  sumNA += load * w.x;
46438
47064
  sumNB += load * w.z;
@@ -46468,7 +47094,7 @@ var GameVehiclesThree = (() => {
46468
47094
  if (locked || lockedByBrake) fl += moving ? -Math.sign(vl) * capacity * (lockedByBrake ? SLIDING_GRIP : 1) : 0;
46469
47095
  else if (brakeShare > 0 && moving) fl += -Math.sign(vl) * brakeShare;
46470
47096
  if (brakeShare > 0 && moving) brakeWork += Math.min(brakeShare, capacity) * Math.abs(vl) * dt;
46471
- const rolling = ws.bog && accelInput > 0 ? spec.bogResistance : spec.rolling * (ws.flat ? 6 : 1);
47097
+ const rolling = ws.bog && accelInput > 0 ? spec.bogResistance : ws.rim ? RIM_ROLLING : spec.rolling * (ws.flat ? FLAT_ROLLING : 1);
46472
47098
  const alpha = Math.atan2(vc, Math.abs(vl) + 0.5);
46473
47099
  let fc = -Math.tanh(spec.tyre.corneringStiffness * alpha) * capacity;
46474
47100
  if (locked) fc *= 0.6;
@@ -46543,6 +47169,21 @@ var GameVehiclesThree = (() => {
46543
47169
  const brakeForce = base.brakeForce ?? body.mass * 9.81 * (carRatio + (0.6 - carRatio) * heavyShare);
46544
47170
  return { ...spec, mass: body.mass, wheels, fourWheelDrive: driveAll, engine, gears, drag, brakeForce, tyre: { ...spec.tyre, friction: 0.8 + 0.03 * stats2.handling } };
46545
47171
  }
47172
+ var DRIVE_PROFILES = {
47173
+ eco: { throttle: 0.7, upshift: 0.55, downshift: 0.25 },
47174
+ normal: { throttle: 1, upshift: 0.85, downshift: 0.35 },
47175
+ sport: { throttle: 1, upshift: 0.97, downshift: 0.55 }
47176
+ };
47177
+ var DEFAULT_TRACTION = { slip: 0.97, floor: 0.35 };
47178
+ function tractionLimit(slips, options = DEFAULT_TRACTION) {
47179
+ const worst = Math.max(0, ...slips);
47180
+ if (worst <= options.slip) return 1;
47181
+ return Math.max(options.floor, 1 - (worst - options.slip) / (1 - options.slip));
47182
+ }
47183
+ var DEFAULT_CRUISE = { gain: 0.35, minSpeed: 8.3 };
47184
+ function cruiseThrottle(target, speed, options = DEFAULT_CRUISE) {
47185
+ return Math.max(0, Math.min(1, (target - speed) * options.gain + 0.12));
47186
+ }
46546
47187
  var DEFAULT_CONTROL_KEYS = {
46547
47188
  throttle: ["KeyW", "ArrowUp"],
46548
47189
  brake: ["KeyS", "ArrowDown"],
@@ -46550,6 +47191,10 @@ var GameVehiclesThree = (() => {
46550
47191
  right: ["KeyD", "ArrowRight"],
46551
47192
  handbrake: ["Space"],
46552
47193
  horn: ["KeyH"],
47194
+ clutch: ["KeyC"],
47195
+ flash: ["KeyX"],
47196
+ washer: ["KeyZ"],
47197
+ hotwire: ["KeyI"],
46553
47198
  ignition: ["KeyI"],
46554
47199
  gearbox: ["KeyM"],
46555
47200
  gear_up: ["KeyE"],
@@ -46563,13 +47208,24 @@ var GameVehiclesThree = (() => {
46563
47208
  alarm: ["KeyY"],
46564
47209
  radio: ["KeyR"],
46565
47210
  radio_source: ["KeyT"],
47211
+ radio_track: ["BracketRight"],
46566
47212
  exit: ["KeyF"],
46567
47213
  indicator_left: ["Comma"],
46568
47214
  indicator_right: ["Period"],
46569
47215
  hazard: ["KeyB"],
46570
47216
  wipers: ["KeyO"],
46571
- seatbelt: ["KeyJ"]
47217
+ seatbelt: ["KeyJ"],
47218
+ drive_profile: ["Digit7"],
47219
+ traction_control: ["Digit8"],
47220
+ abs: ["Digit9"],
47221
+ cruise: ["Digit0"],
47222
+ climate: ["Minus"],
47223
+ demist: ["Equal"],
47224
+ siren: ["Semicolon"],
47225
+ beacon: ["Quote"],
47226
+ glovebox: ["Slash"]
46572
47227
  };
47228
+ var HELD_KEYS = ["throttle", "brake", "left", "right", "handbrake", "horn", "clutch", "flash", "washer", "hotwire"];
46573
47229
  function createVehicleControls(options) {
46574
47230
  const keys = { ...DEFAULT_CONTROL_KEYS, ...options.keys };
46575
47231
  const auto4 = options.autoFourWheel ?? { slip: 0.18, releaseSeconds: 2 };
@@ -46577,7 +47233,7 @@ var GameVehiclesThree = (() => {
46577
47233
  const held = /* @__PURE__ */ new Set();
46578
47234
  const any = (codes) => codes.some((c) => held.has(c));
46579
47235
  const commandOf = /* @__PURE__ */ new Map();
46580
- for (const [command2, codes] of Object.entries(keys)) if (!["throttle", "brake", "left", "right", "handbrake", "horn"].includes(command2)) for (const code of codes) commandOf.set(code, command2);
47236
+ for (const [command2, codes] of Object.entries(keys)) if (!HELD_KEYS.includes(command2)) for (const code of codes) commandOf.set(code, command2);
46581
47237
  const driving = new Set(Object.values(keys).flat());
46582
47238
  const state = {
46583
47239
  ignition: !!options.running,
@@ -46586,18 +47242,38 @@ var GameVehiclesThree = (() => {
46586
47242
  parkingBrake: false,
46587
47243
  driveMode: options.fourWheelDrive ? "auto" : "4x2",
46588
47244
  fourWheelEngaged: false,
46589
- lamps: { mode: "off", fog: false, interior: false },
47245
+ lamps: { mode: "off", fog: false, interior: false, flash: false },
46590
47246
  brakeLights: false,
46591
47247
  reverseLights: false,
46592
47248
  horn: false,
46593
47249
  alarm: "off",
46594
- radio: { on: false, source: "radio" },
47250
+ radio: { on: false, source: "radio", track: 0 },
46595
47251
  indicator: "off",
46596
47252
  blinkOn: false,
46597
47253
  tick: false,
46598
47254
  wipers: "off",
46599
- seatbelt: false
46600
- };
47255
+ washer: false,
47256
+ seatbelt: false,
47257
+ clutch: 0,
47258
+ profile: "normal",
47259
+ tractionControl: true,
47260
+ abs: true,
47261
+ cruise: { on: false, speed: 0 },
47262
+ climate: { on: false, demist: false },
47263
+ cranking: false,
47264
+ hotwire: { progress: 0, done: false },
47265
+ siren: "off",
47266
+ beacon: false,
47267
+ beaconSide: 0,
47268
+ glovebox: false
47269
+ };
47270
+ const hasKey = options.hasKey !== false;
47271
+ const hotwireSeconds = Math.max(0.1, options.hotwireSeconds ?? 8);
47272
+ const beaconHz = options.beaconHz ?? 2.5;
47273
+ let beaconClock = 0;
47274
+ let crankAsked = false;
47275
+ let lastSpeed = 0;
47276
+ let engineRunning = !!options.running;
46601
47277
  const blinkHz = options.blinkHz ?? 1.5;
46602
47278
  const cancelAt = options.indicatorCancel ?? 0.15;
46603
47279
  let blink = 0;
@@ -46609,15 +47285,21 @@ var GameVehiclesThree = (() => {
46609
47285
  const emit = (event) => options.onEvent?.(event);
46610
47286
  const LAMPS = ["off", "position", "low"];
46611
47287
  const MODES = ["4x2", "4x4", "auto"];
47288
+ const WIPERS = ["off", "intermittent", "slow", "fast"];
47289
+ const PROFILES = ["eco", "normal", "sport"];
47290
+ const SIRENS = ["off", "wail", "yelp"];
46612
47291
  const command = (c) => {
46613
47292
  switch (c) {
46614
47293
  case "ignition":
47294
+ if (!hasKey && !state.hotwire.done) return emit({ type: "refused", command: c, reason: "no_key" });
46615
47295
  state.ignition = !state.ignition;
46616
47296
  if (state.ignition && state.alarm !== "off") state.alarm = "off";
47297
+ if (state.ignition) crankAsked = true;
47298
+ else state.cruise.on = false;
46617
47299
  break;
46618
47300
  case "gearbox":
46619
47301
  state.gearbox = state.gearbox === "automatic" ? "manual" : "automatic";
46620
- state.gear = Math.max(1, state.gear);
47302
+ state.gear = options.clutchPedal && state.gearbox === "manual" && Math.abs(lastSpeed) < 0.5 ? 0 : Math.max(1, state.gear);
46621
47303
  break;
46622
47304
  case "gear_up":
46623
47305
  case "gear_down":
@@ -46656,6 +47338,9 @@ var GameVehiclesThree = (() => {
46656
47338
  case "radio_source":
46657
47339
  state.radio.source = state.radio.source === "radio" ? "tape" : "radio";
46658
47340
  break;
47341
+ case "radio_track":
47342
+ state.radio.track = (state.radio.track + 1) % Math.max(1, options.radioTracks ?? 3);
47343
+ break;
46659
47344
  case "exit":
46660
47345
  break;
46661
47346
  case "indicator_left":
@@ -46672,11 +47357,48 @@ var GameVehiclesThree = (() => {
46672
47357
  blink = 0;
46673
47358
  break;
46674
47359
  case "wipers":
46675
- state.wipers = state.wipers === "off" ? "slow" : state.wipers === "slow" ? "fast" : "off";
47360
+ state.wipers = WIPERS[(WIPERS.indexOf(state.wipers) + 1) % WIPERS.length];
47361
+ break;
47362
+ case "drive_profile":
47363
+ state.profile = PROFILES[(PROFILES.indexOf(state.profile) + 1) % PROFILES.length];
47364
+ break;
47365
+ case "traction_control":
47366
+ state.tractionControl = !state.tractionControl;
47367
+ break;
47368
+ case "abs":
47369
+ state.abs = !state.abs;
47370
+ break;
47371
+ case "cruise":
47372
+ if (state.cruise.on) state.cruise.on = false;
47373
+ else if (!engineRunning) return emit({ type: "refused", command: c, reason: "ignition_off" });
47374
+ else if (lastSpeed < DEFAULT_CRUISE.minSpeed) return emit({ type: "refused", command: c, reason: "too_slow" });
47375
+ else state.cruise = { on: true, speed: lastSpeed };
47376
+ break;
47377
+ case "climate":
47378
+ state.climate.on = !state.climate.on;
47379
+ if (!state.climate.on) state.climate.demist = false;
47380
+ break;
47381
+ case "demist":
47382
+ state.climate.demist = !state.climate.demist;
47383
+ if (state.climate.demist) state.climate.on = true;
46676
47384
  break;
46677
47385
  case "seatbelt":
46678
47386
  state.seatbelt = !state.seatbelt;
46679
47387
  break;
47388
+ case "siren":
47389
+ if (!options.emergency) return emit({ type: "refused", command: c, reason: "no_siren" });
47390
+ state.siren = SIRENS[(SIRENS.indexOf(state.siren) + 1) % SIRENS.length];
47391
+ if (state.siren !== "off") state.beacon = true;
47392
+ break;
47393
+ case "beacon":
47394
+ if (!options.emergency) return emit({ type: "refused", command: c, reason: "no_siren" });
47395
+ state.beacon = !state.beacon;
47396
+ if (!state.beacon) state.siren = "off";
47397
+ beaconClock = 0;
47398
+ break;
47399
+ case "glovebox":
47400
+ state.glovebox = !state.glovebox;
47401
+ break;
46680
47402
  }
46681
47403
  emit({ type: "command", command: c, state });
46682
47404
  };
@@ -46715,17 +47437,66 @@ var GameVehiclesThree = (() => {
46715
47437
  else if (turned && way < cancelAt) state.indicator = "off";
46716
47438
  }
46717
47439
  } else state.blinkOn = false;
47440
+ if (state.beacon) {
47441
+ beaconClock += dt;
47442
+ state.beaconSide = Math.floor(beaconClock * beaconHz) % 2;
47443
+ }
47444
+ if (!hasKey && !state.hotwire.done && !state.ignition && any(keys.hotwire)) {
47445
+ if (state.hotwire.progress === 0) {
47446
+ if (state.alarm === "armed") state.alarm = "ringing";
47447
+ emit({ type: "hotwire", stage: "started" });
47448
+ }
47449
+ state.hotwire.progress = Math.min(1, state.hotwire.progress + dt / hotwireSeconds);
47450
+ if (state.hotwire.progress >= 1) {
47451
+ state.hotwire.done = true;
47452
+ state.ignition = true;
47453
+ crankAsked = true;
47454
+ emit({ type: "hotwire", stage: "joined" });
47455
+ }
47456
+ }
46718
47457
  state.horn = any(keys.horn);
46719
- const gasNow = state.ignition ? throttle : 0;
47458
+ state.lamps.flash = any(keys.flash);
47459
+ state.washer = any(keys.washer);
47460
+ state.clutch = ease(state.clutch, any(keys.clutch) ? 1 : 0, any(keys.clutch) ? 8 : 2.5, dt);
47461
+ const speed = driver?.state.u ?? 0;
47462
+ lastSpeed = speed;
47463
+ if (state.cruise.on && (brake > 0.05 || state.clutch > 0.5 || handbrake)) {
47464
+ state.cruise.on = false;
47465
+ emit({ type: "cruise_cancelled" });
47466
+ }
47467
+ const tune = DRIVE_PROFILES[state.profile];
47468
+ let gasNow = state.ignition ? throttle * tune.throttle : 0;
47469
+ if (state.cruise.on && state.ignition) gasNow = Math.max(gasNow, cruiseThrottle(state.cruise.speed, speed));
47470
+ const gasStart = state.gearbox === "automatic" && state.ignition && !engineRunning && throttle > 0 && Math.abs(speed) < 0.5;
47471
+ state.cranking = false;
47472
+ if (crankAsked || gasStart) {
47473
+ crankAsked = false;
47474
+ if (options.canCrank && !options.canCrank()) emit({ type: "refused", command: "ignition", reason: "battery_flat" });
47475
+ else {
47476
+ state.cranking = true;
47477
+ options.onCrank?.();
47478
+ }
47479
+ }
47480
+ const driven = (driver?.state.slip ?? []).filter((_, i) => driver?.spec.wheels?.[i]?.drive ?? true);
47481
+ const powerLimit = (state.tractionControl ? tractionLimit(driven) : 1) * (options.powerLimit?.() ?? 1);
46720
47482
  const reversing = state.gearbox === "manual" ? state.gear < 0 : (driver?.state.gear ?? 1) < 0;
46721
47483
  state.reverseLights = reversing;
46722
- state.brakeLights = handbrake || (reversing ? gasNow > 0.05 && (driver?.state.u ?? 0) < -0.3 : brake > 0.05);
46723
- if (state.gearbox === "automatic") return { throttle: gasNow, brake, steer, handbrake, horn: state.horn };
46724
- return state.gear < 0 ? { throttle: brake, brake: gasNow, steer, handbrake, horn: state.horn, gear: state.gear } : { throttle: gasNow, brake, steer, handbrake, horn: state.horn, gear: state.gear };
47484
+ state.brakeLights = handbrake || (reversing ? gasNow > 0.05 && speed < -0.3 : brake > 0.05);
47485
+ const common = { steer, handbrake, horn: state.horn, start: state.cranking, powerLimit };
47486
+ if (state.gearbox === "automatic") return { ...common, throttle: gasNow, brake };
47487
+ const pedals = state.gear < 0 ? { throttle: brake, brake: gasNow } : { throttle: gasNow, brake };
47488
+ return { ...common, ...pedals, gear: state.gear, ...options.clutchPedal ? { clutch: state.clutch } : {} };
46725
47489
  },
46726
47490
  after(driver, dt) {
46727
- if (driver.state.engineOn && !state.ignition && driver.state.u !== 0) state.ignition = true;
46728
47491
  if (!state.ignition && driver.state.engineOn) driver.state.engineOn = false;
47492
+ engineRunning = driver.state.engineOn;
47493
+ if (!engineRunning) state.cruise.on = false;
47494
+ const spec = driver.spec;
47495
+ spec.abs = state.abs;
47496
+ if (spec.upshiftFraction != null) {
47497
+ spec.upshiftFraction = DRIVE_PROFILES[state.profile].upshift;
47498
+ spec.downshiftFraction = DRIVE_PROFILES[state.profile].downshift;
47499
+ }
46729
47500
  if (state.driveMode === "auto" && options.fourWheelDrive) {
46730
47501
  const slip = Math.max(0, ...driver.state.slip);
46731
47502
  if (slip > auto4.slip) {
@@ -46739,6 +47510,10 @@ var GameVehiclesThree = (() => {
46739
47510
  held.clear();
46740
47511
  throttle = brake = steer = 0;
46741
47512
  state.horn = false;
47513
+ state.lamps.flash = false;
47514
+ state.washer = false;
47515
+ state.clutch = 0;
47516
+ state.cruise.on = false;
46742
47517
  }
46743
47518
  };
46744
47519
  }
@@ -46753,15 +47528,212 @@ var GameVehiclesThree = (() => {
46753
47528
  best = tyre;
46754
47529
  }
46755
47530
  }
46756
- return best?.health ?? 1;
47531
+ return best;
46757
47532
  });
46758
47533
  return {
46759
- tyres,
47534
+ tyres: tyres.map((tyre) => tyre?.health ?? 1),
47535
+ rims: tyres.map((tyre) => !!tyre?.rim),
46760
47536
  ...report.brakes != null ? { brakes: report.brakes } : {},
46761
47537
  ...report.engine != null ? { engine: report.engine } : {},
46762
47538
  ...report.suspension != null ? { suspension: report.suspension } : {}
46763
47539
  };
46764
47540
  }
47541
+ var DEFAULT_LOADS = { position: 20, low: 110, high: 130, fog: 110, interior: 10, radio: 25, wipers: 60, indicators: 42, horn: 60, alarm: 5, brakeLights: 42, climate: 180, siren: 100, beacon: 70 };
47542
+ var clamp3 = (x, a, b) => Math.max(a, Math.min(b, x));
47543
+ function createVehicleSystems(options = {}) {
47544
+ const loads = { ...DEFAULT_LOADS, ...options.loads };
47545
+ const capacity = options.batteryWh ?? 720;
47546
+ const alternator = options.alternatorW ?? { idle: 350, full: 1100 };
47547
+ const crankCharge = options.crankCharge ?? 0.25;
47548
+ const startWh = options.startWh ?? 0.6;
47549
+ const ambient = options.ambientC ?? 25;
47550
+ const thermostat = options.thermostatC ?? 90;
47551
+ const limp = options.limpC ?? 115;
47552
+ const seize = options.seizeC ?? 130;
47553
+ const heat = options.heatPerSecond ?? 0.5;
47554
+ const cooling = options.cooling ?? { stopped: 4e-3, fast: 0.03 };
47555
+ const state = { charge: clamp3(options.charge ?? 1, 0, 1), coolantC: ambient, temperature: 0, overheating: false, seized: false, spares: options.spares ?? 1, netW: 0 };
47556
+ const draw = (c) => {
47557
+ const lamps = c.lamps;
47558
+ let w = lamps.mode === "off" ? 0 : loads.position;
47559
+ if (lamps.mode === "low") w += loads.low;
47560
+ if (lamps.mode === "high") w += loads.low + loads.high;
47561
+ if (lamps.flash) w += loads.high;
47562
+ if (lamps.fog) w += loads.fog;
47563
+ if (lamps.interior) w += loads.interior;
47564
+ if (c.radio.on) w += loads.radio;
47565
+ if (c.wipers !== "off" || c.washer) w += loads.wipers;
47566
+ if (c.indicator !== "off") w += loads.indicators * (c.indicator === "hazard" ? 2 : 1) * 0.5;
47567
+ if (c.horn) w += loads.horn;
47568
+ if (c.alarm !== "off") w += loads.alarm;
47569
+ if (c.brakeLights) w += loads.brakeLights;
47570
+ if (c.climate.on) w += loads.climate;
47571
+ if (c.siren !== "off") w += loads.siren;
47572
+ if (c.beacon) w += loads.beacon;
47573
+ return w;
47574
+ };
47575
+ return {
47576
+ state,
47577
+ canCrank: () => state.charge >= crankCharge && !state.seized,
47578
+ crank() {
47579
+ state.charge = clamp3(state.charge - startWh / capacity, 0, 1);
47580
+ },
47581
+ powerLimit: () => state.coolantC <= limp ? 1 : clamp3(1 - (state.coolantC - limp) / (seize - limp), 0.25, 1),
47582
+ step({ driver, controls, throttle, dt, radiator = 1, coolantC }) {
47583
+ const events = [];
47584
+ const running = driver.state.engineOn;
47585
+ const charging = running ? alternator.idle + (alternator.full - alternator.idle) * clamp3((driver.state.rpm - 900) / 1100, 0, 1) : 0;
47586
+ const net = charging - draw(controls);
47587
+ state.netW = net;
47588
+ const before = state.charge;
47589
+ state.charge = clamp3(state.charge + net * dt / 3600 / capacity, 0, 1);
47590
+ if (before >= crankCharge && state.charge < crankCharge && !running) events.push({ type: "battery_flat" });
47591
+ const work = running ? clamp3(0.15 + 0.85 * throttle * (driver.state.rpm / 6500), 0, 1) : 0;
47592
+ const air = cooling.stopped + (cooling.fast - cooling.stopped) * clamp3(Math.abs(driver.state.u) / 27.8, 0, 1);
47593
+ const cool = air * clamp3(radiator, 0.05, 1) * (running ? 1 + (controls.climate.on ? 0.15 : 0) : 1);
47594
+ const opened = state.coolantC >= thermostat ? 1 : 0.15;
47595
+ state.coolantC += (heat * work - cool * opened * (state.coolantC - ambient)) * dt;
47596
+ state.coolantC = coolantC ?? Math.max(ambient, state.coolantC);
47597
+ state.temperature = clamp3((state.coolantC - ambient) / (seize - ambient), 0, 1);
47598
+ const hot = state.coolantC > limp;
47599
+ if (hot && !state.overheating) events.push({ type: "overheating" });
47600
+ state.overheating = hot;
47601
+ if (state.coolantC >= seize && !state.seized) {
47602
+ state.seized = true;
47603
+ events.push({ type: "seized" });
47604
+ }
47605
+ if (state.seized) driver.state.engineOn = false;
47606
+ return events;
47607
+ },
47608
+ fitSpare(driver, wheel) {
47609
+ if (state.spares <= 0) return { type: "no_spare" };
47610
+ state.spares--;
47611
+ const tyres = [...driver.condition.tyres ?? []];
47612
+ tyres[wheel] = 1;
47613
+ driver.condition = { ...driver.condition, tyres };
47614
+ return { type: "spare_fitted", wheel };
47615
+ },
47616
+ charge(share) {
47617
+ state.charge = clamp3(state.charge + share, 0, 1);
47618
+ }
47619
+ };
47620
+ }
47621
+ var DEFAULT_EJECTION = { frontal: 8.3, side: 6, keep: 0.8, lift: 2.5 };
47622
+ function ejectionOf(pulse, belted, options = DEFAULT_EJECTION) {
47623
+ if (belted) return null;
47624
+ const du = pulse.after.u - pulse.before.u;
47625
+ const dw = pulse.after.w - pulse.before.w;
47626
+ const deltaV = Math.hypot(du, dw);
47627
+ const frontal = Math.abs(du) >= Math.abs(dw);
47628
+ if (frontal && Math.abs(du) < options.frontal && !pulse.rolled) return null;
47629
+ if (!frontal && Math.abs(dw) < options.side && !pulse.rolled) return null;
47630
+ const exit = frontal ? du < 0 ? "windshield" : "rear_window" : dw < 0 ? "right_window" : "left_window";
47631
+ return { exit, velocity: [pulse.before.u * options.keep, pulse.before.w * options.keep, options.lift], deltaV };
47632
+ }
47633
+ var DEFAULT_PARKING_SENSOR = { range: 1.5, solid: 0.3, slowest: 0.8, fastest: 0.12 };
47634
+ function parkingBeepInterval(distance, options = DEFAULT_PARKING_SENSOR) {
47635
+ if (distance > options.range) return null;
47636
+ if (distance <= options.solid) return 0;
47637
+ const t = (distance - options.solid) / (options.range - options.solid);
47638
+ return options.fastest + (options.slowest - options.fastest) * t;
47639
+ }
47640
+ function bumperDistance(params) {
47641
+ const [hx, hz2] = params.heading;
47642
+ const sign2 = params.forward ? 1 : -1;
47643
+ let best = Infinity;
47644
+ for (const [ox, oz] of params.obstacles) {
47645
+ const dx = ox - params.x;
47646
+ const dz = oz - params.z;
47647
+ const along = (dx * hx + dz * hz2) * sign2 - params.length / 2;
47648
+ const side2 = Math.abs(-dx * hz2 + dz * hx);
47649
+ if (along >= 0 && side2 <= params.width / 2 + 0.2) best = Math.min(best, along);
47650
+ }
47651
+ return best;
47652
+ }
47653
+ var GLOVEBOX = { id: "glovebox", slots: 6, capacityKg: 4, access: "cabin" };
47654
+ var trunkFor = (litres = 450) => ({ id: "trunk", slots: Math.max(4, Math.round(litres / 25)), capacityKg: Math.round(litres / 4), access: "lid" });
47655
+ var weightOf = (stack) => stack.unitKg * stack.count;
47656
+ function createVehicleStorage(options = {}) {
47657
+ const compartments = [...options.compartments ?? [GLOVEBOX, trunkFor()]];
47658
+ const slots = new Map(compartments.map((c) => [c.id, Array.from({ length: c.slots }, () => null)]));
47659
+ const specOf = (id) => compartments.find((c) => c.id === id);
47660
+ const load = (id) => (slots.get(id) ?? []).reduce((kg, s) => kg + (s ? weightOf(s) : 0), 0);
47661
+ const reachable2 = (id, access) => {
47662
+ const spec = specOf(id);
47663
+ if (!spec) return false;
47664
+ return spec.access === "lid" ? access.lid >= 0.5 : access.gloveboxOpen && (access.seated || access.frontDoorOpen);
47665
+ };
47666
+ const place = (id, stack) => {
47667
+ const spec = specOf(id);
47668
+ const list2 = slots.get(id);
47669
+ if (!spec || !list2) return "missing";
47670
+ if (load(id) + weightOf(stack) > spec.capacityKg + 1e-9) return "too_heavy";
47671
+ const same = stack.data === void 0 ? list2.findIndex((s) => s !== null && s.itemId === stack.itemId && s.data === void 0) : -1;
47672
+ if (same >= 0) {
47673
+ const s = list2[same];
47674
+ list2[same] = { ...s, count: s.count + stack.count };
47675
+ return "ok";
47676
+ }
47677
+ const free = list2.indexOf(null);
47678
+ if (free < 0) return "full";
47679
+ list2[free] = { ...stack };
47680
+ return "ok";
47681
+ };
47682
+ return {
47683
+ compartments,
47684
+ reachable: reachable2,
47685
+ contents: (id) => [...slots.get(id) ?? []],
47686
+ loadKg: (id) => id === void 0 ? compartments.reduce((kg, c) => kg + load(c.id), 0) : load(id),
47687
+ put(id, stack, access) {
47688
+ if (!specOf(id)) return "missing";
47689
+ if (!reachable2(id, access)) return "shut";
47690
+ return place(id, stack);
47691
+ },
47692
+ take(id, slot, access) {
47693
+ const list2 = slots.get(id);
47694
+ if (!list2 || !reachable2(id, access)) return null;
47695
+ const stack = list2[slot] ?? null;
47696
+ if (stack) list2[slot] = null;
47697
+ return stack;
47698
+ },
47699
+ stock(id, stacks) {
47700
+ return stacks.filter((s) => place(id, s) !== "ok");
47701
+ },
47702
+ clear() {
47703
+ for (const list2 of slots.values()) list2.fill(null);
47704
+ }
47705
+ };
47706
+ }
47707
+ var clamp4 = (x, a, b) => Math.max(a, Math.min(b, x));
47708
+ function createWindshieldFog(options = {}) {
47709
+ const outside = options.outsideHumidity ?? 0.6;
47710
+ const breath = options.breath ?? 4e-3;
47711
+ const wet = options.wet ?? 2e-3;
47712
+ const air = options.air ?? { shut: 2e-3, window: 0.05, climate: 0.01, demist: 0.06 };
47713
+ const coldSpan = options.coldSpan ?? 15;
47714
+ const rates = options.rates ?? { build: 0.05, clear: 0.03, demist: 0.25 };
47715
+ const state = { humidity: outside, fog: 0 };
47716
+ return {
47717
+ state,
47718
+ step({ dt, occupants, outsideC, cabinC, rain = 0, speed = 0, windows = 0, climate, engineWarm = 1 }) {
47719
+ const exchange = air.shut + air.window * clamp4(windows, 0, 1) + (climate.on ? air.climate : 0) + (climate.demist ? air.demist : 0);
47720
+ const source = breath * Math.max(0, occupants) + wet * clamp4(rain, 0, 1) * (occupants > 0 ? 1 : 0);
47721
+ const towards = climate.on ? outside * 0.4 : outside;
47722
+ state.humidity = clamp4(state.humidity + (source - exchange * (state.humidity - towards)) * dt, 0, 1);
47723
+ const cold = Math.max(0, cabinC - outsideC) * (1 - 0.7 * clamp4(windows, 0, 1)) + 6 * clamp4(rain, 0, 1) + 2 * clamp4(speed / 27.8, 0, 1);
47724
+ const warmed = climate.demist ? cold * (1 - 0.9 * clamp4(engineWarm, 0, 1)) : cold;
47725
+ const clearUpTo = 1 - 0.5 * clamp4(warmed / coldSpan, 0, 1);
47726
+ const target = clamp4((state.humidity - clearUpTo) * 6, 0, 1);
47727
+ const down = climate.demist ? rates.demist * (0.3 + 0.7 * clamp4(engineWarm, 0, 1)) : rates.clear;
47728
+ state.fog = target > state.fog ? Math.min(target, state.fog + rates.build * dt) : Math.max(target, state.fog - down * dt);
47729
+ return state;
47730
+ },
47731
+ set(next) {
47732
+ if (next.humidity !== void 0) state.humidity = clamp4(next.humidity, 0, 1);
47733
+ if (next.fog !== void 0) state.fog = clamp4(next.fog, 0, 1);
47734
+ }
47735
+ };
47736
+ }
46765
47737
 
46766
47738
  // src/vehicle-lod.ts
46767
47739
  var VEHICLE_LOD_DISTANCE = 14;
@@ -46784,7 +47756,8 @@ var GameVehiclesThree = (() => {
46784
47756
  culled.clear();
46785
47757
  modelKey = keyOf();
46786
47758
  isUploaded = false;
46787
- full.traverse((object2) => {
47759
+ full.traverse((node) => {
47760
+ const object2 = node;
46788
47761
  if (!object2.isMesh || culled.has(object2)) return;
46789
47762
  culled.set(object2, object2.frustumCulled);
46790
47763
  object2.frustumCulled = false;
@@ -46811,7 +47784,8 @@ var GameVehiclesThree = (() => {
46811
47784
  const shadowProxyOf = (fused) => {
46812
47785
  const positions = [];
46813
47786
  const indices = [];
46814
- for (const mesh2 of fused.children) {
47787
+ for (const child of fused.children) {
47788
+ const mesh2 = child;
46815
47789
  if (!mesh2.isMesh || !mesh2.castShadow) continue;
46816
47790
  const geometry2 = mesh2.geometry.clone().applyMatrix4(mesh2.matrix);
46817
47791
  const offset = positions.length / 3;
@@ -46850,7 +47824,8 @@ var GameVehiclesThree = (() => {
46850
47824
  lite.visible = isFused;
46851
47825
  shadow ?? (shadow = shadowProxyOf(lite));
46852
47826
  shadow.visible = !isFused;
46853
- full.traverse((object2) => {
47827
+ full.traverse((node) => {
47828
+ const object2 = node;
46854
47829
  if (!object2.isMesh || !object2.castShadow) return;
46855
47830
  castsShadow.add(object2);
46856
47831
  object2.castShadow = false;
@@ -46872,7 +47847,8 @@ var GameVehiclesThree = (() => {
46872
47847
  root.updateMatrixWorld(true);
46873
47848
  const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert();
46874
47849
  const textured = [];
46875
- full.traverse((object2) => {
47850
+ full.traverse((node) => {
47851
+ const object2 = node;
46876
47852
  if (!object2.isMesh || object2.userData.skipLite || !isShown2(object2, full)) return;
46877
47853
  const materials = Array.isArray(object2.material) ? object2.material : [object2.material];
46878
47854
  if (materials.some((material) => material?.map && !/plate/i.test(material.name ?? "")) && !/plate/i.test(object2.name ?? "")) textured.push(object2);
@@ -47402,6 +48378,7 @@ var GameVehiclesThree = (() => {
47402
48378
  var itemId = (key) => ITEM_OF[key] ?? key;
47403
48379
  var LANE = { z: 11, from: -12, wall: 13 };
47404
48380
  var SPARK_EVERY_S = 0.08;
48381
+ var TYRE_DAMAGE = /* @__PURE__ */ new Set(["flat_tyre", "torn_tyre", "run_flat"]);
47405
48382
  var LEAF_REST_RADIANS = 0.02;
47406
48383
  var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
47407
48384
  var LANE_START_S = 1.2;
@@ -47697,7 +48674,8 @@ var GameVehiclesThree = (() => {
47697
48674
  state.time += dt;
47698
48675
  const braking = settings.damage !== "into_wall" && gap() < brakeAt() && state.time > 0.5;
47699
48676
  const moving = state.time > LANE_START_S;
47700
- const input = { ...idle2, throttle: braking || !moving ? 0 : 1, brake: braking ? 1 : 0, steer: settings.damage === "into_wall" || !moving ? 0 : Math.sin(state.time * 1.4) * 0.15 };
48677
+ const straight = settings.damage === "into_wall" || TYRE_DAMAGE.has(settings.damage) || !moving;
48678
+ const input = { ...idle2, throttle: braking || !moving ? 0 : 1, brake: braking ? 1 : 0, steer: straight ? 0 : Math.sin(state.time * 1.4) * 0.15 };
47701
48679
  if (mode === "arcade") {
47702
48680
  const { events } = arcade.step(input, dt, { blocked: (x, z, yaw) => vehicleCorners(arcade.spec, x, z, yaw).some(([px]) => px >= LANE.wall) });
47703
48681
  for (const e of events) {
@@ -47735,7 +48713,8 @@ var GameVehiclesThree = (() => {
47735
48713
  speed,
47736
48714
  throttle: input.throttle,
47737
48715
  ...semiState ? { rpm: semiState.rpm, slip: Math.max(0, ...semiState.slip.map(Math.abs)) } : { slip: braking && speed > 3 ? 0.6 : 0 },
47738
- surface: /dirt|gravel|sand|grass|mud/.test(settings.surface) ? "dirt" : "asphalt"
48716
+ surface: /dirt|gravel|sand|grass|mud/.test(settings.surface) ? "dirt" : "asphalt",
48717
+ brake: input.brake
47739
48718
  });
47740
48719
  }
47741
48720
  state.exhaust -= dt;
@@ -47911,6 +48890,7 @@ var GameVehiclesThree = (() => {
47911
48890
  if (isRenderPools(lent.pools)) lent.pools.materials.register("vehicles.cracked-glass", () => crackedGlassMaterial(three, new three.DataTexture(new Uint8Array(4), 1, 1)));
47912
48891
  lent.audio?.register?.(LIBRARY_BANK);
47913
48892
  lent.audio?.register?.(VEHICLE_SOUND_BANK);
48893
+ lent.audio?.preload?.(VEHICLE_PRELOAD_SOUNDS);
47914
48894
  return () => {
47915
48895
  stopChange?.();
47916
48896
  stopFrame();
@@ -48348,6 +49328,127 @@ var GameVehiclesThree = (() => {
48348
49328
  return { ...certificate, results, status: statusOf(results) };
48349
49329
  }
48350
49330
 
49331
+ // src/body.ts
49332
+ var OPEN_SOUND = "car_door_open";
49333
+ var SHUT_SOUND = { door: "car_door_close", hood: "car_hood_close", lid: "car_trunk_close" };
49334
+ var clamp5 = (x, a, b) => Math.max(a, Math.min(b, x));
49335
+ function createVehicleBody(built2, options) {
49336
+ const swing = options.swingSeconds ?? 1.2;
49337
+ const windowSeconds = options.windowSeconds ?? 3.5;
49338
+ const travel = options.windowTravel ?? 0.92;
49339
+ const root = built2.context?.vehicle ?? built2.group;
49340
+ const poses = /* @__PURE__ */ new Map();
49341
+ for (const clip of options.clips) {
49342
+ if (!clip.id.startsWith("open_")) continue;
49343
+ const last = clip.keys[clip.keys.length - 1];
49344
+ for (const [id, pose] of Object.entries(last?.parts ?? {})) if (pose.rotation) poses.set(id, pose.rotation);
49345
+ }
49346
+ const hinges = /* @__PURE__ */ new Map();
49347
+ for (const [id, open] of poses) {
49348
+ const pivot = root.getObjectByName(id);
49349
+ if (pivot) hinges.set(id, { pivot, open, now: 0, target: 0 });
49350
+ }
49351
+ const kind = (part) => part.startsWith("door") ? "door" : part === "hood" ? "hood" : "lid";
49352
+ const panes = /* @__PURE__ */ new Map();
49353
+ for (const glass of built2.context?.glass ?? []) {
49354
+ const g = glass.userData.glass;
49355
+ if (!g || g.key !== "window_front" && g.key !== "window_rear") continue;
49356
+ const id = `${g.key === "window_front" ? "front" : "rear"}_${(g.side ?? 1) < 0 ? "left" : "right"}`;
49357
+ if (panes.has(id) || !glass.geometry) continue;
49358
+ glass.geometry.computeBoundingBox();
49359
+ const box2 = glass.geometry.boundingBox;
49360
+ if (box2) panes.set(id, { pane: glass, drop: (box2.max.y - box2.min.y) * travel, now: 0, target: 0, applied: 0 });
49361
+ }
49362
+ const sunroofs = [];
49363
+ const wipers = [];
49364
+ const T = options.THREE;
49365
+ const turn = new T.Quaternion();
49366
+ root.traverse((o) => {
49367
+ const sun = o.userData.sunroof;
49368
+ if (sun) sunroofs.push({ obj: o, slide: sun.slide, base: o.position.x });
49369
+ });
49370
+ for (const { part, tag } of built2.context?.wipers ?? [])
49371
+ wipers.push({ obj: part, arm: tag.arm, sweep: tag.sweep, spindle: new T.Vector3(...tag.spindle), axis: new T.Vector3(...tag.axis), rest: part.position.clone(), restQ: part.quaternion.clone() });
49372
+ let sunNow = 0;
49373
+ let sunTarget = 0;
49374
+ let wipePhase = 0;
49375
+ let locked = false;
49376
+ const placeWiper = (w, angle) => {
49377
+ if (angle === 0) {
49378
+ w.obj.position.copy(w.rest);
49379
+ w.obj.quaternion.copy(w.restQ);
49380
+ return;
49381
+ }
49382
+ turn.setFromAxisAngle(w.axis, angle);
49383
+ w.obj.position.copy(w.rest).sub(w.spindle).applyQuaternion(turn).add(w.spindle);
49384
+ w.obj.quaternion.copy(turn).multiply(w.restQ);
49385
+ };
49386
+ const ease = (now, target, seconds, dt) => now + clamp5(target - now, -dt / seconds, dt / seconds);
49387
+ return {
49388
+ parts: [...hinges.keys()],
49389
+ windows: [...panes.keys()],
49390
+ hasSunroof: sunroofs.length > 0,
49391
+ wipers: new Set(wipers.map((w) => w.arm)).size,
49392
+ get locked() {
49393
+ return locked;
49394
+ },
49395
+ openness: (part) => hinges.get(part)?.now ?? 0,
49396
+ toggle(part) {
49397
+ const h = hinges.get(part);
49398
+ if (!h) return [{ type: "refused", part, reason: "missing" }];
49399
+ if (locked && kind(part) !== "hood" && h.target === 0) return [{ type: "refused", part, reason: "locked" }];
49400
+ h.target = h.target > 0 ? 0 : 1;
49401
+ return h.target > 0 ? [{ type: "sound", key: OPEN_SOUND, part }] : [];
49402
+ },
49403
+ window: (id) => panes.get(id)?.now ?? 0,
49404
+ toggleWindow(id) {
49405
+ const w = panes.get(id);
49406
+ if (w) w.target = w.target > 0.5 ? 0 : 1;
49407
+ },
49408
+ setWindow(id, open) {
49409
+ const w = panes.get(id);
49410
+ if (w) w.target = clamp5(open, 0, 1);
49411
+ },
49412
+ toggleSunroof() {
49413
+ sunTarget = sunTarget > 0.5 ? 0 : 1;
49414
+ },
49415
+ lock(on) {
49416
+ locked = on;
49417
+ return [{ type: "sound", key: "blinker_tick", part: "doors" }];
49418
+ },
49419
+ update(dt, wipe = 0) {
49420
+ const events = [];
49421
+ for (const [id, h] of hinges) {
49422
+ if (h.now === h.target) continue;
49423
+ h.now = ease(h.now, h.target, swing, dt);
49424
+ h.pivot.rotation.set(h.open[0] * h.now, h.open[1] * h.now, h.open[2] * h.now);
49425
+ if (h.now === 0) events.push({ type: "sound", key: SHUT_SOUND[kind(id)] ?? "car_door_close", part: id });
49426
+ }
49427
+ for (const w of panes.values()) {
49428
+ if (w.now === w.target) continue;
49429
+ w.now = ease(w.now, w.target, windowSeconds, dt);
49430
+ const offset = -w.drop * w.now;
49431
+ w.pane.position.y += offset - w.applied;
49432
+ w.applied = offset;
49433
+ }
49434
+ if (sunNow !== sunTarget) {
49435
+ sunNow = ease(sunNow, sunTarget, 2.5, dt);
49436
+ for (const s of sunroofs) s.obj.position.x = s.base - s.slide * sunNow;
49437
+ }
49438
+ wipePhase = wipe > 0 ? (wipePhase + dt * wipe) % 1 : wipePhase > 0 ? Math.min(1, wipePhase + dt * 1.5) % 1 : 0;
49439
+ const sweep = Math.sin(Math.PI * wipePhase);
49440
+ for (const w of wipers) placeWiper(w, w.sweep * sweep);
49441
+ return events;
49442
+ },
49443
+ dispose() {
49444
+ for (const h of hinges.values()) h.pivot.rotation.set(0, 0, 0);
49445
+ for (const w of panes.values()) w.pane.position.y -= w.applied;
49446
+ for (const s of sunroofs) s.obj.position.x = s.base;
49447
+ for (const w of wipers) placeWiper(w, 0);
49448
+ }
49449
+ };
49450
+ }
49451
+
48351
49452
  // src/dashboard.ts
48352
49453
  var DASHBOARD_LOOK = {
48353
49454
  face: "#0b1110",
@@ -48522,6 +49623,8 @@ var GameVehiclesThree = (() => {
48522
49623
  lamp(g, 104, 214, r.highBeam, DASHBOARD_LOOK.blue, beam);
48523
49624
  lamp(g, 152, 214, r.lowBeam || r.fog, r.fog ? DASHBOARD_LOOK.amber : DASHBOARD_LOOK.green, beam);
48524
49625
  bar(g, 40, r.fuel, false, "F");
49626
+ lamp(g, 104, 244, !!r.cruise, DASHBOARD_LOOK.green, letter("CRZ"));
49627
+ if (r.profile && r.profile !== "normal") lamp(g, 152, 244, true, r.profile === "eco" ? DASHBOARD_LOOK.green : DASHBOARD_LOOK.amber, letter(r.profile === "eco" ? "E" : "S"));
48525
49628
  } else {
48526
49629
  dial(g, r.rpm / 1e3, Math.ceil(redline / 1e3) + 1, 1, redline / 1e3, (v) => String(Math.round(v)));
48527
49630
  g.fillStyle = DASHBOARD_LOOK.marks;
@@ -48530,7 +49633,9 @@ var GameVehiclesThree = (() => {
48530
49633
  g.fillText(r.gear < 0 ? "R" : r.gear === 0 ? "N" : String(r.gear), SIZE / 2, SIZE / 2 + 54);
48531
49634
  lamp(g, 86, 178, r.parkingBrake, DASHBOARD_LOOK.red, ring2("P"));
48532
49635
  lamp(g, 128, 214, r.ignition && !r.seatbelt, DASHBOARD_LOOK.red, belt);
48533
- lamp(g, 170, 178, warn, DASHBOARD_LOOK.red, letter("+\u2212"));
49636
+ lamp(g, 170, 178, warn || r.engineOn && r.charging === false, DASHBOARD_LOOK.red, letter("+\u2212"));
49637
+ lamp(g, 104, 244, r.ignition && !!r.tractionOff, DASHBOARD_LOOK.amber, letter("TC"));
49638
+ lamp(g, 152, 244, r.ignition && !!r.absOff, DASHBOARD_LOOK.amber, letter("ABS"));
48534
49639
  lamp(g, 86, 214, warn, DASHBOARD_LOOK.red, letter("OIL"));
48535
49640
  lamp(g, 170, 214, r.fourWheel, DASHBOARD_LOOK.green, letter("4x4"));
48536
49641
  bar(g, SIZE - 40, r.temperature, true, "T");
@@ -48566,7 +49671,7 @@ var GameVehiclesThree = (() => {
48566
49671
 
48567
49672
  // src/lamps.ts
48568
49673
  var LAMP_RULES = {
48569
- glow: { head: { off: 0.04, position: 0.3, low: 1, high: 1.35 }, tail: { off: 0.15, on: 0.7, brake: 1.6 }, fog: { off: 0.04, on: 1.1 }, amber: { off: 0.1, on: 2.2 } },
49674
+ glow: { head: { off: 0.04, position: 0.3, low: 1, high: 1.35 }, tail: { off: 0.15, on: 0.7, brake: 1.6 }, fog: { off: 0.04, on: 1.1 }, amber: { off: 0.1, on: 2.2 }, beacon: { off: 0.35, dark: 0.12, lit: 2.8 } },
48570
49675
  low: { color: 16773584, intensity: 2.4, distance: 32, angle: 0.42, penumbra: 0.55, decay: 1.6, drop: 0.09 },
48571
49676
  high: { color: 16774882, intensity: 3.6, distance: 70, angle: 0.3, penumbra: 0.45, decay: 1.4, drop: 0.015 },
48572
49677
  fog: { color: 16773320, intensity: 1.6, distance: 16, angle: 0.75, penumbra: 0.7, decay: 1.8, drop: 0.12 },
@@ -48583,7 +49688,11 @@ var GameVehiclesThree = (() => {
48583
49688
  if (o.userData?.lamp === "fog" && o.material && !bulbs.includes(o)) bulbs.push(o);
48584
49689
  });
48585
49690
  if (bulbs.length) lamps.push({ kind: "fog", inner: bulbs });
48586
- const has = { head: lamps.some((l) => l.kind === "headlight"), tail: lamps.some((l) => l.kind === "taillight"), fog: lamps.some((l) => l.kind === "fog"), indicators: false };
49691
+ const beacons = [];
49692
+ built2.group.traverse((o) => {
49693
+ if (o.userData?.lamp === "beacon" && o.material) beacons.push({ mesh: o, side: o.userData.beaconSide === 1 ? 1 : 0 });
49694
+ });
49695
+ const has = { head: lamps.some((l) => l.kind === "headlight"), tail: lamps.some((l) => l.kind === "taillight"), fog: lamps.some((l) => l.kind === "fog"), indicators: false, beacon: beacons.length > 0 };
48587
49696
  const originals = /* @__PURE__ */ new Map();
48588
49697
  const copies = /* @__PURE__ */ new Map();
48589
49698
  const base = /* @__PURE__ */ new Map();
@@ -48598,6 +49707,7 @@ var GameVehiclesThree = (() => {
48598
49707
  mesh2.material = copy;
48599
49708
  return copy;
48600
49709
  };
49710
+ for (const b of beacons) own(b.mesh);
48601
49711
  const haloBase = /* @__PURE__ */ new Map();
48602
49712
  for (const l of lamps) {
48603
49713
  for (const m of l.inner) own(m);
@@ -48673,6 +49783,8 @@ var GameVehiclesThree = (() => {
48673
49783
  else if (l.kind === "fog") glow(l, state.fog ? g.fog.on : g.fog.off);
48674
49784
  }
48675
49785
  for (const m of amber) glowMesh(m, state[sideOf.get(m) ?? "left"] ? g.amber.on : g.amber.off);
49786
+ const lit = state.beacon ?? null;
49787
+ for (const b of beacons) glowMesh(b.mesh, lit === null ? g.beacon.off : lit === b.side ? g.beacon.lit : g.beacon.dark);
48676
49788
  const beamId = state.mode === "low" || state.mode === "high" ? state.mode : null;
48677
49789
  for (const id of ["low", "high"]) beam(id, beamId === id, () => spots(front, LAMP_RULES[id]));
48678
49790
  beam("fog", state.fog && has.fog, () => spots(fogAnchors, LAMP_RULES.fog));
@@ -48686,7 +49798,7 @@ var GameVehiclesThree = (() => {
48686
49798
  return { ...state };
48687
49799
  },
48688
49800
  set(next) {
48689
- if (next.mode === state.mode && next.fog === state.fog && next.interior === state.interior && next.brake === state.brake && next.reverse === state.reverse && next.left === state.left && next.right === state.right) return;
49801
+ if (next.mode === state.mode && next.fog === state.fog && next.interior === state.interior && next.brake === state.brake && next.reverse === state.reverse && next.left === state.left && next.right === state.right && (next.beacon ?? null) === (state.beacon ?? null)) return;
48690
49802
  Object.assign(state, next);
48691
49803
  apply();
48692
49804
  },
@@ -48695,15 +49807,17 @@ var GameVehiclesThree = (() => {
48695
49807
  sources.clear();
48696
49808
  for (const a of anchors) a.removeFromParent();
48697
49809
  anchors.length = 0;
49810
+ const giveBack = (m) => {
49811
+ const copy = m.material;
49812
+ const original = originals.get(m);
49813
+ if (!original) return;
49814
+ originals.delete(m);
49815
+ m.material = original;
49816
+ if (copy && !Array.isArray(copy)) copy.dispose?.();
49817
+ };
49818
+ for (const b of beacons) giveBack(b.mesh);
48698
49819
  for (const l of lamps) {
48699
- for (const m of l.inner) {
48700
- const copy = m.material;
48701
- const original = originals.get(m);
48702
- if (original) {
48703
- m.material = original;
48704
- if (copy && !Array.isArray(copy)) copy.dispose?.();
48705
- }
48706
- }
49820
+ for (const m of l.inner) giveBack(m);
48707
49821
  for (const h of l.halo ?? []) {
48708
49822
  h.visible = true;
48709
49823
  const s = haloBase.get(h) ?? 1;
@@ -48717,14 +49831,85 @@ var GameVehiclesThree = (() => {
48717
49831
  };
48718
49832
  }
48719
49833
 
49834
+ // src/windshield-mist.ts
49835
+ var MIST_RULES = { color: 15001836, opacity: 0.72, others: 0.8 };
49836
+ var DOUBLE_SIDE = 2;
49837
+ function createWindshieldMist(THREE, built2) {
49838
+ const glass = (built2.context?.glass ?? []).filter((p) => p?.geometry);
49839
+ const films = [];
49840
+ for (const pane of glass) {
49841
+ const side2 = !Array.isArray(pane.material) && pane.material?.side || DOUBLE_SIDE;
49842
+ const material = new THREE.MeshBasicMaterial({ color: MIST_RULES.color, transparent: true, opacity: 0, depthWrite: false, side: side2, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1 });
49843
+ const mesh2 = new THREE.Mesh(pane.geometry, material);
49844
+ mesh2.name = "windshield_mist";
49845
+ mesh2.raycast = () => {
49846
+ };
49847
+ mesh2.userData.mist = true;
49848
+ mesh2.renderOrder = pane.renderOrder + 1;
49849
+ mesh2.visible = false;
49850
+ pane.add(mesh2);
49851
+ films.push({ mesh: mesh2, material, share: pane.userData.glass?.key === "windshield" ? 1 : MIST_RULES.others });
49852
+ }
49853
+ let fog = 0;
49854
+ return {
49855
+ panes: films.length,
49856
+ get fog() {
49857
+ return fog;
49858
+ },
49859
+ set(next) {
49860
+ const share = Math.max(0, Math.min(1, next));
49861
+ if (Math.abs(share - fog) < 5e-3 && share > 0 === fog > 0) return;
49862
+ fog = share;
49863
+ for (const f of films) {
49864
+ f.material.opacity = MIST_RULES.opacity * fog * f.share;
49865
+ f.mesh.visible = fog > 0.01;
49866
+ }
49867
+ },
49868
+ dispose() {
49869
+ for (const f of films) {
49870
+ f.mesh.removeFromParent();
49871
+ f.material.dispose();
49872
+ }
49873
+ films.length = 0;
49874
+ }
49875
+ };
49876
+ }
49877
+
48720
49878
  // src/test-area/proving/cockpit.ts
48721
- var DEFAULT_COCKPIT_SOUNDS = { horn: "horn", blinker: "blinker_tick", starter: "starter_crank", alarm: "car_alarm", chirp: "alarm_chirp", wiper: "wiper_swish", chime: "seatbelt_chime", radio: "radio_static" };
49879
+ var DEFAULT_COCKPIT_SOUNDS = {
49880
+ horn: "horn",
49881
+ blinker: "blinker_tick",
49882
+ starter: "starter_crank",
49883
+ alarm: "car_alarm",
49884
+ chirp: "alarm_chirp",
49885
+ wiper: "wiper_swish",
49886
+ chime: "seatbelt_chime",
49887
+ radio: "radio_static",
49888
+ beep: "parking_beep",
49889
+ wail: "siren_wail",
49890
+ yelp: "siren_yelp",
49891
+ spark: "hotwire_spark",
49892
+ glovebox: "glovebox_latch"
49893
+ };
49894
+ var BODY_KEYS = {
49895
+ Digit1: "door_front_left",
49896
+ Digit2: "door_front_right",
49897
+ Digit3: "door_rear_left",
49898
+ Digit4: "door_rear_right",
49899
+ Digit5: "hood",
49900
+ Digit6: "lid"
49901
+ };
49902
+ var WINDOW_KEYS = { Digit1: "front_left", Digit2: "front_right", Digit3: "rear_left", Digit4: "rear_right" };
49903
+ var LOCK_KEY = "KeyV";
49904
+ var WIPE_RATE = { off: 0, intermittent: 0.25, slow: 0.7, fast: 1.4 };
48722
49905
  function createCockpit(o) {
48723
49906
  const sounds = { ...DEFAULT_COCKPIT_SOUNDS, ...o.sounds };
48724
49907
  const spec = subjectSpec(o.subject);
48725
49908
  const driver = createSemiSimDriver(spec);
48726
49909
  driver.place(o.place[0], o.place[1], o.place[2]);
48727
49910
  const lamps = createVehicleLamps(o.THREE, o.built, { lights: o.host.lights, owner: "proving-ground" });
49911
+ const mist = createWindshieldMist(o.THREE, o.built);
49912
+ const fog = createWindshieldFog();
48728
49913
  const audio = o.host.audio;
48729
49914
  const top = o.subject.stats.maxSpeedKmh;
48730
49915
  const dashboard = o.gauge ? createVehicleDashboard(o.THREE, o.built, { gauge: o.gauge, needle: o.needle, redlineRpm: spec.engine.redlineRpm, maxKmh: top ? Math.ceil((top + 10) / 20) * 20 : void 0 }) : null;
@@ -48735,27 +49920,77 @@ var GameVehiclesThree = (() => {
48735
49920
  let wipeAgain = 0;
48736
49921
  let chimeAgain = 0;
48737
49922
  let radioAgain = 0;
49923
+ let beepAgain = 0;
49924
+ let bar = 0;
48738
49925
  let exitAsked = false;
49926
+ let sirenAgain = 0;
49927
+ let sparkAgain = 0;
49928
+ let wiresBefore = 0;
48739
49929
  let gas = 0;
49930
+ const systems = createVehicleSystems();
49931
+ const context = o.built.context;
49932
+ const destructible = o.kit && context ? createVehicleDestructible(o.kit, context, { id: "driven", frame: o.built.group }) : null;
49933
+ const body = destructible ? createVehicleBody(o.built, { THREE: o.THREE, clips: destructible.animations() }) : null;
49934
+ const compartments = [];
49935
+ if (!body || body.parts.some((p) => p.startsWith("door"))) compartments.push(GLOVEBOX);
49936
+ if (body?.parts.includes("lid")) compartments.push(trunkFor(o.trunkLitres));
49937
+ const storage = createVehicleStorage({ compartments });
49938
+ for (const c of compartments) storage.stock(c.id, o.stock?.(c.id) ?? []);
49939
+ const access = () => ({
49940
+ seated: true,
49941
+ frontDoorOpen: (body?.openness("door_front_left") ?? 0) >= 0.5 || (body?.openness("door_front_right") ?? 0) >= 0.5,
49942
+ gloveboxOpen: controls.state.glovebox,
49943
+ lid: body?.openness("lid") ?? 0
49944
+ });
49945
+ const bodyEvents = (events) => {
49946
+ for (const event of events) {
49947
+ if (event.type === "sound") play(event.key, 0.9);
49948
+ o.onEvent?.(event);
49949
+ }
49950
+ };
48740
49951
  const controls = createVehicleControls({
49952
+ canCrank: systems.canCrank,
49953
+ onCrank: systems.crank,
49954
+ powerLimit: systems.powerLimit,
48741
49955
  gears: spec.gears?.length ?? 5,
48742
49956
  fourWheelDrive: !!o.subject.body.fourWheelDrive,
48743
49957
  fogLamps: lamps.has.fog,
49958
+ clutchPedal: true,
49959
+ radioTracks: RADIO_TRACKS.length,
49960
+ hasKey: o.hasKey,
49961
+ emergency: lamps.has.beacon,
48744
49962
  onEvent: (event) => {
48745
49963
  if (event.type === "command") {
48746
- if (event.command === "ignition" && event.state.ignition && sounds.starter) play(sounds.starter);
48747
49964
  if (event.command === "ignition" && !event.state.ignition) driver.state.engineOn = false;
48748
49965
  if (event.command === "alarm" && event.state.alarm === "armed") play(sounds.chirp);
48749
49966
  if (event.command === "alarm") alarmAgain = 0;
48750
- if (event.command === "radio" || event.command === "radio_source") radioAgain = 0;
49967
+ if (event.command === "radio" || event.command === "radio_source" || event.command === "radio_track") {
49968
+ radioAgain = 0;
49969
+ bar = 0;
49970
+ }
48751
49971
  if (event.command === "exit") exitAsked = true;
49972
+ if (event.command === "siren") sirenAgain = 0;
49973
+ if (event.command === "glovebox") play(sounds.glovebox, 0.8);
48752
49974
  }
48753
49975
  o.onEvent?.(event);
48754
49976
  }
48755
49977
  });
48756
49978
  driver.state.engineOn = false;
48757
49979
  const target = typeof window !== "undefined" ? window : null;
49980
+ const bodyKey = (e) => {
49981
+ if (!body) return false;
49982
+ if (e.code === LOCK_KEY) bodyEvents(body.lock(!body.locked));
49983
+ else if (e.shiftKey && e.code === "Digit5") body.toggleSunroof();
49984
+ else if (e.shiftKey && WINDOW_KEYS[e.code]) body.toggleWindow(WINDOW_KEYS[e.code]);
49985
+ else if (!e.shiftKey && BODY_KEYS[e.code]) bodyEvents(body.toggle(BODY_KEYS[e.code]));
49986
+ else return false;
49987
+ return true;
49988
+ };
48758
49989
  const down = (e) => {
49990
+ if (!e.repeat && bodyKey(e)) {
49991
+ e.stopImmediatePropagation();
49992
+ return;
49993
+ }
48759
49994
  if (!controls.key(e.code, true)) return;
48760
49995
  e.stopImmediatePropagation();
48761
49996
  if (e.code.startsWith("Arrow") || e.code === "Space") e.preventDefault();
@@ -48767,9 +50002,19 @@ var GameVehiclesThree = (() => {
48767
50002
  target?.addEventListener("keydown", down, true);
48768
50003
  target?.addEventListener("keyup", up, true);
48769
50004
  target?.addEventListener("blur", blur);
48770
- return {
50005
+ const cockpit = {
50006
+ radiator: 1,
50007
+ storage,
50008
+ access,
50009
+ fog,
50010
+ mist,
50011
+ weather: { ...o.weather ?? { outsideC: 25, rain: 0 } },
50012
+ emergency: lamps.has.beacon,
48771
50013
  driver,
48772
50014
  controls,
50015
+ systems,
50016
+ body,
50017
+ destructible,
48773
50018
  lamps,
48774
50019
  dashboard,
48775
50020
  get throttle() {
@@ -48778,18 +50023,39 @@ var GameVehiclesThree = (() => {
48778
50023
  step(dt) {
48779
50024
  const input = controls.input(dt, driver);
48780
50025
  gas = input.throttle;
48781
- driver.step(input, dt);
50026
+ if (input.start && sounds.starter) play(sounds.starter);
50027
+ const engine = driver.step(input, dt).events;
48782
50028
  controls.after(driver, dt);
48783
50029
  const s = controls.state;
50030
+ const report = [...engine.filter((e) => e.type !== "shift"), ...systems.step({ driver, controls: s, throttle: input.throttle, dt, radiator: cockpit.radiator })];
50031
+ for (const event of report) o.onEvent?.(event);
50032
+ const powered = driver.state.engineOn || systems.state.charge > 0;
50033
+ const high = s.lamps.mode === "high" || s.lamps.flash;
48784
50034
  lamps.set({
48785
- mode: s.lamps.mode,
50035
+ mode: !powered ? "off" : s.lamps.flash ? "high" : s.lamps.mode,
48786
50036
  fog: s.lamps.fog,
48787
50037
  interior: s.lamps.interior,
48788
50038
  brake: s.brakeLights,
48789
50039
  reverse: s.reverseLights,
48790
50040
  left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
48791
- right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard")
48792
- });
50041
+ right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
50042
+ beacon: s.beacon && powered ? s.beaconSide : null
50043
+ });
50044
+ const w = cockpit.weather;
50045
+ const windows = body ? body.windows.reduce((sum, id) => sum + body.window(id), 0) / Math.max(1, body.windows.length) : 0;
50046
+ const warm = systems.state.temperature;
50047
+ const cabinC = s.climate.on ? 22 : w.outsideC + (driver.state.engineOn ? 3 + 5 * Math.min(1, warm * 2) : 2);
50048
+ mist.set(fog.step({ dt, occupants: 1, outsideC: w.outsideC, cabinC, rain: w.rain, speed: Math.abs(driver.state.u), windows, climate: s.climate, engineWarm: driver.state.engineOn ? Math.min(1, warm * 2) : 0 }).fog);
50049
+ if (s.siren !== "off" && powered && (sirenAgain -= dt) <= 0) {
50050
+ play(s.siren === "wail" ? sounds.wail : sounds.yelp);
50051
+ sirenAgain = s.siren === "wail" ? 2.95 : 1.95;
50052
+ }
50053
+ const working = s.hotwire.progress > wiresBefore;
50054
+ wiresBefore = s.hotwire.progress;
50055
+ if (working && (sparkAgain -= dt) <= 0) {
50056
+ play(sounds.spark, 0.7);
50057
+ sparkAgain = 0.45 + 0.4 * (s.hotwire.progress * 7 % 1);
50058
+ }
48793
50059
  dashboard?.update(
48794
50060
  {
48795
50061
  kmh: Math.abs(driver.state.u) * 3.6,
@@ -48799,7 +50065,14 @@ var GameVehiclesThree = (() => {
48799
50065
  engineOn: driver.state.engineOn,
48800
50066
  left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
48801
50067
  right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
48802
- highBeam: s.lamps.mode === "high",
50068
+ highBeam: high,
50069
+ fuel: driver.state.fuel / spec.tank.capacity,
50070
+ temperature: systems.state.temperature,
50071
+ cruise: s.cruise.on,
50072
+ profile: s.profile,
50073
+ tractionOff: !s.tractionControl,
50074
+ absOff: !s.abs,
50075
+ charging: systems.state.netW >= 0,
48803
50076
  lowBeam: s.lamps.mode === "low" || s.lamps.mode === "position",
48804
50077
  fog: s.lamps.fog,
48805
50078
  parkingBrake: s.parkingBrake,
@@ -48808,7 +50081,19 @@ var GameVehiclesThree = (() => {
48808
50081
  },
48809
50082
  dt
48810
50083
  );
50084
+ if (body) bodyEvents(body.update(dt, s.washer ? 1.2 : WIPE_RATE[s.wipers] ?? 0));
48811
50085
  if (s.tick) play(sounds.blinker, 0.8);
50086
+ const near = o.obstacles?.() ?? [];
50087
+ if (s.ignition && near.length && Math.abs(driver.state.u) < 3) {
50088
+ const g = o.built.group;
50089
+ const reversing = driver.state.gear < 0 || driver.state.u < -0.05;
50090
+ const distance = bumperDistance({ x: g.position.x, z: g.position.z, heading: [Math.sin(g.rotation.y), Math.cos(g.rotation.y)], length: o.built.length, width: o.built.width, forward: !reversing, obstacles: near });
50091
+ const every = parkingBeepInterval(distance);
50092
+ if (every != null && (beepAgain -= dt) <= 0) {
50093
+ play(sounds.beep, 0.7);
50094
+ beepAgain = every === 0 ? 0.09 : every;
50095
+ }
50096
+ }
48812
50097
  if (s.horn && (hornAgain -= dt) <= 0) {
48813
50098
  play(sounds.horn);
48814
50099
  hornAgain = 0.5;
@@ -48817,9 +50102,9 @@ var GameVehiclesThree = (() => {
48817
50102
  play(sounds.alarm);
48818
50103
  alarmAgain = 2.9;
48819
50104
  }
48820
- if (s.wipers !== "off" && (wipeAgain -= dt) <= 0) {
50105
+ if ((s.wipers !== "off" || s.washer) && (wipeAgain -= dt) <= 0) {
48821
50106
  play(sounds.wiper, 0.7);
48822
- wipeAgain = s.wipers === "fast" ? 0.6 : 1.4;
50107
+ wipeAgain = s.washer || s.wipers === "fast" ? 0.6 : s.wipers === "slow" ? 1.4 : 4;
48823
50108
  }
48824
50109
  const kmh2 = Math.abs(driver.state.u) * 3.6;
48825
50110
  if (!s.seatbelt && s.ignition && kmh2 > 10 && (chimeAgain -= dt) <= 0) {
@@ -48827,8 +50112,13 @@ var GameVehiclesThree = (() => {
48827
50112
  chimeAgain = 6;
48828
50113
  }
48829
50114
  if (s.radio.on && (radioAgain -= dt) <= 0) {
48830
- play(sounds.radio, s.radio.source === "tape" ? 0.25 : 0.45);
48831
- radioAgain = 0.55;
50115
+ const song = RADIO_TRACKS[s.radio.track % RADIO_TRACKS.length];
50116
+ if (song) {
50117
+ play(song.bars[bar % song.bars.length], s.radio.source === "tape" ? 0.8 : 0.7);
50118
+ bar++;
50119
+ }
50120
+ if (s.radio.source === "radio") play(sounds.radio, 0.12);
50121
+ radioAgain = song?.barSeconds ?? 2;
48832
50122
  }
48833
50123
  if (exitAsked) {
48834
50124
  exitAsked = false;
@@ -48841,11 +50131,174 @@ var GameVehiclesThree = (() => {
48841
50131
  target?.removeEventListener("blur", blur);
48842
50132
  controls.release();
48843
50133
  lamps.dispose();
50134
+ mist.dispose();
50135
+ storage.clear();
48844
50136
  dashboard?.dispose();
50137
+ body?.dispose();
50138
+ destructible?.dispose?.();
50139
+ }
50140
+ };
50141
+ return cockpit;
50142
+ }
50143
+
50144
+ // src/wheel-roll.ts
50145
+ var DEFAULT_RADIUS = 0.32;
50146
+ function createWheelRoll(THREE, built2) {
50147
+ const wheels = (built2.context?.wheels ?? []).map((wheel) => ({
50148
+ wheel,
50149
+ rest: wheel.quaternion.clone(),
50150
+ radius: Number(wheel.userData.r) || DEFAULT_RADIUS,
50151
+ // in the kit's frame the front is +x: the wheels ahead of the middle steer
50152
+ front: wheel.position.x > 0,
50153
+ angle: 0
50154
+ }));
50155
+ const axle = new THREE.Vector3(0, 0, 1);
50156
+ const up = new THREE.Vector3(0, 1, 0);
50157
+ const spin = new THREE.Quaternion();
50158
+ const steering = new THREE.Quaternion();
50159
+ return {
50160
+ count: wheels.length,
50161
+ roll(distance, steer) {
50162
+ for (const w of wheels) {
50163
+ w.angle = (w.angle - distance / w.radius) % (Math.PI * 2);
50164
+ spin.setFromAxisAngle(axle, w.angle);
50165
+ steering.setFromAxisAngle(up, w.front ? steer : 0);
50166
+ w.wheel.quaternion.copy(w.rest).premultiply(spin).premultiply(steering);
50167
+ }
50168
+ },
50169
+ dispose() {
50170
+ for (const w of wheels) w.wheel.quaternion.copy(w.rest);
48845
50171
  }
48846
50172
  };
48847
50173
  }
48848
50174
 
50175
+ // src/test-area/proving/crash.ts
50176
+ var GLASS_BLOW = 6e3;
50177
+ function createProvingCrash(host, occupant) {
50178
+ const thrown = [];
50179
+ const breakGlass = (destructible, exit, forward) => {
50180
+ if (!destructible) return;
50181
+ const want = exit === "windshield" ? "windshield" : exit === "rear_window" ? "rear" : exit.startsWith("left") ? "left" : "right";
50182
+ const pane = destructible.describe().parts.find((p) => p.id.includes(exit === "windshield" || exit === "rear_window" ? want : "window") && (exit === "windshield" || exit === "rear_window" || p.id.includes(want)));
50183
+ if (!pane) return;
50184
+ destructible.hit({ type: "blunt", partId: pane.id, point: destructible.partWorldCenter(pane.id), direction: forward, energy: GLASS_BLOW, radius: 0.35, source: "occupant" });
50185
+ };
50186
+ return {
50187
+ crash({ car, u, w, belted, destructible }) {
50188
+ const ejection = ejectionOf({ before: { u, w }, after: { u: 0, w: 0 } }, belted);
50189
+ if (!ejection) return null;
50190
+ const s = Math.sin(car.rotationY);
50191
+ const c = Math.cos(car.rotationY);
50192
+ const forward = [s, 0, c];
50193
+ const right = [-c, 0, s];
50194
+ breakGlass(destructible, ejection.exit, forward);
50195
+ const [vf, vr, vu] = ejection.velocity;
50196
+ const start2 = [car.position.x + forward[0] * 1.2, 1.1, car.position.z + forward[2] * 1.2];
50197
+ const placed = occupant ? host.addObject?.(occupant, { position: start2, rotationY: car.rotationY }) : null;
50198
+ if (placed) {
50199
+ launchBody(placed.root, [forward[0] * vf + right[0] * vr, vu, forward[2] * vf + right[2] * vr]);
50200
+ thrown.push(placed.remove);
50201
+ }
50202
+ return ejection;
50203
+ },
50204
+ dispose() {
50205
+ for (const remove of thrown.splice(0)) remove();
50206
+ }
50207
+ };
50208
+ }
50209
+
50210
+ // src/test-area/proving/systems-text.ts
50211
+ var SYSTEMS_TEXT = {
50212
+ "pt-BR": {
50213
+ keys: "C embreagem (c\xE2mbio manual) \xB7 X lampejo do farol alto \xB7 Z esguicho do limpador \xB7 7 modo econ\xF4mico/normal/esporte \xB7 8 controle de tra\xE7\xE3o \xB7 9 ABS \xB7 0 piloto autom\xE1tico \xB7 - ar-condicionado \xB7 = desemba\xE7ador \xB7 ] troca a faixa do r\xE1dio ou da fita \xB7 ; sirene \xB7 ' giroflex (ve\xEDculo de emerg\xEAncia) \xB7 / porta-luvas \xB7 sem a chave, segure I: liga\xE7\xE3o direta",
50214
+ profile: "Modo de condu\xE7\xE3o",
50215
+ profiles: { eco: "econ\xF4mico", normal: "normal", sport: "esporte" },
50216
+ traction: "Controle de tra\xE7\xE3o",
50217
+ abs: "ABS",
50218
+ cruise: "Piloto autom\xE1tico",
50219
+ cruiseSet: "Piloto autom\xE1tico: segurando {0} km/h",
50220
+ climate: "Ar-condicionado",
50221
+ demist: "Desemba\xE7ador",
50222
+ wiperModes: { off: "desligado", intermittent: "intermitente", slow: "lento", fast: "r\xE1pido" },
50223
+ refused: { battery_flat: "Bateria descarregada: o motor de partida n\xE3o gira", too_slow: "Piloto autom\xE1tico s\xF3 acima de 30 km/h", locked: "Porta travada", missing: "Este ve\xEDculo n\xE3o tem essa pe\xE7a", no_key: "Sem a chave: segure I para fazer a liga\xE7\xE3o direta", no_siren: "Este ve\xEDculo n\xE3o tem sirene nem giroflex" },
50224
+ events: {
50225
+ stall: "O motor morreu",
50226
+ out_of_fuel: "Acabou o combust\xEDvel",
50227
+ bogged: "Atolou",
50228
+ aquaplaning: "Aquaplanagem",
50229
+ battery_flat: "A bateria descarregou",
50230
+ overheating: "Motor superaquecendo: perdendo pot\xEAncia",
50231
+ seized: "O motor fundiu de tanto esquentar",
50232
+ spare_fitted: "Estepe colocado",
50233
+ no_spare: "N\xE3o h\xE1 estepe",
50234
+ cruise_cancelled: "Piloto autom\xE1tico desligado",
50235
+ hotwire_started: "Fios descascados sob a coluna: continue segurando I",
50236
+ hotwire_joined: "Liga\xE7\xE3o direta feita: a igni\xE7\xE3o pegou e o motor de partida gira"
50237
+ },
50238
+ actions: { puncture: "Furar pneu (dianteiro esquerdo)", fit_spare: "Trocar pelo estepe", refuel: "Abastecer", drain_battery: "Descarregar bateria", jump_start: "Chupeta (carregar bateria)", break_radiator: "Furar o radiador", empty_tank: "Deixar o tanque na reserva", remote_lock: "Controle: travar (arma o alarme)", remote_unlock: "Controle: destravar", force_door: "For\xE7ar a porta do motorista", crash_wall: "Bater no muro (parada seca)", park_behind: "Estacionar um carro atr\xE1s (sensor de r\xE9)", take_key: "Tirar a chave (liga\xE7\xE3o direta) / devolver", cold_rain: "Tempo: frio e chuva / bom", glovebox_contents: "Ver o porta-luvas", trunk_contents: "Ver o porta-malas", take_from_trunk: "Tirar um item do porta-malas para o ch\xE3o" },
50239
+ siren: "Sirene",
50240
+ sirenModes: { off: "desligada", wail: "cont\xEDnua", yelp: "r\xE1pida (cruzamento)" },
50241
+ beacon: "Giroflex",
50242
+ glovebox: "Porta-luvas",
50243
+ lid: { open: "aberto", shut: "fechado" },
50244
+ storage: { names: { glovebox: "Porta-luvas", trunk: "Porta-malas" }, empty: "{0}: vazio", shut: "{0} fechado: abra antes (porta-luvas com / sentado, porta-malas com 6)", list: "{0} ({1} kg): {2}", none: "Este ve\xEDculo n\xE3o tem {0}", taken: "{0} tirado do porta-malas e posto no ch\xE3o", ground: "{0} tirado do porta-malas (sem cat\xE1logo de itens nesta sala para p\xF4-lo no ch\xE3o)" },
50245
+ fog: { misted: "O para-brisa emba\xE7ou por dentro: = desemba\xE7ador, ar-condicionado ou um vidro aberto", clear: "Para-brisa desemba\xE7ado", weather: "L\xE1 fora: 8 \xB0C e chuva", fair: "L\xE1 fora: 25 \xB0C, sem chuva", metric: "Vidro emba\xE7ado" },
50246
+ keyTaken: "Sem a chave no contato: segure I para a liga\xE7\xE3o direta",
50247
+ keyBack: "Chave de volta no contato",
50248
+ parking: { placed: "Carro parado 3 m atr\xE1s: d\xEA r\xE9 e escute o sensor", none: "Sem cat\xE1logo de ve\xEDculos nesta sala: o sensor n\xE3o tem o que ver" },
50249
+ track: "faixa {0}",
50250
+ adjust: { seat_forward: "Banco: frente/tr\xE1s (m)", seat_height: "Banco: altura (m)", mirror_left: "Retrovisor esquerdo: \xE2ngulo", mirror_right: "Retrovisor direito: \xE2ngulo" },
50251
+ crash: { mild: "Batida a {0} km/h: fraca demais para lan\xE7ar quem est\xE1 sem cinto", stayed: "Batida a {0} km/h: o cinto segurou o motorista", ejected: "Batida a {0} km/h sem cinto: o motorista saiu pelo {1}", exits: { windshield: "para-brisa", left_window: "vidro esquerdo", right_window: "vidro direito", rear_window: "vidro traseiro" } },
50252
+ bodyKeys: "1 a 4 portas \xB7 5 cap\xF4 \xB7 6 porta-malas \xB7 Shift+1 a 4 vidros \xB7 Shift+5 teto solar \xB7 V trava as portas",
50253
+ remote: { locked: "Travado pelo controle: alarme armado", unlocked: "Destravado pelo controle", forced: "Porta for\xE7ada" },
50254
+ done: { puncture: "Pneu dianteiro esquerdo furado", refuel: "Tanque cheio", drain_battery: "Bateria quase sem carga", jump_start: "Bateria carregada", break_radiator: "Radiador furado: o motor vai esquentar", empty_tank: "Tanque na reserva" }
50255
+ },
50256
+ en: {
50257
+ keys: "C clutch (manual gearbox) \xB7 X flash the high beam \xB7 Z washer \xB7 7 eco/normal/sport \xB7 8 traction control \xB7 9 ABS \xB7 0 cruise control \xB7 - air conditioning \xB7 = demister \xB7 ] next radio or tape track \xB7 ; siren \xB7 ' beacon (emergency vehicle) \xB7 / glovebox \xB7 without the key, hold I: hotwire",
50258
+ profile: "Drive profile",
50259
+ profiles: { eco: "eco", normal: "normal", sport: "sport" },
50260
+ traction: "Traction control",
50261
+ abs: "ABS",
50262
+ cruise: "Cruise control",
50263
+ cruiseSet: "Cruise control: holding {0} km/h",
50264
+ climate: "Air conditioning",
50265
+ demist: "Demister",
50266
+ wiperModes: { off: "off", intermittent: "intermittent", slow: "slow", fast: "fast" },
50267
+ refused: { battery_flat: "Flat battery: the starter does not turn", too_slow: "Cruise control only above 30 km/h", locked: "The door is locked", missing: "This vehicle has no such part", no_key: "No key: hold I to hotwire it", no_siren: "This vehicle has no siren or beacon" },
50268
+ events: {
50269
+ stall: "The engine stalled",
50270
+ out_of_fuel: "Out of fuel",
50271
+ bogged: "Bogged down",
50272
+ aquaplaning: "Aquaplaning",
50273
+ battery_flat: "The battery went flat",
50274
+ overheating: "Engine overheating: losing power",
50275
+ seized: "The engine seized from the heat",
50276
+ spare_fitted: "Spare tyre fitted",
50277
+ no_spare: "No spare tyre left",
50278
+ cruise_cancelled: "Cruise control off",
50279
+ hotwire_started: "Wires stripped under the column: keep holding I",
50280
+ hotwire_joined: "Hotwired: the ignition is live and the starter turns"
50281
+ },
50282
+ actions: { puncture: "Puncture a tyre (front left)", fit_spare: "Fit the spare", refuel: "Refuel", drain_battery: "Drain the battery", jump_start: "Jump start (charge the battery)", break_radiator: "Puncture the radiator", empty_tank: "Leave the tank on reserve", remote_lock: "Remote: lock (arms the alarm)", remote_unlock: "Remote: unlock", force_door: "Force the driver's door", crash_wall: "Hit a wall (dead stop)", park_behind: "Park a car behind (rear sensor)", take_key: "Take the key out (hotwire) / give it back", cold_rain: "Weather: cold and rain / fair", glovebox_contents: "Look in the glovebox", trunk_contents: "Look in the trunk", take_from_trunk: "Take an item out of the trunk to the ground" },
50283
+ siren: "Siren",
50284
+ sirenModes: { off: "off", wail: "wail", yelp: "yelp (junction)" },
50285
+ beacon: "Beacon",
50286
+ glovebox: "Glovebox",
50287
+ lid: { open: "open", shut: "shut" },
50288
+ storage: { names: { glovebox: "Glovebox", trunk: "Trunk" }, empty: "{0}: empty", shut: "{0} shut: open it first (glovebox with / while seated, trunk with 6)", list: "{0} ({1} kg): {2}", none: "This vehicle has no {0}", taken: "{0} taken out of the trunk and put on the ground", ground: "{0} taken out of the trunk (no item catalogue in this room to put it on the ground)" },
50289
+ fog: { misted: "The windshield misted up inside: = demister, air conditioning or a window down", clear: "Windshield clear", weather: "Outside: 8 \xB0C and rain", fair: "Outside: 25 \xB0C, no rain", metric: "Glass misted" },
50290
+ keyTaken: "No key in the ignition: hold I to hotwire",
50291
+ keyBack: "Key back in the ignition",
50292
+ parking: { placed: "A car parked 3 m behind: reverse and listen to the sensor", none: "No vehicle catalogue in this room: the sensor has nothing to see" },
50293
+ track: "track {0}",
50294
+ adjust: { seat_forward: "Seat: forward/back (m)", seat_height: "Seat: height (m)", mirror_left: "Left mirror: angle", mirror_right: "Right mirror: angle" },
50295
+ crash: { mild: "Crash at {0} km/h: too mild to throw an unbelted occupant out", stayed: "Crash at {0} km/h: the belt held the driver", ejected: "Crash at {0} km/h without a belt: the driver went out through the {1}", exits: { windshield: "windshield", left_window: "left window", right_window: "right window", rear_window: "rear window" } },
50296
+ bodyKeys: "1 to 4 doors \xB7 5 hood \xB7 6 trunk \xB7 Shift+1 to 4 windows \xB7 Shift+5 sunroof \xB7 V locks the doors",
50297
+ remote: { locked: "Locked by the remote: alarm armed", unlocked: "Unlocked by the remote", forced: "Door forced" },
50298
+ done: { puncture: "Front left tyre punctured", refuel: "Tank full", drain_battery: "Battery almost flat", jump_start: "Battery charged", break_radiator: "Radiator punctured: the engine will heat up", empty_tank: "Tank on reserve" }
50299
+ }
50300
+ };
50301
+
48849
50302
  // src/test-area/proving/text.ts
48850
50303
  var PROVING_TEXT = {
48851
50304
  "pt-BR": {
@@ -48990,9 +50443,11 @@ var GameVehiclesThree = (() => {
48990
50443
  right: { x: 0.79, y: 0.72, width: 0.19, height: 0.13, rounded: 0.3 },
48991
50444
  reverse: { x: 0.36, y: 0.13, width: 0.28, height: 0.18, rounded: 0.06 }
48992
50445
  };
50446
+ var ADJUST_KEYS = { seat_forward: "seatForward", seat_height: "seatHeight", mirror_left: "mirrorLeft", mirror_right: "mirrorRight" };
48993
50447
  function createProvingModule(options) {
48994
50448
  const locale = options.locale ?? "pt-BR";
48995
50449
  const text = PROVING_TEXT[locale] ?? PROVING_TEXT["pt-BR"];
50450
+ const sysText = SYSTEMS_TEXT[locale] ?? SYSTEMS_TEXT["pt-BR"];
48996
50451
  const tests = options.tests ?? createProvingTests();
48997
50452
  const vehicles = options.vehicles;
48998
50453
  const sounds = { ...defaultSoundIds, ...options.sounds };
@@ -49009,7 +50464,19 @@ var GameVehiclesThree = (() => {
49009
50464
  let run = null;
49010
50465
  let parking = null;
49011
50466
  let driving = null;
50467
+ let crashes = null;
50468
+ let wheels = null;
50469
+ let lastAt = null;
50470
+ let parked = null;
50471
+ const adjust = { seatForward: 0, seatHeight: 0, mirrorLeft: 0, mirrorRight: 0 };
50472
+ const aimed = (pose2) => {
50473
+ const turn = pose2.id === "left" ? adjust.mirrorLeft : pose2.id === "right" ? -adjust.mirrorRight : 0;
50474
+ return turn ? { ...pose2, look: [pose2.look[0] + turn, pose2.look[1], pose2.look[2]] } : pose2;
50475
+ };
49012
50476
  let gearbox = "automatic";
50477
+ let hasKey = true;
50478
+ let weather = { outsideC: 25, rain: 0 };
50479
+ let misted = false;
49013
50480
  let lastGear = null;
49014
50481
  let driveClock = 0;
49015
50482
  let queue = [];
@@ -49170,6 +50637,8 @@ var GameVehiclesThree = (() => {
49170
50637
  };
49171
50638
  const removeCar = () => {
49172
50639
  if (!car) return;
50640
+ wheels = null;
50641
+ lastAt = null;
49173
50642
  discardObject3D(car.built.group);
49174
50643
  car = null;
49175
50644
  };
@@ -49183,6 +50652,7 @@ var GameVehiclesThree = (() => {
49183
50652
  built2.group.add(guides);
49184
50653
  host.scene.add(built2.group);
49185
50654
  car = { built: built2, guides, subject };
50655
+ wheels = createWheelRoll(three, built2);
49186
50656
  pose({ x: 0, z: TRACK.start, yaw: 0, pitch: 0, roll: 0, height: 0 }, 0);
49187
50657
  rig.vehicle?.reset();
49188
50658
  drawBoard();
@@ -49233,11 +50703,18 @@ var GameVehiclesThree = (() => {
49233
50703
  car.built.group.position.set(TRACK.x0 + wrapped, 0, TRACK.z + s.z);
49234
50704
  car.built.group.rotation.set(-s.pitch, s.yaw + Math.PI / 2, s.roll, "YXZ");
49235
50705
  bendGuides(steer);
50706
+ if (lastAt) {
50707
+ const along = (s.x - lastAt.x) * Math.cos(s.yaw) - (s.z - lastAt.z) * Math.sin(s.yaw);
50708
+ if (Math.abs(along) < 5) wheels?.roll(along, steer);
50709
+ }
50710
+ lastAt = { x: s.x, z: s.z };
49236
50711
  };
49237
50712
  const frameOf = (speed) => {
49238
50713
  if (!car) return null;
49239
50714
  const g = car.built.group;
49240
- return { x: g.position.x, y: g.position.y, z: g.position.z, yaw: g.rotation.y, pitch: -g.rotation.x, roll: g.rotation.z, speed, length: car.built.length, width: car.built.width, height: car.built.height, ...car.built.driverEye ? { eye: car.built.driverEye } : {} };
50715
+ const size = { length: car.built.length, width: car.built.width, height: car.built.height };
50716
+ const [ex, ey, ez] = car.built.driverEye ?? defaultEye(size);
50717
+ return { x: g.position.x, y: g.position.y, z: g.position.z, yaw: g.rotation.y, pitch: -g.rotation.x, roll: g.rotation.z, speed, ...size, eye: [ex, ey + adjust.seatHeight, ez + adjust.seatForward] };
49241
50718
  };
49242
50719
  const applyCamera = () => {
49243
50720
  if (!host?.setViewCamera) return;
@@ -49319,26 +50796,42 @@ var GameVehiclesThree = (() => {
49319
50796
  run = null;
49320
50797
  queue = [];
49321
50798
  parking = null;
49322
- setSurface("dry_asphalt");
50799
+ setSurface(weather.rain > 0 ? "wet_asphalt" : "dry_asphalt");
50800
+ misted = false;
49323
50801
  driving = createCockpit({
49324
50802
  THREE: three,
49325
50803
  host,
49326
50804
  built: car.built,
49327
50805
  subject: car.subject,
49328
50806
  place: [0, 0, 0],
50807
+ kit: kitFor(three),
50808
+ obstacles: () => parked ? [[parked.root.position.x, parked.root.position.z]] : [],
49329
50809
  gauge: kitFor(three).materials?.gauge,
49330
50810
  needle: kitFor(three).materials?.amber,
49331
50811
  // the engine's own sound turns the key (starter, catch, idle by RPM)
49332
50812
  sounds: { starter: "" },
50813
+ hasKey,
50814
+ weather,
50815
+ stock: (compartment) => options.stock?.(compartment, settings.vehicle) ?? [],
49333
50816
  onExit: () => stopDriving(),
49334
50817
  onEvent: (event) => {
49335
- if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? event.reason);
50818
+ if (event.type === "hotwire") {
50819
+ host?.log(sysText.events[event.stage === "joined" ? "hotwire_joined" : "hotwire_started"], { vehicle: settings.vehicle, stage: event.stage }, "proving_system");
50820
+ if (event.stage === "joined" && car && driving?.systems.canCrank() !== false) engine?.start([car.built.group.position.x, 0.5, car.built.group.position.z]);
50821
+ return;
50822
+ }
50823
+ if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? sysText.refused[event.reason] ?? event.reason);
50824
+ if (event.type === "sound") return;
50825
+ if (event.type !== "command") {
50826
+ if (event.type === "out_of_fuel" || event.type === "stall" || event.type === "seized") engine?.stop([car?.built.group.position.x ?? 0, 0.5, car?.built.group.position.z ?? 0]);
50827
+ return host?.log(sysText.events[event.type] ?? event.type, { vehicle: settings.vehicle, ...event }, "proving_system");
50828
+ }
49336
50829
  const st = event.state;
49337
50830
  const line = cabinLine(event.command, st);
49338
50831
  if (line) host?.log(line);
49339
50832
  if (event.command === "ignition" && car) {
49340
50833
  const at = [car.built.group.position.x, 0.5, car.built.group.position.z];
49341
- if (st.ignition) engine?.start(at);
50834
+ if (st.ignition && driving?.systems.canCrank() !== false) engine?.start(at);
49342
50835
  else engine?.stop(at);
49343
50836
  }
49344
50837
  if (event.command === "gearbox") {
@@ -49357,7 +50850,7 @@ var GameVehiclesThree = (() => {
49357
50850
  rig.vehicle?.reset();
49358
50851
  stopEngine();
49359
50852
  engine = createVehicleSound(host.audio, { sounds });
49360
- host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: text.cabin.keys });
50853
+ host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: `${text.cabin.keys} \xB7 ${sysText.keys} \xB7 ${sysText.bodyKeys}` });
49361
50854
  host.log(fillText(text.log.drive, name(settings.vehicle)));
49362
50855
  };
49363
50856
  const onOff = (on) => on ? text.cabin.on : text.cabin.off;
@@ -49386,19 +50879,156 @@ var GameVehiclesThree = (() => {
49386
50879
  return `${c.alarm}: ${c.alarmStates[st.alarm]}`;
49387
50880
  case "radio":
49388
50881
  case "radio_source":
49389
- return `${c.radio}: ${st.radio.on ? c.sources[st.radio.source] : c.off}`;
50882
+ case "radio_track":
50883
+ return `${c.radio}: ${st.radio.on ? `${c.sources[st.radio.source]} \xB7 ${fillText(sysText.track, st.radio.track + 1)}` : c.off}`;
49390
50884
  case "indicator_left":
49391
50885
  case "indicator_right":
49392
50886
  case "hazard":
49393
50887
  return `${c.indicator}: ${c.indicators[st.indicator]}`;
49394
50888
  case "wipers":
49395
- return `${c.wipers}: ${c.wiperModes[st.wipers]}`;
50889
+ return `${c.wipers}: ${sysText.wiperModes[st.wipers]}`;
50890
+ case "drive_profile":
50891
+ return `${sysText.profile}: ${sysText.profiles[st.profile]}`;
50892
+ case "traction_control":
50893
+ return `${sysText.traction}: ${onOff(st.tractionControl)}`;
50894
+ case "abs":
50895
+ return `${sysText.abs}: ${onOff(st.abs)}`;
50896
+ case "cruise":
50897
+ return st.cruise.on ? fillText(sysText.cruiseSet, Math.round(st.cruise.speed * 3.6)) : `${sysText.cruise}: ${c.off}`;
50898
+ case "climate":
50899
+ return `${sysText.climate}: ${onOff(st.climate.on)}`;
50900
+ case "demist":
50901
+ return `${sysText.demist}: ${onOff(st.climate.demist)}`;
49396
50902
  case "seatbelt":
49397
50903
  return `${c.seatbelt}: ${onOff(st.seatbelt)}`;
50904
+ case "siren":
50905
+ return `${sysText.siren}: ${sysText.sirenModes[st.siren]}`;
50906
+ case "beacon":
50907
+ return `${sysText.beacon}: ${onOff(st.beacon)}`;
50908
+ case "glovebox":
50909
+ return `${sysText.glovebox}: ${sysText.lid[st.glovebox ? "open" : "shut"]}`;
49398
50910
  default:
49399
50911
  return null;
49400
50912
  }
49401
50913
  };
50914
+ const itemName = (id) => options.itemName?.(id) ?? id;
50915
+ const listCompartment = (cockpit, id) => {
50916
+ const label = sysText.storage.names[id];
50917
+ const data = { vehicle: settings.vehicle, compartment: id };
50918
+ if (!cockpit.storage.compartments.some((c) => c.id === id)) return host?.log(fillText(sysText.storage.none, label.toLowerCase()), data, "proving_storage");
50919
+ if (!cockpit.storage.reachable(id, cockpit.access())) return host?.log(fillText(sysText.storage.shut, label), data, "proving_storage");
50920
+ const stacks = cockpit.storage.contents(id).filter((x) => x !== null);
50921
+ if (!stacks.length) return host?.log(fillText(sysText.storage.empty, label), data, "proving_storage");
50922
+ host?.log(fillText(sysText.storage.list, label, cockpit.storage.loadKg(id).toFixed(1), stacks.map((x) => x.count > 1 ? `${x.count}\xD7 ${itemName(x.itemId)}` : itemName(x.itemId)).join(", ")), { ...data, stacks: stacks.map((x) => ({ item: x.itemId, count: x.count })) }, "proving_storage");
50923
+ };
50924
+ const systemsAction = (id) => {
50925
+ if (id === "take_key") {
50926
+ hasKey = !hasKey;
50927
+ if (driving) stopDriving();
50928
+ startDriving();
50929
+ host?.log(hasKey ? sysText.keyBack : sysText.keyTaken, { vehicle: settings.vehicle, hasKey }, "proving_system");
50930
+ return;
50931
+ }
50932
+ if (id === "cold_rain") {
50933
+ weather = weather.rain > 0 ? { outsideC: 25, rain: 0 } : { outsideC: 8, rain: 1 };
50934
+ if (driving) driving.weather = { ...weather };
50935
+ setSurface(weather.rain > 0 ? "wet_asphalt" : "dry_asphalt");
50936
+ host?.log(weather.rain > 0 ? sysText.fog.weather : sysText.fog.fair, { vehicle: settings.vehicle, ...weather }, "proving_system");
50937
+ return;
50938
+ }
50939
+ if (!driving) startDriving();
50940
+ const cockpit = driving;
50941
+ if (!cockpit) return;
50942
+ const d = cockpit.driver;
50943
+ const done = (key) => host?.log(sysText.done[key], { vehicle: settings.vehicle, action: key }, "proving_system");
50944
+ switch (id) {
50945
+ case "puncture": {
50946
+ const tyres = d.spec.wheels.map((_, i) => d.condition.tyres?.[i] ?? 1);
50947
+ tyres[0] = 0;
50948
+ d.condition = { ...d.condition, tyres };
50949
+ return done("puncture");
50950
+ }
50951
+ case "fit_spare": {
50952
+ const flat = d.spec.wheels.findIndex((_, i) => (d.condition.tyres?.[i] ?? 1) < 0.2);
50953
+ const event = cockpit.systems.fitSpare(d, Math.max(0, flat));
50954
+ host?.log(sysText.events[event.type], { vehicle: settings.vehicle, ...event }, "proving_system");
50955
+ return;
50956
+ }
50957
+ case "refuel":
50958
+ d.refuel(d.spec.tank.capacity);
50959
+ return done("refuel");
50960
+ case "empty_tank":
50961
+ d.state.fuel = Math.min(d.state.fuel, d.spec.tank.capacity * 0.04);
50962
+ return done("empty_tank");
50963
+ case "drain_battery":
50964
+ cockpit.systems.charge(-(cockpit.systems.state.charge - 0.15));
50965
+ return done("drain_battery");
50966
+ case "jump_start":
50967
+ cockpit.systems.charge(1);
50968
+ return done("jump_start");
50969
+ case "break_radiator":
50970
+ cockpit.radiator = 0.05;
50971
+ return done("break_radiator");
50972
+ case "remote_lock":
50973
+ case "remote_unlock": {
50974
+ const lock = id === "remote_lock";
50975
+ cockpit.body?.lock(lock);
50976
+ cockpit.controls.state.alarm = lock ? "armed" : "off";
50977
+ host?.audio?.play?.({ sound: "alarm_chirp", position: [car?.built.group.position.x ?? 0, 1, car?.built.group.position.z ?? 0] });
50978
+ host?.log(sysText.remote[lock ? "locked" : "unlocked"], { vehicle: settings.vehicle, locked: lock }, "proving_system");
50979
+ return;
50980
+ }
50981
+ case "crash_wall": {
50982
+ const g = car?.built.group;
50983
+ if (!g || !host) return;
50984
+ crashes ?? (crashes = createProvingCrash(host, options.occupant));
50985
+ const speed = d.state.u;
50986
+ const ejection = crashes.crash({ car: { position: g.position, rotationY: g.rotation.y }, u: d.state.u, w: d.state.w, belted: cockpit.controls.state.seatbelt, destructible: cockpit.destructible });
50987
+ d.state.u = d.state.w = d.state.yawRate = 0;
50988
+ const kmh2 = Math.round(Math.abs(speed) * 3.6);
50989
+ const belted = cockpit.controls.state.seatbelt;
50990
+ host.log(ejection ? fillText(sysText.crash.ejected, kmh2, sysText.crash.exits[ejection.exit]) : fillText(belted ? sysText.crash.stayed : sysText.crash.mild, kmh2), { vehicle: settings.vehicle, kmh: kmh2, belted: cockpit.controls.state.seatbelt, ejection }, "proving_crash");
50991
+ return;
50992
+ }
50993
+ case "park_behind": {
50994
+ const g = car?.built.group;
50995
+ if (!g || !host) return;
50996
+ parked?.remove();
50997
+ const back = (car?.built.length ?? 4.5) / 2 + 3 + 2.3;
50998
+ const at = [g.position.x - Math.sin(g.rotation.y) * back, 0, g.position.z - Math.cos(g.rotation.y) * back];
50999
+ const placed = host.addObject?.(options.parkedCar ?? "sedan", { position: at, rotationY: g.rotation.y });
51000
+ parked = placed ? { root: placed.root, remove: placed.remove } : null;
51001
+ host.log(placed ? sysText.parking.placed : sysText.parking.none, { vehicle: settings.vehicle }, "proving_system");
51002
+ return;
51003
+ }
51004
+ case "glovebox_contents":
51005
+ return listCompartment(cockpit, "glovebox");
51006
+ case "trunk_contents":
51007
+ return listCompartment(cockpit, "trunk");
51008
+ case "take_from_trunk": {
51009
+ const storage = cockpit.storage;
51010
+ if (!storage.compartments.some((c) => c.id === "trunk")) return listCompartment(cockpit, "trunk");
51011
+ const slot = storage.contents("trunk").findIndex((x) => x !== null);
51012
+ if (slot < 0 || !storage.reachable("trunk", cockpit.access())) return listCompartment(cockpit, "trunk");
51013
+ const stack = storage.take("trunk", slot, cockpit.access());
51014
+ if (!stack) return;
51015
+ const g = car?.built.group;
51016
+ const back = (car?.built.length ?? 4.5) / 2 + 0.8;
51017
+ const placed = g ? host?.addObject?.(stack.itemId, { position: [g.position.x - Math.sin(g.rotation.y) * back, 0, g.position.z - Math.cos(g.rotation.y) * back] }) : null;
51018
+ host?.log(fillText(placed ? sysText.storage.taken : sysText.storage.ground, itemName(stack.itemId)), { vehicle: settings.vehicle, item: stack.itemId, count: stack.count, placed: !!placed }, "proving_storage");
51019
+ return;
51020
+ }
51021
+ case "force_door": {
51022
+ const body = cockpit.body;
51023
+ if (!body) return;
51024
+ if (body.locked) body.lock(false);
51025
+ body.toggle("door_front_left");
51026
+ if (cockpit.controls.state.alarm === "armed") cockpit.controls.state.alarm = "ringing";
51027
+ host?.log(sysText.remote.forced, { vehicle: settings.vehicle, alarm: cockpit.controls.state.alarm }, "proving_system");
51028
+ return;
51029
+ }
51030
+ }
51031
+ };
49402
51032
  const gearName = (gear) => gear < 0 ? "R" : gear === 0 ? "N" : String(gear);
49403
51033
  const onChange = (change) => {
49404
51034
  if (change.kind === "layer") {
@@ -49407,6 +51037,7 @@ var GameVehiclesThree = (() => {
49407
51037
  }
49408
51038
  if (change.kind === "action") {
49409
51039
  if (change.id === "drive") return driving ? stopDriving() : startDriving();
51040
+ if (change.id in sysText.actions) return systemsAction(change.id);
49410
51041
  if (change.id === "run_test" || change.id === "run_all" || change.id === "reverse_park") stopDriving();
49411
51042
  if (change.id === "run_test") {
49412
51043
  queue = [];
@@ -49453,6 +51084,12 @@ var GameVehiclesThree = (() => {
49453
51084
  settings.camera = String(change.value);
49454
51085
  applyCamera();
49455
51086
  break;
51087
+ case "seat_forward":
51088
+ case "seat_height":
51089
+ case "mirror_left":
51090
+ case "mirror_right":
51091
+ adjust[ADJUST_KEYS[change.id]] = Number(change.value);
51092
+ break;
49456
51093
  case "time_scale":
49457
51094
  settings.timeScale = Number(change.value);
49458
51095
  break;
@@ -49487,6 +51124,13 @@ var GameVehiclesThree = (() => {
49487
51124
  const d = driving;
49488
51125
  d.step(dt);
49489
51126
  if (!driving) return;
51127
+ if (!misted && d.fog.state.fog > 0.6) {
51128
+ misted = true;
51129
+ host.log(sysText.fog.misted, { vehicle: settings.vehicle, fog: +d.fog.state.fog.toFixed(2), humidity: +d.fog.state.humidity.toFixed(2) }, "proving_system");
51130
+ } else if (misted && d.fog.state.fog < 0.1) {
51131
+ misted = false;
51132
+ host.log(sysText.fog.clear, { vehicle: settings.vehicle }, "proving_system");
51133
+ }
49490
51134
  pose(d.driver.state, d.driver.state.steerAngle);
49491
51135
  speed = d.driver.state.u;
49492
51136
  steer = d.driver.state.steerAngle;
@@ -49502,7 +51146,7 @@ var GameVehiclesThree = (() => {
49502
51146
  if (car) engine?.update({ dt, position: [car.built.group.position.x, 0.5, car.built.group.position.z], speed, throttle: run ? 1 : driving?.throttle ?? 0, rpm: driving ? driving.driver.state.rpm : void 0 });
49503
51147
  const f = frameOf(speed);
49504
51148
  if (f && rig.vehicle && settings.camera !== "room") rig.vehicle.update(rig.camera, f, dt);
49505
- if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, m.pose, f);
51149
+ if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, aimed(m.pose), f);
49506
51150
  void steer;
49507
51151
  const key = `${host.bench?.layer("mirrors")}|${host.bench?.layer("reverse_camera")}`;
49508
51152
  if (key !== mirrorKey) {
@@ -49521,6 +51165,10 @@ var GameVehiclesThree = (() => {
49521
51165
  const speedNow = () => Math.abs(run?.driver.state.u ?? driving?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
49522
51166
  const counts = (verdict) => certificateOf(settings.vehicle)?.results.filter((r) => r.verdict === verdict).length ?? 0;
49523
51167
  const teardown = () => {
51168
+ crashes?.dispose();
51169
+ crashes = null;
51170
+ parked?.remove();
51171
+ parked = null;
49524
51172
  stopDriving();
49525
51173
  stopEngine();
49526
51174
  for (const m of rig.mirrors) m.remove();
@@ -49551,9 +51199,14 @@ var GameVehiclesThree = (() => {
49551
51199
  { kind: "choice", id: "vehicle", name: text.controls.vehicle, hint: text.hints.vehicle, options: vehicles.map((id) => ({ id, name: name(id) })), default: settings.vehicle },
49552
51200
  { kind: "choice", id: "test", name: text.controls.test, hint: text.hints.test, options: measured.map((id) => ({ id, name: text.tests[id] ?? id })), default: settings.test },
49553
51201
  { kind: "choice", id: "camera", name: text.controls.camera, hint: text.hints.camera, options: ["room", "chase", "cockpit", "hood"].map((id) => ({ id, name: text.cameras[id] })), default: settings.camera },
51202
+ { kind: "number", id: "seat_forward", name: sysText.adjust.seat_forward, min: -0.2, max: 0.2, step: 0.02, default: 0 },
51203
+ { kind: "number", id: "seat_height", name: sysText.adjust.seat_height, min: -0.1, max: 0.1, step: 0.01, default: 0 },
51204
+ { kind: "number", id: "mirror_left", name: sysText.adjust.mirror_left, min: -0.3, max: 0.3, step: 0.02, default: 0 },
51205
+ { kind: "number", id: "mirror_right", name: sysText.adjust.mirror_right, min: -0.3, max: 0.3, step: 0.02, default: 0 },
49554
51206
  { kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 20, step: 1, default: settings.timeScale },
49555
51207
  { kind: "choice", id: "visual_check", name: text.controls.visual_check, hint: text.hints.visual_check, options: VISUAL_CHECKS.map((id) => ({ id, name: text.tests[id] ?? id })), default: settings.visual },
49556
51208
  { kind: "action", id: "drive", name: text.actions.drive },
51209
+ ...Object.keys(sysText.actions).map((id) => ({ kind: "action", id, name: sysText.actions[id] })),
49557
51210
  { kind: "choice", id: "gearbox", name: text.controls.gearbox, hint: text.hints.gearbox, options: ["automatic", "manual"].map((id) => ({ id, name: text.gearbox[id] })), default: gearbox },
49558
51211
  { kind: "action", id: "run_test", name: text.actions.run_test },
49559
51212
  { kind: "action", id: "run_all", name: text.actions.run_all },
@@ -49573,6 +51226,7 @@ var GameVehiclesThree = (() => {
49573
51226
  { id: "gear", name: text.metrics.gear, read: () => driving?.driver.state.gear ?? run?.driver.state.gear ?? 0 },
49574
51227
  { id: "rpm", name: text.metrics.rpm, unit: "rpm", digits: 0, read: () => driving?.driver.state.rpm ?? run?.driver.state.rpm ?? 0 },
49575
51228
  { id: "time", name: text.metrics.time, unit: "s", digits: 1, read: () => run?.time ?? 0 },
51229
+ { id: "fog", name: sysText.fog.metric, unit: "%", digits: 0, read: () => (driving?.fog.state.fog ?? 0) * 100 },
49576
51230
  { id: "value", name: text.metrics.value, digits: 1, read: () => run ? run.trace[run.trace.length - 1]?.[1] ?? 0 : 0 },
49577
51231
  { id: "passed", name: text.metrics.passed, read: () => counts("pass") },
49578
51232
  { id: "failed", name: text.metrics.failed, read: () => counts("fail") },
@@ -49587,6 +51241,8 @@ var GameVehiclesThree = (() => {
49587
51241
  rig.vehicle = createVehicleCamera(three);
49588
51242
  lent.audio?.register?.(LIBRARY_BANK);
49589
51243
  lent.audio?.register?.(CABIN_BANK);
51244
+ lent.audio?.register?.(VEHICLE_SOUND_BANK);
51245
+ lent.audio?.register?.(RADIO_BANK);
49590
51246
  const stopChange = lent.bench?.onChange(onChange);
49591
51247
  const stopFrame = lent.onFrame(frame);
49592
51248
  const stopClear = lent.onClear?.(() => {