@adatechnology/game-weapons-three 0.14.1 → 0.15.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.
@@ -32,6 +32,7 @@ var GameWeaponsThree = (() => {
32
32
  DEFAULT_THUMBNAIL_BASE: () => DEFAULT_THUMBNAIL_BASE,
33
33
  EXPLOSIVES_AREA_ID: () => EXPLOSIVES_AREA_ID,
34
34
  EXPLOSIVES_TEXT: () => EXPLOSIVES_TEXT,
35
+ EXPLOSIVE_MODEL_IDS: () => EXPLOSIVE_MODEL_IDS,
35
36
  GAME_DISTANCE_CURVE: () => GAME_DISTANCE_CURVE,
36
37
  GAME_SOUND_CLASSES: () => GAME_SOUND_CLASSES,
37
38
  GUNSHOT_PROFILES: () => GUNSHOT_PROFILES,
@@ -52,10 +53,12 @@ var GameWeaponsThree = (() => {
52
53
  applyCondition: () => applyCondition,
53
54
  buildCartridge: () => buildCartridge,
54
55
  buildCasing: () => buildCasing,
56
+ buildExplosiveModel: () => buildExplosiveModel,
55
57
  buildItemWeaponModel: () => buildItemWeaponModel,
56
58
  buildModeledProp: () => buildModeledProp,
57
59
  buildProp: () => buildProp,
58
60
  buildRound: () => buildRound,
61
+ buildThrowableModel: () => buildThrowableModel,
59
62
  buildToolClips: () => buildToolClips,
60
63
  buildTripodMountedProp: () => buildTripodMountedProp,
61
64
  buildWeaponModel: () => buildWeaponModel,
@@ -75,6 +78,8 @@ var GameWeaponsThree = (() => {
75
78
  deriveFrames: () => deriveFrames,
76
79
  explosivesTextOf: () => explosivesTextOf,
77
80
  fitAttachmentModels: () => fitAttachmentModels,
81
+ hasExplosiveModel: () => hasExplosiveModel,
82
+ hasThrowableModel: () => hasThrowableModel,
78
83
  hasWeaponModel: () => hasWeaponModel,
79
84
  itemModelOfWeapon: () => itemModelOfWeapon,
80
85
  mechanicalSoundOf: () => mechanicalSoundOf,
@@ -5951,7 +5956,10 @@ var GameWeaponsThree = (() => {
5951
5956
  ]
5952
5957
  }
5953
5958
  ],
5954
- fitted: []
5959
+ fitted: [],
5960
+ tags: [
5961
+ "pdw"
5962
+ ]
5955
5963
  },
5956
5964
  {
5957
5965
  id: "kriss_vector",
@@ -10410,7 +10418,9 @@ var GameWeaponsThree = (() => {
10410
10418
  "arsenal",
10411
10419
  "reference",
10412
10420
  "derived"
10413
- ]
10421
+ ],
10422
+ mounts: [],
10423
+ fitted: []
10414
10424
  },
10415
10425
  {
10416
10426
  id: "hk_mp5sd3",
@@ -11169,7 +11179,10 @@ var GameWeaponsThree = (() => {
11169
11179
  fitted: [],
11170
11180
  stock: {
11171
11181
  kind: "retract"
11172
- }
11182
+ },
11183
+ tags: [
11184
+ "pdw"
11185
+ ]
11173
11186
  },
11174
11187
  {
11175
11188
  id: "hk_g36",
@@ -12870,7 +12883,8 @@ var GameWeaponsThree = (() => {
12870
12883
  dmr: "marksman rifle",
12871
12884
  hunting: "hunting rifle",
12872
12885
  machine_gun: "machine gun",
12873
- launcher: "launcher"
12886
+ launcher: "launcher",
12887
+ pdw: "PDW (personal defence weapon)"
12874
12888
  };
12875
12889
  var faultsEn = { misfire: "misfire", stovepipe: "stovepipe", double_feed: "double feed", failure_to_extract: "failure to extract" };
12876
12890
  var conditionsEn = { new: "new", used: "used", worn: "worn", rusty: "rusty" };
@@ -12923,7 +12937,27 @@ var GameWeaponsThree = (() => {
12923
12937
  hunting_rifle_30_06: ".30-06 hunting rifle",
12924
12938
  m2_flamethrower: "M2 flamethrower",
12925
12939
  improvised_flamethrower: "Improvised flamethrower",
12926
- m79: "M79 grenade launcher"
12940
+ m79: "M79 grenade launcher",
12941
+ m203: "M203 grenade launcher",
12942
+ gp_25: "GP-25 grenade launcher",
12943
+ ag36: "AG36 grenade launcher",
12944
+ flashbang: "Flashbang",
12945
+ dynamite: "Dynamite",
12946
+ c4: "C4 charge",
12947
+ claymore: "Claymore mine",
12948
+ tactical_knife: "Tactical knife",
12949
+ meat_cleaver: "Meat cleaver",
12950
+ hatchet: "Hatchet",
12951
+ nail_bat: "Nail bat",
12952
+ iron_pipe: "Iron pipe",
12953
+ pickaxe: "Pickaxe",
12954
+ sickle: "Sickle",
12955
+ police_baton: "Police baton",
12956
+ cast_iron_skillet: "Cast iron skillet",
12957
+ improvised_spear: "Improvised spear",
12958
+ chainsaw: "Chainsaw",
12959
+ recurve_bow: "Recurve bow",
12960
+ m134: "M134 Minigun"
12927
12961
  },
12928
12962
  modes: modesEn,
12929
12963
  categories: categoriesEn,
@@ -12997,7 +13031,27 @@ var GameWeaponsThree = (() => {
12997
13031
  hunting_rifle_30_06: "Rifle de ca\xE7a .30-06",
12998
13032
  m2_flamethrower: "Lan\xE7a-chamas M2",
12999
13033
  improvised_flamethrower: "Lan\xE7a-chamas caseiro",
13000
- m79: "Lan\xE7a-granadas M79"
13034
+ m79: "Lan\xE7a-granadas M79",
13035
+ m203: "Lan\xE7a-granadas M203",
13036
+ gp_25: "Lan\xE7a-granadas GP-25",
13037
+ ag36: "Lan\xE7a-granadas AG36",
13038
+ flashbang: "granada de luz",
13039
+ dynamite: "dinamite",
13040
+ c4: "C4 com detonador",
13041
+ claymore: "mina Claymore",
13042
+ tactical_knife: "faca t\xE1tica",
13043
+ meat_cleaver: "cutelo",
13044
+ hatchet: "machadinha",
13045
+ nail_bat: "taco com pregos",
13046
+ iron_pipe: "cano de ferro",
13047
+ pickaxe: "picareta",
13048
+ sickle: "foice",
13049
+ police_baton: "cassetete",
13050
+ cast_iron_skillet: "frigideira de ferro",
13051
+ improvised_spear: "lan\xE7a improvisada",
13052
+ chainsaw: "motosserra",
13053
+ recurve_bow: "arco recurvo",
13054
+ m134: "Minigun M134"
13001
13055
  },
13002
13056
  modes: { single: "tiro \xFAnico", semi: "semiautom\xE1tico", burst: "rajada", auto: "autom\xE1tico", pump: "repeti\xE7\xE3o", bolt: "ferrolho", lever: "alavanca", break: "basculante", volley: "salva" },
13003
13057
  categories: {
@@ -13021,7 +13075,8 @@ var GameWeaponsThree = (() => {
13021
13075
  dmr: "fuzil de precis\xE3o semiautom\xE1tico",
13022
13076
  hunting: "arma de ca\xE7a",
13023
13077
  machine_gun: "metralhadora",
13024
- launcher: "lan\xE7ador"
13078
+ launcher: "lan\xE7ador",
13079
+ pdw: "PDW (arma de defesa pessoal)"
13025
13080
  },
13026
13081
  explosives: {
13027
13082
  m67: "Granada M67",
@@ -13114,7 +13169,13 @@ var GameWeaponsThree = (() => {
13114
13169
  // Arsenal §11: montar e desmontar exige chave de fenda ou bancada; 2/15/30 s e a troca do lança-granadas (0,8 s) são padrões aprovados pelo dono (2026-10-24)
13115
13170
  attachments: { fitSeconds: { field: 2, tool: 15, bench: 30 }, switchSeconds: 0.8, speedOfSound: 343 },
13116
13171
  // Arsenal §08: tempo de mira = 0,15 s + 0,05 s por kg. O quadril do Arsenal é ×6 (o ZombiAdA liga isso por configuração); o SDK é neutro (1)
13117
- aim: { base: 0.15, perKg: 0.05, hipSpread: 1 },
13172
+ aim: {
13173
+ base: 0.15,
13174
+ perKg: 0.05,
13175
+ hipSpread: 1,
13176
+ // the iron sights close the view in a little: more for a long gun, whose sights sit farther from the eye, than for a handgun
13177
+ ironSightZoom: { pistol: 1.1, revolver: 1.1, smg: 1.2, carbine: 1.25, shotgun: 1.15, rifle: 1.3, hunting: 1.35, dmr: 1.4, sniper: 1.4, machine_gun: 1.2, launcher: 1.15, bow: 1.15, crossbow: 1.25 }
13178
+ },
13118
13179
  // Arsenal §08/§11: coronha retrátil saca mais rápido, mais recuo; os números são padrões aprovados pelo dono (2026-10-24)
13119
13180
  stock: {
13120
13181
  seconds: 0.6,
@@ -14713,6 +14774,26 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
14713
14774
  const id = caliber?.propellant === "black_powder" ? "black_powder" : caliber?.propellant === "none" ? "none" : "smokeless";
14714
14775
  return MUZZLE_SMOKE[id];
14715
14776
  }
14777
+ function aimZoomOf(arsenal, weaponId, attachments = arsenal.weapons.get(weaponId)?.fitted?.map((part) => part.id) ?? []) {
14778
+ const def = arsenal.weapons.get(weaponId);
14779
+ if (!def) return 1;
14780
+ const optic = attachments.map((id) => arsenal.attachments.get(id)?.optic).find((entry2) => entry2 !== void 0);
14781
+ if (optic) return optic.zoom[0];
14782
+ return arsenal.rules.aim.ironSightZoom[def.category] ?? 1;
14783
+ }
14784
+ var isGenericKind = (kind, category) => kind === "firearm" && category !== "bow" && category !== "crossbow" || kind === "flame";
14785
+ function genericWeapons(arsenal) {
14786
+ const archetypes = new Set(ARCHETYPE_IDS);
14787
+ const generics = /* @__PURE__ */ new Map();
14788
+ for (const def of arsenal.weapons.values()) if (archetypes.has(def.id) && isGenericKind(def.action.kind, def.category) && !def.maker) generics.set(def.id, void 0);
14789
+ for (const def of [...arsenal.weapons.values()].sort((a, b) => a.id < b.id ? -1 : 1)) {
14790
+ const { archetype } = def;
14791
+ if (!archetype || archetypes.has(def.id) || !generics.has(archetype) || generics.get(archetype)) continue;
14792
+ if (arsenal.weapons.get(archetype)?.action.kind === def.action.kind) generics.set(archetype, def.id);
14793
+ }
14794
+ for (const [id, brand] of generics) if (!brand && arsenal.weapons.get(id)?.action.kind === "flame") generics.delete(id);
14795
+ return generics;
14796
+ }
14716
14797
 
14717
14798
  // ../game-render-three/dist/index.js
14718
14799
  var DEFAULT_ANGLE = Math.PI / 3;
@@ -15964,8 +16045,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
15964
16045
  const face = faces[i];
15965
16046
  f3(face[2] + offset, face[1] + offset, face[0] + offset);
15966
16047
  }
15967
- const layer2 = steps + bevelSegments * 2;
15968
- offset = vlen * layer2;
16048
+ const layer3 = steps + bevelSegments * 2;
16049
+ offset = vlen * layer3;
15969
16050
  for (let i = 0; i < flen; i++) {
15970
16051
  const face = faces[i];
15971
16052
  f3(face[0] + offset, face[1] + offset, face[2] + offset);
@@ -17392,11 +17473,11 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
17392
17473
  B.open_suitcases = (M, a) => {
17393
17474
  const K = kit(M, a), cs = [K.main(11549236), 2767450], rs = [13156520, 4876938, 14736592, 11028538];
17394
17475
  const suitcase = (x, z, ww, dd, c, ry, al) => {
17395
- const lx = x, hz = z - dd / 2;
17476
+ const lx = x, hz2 = z - dd / 2;
17396
17477
  K.box("plain", lx, 0, z, ww, 0.16, dd, c, ry);
17397
17478
  K.box("fabric", lx, 0.12, z, ww - 0.06, 0.05, dd - 0.06, 14209216, ry);
17398
17479
  for (let i = 0; i < 5; i++) K.box("fabric", lx + (K.rnd() - 0.5) * (ww - 0.2), 0.16, z + (K.rnd() - 0.5) * (dd - 0.2), 0.14 + K.rnd() * 0.1, 0.04 + K.rnd() * 0.04, 0.12 + K.rnd() * 0.1, rs[i % 4], K.rnd() * 3);
17399
- K.ramp("plain", lx, 0.16, hz, ww, 0.07, dd, c, al);
17480
+ K.ramp("plain", lx, 0.16, hz2, ww, 0.07, dd, c, al);
17400
17481
  K.box("metal", lx + ww * 0.3, 0.1, z + dd / 2, 0.04, 0.03, 0.01, 12632256);
17401
17482
  };
17402
17483
  suitcase(-0.4, 0, 0.75, 0.5, cs[0], 0.15, 1.35);
@@ -19582,26 +19663,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19582
19663
  for (const e of planned.items) if (e.type === "wall_switch") e.lit = (e.connections.controls || []).some((id) => planned.items.find((x) => x.id === id)?.lit === true);
19583
19664
  }
19584
19665
  for (const e of planned.items) if (e.type === "lantern_lamp") e.item.on = !!e.lit;
19585
- let scratch2 = null;
19666
+ let scratch3 = null;
19586
19667
  function probe(e, hinge, setback, blockedAt) {
19587
- if (!scratch2) scratch2 = GI.createWorld({ locale, strings: hh.strings, items: hh.items, loot: hh.loot, grid: false });
19668
+ if (!scratch3) scratch3 = GI.createWorld({ locale, strings: hh.strings, items: hh.items, loot: hh.loot, grid: false });
19588
19669
  const it = at(e.item);
19589
19670
  const x = it.x + Math.sin(it.ry) * setback;
19590
19671
  const z = it.z + Math.cos(it.ry) * setback;
19591
19672
  const spec = hh.build(e.type, Object.assign({}, e.item, { hinge }), recorderApi());
19592
- scratch2.add("__probe", spec, { x, y: e.floorY, z, ry: it.ry });
19673
+ scratch3.add("__probe", spec, { x, y: e.floorY, z, ry: it.ry });
19593
19674
  const blocks = (px, pz) => blockedAt(px, pz, e.floor);
19594
19675
  let sum2 = 0;
19595
19676
  let min2 = 1;
19596
19677
  let n = 0;
19597
19678
  for (const p of spec.parts || []) {
19598
19679
  if (!p.motion || p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
19599
- const t = scratch2.fitSweep("__probe", p.id, blocks);
19680
+ const t = scratch3.fitSweep("__probe", p.id, blocks);
19600
19681
  sum2 += t;
19601
19682
  min2 = Math.min(min2, t);
19602
19683
  n++;
19603
19684
  }
19604
- scratch2.remove("__probe");
19685
+ scratch3.remove("__probe");
19605
19686
  return { sum: sum2, min: min2, n };
19606
19687
  }
19607
19688
  const better = (a, b) => a.sum > b.sum + 1e-9 || Math.abs(a.sum - b.sum) <= 1e-9 && a.min > b.min + 1e-9;
@@ -19926,26 +20007,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19926
20007
  return { point: [p[0], sign2 * hh, p[2]], normal: [0, sign2, 0] };
19927
20008
  };
19928
20009
  var aabbOf = (s, center, q) => {
19929
- let hx, hy, hz;
20010
+ let hx, hy, hz2;
19930
20011
  if (s.kind === "sphere") {
19931
- hx = hy = hz = s.radius;
20012
+ hx = hy = hz2 = s.radius;
19932
20013
  } else {
19933
20014
  const m = quat.toMatrix(q);
19934
20015
  if (s.kind === "box") {
19935
20016
  const a = s.size[0] / 2, b = s.size[1] / 2, c = s.size[2] / 2;
19936
20017
  hx = Math.abs(m[0]) * a + Math.abs(m[1]) * b + Math.abs(m[2]) * c;
19937
20018
  hy = Math.abs(m[3]) * a + Math.abs(m[4]) * b + Math.abs(m[5]) * c;
19938
- hz = Math.abs(m[6]) * a + Math.abs(m[7]) * b + Math.abs(m[8]) * c;
20019
+ hz2 = Math.abs(m[6]) * a + Math.abs(m[7]) * b + Math.abs(m[8]) * c;
19939
20020
  } else {
19940
20021
  const ax = [m[1], m[4], m[7]];
19941
20022
  const hh = s.height / 2;
19942
20023
  const e = (i) => s.radius * Math.sqrt(Math.max(0, 1 - ax[i] * ax[i])) + hh * Math.abs(ax[i]);
19943
20024
  hx = e(0);
19944
20025
  hy = e(1);
19945
- hz = e(2);
20026
+ hz2 = e(2);
19946
20027
  }
19947
20028
  }
19948
- return { min: [center[0] - hx, center[1] - hy, center[2] - hz], max: [center[0] + hx, center[1] + hy, center[2] + hz] };
20029
+ return { min: [center[0] - hx, center[1] - hy, center[2] - hz2], max: [center[0] + hx, center[1] + hy, center[2] + hz2] };
19949
20030
  };
19950
20031
  var createRng2 = (seed) => {
19951
20032
  let s = seed | 0;
@@ -20001,6 +20082,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20001
20082
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
20002
20083
  };
20003
20084
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
20085
+ var splitAlong = (box5, count, axis2, rnd2) => {
20086
+ const shares = Array.from({ length: count }, () => 0.25 + 1.5 * rnd2());
20087
+ const total = shares.reduce((sum2, share) => sum2 + share, 0);
20088
+ const out = [];
20089
+ let from = box5.c[axis2] - box5.s[axis2] / 2;
20090
+ for (const share of shares) {
20091
+ const length2 = box5.s[axis2] * share / total;
20092
+ const c = [box5.c[0], box5.c[1], box5.c[2]];
20093
+ const size = [box5.s[0], box5.s[1], box5.s[2]];
20094
+ c[axis2] = from + length2 / 2;
20095
+ size[axis2] = length2;
20096
+ out.push({ c, s: size });
20097
+ from += length2;
20098
+ }
20099
+ return out;
20100
+ };
20004
20101
  var splinterBox = (inp, box5, limit = inp.maxFragments) => {
20005
20102
  const { rnd: rnd2 } = inp;
20006
20103
  const dims = box5.s;
@@ -20010,35 +20107,81 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20010
20107
  const w = Math.max(fmin * 1.5, 0.01);
20011
20108
  const c = clamp(Math.round(dims[a] * dims[b] / (w * w)), 3, Math.max(3, Math.floor(limit / 2)));
20012
20109
  const maxLen = fmax * 4;
20013
- const cells = bsp(box5, c, [a, b], rnd2);
20110
+ const cells = [];
20111
+ for (const cell of bsp(box5, c, [a, b], rnd2)) {
20112
+ const wide2 = cell.s[a] >= cell.s[b] ? a : b;
20113
+ if (rnd2() < 0.4 && cell.s[wide2] > fmin * 0.5) for (const part of splitAlong(cell, 2, wide2, rnd2)) cells.push(part);
20114
+ else cells.push(cell);
20115
+ }
20014
20116
  const pieces = [];
20015
20117
  for (const cell of cells) {
20016
- const extra = rnd2() < 0.4 ? 1 : 0;
20118
+ const extra = Math.floor(rnd2() * 3);
20017
20119
  const k = Math.max(1, Math.ceil(dims[g] / maxLen)) + extra;
20018
- for (const p of bsp(cell, k, [g], rnd2)) pieces.push(p);
20120
+ for (const p of splitAlong(cell, k, g, rnd2)) pieces.push(p);
20019
20121
  }
20020
20122
  return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
20021
20123
  };
20124
+ var PLY = 3e-3;
20125
+ var PLY_GAP = 0.6;
20126
+ var PLY_SHEET = 0.012;
20127
+ var plyBreak = (inp, root, limit) => {
20128
+ const { rnd: rnd2 } = inp;
20129
+ const dims = root.s;
20130
+ const sorted = [0, 1, 2].sort((x, y) => dims[x] - dims[y]);
20131
+ const t = sorted[0];
20132
+ const [u, v] = [sorted[1], sorted[2]].sort((x, y) => x - y);
20133
+ const along = dims[u] >= dims[v] ? u : v;
20134
+ const across2 = along === u ? v : u;
20135
+ const layers = clamp(Math.round(dims[t] / PLY / 2), 2, 3);
20136
+ const pitch = dims[t] / layers;
20137
+ const th = Math.min(pitch, PLY_SHEET);
20138
+ const per = Math.max(2, Math.floor(limit / layers));
20139
+ const out = [];
20140
+ for (let k = 0; k < layers; k++) {
20141
+ const layer3 = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
20142
+ layer3.c[t] = root.c[t] + (-dims[t] / 2 + (k + 0.5) * pitch) * (pitch > PLY_SHEET ? 1 : 1 + PLY_GAP);
20143
+ layer3.s[t] = th;
20144
+ for (const cell of bsp(layer3, per, [u, v], rnd2)) {
20145
+ if (out.length >= limit) return out;
20146
+ if (rnd2() < 0.35) {
20147
+ const strand = cell.s[across2] * (0.15 + 0.2 * rnd2());
20148
+ out.push({ center: cell.c, size: [cell.s[along], strand, th * 0.7], q: frame(along, across2, t), shape: "splinter", weight: cell.s[along] * strand * th });
20149
+ } else {
20150
+ out.push({ center: cell.c, size: [cell.s[u], cell.s[v], th], q: frame(u, v, t, (rnd2() - 0.5) * 0.5), shape: "scrap", weight: cell.s[u] * cell.s[v] * th });
20151
+ }
20152
+ }
20153
+ }
20154
+ return out;
20155
+ };
20156
+ var granules = (inp, box5, count) => bsp(box5, count, [0, 1, 2], inp.rnd).map((b) => ({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] }));
20157
+ var CRUMBS = 6;
20022
20158
  var severAt = (inp, root, at3, gap) => {
20023
20159
  const dims = root.s;
20024
20160
  const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
20025
20161
  const half = dims[L] / 2;
20026
20162
  const lo = clamp(at3 - gap / 2, -half, half), hi = clamp(at3 + gap / 2, -half, half);
20027
20163
  const out = [];
20028
- const stub = (from, to) => {
20164
+ const fibrous = inp.form === "slivers" || inp.form === "plies";
20165
+ const [a, b] = [0, 1, 2].filter((i) => i !== L);
20166
+ const stub = (from, to, breakAtEnd) => {
20029
20167
  if (to - from < 0.02) return;
20030
20168
  const c = [root.c[0], root.c[1], root.c[2]], sz = [dims[0], dims[1], dims[2]];
20031
20169
  c[L] = (from + to) / 2;
20032
20170
  sz[L] = to - from;
20033
- out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight: sz[0] * sz[1] * sz[2] });
20171
+ const weight = sz[0] * sz[1] * sz[2];
20172
+ if (fibrous) out.push({ center: c, size: [sz[L], sz[a], sz[b]], q: frame(L, a, b, breakAtEnd ? 0 : Math.PI), shape: "slab", weight });
20173
+ else out.push({ center: c, size: sz, q: quat.identity(), shape: "slab", weight });
20034
20174
  };
20035
- stub(-half, lo);
20036
- stub(hi, half);
20175
+ stub(-half, lo, true);
20176
+ stub(hi, half, false);
20037
20177
  const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
20038
20178
  band.c[L] = (lo + hi) / 2;
20039
20179
  band.s[L] = Math.max(hi - lo, 0.01);
20040
- if (inp.style === "snap") {
20041
- for (const b of bsp(band, Math.max(3, Math.min(6, inp.maxFragments - out.length)), [0, 1, 2], inp.rnd)) out.push({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] });
20180
+ const room = inp.maxFragments - out.length;
20181
+ if (inp.style === "snap" && inp.form === "plies") for (const p of plyBreak(inp, band, Math.max(3, room))) out.push(p);
20182
+ else if (inp.style === "snap" && inp.form === "granules") for (const p of granules(inp, band, clamp(room, 3, 12))) out.push(p);
20183
+ else if (inp.style === "snap") {
20184
+ for (const b2 of bsp(band, Math.max(3, Math.min(6, room)), [0, 1, 2], inp.rnd)) out.push({ center: b2.c, size: b2.s, q: quat.identity(), shape: "chunk", weight: b2.s[0] * b2.s[1] * b2.s[2] });
20042
20185
  } else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
20043
20186
  return out;
20044
20187
  };
@@ -20071,8 +20214,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20071
20214
  const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
20072
20215
  if (style === "snap") {
20073
20216
  if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
20217
+ if (inp.form === "plies") return plyBreak(inp, root, maxFragments);
20074
20218
  const n2 = Math.min(maxFragments, 2 + Math.floor(rnd2() * 3));
20075
20219
  for (const b of bsp(root, n2, [0, 1, 2], rnd2)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
20220
+ if (inp.form !== "granules") return out;
20221
+ const crumbs = Math.min(CRUMBS, maxFragments - out.length);
20222
+ if (crumbs <= 0) return out;
20223
+ const band = { c: [0, 0, 0], s: [0, 0, 0] };
20224
+ for (let i = 0; i < 3; i++) {
20225
+ band.s[i] = Math.min(dims[i], Math.max(fmin, dims[i] * 0.2));
20226
+ band.c[i] = clamp(inp.hit[i], -dims[i] / 2 + band.s[i] / 2, dims[i] / 2 - band.s[i] / 2);
20227
+ }
20228
+ for (const p of granules(inp, band, crumbs)) out.push({ ...p, weight: p.weight * 0.5 });
20076
20229
  return out;
20077
20230
  }
20078
20231
  if (style === "crumble" || style === "none") {
@@ -20179,13 +20332,95 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20179
20332
  }
20180
20333
  return marks;
20181
20334
  };
20335
+ var JOINT_GAP = 1e-4;
20336
+ var MIN_BEARING_SHARE = 0.25;
20337
+ var unsupportedCells = (cells, thin, dims, standing) => {
20338
+ if (thin === 1) return [];
20339
+ const [u, v] = planeAxes(thin);
20340
+ const bottom = -dims[v] / 2;
20341
+ const rowOf = (y) => Math.round((y - bottom) * 1e4);
20342
+ const rows = /* @__PURE__ */ new Map();
20343
+ for (let i = 0; i < cells.length; i++) {
20344
+ if (!standing(i)) continue;
20345
+ const cell = cells[i];
20346
+ const key2 = rowOf(cell.center[v] - cell.size[v] / 2);
20347
+ const row = rows.get(key2);
20348
+ if (row) row.push(i);
20349
+ else rows.set(key2, [i]);
20350
+ }
20351
+ const held = /* @__PURE__ */ new Set();
20352
+ const queue = [];
20353
+ for (const i of rows.get(0) ?? []) {
20354
+ held.add(i);
20355
+ queue.push(i);
20356
+ }
20357
+ const overlap = (a, b) => Math.min(a.center[u] + a.size[u] / 2, b.center[u] + b.size[u] / 2) - Math.max(a.center[u] - a.size[u] / 2, b.center[u] - b.size[u] / 2);
20358
+ const climb = () => {
20359
+ while (queue.length > 0) {
20360
+ const cell = cells[queue.pop()];
20361
+ for (const j of rows.get(rowOf(cell.center[v] + cell.size[v] / 2)) ?? []) {
20362
+ const other = cells[j];
20363
+ if (held.has(j) || overlap(cell, other) <= Math.max(JOINT_GAP, MIN_BEARING_SHARE * Math.min(cell.size[u], other.size[u]))) continue;
20364
+ held.add(j);
20365
+ queue.push(j);
20366
+ }
20367
+ }
20368
+ };
20369
+ for (const row of rows.values()) row.sort((a, b) => cells[a].center[u] - cells[b].center[u]);
20370
+ const touching = (a, b) => Math.abs(a.center[u] + a.size[u] / 2 - (b.center[u] - b.size[u] / 2)) <= 1e-3;
20371
+ for (let wedged = true; wedged; ) {
20372
+ climb();
20373
+ wedged = false;
20374
+ for (const row of rows.values()) {
20375
+ for (let k = 1; k < row.length - 1; k++) {
20376
+ const i = row[k];
20377
+ if (held.has(i) || !held.has(row[k - 1]) || !held.has(row[k + 1])) continue;
20378
+ if (!touching(cells[row[k - 1]], cells[i]) || !touching(cells[i], cells[row[k + 1]])) continue;
20379
+ held.add(i);
20380
+ queue.push(i);
20381
+ wedged = true;
20382
+ }
20383
+ }
20384
+ }
20385
+ const loose = [];
20386
+ for (const row of rows.values()) for (const i of row) if (!held.has(i)) loose.push(i);
20387
+ return loose;
20388
+ };
20389
+ var clumpCells = (cells, thin, cap) => {
20390
+ if (cells.length <= cap) return [...cells];
20391
+ const [u, v] = planeAxes(thin);
20392
+ let lo0 = Infinity, lo1 = Infinity, hi0 = -Infinity, hi1 = -Infinity;
20393
+ for (const cell of cells) {
20394
+ lo0 = Math.min(lo0, cell.center[u] - cell.size[u] / 2);
20395
+ hi0 = Math.max(hi0, cell.center[u] + cell.size[u] / 2);
20396
+ lo1 = Math.min(lo1, cell.center[v] - cell.size[v] / 2);
20397
+ hi1 = Math.max(hi1, cell.center[v] + cell.size[v] / 2);
20398
+ }
20399
+ let side = Math.sqrt((hi0 - lo0) * (hi1 - lo1) / cap);
20400
+ for (; ; ) {
20401
+ const bins = /* @__PURE__ */ new Map();
20402
+ for (const cell of cells) {
20403
+ const key2 = `${Math.floor((cell.center[u] - lo0) / side)}:${Math.floor((cell.center[v] - lo1) / side)}`;
20404
+ const bin = bins.get(key2) ?? { lo: [Infinity, Infinity, Infinity], hi: [-Infinity, -Infinity, -Infinity] };
20405
+ for (let k = 0; k < 3; k++) {
20406
+ bin.lo[k] = Math.min(bin.lo[k], cell.center[k] - cell.size[k] / 2);
20407
+ bin.hi[k] = Math.max(bin.hi[k], cell.center[k] + cell.size[k] / 2);
20408
+ }
20409
+ bins.set(key2, bin);
20410
+ }
20411
+ if (bins.size <= cap) {
20412
+ return [...bins.values()].map(({ lo, hi }) => ({ center: [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2], size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]] }));
20413
+ }
20414
+ side *= 1.25;
20415
+ }
20416
+ };
20182
20417
  var MAX_SPALLS = 64;
20183
20418
  var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
20184
20419
  var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
20185
- var onFace = (layer2, face) => layer2.def.faces === "both" || layer2.def.faces === (face > 0 ? "+" : "-");
20186
- var isSpalled = (spalls, layer2, face, point) => {
20420
+ var onFace = (layer3, face) => layer3.def.faces === "both" || layer3.def.faces === (face > 0 ? "+" : "-");
20421
+ var isSpalled = (spalls, layer3, face, point) => {
20187
20422
  for (const s of spalls ?? []) {
20188
- if (s[0] !== layer2 || s[1] !== face) continue;
20423
+ if (s[0] !== layer3 || s[1] !== face) continue;
20189
20424
  const dx = s[2] - point[0], dy = s[3] - point[1], dz = s[4] - point[2];
20190
20425
  if (dx * dx + dy * dy + dz * dz <= s[5] * s[5]) return true;
20191
20426
  }
@@ -20771,12 +21006,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20771
21006
  const [dx, dz] = c.direction;
20772
21007
  const along = (p) => {
20773
21008
  const cx = (p.min[0] + p.max[0]) / 2, cz = (p.min[2] + p.max[2]) / 2;
20774
- const hx = (p.max[0] - p.min[0]) / 2, hz = (p.max[2] - p.min[2]) / 2;
21009
+ const hx = (p.max[0] - p.min[0]) / 2, hz2 = (p.max[2] - p.min[2]) / 2;
20775
21010
  return {
20776
21011
  u: cx * dx + cz * dz,
20777
21012
  v: -cx * dz + cz * dx,
20778
- du: 2 * (Math.abs(dx) * hx + Math.abs(dz) * hz),
20779
- dv: 2 * (Math.abs(dz) * hx + Math.abs(dx) * hz)
21013
+ du: 2 * (Math.abs(dx) * hx + Math.abs(dz) * hz2),
21014
+ dv: 2 * (Math.abs(dz) * hx + Math.abs(dx) * hz2)
20780
21015
  };
20781
21016
  };
20782
21017
  const g = parts.map(along);
@@ -20871,9 +21106,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20871
21106
  for (const b of p.beams ?? []) {
20872
21107
  const q = parts[b];
20873
21108
  if (!q?.elasticPa) continue;
20874
- const hx = q.max[0] - q.min[0], hz = q.max[2] - q.min[2];
20875
- const long = Math.max(hx, hz), short = Math.min(hx, hz);
20876
- const cos2 = hx >= hz ? Math.abs(dx) : Math.abs(dz);
21109
+ const hx = q.max[0] - q.min[0], hz2 = q.max[2] - q.min[2];
21110
+ const long = Math.max(hx, hz2), short = Math.min(hx, hz2);
21111
+ const cos2 = hx >= hz2 ? Math.abs(dx) : Math.abs(dz);
20877
21112
  const depth = q.max[1] - q.min[1];
20878
21113
  const inertia = tBeamInertia(short, depth, short + 2 * c.flange * long, q.slab ?? 0);
20879
21114
  beams += cos2 * cos2 * q.elasticPa * c.beamFactor * inertia / Math.max(long, 1e-6);
@@ -21158,6 +21393,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21158
21393
  },
21159
21394
  {
21160
21395
  id: "wood_soft",
21396
+ splitForm: "slivers",
21161
21397
  density: 500,
21162
21398
  hpPerKg: 90,
21163
21399
  penetration: 9,
@@ -21203,6 +21439,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21203
21439
  },
21204
21440
  {
21205
21441
  id: "wood_hard",
21442
+ splitForm: "slivers",
21206
21443
  density: 750,
21207
21444
  hpPerKg: 140,
21208
21445
  penetration: 14,
@@ -21247,6 +21484,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21247
21484
  },
21248
21485
  {
21249
21486
  id: "plywood",
21487
+ splitForm: "plies",
21250
21488
  density: 600,
21251
21489
  hpPerKg: 70,
21252
21490
  penetration: 7,
@@ -21294,6 +21532,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21294
21532
  },
21295
21533
  {
21296
21534
  id: "mdf",
21535
+ splitForm: "granules",
21297
21536
  density: 750,
21298
21537
  hpPerKg: 45,
21299
21538
  penetration: 6,
@@ -21805,6 +22044,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21805
22044
  },
21806
22045
  {
21807
22046
  id: "green_wood",
22047
+ splitForm: "slivers",
21808
22048
  density: 650,
21809
22049
  hpPerKg: 5,
21810
22050
  penetration: 10,
@@ -22085,6 +22325,125 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22085
22325
  "brittle"
22086
22326
  ],
22087
22327
  compressive: 1e5
22328
+ },
22329
+ {
22330
+ id: "mud",
22331
+ density: 1700,
22332
+ hpPerKg: 1,
22333
+ penetration: 5,
22334
+ ductility: 0.8,
22335
+ fracture: "crumble",
22336
+ fragmentSize: [
22337
+ 0.02,
22338
+ 0.08
22339
+ ],
22340
+ hitDebris: "dust",
22341
+ breakDebris: "dust",
22342
+ thermal: {
22343
+ conductivity: 0.5,
22344
+ heatCapacity: 3.2
22345
+ },
22346
+ tags: [
22347
+ "soil",
22348
+ "wet"
22349
+ ],
22350
+ compressive: 5e3
22351
+ },
22352
+ {
22353
+ id: "clay",
22354
+ density: 1800,
22355
+ hpPerKg: 5,
22356
+ penetration: 7,
22357
+ ductility: 0.05,
22358
+ fracture: "crumble",
22359
+ fragmentSize: [
22360
+ 0.02,
22361
+ 0.1
22362
+ ],
22363
+ hitDebris: "chips",
22364
+ breakDebris: "dust",
22365
+ thermal: {
22366
+ conductivity: 0.15,
22367
+ heatCapacity: 2.2
22368
+ },
22369
+ tags: [
22370
+ "soil",
22371
+ "brittle"
22372
+ ],
22373
+ compressive: 6e4
22374
+ },
22375
+ {
22376
+ id: "snow",
22377
+ density: 300,
22378
+ hpPerKg: 1,
22379
+ penetration: 3,
22380
+ ductility: 0.6,
22381
+ fracture: "crumble",
22382
+ fragmentSize: [
22383
+ 0.01,
22384
+ 0.05
22385
+ ],
22386
+ hitDebris: "dust",
22387
+ breakDebris: "dust",
22388
+ thermal: {
22389
+ conductivity: 0.1,
22390
+ heatCapacity: 2.1
22391
+ },
22392
+ tags: [
22393
+ "soil",
22394
+ "frozen"
22395
+ ],
22396
+ compressive: 1e3
22397
+ },
22398
+ {
22399
+ id: "cobble",
22400
+ density: 2650,
22401
+ hpPerKg: 25,
22402
+ penetration: 25,
22403
+ penetrationClass: "concrete",
22404
+ penetrationScale: 0.9,
22405
+ ductility: 0.03,
22406
+ fracture: "crumble",
22407
+ fragmentSize: [
22408
+ 0.05,
22409
+ 0.15
22410
+ ],
22411
+ hitDebris: "chips",
22412
+ breakDebris: "chips",
22413
+ thermal: {
22414
+ conductivity: 0.2,
22415
+ heatCapacity: 2.5
22416
+ },
22417
+ tags: [
22418
+ "masonry",
22419
+ "brittle"
22420
+ ],
22421
+ compressive: 3e5
22422
+ },
22423
+ {
22424
+ id: "paver",
22425
+ density: 2300,
22426
+ hpPerKg: 14,
22427
+ penetration: 18,
22428
+ penetrationClass: "concrete",
22429
+ penetrationScale: 1,
22430
+ ductility: 0.03,
22431
+ fracture: "crumble",
22432
+ fragmentSize: [
22433
+ 0.04,
22434
+ 0.12
22435
+ ],
22436
+ hitDebris: "chips",
22437
+ breakDebris: "chips",
22438
+ thermal: {
22439
+ conductivity: 0.15,
22440
+ heatCapacity: 2.5
22441
+ },
22442
+ tags: [
22443
+ "masonry",
22444
+ "brittle"
22445
+ ],
22446
+ compressive: 35e4
22088
22447
  }
22089
22448
  ];
22090
22449
  var material_hints_default = {
@@ -22297,6 +22656,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22297
22656
  var BREACH_BLAST_FACTOR = 6;
22298
22657
  var BREACH_AREA = 0.25;
22299
22658
  var BREACH_BLAST_REACH = 0.12;
22659
+ var MEMBER_CUT_REACH = 0.5;
22660
+ var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
22661
+ var BLAST_EDGE_RAGGED = 0.3;
22662
+ var BLAST_EDGE_LOOSE = 1.35;
22663
+ var BLAST_EDGE_WEAR = 0.6;
22300
22664
  var RESIDUAL_CAPACITY = 0.15;
22301
22665
  var SEVER_BAND = 0.1;
22302
22666
  var SEVER_ASPECT = 3;
@@ -23549,7 +23913,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23549
23913
  if (j.st.open) continue;
23550
23914
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
23551
23915
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
23552
- j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
23916
+ j.st.hp -= span === void 0 ? amount : Math.min(amount, j.st.maxHp) * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
23553
23917
  if (j.st.hp <= 0) {
23554
23918
  j.st.hp = 0;
23555
23919
  j.st.open = true;
@@ -23571,14 +23935,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23571
23935
  var _a;
23572
23936
  if (p.layers.length === 0) return;
23573
23937
  const lp = objLocal(o, wp);
23574
- const layer2 = outerLayer(p.layers, p.rt.spalls, face, lp);
23575
- if (!layer2) return;
23938
+ const layer3 = outerLayer(p.layers, p.rt.spalls, face, lp);
23939
+ if (!layer3) return;
23576
23940
  const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
23577
- spalls.push([layer2.index, face, lp[0], lp[1], lp[2], radius]);
23941
+ spalls.push([layer3.index, face, lp[0], lp[1], lp[2], radius]);
23578
23942
  if (spalls.length > MAX_SPALLS) spalls.shift();
23579
23943
  markDamaged(p);
23580
- emit2({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer2.index, point: wp, area: Math.PI * radius * radius });
23581
- debris2(o, p, layer2.mat.hitDebris, wp, normal, 5, 2, src, layer2.mat.id);
23944
+ emit2({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer3.index, point: wp, area: Math.PI * radius * radius });
23945
+ debris2(o, p, layer3.mat.hitDebris, wp, normal, 5, 2, src, layer3.mat.id);
23582
23946
  setStage(o, p, 1, {}, src);
23583
23947
  };
23584
23948
  const crater = (o, p, wp, normal, radius, src) => {
@@ -23677,6 +24041,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23677
24041
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
23678
24042
  const cut = (_b = p.rt).craters ?? (_b.craters = {});
23679
24043
  const out = [];
24044
+ const wholeBefore = Object.values(worn).filter((w) => w === -1).length;
23680
24045
  const shot = kind === "ballistic";
23681
24046
  for (let i = 0; i < grid.length; i++) {
23682
24047
  const cell = grid[i];
@@ -23735,6 +24100,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23735
24100
  emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
23736
24101
  debris2(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
23737
24102
  }
24103
+ if (Object.values(worn).filter((w) => w === -1).length > wholeBefore) dropUnsupported(o, p, src);
23738
24104
  if (p.spec.reinforcement) exposeRebar(o, p, src);
23739
24105
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
23740
24106
  if (p.structural && !isLost(p)) {
@@ -23838,10 +24204,122 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23838
24204
  setStage(o, p, 1, {}, src);
23839
24205
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
23840
24206
  debris2(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
24207
+ dropUnsupported(o, p, src);
23841
24208
  const reinforcement = p.spec.reinforcement;
23842
24209
  if (reinforcement) exposeRebar(o, p, src);
23843
24210
  if (p.structural) solveStructure(o);
23844
24211
  };
24212
+ const blastReachOf = (p, energy) => {
24213
+ const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
24214
+ const resistance = Math.max(1, perKg * p.mat.density * p.thickness);
24215
+ return BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3) * clamp2(Math.cbrt(BLAST_REFERENCE_RESISTANCE / resistance), 0.3, 2);
24216
+ };
24217
+ const blastUnits = (o, p, wp, energy, dist2, src) => {
24218
+ var _a, _b;
24219
+ if (!p.staged || p.st.stripped || isLost(p) || chipEnergy <= 0) return false;
24220
+ const reach = blastReachOf(p, energy);
24221
+ if (dist2 >= reach) return false;
24222
+ const radius = Math.sqrt(reach * reach - dist2 * dist2);
24223
+ const [u, v] = planeAxes(p.thin);
24224
+ const lp = toLocal(p.pos, p.q, objLocal(o, wp));
24225
+ const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
24226
+ const grid = gridOf(p);
24227
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
24228
+ const perKg = p.mat.hits?.blunt?.unitJoules ?? chipEnergy;
24229
+ const lobes = [rnd2() * 2 * Math.PI, rnd2() * 2 * Math.PI];
24230
+ const out = [];
24231
+ for (let i = 0; i < grid.length; i++) {
24232
+ const cell = grid[i];
24233
+ if (worn[i] === -1) continue;
24234
+ if (op && p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius) continue;
24235
+ const du = cell.center[u] - lp[u], dv = cell.center[v] - lp[v];
24236
+ const angle = Math.atan2(dv, du);
24237
+ const edge = radius * (1 + BLAST_EDGE_RAGGED * (0.5 * Math.sin(3 * angle + lobes[0]) + 0.3 * Math.sin(5 * angle + lobes[1]) + 0.4 * (rnd2() - 0.5)));
24238
+ const d = Math.hypot(du, dv);
24239
+ if (d <= edge) {
24240
+ worn[i] = -1;
24241
+ out.push(i);
24242
+ } else if (d <= edge * BLAST_EDGE_LOOSE) {
24243
+ const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
24244
+ worn[i] = Math.max(worn[i] ?? 0, perKg * mass * BLAST_EDGE_WEAR);
24245
+ }
24246
+ }
24247
+ if (out.length === 0) return false;
24248
+ const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !(op && p.st.opening && Math.hypot(grid[i].center[u] - op[u], grid[i].center[v] - op[v]) <= p.st.opening.radius));
24249
+ for (const i of loose) worn[i] = -1;
24250
+ const blown = out.map((i) => grid[i]), fallen = loose.map((i) => grid[i]);
24251
+ const volume = (cells2) => cells2.reduce((sum2, cell) => sum2 + cell.size[0] * cell.size[1] * cell.size[2], 0);
24252
+ const blownMass = Math.min(p.st.mass, p.mat.density * volume(blown));
24253
+ const fallenMass = Math.min(p.st.mass - blownMass, p.mat.density * volume(fallen));
24254
+ const speed = clamp2(Math.sqrt(2 * energy * 0.02 / Math.max(blownMass, 0.05)), 1, 12);
24255
+ const { c, q } = partXf(o, p);
24256
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
24257
+ const tag = `x${chipped.length}_`;
24258
+ const fragments = [
24259
+ ...cellFragments(o, p, clumpCells(blown, p.thin, maxFragments), wp, null, speed, blownMass, tag, true),
24260
+ ...fallen.length > 0 ? cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), wp, null, 0.3, fallenMass, `${tag}f`, true) : []
24261
+ ];
24262
+ const cells = [...blown, ...fallen];
24263
+ const holes = localHolesOf(p);
24264
+ p.st.holes = p.st.holes.filter((_, k) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
24265
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
24266
+ for (const cell of cells) {
24267
+ chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
24268
+ for (let k = 0; k < 3; k++) {
24269
+ lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
24270
+ hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
24271
+ }
24272
+ }
24273
+ p.st.mass = Math.max(0, p.st.mass - blownMass - fallenMass);
24274
+ p.rt.removed = (p.rt.removed ?? 0) + blownMass + fallenMass;
24275
+ markDamaged(p);
24276
+ setStage(o, p, 1, {}, src);
24277
+ const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
24278
+ const units = cells.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
24279
+ emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
24280
+ debris2(o, p, p.mat.breakDebris, center, vec.normalize(vec.sub(center, wp), [0, 1, 0]), 14, 3, src);
24281
+ if (p.spec.reinforcement) exposeRebar(o, p, src);
24282
+ if (p.structural && !isLost(p)) {
24283
+ solveStructure(o);
24284
+ collapseCheck(o, src);
24285
+ }
24286
+ return true;
24287
+ };
24288
+ const dropUnsupported = (o, p, src) => {
24289
+ var _a, _b;
24290
+ if (!p.spec.units || p.spec.units.mortar === p.spec.material || p.st.stripped || isLost(p)) return 0;
24291
+ const [u, v] = planeAxes(p.thin);
24292
+ const grid = gridOf(p);
24293
+ const worn = (_a = p.rt).chips ?? (_a.chips = {});
24294
+ const op = p.st.opening ? toLocal(p.pos, p.q, p.st.opening.center) : null;
24295
+ const opened = (cell) => !!op && !!p.st.opening && Math.hypot(cell.center[u] - op[u], cell.center[v] - op[v]) <= p.st.opening.radius;
24296
+ const loose = unsupportedCells(grid, p.thin, p.dims, (i) => worn[i] !== -1 && !opened(grid[i]));
24297
+ if (loose.length === 0) return 0;
24298
+ for (const i of loose) worn[i] = -1;
24299
+ const fallen = loose.map((i) => grid[i]);
24300
+ const mass = Math.min(p.st.mass, p.mat.density * fallen.reduce((sum2, cell) => sum2 + cell.size[0] * cell.size[1] * cell.size[2], 0));
24301
+ const { c, q } = partXf(o, p);
24302
+ const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
24303
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
24304
+ for (const cell of fallen) {
24305
+ for (let k = 0; k < 3; k++) {
24306
+ lo[k] = Math.min(lo[k], cell.center[k] - cell.size[k] / 2);
24307
+ hi[k] = Math.max(hi[k], cell.center[k] + cell.size[k] / 2);
24308
+ }
24309
+ }
24310
+ const center = toWorld(c, q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
24311
+ const fragments = cellFragments(o, p, clumpCells(fallen, p.thin, Math.max(2, Math.round(maxFragments / 4))), vec.add(center, [0, 1, 0]), null, 0.3, mass, `u${chipped.length}_`, true);
24312
+ const holes = localHolesOf(p);
24313
+ p.st.holes = p.st.holes.filter((_, k) => !fallen.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length > 0));
24314
+ for (const cell of fallen) chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
24315
+ p.st.mass = Math.max(0, p.st.mass - mass);
24316
+ p.rt.removed = (p.rt.removed ?? 0) + mass;
24317
+ markDamaged(p);
24318
+ const units = fallen.map((cell) => ({ center: toWorld(c, q, cell.center), size: [cell.size[0], cell.size[1], cell.size[2]] }));
24319
+ emit2({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]], rotation: q, fragments, units });
24320
+ debris2(o, p, p.mat.breakDebris, center, [0, -1, 0], 6, 1, src);
24321
+ return loose.length;
24322
+ };
23845
24323
  const stripPart = (o, p, reinforcement, src) => {
23846
24324
  const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
23847
24325
  p.st.stripped = true;
@@ -23908,6 +24386,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23908
24386
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
23909
24387
  const axis2 = style === "splinter" || style === "snap" ? severAxis(p) : -1;
23910
24388
  const cutAt2 = axis2 < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis2] : void 0);
24389
+ const planRnd = p.mat.splitForm ? splitStream(o.id, p.spec.id, hitLocal) : rnd2;
23911
24390
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
23912
24391
  style,
23913
24392
  dims: p.dims,
@@ -23916,8 +24395,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23916
24395
  hit: hitLocal,
23917
24396
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
23918
24397
  maxFragments,
23919
- rnd: rnd2,
24398
+ rnd: planRnd,
23920
24399
  sheet: p.mat.sheet,
24400
+ ...p.mat.splitForm ? { form: p.mat.splitForm } : {},
23921
24401
  ...cutAt2 !== void 0 ? { cut: { at: cutAt2, gap: SEVER_BAND } } : {}
23922
24402
  });
23923
24403
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
@@ -24056,7 +24536,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24056
24536
  const fragments = [];
24057
24537
  for (let i = 0; i < n; i++) {
24058
24538
  const back = !exit || i % 3 === 2;
24059
- const len = scale * (fibre ? 1.2 + 1.2 * rnd2() : 3 + 4 * rnd2()), w = scale * (0.8 + 0.8 * rnd2()), t = scale * (fibre ? 0.7 + 0.6 * rnd2() : 0.4 + 0.4 * rnd2());
24539
+ const plies = p.mat.splitForm === "plies";
24540
+ const len = scale * (fibre ? 1.2 + 1.2 * rnd2() : 3 + 4 * rnd2()), w = scale * (0.8 + 0.8 * rnd2()), t = scale * (fibre ? 0.7 + 0.6 * rnd2() : plies ? 0.15 + 0.15 * rnd2() : 0.4 + 0.4 * rnd2());
24060
24541
  const size = [w, w, w];
24061
24542
  size[along] = len;
24062
24543
  size[(along + 1) % 3] = w;
@@ -24217,7 +24698,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24217
24698
  checkIgnite(o, p, src);
24218
24699
  }
24219
24700
  }
24220
- wearJoints(o, p, Ein * 0.5, void 0, 0, src);
24701
+ wearJoints(o, p, Ein * 0.5, void 0, 0, src, false, 2 * BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3));
24221
24702
  const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
24222
24703
  if (blastFrontSpeed > 0 && dist2 > 0 && mat.tags?.includes("glass")) {
24223
24704
  frontQueue.push({ o, p, due: time + dist2 / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
@@ -24227,7 +24708,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24227
24708
  const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
24228
24709
  res.damage += dealt;
24229
24710
  if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
24230
- if (dist2 <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
24711
+ const breachReach = BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3);
24712
+ if (p.spec.units && p.spec.units.mortar !== p.spec.material) blastUnits(o, p, wp, energy, dist2, src);
24713
+ else if (p.spec.reinforcement && p.spec.units && dist2 > MEMBER_CUT_REACH * breachReach) blastUnits(o, p, wp, energy, dist2, src);
24714
+ else if (dist2 <= breachReach) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
24231
24715
  return reach;
24232
24716
  };
24233
24717
  const blastProbe = { partsTested: 0, partsNear: 0 };
@@ -24473,6 +24957,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24473
24957
  }
24474
24958
  return h | 0;
24475
24959
  };
24960
+ const splitStream = (objectId, partId, hit) => {
24961
+ const codes = Array.from(`${objectId}|${partId}`, (char) => char.charCodeAt(0) / 1e3);
24962
+ const stream = createRng2(seedOf2(rng2.state() / 1e3, ...codes, hit[0], hit[1], hit[2]));
24963
+ return () => stream.next();
24964
+ };
24476
24965
  const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
24477
24966
  const v = frag.velocity;
24478
24967
  const speed = Math.hypot(v[0], v[1], v[2]);
@@ -25420,7 +25909,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25420
25909
  var defaultSet;
25421
25910
  var defaultAssemblies = () => defaultSet ?? (defaultSet = assembliesOver(ASSEMBLIES));
25422
25911
  var getAssembly = (id) => defaultAssemblies().get(id);
25423
- var faceCount = (layer2) => layer2.faces === "both" ? 2 : 1;
25912
+ var faceCount = (layer3) => layer3.faces === "both" ? 2 : 1;
25424
25913
  var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum2, l) => sum2 + l.thickness * faceCount(l), 0);
25425
25914
  var buildAssemblyPart = (def, assemblyId, options) => {
25426
25915
  if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
@@ -25768,7 +26257,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25768
26257
  var DEFAULT_DENSITY = 500;
25769
26258
  var densityOf = (material) => DENSITY[material] ?? DEFAULT_DENSITY;
25770
26259
  var OPEN_ROOM_TYPES = ["balcony", "terrace", "porch"];
26260
+ var WALL_PILLAR_SIDE = 0.2;
26261
+ var HALF = WALL_PILLAR_SIDE / 2;
25771
26262
  var CLOSED_ONLY = [...OPEN_ROOM_TYPES, "void"];
26263
+ var HALF_PILLAR = WALL_PILLAR_SIDE / 2;
25772
26264
  var entry = (itemId, min2, max2, group, pickable, weight) => ({ itemId, min: min2, max: max2, group, pickable, weight });
25773
26265
  var FRIDGE = [
25774
26266
  entry("water_bottle", 1, 3, "water", true, 3),
@@ -26503,6 +26995,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26503
26995
  }
26504
26996
  var LINTEL_DEPTH2 = 0.2;
26505
26997
  var LINTEL_BEARING2 = 0.2;
26998
+ var SILL_BEAM_DEPTH = 0.1;
26999
+ var LINTEL_SPAN_RATIO = 10;
26506
27000
  var MIN_PIECE = 0.05;
26507
27001
  var frameOf = (wall) => {
26508
27002
  const alongU = wall.aabb[2] - wall.aabb[0] >= wall.aabb[3] - wall.aabb[1];
@@ -26516,6 +27010,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26516
27010
  const drafts = [];
26517
27011
  const piers = [];
26518
27012
  const lintels = [];
27013
+ const sillBeams = [];
26519
27014
  const spandrels = [];
26520
27015
  const height = wall.y1 - wall.y0;
26521
27016
  let cursor = frame2.from;
@@ -26526,23 +27021,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26526
27021
  };
26527
27022
  own.forEach((opening2, k) => {
26528
27023
  addPier(Math.max(cursor, opening2.from));
26529
- const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(opening2.to - opening2.from) ?? 0));
27024
+ const span = opening2.to - opening2.from;
27025
+ const lintelTop = Math.min(wall.y1, opening2.y1 + Math.max(LINTEL_DEPTH2, style.lintelDepth?.(span) ?? 0, (span + LINTEL_BEARING2) / LINTEL_SPAN_RATIO));
26530
27026
  const lintelFrom = Math.max(frame2.from, opening2.from - LINTEL_BEARING2);
26531
27027
  const lintelTo = Math.min(frame2.to, opening2.to + LINTEL_BEARING2);
26532
27028
  const lintelHeight = lintelTop - opening2.y1;
26533
- if (opening2.y0 - wall.y0 > MIN_PIECE) drafts.push({ id: `${wall.id}_sill_${k}`, role: "partition", material: wall.material, from: opening2.from, to: opening2.to, y0: wall.y0, y1: opening2.y0, volume: (opening2.to - opening2.from) * (opening2.y0 - wall.y0) });
27029
+ const sillTop = style.sillBeam && opening2.y0 - wall.y0 > SILL_BEAM_DEPTH + MIN_PIECE ? opening2.y0 - SILL_BEAM_DEPTH : opening2.y0;
27030
+ if (sillTop - wall.y0 > MIN_PIECE) drafts.push({ id: `${wall.id}_sill_${k}`, role: "partition", material: wall.material, from: opening2.from, to: opening2.to, y0: wall.y0, y1: sillTop, volume: (opening2.to - opening2.from) * (sillTop - wall.y0) });
27031
+ if (sillTop < opening2.y0) sillBeams.push({ id: `${wall.id}_sillbeam_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: sillTop, y1: opening2.y0, volume: (lintelTo - lintelFrom) * SILL_BEAM_DEPTH });
26534
27032
  lintels.push({ id: `${wall.id}_lintel_${k}`, role: "beam", material: style.lintelMaterial, from: lintelFrom, to: lintelTo, y0: opening2.y1, y1: lintelTop, volume: (lintelTo - lintelFrom) * lintelHeight });
26535
27033
  spandrels.push(wall.y1 - lintelTop > MIN_PIECE ? { id: `${wall.id}_spandrel_${k}`, role: roleOf2, material: wall.material, from: opening2.from, to: opening2.to, y0: lintelTop, y1: wall.y1, volume: (opening2.to - opening2.from) * (wall.y1 - lintelTop) } : void 0);
26536
27034
  cursor = opening2.to;
26537
27035
  });
26538
27036
  addPier(frame2.to);
26539
27037
  for (const pier of piers) {
26540
- for (const lintel of lintels) {
27038
+ for (const lintel of [...lintels, ...sillBeams]) {
26541
27039
  const overlap = Math.max(0, Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from));
26542
27040
  pier.volume -= overlap * (lintel.y1 - lintel.y0);
26543
27041
  }
26544
27042
  }
26545
- const all = [...piers, ...drafts, ...lintels, ...spandrels.filter((d) => d !== void 0)];
27043
+ const all = [...piers, ...drafts, ...sillBeams, ...lintels, ...spandrels.filter((d) => d !== void 0)];
26546
27044
  const total = all.reduce((sum2, d) => sum2 + d.volume, 0);
26547
27045
  const pieces = all.map((d) => ({
26548
27046
  id: d.id,
@@ -26557,6 +27055,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26557
27055
  ...d.role === "beam" ? style.lintelAssembly ? { assembly: style.lintelAssembly } : {} : wall.assembly ? { assembly: wall.assembly } : {}
26558
27056
  }));
26559
27057
  const internal = [];
27058
+ for (const beam of sillBeams) {
27059
+ const sill = drafts.find((d) => d.id === beam.id.replace("_sillbeam_", "_sill_"));
27060
+ if (sill) internal.push({ part: beam.id, on: sill.id, joint: style.lintelJoint });
27061
+ for (const pier of piers) if (Math.min(pier.to, beam.to) - Math.max(pier.from, beam.from) > 0) internal.push({ part: beam.id, on: pier.id, joint: style.lintelJoint });
27062
+ }
26560
27063
  lintels.forEach((lintel, k) => {
26561
27064
  for (const pier of piers) if (Math.min(pier.to, lintel.to) - Math.max(pier.from, lintel.from) > 0) internal.push({ part: lintel.id, on: pier.id, joint: style.lintelJoint });
26562
27065
  const spandrel = spandrels[k];
@@ -26586,7 +27089,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26586
27089
  ...system.timber ? {} : { lintelAssembly: "reinforced_concrete" },
26587
27090
  lintelJoint: system.joints.beam,
26588
27091
  wallJoint: system.joints.wall,
26589
- ...lintelDepth && !system.timber ? { lintelDepth } : {}
27092
+ ...lintelDepth && !system.timber ? { lintelDepth } : {},
27093
+ ...construction.system === "masonry" ? { sillBeam: true } : {}
26590
27094
  });
26591
27095
  if (split.pieces.length > 1) splits.set(part.id, split);
26592
27096
  }
@@ -26594,7 +27098,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26594
27098
  }
26595
27099
  function applyPanels(input, structure, detail, lintelDepth) {
26596
27100
  if (detail === "coarse") return input;
26597
- return applySplits(input, panelSplits(input, structure, lintelDepth));
27101
+ return onJambPillars(applySplits(input, panelSplits(input, structure, lintelDepth)));
27102
+ }
27103
+ var OPENING_BEAM = /_(lintel|sillbeam)_\d+$/;
27104
+ var overlaps2 = (a, b) => a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
27105
+ function onJambPillars(split) {
27106
+ const pillars = split.parts.filter((part) => part.role === "column" && part.material === "concrete");
27107
+ const extra = [];
27108
+ for (const beam of split.parts) {
27109
+ if (beam.role !== "beam" || !OPENING_BEAM.test(beam.id)) continue;
27110
+ for (const pillar of pillars) if (pillar.floor === beam.floor && overlaps2(pillar.aabb, beam.aabb) && pillar.y0 <= beam.y0 && pillar.y1 >= beam.y1 - 0.01) extra.push({ part: beam.id, on: pillar.id, joint: "rebar" });
27111
+ }
27112
+ return extra.length ? { parts: split.parts, supports: [...split.supports, ...extra] } : split;
26598
27113
  }
26599
27114
  function applyUnits(input, structure) {
26600
27115
  const splits = /* @__PURE__ */ new Map();
@@ -26656,21 +27171,35 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26656
27171
  };
26657
27172
  var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
26658
27173
  var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
27174
+ var CAGE_COVER = 0.03;
27175
+ var CAGE_BUCKLING_SHARE = 0.5;
27176
+ var DEFAULT_RESIDUAL = 0.15;
27177
+ function cageResidualCapacity(size, reinforcement, capacity) {
27178
+ const [a = 0, b = 0] = [...size].sort((x, y) => x - y);
27179
+ const perSide = (w) => Math.max(2, Math.floor(Math.max(0, w - 2 * CAGE_COVER) / reinforcement.spacing) + 1);
27180
+ const bars = 2 * perSide(a) + 2 * perSide(b) - 4;
27181
+ const steel = MATERIALS2.find((m) => m.id === reinforcement.material)?.compressive ?? 0;
27182
+ const carries2 = bars * Math.PI * (reinforcement.diameter / 2) ** 2 * steel * CAGE_BUCKLING_SHARE;
27183
+ if (!capacity || capacity <= 0 || carries2 <= 0) return DEFAULT_RESIDUAL;
27184
+ return Math.min(1, Math.max(DEFAULT_RESIDUAL, carries2 / capacity));
27185
+ }
26659
27186
  var caged = (spec, part, rebar2) => {
26660
27187
  if (!rebar2 || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
26661
27188
  const size = spec.shape.kind === "box" ? spec.shape.size : null;
26662
27189
  if (!size) return spec;
26663
27190
  const thin = Math.min(...size);
27191
+ const reinforcement = { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter };
26664
27192
  return {
26665
27193
  ...spec,
26666
- reinforcement: { material: rebar2, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
27194
+ reinforcement: part.role === "column" ? { ...reinforcement, residualCapacity: cageResidualCapacity(size, reinforcement, spec.structure?.capacity) } : reinforcement,
26667
27195
  units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
26668
27196
  };
26669
27197
  };
26670
27198
  var partSpecOf = (part, capacity, detailed, rebar2) => {
26671
27199
  if (detailed && part.assembly) return assemblySpec(part, capacity);
26672
27200
  const spec = boxSpec(part, capacity);
26673
- return detailed ? caged(spec, part, rebar2) : spec;
27201
+ const out = detailed ? caged(spec, part, rebar2) : spec;
27202
+ return part.beamKind === "ground_beam" ? { ...out, anchored: true, structure: { role: "foundation" } } : out;
26674
27203
  };
26675
27204
  function toDestructibleSpec(structure, options = {}) {
26676
27205
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
@@ -26686,7 +27215,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26686
27215
  const walled = applyPanels(slabbed, structure, detail, slabOptions && structure.construction.frame.rebar ? (span) => beamDepthFor(span, slabOptions) : void 0);
26687
27216
  const panels = slabOptions && detail !== "coarse" ? deepenBondBeams(walled, structure, slabOptions) : walled;
26688
27217
  const sizing = { safety, redundancy, safetyOthers };
26689
- const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
27218
+ const foundation = new Set(panels.parts.filter((part) => part.beamKind === "ground_beam").map((part) => part.id));
27219
+ const onFoundation = foundation.size ? panels.supports.filter((s) => !foundation.has(s.part)).map((s) => foundation.has(s.on) ? { ...s, on: GROUND } : s) : panels.supports;
27220
+ const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts.filter((part) => !foundation.has(part.id)), onFoundation, sizing) };
26690
27221
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
26691
27222
  const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
26692
27223
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
@@ -26751,6 +27282,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26751
27282
  out.parts = out.parts.map((p, i) => i === ground ? raft2 : p);
26752
27283
  return out;
26753
27284
  }
27285
+ var LOW_WINDOW = SILL_BEAM_DEPTH + 0.05;
26754
27286
  var slabId = (g, f) => f === g.floors - 1 && g.roof ? "roof" : `slab_${f}`;
26755
27287
  var storeyBase = (g, f) => f * (g.height + g.slab);
26756
27288
  var slabHalf = (g) => [(g.spanX + g.columnSize + 0.2) / 2, (g.spanZ + g.columnSize + 0.2) / 2];
@@ -26856,13 +27388,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26856
27388
  const c = { ...CURTAIN_DEFAULTS, ...options };
26857
27389
  const parts = [...spec.parts];
26858
27390
  const joints = [...spec.joints ?? []];
26859
- const [hx, hz] = slabHalf(g);
27391
+ const [hx, hz2] = slabHalf(g);
26860
27392
  const h = g.height + g.slab;
26861
27393
  const sides = [
26862
- { name: "n", along: 0, length: 2 * hx, fixed: hz + c.offset, axis: 2 },
26863
- { name: "s", along: 0, length: 2 * hx, fixed: -hz - c.offset, axis: 2 },
26864
- { name: "e", along: 2, length: 2 * hz, fixed: hx + c.offset, axis: 0 },
26865
- { name: "w", along: 2, length: 2 * hz, fixed: -hx - c.offset, axis: 0 }
27394
+ { name: "n", along: 0, length: 2 * hx, fixed: hz2 + c.offset, axis: 2 },
27395
+ { name: "s", along: 0, length: 2 * hx, fixed: -hz2 - c.offset, axis: 2 },
27396
+ { name: "e", along: 2, length: 2 * hz2, fixed: hx + c.offset, axis: 0 },
27397
+ { name: "w", along: 2, length: 2 * hz2, fixed: -hx - c.offset, axis: 0 }
26866
27398
  ];
26867
27399
  for (let f = 0; f < g.floors; f++) {
26868
27400
  if (c.skip?.(f)) continue;
@@ -26909,7 +27441,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26909
27441
  parts.push({ id, material: c.material, shape: box2(size), position, structure: { role: "partition" }, tags: ["infill"] });
26910
27442
  for (const o of on) rests(joints, id, o, "mortar");
26911
27443
  };
26912
- const bay = (side, k, along, centre, fixed2, length2, c1, c2) => {
27444
+ const bay = (side, k, along, centre2, fixed2, length2, c1, c2) => {
26913
27445
  const clear = length2 - g.columnSize;
26914
27446
  const win = clear * c.window;
26915
27447
  const pier = (clear - win) / 2;
@@ -26920,11 +27452,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26920
27452
  wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
26921
27453
  };
26922
27454
  const id = (name) => `infill_${f}_${side}_${k}_${name}`;
26923
- put("sill", clear, sill, yb + sill / 2, centre, [c1, c2]);
26924
- put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
26925
- put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
26926
- put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
26927
- const gp = at23(centre);
27455
+ put("sill", clear, sill, yb + sill / 2, centre2, [c1, c2]);
27456
+ put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre2 - clear / 2 + pier / 2, [c1, id("sill")]);
27457
+ put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre2 + clear / 2 - pier / 2, [c2, id("sill")]);
27458
+ put("head", clear, room - head, yb + head + (room - head) / 2, centre2, [c1, c2]);
27459
+ const gp = at23(centre2);
26928
27460
  const gid = `glass_${f}_${side}_${k}`;
26929
27461
  parts.push({
26930
27462
  id: gid,
@@ -27391,7 +27923,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
27391
27923
  }
27392
27924
  var towerLocales = { en: { labels: en_default.labels }, "pt-BR": { labels: pt_BR_default.labels } };
27393
27925
 
27394
- // ../game-floorplan-three/dist/chunk-QUVGWVEC.js
27926
+ // ../game-floorplan-three/dist/chunk-7XVCZF6H.js
27395
27927
  var en2 = {
27396
27928
  "prompt.open": "E: open {what}",
27397
27929
  "prompt.close": "E: close {what}",
@@ -28133,6 +28665,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
28133
28665
  }
28134
28666
  function installHouseWalls(K, shared2) {
28135
28667
  const { EPS: EPS6, Y0, boxC, bx, cursor, effectiveHeight, pieceRecord, structurePiece, wallColor } = K;
28668
+ const sh = (color2, factor) => K.sh(color2, factor);
28136
28669
  function wallBox(alongU, L, a, b, ya, yb, th, cL, cH, mL, mH) {
28137
28670
  if (yb - ya < 1e-4 || b - a < 1e-4) return;
28138
28671
  if (alongU) {
@@ -28176,6 +28709,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
28176
28709
  function walls() {
28177
28710
  const R = shared2.ctx.r, cut = shared2.ctx.cut, nA = R.floors.length, vMin = Math.min(...R.walls.map((w) => w.axis === "u" ? w.v0 : Math.min(w.v0, w.v1)));
28178
28711
  const corners = /* @__PURE__ */ new Map();
28712
+ const looks = /* @__PURE__ */ new Map();
28179
28713
  for (const w of R.walls) {
28180
28714
  const f = w.floor;
28181
28715
  cursor.group = "f" + f;
@@ -28185,6 +28719,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
28185
28719
  const hh = cut ? Math.min(h, 1.2) : h, L = alongU ? w.v0 : w.u0, s0 = Math.min(alongU ? w.u0 : w.v0, alongU ? w.u1 : w.v1), s1 = Math.max(alongU ? w.u0 : w.v0, alongU ? w.u1 : w.v1);
28186
28720
  const front = alongU && Math.abs(L - vMin) < 0.05;
28187
28721
  const sideL = sideOf(w.sides[0], front), sideH = sideOf(w.sides[1], front);
28722
+ if (w.id) {
28723
+ const face = sideL.exterior ? sideL : sideH;
28724
+ looks.set(w.id, { mat: face.mat, color: face.color, top: y0 + hh });
28725
+ }
28188
28726
  shared2.ctx.colliders.push({ type: "wall", piece: w.id, f, x0: alongU ? s0 : L - th / 2, z0: alongU ? L - th / 2 : s0, x1: alongU ? s1 : L + th / 2, z1: alongU ? L + th / 2 : s1 });
28189
28727
  if (w.type === "railing") {
28190
28728
  const c0 = shared2.ctx.colors.metal != null ? shared2.ctx.colors.metal : 9080466;
@@ -28231,8 +28769,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
28231
28769
  cursor.group = "f" + e.f;
28232
28770
  bx(e.color.mat, e.pu - e.th / 2, e.y0, e.pv - e.th / 2, e.pu + e.th / 2, e.y0 + e.hh, e.pv + e.th / 2, e.color.color);
28233
28771
  }
28772
+ if (shared2.ctx.showPillars) wallPillars(looks);
28773
+ }
28774
+ const PILLAR_IN_WALL = /^(wall_column_|column_\d|jamb_column_)/;
28775
+ const wallOf = (pillar, parts) => {
28776
+ const u = (pillar.aabb[0] + pillar.aabb[2]) / 2, v = (pillar.aabb[1] + pillar.aabb[3]) / 2;
28777
+ return parts.find((q) => (q.role === "wall" || q.role === "partition") && q.assembly !== void 0 && q.floor === pillar.floor && u >= q.aabb[0] - 1e-3 && u <= q.aabb[2] + 1e-3 && v >= q.aabb[1] - 1e-3 && v <= q.aabb[3] + 1e-3);
28778
+ };
28779
+ function wallPillars(looks) {
28780
+ const parts = Object.values(shared2.ctx.structureIndex);
28781
+ for (const e of parts) {
28782
+ if (e.role !== "column" || !PILLAR_IN_WALL.test(e.id)) continue;
28783
+ const host = wallOf(e, parts);
28784
+ const look = host && looks.get(host.id);
28785
+ if (!host || !look) continue;
28786
+ cursor.group = "f" + e.floor;
28787
+ cursor.pc = structurePiece(host.id);
28788
+ bx(look.mat, e.aabb[0], e.y0, e.aabb[1], e.aabb[2], Math.min(e.y1, look.top), e.aabb[3], sh(look.color != null ? look.color : 15262420, 0.93));
28789
+ }
28790
+ cursor.pc = null;
28234
28791
  }
28235
- Object.assign(K, { wallBox, sideOf, pieceOf, nearestWall, walls });
28792
+ Object.assign(K, { wallBox, sideOf, pieceOf, nearestWall, walls, wallPillars });
28236
28793
  }
28237
28794
  function installHouseDoors(K, shared2) {
28238
28795
  const seedKey = (id) => K.seedKey(id);
@@ -33604,7 +34161,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33604
34161
  if (!pc || pc.unit) return sp;
33605
34162
  if (pc.role !== "wall" && pc.role !== "partition") {
33606
34163
  const def = getAssembly(pc.assembly || (pc.material === "concrete" && ["slab", "beam", "column", "stair"].includes(pc.role) ? "reinforced_concrete" : ""));
33607
- const out = def && def.reinforcement && !sp.reinforcement ? { ...sp, reinforcement: { ...def.reinforcement } } : sp;
34164
+ const cage = def && def.reinforcement ? pc.role === "column" && sp.shape.kind === "box" ? { ...def.reinforcement, residualCapacity: cageResidualCapacity(sp.shape.size, def.reinforcement, sp.structure?.capacity) } : { ...def.reinforcement } : null;
34165
+ const out = cage && !sp.reinforcement ? { ...sp, reinforcement: cage } : sp;
33608
34166
  if (out.reinforcement && !out.units && pc.material === "concrete" && (pc.role === "beam" || pc.role === "column") && sp.shape.kind === "box") {
33609
34167
  return { ...out, units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, Math.min(...sp.shape.size)], bond: "stack", mortar: "concrete" } };
33610
34168
  }
@@ -33630,6 +34188,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33630
34188
  const a = pos.array;
33631
34189
  const u = mesh3.userData;
33632
34190
  if (!u._p0) u._p0 = a.slice();
34191
+ if (!u.plainPositions) Object.defineProperty(u, "plainPositions", { value: u._p0, enumerable: false, configurable: true, writable: true });
33633
34192
  const list2 = u.collapsedRanges ?? (u.collapsedRanges = []);
33634
34193
  if (!list2.some((r) => r[0] === v0 && r[1] === v1)) list2.push([v0, v1]);
33635
34194
  const saved = a.slice(v0 * 3, v1 * 3);
@@ -33713,10 +34272,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33713
34272
  const v = house.worldToLocal(tmp.p.set(p[0], p[1], p[2]));
33714
34273
  return [v.x, v.y, v.z];
33715
34274
  };
33716
- function cells(part, units) {
34275
+ function cells(part, units, mask) {
33717
34276
  const [u0, v0, u1, v1] = part.aabb;
33718
- const alongU = u1 - u0 >= v1 - v0;
34277
+ const alongU = mask.alongU;
33719
34278
  const len = alongU ? u1 - u0 : v1 - v0;
34279
+ const offset = (alongU ? u0 : v0) - mask.start;
33720
34280
  const [ul, uh] = units.size;
33721
34281
  const rows = Math.max(1, Math.round((part.y1 - part.y0) / uh));
33722
34282
  const rowH = (part.y1 - part.y0) / rows;
@@ -33727,7 +34287,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33727
34287
  const shift = units.bond === "running" && r & 1 ? w / 2 : 0;
33728
34288
  for (let i = 0; i < per + (shift ? 1 : 0); i++) {
33729
34289
  const a = Math.max(0, i * w - shift), b = Math.min(len, i * w - shift + w);
33730
- if (b - a > 1e-3) out.push({ a, b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
34290
+ if (b - a > 1e-3) out.push({ a: offset + a, b: offset + b, y0: part.y0 + r * rowH, y1: part.y0 + (r + 1) * rowH, r, i });
33731
34291
  }
33732
34292
  }
33733
34293
  return out;
@@ -33741,8 +34301,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33741
34301
  for (const q of structure.parts) {
33742
34302
  if (q.id !== partId && same(q.aabb, part.aabb) && Math.abs(q.y1 - part.y0) < 0.01 && structure.meshesOf(q.id).some((it) => it.mesh)) return q.id;
33743
34303
  }
34304
+ return part.role === "column" ? wallAround(part) : null;
34305
+ }
34306
+ function wallAround(part) {
34307
+ const cu = (part.aabb[0] + part.aabb[2]) / 2, cv = (part.aabb[1] + part.aabb[3]) / 2;
34308
+ for (const q of structure.parts) {
34309
+ if (q.id === part.id || q.role !== "wall" && q.role !== "partition" || !structure.meshesOf(q.id).some((it) => it.mesh)) continue;
34310
+ const inside2 = cu >= q.aabb[0] - 1e-3 && cu <= q.aabb[2] + 1e-3 && cv >= q.aabb[1] - 1e-3 && cv <= q.aabb[3] + 1e-3;
34311
+ if (inside2 && Math.min(q.y1, part.y1) - Math.max(q.y0, part.y0) > 0.5) return q.id;
34312
+ }
33744
34313
  return null;
33745
34314
  }
34315
+ const pillarSpans = (view) => structure.parts.filter((q) => q.role === "column" && wallAround(q) === view.host).map((q) => {
34316
+ const a = (view.mask.alongU ? q.aabb[0] : q.aabb[1]) - view.mask.start, b = (view.mask.alongU ? q.aabb[2] : q.aabb[3]) - view.mask.start;
34317
+ return { a, b, y0: q.y0, y1: q.y1 };
34318
+ });
33746
34319
  const riders = (hostId) => structure.parts.filter((q) => q.id !== hostId && specOf(q.id)?.units && hostOf(q.id) === hostId && !structure.meshesOf(q.id).some((it) => it.mesh)).map((q) => q.id);
33747
34320
  function shell(it, mask) {
33748
34321
  const g0 = it.mesh.geometry, g = new THREE.BufferGeometry();
@@ -33831,6 +34404,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33831
34404
  texture.needsUpdate = true;
33832
34405
  return { data, texture, w, h, alongU, start: alongU ? u0 : v0, len, y0, height: y1 - y0, rect: new THREE.Vector4(alongU ? u0 : v0, y0, len, y1 - y0) };
33833
34406
  }
34407
+ function besidePillars(c, pillars) {
34408
+ let { a, b } = c;
34409
+ for (const k of pillars) {
34410
+ if (c.y1 <= k.y0 || c.y0 >= k.y1 || b <= k.a || a >= k.b) continue;
34411
+ const centre = (a + b) / 2;
34412
+ if (centre > k.a && centre < k.b) return [];
34413
+ if (centre <= k.a) b = Math.min(b, k.a);
34414
+ else a = Math.max(a, k.b);
34415
+ }
34416
+ return b - a > 1e-3 ? [{ ...c, a, b }] : [];
34417
+ }
33834
34418
  const boxDistance = (s, y, a, b, c, d) => Math.hypot(Math.max(a - s, 0, s - b), Math.max(c - y, 0, y - d));
33835
34419
  const ellipseDistance = (s, y, cs, cy, rs, ry) => (Math.hypot((s - cs) / Math.max(rs, 1e-4), (y - cy) / Math.max(ry, 1e-4)) - 1) * Math.min(rs, ry);
33836
34420
  function covered(c, boxes) {
@@ -33853,9 +34437,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33853
34437
  const discs = [];
33854
34438
  const flakes = [];
33855
34439
  const shown = [];
34440
+ const pillars = pillarSpans(view);
33856
34441
  for (const id of view.parts) {
33857
34442
  const part = structure.byId.get(id), units2 = specOf(id).units, chips2 = view.chips.get(id), opening = view.openings.get(id), spalls = view.spalls.get(id);
33858
- const all = cells(part, units2);
34443
+ const own = cells(part, units2, mask);
34444
+ const all = id === view.host ? own.flatMap((c) => besidePillars(c, pillars)) : own;
34445
+ if (view.fallen.has(id)) {
34446
+ holes.push(...all);
34447
+ continue;
34448
+ }
33859
34449
  const thick = mask.alongU ? part.aabb[3] - part.aabb[1] : part.aabb[2] - part.aabb[0];
33860
34450
  const mid = mask.alongU ? (part.aabb[1] + part.aabb[3]) / 2 : (part.aabb[0] + part.aabb[2]) / 2;
33861
34451
  if (opening) discs.push({ s: along(opening.center), y: opening.center[1], r: opening.radius });
@@ -33951,52 +34541,55 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
33951
34541
  }
33952
34542
  return { center: local2(chip.center), along: a, up: u };
33953
34543
  }
34544
+ function newView(hostId) {
34545
+ const items = structure.meshesOf(hostId).filter((it) => it.mesh);
34546
+ if (!items.length) return null;
34547
+ const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
34548
+ const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
34549
+ return {
34550
+ host: hostId,
34551
+ parts,
34552
+ items,
34553
+ mask,
34554
+ core,
34555
+ unitMaterial: unitsMaterial(core),
34556
+ shells: [],
34557
+ saved: null,
34558
+ units: null,
34559
+ gone: 0,
34560
+ pits: 0,
34561
+ bounds: drawnBounds(items),
34562
+ chips: new Map(parts.map((id) => [id, []])),
34563
+ spalls: new Map(parts.map((id) => [id, []])),
34564
+ openings: /* @__PURE__ */ new Map(),
34565
+ flakes: 0,
34566
+ fallen: /* @__PURE__ */ new Set()
34567
+ };
34568
+ }
34569
+ function cut(view) {
34570
+ if (view.saved) return;
34571
+ view.shells = view.items.map((it) => shell(it, view.mask));
34572
+ for (const m of view.shells) m.userData.behind = () => view.units;
34573
+ view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
34574
+ }
33954
34575
  function draw2(partId, change) {
33955
34576
  if (lod !== 0) return false;
33956
34577
  const hostId = hostOf(partId);
33957
34578
  if (!hostId || !specOf(partId)?.units) return false;
33958
- let view = views.get(hostId);
33959
- if (!view) {
33960
- const items = structure.meshesOf(hostId).filter((it) => it.mesh);
33961
- if (!items.length) return false;
33962
- const parts = [hostId, ...riders(hostId)].filter((id) => structure.byId.get(id) && specOf(id)?.units);
33963
- if (!parts.includes(partId)) return false;
33964
- const mask = maskOf(hostId, parts), core = maskOf(hostId, parts);
33965
- view = {
33966
- host: hostId,
33967
- parts,
33968
- items,
33969
- mask,
33970
- core,
33971
- unitMaterial: unitsMaterial(core),
33972
- shells: [],
33973
- saved: null,
33974
- units: null,
33975
- gone: 0,
33976
- pits: 0,
33977
- bounds: drawnBounds(items),
33978
- chips: new Map(parts.map((id) => [id, []])),
33979
- spalls: new Map(parts.map((id) => [id, []])),
33980
- openings: /* @__PURE__ */ new Map(),
33981
- flakes: 0
33982
- };
33983
- }
33984
- if (!view.chips.has(partId)) return false;
34579
+ const view = views.get(hostId) ?? newView(hostId);
34580
+ if (!view || !view.chips.has(partId)) return false;
33985
34581
  if (change.opening) {
33986
34582
  if (view.openings.has(partId)) return false;
33987
34583
  view.openings.set(partId, { center: local2(change.opening.center), radius: change.opening.radius });
33988
34584
  }
33989
34585
  if (change.chip) view.chips.get(partId).push(chipBox(change.chip, view.mask.alongU));
34586
+ for (const chip of change.chips ?? []) view.chips.get(partId).push(chipBox(chip, view.mask.alongU));
33990
34587
  if (change.spall) {
33991
34588
  const list2 = view.spalls.get(partId);
33992
34589
  list2.push({ center: local2(change.spall.center), radius: change.spall.radius });
33993
34590
  if (list2.length > MAX_FLAKES) list2.shift();
33994
34591
  }
33995
- if (!view.saved) {
33996
- view.shells = view.items.map((it) => shell(it, view.mask));
33997
- for (const m of view.shells) m.userData.behind = () => view.units;
33998
- view.saved = view.items.map((it) => ({ it, v0: it.v0, data: collapseRange(it.mesh, it.v0, it.v1) }));
33999
- }
34592
+ cut(view);
34000
34593
  views.set(hostId, view);
34001
34594
  if (opts.deferPaint && change.spall) dirty.add(view);
34002
34595
  else {
@@ -34107,14 +34700,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34107
34700
  */
34108
34701
  chip(event) {
34109
34702
  if (event.torn) return false;
34110
- return draw2(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation: event.rotation ?? [0, 0, 0, 1] } });
34703
+ const rotation2 = event.rotation ?? [0, 0, 0, 1];
34704
+ if (event.units?.length) return draw2(event.part, { chips: event.units.map((unit) => ({ center: unit.center, size: unit.size, rotation: rotation2 })) });
34705
+ return draw2(event.part, { chip: { center: event.center, size: event.size ?? [0, 0, 0], rotation: rotation2 } });
34111
34706
  },
34112
34707
  /** The part broke or fell: its damage goes (the plain range stays collapsed, the renderer hides the part). */
34113
34708
  drop(partId) {
34114
- const v = views.get(partId);
34115
- if (!v) return;
34709
+ let host = views.get(partId) ?? [...views.values()].find((view) => view.parts.includes(partId));
34710
+ if (!host && lod === 0 && riders(partId).length > 0) {
34711
+ host = newView(partId);
34712
+ if (host) {
34713
+ cut(host);
34714
+ views.set(partId, host);
34715
+ }
34716
+ }
34717
+ if (!host) return;
34718
+ host.fallen.add(partId);
34719
+ if (host.parts.some((id) => !host.fallen.has(id))) {
34720
+ dirty.delete(host);
34721
+ paint2(host);
34722
+ invalidate();
34723
+ invalidateShadow();
34724
+ return;
34725
+ }
34726
+ const v = host;
34116
34727
  dispose(v);
34117
- views.delete(partId);
34728
+ views.delete(v.host);
34118
34729
  if (v.saved) dropped.push(v);
34119
34730
  },
34120
34731
  /** LOD 0 draws the cut wall; LOD 1 and 2 give the wall its box back. */
@@ -34589,6 +35200,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
34589
35200
  shared2.ctx.noPiece = false;
34590
35201
  cursor.separate = opts.pieces === "meshes";
34591
35202
  cursor.pc = null;
35203
+ shared2.ctx.showPillars = opts.pillars !== "hidden";
34592
35204
  shared2.ctx.apartment = r.template === "apartment_modern";
34593
35205
  shared2.ctx.pa = Math.max(3, Math.min(20, pr.floors != null ? pr.floors : 5));
34594
35206
  shared2.ctx.pf = Math.max(1, Math.min(shared2.ctx.pa, pr.floor != null ? pr.floor : 3));
@@ -36723,11 +37335,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36723
37335
  else return false;
36724
37336
  return true;
36725
37337
  };
36726
- const zoomOf = (tool2) => {
36727
- const optic = (tool2.attachments ?? []).map((part) => arsenal.attachments.get(part.id)?.optic).find((entry2) => entry2 !== void 0);
36728
- return optic?.zoom[0] ?? 1;
37338
+ const zoomOf = (tool2) => aimZoomOf(arsenal, tool2.weapon, tool2.attachments?.map((part) => part.id));
37339
+ const aims = (tool2) => {
37340
+ const kind = arsenal.weapons.get(tool2.weapon)?.action.kind;
37341
+ return kind === "firearm" || kind === "launcher";
36729
37342
  };
36730
- const isFirearm = (tool2) => arsenal.weapons.get(tool2.weapon)?.action.kind === "firearm";
36731
37343
  const setAim = (on) => {
36732
37344
  if (on === aiming) return;
36733
37345
  aiming = on;
@@ -36745,6 +37357,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36745
37357
  camera2.updateProjectionMatrix();
36746
37358
  };
36747
37359
  const onKeyDown = (event) => {
37360
+ if (host.playerHeld?.()) return;
36748
37361
  if (event.repeat || event.ctrlKey || event.metaKey || event.altKey || TEXT_FIELDS.has(event.target?.tagName ?? "")) return;
36749
37362
  const handled2 = event.code === ACCESSORY_KEYS.light ? act(ACTION_IDS.light) : event.code === ACCESSORY_KEYS.laser ? act(ACTION_IDS.laser) : event.code === ACCESSORY_KEYS.underbarrel ? act(ACTION_IDS.underbarrel) : event.code === ACCESSORY_KEYS.stock && toggleStock();
36750
37363
  if (!handled2) return;
@@ -36753,7 +37366,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36753
37366
  };
36754
37367
  const isOnRoom = (event) => Boolean(document.pointerLockElement) || event.target instanceof HTMLCanvasElement;
36755
37368
  const onMouseDown = (event) => {
36756
- if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !isFirearm(host.tool())) return;
37369
+ if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !aims(host.tool())) return;
36757
37370
  event.preventDefault();
36758
37371
  event.stopImmediatePropagation();
36759
37372
  setAim(true);
@@ -36929,7 +37542,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36929
37542
  };
36930
37543
  var accessoryTextOf = (locale) => ACCESSORY_TEXT[locale] ?? ACCESSORY_TEXT.en;
36931
37544
 
36932
- // ../game-audio/dist/chunk-KBTUANNG.js
37545
+ // ../game-audio/dist/chunk-C2K7XN2K.js
36933
37546
  var DISTANCE_CURVES = {
36934
37547
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
36935
37548
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -37191,12 +37804,46 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37191
37804
  }
37192
37805
  return merged2;
37193
37806
  }
37807
+ var trimmed = (refs, alias) => refs.map((ref) => ({
37808
+ ...ref,
37809
+ ...alias.offset === void 0 ? {} : { offset: alias.offset },
37810
+ ...alias.duration === void 0 ? {} : { duration: alias.duration }
37811
+ }));
37812
+ function aliasSample(alias, file) {
37813
+ const { tiers: _tiers, fallback: _fallback, choke: _choke, ...rest2 } = file;
37814
+ return {
37815
+ ...rest2,
37816
+ id: alias.id,
37817
+ close: trimmed(file.close, alias),
37818
+ ...file.far ? { far: trimmed(file.far, alias) } : {},
37819
+ ...alias.loop === void 0 ? {} : { loop: alias.loop },
37820
+ ...alias.baseFrequency === void 0 ? {} : { baseFrequency: alias.baseFrequency },
37821
+ ...alias.gain === void 0 ? {} : { gain: alias.gain },
37822
+ ...alias.pitch === void 0 ? {} : { pitch: alias.pitch },
37823
+ ...alias.falloff ? { falloff: alias.falloff } : {},
37824
+ fallback: alias.id,
37825
+ sources: [...file.sources, `alias: ${alias.id} \u2192 ${alias.file}`]
37826
+ };
37827
+ }
37194
37828
  function createSoundRegistry(banks = []) {
37195
37829
  const sounds = /* @__PURE__ */ new Map();
37196
37830
  const loops = /* @__PURE__ */ new Map();
37197
37831
  const sets = /* @__PURE__ */ new Map();
37198
37832
  const samples = /* @__PURE__ */ new Map();
37199
37833
  const owners = /* @__PURE__ */ new Map();
37834
+ const aliases = /* @__PURE__ */ new Map();
37835
+ const derived = /* @__PURE__ */ new Map();
37836
+ const resolveAlias = (id) => {
37837
+ const alias = aliases.get(id);
37838
+ if (!alias) return void 0;
37839
+ const cached = derived.get(id);
37840
+ if (cached) return cached;
37841
+ const file = samples.get(alias.file);
37842
+ if (!file) return void 0;
37843
+ const sample = aliasSample(alias, file);
37844
+ derived.set(id, sample);
37845
+ return sample;
37846
+ };
37200
37847
  const registry = {
37201
37848
  register(bank) {
37202
37849
  for (const sound3 of bank.sounds ?? []) {
@@ -37216,6 +37863,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37216
37863
  samples.set(sample.id, sample);
37217
37864
  owners.set(sample.id, bank.id);
37218
37865
  }
37866
+ for (const alias of bank.sampleAliases ?? []) {
37867
+ aliases.set(alias.id, alias);
37868
+ if (!owners.has(alias.id)) owners.set(alias.id, bank.id);
37869
+ }
37870
+ derived.clear();
37219
37871
  },
37220
37872
  patch(id, patch) {
37221
37873
  const sound3 = sounds.get(id);
@@ -37224,22 +37876,31 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37224
37876
  if (loop) loops.set(id, mergeRecords(loop, patch));
37225
37877
  const sample = samples.get(id);
37226
37878
  if (sample) samples.set(id, mergeRecords(sample, patch));
37879
+ derived.clear();
37227
37880
  },
37228
37881
  remove(id) {
37229
37882
  sounds.delete(id);
37230
37883
  loops.delete(id);
37231
37884
  sets.delete(id);
37232
37885
  samples.delete(id);
37886
+ aliases.delete(id);
37233
37887
  owners.delete(id);
37888
+ derived.clear();
37889
+ },
37890
+ removeSample(id) {
37891
+ samples.delete(id);
37892
+ aliases.delete(id);
37893
+ if (!sounds.has(id) && !loops.has(id) && !sets.has(id)) owners.delete(id);
37894
+ derived.clear();
37234
37895
  },
37235
37896
  sound: (id) => sounds.get(id),
37236
37897
  loop: (id) => loops.get(id),
37237
37898
  set: (id) => sets.get(id),
37238
- sample: (id) => samples.get(id),
37899
+ sample: (id) => samples.get(id) ?? resolveAlias(id),
37239
37900
  soundIds: () => [...sounds.keys()],
37240
37901
  setIds: () => [...sets.keys()],
37241
37902
  loopIds: () => [...loops.keys()],
37242
- sampleIds: () => [...samples.keys()],
37903
+ sampleIds: () => [...samples.keys(), ...[...aliases.keys()].filter((id) => !samples.has(id) && resolveAlias(id) !== void 0)],
37243
37904
  bankOf: (id) => owners.get(id)
37244
37905
  };
37245
37906
  for (const bank of banks) registry.register(bank);
@@ -37356,16 +38017,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37356
38017
  }
37357
38018
  return pending;
37358
38019
  }
37359
- function drawLayer(layer2, scope2) {
38020
+ function drawLayer(layer3, scope2) {
37360
38021
  const pending = { at: 0, gain: 1, filters: [], modulators: [] };
37361
- for (const key2 of Object.keys(layer2)) {
37362
- if (key2 === "at" && layer2.at !== void 0) pending.at = draw(layer2.at, scope2);
37363
- else if (key2 === "duration" && layer2.duration !== void 0) pending.duration = draw(layer2.duration, scope2);
37364
- else if (key2 === "gain" && layer2.gain !== void 0) pending.gain = draw(layer2.gain, scope2);
37365
- else if (key2 === "source" && layer2.source.kind === "osc") pending.automation = drawAutomation(layer2.source.frequency, scope2);
37366
- else if (key2 === "source" && layer2.source.kind === "formant_voice") pending.voice = drawVoice(layer2.source, scope2);
37367
- else if (key2 === "filters" && layer2.filters) pending.filters = layer2.filters.bank.map((spec) => drawFilter(spec, scope2));
37368
- else if (key2 === "modulators" && layer2.modulators) pending.modulators = layer2.modulators.map((modulator) => drawModulator(modulator, scope2));
38022
+ for (const key2 of Object.keys(layer3)) {
38023
+ if (key2 === "at" && layer3.at !== void 0) pending.at = draw(layer3.at, scope2);
38024
+ else if (key2 === "duration" && layer3.duration !== void 0) pending.duration = draw(layer3.duration, scope2);
38025
+ else if (key2 === "gain" && layer3.gain !== void 0) pending.gain = draw(layer3.gain, scope2);
38026
+ else if (key2 === "source" && layer3.source.kind === "osc") pending.automation = drawAutomation(layer3.source.frequency, scope2);
38027
+ else if (key2 === "source" && layer3.source.kind === "formant_voice") pending.voice = drawVoice(layer3.source, scope2);
38028
+ else if (key2 === "filters" && layer3.filters) pending.filters = layer3.filters.bank.map((spec) => drawFilter(spec, scope2));
38029
+ else if (key2 === "modulators" && layer3.modulators) pending.modulators = layer3.modulators.map((modulator) => drawModulator(modulator, scope2));
37369
38030
  }
37370
38031
  return pending;
37371
38032
  }
@@ -37376,8 +38037,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37376
38037
  if (source.offset === "fit") return scope2.random() * Math.max(0, noiseSeconds - duration);
37377
38038
  return scope2.random() * (source.offsetScale ?? 1);
37378
38039
  }
37379
- function finishSource(layer2, pending, duration, offset) {
37380
- const source = layer2.source;
38040
+ function finishSource(layer3, pending, duration, offset) {
38041
+ const source = layer3.source;
37381
38042
  if (source.kind === "noise") return { kind: "noise", offset, loop: source.loop === true };
37382
38043
  if (source.kind === "osc") return { kind: "osc", wave: source.wave, frequency: finishAutomation(pending.automation ?? {}, duration) };
37383
38044
  const voice2 = pending.voice;
@@ -37399,23 +38060,23 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37399
38060
  function finishEnvelope(envelope2, duration, level) {
37400
38061
  return envelope2.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
37401
38062
  }
37402
- function planLayer(layer2, scope2, defaults, levelInEnvelope, soundLevel) {
37403
- const pending = drawLayer(layer2, scope2);
38063
+ function planLayer(layer3, scope2, defaults, levelInEnvelope, soundLevel) {
38064
+ const pending = drawLayer(layer3, scope2);
37404
38065
  const duration = pending.duration ?? defaults.duration;
37405
38066
  if (duration === void 0) throw new Error("game-audio: a layer needs a duration (its own or the recipe\u2019s)");
37406
- const offset = drawOffset(layer2.source, duration, scope2, defaults.noiseSeconds);
38067
+ const offset = drawOffset(layer3.source, duration, scope2, defaults.noiseSeconds);
37407
38068
  const level = pending.gain * soundLevel;
37408
- const sourceFrequency = layer2.source.kind === "noise" ? 0 : startFrequency(pending.automation ?? pending.voice?.frequency ?? {});
38069
+ const sourceFrequency = layer3.source.kind === "noise" ? 0 : startFrequency(pending.automation ?? pending.voice?.frequency ?? {});
37409
38070
  const planned = {
37410
38071
  at: pending.at,
37411
38072
  duration,
37412
- tail: layer2.tail ?? defaults.tail ?? DEFAULT_TAIL,
38073
+ tail: layer3.tail ?? defaults.tail ?? DEFAULT_TAIL,
37413
38074
  level,
37414
- ...layer2.wet === void 0 ? {} : { wet: layer2.wet },
37415
- source: finishSource(layer2, pending, duration, offset),
37416
- ...layer2.filters ? {
38075
+ ...layer3.wet === void 0 ? {} : { wet: layer3.wet },
38076
+ source: finishSource(layer3, pending, duration, offset),
38077
+ ...layer3.filters ? {
37417
38078
  filters: {
37418
- mode: layer2.filters.mode,
38079
+ mode: layer3.filters.mode,
37419
38080
  bank: pending.filters.map(({ spec, frequency, q, gain, level: filterLevel }) => ({
37420
38081
  type: spec.type,
37421
38082
  frequency: finishAutomation(frequency, duration),
@@ -37426,10 +38087,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37426
38087
  }
37427
38088
  } : {},
37428
38089
  modulators: pending.modulators.map((modulator) => finishModulator(modulator, sourceFrequency)),
37429
- envelope: finishEnvelope(layer2.envelope, duration, levelInEnvelope ? level : 1)
38090
+ envelope: finishEnvelope(layer3.envelope, duration, levelInEnvelope ? level : 1)
37430
38091
  };
37431
- if (!layer2.repeat) return [planned];
37432
- return Array.from({ length: layer2.repeat.count }, (_, index) => ({ ...planned, at: planned.at + index * (layer2.repeat?.spacing ?? 0) }));
38092
+ if (!layer3.repeat) return [planned];
38093
+ return Array.from({ length: layer3.repeat.count }, (_, index) => ({ ...planned, at: planned.at + index * (layer3.repeat?.spacing ?? 0) }));
37433
38094
  }
37434
38095
  function scaleAutomation(automation, divisor) {
37435
38096
  const scale = (value) => value === void 0 ? void 0 : value / divisor;
@@ -37437,12 +38098,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37437
38098
  for (const key2 of ["value", "from", "to"]) if (automation[key2] !== void 0) scaled2[key2] = scale(automation[key2]);
37438
38099
  return scaled2;
37439
38100
  }
37440
- function muffle(layer2, divisor) {
37441
- if (divisor === 1 || !layer2.filters) return layer2;
37442
- const bank = layer2.filters.bank.map((filter) => filter.type === "lowpass" ? { ...filter, frequency: scaleAutomation(filter.frequency, divisor) } : filter);
37443
- return { ...layer2, filters: { ...layer2.filters, bank } };
38101
+ function muffle(layer3, divisor) {
38102
+ if (divisor === 1 || !layer3.filters) return layer3;
38103
+ const bank = layer3.filters.bank.map((filter) => filter.type === "lowpass" ? { ...filter, frequency: scaleAutomation(filter.frequency, divisor) } : filter);
38104
+ return { ...layer3, filters: { ...layer3.filters, bank } };
37444
38105
  }
37445
- var layerEnd = (layer2) => layer2.at + layer2.duration + layer2.tail;
38106
+ var layerEnd = (layer3) => layer3.at + layer3.duration + layer3.tail;
37446
38107
  function planSound(recipe2, input = {}) {
37447
38108
  const listener = input.listener ?? DEFAULT_LISTENER;
37448
38109
  const distance3 = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0);
@@ -37470,7 +38131,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37470
38131
  noiseSeconds: input.noiseSeconds ?? DEFAULT_NOISE_SECONDS
37471
38132
  };
37472
38133
  const divisor = cutoffDivisor(recipe2.falloff, distance3) / (1 - OCCLUSION_CUTOFF * occlusion);
37473
- const layers = recipe2.layers.flatMap((layer2) => planLayer(layer2, scope2, defaults, order !== "layered", level)).map((layer2) => muffle(layer2, divisor));
38134
+ const layers = recipe2.layers.flatMap((layer3) => planLayer(layer3, scope2, defaults, order !== "layered", level)).map((layer3) => muffle(layer3, divisor));
37474
38135
  return { ...base, silent: false, length: Math.max(0, ...layers.map(layerEnd)), layers };
37475
38136
  }
37476
38137
  function fillNoise(random, length2) {
@@ -37856,7 +38517,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37856
38517
  const index = pickTake(refs.length, input.memory?.get(key2), input.random);
37857
38518
  input.memory?.set(key2, index);
37858
38519
  const ref = refs[index];
37859
- layers.push({ role, url: ref.url, take: index, gain: gain * (ref.gain ?? 1) * level, rate, at: at3 });
38520
+ layers.push({
38521
+ role,
38522
+ url: ref.url,
38523
+ take: index,
38524
+ gain: gain * (ref.gain ?? 1) * level,
38525
+ rate,
38526
+ at: at3,
38527
+ ...ref.offset === void 0 ? {} : { offset: ref.offset },
38528
+ ...ref.duration === void 0 ? {} : { duration: ref.duration }
38529
+ });
37860
38530
  };
37861
38531
  take("close", close, gains.close, 0);
37862
38532
  take("far", far, gains.far, 0);
@@ -37883,6 +38553,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37883
38553
  var DEFAULT_LOOP_CROSSFADE = 0.25;
37884
38554
  var LOOKAHEAD = 1.5;
37885
38555
  var VOLUME_SMOOTHING = 0.05;
38556
+ var RATE_SMOOTHING = 0.04;
38557
+ var RATE_RANGE = [0.25, 4];
37886
38558
  function loopCrossfade(sound3) {
37887
38559
  if (!sound3.loop) return void 0;
37888
38560
  return sound3.loop === true ? DEFAULT_LOOP_CROSSFADE : Math.max(0, sound3.loop.crossfade ?? DEFAULT_LOOP_CROSSFADE);
@@ -37894,6 +38566,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37894
38566
  param2.setValueAtTime(value(0), at3);
37895
38567
  for (let step = 1; step <= CROSSFADE_STEPS; step++) param2.linearRampToValueAtTime(value(step / CROSSFADE_STEPS), at3 + seconds * step / CROSSFADE_STEPS);
37896
38568
  };
38569
+ var loopRate = (sound3, frequency) => sound3.baseFrequency && sound3.baseFrequency > 0 && frequency > 0 ? Math.max(RATE_RANGE[0], Math.min(RATE_RANGE[1], frequency / sound3.baseFrequency)) : 1;
37897
38570
  function createSampleLoopHandle(sound3, host, volume = 0) {
37898
38571
  const crossfade = loopCrossfade(sound3) ?? DEFAULT_LOOP_CROSSFADE;
37899
38572
  let level = volume;
@@ -37903,13 +38576,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37903
38576
  let segments = [];
37904
38577
  let last;
37905
38578
  let timer;
38579
+ let panner;
38580
+ let rate = 1;
38581
+ let position;
37906
38582
  const entry2 = {
37907
38583
  get created() {
37908
38584
  return master !== void 0;
37909
38585
  },
37910
38586
  rewire() {
37911
- master?.disconnect();
37912
- master?.connect(host.output(sound3.bus));
38587
+ const out = panner ?? master;
38588
+ out?.disconnect();
38589
+ out?.connect(host.output(sound3.bus));
37913
38590
  },
37914
38591
  stop() {
37915
38592
  stopped = true;
@@ -37926,23 +38603,29 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37926
38603
  segments = [];
37927
38604
  master?.disconnect();
37928
38605
  master = void 0;
38606
+ panner?.disconnect();
38607
+ panner = void 0;
37929
38608
  fallback2?.stop();
37930
38609
  fallback2 = void 0;
37931
38610
  host.unregister(entry2);
37932
38611
  }
37933
38612
  };
37934
- const nextBuffer = () => {
38613
+ const nextTake = () => {
37935
38614
  const takes2 = sound3.close;
37936
38615
  const take = pickTake(takes2.length, last, host.random);
37937
38616
  last = take;
37938
38617
  const ref = takes2[take];
37939
- return ref ? host.buffer(ref.url) : void 0;
38618
+ const buffer = ref ? host.buffer(ref.url) : void 0;
38619
+ return ref && buffer ? { buffer, ref } : void 0;
37940
38620
  };
37941
38621
  const schedule = (context, output, at3, first) => {
37942
- const buffer = nextBuffer();
37943
- if (!buffer) return void 0;
38622
+ const next = nextTake();
38623
+ if (!next) return void 0;
38624
+ const { buffer, ref } = next;
38625
+ const offset = Math.max(0, Math.min(ref.offset ?? 0, buffer.duration));
37944
38626
  const source = context.createBufferSource();
37945
38627
  source.buffer = buffer;
38628
+ source.playbackRate.value = rate;
37946
38629
  const gain = context.createGain();
37947
38630
  source.connect(gain);
37948
38631
  gain.connect(output);
@@ -37950,15 +38633,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37950
38633
  if (buffer.duration < 2 * crossfade || sound3.close.length === 1) {
37951
38634
  source.loop = true;
37952
38635
  if (!first) ramp2(gain.gain, "in", at3, Math.max(1e-3, fade));
37953
- source.start(at3);
38636
+ source.start(at3, offset);
37954
38637
  segments.push({ source, gain, end: Number.POSITIVE_INFINITY });
37955
38638
  return void 0;
37956
38639
  }
37957
38640
  if (first) gain.gain.setValueAtTime(1, at3);
37958
38641
  else ramp2(gain.gain, "in", at3, fade);
37959
- const end3 = at3 + buffer.duration;
38642
+ const end3 = at3 + (buffer.duration - offset) / rate;
37960
38643
  ramp2(gain.gain, "out", end3 - fade, fade);
37961
- source.start(at3);
38644
+ source.start(at3, offset);
37962
38645
  source.stop(end3);
37963
38646
  segments.push({ source, gain, end: end3 });
37964
38647
  source.onended = () => {
@@ -37979,14 +38662,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37979
38662
  pump(context, output, next, false);
37980
38663
  }, wait);
37981
38664
  };
37982
- const fileLevel = () => level * (sound3.gain ?? 1);
38665
+ const spatial = () => {
38666
+ if (!position || !host.listener) return 1;
38667
+ return falloffGain(sound3.falloff ?? DEFAULT_SAMPLE_FALLOFF, 1, listenerDistance(host.listener(), position, host.distance));
38668
+ };
38669
+ const fileLevel = () => level * (sound3.gain ?? 1) * spatial();
38670
+ const place = (context) => {
38671
+ if (panner && position && host.listener) panner.pan.setTargetAtTime(panFor(host.listener(), position), context.currentTime, VOLUME_SMOOTHING);
38672
+ };
37983
38673
  const startFiles = () => {
37984
38674
  if (stopped || master || !host.isReady(sound3)) return master !== void 0;
37985
38675
  const context = host.ready();
37986
38676
  if (!context) return false;
37987
38677
  master = context.createGain();
37988
38678
  master.gain.setValueAtTime(fileLevel(), context.currentTime);
37989
- master.connect(host.output(sound3.bus));
38679
+ if (sound3.pan !== false && position && typeof context.createStereoPanner === "function") {
38680
+ panner = context.createStereoPanner();
38681
+ if (host.listener) panner.pan.value = panFor(host.listener(), position);
38682
+ master.connect(panner);
38683
+ panner.connect(host.output(sound3.bus));
38684
+ } else master.connect(host.output(sound3.bus));
37990
38685
  host.register(entry2);
37991
38686
  pump(context, master, context.currentTime, true);
37992
38687
  if (fallback2) {
@@ -38012,9 +38707,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38012
38707
  set(next) {
38013
38708
  if (stopped) return;
38014
38709
  if (next.volume !== void 0) level = host.muted(sound3.bus) ? 0 : Math.max(0, next.volume);
38710
+ if (next.position) position = next.position;
38711
+ if (next.frequency !== void 0 && sound3.baseFrequency) rate = loopRate(sound3, next.frequency);
38015
38712
  if (level > 0) ensure();
38016
38713
  const context = host.ready();
38017
- if (master && context) master.gain.setTargetAtTime(fileLevel(), context.currentTime, VOLUME_SMOOTHING);
38714
+ if (master && context) {
38715
+ master.gain.setTargetAtTime(fileLevel(), context.currentTime, VOLUME_SMOOTHING);
38716
+ place(context);
38717
+ if (next.frequency !== void 0 && sound3.baseFrequency) for (const segment of segments) segment.source.playbackRate.setTargetAtTime(rate, context.currentTime, RATE_SMOOTHING);
38718
+ }
38018
38719
  fallback2?.set(next.volume === void 0 ? next : { ...next, volume: level });
38019
38720
  },
38020
38721
  stop: entry2.stop
@@ -38117,9 +38818,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38117
38818
  };
38118
38819
  return queue;
38119
38820
  }
38120
- var end = (layer2, start) => start + layer2.duration + layer2.tail;
38121
- var firstFilter = (layer2) => layer2.filters?.bank[0];
38122
- var offsetOf = (layer2) => layer2.source.kind === "noise" ? layer2.source.offset : 0;
38821
+ var end = (layer3, start) => start + layer3.duration + layer3.tail;
38822
+ var firstFilter = (layer3) => layer3.filters?.bank[0];
38823
+ var offsetOf = (layer3) => layer3.source.kind === "noise" ? layer3.source.offset : 0;
38123
38824
  function setFilter(node, filter, time) {
38124
38825
  if (!filter) return;
38125
38826
  node.type = filter.type;
@@ -38128,28 +38829,28 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38128
38829
  }
38129
38830
  function realizeBurst(plan, target) {
38130
38831
  const { context } = target;
38131
- const layer2 = plan.layers[0];
38132
- const time = target.time + layer2.at;
38832
+ const layer3 = plan.layers[0];
38833
+ const time = target.time + layer3.at;
38133
38834
  const gain = context.createGain();
38134
38835
  gain.connect(target.output);
38135
- const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
38836
+ const source = noiseSource(target, layer3.source.kind === "noise" && layer3.source.loop);
38136
38837
  const filter = context.createBiquadFilter();
38137
38838
  source.connect(filter);
38138
38839
  filter.connect(gain);
38139
- setFilter(filter, firstFilter(layer2), time);
38140
- applyEnvelope(gain.gain, layer2.envelope, time);
38141
- source.start(time, offsetOf(layer2));
38142
- source.stop(end(layer2, time));
38840
+ setFilter(filter, firstFilter(layer3), time);
38841
+ applyEnvelope(gain.gain, layer3.envelope, time);
38842
+ source.start(time, offsetOf(layer3));
38843
+ source.stop(end(layer3, time));
38143
38844
  return voiceOf([source]);
38144
38845
  }
38145
38846
  function realizeScream(plan, target) {
38146
38847
  const { context } = target;
38147
- const layer2 = plan.layers[0];
38148
- const time = target.time + layer2.at;
38149
- const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
38848
+ const layer3 = plan.layers[0];
38849
+ const time = target.time + layer3.at;
38850
+ const source = noiseSource(target, layer3.source.kind === "noise" && layer3.source.loop);
38150
38851
  const gain = context.createGain();
38151
38852
  gain.connect(target.output);
38152
- for (const band of layer2.filters?.bank ?? []) {
38853
+ for (const band of layer3.filters?.bank ?? []) {
38153
38854
  const filter = context.createBiquadFilter();
38154
38855
  filter.type = band.type;
38155
38856
  if (band.q !== void 0) filter.Q.value = band.q;
@@ -38162,7 +38863,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38162
38863
  level.connect(gain);
38163
38864
  }
38164
38865
  const sources = [source];
38165
- const lfos = layer2.modulators.map((modulator) => {
38866
+ const lfos = layer3.modulators.map((modulator) => {
38166
38867
  const lfo = context.createOscillator();
38167
38868
  const depth = context.createGain();
38168
38869
  if (modulator.wave) lfo.type = modulator.wave;
@@ -38172,75 +38873,75 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38172
38873
  depth.connect(gain.gain);
38173
38874
  return lfo;
38174
38875
  });
38175
- applyEnvelope(gain.gain, layer2.envelope, time);
38176
- source.start(time, offsetOf(layer2));
38177
- source.stop(end(layer2, time));
38876
+ applyEnvelope(gain.gain, layer3.envelope, time);
38877
+ source.start(time, offsetOf(layer3));
38878
+ source.stop(end(layer3, time));
38178
38879
  for (const lfo of lfos) {
38179
38880
  lfo.start(time);
38180
- lfo.stop(end(layer2, time));
38881
+ lfo.stop(end(layer3, time));
38181
38882
  sources.push(lfo);
38182
38883
  }
38183
38884
  return voiceOf(sources);
38184
38885
  }
38185
38886
  function realizeGroan(plan, target) {
38186
38887
  const { context } = target;
38187
- const layer2 = plan.layers[0];
38188
- const time = target.time + layer2.at;
38888
+ const layer3 = plan.layers[0];
38889
+ const time = target.time + layer3.at;
38189
38890
  const oscillator = context.createOscillator();
38190
38891
  const gain = context.createGain();
38191
38892
  const filter = context.createBiquadFilter();
38192
- const band = firstFilter(layer2);
38193
- if (layer2.source.kind === "osc") oscillator.type = layer2.source.wave;
38893
+ const band = firstFilter(layer3);
38894
+ if (layer3.source.kind === "osc") oscillator.type = layer3.source.wave;
38194
38895
  if (band) {
38195
38896
  filter.type = band.type;
38196
38897
  if (band.q !== void 0) filter.Q.value = band.q;
38197
38898
  }
38198
- if (layer2.source.kind === "osc") {
38199
- assign(oscillator.frequency, layer2.source.frequency, time);
38200
- rampTo(oscillator.frequency, layer2.source.frequency, time);
38899
+ if (layer3.source.kind === "osc") {
38900
+ assign(oscillator.frequency, layer3.source.frequency, time);
38901
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38201
38902
  }
38202
38903
  if (band) assign(filter.frequency, band.frequency, time);
38203
- applyEnvelope(gain.gain, layer2.envelope, time);
38904
+ applyEnvelope(gain.gain, layer3.envelope, time);
38204
38905
  oscillator.connect(filter);
38205
38906
  filter.connect(gain);
38206
38907
  gain.connect(target.output);
38207
38908
  oscillator.start(time);
38208
- oscillator.stop(end(layer2, time));
38909
+ oscillator.stop(end(layer3, time));
38209
38910
  return voiceOf([oscillator]);
38210
38911
  }
38211
38912
  function realizeBoom(plan, target) {
38212
38913
  const { context } = target;
38213
- const layer2 = plan.layers[0];
38214
- const time = target.time + layer2.at;
38914
+ const layer3 = plan.layers[0];
38915
+ const time = target.time + layer3.at;
38215
38916
  const oscillator = context.createOscillator();
38216
38917
  const gain = context.createGain();
38217
- if (layer2.source.kind === "osc") {
38218
- oscillator.type = layer2.source.wave;
38219
- assign(oscillator.frequency, layer2.source.frequency, time);
38220
- rampTo(oscillator.frequency, layer2.source.frequency, time);
38918
+ if (layer3.source.kind === "osc") {
38919
+ oscillator.type = layer3.source.wave;
38920
+ assign(oscillator.frequency, layer3.source.frequency, time);
38921
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38221
38922
  }
38222
- applyEnvelope(gain.gain, layer2.envelope, time);
38923
+ applyEnvelope(gain.gain, layer3.envelope, time);
38223
38924
  oscillator.connect(gain);
38224
38925
  gain.connect(target.output);
38225
38926
  oscillator.start(time);
38226
- oscillator.stop(end(layer2, time));
38927
+ oscillator.stop(end(layer3, time));
38227
38928
  return voiceOf([oscillator]);
38228
38929
  }
38229
38930
  function realizeStatic(plan, target) {
38230
38931
  const { context } = target;
38231
- const layer2 = plan.layers[0];
38232
- const time = target.time + layer2.at;
38932
+ const layer3 = plan.layers[0];
38933
+ const time = target.time + layer3.at;
38233
38934
  const gain = context.createGain();
38234
38935
  const source = context.createBufferSource();
38235
38936
  const filter = context.createBiquadFilter();
38236
38937
  source.buffer = target.noise();
38237
- setFilter(filter, firstFilter(layer2), time);
38238
- applyEnvelope(gain.gain, layer2.envelope, time);
38938
+ setFilter(filter, firstFilter(layer3), time);
38939
+ applyEnvelope(gain.gain, layer3.envelope, time);
38239
38940
  source.connect(filter);
38240
38941
  filter.connect(gain);
38241
38942
  gain.connect(target.output);
38242
- source.start(time, offsetOf(layer2));
38243
- source.stop(end(layer2, time));
38943
+ source.start(time, offsetOf(layer3));
38944
+ source.stop(end(layer3, time));
38244
38945
  return voiceOf([source]);
38245
38946
  }
38246
38947
  function realizeChord(plan, target) {
@@ -38252,14 +38953,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38252
38953
  const source = layers[index].source;
38253
38954
  if (source.kind === "osc") oscillators[index].type = source.wave;
38254
38955
  }
38255
- layers.forEach((layer2, index) => {
38256
- if (layer2.source.kind === "osc") assign(oscillators[index].frequency, layer2.source.frequency, target.time + layer2.at);
38956
+ layers.forEach((layer3, index) => {
38957
+ if (layer3.source.kind === "osc") assign(oscillators[index].frequency, layer3.source.frequency, target.time + layer3.at);
38257
38958
  });
38258
38959
  applyEnvelope(gain.gain, layers[0].envelope, target.time + layers[0].at);
38259
38960
  for (const oscillator of oscillators) oscillator.connect(gain);
38260
38961
  gain.connect(target.output);
38261
- layers.forEach((layer2, index) => oscillators[index].start(target.time + layer2.at));
38262
- layers.forEach((layer2, index) => oscillators[index].stop(end(layer2, target.time + layer2.at)));
38962
+ layers.forEach((layer3, index) => oscillators[index].start(target.time + layer3.at));
38963
+ layers.forEach((layer3, index) => oscillators[index].stop(end(layer3, target.time + layer3.at)));
38263
38964
  return voiceOf(oscillators);
38264
38965
  }
38265
38966
  function realizeCalls(plan, target) {
@@ -38271,32 +38972,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38271
38972
  if (band) filter.type = band.type;
38272
38973
  filter.connect(gain);
38273
38974
  gain.connect(target.output);
38274
- const oscillators = plan.layers.map((layer2) => {
38275
- const time = target.time + layer2.at;
38975
+ const oscillators = plan.layers.map((layer3) => {
38976
+ const time = target.time + layer3.at;
38276
38977
  const oscillator = context.createOscillator();
38277
- if (layer2.source.kind === "osc") {
38278
- oscillator.type = layer2.source.wave;
38279
- assign(oscillator.frequency, layer2.source.frequency, time);
38280
- rampTo(oscillator.frequency, layer2.source.frequency, time);
38978
+ if (layer3.source.kind === "osc") {
38979
+ oscillator.type = layer3.source.wave;
38980
+ assign(oscillator.frequency, layer3.source.frequency, time);
38981
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38281
38982
  }
38282
38983
  if (band) {
38283
38984
  assign(filter.frequency, band.frequency, time);
38284
38985
  if (band.q !== void 0) filter.Q.value = band.q;
38285
38986
  }
38286
38987
  const envelope2 = context.createGain();
38287
- applyEnvelope(envelope2.gain, layer2.envelope, time);
38988
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38288
38989
  oscillator.connect(envelope2);
38289
38990
  envelope2.connect(filter);
38290
38991
  oscillator.start(time);
38291
- oscillator.stop(end(layer2, time));
38992
+ oscillator.stop(end(layer3, time));
38292
38993
  return oscillator;
38293
38994
  });
38294
38995
  return voiceOf(oscillators);
38295
38996
  }
38296
- var end2 = (layer2, start) => start + layer2.duration + layer2.tail;
38297
- function outputStage2(plan, layer2, target) {
38997
+ var end2 = (layer3, start) => start + layer3.duration + layer3.tail;
38998
+ function outputStage2(plan, layer3, target) {
38298
38999
  const { context } = target;
38299
- const level = gainNode(context, layer2.level);
39000
+ const level = gainNode(context, layer3.level);
38300
39001
  let node = level;
38301
39002
  if (plan.pan !== void 0 && typeof context.createStereoPanner === "function") {
38302
39003
  const panner = context.createStereoPanner();
@@ -38305,54 +39006,54 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38305
39006
  node = panner;
38306
39007
  }
38307
39008
  node.connect(target.output);
38308
- if (layer2.wet && target.wet) {
38309
- const send = gainNode(context, layer2.wet);
39009
+ if (layer3.wet && target.wet) {
39010
+ const send = gainNode(context, layer3.wet);
38310
39011
  node.connect(send);
38311
39012
  send.connect(target.wet);
38312
39013
  }
38313
39014
  return level;
38314
39015
  }
38315
- function realizeNoiseLayer(plan, layer2, target) {
39016
+ function realizeNoiseLayer(plan, layer3, target) {
38316
39017
  const { context } = target;
38317
- const time = target.time + layer2.at;
38318
- const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
38319
- const filters = (layer2.filters?.bank ?? []).map((filter) => {
39018
+ const time = target.time + layer3.at;
39019
+ const source = noiseSource(target, layer3.source.kind === "noise" && layer3.source.loop);
39020
+ const filters = (layer3.filters?.bank ?? []).map((filter) => {
38320
39021
  const node = filterNode(context, filter, time);
38321
39022
  rampTo(node.frequency, filter.frequency, time);
38322
39023
  return node;
38323
39024
  });
38324
39025
  const envelope2 = context.createGain();
38325
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39026
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38326
39027
  let tail = source;
38327
39028
  for (const filter of filters) tail = tail.connect(filter);
38328
39029
  tail.connect(envelope2);
38329
- envelope2.connect(outputStage2(plan, layer2, target));
38330
- source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
38331
- source.stop(end2(layer2, time));
39030
+ envelope2.connect(outputStage2(plan, layer3, target));
39031
+ source.start(time, layer3.source.kind === "noise" ? layer3.source.offset : 0);
39032
+ source.stop(end2(layer3, time));
38332
39033
  return [source];
38333
39034
  }
38334
- function realizeToneLayer(plan, layer2, target) {
39035
+ function realizeToneLayer(plan, layer3, target) {
38335
39036
  const { context } = target;
38336
- const time = target.time + layer2.at;
39037
+ const time = target.time + layer3.at;
38337
39038
  const oscillator = context.createOscillator();
38338
- if (layer2.source.kind === "osc") {
38339
- oscillator.type = layer2.source.wave;
38340
- assign(oscillator.frequency, layer2.source.frequency, time);
38341
- rampTo(oscillator.frequency, layer2.source.frequency, time);
39039
+ if (layer3.source.kind === "osc") {
39040
+ oscillator.type = layer3.source.wave;
39041
+ assign(oscillator.frequency, layer3.source.frequency, time);
39042
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38342
39043
  }
38343
39044
  const envelope2 = context.createGain();
38344
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39045
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38345
39046
  oscillator.connect(envelope2);
38346
- envelope2.connect(outputStage2(plan, layer2, target));
39047
+ envelope2.connect(outputStage2(plan, layer3, target));
38347
39048
  oscillator.start(time);
38348
- oscillator.stop(end2(layer2, time));
39049
+ oscillator.stop(end2(layer3, time));
38349
39050
  return [oscillator];
38350
39051
  }
38351
- function realizeVoiceLayer(plan, layer2, target) {
38352
- const source = layer2.source;
39052
+ function realizeVoiceLayer(plan, layer3, target) {
39053
+ const source = layer3.source;
38353
39054
  if (source.kind !== "formant_voice") return [];
38354
39055
  const { context } = target;
38355
- const time = target.time + layer2.at;
39056
+ const time = target.time + layer3.at;
38356
39057
  const oscillator = context.createOscillator();
38357
39058
  oscillator.type = source.wave;
38358
39059
  assign(oscillator.frequency, source.frequency, time);
@@ -38373,10 +39074,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38373
39074
  for (const formant of formants) formant.connect(mix3);
38374
39075
  breathLevel.connect(mix3);
38375
39076
  const envelope2 = context.createGain();
38376
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39077
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38377
39078
  mix3.connect(envelope2);
38378
- envelope2.connect(outputStage2(plan, layer2, target));
38379
- const stop = end2(layer2, time);
39079
+ envelope2.connect(outputStage2(plan, layer3, target));
39080
+ const stop = end2(layer3, time);
38380
39081
  oscillator.start(time);
38381
39082
  vibrato.start(time);
38382
39083
  breath.start(time, source.breath.offset);
@@ -38386,24 +39087,24 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38386
39087
  return [oscillator, vibrato, breath];
38387
39088
  }
38388
39089
  function realizeLayered(plan, target) {
38389
- const sources = plan.layers.flatMap((layer2) => {
38390
- if (layer2.source.kind === "noise") return realizeNoiseLayer(plan, layer2, target);
38391
- if (layer2.source.kind === "osc") return realizeToneLayer(plan, layer2, target);
38392
- return realizeVoiceLayer(plan, layer2, target);
39090
+ const sources = plan.layers.flatMap((layer3) => {
39091
+ if (layer3.source.kind === "noise") return realizeNoiseLayer(plan, layer3, target);
39092
+ if (layer3.source.kind === "osc") return realizeToneLayer(plan, layer3, target);
39093
+ return realizeVoiceLayer(plan, layer3, target);
38393
39094
  });
38394
39095
  return voiceOf(sources);
38395
39096
  }
38396
39097
  function realizeSiren(plan, target) {
38397
39098
  const { context } = target;
38398
- const layer2 = plan.layers[0];
38399
- const time = target.time + layer2.at;
39099
+ const layer3 = plan.layers[0];
39100
+ const time = target.time + layer3.at;
38400
39101
  const oscillator = context.createOscillator();
38401
- if (layer2.source.kind === "osc") {
38402
- oscillator.type = layer2.source.wave;
38403
- assign(oscillator.frequency, layer2.source.frequency, time);
38404
- rampTo(oscillator.frequency, layer2.source.frequency, time);
39102
+ if (layer3.source.kind === "osc") {
39103
+ oscillator.type = layer3.source.wave;
39104
+ assign(oscillator.frequency, layer3.source.frequency, time);
39105
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38405
39106
  }
38406
- const depths = layer2.modulators.map((modulator) => {
39107
+ const depths = layer3.modulators.map((modulator) => {
38407
39108
  const lfo = context.createOscillator();
38408
39109
  if (modulator.wave) lfo.type = modulator.wave;
38409
39110
  lfo.frequency.value = modulator.rate;
@@ -38414,57 +39115,57 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38414
39115
  });
38415
39116
  const lfos = depths.map(({ lfo }) => lfo);
38416
39117
  const envelope2 = context.createGain();
38417
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39118
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38418
39119
  for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope2.gain);
38419
- const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
39120
+ const filters = (layer3.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
38420
39121
  let tail = oscillator;
38421
39122
  for (const filter of filters) tail = tail.connect(filter);
38422
39123
  tail.connect(envelope2);
38423
39124
  envelope2.connect(target.output);
38424
- if (layer2.wet && target.wet) {
38425
- const send = gainNode(context, layer2.wet);
39125
+ if (layer3.wet && target.wet) {
39126
+ const send = gainNode(context, layer3.wet);
38426
39127
  envelope2.connect(send);
38427
39128
  send.connect(target.wet);
38428
39129
  }
38429
- const stop = end2(layer2, time);
39130
+ const stop = end2(layer3, time);
38430
39131
  oscillator.start(time);
38431
39132
  for (const lfo of lfos) lfo.start(time);
38432
39133
  oscillator.stop(stop);
38433
39134
  for (const lfo of lfos) lfo.stop(stop);
38434
39135
  return voiceOf([oscillator, ...lfos]);
38435
39136
  }
38436
- function noiseLayer(layer2, target, time) {
39137
+ function noiseLayer(layer3, target, time) {
38437
39138
  const { context } = target;
38438
- const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
38439
- const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
39139
+ const source = noiseSource(target, layer3.source.kind === "noise" && layer3.source.loop);
39140
+ const filters = (layer3.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
38440
39141
  const envelope2 = context.createGain();
38441
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39142
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38442
39143
  let tail = source;
38443
39144
  for (const filter of filters) tail = tail.connect(filter);
38444
39145
  tail.connect(envelope2).connect(target.output);
38445
- source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
38446
- source.stop(time + layer2.duration + layer2.tail);
39146
+ source.start(time, layer3.source.kind === "noise" ? layer3.source.offset : 0);
39147
+ source.stop(time + layer3.duration + layer3.tail);
38447
39148
  return source;
38448
39149
  }
38449
- function toneLayer(layer2, target, time) {
39150
+ function toneLayer(layer3, target, time) {
38450
39151
  const { context } = target;
38451
39152
  const oscillator = context.createOscillator();
38452
- if (layer2.source.kind === "osc") {
38453
- oscillator.type = layer2.source.wave;
38454
- assign(oscillator.frequency, layer2.source.frequency, time);
38455
- rampTo(oscillator.frequency, layer2.source.frequency, time);
39153
+ if (layer3.source.kind === "osc") {
39154
+ oscillator.type = layer3.source.wave;
39155
+ assign(oscillator.frequency, layer3.source.frequency, time);
39156
+ rampTo(oscillator.frequency, layer3.source.frequency, time);
38456
39157
  }
38457
39158
  const envelope2 = context.createGain();
38458
- applyEnvelope(envelope2.gain, layer2.envelope, time);
39159
+ applyEnvelope(envelope2.gain, layer3.envelope, time);
38459
39160
  oscillator.connect(envelope2).connect(target.output);
38460
39161
  oscillator.start(time);
38461
- oscillator.stop(time + layer2.duration + layer2.tail);
39162
+ oscillator.stop(time + layer3.duration + layer3.tail);
38462
39163
  return oscillator;
38463
39164
  }
38464
39165
  function realizeWeapon(plan, target) {
38465
- const sources = plan.layers.map((layer2) => {
38466
- const time = target.time + layer2.at;
38467
- return layer2.source.kind === "noise" ? noiseLayer(layer2, target, time) : toneLayer(layer2, target, time);
39166
+ const sources = plan.layers.map((layer3) => {
39167
+ const time = target.time + layer3.at;
39168
+ return layer3.source.kind === "noise" ? noiseLayer(layer3, target, time) : toneLayer(layer3, target, time);
38468
39169
  });
38469
39170
  return voiceOf(sources);
38470
39171
  }
@@ -38512,19 +39213,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38512
39213
  rate: modulator.rate * rate,
38513
39214
  ...modulator.target === "frequency" ? { depth: modulator.depth * rate } : {}
38514
39215
  });
38515
- var tuneLayer = (layer2, rate) => ({
38516
- ...layer2,
38517
- at: layer2.at / rate,
38518
- duration: layer2.duration / rate,
38519
- tail: layer2.tail / rate,
38520
- source: tuneSource(layer2.source, rate),
38521
- ...layer2.filters ? { filters: { ...layer2.filters, bank: layer2.filters.bank.map((filter) => tuneFilter(filter, rate)) } } : {},
38522
- modulators: layer2.modulators.map((modulator) => tuneModulator(modulator, rate)),
38523
- envelope: layer2.envelope.map((point) => ({ ...point, time: point.time / rate }))
39216
+ var tuneLayer = (layer3, rate) => ({
39217
+ ...layer3,
39218
+ at: layer3.at / rate,
39219
+ duration: layer3.duration / rate,
39220
+ tail: layer3.tail / rate,
39221
+ source: tuneSource(layer3.source, rate),
39222
+ ...layer3.filters ? { filters: { ...layer3.filters, bank: layer3.filters.bank.map((filter) => tuneFilter(filter, rate)) } } : {},
39223
+ modulators: layer3.modulators.map((modulator) => tuneModulator(modulator, rate)),
39224
+ envelope: layer3.envelope.map((point) => ({ ...point, time: point.time / rate }))
38524
39225
  });
38525
39226
  function retunePlan(plan, rate) {
38526
39227
  if (rate === 1 || !(rate > 0)) return plan;
38527
- return { ...plan, length: plan.length / rate, layers: plan.layers.map((layer2) => tuneLayer(layer2, rate)) };
39228
+ return { ...plan, length: plan.length / rate, layers: plan.layers.map((layer3) => tuneLayer(layer3, rate)) };
38528
39229
  }
38529
39230
  function createSetPicker() {
38530
39231
  const last = /* @__PURE__ */ new Map();
@@ -38612,6 +39313,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38612
39313
  }
38613
39314
  };
38614
39315
  }
39316
+ var TRIM_FADE = 0.04;
38615
39317
  function realizeSample(plan, target) {
38616
39318
  const context = target.context;
38617
39319
  let head = target.output;
@@ -38633,16 +39335,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38633
39335
  voice2.connect(head);
38634
39336
  head = voice2;
38635
39337
  const sources = [];
38636
- for (const layer2 of plan.layers) {
38637
- const buffer = target.buffer(layer2.url);
39338
+ for (const layer3 of plan.layers) {
39339
+ const buffer = target.buffer(layer3.url);
38638
39340
  if (!buffer) continue;
38639
39341
  const source = context.createBufferSource();
38640
39342
  source.buffer = buffer;
38641
- source.playbackRate.value = layer2.rate;
38642
- const gain = gainNode(context, layer2.gain);
39343
+ source.playbackRate.value = layer3.rate;
39344
+ const gain = gainNode(context, layer3.gain);
38643
39345
  source.connect(gain);
38644
39346
  gain.connect(head);
38645
- source.start(target.time + layer2.at);
39347
+ const at3 = target.time + layer3.at;
39348
+ const offset = Math.max(0, Math.min(layer3.offset ?? 0, buffer.duration));
39349
+ if (layer3.duration === void 0) source.start(at3, offset);
39350
+ else {
39351
+ const duration = Math.max(0, Math.min(layer3.duration, buffer.duration - offset));
39352
+ const end3 = at3 + duration / layer3.rate;
39353
+ const fade = Math.min(TRIM_FADE, (end3 - at3) / 4);
39354
+ gain.gain.setValueAtTime(layer3.gain, end3 - fade);
39355
+ gain.gain.linearRampToValueAtTime(0, end3);
39356
+ source.start(at3, offset, duration);
39357
+ }
38646
39358
  sources.push(source);
38647
39359
  }
38648
39360
  const realized = voiceOf(sources);
@@ -38662,7 +39374,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38662
39374
  }
38663
39375
  function sampleLength(plan, seconds) {
38664
39376
  let length2 = 0;
38665
- for (const layer2 of plan.layers) length2 = Math.max(length2, layer2.at + (seconds(layer2.url) ?? 0) / layer2.rate);
39377
+ for (const layer3 of plan.layers) {
39378
+ const file = seconds(layer3.url) ?? 0;
39379
+ const played = layer3.duration === void 0 ? file - (layer3.offset ?? 0) : Math.min(layer3.duration, file - (layer3.offset ?? 0));
39380
+ length2 = Math.max(length2, layer3.at + Math.max(0, played) / layer3.rate);
39381
+ }
38666
39382
  return length2;
38667
39383
  }
38668
39384
  function limiterCurve2(knee, ceiling, range3, points = 2049) {
@@ -38676,8 +39392,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38676
39392
  }
38677
39393
  return curve;
38678
39394
  }
38679
- var usesNoise = (recipe2) => recipe2.layers.some((layer2) => layer2.source.kind !== "osc");
38680
- var planUsesNoise = (plan) => plan.layers.some((layer2) => layer2.source.kind !== "osc");
39395
+ var usesNoise = (recipe2) => recipe2.layers.some((layer3) => layer3.source.kind !== "osc");
39396
+ var planUsesNoise = (plan) => plan.layers.some((layer3) => layer3.source.kind !== "osc");
38681
39397
  function createAudioEngine(options = {}) {
38682
39398
  const seed = options.seed ?? 1;
38683
39399
  const registry = createSoundRegistry(options.banks);
@@ -38916,7 +39632,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38916
39632
  level: plan.level,
38917
39633
  distance: plan.distance,
38918
39634
  ...plan.pan === void 0 ? {} : { pan: plan.pan },
38919
- layers: plan.layers.map((layer2) => ({ role: layer2.role, url: layer2.url, take: layer2.take, rate: layer2.rate })),
39635
+ layers: plan.layers.map((layer3) => ({ role: layer3.role, url: layer3.url, take: layer3.take, rate: layer3.rate })),
38920
39636
  ...plan.tier ? { tier: plan.tier } : {}
38921
39637
  });
38922
39638
  voices.setLength(voiceId, sampleLength(plan, (url2) => sampleStore.seconds(url2)) + (playOptions.delay ?? 0));
@@ -39049,7 +39765,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39049
39765
  random: engineRandom,
39050
39766
  register: loopHost.register,
39051
39767
  unregister: loopHost.unregister,
39052
- muted: (bus) => busSettings.get(bus)?.muted === true
39768
+ muted: (bus) => busSettings.get(bus)?.muted === true,
39769
+ listener: () => listener,
39770
+ ...options.distance ? { distance: options.distance } : {}
39053
39771
  },
39054
39772
  loopOptions.volume
39055
39773
  );
@@ -39234,7 +39952,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39234
39952
  ["smg_45_auto", 2, "sfx_pistol"],
39235
39953
  ["weapon_draw", 2],
39236
39954
  ["suppressor_attach", 3],
39237
- ["suppressor_detach", 3]
39955
+ ["suppressor_detach", 3],
39956
+ ["minigun_loop", 3],
39957
+ ["smg_5_7x28", 3],
39958
+ ["mg_belt_adjust", 3],
39959
+ ["mg_belt_box_attach", 3],
39960
+ ["mg_cover_open", 3],
39961
+ ["mg_cover_close", 3],
39962
+ ["weapon_holster", 3],
39963
+ ["scope_zoom", 3]
39964
+ ]),
39965
+ ...entries("impactos", [
39966
+ ["knife_hit_wall", 3],
39967
+ ["helmet_hit", 3],
39968
+ ["vest_hit", 3],
39969
+ ["headshot", 3],
39970
+ ["blood_squirt", 3]
39238
39971
  ]),
39239
39972
  ...entries("explosoes-fogo", [
39240
39973
  ["explosion_grenade", 1, "boom"],
@@ -39252,7 +39985,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39252
39985
  ["grenade_bounce", 1],
39253
39986
  ["c4_plant", 2],
39254
39987
  ["c4_beep", 2],
39255
- ["c4_disarm", 2]
39988
+ ["c4_disarm", 2],
39989
+ ["c4_disarming_loop", 3],
39990
+ ["c4_remote_detonate", 3],
39991
+ ["smoke_grenade_hiss", 3],
39992
+ ["grenade_pin_pull", 3],
39993
+ ["electric_shock", 3],
39994
+ ["electric_spark", 3]
39256
39995
  ]),
39257
39996
  ...entries("personagem", [
39258
39997
  ["footstep_concrete_walk", 1, "sfx_step"],
@@ -39280,7 +40019,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39280
40019
  ["zombie_attack", 1],
39281
40020
  ["zombie_pain", 1],
39282
40021
  ["zombie_claw_hit", 1, "sfx_flesh"],
39283
- ["zombie_claw_miss", 2]
40022
+ ["zombie_claw_miss", 2],
40023
+ ["swing_whoosh", 3],
40024
+ ["heartbeat_critical", 3],
40025
+ ["fall_water", 3],
40026
+ ["fall_damage", 3],
40027
+ ["fall_scream", 3],
40028
+ ["footstep_glass_walk", 3]
39284
40029
  ]),
39285
40030
  ...entries("veiculos", [
39286
40031
  ["engine_loop_car", 1],
@@ -39290,7 +40035,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39290
40035
  ["car_door_open", 1, "latch"],
39291
40036
  ["car_door_close", 1, "shut_metal"],
39292
40037
  ["horn_car", 2, "horn"],
39293
- ["tire_skid_asphalt", 2]
40038
+ ["tire_skid_asphalt", 2],
40039
+ ["jet_flyby", 3],
40040
+ ["helicopter_turbine", 3],
40041
+ ["tire_burnout", 3],
40042
+ ["handbrake_turn", 3],
40043
+ ["siren_police", 3]
39294
40044
  ]),
39295
40045
  ...entries("itens-portas", [
39296
40046
  ["item_pickup_metal", 1],
@@ -39301,7 +40051,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39301
40051
  ["door_creak", 1, "creak_wood"],
39302
40052
  ["door_locked_rattle", 1, "rattle"],
39303
40053
  ["drawer_open", 2, "slide"],
39304
- ["fridge_close", 2, "shut_metal"]
40054
+ ["fridge_close", 2, "shut_metal"],
40055
+ ["inventory_open", 3],
40056
+ ["inventory_close", 3],
40057
+ ["inventory_select", 3],
40058
+ ["inventory_place", 3],
40059
+ ["pickup_chime", 3],
40060
+ ["item_pickup_coins", 3],
40061
+ ["bandage_use", 3],
40062
+ ["bandage_refuse", 3],
40063
+ ["flashlight_toggle", 3],
40064
+ ["gear_draw", 3],
40065
+ ["gear_holster", 3],
40066
+ ["button_press", 3],
40067
+ ["lock_desk_open", 3],
40068
+ ["elevator_move_loop", 3],
40069
+ ["elevator_stop", 3],
40070
+ ["door_sliding", 3],
40071
+ ["door_kick", 3],
40072
+ ["door_wood_break", 3],
40073
+ ["gate_iron_open", 3],
40074
+ ["gate_iron_close", 3]
39305
40075
  ]),
39306
40076
  ...entries("ambiente", [
39307
40077
  ["rain_loop", 2],
@@ -39309,7 +40079,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39309
40079
  ["thunder_near", 2],
39310
40080
  ["thunder_far", 2],
39311
40081
  ["water_splash", 2],
39312
- ["helicopter_loop", 3]
40082
+ ["helicopter_loop", 3],
40083
+ ["bird_flush", 3],
40084
+ ["dog_growl", 3],
40085
+ ["dog_whimper", 3],
40086
+ ["dog_howl", 3],
40087
+ ["zombie_dog_bark", 3],
40088
+ ["zombie_dog_howl_far", 3],
40089
+ ["insect_squash", 3],
40090
+ ["insect_crawl", 3],
40091
+ ["fan_loop", 3]
39313
40092
  ])
39314
40093
  ];
39315
40094
  var SFX_MATERIALS = [
@@ -39445,6 +40224,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39445
40224
  }
39446
40225
  return { id: options.id, samples };
39447
40226
  }
40227
+ var CATALOG = new Map([...SFX_CATALOG, ...SFX_IMPACTS].map((entry2) => [entry2.id, entry2]));
39448
40228
  var PT = {
39449
40229
  group: "Som",
39450
40230
  name: "Painel de som (arquivos e C1\u2013C10)",
@@ -39519,9 +40299,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39519
40299
  strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
39520
40300
  states: { file: "A", synth: "S", missing: "F" },
39521
40301
  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" },
39522
- 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" },
40302
+ 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" },
39523
40303
  run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
39524
- words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
40304
+ 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" },
39525
40305
  sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
39526
40306
  probe: "Fonte do som",
39527
40307
  wall: "Parede (oclus\xE3o)",
@@ -39592,9 +40372,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39592
40372
  environments: { indoor: "indoor", outdoor: "outdoor" },
39593
40373
  strengths: { light: "light", medium: "medium", heavy: "heavy" },
39594
40374
  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" },
39595
- 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" },
40375
+ 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" },
39596
40376
  run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
39597
- words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
40377
+ words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner", local_file: "local pack file", sdk_file: "SDK recording", synth: "SDK synthesis" },
39598
40378
  sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
39599
40379
  probe: "Sound source",
39600
40380
  wall: "Wall (occlusion)",
@@ -39665,6 +40445,66 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39665
40445
  };
39666
40446
  }
39667
40447
  var sceneMany = (count, make3) => Array.from({ length: count }, make3);
40448
+ var hz = (midi) => 440 * 2 ** ((midi - 69) / 12);
40449
+ var layer = (note2, beat, wave, level) => {
40450
+ const [at3, midi, beats] = note2;
40451
+ const length2 = beats * beat;
40452
+ return {
40453
+ at: at3 * beat,
40454
+ duration: length2,
40455
+ source: { kind: "osc", wave, frequency: { set: hz(midi) } },
40456
+ envelope: [[0, 1e-4, "set"], [0.01, level, "linear"], [Math.max(0.02, length2 * 0.9), level * 0.35, "linear"], [length2, 1e-4, "exp", "fixed"]]
40457
+ };
40458
+ };
40459
+ function radioTrack(spec) {
40460
+ const beat = 60 / spec.bpm;
40461
+ const recipes = Array.from({ length: spec.bars }, (_, bar2) => {
40462
+ const inBar = (notes) => notes.filter((n) => n[0] >= bar2 * 4 && n[0] < bar2 * 4 + 4).map((n) => [n[0] - bar2 * 4, n[1], n[2]]);
40463
+ const layers = [
40464
+ ...inBar(spec.bass).map((n) => layer(n, beat, "triangle", 0.16)),
40465
+ ...inBar(spec.chords).map((n) => layer(n, beat, "sine", 0.05)),
40466
+ ...inBar(spec.melody).map((n) => layer(n, beat, spec.wave, 0.07))
40467
+ ];
40468
+ return { id: `${spec.id}_${bar2}`, bus: "world", duration: 4 * beat, layers, falloff: { model: "none" } };
40469
+ });
40470
+ return { song: { id: spec.id, barSeconds: 4 * beat, bars: recipes.map((r) => r.id) }, recipes };
40471
+ }
40472
+ var chord = (bar2, root, minor = false) => [[bar2 * 4, root, 4], [bar2 * 4, root + (minor ? 3 : 4), 4], [bar2 * 4, root + 7, 4]];
40473
+ var bassLine = (roots, pattern) => roots.flatMap((root, bar2) => pattern.map((b) => [bar2 * 4 + b, root - 12, 0.9]));
40474
+ var TRACKS = [
40475
+ // a bright pop loop: I–V–vi–IV in C
40476
+ radioTrack({
40477
+ id: "radio_track_pop",
40478
+ bpm: 112,
40479
+ bars: 4,
40480
+ wave: "square",
40481
+ bass: bassLine([48, 55, 57, 53], [0, 1.5, 2, 3]),
40482
+ chords: [...chord(0, 60), ...chord(1, 55), ...chord(2, 57, true), ...chord(3, 53)],
40483
+ 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]]
40484
+ }),
40485
+ // a syncopated loop in A minor, the bass on the off-beats
40486
+ radioTrack({
40487
+ id: "radio_track_groove",
40488
+ bpm: 100,
40489
+ bars: 4,
40490
+ wave: "triangle",
40491
+ bass: bassLine([57, 62, 64, 57], [0, 0.75, 1.5, 2.5, 3.25]),
40492
+ chords: [...chord(0, 57, true), ...chord(1, 62, true), ...chord(2, 64), ...chord(3, 57, true)],
40493
+ 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]]
40494
+ }),
40495
+ // a slow blues-like loop in F with a swung melody
40496
+ radioTrack({
40497
+ id: "radio_track_slow",
40498
+ bpm: 76,
40499
+ bars: 4,
40500
+ wave: "sine",
40501
+ bass: bassLine([53, 58, 53, 60], [0, 2]),
40502
+ chords: [...chord(0, 65), ...chord(1, 58), ...chord(2, 65), ...chord(3, 60)],
40503
+ 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]]
40504
+ })
40505
+ ];
40506
+ var RADIO_TRACKS = TRACKS.map((t) => t.song);
40507
+ var RADIO_BANK = { id: "vehicle_radio", sounds: TRACKS.flatMap((t) => t.recipes), loops: [] };
39668
40508
  var FOLEY_FALLOFF = {
39669
40509
  model: "inverse_square",
39670
40510
  k: 0.02,
@@ -40271,11 +41111,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40271
41111
  ...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 })] : []
40272
41112
  ];
40273
41113
  }
40274
- var quieter = (level, layers) => layers.map((layer2) => {
40275
- const gain = layer2.gain ?? 1;
40276
- if (typeof gain === "number") return { ...layer2, gain: gain * level };
40277
- if (Array.isArray(gain)) return { ...layer2, gain: [gain[0] * level, gain[1] * level] };
40278
- return layer2;
41114
+ var quieter = (level, layers) => layers.map((layer22) => {
41115
+ const gain = layer22.gain ?? 1;
41116
+ if (typeof gain === "number") return { ...layer22, gain: gain * level };
41117
+ if (Array.isArray(gain)) return { ...layer22, gain: [gain[0] * level, gain[1] * level] };
41118
+ return layer22;
40279
41119
  });
40280
41120
  var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
40281
41121
  var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
@@ -40291,7 +41131,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40291
41131
  (take) => quieter(LANDING_LEVEL, [
40292
41132
  // both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
40293
41133
  ...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
40294
- ...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 })),
41134
+ ...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer22) => ({ ...layer22, at: (typeof layer22.at === "number" ? layer22.at : 0) + 0.018 * take.t })),
40295
41135
  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 }),
40296
41136
  sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
40297
41137
  ]),
@@ -40337,7 +41177,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40337
41177
  fall_damage: (take) => [
40338
41178
  ...BODY.body_fall(take),
40339
41179
  sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
40340
- ...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 }))
41180
+ ...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer22) => ({ ...layer22, at: 0.08 }))
40341
41181
  ],
40342
41182
  player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
40343
41183
  player_breath_run: (take) => [
@@ -42410,7 +43250,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
42410
43250
  if (base.space === "indoor") {
42411
43251
  for (const voice2 of shot.voices) if (voice2.group === "tail") playGunshotVoice(voice2, extras);
42412
43252
  }
42413
- const layers = handle?.kind === "sample" ? handle.plan.layers.map((layer2) => ({ role: layer2.role, take: layer2.take, rate: layer2.rate })) : [];
43253
+ const layers = handle?.kind === "sample" ? handle.plan.layers.map((layer3) => ({ role: layer3.role, take: layer3.take, rate: layer3.rate })) : [];
42414
43254
  options.onGunshot?.(shot, { ...base, take, pitch }, { kind: "sample", id: recorded, layers });
42415
43255
  return;
42416
43256
  }
@@ -43232,6 +44072,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43232
44072
  "drink",
43233
44073
  "sticky"
43234
44074
  ]
44075
+ },
44076
+ {
44077
+ id: "mud",
44078
+ color: 4008988,
44079
+ opacity: 0.95,
44080
+ stainColor: 3811866,
44081
+ viscosity: 40,
44082
+ density: 1600,
44083
+ dryTime: 900,
44084
+ gloss: 0.4,
44085
+ tags: [
44086
+ "dirty"
44087
+ ]
43235
44088
  }
43236
44089
  ];
43237
44090
  var LIQUIDS = liquids_default;
@@ -49837,6 +50690,273 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49837
50690
  return group;
49838
50691
  }
49839
50692
 
50693
+ // src/models/rounded-box.ts
50694
+ var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
50695
+ const arcLength = 2 * Math.PI * radius / 4;
50696
+ const centerLength = Math.max(sideLength - 2 * radius, 0);
50697
+ const halfArc = Math.PI / 4;
50698
+ const projected = new THREE.Vector3().copy(normal);
50699
+ projected[projectionAxis] = 0;
50700
+ projected.normalize();
50701
+ const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
50702
+ const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
50703
+ if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
50704
+ const centerUv = centerLength / (arcLength + centerLength);
50705
+ return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
50706
+ };
50707
+ var FACES = [
50708
+ { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
50709
+ { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
50710
+ { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
50711
+ { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
50712
+ { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
50713
+ { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
50714
+ ];
50715
+ function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
50716
+ const count = segments * 2 + 1;
50717
+ const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
50718
+ const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
50719
+ const flat2 = geometry.toNonIndexed();
50720
+ geometry.setIndex(null);
50721
+ geometry.setAttribute("position", flat2.attributes.position);
50722
+ geometry.setAttribute("normal", flat2.attributes.normal);
50723
+ geometry.setAttribute("uv", flat2.attributes.uv);
50724
+ const position = new THREE.Vector3();
50725
+ const normal = new THREE.Vector3();
50726
+ const box5 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
50727
+ const positions2 = geometry.attributes.position.array;
50728
+ const normals = geometry.attributes.normal.array;
50729
+ const uvs = geometry.attributes.uv.array;
50730
+ const faceTriangles = positions2.length / 6;
50731
+ const halfSegment = 0.5 / count;
50732
+ const sides = { x: width, y: height, z: depth };
50733
+ for (let i = 0, j = 0; i < positions2.length; i += 3, j += 2) {
50734
+ position.fromArray(positions2, i);
50735
+ normal.copy(position);
50736
+ normal.x -= Math.sign(normal.x) * halfSegment;
50737
+ normal.y -= Math.sign(normal.y) * halfSegment;
50738
+ normal.z -= Math.sign(normal.z) * halfSegment;
50739
+ normal.normalize();
50740
+ positions2[i] = box5.x * Math.sign(position.x) + normal.x * rounding;
50741
+ positions2[i + 1] = box5.y * Math.sign(position.y) + normal.y * rounding;
50742
+ positions2[i + 2] = box5.z * Math.sign(position.z) + normal.z * rounding;
50743
+ normals[i] = normal.x;
50744
+ normals[i + 1] = normal.y;
50745
+ normals[i + 2] = normal.z;
50746
+ const face = FACES[Math.floor(i / faceTriangles)];
50747
+ if (!face) continue;
50748
+ const direction = new THREE.Vector3(...face.direction);
50749
+ for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
50750
+ const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
50751
+ uvs[j + offset] = mirrored ? 1 - value : value;
50752
+ }
50753
+ }
50754
+ return geometry;
50755
+ }
50756
+
50757
+ // src/models/kit.ts
50758
+ var PI2 = Math.PI;
50759
+ var createMaterials = (THREE) => {
50760
+ const standard = (color2, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color: color2, roughness, metalness, ...extra });
50761
+ const basic = (color2, extra = {}) => new THREE.MeshBasicMaterial({ color: color2, ...extra });
50762
+ const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
50763
+ const materials = {
50764
+ metal: standard(3422010, 0.42, 0.75),
50765
+ steel: standard(7106418, 0.32, 0.85),
50766
+ steelL: standard(9080208, 0.28, 0.9),
50767
+ dark: standard(1711133, 0.55, 0.5),
50768
+ poly: standard(2369058, 0.8, 0.05),
50769
+ olive: standard(4936241, 0.8, 0.05),
50770
+ tan: standard(10259043, 0.8, 0.05),
50771
+ wood: standard(4860951, 0.55, 0),
50772
+ woodL: standard(6175263, 0.55, 0),
50773
+ brass: standard(12819013, 0.28, 0.9),
50774
+ copper: standard(11561531, 0.3, 0.85),
50775
+ lead: standard(7171955, 0.5, 0.5),
50776
+ shell: standard(10692380, 0.5, 0.05),
50777
+ glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
50778
+ lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
50779
+ rubber: standard(1381653, 0.85, 0),
50780
+ white: standard(13617332, 0.8, 0),
50781
+ hole: standard(526343, 1, 0),
50782
+ bake: standard(5911072, 0.55, 0),
50783
+ rust: standard(7027230, 0.95, 0.3),
50784
+ rustD: standard(4860440, 0.95, 0.25),
50785
+ worn: standard(5593178, 0.6, 0.6),
50786
+ fire: basic(16742946),
50787
+ fire2: basic(16764762),
50788
+ flashA: basic(16757575, additive),
50789
+ flashB: basic(16773568, additive),
50790
+ smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
50791
+ /** Launcher paints. */
50792
+ yellow: standard(11570978, 0.6, 0),
50793
+ red: standard(9313300, 0.45, 0.1),
50794
+ redPaint: standard(10691354, 0.35, 0.2),
50795
+ tape: standard(9079944, 0.9, 0),
50796
+ pipe: standard(10115634, 0.35, 0.8),
50797
+ green: standard(3818536, 0.75, 0.05),
50798
+ /** Explosive finishes: the molotov's bottle, the dynamite's paper, the C4's putty and the detonator's wire. */
50799
+ bottle: standard(3103284, 0.1, 0.1, { transparent: true, opacity: 0.72 }),
50800
+ dynamite: standard(10365471, 0.7, 0),
50801
+ c4: standard(7170636, 0.95, 0),
50802
+ wire: standard(11542560, 0.5, 0),
50803
+ /** Magazine finishes named by the options. */
50804
+ bakelite: standard(6958620, 0.6, 0),
50805
+ polyMag: standard(2763302, 0.85, 0)
50806
+ };
50807
+ markShared(...Object.values(materials));
50808
+ return materials;
50809
+ };
50810
+ var kits = /* @__PURE__ */ new WeakMap();
50811
+ function modelKit(host) {
50812
+ const cached = kits.get(host);
50813
+ if (cached) return cached;
50814
+ const THREE = shapeGeometries(host);
50815
+ const geometries = /* @__PURE__ */ new Map();
50816
+ const shadowed = (mesh3) => {
50817
+ mesh3.castShadow = true;
50818
+ mesh3.receiveShadow = true;
50819
+ return mesh3;
50820
+ };
50821
+ const place = (parent, mesh3, x, y, z) => {
50822
+ mesh3.position.set(x, y, z);
50823
+ parent.add(mesh3);
50824
+ return mesh3;
50825
+ };
50826
+ const kit = {
50827
+ THREE,
50828
+ M: createMaterials(THREE),
50829
+ roundness: null,
50830
+ group: () => new THREE.Group(),
50831
+ box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
50832
+ const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
50833
+ const key2 = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
50834
+ let geometry = geometries.get(key2);
50835
+ if (!geometry) {
50836
+ geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
50837
+ markShared(geometry);
50838
+ geometries.set(key2, geometry);
50839
+ }
50840
+ const mesh3 = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
50841
+ mesh3.rotation.set(rx, ry, rz);
50842
+ return mesh3;
50843
+ },
50844
+ cylinderX(parent, radius, length2, material, x, y, z, segments = 16, radiusEnd) {
50845
+ const mesh3 = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length2, segments), material));
50846
+ mesh3.rotation.z = -PI2 / 2;
50847
+ return place(parent, mesh3, x, y, z);
50848
+ },
50849
+ cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
50850
+ return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
50851
+ },
50852
+ cylinderZ(parent, radius, length2, material, x, y, z, segments = 16) {
50853
+ const mesh3 = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length2, segments), material));
50854
+ mesh3.rotation.x = PI2 / 2;
50855
+ return place(parent, mesh3, x, y, z);
50856
+ },
50857
+ sphere(parent, radius, material, x, y, z, detail = 1) {
50858
+ return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
50859
+ },
50860
+ torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
50861
+ mark(parent, x, y, z) {
50862
+ const object2 = new THREE.Object3D();
50863
+ object2.position.set(x, y, z);
50864
+ parent.add(object2);
50865
+ return object2;
50866
+ },
50867
+ pivot(parent, x = 0, y = 0, z = 0) {
50868
+ const group = new THREE.Group();
50869
+ group.position.set(x, y, z);
50870
+ parent.add(group);
50871
+ return group;
50872
+ }
50873
+ };
50874
+ kits.set(host, kit);
50875
+ return kit;
50876
+ }
50877
+ function heatify(glow, mesh3) {
50878
+ mesh3.material = mesh3.material.clone();
50879
+ glow.push(mesh3);
50880
+ return mesh3;
50881
+ }
50882
+
50883
+ // src/models/explosives.ts
50884
+ var grenade = (kit, group) => {
50885
+ const { M, sphere: sphere2, cylinderY, box: box5, torus: torus2, THREE } = kit;
50886
+ sphere2(group, 0.032, M.olive, 0, 0.032, 0, 1);
50887
+ cylinderY(group, 0.011, 0.022, M.metal, 0, 0.072, 0);
50888
+ box5(group, 0.012, 0.07, 6e-3, M.metal, 0.016, 0.05, 0, 0.2);
50889
+ torus2(group, new THREE.TorusGeometry(85e-4, 16e-4, 6, 12), M.steel, -0.012, 0.082, 0);
50890
+ kit.mark(group, 0, 0.032, 0).name = "handle";
50891
+ };
50892
+ var molotov = (kit, group) => {
50893
+ const { M, cylinderY, box: box5 } = kit;
50894
+ cylinderY(group, 0.036, 0.17, M.bottle, 0, 0.085, 0, 10);
50895
+ cylinderY(group, 0.012, 0.06, M.bottle, 0, 0.2, 0, 8, 0.036);
50896
+ box5(group, 0.03, 0.08, 0.02, M.white, 5e-3, 0.25, 0, 0.3);
50897
+ cylinderY(group, 0.02, 0.07, M.fire, 0.01, 0.31, 0, 6, 0).userData.flame = true;
50898
+ kit.mark(group, 0, 0.085, 0).name = "handle";
50899
+ };
50900
+ var pipeBomb = (kit, group) => {
50901
+ const { M, cylinderX, cylinderY } = kit;
50902
+ cylinderX(group, 0.025, 0.2, M.metal, 0, 0.03, 0, 8);
50903
+ cylinderX(group, 0.031, 0.025, M.dark, -0.1, 0.03, 0, 6);
50904
+ cylinderX(group, 0.031, 0.025, M.dark, 0.1, 0.03, 0, 6);
50905
+ cylinderY(group, 2e-3, 0.06, M.white, 0.1, 0.07, 0);
50906
+ kit.mark(group, 0, 0.03, 0).name = "handle";
50907
+ };
50908
+ var dynamite = (kit, group) => {
50909
+ const { M, cylinderX } = kit;
50910
+ for (const [z, y] of [[-0.015, 0], [0.015, 0], [0, 0.026]]) cylinderX(group, 0.014, 0.2, M.dynamite, 0, 0.016 + y, z, 8);
50911
+ cylinderX(group, 0.031, 0.03, M.dark, -0.04, 0.025, 0, 8);
50912
+ cylinderX(group, 0.031, 0.03, M.dark, 0.05, 0.025, 0, 8);
50913
+ cylinderX(group, 2e-3, 0.1, M.white, 0.14, 0.03, 0, 4);
50914
+ kit.mark(group, 0, 0.025, 0).name = "handle";
50915
+ };
50916
+ var c4 = (kit, group) => {
50917
+ const { M, box: box5, cylinderY } = kit;
50918
+ box5(group, 0.28, 0.05, 0.11, M.c4, 0, 0.025, 0);
50919
+ box5(group, 0.05, 0.03, 0.04, M.dark, 0.06, 0.065, 0);
50920
+ cylinderY(group, 4e-3, 0.05, M.metal, 0.03, 0.07, 0);
50921
+ box5(group, 0.24, 3e-3, 3e-3, M.wire, 0.18, 0.055, 0.02);
50922
+ box5(group, 0.24, 3e-3, 3e-3, M.metal, 0.18, 0.055, 0.03);
50923
+ kit.mark(group, 0, 0.025, 0).name = "handle";
50924
+ };
50925
+ var claymore = (kit, group) => {
50926
+ const { M, box: box5 } = kit;
50927
+ for (const i of [-1, 0, 1]) box5(group, 0.075, 0.083, 0.035, M.olive, i * 0.072, 0.12, Math.abs(i) * 0.012, 0, -i * 0.25);
50928
+ for (const [x, z] of [[-0.07, 0.03], [-0.07, -0.03], [0.07, 0.03], [0.07, -0.03]]) box5(group, 4e-3, 0.09, 4e-3, M.metal, x, 0.04, z, x > 0 ? -0.25 : 0.25);
50929
+ kit.mark(group, 0, 0.12, 0).name = "handle";
50930
+ };
50931
+ var flashbang = (kit, group) => {
50932
+ const { M, cylinderY, box: box5 } = kit;
50933
+ cylinderY(group, 0.022, 0.13, M.olive, 0, 0.065, 0, 10);
50934
+ for (let i = 0; i < 4; i++) cylinderY(group, 0.023, 8e-3, M.dark, 0, 0.025 + i * 0.028, 0, 10);
50935
+ cylinderY(group, 0.012, 0.02, M.metal, 0, 0.14, 0);
50936
+ box5(group, 0.01, 0.08, 5e-3, M.metal, 0.024, 0.1, 0, 0.15);
50937
+ kit.mark(group, 0, 0.065, 0).name = "handle";
50938
+ };
50939
+ var BUILDERS3 = {
50940
+ m67: grenade,
50941
+ molotov,
50942
+ pipe_bomb: pipeBomb,
50943
+ dynamite,
50944
+ c4,
50945
+ claymore,
50946
+ flashbang
50947
+ };
50948
+ var EXPLOSIVE_MODEL_IDS = Object.keys(BUILDERS3);
50949
+ var hasExplosiveModel = (explosiveId) => explosiveId in BUILDERS3;
50950
+ function buildExplosiveModel(THREE, explosiveId) {
50951
+ const builder = BUILDERS3[explosiveId];
50952
+ if (!builder) return void 0;
50953
+ const kit = modelKit(THREE);
50954
+ const group = markKitOrigin(kit.group(), { owner: "game-weapons-three", kind: "explosive", id: explosiveId });
50955
+ group.name = explosiveId;
50956
+ builder(kit, group);
50957
+ return group;
50958
+ }
50959
+
49840
50960
  // src/weapon-item-model.ts
49841
50961
  var SHIPPED_WEAPON_ITEM_MODELS = [
49842
50962
  ["wrench", { item: "adjustable_wrench" }],
@@ -49845,8 +50965,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49845
50965
  ["crossbow", { turn: 1 }],
49846
50966
  ["knife", { flip: true }],
49847
50967
  ["meat_cleaver", { flip: true }],
49848
- ["screwdriver", { flip: true }]
50968
+ ["screwdriver", { flip: true }],
50969
+ // the Itens do Vale Cinzento hammer is a claw hammer: the head and the two-pronged claw
50970
+ ["claw_hammer", { item: "hammer" }]
49849
50971
  ];
50972
+ var hasThrowableModel = (def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 && hasExplosiveModel(def.action.explosive);
50973
+ var buildThrowableModel = (THREE, def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 ? buildExplosiveModel(THREE, def.action.explosive) : void 0;
49850
50974
  var weaponPropsOver = (models) => {
49851
50975
  const itemModelOf = (weaponId, items) => {
49852
50976
  const id = models.get(weaponId)?.item ?? weaponId;
@@ -49862,7 +50986,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49862
50986
  get: (weaponId) => models.get(weaponId),
49863
50987
  itemModelOf,
49864
50988
  buildItemModel,
49865
- buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildProp(THREE, weaponProp(def))
50989
+ buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def) ?? buildProp(THREE, weaponProp(def))
49866
50990
  };
49867
50991
  };
49868
50992
  var defaultProps;
@@ -49874,6 +50998,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49874
50998
 
49875
50999
  // src/box-fx.ts
49876
51000
  var EJECTS_ON_SHOT = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper", "shotgun"]);
51001
+ var SPOUTS = /* @__PURE__ */ new Set(["firearm", "flame", "heat", "extinguisher"]);
49877
51002
  var DROPS_MAGAZINE = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper"]);
49878
51003
  var CYCLES = /* @__PURE__ */ new Set(["shotgun_pump_12", "rifle_bolt_308"]);
49879
51004
  var FALLBACK_ENERGY = 300;
@@ -49908,6 +51033,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49908
51033
  const q = new THREE.Quaternion();
49909
51034
  const q2 = new THREE.Quaternion();
49910
51035
  const pointForward = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
51036
+ const frontOf = new THREE.Vector3();
49911
51037
  const equip = (weapon) => {
49912
51038
  if (model) hand.remove(model);
49913
51039
  model = null;
@@ -49918,6 +51044,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
49918
51044
  const length2 = new THREE.Box3().setFromObject(model).getSize(v).x;
49919
51045
  rear = new THREE.Box3().setFromObject(model).min.x;
49920
51046
  lengthMetres = length2;
51047
+ frontOf.copy(new THREE.Box3().setFromObject(model).getCenter(v)).setX(rear + length2);
49921
51048
  model.quaternion.copy(pointForward);
49922
51049
  hand.add(model);
49923
51050
  inHand = def.id;
@@ -50018,6 +51145,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
50018
51145
  inHand = null;
50019
51146
  hold = null;
50020
51147
  },
51148
+ muzzle() {
51149
+ if (!model || !def || !SPOUTS.has(def.action.kind)) return null;
51150
+ hand.updateMatrixWorld(true);
51151
+ const at3 = model.localToWorld(v.copy(frontOf));
51152
+ const along = v2.set(1, 0, 0).applyQuaternion(model.getWorldQuaternion(q2)).normalize();
51153
+ return { position: [at3.x, at3.y, at3.z], direction: [along.x, along.y, along.z], drawsFlash: false };
51154
+ },
50021
51155
  handTargets() {
50022
51156
  if (!model || !hold) return null;
50023
51157
  hand.updateMatrixWorld(true);
@@ -51618,193 +52752,23 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51618
52752
  options: {
51619
52753
  rpm: 3e3
51620
52754
  }
51621
- }
51622
- };
51623
-
51624
- // src/models/rounded-box.ts
51625
- var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
51626
- const arcLength = 2 * Math.PI * radius / 4;
51627
- const centerLength = Math.max(sideLength - 2 * radius, 0);
51628
- const halfArc = Math.PI / 4;
51629
- const projected = new THREE.Vector3().copy(normal);
51630
- projected[projectionAxis] = 0;
51631
- projected.normalize();
51632
- const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
51633
- const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
51634
- if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
51635
- const centerUv = centerLength / (arcLength + centerLength);
51636
- return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
51637
- };
51638
- var FACES = [
51639
- { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
51640
- { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
51641
- { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
51642
- { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
51643
- { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
51644
- { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
51645
- ];
51646
- function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
51647
- const count = segments * 2 + 1;
51648
- const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
51649
- const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
51650
- const flat2 = geometry.toNonIndexed();
51651
- geometry.setIndex(null);
51652
- geometry.setAttribute("position", flat2.attributes.position);
51653
- geometry.setAttribute("normal", flat2.attributes.normal);
51654
- geometry.setAttribute("uv", flat2.attributes.uv);
51655
- const position = new THREE.Vector3();
51656
- const normal = new THREE.Vector3();
51657
- const box5 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
51658
- const positions2 = geometry.attributes.position.array;
51659
- const normals = geometry.attributes.normal.array;
51660
- const uvs = geometry.attributes.uv.array;
51661
- const faceTriangles = positions2.length / 6;
51662
- const halfSegment = 0.5 / count;
51663
- const sides = { x: width, y: height, z: depth };
51664
- for (let i = 0, j = 0; i < positions2.length; i += 3, j += 2) {
51665
- position.fromArray(positions2, i);
51666
- normal.copy(position);
51667
- normal.x -= Math.sign(normal.x) * halfSegment;
51668
- normal.y -= Math.sign(normal.y) * halfSegment;
51669
- normal.z -= Math.sign(normal.z) * halfSegment;
51670
- normal.normalize();
51671
- positions2[i] = box5.x * Math.sign(position.x) + normal.x * rounding;
51672
- positions2[i + 1] = box5.y * Math.sign(position.y) + normal.y * rounding;
51673
- positions2[i + 2] = box5.z * Math.sign(position.z) + normal.z * rounding;
51674
- normals[i] = normal.x;
51675
- normals[i + 1] = normal.y;
51676
- normals[i + 2] = normal.z;
51677
- const face = FACES[Math.floor(i / faceTriangles)];
51678
- if (!face) continue;
51679
- const direction = new THREE.Vector3(...face.direction);
51680
- for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
51681
- const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
51682
- uvs[j + offset] = mirrored ? 1 - value : value;
52755
+ },
52756
+ m79: {
52757
+ builder: "breakLauncher",
52758
+ caliber: "40\xD746",
52759
+ length: 730,
52760
+ barrel: 356,
52761
+ options: {
52762
+ stock: "fixed",
52763
+ wood: 1,
52764
+ mag: "none",
52765
+ hg: "wood",
52766
+ hgFrac: 0.4,
52767
+ br: 0.022,
52768
+ hR: 0.05
51683
52769
  }
51684
52770
  }
51685
- return geometry;
51686
- }
51687
-
51688
- // src/models/kit.ts
51689
- var PI2 = Math.PI;
51690
- var createMaterials = (THREE) => {
51691
- const standard = (color2, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color: color2, roughness, metalness, ...extra });
51692
- const basic = (color2, extra = {}) => new THREE.MeshBasicMaterial({ color: color2, ...extra });
51693
- const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
51694
- const materials = {
51695
- metal: standard(3422010, 0.42, 0.75),
51696
- steel: standard(7106418, 0.32, 0.85),
51697
- steelL: standard(9080208, 0.28, 0.9),
51698
- dark: standard(1711133, 0.55, 0.5),
51699
- poly: standard(2369058, 0.8, 0.05),
51700
- olive: standard(4936241, 0.8, 0.05),
51701
- tan: standard(10259043, 0.8, 0.05),
51702
- wood: standard(4860951, 0.55, 0),
51703
- woodL: standard(6175263, 0.55, 0),
51704
- brass: standard(12819013, 0.28, 0.9),
51705
- copper: standard(11561531, 0.3, 0.85),
51706
- lead: standard(7171955, 0.5, 0.5),
51707
- shell: standard(10692380, 0.5, 0.05),
51708
- glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
51709
- lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
51710
- rubber: standard(1381653, 0.85, 0),
51711
- white: standard(13617332, 0.8, 0),
51712
- hole: standard(526343, 1, 0),
51713
- bake: standard(5911072, 0.55, 0),
51714
- rust: standard(7027230, 0.95, 0.3),
51715
- rustD: standard(4860440, 0.95, 0.25),
51716
- worn: standard(5593178, 0.6, 0.6),
51717
- fire: basic(16742946),
51718
- fire2: basic(16764762),
51719
- flashA: basic(16757575, additive),
51720
- flashB: basic(16773568, additive),
51721
- smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
51722
- /** Launcher paints. */
51723
- yellow: standard(11570978, 0.6, 0),
51724
- red: standard(9313300, 0.45, 0.1),
51725
- redPaint: standard(10691354, 0.35, 0.2),
51726
- tape: standard(9079944, 0.9, 0),
51727
- pipe: standard(10115634, 0.35, 0.8),
51728
- green: standard(3818536, 0.75, 0.05),
51729
- /** Magazine finishes named by the options. */
51730
- bakelite: standard(6958620, 0.6, 0),
51731
- polyMag: standard(2763302, 0.85, 0)
51732
- };
51733
- markShared(...Object.values(materials));
51734
- return materials;
51735
52771
  };
51736
- var kits = /* @__PURE__ */ new WeakMap();
51737
- function modelKit(host) {
51738
- const cached = kits.get(host);
51739
- if (cached) return cached;
51740
- const THREE = shapeGeometries(host);
51741
- const geometries = /* @__PURE__ */ new Map();
51742
- const shadowed = (mesh3) => {
51743
- mesh3.castShadow = true;
51744
- mesh3.receiveShadow = true;
51745
- return mesh3;
51746
- };
51747
- const place = (parent, mesh3, x, y, z) => {
51748
- mesh3.position.set(x, y, z);
51749
- parent.add(mesh3);
51750
- return mesh3;
51751
- };
51752
- const kit = {
51753
- THREE,
51754
- M: createMaterials(THREE),
51755
- roundness: null,
51756
- group: () => new THREE.Group(),
51757
- box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
51758
- const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
51759
- const key2 = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
51760
- let geometry = geometries.get(key2);
51761
- if (!geometry) {
51762
- geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
51763
- markShared(geometry);
51764
- geometries.set(key2, geometry);
51765
- }
51766
- const mesh3 = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
51767
- mesh3.rotation.set(rx, ry, rz);
51768
- return mesh3;
51769
- },
51770
- cylinderX(parent, radius, length2, material, x, y, z, segments = 16, radiusEnd) {
51771
- const mesh3 = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length2, segments), material));
51772
- mesh3.rotation.z = -PI2 / 2;
51773
- return place(parent, mesh3, x, y, z);
51774
- },
51775
- cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
51776
- return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
51777
- },
51778
- cylinderZ(parent, radius, length2, material, x, y, z, segments = 16) {
51779
- const mesh3 = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length2, segments), material));
51780
- mesh3.rotation.x = PI2 / 2;
51781
- return place(parent, mesh3, x, y, z);
51782
- },
51783
- sphere(parent, radius, material, x, y, z, detail = 1) {
51784
- return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
51785
- },
51786
- torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
51787
- mark(parent, x, y, z) {
51788
- const object2 = new THREE.Object3D();
51789
- object2.position.set(x, y, z);
51790
- parent.add(object2);
51791
- return object2;
51792
- },
51793
- pivot(parent, x = 0, y = 0, z = 0) {
51794
- const group = new THREE.Group();
51795
- group.position.set(x, y, z);
51796
- parent.add(group);
51797
- return group;
51798
- }
51799
- };
51800
- kits.set(host, kit);
51801
- return kit;
51802
- }
51803
- function heatify(glow, mesh3) {
51804
- mesh3.material = mesh3.material.clone();
51805
- glow.push(mesh3);
51806
- return mesh3;
51807
- }
51808
52772
 
51809
52773
  // src/models/break-action.ts
51810
52774
  function buildBreakAction(kit, options) {
@@ -51839,6 +52803,509 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51839
52803
  return { group, parts };
51840
52804
  }
51841
52805
 
52806
+ // src/models/attachments/muzzle-brake.ts
52807
+ function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
52808
+ const { M, box: box5, cylinderX } = kit;
52809
+ const scale = kind === 2 ? 3.2 : 2;
52810
+ const length2 = kind === 2 ? 0.12 : 0.06;
52811
+ heat(cylinderX(parent, barrelRadius * scale, length2, M.dark, x - length2 / 2, y, 0, kind === 2 ? 8 : 16));
52812
+ for (let i = 0; i < 3; i++) box5(parent, length2 * 0.16, barrelRadius * scale * 1.3, barrelRadius * scale * 2.2, M.hole, x - length2 * 0.25 - i * length2 * 0.25, y, 0);
52813
+ }
52814
+
52815
+ // src/models/capture.ts
52816
+ function captured(parent, draw2) {
52817
+ const before = parent.children.length;
52818
+ draw2();
52819
+ return parent.children.slice(before);
52820
+ }
52821
+ function recordFitting(parts, id, parent, draw2) {
52822
+ parts.fittings = { ...parts.fittings, [id]: captured(parent, draw2) };
52823
+ }
52824
+
52825
+ // src/models/details.ts
52826
+ var PI3 = Math.PI;
52827
+ function rail(kit, parent, x, length2, y, z, width, rotationX = 0) {
52828
+ const { M, box: box5 } = kit;
52829
+ const pivot = kit.pivot(parent, 0, y, z);
52830
+ pivot.rotation.x = rotationX;
52831
+ box5(pivot, length2, 5e-3, width, M.dark, x + length2 / 2, 0, 0);
52832
+ const slots = Math.floor(length2 / 95e-4);
52833
+ for (let i = 0; i < slots; i++) box5(pivot, 52e-4, 35e-4, width * 1.12, M.metal, x + 6e-3 + i * 95e-4, 35e-4, 0);
52834
+ return pivot;
52835
+ }
52836
+ function vents(kit, parent, x, length2, y, z, count, height) {
52837
+ for (let i = 0; i < count; i++) kit.box(parent, length2 / count * 0.45, height, 3e-3, kit.M.hole, x + (i + 0.5) * length2 / count, y, z);
52838
+ }
52839
+ function scope(kit, parent, kind, cx, cy, cz = 0) {
52840
+ const { M, box: box5, cylinderX, cylinderY, cylinderZ, THREE } = kit;
52841
+ if (kind === 1) {
52842
+ box5(parent, 0.044, 0.01, 0.028, M.dark, cx, cy + 5e-3, cz);
52843
+ cylinderX(parent, 0.0155, 0.034, M.dark, cx + 4e-3, cy + 0.026, cz, 18);
52844
+ cylinderX(parent, 0.0125, 2e-3, M.lens, cx + 0.022, cy + 0.026, cz, 18);
52845
+ cylinderY(parent, 4e-3, 6e-3, M.dark, cx + 4e-3, cy + 0.044, cz, 10);
52846
+ return;
52847
+ }
52848
+ if (kind === 2) {
52849
+ box5(parent, 0.07, 0.012, 0.026, M.dark, cx, cy + 6e-3, cz);
52850
+ cylinderX(parent, 0.016, 0.1, M.dark, cx - 0.01, cy + 0.032, cz, 18);
52851
+ cylinderX(parent, 0.016, 0.035, M.dark, cx + 0.055, cy + 0.032, cz, 18, 0.02);
52852
+ cylinderX(parent, 0.017, 2e-3, M.lens, cx + 0.073, cy + 0.032, cz, 18);
52853
+ cylinderY(parent, 7e-3, 0.012, M.dark, cx, cy + 0.052, cz, 12);
52854
+ box5(parent, 0.03, 0.01, 0.012, M.dark, cx - 0.01, cy + 0.054, cz);
52855
+ return;
52856
+ }
52857
+ const sleeve = kind === 4 ? 0.2 : 0.32;
52858
+ cylinderX(parent, 0.0135, sleeve * 0.55, M.dark, cx, cy + 0.045, cz, 18);
52859
+ cylinderX(parent, 0.0135, sleeve * 0.25, M.dark, cx + sleeve * 0.36, cy + 0.045, cz, 18, 0.026);
52860
+ cylinderX(parent, 0.026, 0.05, M.dark, cx + sleeve * 0.5, cy + 0.045, cz, 18);
52861
+ cylinderX(parent, 0.024, 2e-3, M.lens, cx + sleeve * 0.5 + 0.026, cy + 0.045, cz, 18);
52862
+ cylinderX(parent, 0.019, 0.06, M.dark, cx - sleeve * 0.38, cy + 0.045, cz, 18, 0.0135);
52863
+ cylinderX(parent, 0.019, 0.035, M.rubber, cx - sleeve * 0.38 - 0.045, cy + 0.045, cz, 18);
52864
+ cylinderX(parent, 0.017, 2e-3, M.lens, cx - sleeve * 0.38 - 0.063, cy + 0.045, cz, 18);
52865
+ cylinderY(parent, 0.01, 0.022, M.dark, cx, cy + 0.066, cz, 14);
52866
+ cylinderY(parent, 0.011, 4e-3, M.metal, cx, cy + 0.078, cz, 14);
52867
+ cylinderZ(parent, 0.01, 0.022, M.dark, cx, cy + 0.045, cz + 0.022, 14);
52868
+ for (const offset of [-0.07, 0.07]) {
52869
+ const ring3 = kit.torus(parent, new THREE.TorusGeometry(0.0155, 3e-3, 8, 20), M.dark, cx + offset, cy + 0.045, cz);
52870
+ ring3.rotation.y = PI3 / 2;
52871
+ box5(parent, 0.016, 0.03, 0.022, M.dark, cx + offset, cy + 0.018, cz);
52872
+ }
52873
+ }
52874
+ var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
52875
+ var swivel = (kit, parent, x, y) => void kit.torus(parent, new kit.THREE.TorusGeometry(6e-3, 15e-4, 6, 12), kit.M.metal, x, y, 0);
52876
+
52877
+ // src/models/attachments/bipod.ts
52878
+ function buildBipod(kit, parent, x, y) {
52879
+ const { M, box: box5, cylinderX } = kit;
52880
+ cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
52881
+ for (const side of [-1, 1]) {
52882
+ const leg = kit.pivot(parent, x, y, side * 0.012);
52883
+ leg.rotation.set(side * 0.15, 0, -0.35);
52884
+ box5(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
52885
+ box5(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
52886
+ }
52887
+ }
52888
+ var buildBipodModel = ({ kit }) => {
52889
+ const group = kit.group();
52890
+ buildBipod(kit, group, 0, 0);
52891
+ return group;
52892
+ };
52893
+
52894
+ // src/models/rifle-fittings.ts
52895
+ var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
52896
+ function buildFittings(context, gripX) {
52897
+ const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
52898
+ const { M, box: box5, cylinderX, cylinderZ, sphere: sphere2 } = kit;
52899
+ const middle = (x0 + x1) / 2;
52900
+ if (o.scope) recordFitting(parts, SCOPE_IDS[o.scope] ?? "scope", group, () => scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0));
52901
+ if (o.carry) {
52902
+ box5(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
52903
+ box5(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
52904
+ box5(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
52905
+ }
52906
+ if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
52907
+ if (o.bolt) {
52908
+ const handle = kit.pivot(group, x1 - 0.045, by, 0);
52909
+ parts.boltHandle = handle;
52910
+ parts.boltHandleRest = handle.position.x;
52911
+ cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
52912
+ sphere2(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
52913
+ cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
52914
+ }
52915
+ if (o.lever) {
52916
+ const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
52917
+ parts.lever = lever;
52918
+ box5(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
52919
+ box5(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
52920
+ box5(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
52921
+ box5(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
52922
+ box5(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
52923
+ }
52924
+ if (o.tube) {
52925
+ const tubeLength = (L - x1) * (o.tubeLen || 0.8);
52926
+ cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
52927
+ cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
52928
+ }
52929
+ if (o.pump) {
52930
+ const pump = kit.pivot(group);
52931
+ parts.pump = pump;
52932
+ box5(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
52933
+ for (let i = 0; i < 8; i++) box5(pump, 6e-3, 0.044, 0.048, M.dark, x1 + 0.07 + i * 0.02, by - 0.022, 0);
52934
+ }
52935
+ if (o.heat) {
52936
+ cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
52937
+ for (let i = 0; i < 10; i++) box5(group, 8e-3, r * 1.5, 4e-3, M.hole, x1 + (L - x1) * (0.1 + i * 0.06), by + r * 0.8, r * 1.5);
52938
+ }
52939
+ if (o.bayo) box5(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
52940
+ if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
52941
+ }
52942
+
52943
+ // src/models/rifle-forend.ts
52944
+ var PI4 = Math.PI;
52945
+ function buildForend(context) {
52946
+ const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
52947
+ const { M, box: box5 } = kit;
52948
+ const hasLongScope = (o.scope ?? 0) >= 3;
52949
+ if (!context.isTraditional && o.hg !== "none") {
52950
+ const handguardLength = (L - x1) * (o.hgFrac || 0.5);
52951
+ const hx = x1 + handguardLength / 2;
52952
+ const hh = hR * 0.8;
52953
+ if (o.hg === "rail") {
52954
+ box5(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
52955
+ rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
52956
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI4 / 2);
52957
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI4 / 2);
52958
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI4);
52959
+ } else {
52960
+ const material = o.hg === "wood" ? M.wood : furn;
52961
+ if (o.ak) {
52962
+ box5(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
52963
+ box5(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
52964
+ } else {
52965
+ box5(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
52966
+ vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, wR * 0.54 + 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
52967
+ vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, -wR * 0.54 - 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
52968
+ }
52969
+ }
52970
+ if (!hasLongScope && !o.rail && o.ak) {
52971
+ box5(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
52972
+ box5(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
52973
+ }
52974
+ }
52975
+ if (hasLongScope) return;
52976
+ const sightX = L - (o.ak ? 0.06 : 0.08);
52977
+ if (o.ak) {
52978
+ box5(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
52979
+ box5(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
52980
+ box5(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
52981
+ box5(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
52982
+ } else if (!o.rail || o.carry) {
52983
+ box5(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
52984
+ box5(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
52985
+ } else box5(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
52986
+ }
52987
+
52988
+ // src/models/attachments/belt-box.ts
52989
+ function buildBeltBox(kit, belt) {
52990
+ const { M, box: box5, cylinderX } = kit;
52991
+ box5(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
52992
+ box5(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
52993
+ for (let i = 0; i < 7; i++) {
52994
+ cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
52995
+ box5(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
52996
+ }
52997
+ }
52998
+
52999
+ // src/models/attachments/drum.ts
53000
+ function buildDrum(kit, magazine) {
53001
+ const { M, box: box5, cylinderZ } = kit;
53002
+ cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
53003
+ cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
53004
+ box5(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
53005
+ }
53006
+
53007
+ // src/models/helical-magazine.ts
53008
+ var PI5 = Math.PI;
53009
+ var LENGTH = 0.26;
53010
+ var RADIUS = 0.026;
53011
+ var TURNS = 11;
53012
+ function buildHelicalMagazine(context) {
53013
+ const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
53014
+ const { M, cylinderX, box: box5 } = kit;
53015
+ const x = receiverEnd + LENGTH / 2 + 0.02;
53016
+ const y = barrelY - barrelRadius - RADIUS - 0.012;
53017
+ const magazine = kit.pivot(group, x, y, 0);
53018
+ parts.mag = magazine;
53019
+ cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
53020
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
53021
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
53022
+ for (let i = 0; i < TURNS; i++) box5(magazine, 0.012, RADIUS * 2.15, 0.014, M.metal, -LENGTH / 2 + (i + 0.5) * LENGTH / TURNS, 0, 0, 0, 0, i * PI5 / 4);
53023
+ box5(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
53024
+ parts.magDrop = 0.16;
53025
+ return x;
53026
+ }
53027
+
53028
+ // src/models/rifle-controls.ts
53029
+ function buildGrip(context) {
53030
+ const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
53031
+ const { M, box: box5 } = kit;
53032
+ const gx = x0 + (o.gripAt || 0.07);
53033
+ if (!context.isTraditional && o.grip !== "none") {
53034
+ const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
53035
+ grip.rotation.z = -0.3;
53036
+ parts.grip = grip;
53037
+ box5(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
53038
+ for (let i = 0; i < 3; i++) box5(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
53039
+ box5(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
53040
+ }
53041
+ box5(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
53042
+ box5(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
53043
+ parts.trigger = box5(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
53044
+ if (o.sel) parts.safety = box5(group, 0.016, 5e-3, 3e-3, M.dark, gx + 5e-3, -hR * 0.15, -wR / 2 - 2e-3, 0.4);
53045
+ return gx;
53046
+ }
53047
+ function buildMagazine(context, gx) {
53048
+ const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
53049
+ const { M, box: box5 } = kit;
53050
+ if (o.mag === "helical") return buildHelicalMagazine(context);
53051
+ const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
53052
+ const scale = o.magLen || 1;
53053
+ const magazine = kit.pivot(group, mx, -hR / 2, 0);
53054
+ parts.mag = magazine;
53055
+ const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
53056
+ if (o.mag === "curved") {
53057
+ let px = 0;
53058
+ let py = 0;
53059
+ const material = finish ?? M.metal;
53060
+ for (const angle of [0.06, 0.22, 0.38]) {
53061
+ const length2 = 0.07 * scale;
53062
+ const cx = px + Math.sin(angle) * length2 / 2;
53063
+ const cy = py - Math.cos(angle) * length2 / 2;
53064
+ box5(magazine, 0.052, length2 + 4e-3, 0.024, material, cx, cy, 0, angle);
53065
+ box5(magazine, 0.04, length2 * 0.8, 0.026, M.dark, cx, cy, 0, angle);
53066
+ px += Math.sin(angle) * length2;
53067
+ py -= Math.cos(angle) * length2;
53068
+ }
53069
+ box5(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
53070
+ parts.magDrop = 0.07 * scale * 3 + 0.04;
53071
+ } else if (o.mag === "box") {
53072
+ const length2 = 0.13 * scale;
53073
+ const curve = o.curve || 0;
53074
+ const segment = length2 / 3;
53075
+ const material = finish ?? M.dark;
53076
+ let px = 0;
53077
+ let py = 0;
53078
+ let angle = 0.08;
53079
+ for (let i = 0; i < 3; i++) {
53080
+ angle = 0.08 + curve * i;
53081
+ const cx = px + Math.sin(angle) * segment / 2;
53082
+ const cy = py - Math.cos(angle) * segment / 2;
53083
+ box5(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
53084
+ box5(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
53085
+ px += Math.sin(angle) * segment;
53086
+ py -= Math.cos(angle) * segment;
53087
+ }
53088
+ box5(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
53089
+ parts.magDrop = length2 + 0.03;
53090
+ } else if (o.mag === "drum") {
53091
+ buildDrum(kit, magazine);
53092
+ parts.magDrop = 0.2;
53093
+ } else if (o.mag === "internal") {
53094
+ box5(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
53095
+ parts.mag = null;
53096
+ } else parts.mag = null;
53097
+ if (o.mag === "belt") {
53098
+ const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
53099
+ parts.belt = belt;
53100
+ buildBeltBox(kit, belt);
53101
+ const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
53102
+ parts.cover = cover;
53103
+ box5(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
53104
+ box5(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
53105
+ }
53106
+ return mx;
53107
+ }
53108
+
53109
+ // src/models/rifle-stock.ts
53110
+ function buildStock(context, magazineX) {
53111
+ const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
53112
+ const { M, box: box5, cylinderX } = kit;
53113
+ if (context.isTraditional) {
53114
+ buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
53115
+ box5(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
53116
+ box5(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
53117
+ box5(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
53118
+ const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
53119
+ if (forendLength > 0) box5(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
53120
+ if (o.stock === "straight") box5(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
53121
+ box5(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
53122
+ swivel(kit, group, 0.2, -0.08);
53123
+ swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
53124
+ if (o.bands) for (const fraction of [0.35, 0.75]) cylinderX(group, 0.024, 8e-3, M.metal, x1 + (L - x1) * fraction * (o.fore || 0.62), -4e-3, 0, 16);
53125
+ } else if (o.stock === "fixed" || o.stock === "wood") {
53126
+ box5(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
53127
+ box5(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
53128
+ buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
53129
+ if (o.stock === "wood") box5(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
53130
+ swivel(kit, group, 0.08, -0.09);
53131
+ } else if (o.stock === "skeleton") {
53132
+ box5(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
53133
+ box5(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
53134
+ box5(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
53135
+ buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
53136
+ } else if (o.stock === "retract") {
53137
+ cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
53138
+ for (let i = 0; i < 5; i++) box5(group, 3e-3, 3e-3, 8e-3, M.dark, S * 0.2 + i * 0.015, -0.013, 0);
53139
+ box5(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
53140
+ box5(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
53141
+ buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
53142
+ } else if (o.stock === "fold") {
53143
+ box5(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
53144
+ box5(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
53145
+ box5(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
53146
+ }
53147
+ }
53148
+
53149
+ // src/models/rifle.ts
53150
+ var PI6 = Math.PI;
53151
+ function buildReceiver(context) {
53152
+ const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
53153
+ const { M, box: box5, cylinderX, cylinderY, cylinderZ, sphere: sphere2 } = kit;
53154
+ const receiverMaterial = o.tan ? M.tan : M.metal;
53155
+ const middle = (x0 + x1) / 2;
53156
+ if (o.round) {
53157
+ cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
53158
+ cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
53159
+ } else if (o.ar) {
53160
+ box5(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
53161
+ box5(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
53162
+ box5(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
53163
+ cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
53164
+ box5(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
53165
+ box5(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
53166
+ box5(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
53167
+ } else box5(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
53168
+ if (o.ak) {
53169
+ box5(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
53170
+ for (let i = 0; i < 3; i++) box5(group, 4e-3, hR * 0.3, wR * 0.97, M.dark, x0 + RL * 0.25 + i * 0.02, hR / 2 + 4e-3, 0);
53171
+ box5(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
53172
+ box5(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
53173
+ }
53174
+ if (o.g3) {
53175
+ cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
53176
+ box5(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
53177
+ cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
53178
+ }
53179
+ const ejectX = x1 - (o.ejectBack || 0.07);
53180
+ const ejectY = o.ar ? hR * 0.2 : by;
53181
+ box5(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
53182
+ const bolt = kit.pivot(group);
53183
+ parts.bolt = bolt;
53184
+ parts.stroke = Math.min(0.1, RL * 0.32);
53185
+ box5(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
53186
+ if (o.charge) {
53187
+ box5(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
53188
+ sphere2(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
53189
+ }
53190
+ if (o.ar) box5(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
53191
+ if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
53192
+ }
53193
+ function buildBarrel(context) {
53194
+ const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
53195
+ const { M, box: box5, cylinderX } = kit;
53196
+ heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
53197
+ if (o.jacket) {
53198
+ cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
53199
+ for (let i = 0; i < 6; i++) cylinderX(group, r * 2.15, 4e-3, M.metal, x1 + (L - x1) * (0.05 + i * 0.09), by, 0, 16);
53200
+ }
53201
+ if (o.gas) cylinderX(group, r * 0.75, (L - x1) * 0.55, M.metal, x1 + (L - x1) * 0.3, by + r * 2.2, 0, 14);
53202
+ if (o.brake) {
53203
+ const kind = o.brake === 2 ? 2 : 1;
53204
+ recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
53205
+ } else if (!o.noFH) {
53206
+ recordFitting(parts, "muzzle_default", group, () => {
53207
+ heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
53208
+ for (let i = 0; i < 4; i++) {
53209
+ const angle = i * PI6 / 2 + PI6 / 4;
53210
+ box5(group, 0.026, 25e-4, 4e-3, M.hole, L - 0.02, by + Math.cos(angle) * r * 1.42, Math.sin(angle) * r * 1.42, 0, 0, angle);
53211
+ }
53212
+ });
53213
+ }
53214
+ cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
53215
+ }
53216
+ var familyOf = (o) => {
53217
+ if (o.mag === "belt") return "belt";
53218
+ if (o.pump) return "pump";
53219
+ if (o.lever) return "lever";
53220
+ if (o.tube) return "semi12";
53221
+ if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
53222
+ return o.mag === "internal" ? "semiint" : "rifle";
53223
+ };
53224
+ function buildRifle(kit, options) {
53225
+ const { M } = kit;
53226
+ const group = kit.group();
53227
+ const parts = { family: "rifle", glow: [] };
53228
+ const length2 = (options.L ?? 0) / 1e3;
53229
+ const barrelLength = (options.B ?? 0) / 1e3;
53230
+ const isTraditional = options.stock === "trad" || options.stock === "straight";
53231
+ const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length2));
53232
+ const receiverStart = stockLength;
53233
+ const receiverEnd = Math.max(receiverStart + 0.12, length2 - barrelLength * 0.92);
53234
+ const receiverHeight = options.hR || 0.06;
53235
+ const context = {
53236
+ kit,
53237
+ group,
53238
+ parts,
53239
+ options,
53240
+ furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
53241
+ isTraditional,
53242
+ length: length2,
53243
+ barrelLength,
53244
+ stockLength,
53245
+ receiverStart,
53246
+ receiverEnd,
53247
+ receiverHeight,
53248
+ receiverWidth: options.wR || 0.034,
53249
+ barrelRadius: options.br || 85e-4,
53250
+ barrelY: receiverHeight * 0.15,
53251
+ receiverLength: receiverEnd - receiverStart
53252
+ };
53253
+ buildReceiver(context);
53254
+ buildBarrel(context);
53255
+ buildForend(context);
53256
+ const gripX = buildGrip(context);
53257
+ const magazineX = buildMagazine(context, gripX);
53258
+ parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
53259
+ if (context.isTraditional) buildStock(context, magazineX);
53260
+ buildFittings(context, gripX);
53261
+ const hasHandguard = !context.isTraditional && options.hg !== "none";
53262
+ parts.frames = {
53263
+ length: length2,
53264
+ barrelY: context.barrelY,
53265
+ barrelRadius: context.barrelRadius,
53266
+ receiverStart,
53267
+ receiverEnd,
53268
+ receiverHeight,
53269
+ receiverWidth: context.receiverWidth,
53270
+ handguardEnd: receiverEnd + (length2 - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
53271
+ magazineX,
53272
+ gripX,
53273
+ hasRail: Boolean(options.rail)
53274
+ };
53275
+ const { barrelY: by, receiverWidth: wR } = context;
53276
+ const ejectX = receiverEnd - (options.ejectBack || 0.07);
53277
+ const ejectY = options.ar ? receiverHeight * 0.2 : by;
53278
+ parts.muzzle = kit.mark(group, length2 + 0.01, by, 0);
53279
+ parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
53280
+ parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
53281
+ parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
53282
+ parts.family = familyOf(options);
53283
+ return { group, parts };
53284
+ }
53285
+
53286
+ // src/models/break-launcher.ts
53287
+ function buildBreakLauncher(kit, options) {
53288
+ const built2 = buildRifle(kit, options);
53289
+ const { group, parts } = built2;
53290
+ const frames = parts.frames;
53291
+ if (!frames) return built2;
53292
+ const hingeY = frames.barrelY - frames.barrelRadius;
53293
+ const barrels = kit.pivot(group, frames.receiverEnd, hingeY, 0);
53294
+ group.updateMatrixWorld(true);
53295
+ const centre = new kit.THREE.Vector3();
53296
+ for (const child of [...group.children]) {
53297
+ if (child === barrels) continue;
53298
+ const mesh3 = child;
53299
+ const isAhead = mesh3.isMesh ? new kit.THREE.Box3().setFromObject(mesh3).getCenter(centre).x > frames.receiverEnd : child === parts.muzzle;
53300
+ if (isAhead) barrels.attach(child);
53301
+ }
53302
+ parts.barrels = barrels;
53303
+ parts.breech = [kit.mark(barrels, 4e-3, frames.barrelY - hingeY, 0)];
53304
+ parts.eject = parts.breech[0];
53305
+ parts.family = "break";
53306
+ return built2;
53307
+ }
53308
+
51842
53309
  // src/models/attachments/mount-frames.ts
51843
53310
  var DEFAULT_BARREL_RADIUS = 85e-4;
51844
53311
  var DEFAULT_RECEIVER_WIDTH = 0.034;
@@ -51913,58 +53380,6 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51913
53380
  }
51914
53381
  }
51915
53382
 
51916
- // src/models/details.ts
51917
- var PI3 = Math.PI;
51918
- function rail(kit, parent, x, length2, y, z, width, rotationX = 0) {
51919
- const { M, box: box5 } = kit;
51920
- const pivot = kit.pivot(parent, 0, y, z);
51921
- pivot.rotation.x = rotationX;
51922
- box5(pivot, length2, 5e-3, width, M.dark, x + length2 / 2, 0, 0);
51923
- const slots = Math.floor(length2 / 95e-4);
51924
- for (let i = 0; i < slots; i++) box5(pivot, 52e-4, 35e-4, width * 1.12, M.metal, x + 6e-3 + i * 95e-4, 35e-4, 0);
51925
- return pivot;
51926
- }
51927
- function vents(kit, parent, x, length2, y, z, count, height) {
51928
- for (let i = 0; i < count; i++) kit.box(parent, length2 / count * 0.45, height, 3e-3, kit.M.hole, x + (i + 0.5) * length2 / count, y, z);
51929
- }
51930
- function scope(kit, parent, kind, cx, cy, cz = 0) {
51931
- const { M, box: box5, cylinderX, cylinderY, cylinderZ, THREE } = kit;
51932
- if (kind === 1) {
51933
- box5(parent, 0.044, 0.01, 0.028, M.dark, cx, cy + 5e-3, cz);
51934
- cylinderX(parent, 0.0155, 0.034, M.dark, cx + 4e-3, cy + 0.026, cz, 18);
51935
- cylinderX(parent, 0.0125, 2e-3, M.lens, cx + 0.022, cy + 0.026, cz, 18);
51936
- cylinderY(parent, 4e-3, 6e-3, M.dark, cx + 4e-3, cy + 0.044, cz, 10);
51937
- return;
51938
- }
51939
- if (kind === 2) {
51940
- box5(parent, 0.07, 0.012, 0.026, M.dark, cx, cy + 6e-3, cz);
51941
- cylinderX(parent, 0.016, 0.1, M.dark, cx - 0.01, cy + 0.032, cz, 18);
51942
- cylinderX(parent, 0.016, 0.035, M.dark, cx + 0.055, cy + 0.032, cz, 18, 0.02);
51943
- cylinderX(parent, 0.017, 2e-3, M.lens, cx + 0.073, cy + 0.032, cz, 18);
51944
- cylinderY(parent, 7e-3, 0.012, M.dark, cx, cy + 0.052, cz, 12);
51945
- box5(parent, 0.03, 0.01, 0.012, M.dark, cx - 0.01, cy + 0.054, cz);
51946
- return;
51947
- }
51948
- const sleeve = kind === 4 ? 0.2 : 0.32;
51949
- cylinderX(parent, 0.0135, sleeve * 0.55, M.dark, cx, cy + 0.045, cz, 18);
51950
- cylinderX(parent, 0.0135, sleeve * 0.25, M.dark, cx + sleeve * 0.36, cy + 0.045, cz, 18, 0.026);
51951
- cylinderX(parent, 0.026, 0.05, M.dark, cx + sleeve * 0.5, cy + 0.045, cz, 18);
51952
- cylinderX(parent, 0.024, 2e-3, M.lens, cx + sleeve * 0.5 + 0.026, cy + 0.045, cz, 18);
51953
- cylinderX(parent, 0.019, 0.06, M.dark, cx - sleeve * 0.38, cy + 0.045, cz, 18, 0.0135);
51954
- cylinderX(parent, 0.019, 0.035, M.rubber, cx - sleeve * 0.38 - 0.045, cy + 0.045, cz, 18);
51955
- cylinderX(parent, 0.017, 2e-3, M.lens, cx - sleeve * 0.38 - 0.063, cy + 0.045, cz, 18);
51956
- cylinderY(parent, 0.01, 0.022, M.dark, cx, cy + 0.066, cz, 14);
51957
- cylinderY(parent, 0.011, 4e-3, M.metal, cx, cy + 0.078, cz, 14);
51958
- cylinderZ(parent, 0.01, 0.022, M.dark, cx, cy + 0.045, cz + 0.022, 14);
51959
- for (const offset of [-0.07, 0.07]) {
51960
- const ring3 = kit.torus(parent, new THREE.TorusGeometry(0.0155, 3e-3, 8, 20), M.dark, cx + offset, cy + 0.045, cz);
51961
- ring3.rotation.y = PI3 / 2;
51962
- box5(parent, 0.016, 0.03, 0.022, M.dark, cx + offset, cy + 0.018, cz);
51963
- }
51964
- }
51965
- var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
51966
- var swivel = (kit, parent, x, y) => void kit.torus(parent, new kit.THREE.TorusGeometry(6e-3, 15e-4, 6, 12), kit.M.metal, x, y, 0);
51967
-
51968
53383
  // src/models/bullpup.ts
51969
53384
  function buildBullpup(kit, options) {
51970
53385
  const { M, box: box5, cylinderX } = kit;
@@ -52090,7 +53505,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52090
53505
  }
52091
53506
 
52092
53507
  // src/models/launcher-rounds.ts
52093
- var PI4 = Math.PI;
53508
+ var PI7 = Math.PI;
52094
53509
  function buildRocketGrenade(kit) {
52095
53510
  const { M, cylinderX } = kit;
52096
53511
  const round5 = kit.group();
@@ -52120,7 +53535,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52120
53535
  cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
52121
53536
  for (let i = 0; i < 4; i++) {
52122
53537
  const fin = box5(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
52123
- fin.rotation.x = i * PI4 / 4;
53538
+ fin.rotation.x = i * PI7 / 4;
52124
53539
  }
52125
53540
  return rocket;
52126
53541
  }
@@ -52258,7 +53673,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52258
53673
  }
52259
53674
 
52260
53675
  // src/models/m134.ts
52261
- var PI5 = Math.PI;
53676
+ var PI8 = Math.PI;
52262
53677
  var BARRELS = 6;
52263
53678
  var CLUSTER_RADIUS = 0.03;
52264
53679
  var BARREL_RADIUS = 78e-4;
@@ -52270,7 +53685,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52270
53685
  const length2 = BARREL_END - 0.3;
52271
53686
  let heated;
52272
53687
  for (let i = 0; i < BARRELS; i++) {
52273
- const angle = i * 2 * PI5 / BARRELS;
53688
+ const angle = i * 2 * PI8 / BARRELS;
52274
53689
  const y = Math.cos(angle) * CLUSTER_RADIUS;
52275
53690
  const z = Math.sin(angle) * CLUSTER_RADIUS;
52276
53691
  const barrel = cylinderX(barrels, BARREL_RADIUS, length2, M.metal, 0.3 + length2 / 2, y, z, 14);
@@ -52419,7 +53834,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52419
53834
  }
52420
53835
 
52421
53836
  // src/models/pistol-slide.ts
52422
- var PI6 = Math.PI;
53837
+ var PI9 = Math.PI;
52423
53838
  function buildSlide(kit, slide2, parts, options, style, size, metal2) {
52424
53839
  const { M, box: box5, cylinderX } = kit;
52425
53840
  const { length: L, width: w } = size;
@@ -52447,8 +53862,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52447
53862
  if (style.tri) {
52448
53863
  const tube2 = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
52449
53864
  tube2.castShadow = tube2.receiveShadow = true;
52450
- tube2.rotation.set(0, 0, -PI6 / 2);
52451
- tube2.rotateY(PI6 / 6);
53865
+ tube2.rotation.set(0, 0, -PI9 / 2);
53866
+ tube2.rotateY(PI9 / 6);
52452
53867
  tube2.position.set(L * 0.7, slideHeight * 0.08, 0);
52453
53868
  slide2.add(tube2);
52454
53869
  heatify(parts.glow, tube2);
@@ -52500,7 +53915,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52500
53915
  };
52501
53916
 
52502
53917
  // src/models/pistol.ts
52503
- var PI7 = Math.PI;
53918
+ var PI10 = Math.PI;
52504
53919
  function buildFrame2(kit, group, parts, options, style, size, materials) {
52505
53920
  const { M, box: box5, cylinderZ, sphere: sphere2 } = kit;
52506
53921
  const { length: L, height: h, width: w } = size;
@@ -52592,8 +54007,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52592
54007
  box5(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
52593
54008
  box5(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
52594
54009
  } else {
52595
- const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI7), frameMetal, gx + 0.05, ty - 0.014, 0);
52596
- guard.rotation.z = PI7;
54010
+ const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI10), frameMetal, gx + 0.05, ty - 0.014, 0);
54011
+ guard.rotation.z = PI10;
52597
54012
  guard.scale.set(1.2, 1, 1);
52598
54013
  }
52599
54014
  const isGlock = options.st === "glock" || !options.st;
@@ -52617,7 +54032,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52617
54032
  }
52618
54033
 
52619
54034
  // src/models/revolver.ts
52620
- var PI8 = Math.PI;
54035
+ var PI11 = Math.PI;
52621
54036
  var DEFAULT_CHAMBERS = 6;
52622
54037
  function buildRevolver(kit, options) {
52623
54038
  const { M, box: box5, cylinderX } = kit;
@@ -52635,12 +54050,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52635
54050
  parts.cylinder = cylinder2;
52636
54051
  cylinderX(cylinder2, 0.0215, 0.042, metal2, 0, 0, 0, 24);
52637
54052
  for (let i = 0; i < chambers; i++) {
52638
- const angle = i * 2 * PI8 / chambers;
54053
+ const angle = i * 2 * PI11 / chambers;
52639
54054
  box5(cylinder2, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
52640
54055
  }
52641
54056
  parts.chambers = [];
52642
54057
  for (let i = 0; i < chambers; i++) {
52643
- const angle = i * 2 * PI8 / chambers + PI8 / chambers;
54058
+ const angle = i * 2 * PI11 / chambers + PI11 / chambers;
52644
54059
  parts.chambers.push(kit.mark(cylinder2, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
52645
54060
  cylinderX(cylinder2, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
52646
54061
  }
@@ -52665,434 +54080,6 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
52665
54080
  return { group, parts };
52666
54081
  }
52667
54082
 
52668
- // src/models/attachments/muzzle-brake.ts
52669
- function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
52670
- const { M, box: box5, cylinderX } = kit;
52671
- const scale = kind === 2 ? 3.2 : 2;
52672
- const length2 = kind === 2 ? 0.12 : 0.06;
52673
- heat(cylinderX(parent, barrelRadius * scale, length2, M.dark, x - length2 / 2, y, 0, kind === 2 ? 8 : 16));
52674
- for (let i = 0; i < 3; i++) box5(parent, length2 * 0.16, barrelRadius * scale * 1.3, barrelRadius * scale * 2.2, M.hole, x - length2 * 0.25 - i * length2 * 0.25, y, 0);
52675
- }
52676
-
52677
- // src/models/capture.ts
52678
- function captured(parent, draw2) {
52679
- const before = parent.children.length;
52680
- draw2();
52681
- return parent.children.slice(before);
52682
- }
52683
- function recordFitting(parts, id, parent, draw2) {
52684
- parts.fittings = { ...parts.fittings, [id]: captured(parent, draw2) };
52685
- }
52686
-
52687
- // src/models/attachments/bipod.ts
52688
- function buildBipod(kit, parent, x, y) {
52689
- const { M, box: box5, cylinderX } = kit;
52690
- cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
52691
- for (const side of [-1, 1]) {
52692
- const leg = kit.pivot(parent, x, y, side * 0.012);
52693
- leg.rotation.set(side * 0.15, 0, -0.35);
52694
- box5(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
52695
- box5(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
52696
- }
52697
- }
52698
- var buildBipodModel = ({ kit }) => {
52699
- const group = kit.group();
52700
- buildBipod(kit, group, 0, 0);
52701
- return group;
52702
- };
52703
-
52704
- // src/models/rifle-fittings.ts
52705
- var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
52706
- function buildFittings(context, gripX) {
52707
- const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
52708
- const { M, box: box5, cylinderX, cylinderZ, sphere: sphere2 } = kit;
52709
- const middle = (x0 + x1) / 2;
52710
- if (o.scope) recordFitting(parts, SCOPE_IDS[o.scope] ?? "scope", group, () => scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0));
52711
- if (o.carry) {
52712
- box5(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
52713
- box5(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
52714
- box5(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
52715
- }
52716
- if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
52717
- if (o.bolt) {
52718
- const handle = kit.pivot(group, x1 - 0.045, by, 0);
52719
- parts.boltHandle = handle;
52720
- parts.boltHandleRest = handle.position.x;
52721
- cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
52722
- sphere2(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
52723
- cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
52724
- }
52725
- if (o.lever) {
52726
- const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
52727
- parts.lever = lever;
52728
- box5(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
52729
- box5(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
52730
- box5(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
52731
- box5(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
52732
- box5(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
52733
- }
52734
- if (o.tube) {
52735
- const tubeLength = (L - x1) * (o.tubeLen || 0.8);
52736
- cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
52737
- cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
52738
- }
52739
- if (o.pump) {
52740
- const pump = kit.pivot(group);
52741
- parts.pump = pump;
52742
- box5(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
52743
- for (let i = 0; i < 8; i++) box5(pump, 6e-3, 0.044, 0.048, M.dark, x1 + 0.07 + i * 0.02, by - 0.022, 0);
52744
- }
52745
- if (o.heat) {
52746
- cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
52747
- for (let i = 0; i < 10; i++) box5(group, 8e-3, r * 1.5, 4e-3, M.hole, x1 + (L - x1) * (0.1 + i * 0.06), by + r * 0.8, r * 1.5);
52748
- }
52749
- if (o.bayo) box5(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
52750
- if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
52751
- }
52752
-
52753
- // src/models/rifle-forend.ts
52754
- var PI9 = Math.PI;
52755
- function buildForend(context) {
52756
- const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
52757
- const { M, box: box5 } = kit;
52758
- const hasLongScope = (o.scope ?? 0) >= 3;
52759
- if (!context.isTraditional && o.hg !== "none") {
52760
- const handguardLength = (L - x1) * (o.hgFrac || 0.5);
52761
- const hx = x1 + handguardLength / 2;
52762
- const hh = hR * 0.8;
52763
- if (o.hg === "rail") {
52764
- box5(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
52765
- rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
52766
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI9 / 2);
52767
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI9 / 2);
52768
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI9);
52769
- } else {
52770
- const material = o.hg === "wood" ? M.wood : furn;
52771
- if (o.ak) {
52772
- box5(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
52773
- box5(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
52774
- } else {
52775
- box5(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
52776
- vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, wR * 0.54 + 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
52777
- vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, -wR * 0.54 - 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
52778
- }
52779
- }
52780
- if (!hasLongScope && !o.rail && o.ak) {
52781
- box5(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
52782
- box5(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
52783
- }
52784
- }
52785
- if (hasLongScope) return;
52786
- const sightX = L - (o.ak ? 0.06 : 0.08);
52787
- if (o.ak) {
52788
- box5(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
52789
- box5(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
52790
- box5(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
52791
- box5(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
52792
- } else if (!o.rail || o.carry) {
52793
- box5(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
52794
- box5(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
52795
- } else box5(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
52796
- }
52797
-
52798
- // src/models/attachments/belt-box.ts
52799
- function buildBeltBox(kit, belt) {
52800
- const { M, box: box5, cylinderX } = kit;
52801
- box5(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
52802
- box5(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
52803
- for (let i = 0; i < 7; i++) {
52804
- cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
52805
- box5(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
52806
- }
52807
- }
52808
-
52809
- // src/models/attachments/drum.ts
52810
- function buildDrum(kit, magazine) {
52811
- const { M, box: box5, cylinderZ } = kit;
52812
- cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
52813
- cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
52814
- box5(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
52815
- }
52816
-
52817
- // src/models/helical-magazine.ts
52818
- var PI10 = Math.PI;
52819
- var LENGTH = 0.26;
52820
- var RADIUS = 0.026;
52821
- var TURNS = 11;
52822
- function buildHelicalMagazine(context) {
52823
- const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
52824
- const { M, cylinderX, box: box5 } = kit;
52825
- const x = receiverEnd + LENGTH / 2 + 0.02;
52826
- const y = barrelY - barrelRadius - RADIUS - 0.012;
52827
- const magazine = kit.pivot(group, x, y, 0);
52828
- parts.mag = magazine;
52829
- cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
52830
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
52831
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
52832
- for (let i = 0; i < TURNS; i++) box5(magazine, 0.012, RADIUS * 2.15, 0.014, M.metal, -LENGTH / 2 + (i + 0.5) * LENGTH / TURNS, 0, 0, 0, 0, i * PI10 / 4);
52833
- box5(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
52834
- parts.magDrop = 0.16;
52835
- return x;
52836
- }
52837
-
52838
- // src/models/rifle-controls.ts
52839
- function buildGrip(context) {
52840
- const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
52841
- const { M, box: box5 } = kit;
52842
- const gx = x0 + (o.gripAt || 0.07);
52843
- if (!context.isTraditional && o.grip !== "none") {
52844
- const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
52845
- grip.rotation.z = -0.3;
52846
- parts.grip = grip;
52847
- box5(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
52848
- for (let i = 0; i < 3; i++) box5(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
52849
- box5(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
52850
- }
52851
- box5(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
52852
- box5(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
52853
- parts.trigger = box5(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
52854
- if (o.sel) parts.safety = box5(group, 0.016, 5e-3, 3e-3, M.dark, gx + 5e-3, -hR * 0.15, -wR / 2 - 2e-3, 0.4);
52855
- return gx;
52856
- }
52857
- function buildMagazine(context, gx) {
52858
- const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
52859
- const { M, box: box5 } = kit;
52860
- if (o.mag === "helical") return buildHelicalMagazine(context);
52861
- const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
52862
- const scale = o.magLen || 1;
52863
- const magazine = kit.pivot(group, mx, -hR / 2, 0);
52864
- parts.mag = magazine;
52865
- const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
52866
- if (o.mag === "curved") {
52867
- let px = 0;
52868
- let py = 0;
52869
- const material = finish ?? M.metal;
52870
- for (const angle of [0.06, 0.22, 0.38]) {
52871
- const length2 = 0.07 * scale;
52872
- const cx = px + Math.sin(angle) * length2 / 2;
52873
- const cy = py - Math.cos(angle) * length2 / 2;
52874
- box5(magazine, 0.052, length2 + 4e-3, 0.024, material, cx, cy, 0, angle);
52875
- box5(magazine, 0.04, length2 * 0.8, 0.026, M.dark, cx, cy, 0, angle);
52876
- px += Math.sin(angle) * length2;
52877
- py -= Math.cos(angle) * length2;
52878
- }
52879
- box5(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
52880
- parts.magDrop = 0.07 * scale * 3 + 0.04;
52881
- } else if (o.mag === "box") {
52882
- const length2 = 0.13 * scale;
52883
- const curve = o.curve || 0;
52884
- const segment = length2 / 3;
52885
- const material = finish ?? M.dark;
52886
- let px = 0;
52887
- let py = 0;
52888
- let angle = 0.08;
52889
- for (let i = 0; i < 3; i++) {
52890
- angle = 0.08 + curve * i;
52891
- const cx = px + Math.sin(angle) * segment / 2;
52892
- const cy = py - Math.cos(angle) * segment / 2;
52893
- box5(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
52894
- box5(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
52895
- px += Math.sin(angle) * segment;
52896
- py -= Math.cos(angle) * segment;
52897
- }
52898
- box5(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
52899
- parts.magDrop = length2 + 0.03;
52900
- } else if (o.mag === "drum") {
52901
- buildDrum(kit, magazine);
52902
- parts.magDrop = 0.2;
52903
- } else if (o.mag === "internal") {
52904
- box5(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
52905
- parts.mag = null;
52906
- } else parts.mag = null;
52907
- if (o.mag === "belt") {
52908
- const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
52909
- parts.belt = belt;
52910
- buildBeltBox(kit, belt);
52911
- const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
52912
- parts.cover = cover;
52913
- box5(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
52914
- box5(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
52915
- }
52916
- return mx;
52917
- }
52918
-
52919
- // src/models/rifle-stock.ts
52920
- function buildStock(context, magazineX) {
52921
- const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
52922
- const { M, box: box5, cylinderX } = kit;
52923
- if (context.isTraditional) {
52924
- buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
52925
- box5(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
52926
- box5(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
52927
- box5(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
52928
- const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
52929
- if (forendLength > 0) box5(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
52930
- if (o.stock === "straight") box5(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
52931
- box5(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
52932
- swivel(kit, group, 0.2, -0.08);
52933
- swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
52934
- if (o.bands) for (const fraction of [0.35, 0.75]) cylinderX(group, 0.024, 8e-3, M.metal, x1 + (L - x1) * fraction * (o.fore || 0.62), -4e-3, 0, 16);
52935
- } else if (o.stock === "fixed" || o.stock === "wood") {
52936
- box5(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
52937
- box5(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
52938
- buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
52939
- if (o.stock === "wood") box5(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
52940
- swivel(kit, group, 0.08, -0.09);
52941
- } else if (o.stock === "skeleton") {
52942
- box5(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
52943
- box5(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
52944
- box5(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
52945
- buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
52946
- } else if (o.stock === "retract") {
52947
- cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
52948
- for (let i = 0; i < 5; i++) box5(group, 3e-3, 3e-3, 8e-3, M.dark, S * 0.2 + i * 0.015, -0.013, 0);
52949
- box5(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
52950
- box5(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
52951
- buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
52952
- } else if (o.stock === "fold") {
52953
- box5(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
52954
- box5(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
52955
- box5(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
52956
- }
52957
- }
52958
-
52959
- // src/models/rifle.ts
52960
- var PI11 = Math.PI;
52961
- function buildReceiver(context) {
52962
- const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
52963
- const { M, box: box5, cylinderX, cylinderY, cylinderZ, sphere: sphere2 } = kit;
52964
- const receiverMaterial = o.tan ? M.tan : M.metal;
52965
- const middle = (x0 + x1) / 2;
52966
- if (o.round) {
52967
- cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
52968
- cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
52969
- } else if (o.ar) {
52970
- box5(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
52971
- box5(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
52972
- box5(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
52973
- cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
52974
- box5(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
52975
- box5(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
52976
- box5(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
52977
- } else box5(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
52978
- if (o.ak) {
52979
- box5(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
52980
- for (let i = 0; i < 3; i++) box5(group, 4e-3, hR * 0.3, wR * 0.97, M.dark, x0 + RL * 0.25 + i * 0.02, hR / 2 + 4e-3, 0);
52981
- box5(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
52982
- box5(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
52983
- }
52984
- if (o.g3) {
52985
- cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
52986
- box5(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
52987
- cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
52988
- }
52989
- const ejectX = x1 - (o.ejectBack || 0.07);
52990
- const ejectY = o.ar ? hR * 0.2 : by;
52991
- box5(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
52992
- const bolt = kit.pivot(group);
52993
- parts.bolt = bolt;
52994
- parts.stroke = Math.min(0.1, RL * 0.32);
52995
- box5(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
52996
- if (o.charge) {
52997
- box5(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
52998
- sphere2(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
52999
- }
53000
- if (o.ar) box5(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
53001
- if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
53002
- }
53003
- function buildBarrel(context) {
53004
- const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
53005
- const { M, box: box5, cylinderX } = kit;
53006
- heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
53007
- if (o.jacket) {
53008
- cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
53009
- for (let i = 0; i < 6; i++) cylinderX(group, r * 2.15, 4e-3, M.metal, x1 + (L - x1) * (0.05 + i * 0.09), by, 0, 16);
53010
- }
53011
- if (o.gas) cylinderX(group, r * 0.75, (L - x1) * 0.55, M.metal, x1 + (L - x1) * 0.3, by + r * 2.2, 0, 14);
53012
- if (o.brake) {
53013
- const kind = o.brake === 2 ? 2 : 1;
53014
- recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
53015
- } else if (!o.noFH) {
53016
- recordFitting(parts, "muzzle_default", group, () => {
53017
- heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
53018
- for (let i = 0; i < 4; i++) {
53019
- const angle = i * PI11 / 2 + PI11 / 4;
53020
- box5(group, 0.026, 25e-4, 4e-3, M.hole, L - 0.02, by + Math.cos(angle) * r * 1.42, Math.sin(angle) * r * 1.42, 0, 0, angle);
53021
- }
53022
- });
53023
- }
53024
- cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
53025
- }
53026
- var familyOf = (o) => {
53027
- if (o.mag === "belt") return "belt";
53028
- if (o.pump) return "pump";
53029
- if (o.lever) return "lever";
53030
- if (o.tube) return "semi12";
53031
- if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
53032
- return o.mag === "internal" ? "semiint" : "rifle";
53033
- };
53034
- function buildRifle(kit, options) {
53035
- const { M } = kit;
53036
- const group = kit.group();
53037
- const parts = { family: "rifle", glow: [] };
53038
- const length2 = (options.L ?? 0) / 1e3;
53039
- const barrelLength = (options.B ?? 0) / 1e3;
53040
- const isTraditional = options.stock === "trad" || options.stock === "straight";
53041
- const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length2));
53042
- const receiverStart = stockLength;
53043
- const receiverEnd = Math.max(receiverStart + 0.12, length2 - barrelLength * 0.92);
53044
- const receiverHeight = options.hR || 0.06;
53045
- const context = {
53046
- kit,
53047
- group,
53048
- parts,
53049
- options,
53050
- furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
53051
- isTraditional,
53052
- length: length2,
53053
- barrelLength,
53054
- stockLength,
53055
- receiverStart,
53056
- receiverEnd,
53057
- receiverHeight,
53058
- receiverWidth: options.wR || 0.034,
53059
- barrelRadius: options.br || 85e-4,
53060
- barrelY: receiverHeight * 0.15,
53061
- receiverLength: receiverEnd - receiverStart
53062
- };
53063
- buildReceiver(context);
53064
- buildBarrel(context);
53065
- buildForend(context);
53066
- const gripX = buildGrip(context);
53067
- const magazineX = buildMagazine(context, gripX);
53068
- parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
53069
- if (context.isTraditional) buildStock(context, magazineX);
53070
- buildFittings(context, gripX);
53071
- const hasHandguard = !context.isTraditional && options.hg !== "none";
53072
- parts.frames = {
53073
- length: length2,
53074
- barrelY: context.barrelY,
53075
- barrelRadius: context.barrelRadius,
53076
- receiverStart,
53077
- receiverEnd,
53078
- receiverHeight,
53079
- receiverWidth: context.receiverWidth,
53080
- handguardEnd: receiverEnd + (length2 - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
53081
- magazineX,
53082
- gripX,
53083
- hasRail: Boolean(options.rail)
53084
- };
53085
- const { barrelY: by, receiverWidth: wR } = context;
53086
- const ejectX = receiverEnd - (options.ejectBack || 0.07);
53087
- const ejectY = options.ar ? receiverHeight * 0.2 : by;
53088
- parts.muzzle = kit.mark(group, length2 + 0.01, by, 0);
53089
- parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
53090
- parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
53091
- parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
53092
- parts.family = familyOf(options);
53093
- return { group, parts };
53094
- }
53095
-
53096
54083
  // src/models/index.ts
53097
54084
  var MODEL_ENTRIES = models_default2;
53098
54085
  var ARCHETYPE_MODELS = (() => {
@@ -53105,13 +54092,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
53105
54092
  }
53106
54093
  return chosen;
53107
54094
  })();
53108
- var BUILDERS3 = {
54095
+ var BUILDERS4 = {
53109
54096
  pistol: buildPistol,
53110
54097
  revolver: buildRevolver,
53111
54098
  rifle: buildRifle,
53112
54099
  bullpup: buildBullpup,
53113
54100
  p90: (kit) => buildP90(kit),
53114
54101
  break: buildBreakAction,
54102
+ breakLauncher: buildBreakLauncher,
53115
54103
  m2: (kit) => buildM2(kit),
53116
54104
  m134: (kit) => buildM134(kit),
53117
54105
  launcher: buildLauncher
@@ -53135,7 +54123,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
53135
54123
  const kit = modelKit(THREE);
53136
54124
  const options = { L: entry2.length, B: entry2.barrel, cal: entry2.caliber, ...entry2.options };
53137
54125
  if (id in ARCHETYPE_MODELS) delete options.chambers;
53138
- const built2 = BUILDERS3[entry2.builder](kit, options);
54126
+ const built2 = BUILDERS4[entry2.builder](kit, options);
53139
54127
  nameParts(built2.parts);
53140
54128
  let root;
53141
54129
  if (entry2.builder === "launcher") {
@@ -53370,9 +54358,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
53370
54358
  if (kind !== "one_by_one") events.push(cue(0.7, "move_in", { from: "chambers" }), sound2(0.7, "speedloader"));
53371
54359
  return { tweens: [turn("crane", "x", 0, 0.22, 0, -1.25), turn("crane", "x", 1.05, 0.2, -1.25, 0)], events, end: 1.3 };
53372
54360
  }
53373
- var breakReload = () => ({
54361
+ var breakReload = (chambers) => ({
53374
54362
  tweens: [turn("barrels", "z", 0, 0.2, 0, -0.5), turn("barrels", "z", 1, 0.15, -0.5, 0)],
53375
- events: [sound2(0, "barrels_open"), cue(0.25, "eject_casing", { from: "breech", down: false }), sound2(0.25, "casings_eject"), cue(0.65, "move_in", { from: "breech" }), sound2(0.65, "shell_insert"), sound2(0.8, "shell_insert"), sound2(1.13, "barrels_close")],
54363
+ events: [
54364
+ sound2(0, "barrels_open"),
54365
+ cue(0.25, "eject_casing", { from: "breech", down: false }),
54366
+ sound2(0.25, "casings_eject"),
54367
+ cue(0.65, "move_in", { from: "breech" }),
54368
+ sound2(0.65, "shell_insert"),
54369
+ ...chambers > 1 ? [sound2(0.8, "shell_insert")] : [],
54370
+ sound2(1.13, "barrels_close")
54371
+ ],
53376
54372
  end: 1.2
53377
54373
  });
53378
54374
  var beltReload = (context) => ({
@@ -53445,7 +54441,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
53445
54441
  if (family === "rocket" || family === "flame") return heavyReload(context);
53446
54442
  if (parts.mag) return magazineReload(context, kind);
53447
54443
  if (family === "revolver" && parts.crane) return revolverReload(kind);
53448
- if (family === "break" && parts.barrels) return breakReload();
54444
+ if (family === "break" && parts.barrels) return breakReload(parts.breech?.length ?? 2);
53449
54445
  if (family === "belt" && parts.cover && parts.belt) return beltReload(context);
53450
54446
  if (isTube(context)) return tubeReload(context, kind);
53451
54447
  if (family === "bolt") return boltReload(context);
@@ -55580,16 +56576,16 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
55580
56576
  if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
55581
56577
  return;
55582
56578
  }
55583
- const [hx, hy, hz] = enclosure.halfSize;
56579
+ const [hx, hy, hz2] = enclosure.halfSize;
55584
56580
  const space = {
55585
56581
  cx: enclosure.center[0],
55586
56582
  cy: enclosure.center[1],
55587
56583
  cz: enclosure.center[2],
55588
56584
  hx: Math.abs(hx),
55589
56585
  hy: Math.abs(hy),
55590
- hz: Math.abs(hz),
56586
+ hz: Math.abs(hz2),
55591
56587
  r: rotation(enclosure.orientation),
55592
- radius: Math.hypot(hx, hy, hz),
56588
+ radius: Math.hypot(hx, hy, hz2),
55593
56589
  openings: enclosure.openings ?? [],
55594
56590
  leak: Math.max(0, enclosure.leak ?? 0)
55595
56591
  };
@@ -59772,13 +60768,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
59772
60768
  function composeAfterEffects(context) {
59773
60769
  const { core, def, random, x, y, z } = context;
59774
60770
  const mark = def.mark;
59775
- if (mark.crater && mark.dent && y < 1.2) {
60771
+ const keepsMarks = !core.marklessAt?.(x, z);
60772
+ if (keepsMarks && mark.crater && mark.dent && y < 1.2) {
59776
60773
  core.emit({ type: "crater", x, z, radius: mark.crater, depth: mark.dent });
59777
60774
  core.trace?.crater?.(x, z, mark.crater, mark.dent);
59778
60775
  addCrater(core.marks, core.ground, random, { x, z, radius: mark.crater, depth: mark.dent, marks: core.layers.mark });
59779
60776
  if (core.layers.shrapnel) composeRimDebris(context);
59780
60777
  }
59781
- if (mark.scorch && y < 1.2 && core.layers.mark) {
60778
+ if (keepsMarks && mark.scorch && y < 1.2 && core.layers.mark) {
59782
60779
  core.trace?.scorch?.(x, z, mark.scorch);
59783
60780
  addScorch(core.marks, random, x, z, mark.scorch);
59784
60781
  }
@@ -61487,7 +62484,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
61487
62484
  const wind = { x: DEFAULT_WIND.x, y: DEFAULT_WIND.y, z: DEFAULT_WIND.z };
61488
62485
  if (options.wind) [wind.x, wind.y, wind.z] = options.wind;
61489
62486
  const camera2 = { x: 0, y: 1.6, z: 0 };
61490
- const layers = Object.fromEntries(VFX_LAYERS.map((layer2) => [layer2, true]));
62487
+ const layers = Object.fromEntries(VFX_LAYERS.map((layer3) => [layer3, true]));
61491
62488
  const ground = options.ground;
61492
62489
  const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
61493
62490
  const handles = { next: 0, events: 0 };
@@ -61529,6 +62526,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
61529
62526
  spawned: { puffs: 0, sparks: 0, chunks: 0, lights: 0 },
61530
62527
  clock: { time: 0, lastBlast: -1, lod: 1, forcedLod: null },
61531
62528
  groundAt,
62529
+ ...options.marklessAt ? { marklessAt: options.marklessAt } : {},
61532
62530
  emit: (request) => options.onRequest?.(request)
61533
62531
  };
61534
62532
  function definitionOf(id) {
@@ -61555,6 +62553,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
61555
62553
  return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
61556
62554
  }
61557
62555
  return {
62556
+ hostGroundAt: options.groundAt,
61558
62557
  play(id, point, playOptions = {}) {
61559
62558
  if (!(id in effects)) return null;
61560
62559
  const seed = playOptions.seed ?? (randomMode === "shared" ? void 0 : Math.imul(++handles.events, AUTO_SEED) + (options.seed ?? 0));
@@ -61617,10 +62616,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
61617
62616
  const fires = core.fires;
61618
62617
  for (let index = fires.items.length - 1; index >= 0; index--) if (fires.items[index].handle === handle) fires.removeAt(index);
61619
62618
  },
61620
- setLayer(layer2, on) {
61621
- layers[layer2] = on;
62619
+ setLayer(layer3, on) {
62620
+ layers[layer3] = on;
61622
62621
  },
61623
- layer: (layer2) => layers[layer2],
62622
+ layer: (layer3) => layers[layer3],
61624
62623
  setCamera(position) {
61625
62624
  camera2.x = position[0];
61626
62625
  camera2.y = position[1];
@@ -61707,7 +62706,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
61707
62706
  };
61708
62707
  }
61709
62708
 
61710
- // ../game-vfx-three/dist/chunk-2MZDWTHX.js
62709
+ // ../game-vfx-three/dist/chunk-CPRCWTSQ.js
61711
62710
  var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
61712
62711
  void main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);
61713
62712
  vec3 wp=aP.xyz+(R*position.x+Up*position.y)*aP.w;vU=position.xy;vC=aC;gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);}`;
@@ -62147,7 +63146,11 @@ void main(){
62147
63146
  const dirt = texture(paint(make3, (context, size) => drawDirt(context, size, random)));
62148
63147
  return { scorch, dirt };
62149
63148
  }
62150
- var MAX_CONFORM_STEPS = 48;
63149
+ var FLAT_STEPS_PER_METRE = 8;
63150
+ var MAX_FLAT_STEPS = 48;
63151
+ var DUG_STEPS_PER_METRE = 20;
63152
+ var MAX_DUG_STEPS = 64;
63153
+ var FLAT_TOLERANCE = 2e-3;
62151
63154
  var MIN_CONFORM_STEPS = 16;
62152
63155
  var BLOOD_GROW_RATE = 1.2;
62153
63156
  var BLOOD_START_GROWTH = 0.05;
@@ -62230,9 +63233,21 @@ void main(){
62230
63233
  spareGeometries.push(oldest.mesh.geometry);
62231
63234
  if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
62232
63235
  }
63236
+ const heightAt = options.heightAt ?? ((x, z) => options.ground?.heightAt(x, z) ?? 0);
63237
+ function groundDipsUnder(x, z, radius) {
63238
+ const centre = heightAt(x, z);
63239
+ for (const share of [0.25, 0.5, 0.75]) {
63240
+ for (let ray = 0; ray < 8; ray++) {
63241
+ const angle = ray / 8 * Math.PI * 2;
63242
+ if (Math.abs(heightAt(x + Math.cos(angle) * radius * share, z + Math.sin(angle) * radius * share) - centre) > FLAT_TOLERANCE) return true;
63243
+ }
63244
+ }
63245
+ return false;
63246
+ }
62233
63247
  function conform(mark, material) {
62234
63248
  const radius = mark.radius;
62235
- const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
63249
+ const dug = options.heightAt !== void 0 && groundDipsUnder(mark.x, mark.z, radius);
63250
+ const steps = Math.max(MIN_CONFORM_STEPS, Math.min(dug ? MAX_DUG_STEPS : MAX_FLAT_STEPS, Math.round(radius * (dug ? DUG_STEPS_PER_METRE : FLAT_STEPS_PER_METRE))));
62236
63251
  if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
62237
63252
  const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
62238
63253
  const grid = geometry.attributes.position;
@@ -62244,7 +63259,7 @@ void main(){
62244
63259
  const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
62245
63260
  const worldX = mark.x + localX * cos2 - localZ * sin2;
62246
63261
  const worldZ = mark.z + localX * sin2 + localZ * cos2;
62247
- grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
63262
+ grid.setXYZ(index, worldX, heightAt(worldX, worldZ) + mark.lift, worldZ);
62248
63263
  }
62249
63264
  grid.needsUpdate = true;
62250
63265
  geometry.computeVertexNormals();
@@ -62442,7 +63457,7 @@ void main(){
62442
63457
  };
62443
63458
  }
62444
63459
  var clamp5 = (value, min2, max2) => value < min2 ? min2 : value > max2 ? max2 : value;
62445
- function layer(container, name, blend) {
63460
+ function layer2(container, name, blend) {
62446
63461
  const element = container.ownerDocument.createElement("div");
62447
63462
  element.dataset.vfxOverlay = name;
62448
63463
  Object.assign(element.style, { position: "absolute", inset: "0", pointerEvents: "none", opacity: "0" });
@@ -62451,12 +63466,12 @@ void main(){
62451
63466
  return element;
62452
63467
  }
62453
63468
  function createScreenOverlay(container) {
62454
- const flash2 = layer(container, "flash", "screen");
63469
+ const flash2 = layer2(container, "flash", "screen");
62455
63470
  flash2.style.background = "#fff";
62456
- const white = layer(container, "white");
62457
- const red = layer(container, "red");
62458
- const sides = layer(container, "sides");
62459
- const gas = layer(container, "gas");
63471
+ const white = layer2(container, "white");
63472
+ const red = layer2(container, "red");
63473
+ const sides = layer2(container, "sides");
63474
+ const gas = layer2(container, "gas");
62460
63475
  const elements = [flash2, white, red, sides, gas];
62461
63476
  return {
62462
63477
  update(state) {
@@ -62916,7 +63931,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
62916
63931
  decalLayer(THREE, engine.decals),
62917
63932
  ringLayer(THREE, engine.rings)
62918
63933
  ];
62919
- for (const layer2 of layers) group.add(layer2.object);
63934
+ for (const layer22 of layers) group.add(layer22.object);
62920
63935
  const meshes = createMeshLayer(THREE, engine.meshes);
62921
63936
  group.add(meshes.group);
62922
63937
  const lights = budget ? budgetedLights(engine, lightScale, budget) : legacyLights(THREE, engine, lightScale, group);
@@ -62924,13 +63939,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
62924
63939
  group,
62925
63940
  sync() {
62926
63941
  let drawCalls = 0;
62927
- for (const layer2 of layers) if (layer2.sync()) drawCalls++;
63942
+ for (const layer22 of layers) if (layer22.sync()) drawCalls++;
62928
63943
  drawCalls += meshes.sync();
62929
63944
  lights.sync();
62930
63945
  return drawCalls;
62931
63946
  },
62932
63947
  dispose() {
62933
- for (const layer2 of layers) layer2.dispose();
63948
+ for (const layer22 of layers) layer22.dispose();
62934
63949
  meshes.dispose();
62935
63950
  lights.dispose();
62936
63951
  group.clear();
@@ -63034,7 +64049,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63034
64049
  const shocks = createShockLayer(THREE, pools.shocks);
63035
64050
  const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
63036
64051
  const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI, options.lights);
63037
- const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
64052
+ const marks = createMarkLayer(THREE, { ground: pools.ground, ...world.hostGroundAt ? { heightAt: world.hostGroundAt } : {}, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
63038
64053
  root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
63039
64054
  const stopMarks = world.onMarks((event) => marks.handle(event));
63040
64055
  const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
@@ -63167,7 +64182,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63167
64182
  stopMarks();
63168
64183
  overlay?.dispose();
63169
64184
  workshopLayers?.dispose();
63170
- for (const layer2 of [puffs, sparks, glows, chunks, shocks, lights, marks, sprites]) layer2.dispose();
64185
+ for (const layer22 of [puffs, sparks, glows, chunks, shocks, lights, marks, sprites]) layer22.dispose();
63171
64186
  rockGeometry.dispose();
63172
64187
  plateGeometry.dispose();
63173
64188
  options.scene.remove(root);
@@ -63201,6 +64216,38 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63201
64216
  };
63202
64217
  }
63203
64218
 
64219
+ // src/fx/view-hold.ts
64220
+ var VIEW_HOLD = {
64221
+ /** The right hand's grip, from the eye. */
64222
+ grip: [0.2, -0.2, -0.3],
64223
+ /** A handgun's grip: higher and nearer the middle, the arms out in front. */
64224
+ handgunGrip: [0.13, -0.15, -0.33],
64225
+ /** A rocket launcher's grip: its tube rides on the shoulder, so the hands are higher. */
64226
+ launcherGrip: [0.2, -0.14, -0.3],
64227
+ /** A gun with no grip anchor: the rear of its bore, from the eye. */
64228
+ rear: [0.2, -0.24, -0.12]
64229
+ };
64230
+ var scratch2 = { grip: null };
64231
+ function placeInView(placement) {
64232
+ const { THREE, hand, holder, camera: camera2, right } = placement;
64233
+ holder.scale.setScalar(1);
64234
+ hand.position.copy(camera2.position);
64235
+ hand.quaternion.copy(camera2.quaternion);
64236
+ hand.scale.setScalar(1);
64237
+ holder.position.set(0, 0, 0);
64238
+ hand.updateMatrixWorld(true);
64239
+ if (!right) {
64240
+ holder.position.set(VIEW_HOLD.rear[0], VIEW_HOLD.rear[1], VIEW_HOLD.rear[2] + placement.rear);
64241
+ hand.updateMatrixWorld(true);
64242
+ return;
64243
+ }
64244
+ scratch2.grip ?? (scratch2.grip = new THREE.Vector3());
64245
+ const grip = hand.worldToLocal(right.part.localToWorld(scratch2.grip.copy(right.offset)));
64246
+ const [x, y, z] = placement.kind === "handgun" ? VIEW_HOLD.handgunGrip : placement.kind === "launcher" ? VIEW_HOLD.launcherGrip : VIEW_HOLD.grip;
64247
+ holder.position.set(x - grip.x, y - grip.y, z - grip.z);
64248
+ hand.updateMatrixWorld(true);
64249
+ }
64250
+
63204
64251
  // src/fx/heavy.ts
63205
64252
  var PI14 = Math.PI;
63206
64253
  var ROCKET_LIGHT_NAME = "rocket-light";
@@ -64186,6 +65233,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64186
65233
  let triggerRest = 0;
64187
65234
  let feedCued = false;
64188
65235
  const rootQuaternion = new THREE.Quaternion();
65236
+ const muzzleAt = new THREE.Vector3();
65237
+ const muzzleAlong = new THREE.Vector3();
64189
65238
  const world = {
64190
65239
  kit,
64191
65240
  scene,
@@ -64285,6 +65334,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64285
65334
  if (model.parts.stockMove && onTool?.stock === "collapsed") rig.setPartPose("stock", collapsedStockPose(model.parts.stockMove));
64286
65335
  applyCondition(kit, model, condition);
64287
65336
  lastAmmo = options.tool?.().ammo ?? 0;
65337
+ void audio?.loadSamples();
64288
65338
  hand.updateMatrixWorld(true);
64289
65339
  hold = holdOfModel(THREE, model, 1);
64290
65340
  hold = { ...hold, works: handWorkOf(THREE, model, weapon, hold.right) };
@@ -64337,12 +65387,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64337
65387
  if (!session) return;
64338
65388
  const family = session.model.parts.family;
64339
65389
  session.holderScale = 1;
65390
+ if (options.firstPerson?.()) {
65391
+ placeInView({ THREE, hand, holder, camera: camera2, right: hold?.right ?? null, rear: modelRear, kind: family === "pistol" || family === "revolver" ? "handgun" : family === "rocket" ? "launcher" : "long" });
65392
+ lift = 0;
65393
+ pullFromWall(dt);
65394
+ return;
65395
+ }
65396
+ hand.scale.set(1, 1, 1);
64340
65397
  placeOnBody({ THREE, hand, holder, frame: options.bodyHold?.() ?? eyeFrame(), rear: modelRear, lengthMetres: session.lengthMetres, handgun: family === "pistol" || family === "revolver", right: hold?.right ?? null, left: hold?.left ?? null });
64341
65398
  hand.updateMatrixWorld(true);
64342
65399
  lift += (liftToKeepInView() - lift) * Math.min(1, dt * HAND_IN_VIEW.rate);
64343
65400
  if (lift > 1e-4) hand.position.addScaledVector(up.set(0, 1, 0).applyQuaternion(camera2.quaternion), lift);
64344
65401
  hand.updateMatrixWorld(true);
64345
- if (!options.contact) return;
65402
+ pullFromWall(dt);
65403
+ };
65404
+ const pullFromWall = (dt) => {
65405
+ if (!options.contact || !session) return;
64346
65406
  ahead.set(0, 0, -1).applyQuaternion(camera2.quaternion);
64347
65407
  if (reach === 0) reach = Math.max(0.01, measureReach(session));
64348
65408
  const free = options.contact(camera2.position, ahead, WALL_PULL.probe);
@@ -64373,7 +65433,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64373
65433
  }
64374
65434
  const action = current.def.action;
64375
65435
  if (action.kind === "launcher") {
64376
- launchRocket(heavy(current), options.flyRocket !== false);
65436
+ if (current.model.parts.family === "rocket") launchRocket(heavy(current), options.flyRocket !== false);
64377
65437
  audio?.play("explosion", LAUNCH_VOLUME, distance3());
64378
65438
  }
64379
65439
  if (action.kind !== "firearm") return;
@@ -64450,6 +65510,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64450
65510
  condition = next;
64451
65511
  if (session) applyCondition(kit, session.model, condition);
64452
65512
  },
65513
+ muzzle() {
65514
+ const mark = session?.model.parts.muzzle;
65515
+ if (!session || !mark) return null;
65516
+ hand.updateMatrixWorld(true);
65517
+ const at3 = worldPositionOf(session, mark, muzzleAt);
65518
+ const along = muzzleAlong.set(1, 0, 0).applyQuaternion(session.model.root.getWorldQuaternion(rootQuaternion)).normalize();
65519
+ const kind = session.def.action.kind;
65520
+ return { position: [at3.x, at3.y, at3.z], direction: [along.x, along.y, along.z], drawsFlash: kind === "firearm" || kind === "launcher" };
65521
+ },
64453
65522
  handTargets() {
64454
65523
  if (!session || !hold) return null;
64455
65524
  hand.updateMatrixWorld(true);
@@ -64504,6 +65573,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64504
65573
  ...options.raycast ? { raycast: options.raycast } : {},
64505
65574
  ...options.contact ? { contact: options.contact } : {},
64506
65575
  ...options.bodyHold ? { bodyHold: options.bodyHold } : {},
65576
+ ...options.firstPerson ? { firstPerson: options.firstPerson } : {},
64507
65577
  ...options.effects ? { effects: options.effects } : {},
64508
65578
  ...options.flyRocket === void 0 ? {} : { flyRocket: options.flyRocket },
64509
65579
  ...options.particleLimit ? { particleLimit: options.particleLimit } : {},
@@ -64526,6 +65596,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64526
65596
  },
64527
65597
  setCondition: (condition) => model.setCondition(condition),
64528
65598
  handTargets: () => model.handTargets() ?? box5.handTargets(),
65599
+ muzzle: () => model.inHand !== null ? model.muzzle() : box5.muzzle(),
64529
65600
  layers,
64530
65601
  get inHand() {
64531
65602
  return model.inHand ?? box5.inHand;
@@ -64550,7 +65621,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64550
65621
  var MODEL_BODY = "body";
64551
65622
  function buildModeledProp(THREE, def, name, decorate, items, props = defaultWeaponProps()) {
64552
65623
  const model = buildWeaponModelById(THREE, def.id) ?? (() => {
64553
- const root = props.buildItemModel(THREE, def, items);
65624
+ const root = props.buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def);
64554
65625
  return root ? { root } : void 0;
64555
65626
  })();
64556
65627
  if (!model) return void 0;
@@ -65693,7 +66764,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65693
66764
  };
65694
66765
  var weaponItem = (def, name, items, props = defaultWeaponProps()) => {
65695
66766
  const id = `${WEAPON_ITEM_PREFIX}${def.id}`;
65696
- if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
66767
+ if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items) && !hasThrowableModel(def)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
65697
66768
  return {
65698
66769
  id,
65699
66770
  name,
@@ -65753,7 +66824,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65753
66824
  const seedOption = options.seed === void 0 ? {} : { seed: options.seed };
65754
66825
  let audio;
65755
66826
  const ownAudio = () => audio === void 0 ? audio = options.sound === false ? null : createWeaponAudio(void 0, { ...seedOption, ...options.samples ? { samples: options.samples } : {} }) : audio;
65756
- const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
66827
+ const generics = genericWeapons(arsenal);
66828
+ const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id) && !generics.has(def.id));
65757
66829
  const thumbnailBase = options.thumbnailBase ?? DEFAULT_THUMBNAIL_BASE;
65758
66830
  const withThumbnail = (item, key2) => {
65759
66831
  const thumbnail = thumbnailBase === false ? void 0 : weaponThumbnailUrl(key2, thumbnailBase);
@@ -65841,7 +66913,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65841
66913
  engine: lentEngine,
65842
66914
  ...options.samples ? { samples: options.samples } : {},
65843
66915
  onGunshot: (shot, input, played) => {
65844
- const recorded = played.kind === "sample" && played.layers.length > 0 ? fill(text.gunshotRecorded, { takes: played.layers.map((layer2) => `${layer2.role} ${layer2.take + 1}`).join(", ") }) : "";
66916
+ const recorded = played.kind === "sample" && played.layers.length > 0 ? fill(text.gunshotRecorded, { takes: played.layers.map((layer3) => `${layer3.role} ${layer3.take + 1}`).join(", ") }) : "";
65845
66917
  lent.log(
65846
66918
  fill(text.gunshotSound, { profile: shot.profile, take: String(shot.take + 1), pitch: `${shot.pitch >= 1 ? "+" : ""}${((shot.pitch - 1) * 100).toFixed(1)} %`, space: text.spaces[input.space ?? "outdoor"], distance: String(Math.round(input.distance ?? 0)), far: shot.closeMix < 1 ? ` (${Math.round((1 - shot.closeMix) * 100)} % far)` : "" }) + recorded,
65847
66919
  { profile: shot.profile, take: shot.take, pitch: shot.pitch, space: input.space ?? "outdoor", distance: input.distance ?? 0, closeMix: shot.closeMix, delay: shot.delay, samples: shot.samples, played },
@@ -65870,6 +66942,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65870
66942
  // the weapon against a wall asks as often as the laser, on the clock of the simulation (so a fast-forwarded room measures as often per frame)
65871
66943
  contact: createLaserRaycast(lent.THREE, lent.scene, lent.camera, { now: () => simulatedMs }),
65872
66944
  ...lent.holdFrame ? { bodyHold: () => lent.holdFrame?.() ?? null } : {},
66945
+ firstPerson: () => lent.player().cameraMode === "first",
65873
66946
  ...lent.effects ? { effects: lent.effects } : {},
65874
66947
  ...lent.particleLimit ? { particleLimit: lent.particleLimit } : {},
65875
66948
  ...isLightBudget(lent.lights) ? { lights: lent.lights } : {},
@@ -65920,6 +66993,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65920
66993
  });
65921
66994
  const offFrame = lent.onFrame((dt) => created.update(dt));
65922
66995
  const offHands = lent.setHandTargets?.(() => created.handTargets());
66996
+ const offMuzzle = lent.setMuzzleSource?.(() => created.muzzle());
65923
66997
  created.handle({ type: "tool", weapon: lent.tool().weapon }, lent.tool());
65924
66998
  return () => {
65925
66999
  accessories.dispose();
@@ -65928,6 +67002,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65928
67002
  offFrame();
65929
67003
  offClock();
65930
67004
  offHands?.();
67005
+ offMuzzle?.();
65931
67006
  created.dispose();
65932
67007
  audio?.dispose();
65933
67008
  fx = null;
@@ -66657,6 +67732,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66657
67732
  var SHELTERED_MOST = 1;
66658
67733
  var CHEST = 1.1;
66659
67734
  var MIDDLE = 0.3;
67735
+ var AIM_SEARCH = 1.5;
66660
67736
  var HEAVY_ITEMS = ["camp_table"];
66661
67737
  var PERSON_MASS = 70;
66662
67738
  var GROUND_FRICTION2 = 7;
@@ -66716,6 +67792,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66716
67792
  }
66717
67793
  const partsBefore = new Map(placed.map((body) => [body.objectId, partsOf(body.objectId)]));
66718
67794
  expect(placed.length > 0, fillText2(text.push_found, { n: placed.length, kinds: [...new Set(placed.map((body) => body.kind))].join(", ") }), { placed: placed.map((body) => body.itemId) });
67795
+ const aimOf = new Map(
67796
+ placed.map((body) => {
67797
+ const from = origin.get(body.objectId) ?? body.from;
67798
+ if (body.kind === "zombie" || body.kind === "survivor") return [body.objectId, [from[0], from[1] + CHEST, from[2]]];
67799
+ const boxes = room.renderer.solidsNear?.(from[0], from[2], AIM_SEARCH).filter((box5) => box5.objectId === body.objectId) ?? [];
67800
+ if (boxes.length === 0) return [body.objectId, [from[0], from[1] + MIDDLE, from[2]]];
67801
+ const low = [0, 1, 2].map((axis2) => Math.min(...boxes.map((box5) => box5.min[axis2])));
67802
+ const high = [0, 1, 2].map((axis2) => Math.max(...boxes.map((box5) => box5.max[axis2])));
67803
+ return [body.objectId, [0, 1, 2].map((axis2) => (low[axis2] + high[axis2]) / 2)];
67804
+ })
67805
+ );
67806
+ const coverAt = (point) => new Map(
67807
+ placed.map((body) => {
67808
+ const ignore = source ? [source, body.objectId] : [body.objectId];
67809
+ return [body.objectId, room.world.blastReach?.(point, aimOf.get(body.objectId) ?? body.from, { ignore }) ?? 1];
67810
+ })
67811
+ );
67812
+ let coverOf;
67813
+ const stopListening = room.world.on("*", (event) => {
67814
+ if ((event.type === "explode" || event.type === "blast") && !coverOf) coverOf = coverAt(event.point);
67815
+ });
66719
67816
  if (mode === "p13") bench.actions.shoot();
66720
67817
  else {
66721
67818
  room.setTool("frag_grenade");
@@ -66728,17 +67825,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66728
67825
  step(0.05);
66729
67826
  waited += 0.05;
66730
67827
  }
67828
+ stopListening();
66731
67829
  const blast = bench.lastBlast();
66732
67830
  expect(!!blast, text.push_told, { waited });
66733
67831
  if (!blast) return;
66734
- const coverOf = new Map(
66735
- placed.map((body) => {
66736
- const from = origin.get(body.objectId) ?? body.from;
66737
- const person = body.kind === "zombie" || body.kind === "survivor";
66738
- const ignore = source ? [source, body.objectId] : [body.objectId];
66739
- return [body.objectId, room.world.blastReach?.(blast.point, [from[0], from[1] + (person ? CHEST : MIDDLE), from[2]], { ignore }) ?? 1];
66740
- })
66741
- );
67832
+ const cover = coverOf ?? coverAt(blast.point);
66742
67833
  step(PUSH_READ_AT);
66743
67834
  const rows = placed.map((body) => {
66744
67835
  const from = origin.get(body.objectId) ?? body.from;
@@ -66746,16 +67837,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66746
67837
  const distance3 = flat(from, blast.point);
66747
67838
  const away = Math.max(1e-6, distance3);
66748
67839
  const outward = ((now[0] - from[0]) * (from[0] - blast.point[0]) + (now[2] - from[2]) * (from[2] - blast.point[2])) / away;
66749
- return { ...body, distance: distance3, outward, moved: flat(now, from), cover: coverOf.get(body.objectId) ?? 1 };
67840
+ return { ...body, distance: distance3, outward, moved: flat(now, from), cover: cover.get(body.objectId) ?? 1 };
66750
67841
  });
66751
67842
  const torn = (row) => (PUSH_KINDS.some((entry2) => entry2.kind === row.kind) || HEAVY_ITEMS.includes(row.kind)) && partsOf(row.objectId) < (partsBefore.get(row.objectId) ?? 0);
66752
67843
  const inReach = rows.filter((row) => row.distance < blast.radius * 0.85 && !torn(row));
67844
+ const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
67845
+ const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
66753
67846
  const sheltered = inReach.filter((row) => row.cover < SHELTERED);
66754
67847
  for (const row of sheltered) {
66755
- expect(row.outward < SHELTERED_MOST, fillText2(text.push_sheltered, { what: things[row.kind] ?? row.kind, distance: row.distance.toFixed(1), cover: (row.cover * 100).toFixed(0), moved: Math.max(0, row.outward).toFixed(2) }), { row: { kind: row.kind, distance: row.distance, outward: row.outward, cover: row.cover } });
67848
+ const open = reached.filter((other) => other.kind === row.kind && other.distance <= row.distance).map((other) => other.outward);
67849
+ const most = Math.max(SHELTERED_MOST, row.cover * Math.max(0, ...open));
67850
+ expect(row.outward < most, fillText2(text.push_sheltered, { what: things[row.kind] ?? row.kind, distance: row.distance.toFixed(1), cover: (row.cover * 100).toFixed(0), moved: Math.max(0, row.outward).toFixed(2) }), { row: { kind: row.kind, distance: row.distance, outward: row.outward, cover: row.cover, most } });
66756
67851
  }
66757
- const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
66758
- const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
66759
67852
  const movedAway = reached.filter((row) => row.outward >= MOVED);
66760
67853
  const least = reached.length === 0 ? 0 : Math.min(...reached.map((row) => row.outward));
66761
67854
  expect(reached.length > 0 && movedAway.length === reached.length, fillText2(text.push_outward, { moved: movedAway.length, n: reached.length, least: least.toFixed(2) }), { rows: reached.map((row) => ({ kind: row.kind, distance: row.distance, outward: row.outward })), torn: rows.filter(torn).map((row) => row.objectId) });
@@ -66923,12 +68016,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66923
68016
 
66924
68017
  // src/projectile-model.ts
66925
68018
  function projectileModel(THREE, query2) {
66926
- if (query2.kind !== "rocket") return null;
68019
+ if (query2.kind !== "rocket") return explosiveProjectile(THREE, query2);
66927
68020
  const launcher = buildWeaponModelById(THREE, query2.weapon);
66928
68021
  if (!launcher || launcher.parts.family !== "rocket") return null;
66929
68022
  const round5 = launcher.parts.makeRound ? launcher.parts.makeRound() : buildFlyingRocket(modelKit(THREE), launcher.parts.launcherType);
66930
68023
  return { model: round5, axis: [1, 0, 0] };
66931
68024
  }
68025
+ function explosiveProjectile(THREE, query2) {
68026
+ const model = query2.explosive === void 0 ? void 0 : buildExplosiveModel(THREE, query2.explosive);
68027
+ if (!model) return null;
68028
+ const centre = new THREE.Box3().setFromObject(model).getCenter(new THREE.Vector3());
68029
+ const holder = markKitOrigin(new THREE.Group(), { owner: "game-weapons-three", kind: "explosive", id: model.name });
68030
+ holder.name = model.name;
68031
+ model.position.sub(centre);
68032
+ holder.add(model);
68033
+ return { model: holder, axis: [0, 1, 0] };
68034
+ }
66932
68035
 
66933
68036
  // src/weapon-props.ts
66934
68037
  function createWeaponProps(options = {}) {