@adatechnology/game-weapons-three 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ var GameWeaponsThree = (() => {
26
26
  BODY_ACTIONS: () => BODY_ACTIONS,
27
27
  CLASS_REFERENCE_ENERGY: () => CLASS_REFERENCE_ENERGY,
28
28
  COUNTER_ITEM_ID: () => COUNTER_ITEM_ID,
29
+ CUES_IN_SAMPLE: () => CUES_IN_SAMPLE,
29
30
  DEFAULT_ENERGY_CURVE: () => DEFAULT_ENERGY_CURVE,
30
31
  DEFAULT_EXPLOSIVES_SCENE: () => DEFAULT_EXPLOSIVES_SCENE,
31
32
  DEFAULT_THUMBNAIL_BASE: () => DEFAULT_THUMBNAIL_BASE,
@@ -33,12 +34,18 @@ var GameWeaponsThree = (() => {
33
34
  EXPLOSIVES_TEXT: () => EXPLOSIVES_TEXT,
34
35
  GAME_DISTANCE_CURVE: () => GAME_DISTANCE_CURVE,
35
36
  GAME_SOUND_CLASSES: () => GAME_SOUND_CLASSES,
37
+ GUNSHOT_PROFILES: () => GUNSHOT_PROFILES,
38
+ GUNSHOT_SAMPLE: () => GUNSHOT_SAMPLE,
39
+ GUNSHOT_SAMPLE_IDS: () => GUNSHOT_SAMPLE_IDS,
36
40
  MECHANICAL_SOUNDS: () => MECHANICAL_SOUNDS,
37
41
  MODEL_ENTRIES: () => MODEL_ENTRIES,
38
42
  MOUNTED_M134_ITEM_ID: () => MOUNTED_M134_ITEM_ID,
39
43
  P13_VALVE_MESH: () => P13_VALVE_MESH,
44
+ RELOAD_SAMPLE: () => RELOAD_SAMPLE,
45
+ RELOAD_SAMPLE_HITS: () => RELOAD_SAMPLE_HITS,
40
46
  WEAPON_ITEM_PREFIX: () => WEAPON_ITEM_PREFIX,
41
47
  WEAPON_THUMBNAILS: () => WEAPON_THUMBNAILS,
48
+ WEIGHT_INTENSITY: () => WEIGHT_INTENSITY,
42
49
  WORLD_EFFECTS: () => WORLD_EFFECTS,
43
50
  WORLD_EXPLOSIVE_IDS: () => WORLD_EXPLOSIVE_IDS,
44
51
  WORLD_EXPLOSIVE_PREFIX: () => WORLD_EXPLOSIVE_PREFIX,
@@ -54,6 +61,9 @@ var GameWeaponsThree = (() => {
54
61
  buildWeaponModel: () => buildWeaponModel,
55
62
  buildWeaponModelById: () => buildWeaponModelById,
56
63
  cartridgeShape: () => cartridgeShape,
64
+ computeCasingSound: () => computeCasingSound,
65
+ computeFlyby: () => computeFlyby,
66
+ computeGunshot: () => computeGunshot,
57
67
  computeShotSound: () => computeShotSound,
58
68
  createExplosivesTestModule: () => createExplosivesTestModule,
59
69
  createWeaponAudio: () => createWeaponAudio,
@@ -77,6 +87,8 @@ var GameWeaponsThree = (() => {
77
87
  rigStock: () => rigStock,
78
88
  rigUnderbarrel: () => rigUnderbarrel,
79
89
  weaponProp: () => weaponProp,
90
+ weaponSampleBank: () => weaponSampleBank,
91
+ weaponSampleDefaults: () => weaponSampleDefaults,
80
92
  weaponThumbnailCredit: () => weaponThumbnailCredit,
81
93
  weaponThumbnailUrl: () => weaponThumbnailUrl,
82
94
  weaponsLayout: () => weaponsLayout,
@@ -228,6 +240,21 @@ var GameWeaponsThree = (() => {
228
240
  const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0;
229
241
  return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) };
230
242
  };
243
+ var leadClipEvents = (clip) => {
244
+ const events = clip.events ?? [];
245
+ if (!events.some((event) => (event.lead ?? 0) > 0)) return clip;
246
+ let floor2 = 0;
247
+ const led = events.map((event) => {
248
+ const lead = Math.max(0, Math.min(event.t - floor2, event.lead ?? 0));
249
+ const t = event.t - lead;
250
+ floor2 = Math.max(floor2, t);
251
+ if (event.lead === void 0) return event;
252
+ const moved = { ...event, t, data: lead > 0 ? { ...event.data, lead } : { ...event.data } };
253
+ delete moved.lead;
254
+ return moved;
255
+ });
256
+ return { ...clip, events: led };
257
+ };
231
258
  var createToolClipPlayer = (options) => {
232
259
  const { clips, rig, onEvent } = options;
233
260
  let instances = [];
@@ -270,7 +297,7 @@ var GameWeaponsThree = (() => {
270
297
  }
271
298
  const found = find(step.clip, step.variant);
272
299
  if (!found) return;
273
- const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip;
300
+ const clip = leadClipEvents(step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip);
274
301
  const interval = options.repeatInterval?.() ?? 0.1;
275
302
  instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0);
276
303
  for (let round5 = 0; round5 < (step.repeat ?? 1); round5++) {
@@ -343,20 +370,20 @@ var GameWeaponsThree = (() => {
343
370
  return result2;
344
371
  };
345
372
  var mergeById = (shipped, extra) => {
346
- const entries = /* @__PURE__ */ new Map();
347
- for (const entry2 of [...shipped, ...extra ?? []]) entries.set(entry2.id, entry2);
348
- return entries;
373
+ const entries2 = /* @__PURE__ */ new Map();
374
+ for (const entry2 of [...shipped, ...extra ?? []]) entries2.set(entry2.id, entry2);
375
+ return entries2;
349
376
  };
350
- var applyPatches = (entries, patches) => {
377
+ var applyPatches = (entries2, patches) => {
351
378
  for (const [id, patch] of Object.entries(patches ?? {})) {
352
- const entry2 = entries.get(id);
353
- if (entry2) entries.set(id, deepMerge(entry2, patch));
379
+ const entry2 = entries2.get(id);
380
+ if (entry2) entries2.set(id, deepMerge(entry2, patch));
354
381
  else warnOnce(`registry.patch.${id}`, `patch for "${id}" ignored: no entry has that id`);
355
382
  }
356
383
  };
357
- var removeIds = (entries, ids) => {
384
+ var removeIds = (entries2, ids) => {
358
385
  for (const id of ids ?? []) {
359
- if (!entries.delete(id)) warnOnce(`registry.remove.${id}`, `remove of "${id}" ignored: no entry has that id`);
386
+ if (!entries2.delete(id)) warnOnce(`registry.remove.${id}`, `remove of "${id}" ignored: no entry has that id`);
360
387
  }
361
388
  };
362
389
  var KIT_ORIGIN_KEY = "kitOrigin";
@@ -12684,10 +12711,10 @@ var GameWeaponsThree = (() => {
12684
12711
  ]
12685
12712
  }
12686
12713
  ];
12687
- var findDuplicateIds = (kind, entries) => {
12714
+ var findDuplicateIds = (kind, entries2) => {
12688
12715
  const seen = /* @__PURE__ */ new Set();
12689
12716
  const problems = [];
12690
- for (const { id } of entries ?? []) {
12717
+ for (const { id } of entries2 ?? []) {
12691
12718
  if (seen.has(id)) problems.push(`${kind} "${id}" is listed more than once`);
12692
12719
  seen.add(id);
12693
12720
  }
@@ -13567,8 +13594,8 @@ var GameWeaponsThree = (() => {
13567
13594
  var FUSE_RNG_SALT = 3144134277;
13568
13595
  var BREAK_RNG_SALT = 1013904242;
13569
13596
  var UNDERBARREL_SEED_SALT = 2773480762;
13570
- var findById = (entries, id, label) => {
13571
- const found = entries.find((entry2) => entry2.id === id);
13597
+ var findById = (entries2, id, label) => {
13598
+ const found = entries2.find((entry2) => entry2.id === id);
13572
13599
  if (!found) throw new Error(`game-weapons: unknown ${label} "${id}"`);
13573
13600
  return found;
13574
13601
  };
@@ -14712,7 +14739,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
14712
14739
  shade: () => shade
14713
14740
  });
14714
14741
 
14715
- // ../game-shapes/dist/chunk-IFXQIDSA.js
14742
+ // ../game-shapes/dist/chunk-M5N2SHNL.js
14716
14743
  function canonicalJson(value) {
14717
14744
  if (value === null) return "null";
14718
14745
  if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
@@ -15995,8 +16022,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
15995
16022
  groups: []
15996
16023
  };
15997
16024
  }
15998
- function make(kind, p) {
15999
- switch (kind) {
16025
+ function make(p) {
16026
+ switch (p.kind) {
16000
16027
  case "box": {
16001
16028
  const size = p.size ?? [1, 1, 1];
16002
16029
  const segments = p.segments ?? [1, 1, 1];
@@ -16025,7 +16052,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
16025
16052
  case "mesh":
16026
16053
  return mesh(p);
16027
16054
  default:
16028
- throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
16055
+ throw new Error(`game-shapes: unknown primitive kind "${String(p.kind)}"`);
16029
16056
  }
16030
16057
  }
16031
16058
  var templates = /* @__PURE__ */ new Map();
@@ -16033,7 +16060,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
16033
16060
  var stats = { hits: 0, misses: 0 };
16034
16061
  function buildPrimitive(node, options = {}) {
16035
16062
  const parameters = primitiveParameters(node, options);
16036
- if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
16063
+ if (node.kind === "mesh" || templateLimit === 0) return make({ ...node, ...parameters });
16037
16064
  const key2 = `${node.kind}:${canonicalJson(parameters)}`;
16038
16065
  const cached = templates.get(key2);
16039
16066
  if (cached) {
@@ -16043,7 +16070,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
16043
16070
  return cached;
16044
16071
  }
16045
16072
  stats.misses++;
16046
- const built2 = make(node.kind, parameters);
16073
+ const built2 = make({ ...node, ...parameters });
16047
16074
  templates.set(key2, built2);
16048
16075
  if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
16049
16076
  return built2;
@@ -18758,8 +18785,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18758
18785
  // was `maquina`
18759
18786
  });
18760
18787
  var OWN_TYPES = Object.freeze({ lamp: true, gas_piped: true });
18788
+ var sourceOf = (result2) => result2;
18761
18789
  var ADAPTER_VERSION = "0.3.0";
18762
18790
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
18791
+ var at = (it) => ({ x: it.x ?? 0, z: it.z ?? 0, ry: it.ry ?? 0 });
18763
18792
  function mergeRegistry(base, extra) {
18764
18793
  const out = { ...base };
18765
18794
  if (extra) {
@@ -18778,14 +18807,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18778
18807
  });
18779
18808
  function create2(options = {}) {
18780
18809
  const opts = options || {};
18781
- const GI = opts.GameInteractables;
18782
- const HH = opts.GameInteractablesHousehold;
18783
18810
  const locale = opts.locale || "pt-BR";
18784
18811
  const msg = createTranslator(locale, opts.messages);
18785
- if (!GI || !HH) throw new Error(msg("error.missingKit"));
18812
+ if (!opts.GameInteractables || !opts.GameInteractablesHousehold) throw new Error(msg("error.missingKit"));
18813
+ const GI = opts.GameInteractables;
18814
+ const HH = opts.GameInteractablesHousehold;
18786
18815
  const region = opts.region || "br";
18787
18816
  const hh = HH.createHousehold({ locale, region });
18788
18817
  const householdTypes = new Set(hh.types.map((t) => t.id));
18818
+ const recorderApi = () => HH.createRecorder().api;
18789
18819
  const worlds = /* @__PURE__ */ new WeakMap();
18790
18820
  const typeMap = mergeRegistry(TYPE_MAP, opts.typeMap);
18791
18821
  const contentMap = mergeRegistry(CONTENT_MAP, opts.contentMap);
@@ -18821,7 +18851,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18821
18851
  function colorOf(res, color2) {
18822
18852
  if (color2 == null) return void 0;
18823
18853
  if (typeof color2 === "number") return color2;
18824
- const v = res.colors && res.colors[color2];
18854
+ const v = res.colors && res.colors[String(color2)];
18825
18855
  return typeof v === "number" ? v : void 0;
18826
18856
  }
18827
18857
  function paletteOf(res) {
@@ -18850,17 +18880,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18850
18880
  drawerItem: (s) => drawerItems.has(s.itemId)
18851
18881
  };
18852
18882
  const itemOfContent = (c) => {
18853
- const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || contentByGroup[stackGroup(c)] || contentFallback;
18883
+ const group = stackGroup(c);
18884
+ const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || (group != null ? contentByGroup[group] : void 0) || contentFallback;
18854
18885
  return hh.items[id] ? id : contentFallback;
18855
18886
  };
18856
18887
  const volume = (d) => d.size[0] * d.size[1] * d.size[2];
18857
18888
  const candidates = (id, objectType) => {
18858
18889
  const def = hh.items[id];
18859
- const siblings = Object.values(hh.items).filter((i) => i.category === def.category && i.id !== id).sort((a, b) => volume(a) - volume(b) || (a.id < b.id ? -1 : 1));
18890
+ const siblings = Object.values(hh.items).filter((i) => i.category === def?.category && i.id !== id).sort((a, b) => volume(a) - volume(b) || (a.id < b.id ? -1 : 1));
18860
18891
  return [id].concat(siblings.map((i) => i.id), substitutes[objectType] || []);
18861
18892
  };
18862
18893
  function distribute(type, probeItem, stacks) {
18863
- const spec = hh.build(type, probeItem, HH.createRecorder().api);
18894
+ const spec = hh.build(type, probeItem, recorderApi());
18864
18895
  const conts = spec.containers || [];
18865
18896
  const contents = {};
18866
18897
  for (const c of conts) contents[c.id] = c.slots.map(() => null);
@@ -18891,8 +18922,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18891
18922
  }
18892
18923
  return { contents, rejected };
18893
18924
  }
18894
- function plan(res, popts = {}) {
18895
- popts = popts || {};
18925
+ function plan(source, options2 = {}) {
18926
+ const res = sourceOf(source);
18927
+ const popts = options2 || {};
18896
18928
  const o = { ox: popts.originX || 0, oz: popts.originZ || 0, inw: popts.inw || 1, base: popts.base || 0 };
18897
18929
  const K = conv(o);
18898
18930
  const floorSel = popts.floor;
@@ -18956,7 +18988,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18956
18988
  const item = {
18957
18989
  x: K.x(m.x),
18958
18990
  z: K.z(m.z),
18959
- ry: m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0),
18991
+ ry: m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0),
18960
18992
  w: m.w,
18961
18993
  d: m.d,
18962
18994
  h: m.h,
@@ -18969,7 +19001,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18969
19001
  return item;
18970
19002
  }
18971
19003
  function entry2(id, type, item, origin, floor2, extra) {
18972
- return Object.assign({ id, type, item, origin, floor: floor2, floorY: floorYOf(res, o, floor2), connections: {} }, extra || {});
19004
+ const e = { id, type, item, origin, floor: floor2, floorY: floorYOf(res, o, floor2), connections: {} };
19005
+ return Object.assign(e, extra || {});
18973
19006
  }
18974
19007
  function store(targets, stacks) {
18975
19008
  let rest2 = stacks || [];
@@ -18982,10 +19015,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18982
19015
  }
18983
19016
  const hidden = [];
18984
19017
  function distRect(c, x, z) {
18985
- const dx = x - c.item.x;
18986
- const dz = z - c.item.z;
18987
- const s = Math.sin(c.item.ry);
18988
- const co = Math.cos(c.item.ry);
19018
+ const ci = at(c.item);
19019
+ const dx = x - ci.x;
19020
+ const dz = z - ci.z;
19021
+ const s = Math.sin(ci.ry);
19022
+ const co = Math.cos(ci.ry);
18989
19023
  const lx = dx * co - dz * s;
18990
19024
  const lz = dx * s + dz * co;
18991
19025
  return Math.hypot(Math.max(Math.abs(lx) - (c.item.w || 0) / 2, 0), Math.max(Math.abs(lz) - (c.item.d || 0) / 2, 0));
@@ -18993,9 +19027,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
18993
19027
  function hideCylinders() {
18994
19028
  for (const b of hidden) {
18995
19029
  let target = null;
19030
+ const bi = at(b.item);
18996
19031
  for (const c2 of result2.items) {
18997
19032
  if (c2.type !== "kitchen_cabinet_base" || c2.floor !== b.floor) continue;
18998
- const dist2 = distRect(c2, b.item.x, b.item.z);
19033
+ const dist2 = distRect(c2, bi.x, bi.z);
18999
19034
  if (!target || dist2 < target.dist) target = { c: c2, dist: dist2 };
19000
19035
  }
19001
19036
  if (!target) {
@@ -19003,16 +19038,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19003
19038
  continue;
19004
19039
  }
19005
19040
  const c = target.c;
19006
- const spec = hh.build(c.type, c.item, HH.createRecorder().api);
19007
- const sn = Math.sin(c.item.ry);
19008
- const cs = Math.cos(c.item.ry);
19041
+ const spec = hh.build(c.type, c.item, recorderApi());
19042
+ const ci = at(c.item);
19043
+ const sn = Math.sin(ci.ry);
19044
+ const cs = Math.cos(ci.ry);
19009
19045
  let door = null;
19010
19046
  for (const it of spec.interactions || []) {
19011
- const pt = it.action && it.action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === it.action.part) : null;
19012
- if (!pt || pt.motion.kind !== "hinge") continue;
19013
- const wx = c.item.x + it.pos[0] * cs + it.pos[2] * sn;
19014
- const wz = c.item.z - it.pos[0] * sn + it.pos[2] * cs;
19015
- const dist2 = Math.hypot(wx - b.item.x, wz - b.item.z);
19047
+ const action = it.action;
19048
+ const pt = action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === action.part) : null;
19049
+ if (!pt || !pt.motion || pt.motion.kind !== "hinge") continue;
19050
+ const wx = ci.x + it.pos[0] * cs + it.pos[2] * sn;
19051
+ const wz = ci.z - it.pos[0] * sn + it.pos[2] * cs;
19052
+ const dist2 = Math.hypot(wx - bi.x, wz - bi.z);
19016
19053
  if (!door || dist2 < door.dist) door = { id: pt.id, dist: dist2 };
19017
19054
  }
19018
19055
  if (!door) {
@@ -19042,9 +19079,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19042
19079
  const campCylinder = firstOfType("gas_canister");
19043
19080
  function planFireplace(m, e, item) {
19044
19081
  const def = hh.types.find((t) => t.id === "fireplace");
19045
- const lack = (def.d - m.d) / 2;
19046
- item.x = r3(item.x + Math.sin(item.ry) * lack);
19047
- item.z = r3(item.z + Math.cos(item.ry) * lack);
19082
+ const lack = ((def ? def.d : m.d) - m.d) / 2;
19083
+ const p = at(item);
19084
+ item.x = r3(p.x + Math.sin(p.ry) * lack);
19085
+ item.z = r3(p.z + Math.cos(p.ry) * lack);
19048
19086
  const room = (res.rooms || []).find((c) => c.id === m.room && c.floor === m.floor);
19049
19087
  const f = (res.floors || []).find((x) => x.index === m.floor);
19050
19088
  item.h = r3(room && room.ceiling || f && f.ceiling || 2.6);
@@ -19072,7 +19110,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19072
19110
  e.connections.gas = m.gas || "cylinder";
19073
19111
  e.connections.cylinder = m.cylinder || null;
19074
19112
  }
19075
- if (map.type === "gas_bottle") result2.cylinders.push({ id: e.id, stove: m.link || null, place: m.placement || null });
19113
+ if (map.type === "gas_bottle") result2.cylinders.push({ id: e.id, stove: typeof m.link === "string" ? m.link : null, place: m.placement || null });
19076
19114
  if (map.type === "gas_canister") {
19077
19115
  result2.canisters.push(e.id);
19078
19116
  if (campCylinder && campCylinder.id === m.id && campStove2) result2.cylinders.push({ id: e.id, stove: campStove2.id, place: null });
@@ -19096,7 +19134,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19096
19134
  push(e);
19097
19135
  }
19098
19136
  function planCompound(m, kind) {
19099
- const ry = m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0);
19137
+ const ry = m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0);
19100
19138
  const cx = K.x(m.x);
19101
19139
  const cz = K.z(m.z);
19102
19140
  const part = (id, type, w, d, h, y, lx, lz) => {
@@ -19148,11 +19186,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19148
19186
  } else {
19149
19187
  const basin = part(m.id, "sink_bathroom", m.w, m.d, m.h + 0.2, 0, 0, 0);
19150
19188
  list2.push(basin);
19151
- if (contents[m.id]) {
19152
- if (contents[m.id].length) {
19189
+ const own = contents[m.id];
19190
+ if (own) {
19191
+ if (own.length) {
19153
19192
  const cabinet = part(m.id + "#cabinet", "bathroom_cabinet", Math.max(0.45, Math.min(0.6, m.w)), 0.15, 0.75, 1.3, 0, -(m.d / 2) + 0.075);
19154
19193
  list2.push(cabinet);
19155
- store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], contents[m.id]);
19194
+ store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], own);
19156
19195
  } else {
19157
19196
  store([["sink_bathroom", basin.item]], []);
19158
19197
  }
@@ -19192,7 +19231,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19192
19231
  result2.chairs = [];
19193
19232
  if (opts.chairs !== false && popts.chairs !== false) {
19194
19233
  const blocked = blockedOf(res, { originX: o.ox, originZ: o.oz, inw: o.inw });
19195
- const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && q.aabb;
19234
+ const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && !!q.aabb;
19196
19235
  const placed = [];
19197
19236
  const free = (m, cu, cv, half) => {
19198
19237
  const a = [cu - half + 0.03, cv - half + 0.03, cu + half - 0.03, cv + half - 0.03];
@@ -19208,8 +19247,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19208
19247
  for (const m of res.items || []) {
19209
19248
  const mode = chairTables[m.type];
19210
19249
  if (!mode || !onFloor(m.floor, m.outdoor) || m.frontU == null) continue;
19211
- const f = [m.frontU, m.frontV];
19212
- const pp = [-m.frontV, m.frontU];
19250
+ const fv = m.frontV ?? 0;
19251
+ const f = [m.frontU, fv];
19252
+ const pp = [-fv, m.frontU];
19213
19253
  const seats = [];
19214
19254
  if (mode === "dining") {
19215
19255
  const n = Math.max(1, Math.min(3, Math.floor((m.w - 0.1) / 0.55)));
@@ -19278,8 +19318,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19278
19318
  const item = { x: K.x(s.u), z: K.z(s.v), ry: K.ry(s.frontU, s.frontV), y: s.y, seed: seedOf2(s.id) };
19279
19319
  item.controls = (s.controls || []).map((id) => id + ":lamp");
19280
19320
  const e = entry2(s.id, "wall_switch", item, s.id, s.floor);
19281
- e.connections.controls = (s.controls || []).slice();
19282
- e.lit = e.connections.controls.some((id) => (result2.items.find((x) => x.id === id) || {}).lit === true);
19321
+ const controls = (s.controls || []).slice();
19322
+ e.connections.controls = controls;
19323
+ e.lit = controls.some((id) => result2.items.find((x) => x.id === id)?.lit === true);
19283
19324
  if (floorSel == null) {
19284
19325
  for (const id of s.controls || []) if (!lights.has(id)) result2.warnings.push(msg("warn.switchUnknownLamp", { id: s.id, lamp: id }));
19285
19326
  }
@@ -19319,7 +19360,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19319
19360
  seed: seedOf2("panel"),
19320
19361
  circuits: circuits.map((c) => ({ id: c.id, label: c.room }))
19321
19362
  };
19322
- if (!item.circuits.length) delete item.circuits;
19363
+ if (!item.circuits?.length) delete item.circuits;
19323
19364
  const e = entry2("panel", "electrical_panel", item, "panel", q.floor);
19324
19365
  push(e);
19325
19366
  result2.panel = e.id;
@@ -19341,10 +19382,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19341
19382
  }
19342
19383
  }
19343
19384
  const piped = result2.items.filter((e) => e.type === "stove" && e.connections.gas === "piped");
19344
- if (piped.length) {
19385
+ const firstPiped = piped[0];
19386
+ if (firstPiped) {
19345
19387
  const med = utilities.gas && utilities.gas.meter;
19346
- const ref = med ? { x: K.x(med.u), z: K.z(med.v), y: med.y || 1 } : { x: piped[0].item.x, z: piped[0].item.z, y: 0.3 };
19347
- const e = entry2("piped_gas", "gas_piped", { x: ref.x, z: ref.z, ry: 0, y: ref.y, seed: seedOf2("piped_gas") }, "piped_gas", piped[0].floor);
19388
+ const ref = med ? { x: K.x(med.u), z: K.z(med.v), y: med.y || 1 } : { x: firstPiped.item.x, z: firstPiped.item.z, y: 0.3 };
19389
+ const e = entry2("piped_gas", "gas_piped", { x: ref.x, z: ref.z, ry: 0, y: ref.y, seed: seedOf2("piped_gas") }, "piped_gas", firstPiped.floor);
19348
19390
  e.floorY = o.base;
19349
19391
  push(e);
19350
19392
  for (const f of piped) f.connections.fuel = "piped_gas:gas";
@@ -19391,9 +19433,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19391
19433
  for (const dv of spec.devices || []) if (L.outlet && dv.socket === "auto") dv.socket = L.outlet + ":socket";
19392
19434
  } else {
19393
19435
  const sh = L.shower || {};
19394
- const heater = (spec.devices || []).find((d) => d.id === "heater");
19436
+ const devices = spec.devices || [];
19437
+ const heater = devices.find((d) => d.id === "heater");
19395
19438
  if (heater && sh.electric === false) {
19396
- spec.devices = spec.devices.filter((d) => d !== heater);
19439
+ spec.devices = devices.filter((d) => d !== heater);
19397
19440
  for (const f of spec.fixtures || []) if (f.heater === "heater") delete f.heater;
19398
19441
  } else if (heater) {
19399
19442
  if (sh.powerW) heater.watts = sh.powerW;
@@ -19404,7 +19447,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19404
19447
  for (const b of spec.burners || []) if (b.fuel === "auto") b.fuel = L.fuel;
19405
19448
  }
19406
19449
  if (entry2.type === "fireplace" && L.chimneyTop) {
19407
- const it = entry2.item;
19450
+ const it = at(entry2.item);
19408
19451
  const dx = L.chimneyTop[0] - it.x;
19409
19452
  const dz = L.chimneyTop[2] - it.z;
19410
19453
  const lx = Math.cos(it.ry) * dx - Math.sin(it.ry) * dz;
@@ -19423,8 +19466,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19423
19466
  }
19424
19467
  return spec;
19425
19468
  }
19426
- function createWorld(res, copts = {}) {
19427
- copts = copts || {};
19469
+ function createWorld(source, options2 = {}) {
19470
+ const res = sourceOf(source);
19471
+ const copts = options2 || {};
19428
19472
  const info = { points: /* @__PURE__ */ new Map(), water: copts.water || null, lightsOn: copts.lightsOn };
19429
19473
  const electrical = res.utilities && res.utilities.electrical;
19430
19474
  const world = GI.createWorld({
@@ -19452,8 +19496,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19452
19496
  const info = worlds.get(world);
19453
19497
  return info ? info.points.get(fixtureId) : void 0;
19454
19498
  }
19455
- function blockedOf(res, bopts = {}) {
19456
- bopts = bopts || {};
19499
+ function blockedOf(source, options2 = {}) {
19500
+ const res = sourceOf(source);
19501
+ const bopts = options2 || {};
19457
19502
  const ox = bopts.originX || 0;
19458
19503
  const oz = bopts.originZ || 0;
19459
19504
  const inw = bopts.inw || 1;
@@ -19488,33 +19533,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19488
19533
  return false;
19489
19534
  };
19490
19535
  }
19491
- function add2(world, planned, aopts = {}) {
19492
- aopts = aopts || {};
19536
+ function add2(world, planned, options2 = {}) {
19537
+ const aopts = options2 || {};
19493
19538
  const info = worlds.get(world);
19494
- const build = aopts.build || ((type, item) => hh.build(type, item, HH.createRecorder().api));
19539
+ const build = aopts.build || ((type, item) => hh.build(type, item, recorderApi()));
19495
19540
  const isBlocked2 = aopts.isBlocked || null;
19496
19541
  const out = { added: 0, parts: [], hinges: {}, moved: {}, warnings: [] };
19497
19542
  const override = aopts.lightsOn !== void 0 ? aopts.lightsOn : info && info.lightsOn;
19498
19543
  if (override !== void 0) {
19499
19544
  for (const e of planned.items) if (e.type === "lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
19500
19545
  for (const e of planned.items) if (e.type === "lantern_lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
19501
- 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);
19546
+ 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);
19502
19547
  }
19503
19548
  for (const e of planned.items) if (e.type === "lantern_lamp") e.item.on = !!e.lit;
19504
19549
  let scratch2 = null;
19505
- function probe(e, hinge, setback) {
19550
+ function probe(e, hinge, setback, blockedAt) {
19506
19551
  if (!scratch2) scratch2 = GI.createWorld({ locale, strings: hh.strings, items: hh.items, loot: hh.loot, grid: false });
19507
- const it = e.item;
19552
+ const it = at(e.item);
19508
19553
  const x = it.x + Math.sin(it.ry) * setback;
19509
19554
  const z = it.z + Math.cos(it.ry) * setback;
19510
- const spec = hh.build(e.type, Object.assign({}, it, { hinge }), HH.createRecorder().api);
19555
+ const spec = hh.build(e.type, Object.assign({}, e.item, { hinge }), recorderApi());
19511
19556
  scratch2.add("__probe", spec, { x, y: e.floorY, z, ry: it.ry });
19512
- const blocks = (px, pz) => isBlocked2(px, pz, e.floor);
19557
+ const blocks = (px, pz) => blockedAt(px, pz, e.floor);
19513
19558
  let sum2 = 0;
19514
19559
  let min2 = 1;
19515
19560
  let n = 0;
19516
19561
  for (const p of spec.parts || []) {
19517
- if (p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
19562
+ if (!p.motion || p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
19518
19563
  const t = scratch2.fitSweep("__probe", p.id, blocks);
19519
19564
  sum2 += t;
19520
19565
  min2 = Math.min(min2, t);
@@ -19526,12 +19571,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19526
19571
  const better = (a, b) => a.sum > b.sum + 1e-9 || Math.abs(a.sum - b.sum) <= 1e-9 && a.min > b.min + 1e-9;
19527
19572
  function fitDoors(e) {
19528
19573
  if (!isBlocked2 || !householdTypes.has(e.type)) return;
19529
- const standard = probe(e, void 0, 0);
19574
+ const standard = probe(e, void 0, 0, isBlocked2);
19530
19575
  if (!standard.n || standard.min >= 1) return;
19531
19576
  let bestSide;
19532
19577
  let m = standard;
19533
19578
  for (const side of ["left", "right"]) {
19534
- const r = probe(e, side, 0);
19579
+ const r = probe(e, side, 0, isBlocked2);
19535
19580
  if (better(r, m)) {
19536
19581
  m = r;
19537
19582
  bestSide = side;
@@ -19544,13 +19589,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19544
19589
  if (m.min >= 1) return;
19545
19590
  let target = { setback: 0, r: m };
19546
19591
  for (const setback of [0.01, 0.02, 0.03, 0.04]) {
19547
- const r = probe(e, bestSide, setback);
19592
+ const r = probe(e, bestSide, setback, isBlocked2);
19548
19593
  if (better(r, target.r)) target = { setback, r };
19549
19594
  if (r.min >= 1) break;
19550
19595
  }
19551
19596
  if (target.setback > 0 && target.r.sum > m.sum + 0.2) {
19552
- e.item.x = r3(e.item.x + Math.sin(e.item.ry) * target.setback);
19553
- e.item.z = r3(e.item.z + Math.cos(e.item.ry) * target.setback);
19597
+ const p = at(e.item);
19598
+ e.item.x = r3(p.x + Math.sin(p.ry) * target.setback);
19599
+ e.item.z = r3(p.z + Math.cos(p.ry) * target.setback);
19554
19600
  e.moved = target.setback;
19555
19601
  out.moved[e.id] = target.setback;
19556
19602
  }
@@ -19573,7 +19619,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19573
19619
  }
19574
19620
  }
19575
19621
  connect(e, spec);
19576
- world.add(e.id, spec, { x: e.item.x, y: e.floorY, z: e.item.z, ry: e.item.ry });
19622
+ const pose = at(e.item);
19623
+ world.add(e.id, spec, { x: pose.x, y: e.floorY, z: pose.z, ry: pose.ry });
19577
19624
  out.added++;
19578
19625
  if (!isBlocked2) continue;
19579
19626
  for (const p of spec.parts || []) {
@@ -22322,7 +22369,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22322
22369
  const typed = listeners.get(e.type);
22323
22370
  if (typed) for (const fn of [...typed]) fn(e);
22324
22371
  };
22325
- const base = (o, source) => source === void 0 ? { object: o.id, time } : { object: o.id, time, source };
22372
+ let blow3;
22373
+ const base = (o, source) => ({
22374
+ object: o.id,
22375
+ time,
22376
+ ...source === void 0 ? {} : { source },
22377
+ ...blow3 === void 0 ? {} : { blow: blow3 }
22378
+ });
22326
22379
  const pendingCollapse = /* @__PURE__ */ new Map();
22327
22380
  let runDepth = 0;
22328
22381
  const drainBlasts = () => {
@@ -24467,7 +24520,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24467
24520
  res.events = cur.slice(start);
24468
24521
  return res;
24469
24522
  };
24470
- const doHit = (o, hit, only) => {
24523
+ const withBlow = (type, fn) => () => {
24524
+ const outer = blow3;
24525
+ blow3 = type;
24526
+ try {
24527
+ return fn();
24528
+ } finally {
24529
+ blow3 = outer;
24530
+ }
24531
+ };
24532
+ const doHit = (o, hit, only) => withBlow(hit.type, () => resolveHit(o, hit, only))();
24533
+ const resolveHit = (o, hit, only) => {
24471
24534
  let point = hit.point;
24472
24535
  let dir = hit.direction;
24473
24536
  if (hit.space === "local") {
@@ -24906,6 +24969,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24906
24969
  blastProbe() {
24907
24970
  return { ...blastProbe };
24908
24971
  },
24972
+ blastReach(point, target, options2 = {}) {
24973
+ const L = vec.distance(point, target);
24974
+ if (L < 1e-4) return 1;
24975
+ const shelter = { ...SHELTER_DEFAULTS, ...options2.shelter };
24976
+ const ignore = new Set(options2.ignore ?? []);
24977
+ const d = vec.scale(vec.sub(target, point), 1 / L);
24978
+ let t = 1;
24979
+ for (const o of objects.values()) {
24980
+ if (ignore.has(o.id) || raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
24981
+ for (const p of o.parts) {
24982
+ if (isLost(p) || p.st.stripped) continue;
24983
+ const r = rayPart(o, p, point, d);
24984
+ if (!r) continue;
24985
+ const t0 = Math.max(r.t0, 0);
24986
+ const path = Math.min(r.t1, L) - t0;
24987
+ if (path < shelter.minPath) continue;
24988
+ if (p.st.opening && insideOpening(o, p, toLocal(o.st.position, o.st.rotation, vec.madd(point, d, t0 + path / 2)))) continue;
24989
+ t *= Math.exp(-(p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1)) / shelter.k);
24990
+ if (t < 1e-4) return 0;
24991
+ }
24992
+ }
24993
+ for (const im of impls.values()) {
24994
+ if (ignore.has(im.impl.id)) continue;
24995
+ for (const ps of im.impl.describe().parts) {
24996
+ const mat = materials.get(ps.material);
24997
+ if (!mat) continue;
24998
+ const ov = im.poses.get(ps.id);
24999
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), point, d, ov?.pos);
25000
+ if (!r || r.t1 < 0 || r.t0 > L) continue;
25001
+ let path = Math.min(r.t1, L) - Math.max(r.t0, 0);
25002
+ if (ps.thickness !== void 0 && ps.thickness > 0) path = Math.min(path, 2 * ps.thickness);
25003
+ else if (path < shelter.minPath) continue;
25004
+ t *= Math.exp(-(mat.hpPerKg * mat.density * path * (mat.damageScale?.shelter ?? 1)) / shelter.k);
25005
+ if (t < 1e-4) return 0;
25006
+ }
25007
+ }
25008
+ return t;
25009
+ },
24909
25010
  explode(point, energy, radius, source, blast) {
24910
25011
  blastProbe.partsTested = 0;
24911
25012
  blastProbe.partsNear = 0;
@@ -24930,7 +25031,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24930
25031
  },
24931
25032
  shoot(origin, direction, energy, caliber, source, round5) {
24932
25033
  const extras = typeof round5 === "number" ? { piercing: round5 } : round5 ?? {};
24933
- return withCollapses(run(() => {
25034
+ return withCollapses(run(withBlow("ballistic", () => {
24934
25035
  const results = [];
24935
25036
  const d = vec.normalize(direction);
24936
25037
  const ignore = /* @__PURE__ */ new Set();
@@ -24941,10 +25042,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24941
25042
  if (!rh) break;
24942
25043
  const im = impls.get(rh.object);
24943
25044
  if (im) {
24944
- const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
25045
+ const scaled2 = extras.penetration && E < energy ? { ...extras, penetration: Object.fromEntries(Object.entries(extras.penetration).map(([k, mm]) => [k, mm * (E / energy)])) } : extras;
25046
+ const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...scaled2, ...source !== void 0 ? { source } : {} });
24945
25047
  r.part = rh.part;
24946
25048
  r.point = rh.point;
24947
- r.exitEnergy = r.penetrated ? E * 0.5 : 0;
25049
+ const left = r.events.find((e) => e.type === "hole" && e.energyLeft !== void 0);
25050
+ r.exitEnergy = r.penetrated ? left?.energyLeft ?? E * 0.5 : 0;
24948
25051
  results.push(r);
24949
25052
  ignore.add(`${im.impl.id}\0${rh.part}`);
24950
25053
  if (!r.penetrated) break;
@@ -24963,7 +25066,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
24963
25066
  E = out.exitEnergy;
24964
25067
  }
24965
25068
  return results;
24966
- }));
25069
+ })));
24967
25070
  },
24968
25071
  disassemble(objectId, jointId, tool2, dt) {
24969
25072
  const im = impls.get(objectId);
@@ -25772,8 +25875,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25772
25875
  function linesOf(boxes) {
25773
25876
  return boxes.map(([u0, v0, u1, v1]) => u1 - u0 >= v1 - v0 ? { dir: "u", at: (v0 + v1) / 2, from: u0, to: u1 } : { dir: "v", at: (u0 + u1) / 2, from: v0, to: v1 });
25774
25877
  }
25775
- function coverage(lines, dir, at22, from, to, options) {
25776
- const spans = lines.filter((line) => line.dir === dir && Math.abs(line.at - at22) <= options.lineTolerance).map((line) => [Math.max(from, line.from), Math.min(to, line.to)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
25878
+ function coverage(lines, dir, at23, from, to, options) {
25879
+ const spans = lines.filter((line) => line.dir === dir && Math.abs(line.at - at23) <= options.lineTolerance).map((line) => [Math.max(from, line.from), Math.min(to, line.to)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
25777
25880
  let covered = 0;
25778
25881
  let end3 = from;
25779
25882
  let start = NaN;
@@ -25795,12 +25898,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25795
25898
  const sorted = lines.filter((line) => line.dir === dir).map((line) => line.at).sort((a, b) => a - b);
25796
25899
  const out = [];
25797
25900
  let group = [];
25798
- for (const at22 of sorted) {
25799
- if (group.length && at22 - group[group.length - 1] > tolerance) {
25901
+ for (const at23 of sorted) {
25902
+ if (group.length && at23 - group[group.length - 1] > tolerance) {
25800
25903
  out.push(group.reduce((s, x) => s + x, 0) / group.length);
25801
25904
  group = [];
25802
25905
  }
25803
- group.push(at22);
25906
+ group.push(at23);
25804
25907
  }
25805
25908
  if (group.length) out.push(group.reduce((s, x) => s + x, 0) / group.length);
25806
25909
  return out;
@@ -25813,13 +25916,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25813
25916
  const tooWide = width > options.maxSpan + EPS5;
25814
25917
  const tooDeep = depth > options.maxSpan + EPS5;
25815
25918
  const candidates = [];
25816
- for (const at22 of positions(lines, "v", options.lineTolerance)) {
25817
- if (at22 < u0 + options.minPanel - EPS5 || at22 > u1 - options.minPanel + EPS5) continue;
25818
- candidates.push({ dir: "v", at: at22, coverage: coverage(lines, "v", at22, v0, v1, options), skew: Math.abs(at22 - (u0 + u1) / 2) / (width / 2) });
25919
+ for (const at23 of positions(lines, "v", options.lineTolerance)) {
25920
+ if (at23 < u0 + options.minPanel - EPS5 || at23 > u1 - options.minPanel + EPS5) continue;
25921
+ candidates.push({ dir: "v", at: at23, coverage: coverage(lines, "v", at23, v0, v1, options), skew: Math.abs(at23 - (u0 + u1) / 2) / (width / 2) });
25819
25922
  }
25820
- for (const at22 of positions(lines, "u", options.lineTolerance)) {
25821
- if (at22 < v0 + options.minPanel - EPS5 || at22 > v1 - options.minPanel + EPS5) continue;
25822
- candidates.push({ dir: "u", at: at22, coverage: coverage(lines, "u", at22, u0, u1, options), skew: Math.abs(at22 - (v0 + v1) / 2) / (depth / 2) });
25923
+ for (const at23 of positions(lines, "u", options.lineTolerance)) {
25924
+ if (at23 < v0 + options.minPanel - EPS5 || at23 > v1 - options.minPanel + EPS5) continue;
25925
+ candidates.push({ dir: "u", at: at23, coverage: coverage(lines, "u", at23, u0, u1, options), skew: Math.abs(at23 - (v0 + v1) / 2) / (depth / 2) });
25823
25926
  }
25824
25927
  const boundary = candidates.filter((c) => c.coverage >= options.lineCoverage);
25825
25928
  if (tooWide || tooDeep) {
@@ -25905,20 +26008,20 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25905
26008
  const held = merged(supports.map(([a, b]) => [Math.max(0, a), Math.min(length2, b)]).filter(([a, b]) => b > a));
25906
26009
  if (!held.length) return { span: Infinity, at: length2 / 2, lo: 0, hi: length2 };
25907
26010
  let span = 0;
25908
- let at22 = length2 / 2;
26011
+ let at23 = length2 / 2;
25909
26012
  let lo = 0;
25910
26013
  let hi = length2;
25911
26014
  const lead = held[0][0];
25912
26015
  if (2 * lead > span) {
25913
26016
  span = 2 * lead;
25914
- at22 = 0;
26017
+ at23 = 0;
25915
26018
  lo = 0;
25916
26019
  hi = lead;
25917
26020
  }
25918
26021
  const trail = length2 - held[held.length - 1][1];
25919
26022
  if (2 * trail > span) {
25920
26023
  span = 2 * trail;
25921
- at22 = length2;
26024
+ at23 = length2;
25922
26025
  lo = length2 - trail;
25923
26026
  hi = length2;
25924
26027
  }
@@ -25926,12 +26029,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25926
26029
  const gap = held[k][0] - held[k - 1][1];
25927
26030
  if (gap > span) {
25928
26031
  span = gap;
25929
- at22 = (held[k][0] + held[k - 1][1]) / 2;
26032
+ at23 = (held[k][0] + held[k - 1][1]) / 2;
25930
26033
  lo = held[k - 1][1];
25931
26034
  hi = held[k][0];
25932
26035
  }
25933
26036
  }
25934
- return { span, at: at22, lo, hi };
26037
+ return { span, at: at23, lo, hi };
25935
26038
  }
25936
26039
  var lineOf = (aabb, dir) => dir === "u" ? { dir, at: (aabb[1] + aabb[3]) / 2, from: aabb[0], to: aabb[2] } : { dir, at: (aabb[0] + aabb[2]) / 2, from: aabb[1], to: aabb[3] };
25937
26040
  var pointOf = (line, s) => line.dir === "u" ? [line.from + s, line.at] : [line.at, line.from + s];
@@ -25987,37 +26090,37 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
25987
26090
  const side = options.columnSide;
25988
26091
  const all = () => [...props, ...spots.map((spot) => ({ box: boxOfSpot(spot, side), dist: spot.dist }))];
25989
26092
  const allowed = merged(ground.map((box22) => holds2(line, 0.02, box22)).filter((x) => x !== void 0));
25990
- const place = (at22, lo, hi) => {
25991
- if (!allowed.length) return at22;
26093
+ const place = (at23, lo, hi) => {
26094
+ if (!allowed.length) return at23;
25992
26095
  let best;
25993
26096
  let gap = Infinity;
25994
26097
  for (const [a, b] of allowed) {
25995
26098
  const from = Math.max(a + side / 2, lo + 0.2);
25996
26099
  const to = Math.min(b - side / 2, hi - 0.2);
25997
26100
  if (to < from) continue;
25998
- const clamped = Math.min(to, Math.max(from, at22));
25999
- if (Math.abs(clamped - at22) < gap) {
26000
- gap = Math.abs(clamped - at22);
26101
+ const clamped = Math.min(to, Math.max(from, at23));
26102
+ if (Math.abs(clamped - at23) < gap) {
26103
+ gap = Math.abs(clamped - at23);
26001
26104
  best = clamped;
26002
26105
  }
26003
26106
  }
26004
- return best ?? at22;
26107
+ return best ?? at23;
26005
26108
  };
26006
26109
  let held = heldBy(line, width, all()).intervals;
26007
26110
  for (let guard = 0; guard < 64; guard++) {
26008
- let at22;
26111
+ let at23;
26009
26112
  let lo = 0;
26010
26113
  let hi = length2;
26011
- if (ends && !held.some(([a]) => a <= END_REACH)) at22 = 0;
26012
- else if (ends && !held.some(([, b]) => b >= length2 - END_REACH)) at22 = length2;
26114
+ if (ends && !held.some(([a]) => a <= END_REACH)) at23 = 0;
26115
+ else if (ends && !held.some(([, b]) => b >= length2 - END_REACH)) at23 = length2;
26013
26116
  else {
26014
26117
  const found = beamSpan(length2, held);
26015
26118
  if (found.span <= options.maxSpan + 1e-3) break;
26016
- at22 = found.at;
26119
+ at23 = found.at;
26017
26120
  lo = found.lo;
26018
26121
  hi = found.hi;
26019
26122
  }
26020
- const [u, v] = pointOf(line, at22 === 0 || at22 === length2 ? at22 : place(at22, lo, hi));
26123
+ const [u, v] = pointOf(line, at23 === 0 || at23 === length2 ? at23 : place(at23, lo, hi));
26021
26124
  const spot = { u: round2(u), v: round2(v), dist: dist2 };
26022
26125
  if (spots.some((other) => Math.hypot(other.u - spot.u, other.v - spot.v) < 0.15)) break;
26023
26126
  spots.push(spot);
@@ -26322,7 +26425,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26322
26425
  if (!length2) return [[from, to]];
26323
26426
  const offset = spec.bond === "running" && row % 2 === 1 ? length2 / 2 : 0;
26324
26427
  const cuts = [from];
26325
- for (let at22 = from + (offset || length2); at22 < to - MIN_UNIT; at22 += length2) cuts.push(at22);
26428
+ for (let at23 = from + (offset || length2); at23 < to - MIN_UNIT; at23 += length2) cuts.push(at23);
26326
26429
  cuts.push(to);
26327
26430
  return cuts.slice(0, -1).map((start, k) => [start, cuts[k + 1]]).filter(([a, b]) => b - a > MIN_UNIT);
26328
26431
  }
@@ -26733,9 +26836,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26733
26836
  const w = s.length / n;
26734
26837
  for (let k = 0; k < n; k++) {
26735
26838
  const id = `glass_${f}_${s.name}_${k}`;
26736
- const at22 = -s.length / 2 + (k + 0.5) * w;
26839
+ const at23 = -s.length / 2 + (k + 0.5) * w;
26737
26840
  const size = s.axis === 2 ? [w - 0.01, h - 0.01, c.thickness] : [c.thickness, h - 0.01, w - 0.01];
26738
- const position = s.axis === 2 ? [at22, yb + h / 2, s.fixed] : [s.fixed, yb + h / 2, at22];
26841
+ const position = s.axis === 2 ? [at23, yb + h / 2, s.fixed] : [s.fixed, yb + h / 2, at23];
26739
26842
  parts.push({
26740
26843
  id,
26741
26844
  material: c.material,
@@ -26774,10 +26877,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26774
26877
  const clear = length2 - g.columnSize;
26775
26878
  const win = clear * c.window;
26776
26879
  const pier = (clear - win) / 2;
26777
- const at22 = (u) => along === 0 ? [u, 0, fixed2] : [fixed2, 0, u];
26880
+ const at23 = (u) => along === 0 ? [u, 0, fixed2] : [fixed2, 0, u];
26778
26881
  const size = (u, h) => along === 0 ? [u, h, c.thickness] : [c.thickness, h, u];
26779
26882
  const put = (name, u, h, y, centreU, on) => {
26780
- const p = at22(centreU);
26883
+ const p = at23(centreU);
26781
26884
  wall(`infill_${f}_${side}_${k}_${name}`, size(u, h), [p[0], y, p[2]], on);
26782
26885
  };
26783
26886
  const id = (name) => `infill_${f}_${side}_${k}_${name}`;
@@ -26785,7 +26888,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26785
26888
  put("pl", pier, head - sill, yb + sill + (head - sill) / 2, centre - clear / 2 + pier / 2, [c1, id("sill")]);
26786
26889
  put("pr", pier, head - sill, yb + sill + (head - sill) / 2, centre + clear / 2 - pier / 2, [c2, id("sill")]);
26787
26890
  put("head", clear, room - head, yb + head + (room - head) / 2, centre, [c1, c2]);
26788
- const gp = at22(centre);
26891
+ const gp = at23(centre);
26789
26892
  const gid = `glass_${f}_${side}_${k}`;
26790
26893
  parts.push({
26791
26894
  id: gid,
@@ -26847,13 +26950,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26847
26950
  });
26848
26951
  rests(joints, "roof_membrane", top, "glue");
26849
26952
  for (let k = 0; k < count; k++) {
26850
- const at22 = -long / 2 + (k + 0.5) * spacing;
26953
+ const at23 = -long / 2 + (k + 0.5) * spacing;
26851
26954
  const id = `roof_drain_${k}`;
26852
26955
  parts.push({
26853
26956
  id,
26854
26957
  material: "plastic",
26855
26958
  shape: { kind: "cylinder", radius: 0.05, height: 0.1 },
26856
- position: longX ? [slab.position[0] + at22, my, slab.position[2]] : [slab.position[0], my, slab.position[2] + at22],
26959
+ position: longX ? [slab.position[0] + at23, my, slab.position[2]] : [slab.position[0], my, slab.position[2] + at23],
26857
26960
  fill: 0.2,
26858
26961
  tags: ["drain"]
26859
26962
  });
@@ -26874,11 +26977,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26874
26977
  const floor2 = `slab_${level - 1}`;
26875
26978
  const y = storeyBase(g, level);
26876
26979
  const shafts = options.shafts ?? [];
26877
- const put = (id, material, size, at22, mass, extra = {}) => {
26878
- parts.push({ id: id + sfx, material, shape: box2(size), position: [at22[0], y + size[1] / 2, at22[1]], mass, fill: 0.2, structure: { role: "partition" }, tags: ["mechanical"], ...extra });
26980
+ const put = (id, material, size, at23, mass, extra = {}) => {
26981
+ parts.push({ id: id + sfx, material, shape: box2(size), position: [at23[0], y + size[1] / 2, at23[1]], mass, fill: 0.2, structure: { role: "partition" }, tags: ["mechanical"], ...extra });
26879
26982
  rests(joints, id + sfx, floor2, "bolt");
26880
26983
  };
26881
- shafts.forEach((at22, k) => put(`machine_${k}`, "steel", [1.2, 1, 0.8], at22, c.machineMass + c.suspendedMass, { tags: ["mechanical", "elevator_machine"] }));
26984
+ shafts.forEach((at23, k) => put(`machine_${k}`, "steel", [1.2, 1, 0.8], at23, c.machineMass + c.suspendedMass, { tags: ["mechanical", "elevator_machine"] }));
26882
26985
  const liters = options.waterLiters ?? towerWater(level, options);
26883
26986
  const depth = 2;
26884
26987
  const side = Math.sqrt(liters / 1e3 / depth);
@@ -26919,14 +27022,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
26919
27022
  return n === void 0 ? want : Math.round((want + span / 2) / (span / k)) * (span / k) - span / 2;
26920
27023
  };
26921
27024
  const colZ = column(g.spanZ, g.columnsZ, g.spanZ * 0.35);
26922
- const at22 = options.at ?? [column(g.spanX, g.columnsX, 0), colZ - Math.sign(colZ || 1) * (g.columnSize / 2 + 0.1)];
27025
+ const at23 = options.at ?? [column(g.spanX, g.columnsX, 0), colZ - Math.sign(colZ || 1) * (g.columnSize / 2 + 0.1)];
26923
27026
  const y = storeyBase(g, g.floors - 1);
26924
27027
  const floor2 = `slab_${g.floors - 2}`;
26925
27028
  const part = {
26926
27029
  id: "damper",
26927
27030
  material: c.material,
26928
27031
  shape: box2([side, height, side]),
26929
- position: [at22[0], y + height / 2, at22[1]],
27032
+ position: [at23[0], y + height / 2, at23[1]],
26930
27033
  mass,
26931
27034
  structure: { role: "partition" },
26932
27035
  tags: ["mechanical", "tuned_mass_damper"],
@@ -35428,8 +35531,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
35428
35531
  facadeExtra: (f, rng2, w, d, op) => {
35429
35532
  f.roof = "slab";
35430
35533
  f.finish = rng2() < 0.55 ? "exposed_block" : "partial_plaster";
35431
- f.slab = { exposedRebar: true, waterTank: { u: r22(0.5 + rng2() * (w - 1.5)), v: r22(1 + rng2() * (d - 2)) }, annex: rng2() < 0.55 ? { rect: [0, 0, r22(w * (0.5 + rng2() * 0.3)), r22(d * 0.4)], state: "exposed_brick" } : null };
35432
- if (floorsOf(op) > 1) f.slab.exposedRebar = rng2() < 0.6;
35534
+ const slab = { exposedRebar: true, waterTank: { u: r22(0.5 + rng2() * (w - 1.5)), v: r22(1 + rng2() * (d - 2)) }, annex: rng2() < 0.55 ? { rect: [0, 0, r22(w * (0.5 + rng2() * 0.3)), r22(d * 0.4)], state: "exposed_brick" } : null };
35535
+ f.slab = slab;
35536
+ if (floorsOf(op) > 1) slab.exposedRebar = rng2() < 0.6;
35433
35537
  }
35434
35538
  };
35435
35539
  }
@@ -36789,7 +36893,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36789
36893
  };
36790
36894
  var accessoryTextOf = (locale) => ACCESSORY_TEXT[locale] ?? ACCESSORY_TEXT.en;
36791
36895
 
36792
- // ../game-audio/dist/chunk-DZITVFMJ.js
36896
+ // ../game-audio/dist/chunk-KBTUANNG.js
36793
36897
  var DISTANCE_CURVES = {
36794
36898
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
36795
36899
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -36869,15 +36973,31 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36869
36973
  {
36870
36974
  id: "game",
36871
36975
  master: 0.5,
36872
- noise: { seconds: 1.5, create: "lazy" },
36873
- sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
36976
+ noise: {
36977
+ seconds: 1.5,
36978
+ create: "lazy"
36979
+ },
36980
+ sources: [
36981
+ "jogo:L62077-62081 master 0.5 to the output",
36982
+ "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"
36983
+ ]
36874
36984
  },
36875
36985
  {
36876
36986
  id: "art_scene",
36877
36987
  master: 0.85,
36878
- compressor: { threshold: -12, ratio: 5 },
36879
- reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
36880
- noise: { seconds: 2, create: "eager" },
36988
+ compressor: {
36989
+ threshold: -12,
36990
+ ratio: 5
36991
+ },
36992
+ reverb: {
36993
+ seconds: 2.2,
36994
+ decay: 3.4,
36995
+ wet: 0.4
36996
+ },
36997
+ noise: {
36998
+ seconds: 2,
36999
+ create: "eager"
37000
+ },
36881
37001
  sources: [
36882
37002
  "cena:L1308 compressor -12 dB, ratio 5",
36883
37003
  "cena:L1309 master 0.85",
@@ -36885,12 +37005,36 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36885
37005
  "cena:L1311 wet 0.4",
36886
37006
  "cena:L1312 noise 2 s made with the chain"
36887
37007
  ]
37008
+ },
37009
+ {
37010
+ id: "kit",
37011
+ master: 0.85,
37012
+ compressor: {
37013
+ threshold: -12,
37014
+ ratio: 5
37015
+ },
37016
+ reverb: {
37017
+ seconds: 2.2,
37018
+ decay: 3.4,
37019
+ wet: 0.4
37020
+ },
37021
+ noise: {
37022
+ seconds: 2,
37023
+ create: "eager"
37024
+ },
37025
+ sources: [
37026
+ "sdk: the art scene chain (cena L1308-L1312) with a limiter after the compressor: the page has none, and a blast with its debris and a fire catching reached +7 dBFS"
37027
+ ],
37028
+ limiter: {
37029
+ ceiling: -1,
37030
+ knee: -6
37031
+ }
36888
37032
  }
36889
37033
  ];
36890
37034
  var MIX_PROFILES = Object.fromEntries(
36891
37035
  mix_profiles_default.map((profile) => [profile.id, profile])
36892
37036
  );
36893
- var DEFAULT_MIX = "art_scene";
37037
+ var DEFAULT_MIX = "kit";
36894
37038
  function resolveMix(mix3, profiles = MIX_PROFILES) {
36895
37039
  if (typeof mix3 !== "string") return mix3;
36896
37040
  const profile = profiles[mix3];
@@ -36911,6 +37055,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
36911
37055
  "jogo:L66986 one groan every 0.25 s (`groanBudget`), only nearer than 40 m",
36912
37056
  "jogo:L70472 the budget counts down by the frame time"
36913
37057
  ]
37058
+ },
37059
+ fire_ignite: {
37060
+ maxConcurrent: 2,
37061
+ cooldown: 0.15,
37062
+ sources: [
37063
+ "sdk: many parts catching at once (a blast sets a shelf alight) are one or two ignitions, not forty"
37064
+ ]
37065
+ },
37066
+ fire_crackle: {
37067
+ maxConcurrent: 4,
37068
+ sources: [
37069
+ "sdk: the crackles of every fire share four voices"
37070
+ ]
36914
37071
  }
36915
37072
  };
36916
37073
  var VOICE_GROUPS = voice_groups_default;
@@ -37002,6 +37159,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37002
37159
  const sounds = /* @__PURE__ */ new Map();
37003
37160
  const loops = /* @__PURE__ */ new Map();
37004
37161
  const sets = /* @__PURE__ */ new Map();
37162
+ const samples = /* @__PURE__ */ new Map();
37005
37163
  const owners = /* @__PURE__ */ new Map();
37006
37164
  const registry = {
37007
37165
  register(bank) {
@@ -37018,25 +37176,34 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37018
37176
  sets.set(set2.id, set2);
37019
37177
  owners.set(set2.id, bank.id);
37020
37178
  }
37179
+ for (const sample of bank.samples ?? []) {
37180
+ samples.set(sample.id, sample);
37181
+ owners.set(sample.id, bank.id);
37182
+ }
37021
37183
  },
37022
37184
  patch(id, patch) {
37023
37185
  const sound3 = sounds.get(id);
37024
37186
  if (sound3) sounds.set(id, mergeRecords(sound3, patch));
37025
37187
  const loop = loops.get(id);
37026
37188
  if (loop) loops.set(id, mergeRecords(loop, patch));
37189
+ const sample = samples.get(id);
37190
+ if (sample) samples.set(id, mergeRecords(sample, patch));
37027
37191
  },
37028
37192
  remove(id) {
37029
37193
  sounds.delete(id);
37030
37194
  loops.delete(id);
37031
37195
  sets.delete(id);
37196
+ samples.delete(id);
37032
37197
  owners.delete(id);
37033
37198
  },
37034
37199
  sound: (id) => sounds.get(id),
37035
37200
  loop: (id) => loops.get(id),
37036
37201
  set: (id) => sets.get(id),
37202
+ sample: (id) => samples.get(id),
37037
37203
  soundIds: () => [...sounds.keys()],
37038
37204
  setIds: () => [...sets.keys()],
37039
37205
  loopIds: () => [...loops.keys()],
37206
+ sampleIds: () => [...samples.keys()],
37040
37207
  bankOf: (id) => owners.get(id)
37041
37208
  };
37042
37209
  for (const bank of banks) registry.register(bank);
@@ -37291,7 +37458,32 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37291
37458
  channels.forEach((samples, channel) => buffer.getChannelData(channel).set(samples));
37292
37459
  return buffer;
37293
37460
  }
37461
+ var LIMITER_POINTS = 4097;
37462
+ function limiterCurve(ceilingDb, kneeDb, range3, points = LIMITER_POINTS) {
37463
+ const ceiling = 10 ** (ceilingDb / 20);
37464
+ const knee = Math.min(10 ** (kneeDb / 20), ceiling);
37465
+ const curve = new Float32Array(new ArrayBuffer(points * 4));
37466
+ for (let i = 0; i < points; i++) {
37467
+ const x = (i / (points - 1) * 2 - 1) * range3;
37468
+ const a = Math.abs(x);
37469
+ const y = a <= knee || ceiling === knee ? Math.min(a, ceiling) : knee + (ceiling - knee) * Math.tanh((a - knee) / (ceiling - knee));
37470
+ curve[i] = Math.sign(x) * y;
37471
+ }
37472
+ return curve;
37473
+ }
37294
37474
  function buildChain(context, profile, random) {
37475
+ let limiter;
37476
+ if (profile.limiter) {
37477
+ const range3 = profile.limiter.range ?? 4;
37478
+ const input = context.createGain();
37479
+ input.gain.value = 1 / range3;
37480
+ const shaper = context.createWaveShaper();
37481
+ shaper.curve = limiterCurve(profile.limiter.ceiling, profile.limiter.knee, range3);
37482
+ input.connect(shaper);
37483
+ shaper.connect(context.destination);
37484
+ limiter = { input, shaper };
37485
+ }
37486
+ const output = limiter?.input ?? context.destination;
37295
37487
  let compressor;
37296
37488
  if (profile.compressor) {
37297
37489
  compressor = context.createDynamicsCompressor();
@@ -37300,17 +37492,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37300
37492
  if (profile.compressor.knee !== void 0) compressor.knee.value = profile.compressor.knee;
37301
37493
  if (profile.compressor.attack !== void 0) compressor.attack.value = profile.compressor.attack;
37302
37494
  if (profile.compressor.release !== void 0) compressor.release.value = profile.compressor.release;
37303
- compressor.connect(context.destination);
37495
+ compressor.connect(output);
37304
37496
  }
37305
37497
  const master = context.createGain();
37306
37498
  master.gain.value = profile.master;
37307
- master.connect(compressor ?? context.destination);
37499
+ master.connect(compressor ?? output);
37308
37500
  if (!profile.reverb) {
37309
37501
  return {
37310
37502
  master,
37311
37503
  disconnect: () => {
37312
37504
  master.disconnect();
37313
37505
  compressor?.disconnect();
37506
+ limiter?.input.disconnect();
37507
+ limiter?.shaper.disconnect();
37314
37508
  }
37315
37509
  };
37316
37510
  }
@@ -37329,6 +37523,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37329
37523
  convolver.disconnect();
37330
37524
  master.disconnect();
37331
37525
  compressor?.disconnect();
37526
+ limiter?.input.disconnect();
37527
+ limiter?.shaper.disconnect();
37332
37528
  }
37333
37529
  };
37334
37530
  }
@@ -37338,7 +37534,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37338
37534
  return Constructor ? new Constructor() : null;
37339
37535
  }
37340
37536
  function unlockContext(context) {
37341
- if (context.state === "suspended") void context.resume();
37537
+ if (context.state === "suspended" && typeof context.close === "function") void context.resume();
37342
37538
  }
37343
37539
  var LOOP_START_ABOVE = 2e-3;
37344
37540
  var LOOP_SMOOTHING = { volume: 0.15, frequency: 0.08 };
@@ -37410,9 +37606,19 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37410
37606
  }
37411
37607
  };
37412
37608
  }
37609
+ function outputStage(recipe2, target, gain) {
37610
+ if (!recipe2.pan) {
37611
+ gain.connect(target.output);
37612
+ return { out: gain };
37613
+ }
37614
+ const panner = target.context.createStereoPanner();
37615
+ gain.connect(panner);
37616
+ panner.connect(target.output);
37617
+ return { out: panner, panner };
37618
+ }
37413
37619
  var fixed = (range3) => typeof range3 === "number" ? range3 : 0;
37414
37620
  var fixedFrequency = (automation) => fixed("set" in automation ? automation.set : automation.from);
37415
- function sourceOf(recipe2, target) {
37621
+ function sourceOf2(recipe2, target) {
37416
37622
  if (recipe2.source.kind === "noise") return { source: noiseSource(target, true) };
37417
37623
  const oscillator = target.context.createOscillator();
37418
37624
  oscillator.type = recipe2.source.wave;
@@ -37436,8 +37642,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37436
37642
  gain.gain.value = 0;
37437
37643
  const last = filters.reduce((previous, filter) => previous ? previous.connect(filter) : filter, void 0);
37438
37644
  last?.connect(gain);
37439
- gain.connect(target.output);
37440
- const { source, frequency } = sourceOf(recipe2, target);
37645
+ const output = outputStage(recipe2, target, gain);
37646
+ const { source, frequency } = sourceOf2(recipe2, target);
37441
37647
  recipe2.filters?.bank.forEach((spec, index) => {
37442
37648
  const filter = filters[index];
37443
37649
  filter.type = spec.type;
@@ -37463,11 +37669,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37463
37669
  }
37464
37670
  source.connect(filters[0] ?? gain);
37465
37671
  source.start();
37466
- return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll(sources) };
37672
+ return { gain, ...output, source, ...frequency ? { frequency } : {}, ...filters[0] ? { cutoff: filters[0].frequency } : {}, stop: stopAll(sources) };
37467
37673
  }
37468
37674
  function realizeChainLoop(recipe2, target, volume) {
37469
37675
  const { context } = target;
37470
- const { source, frequency } = sourceOf(recipe2, target);
37676
+ const { source, frequency } = sourceOf2(recipe2, target);
37471
37677
  const filters = (recipe2.filters?.bank ?? []).map(
37472
37678
  (spec) => filterNode(context, { type: spec.type, frequency: { value: fixedFrequency(spec.frequency) }, ...spec.q === void 0 ? {} : { q: fixed(spec.q) } }, target.time)
37473
37679
  );
@@ -37484,10 +37690,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37484
37690
  let tail = source;
37485
37691
  for (const filter of filters) tail = tail.connect(filter);
37486
37692
  tail.connect(gain);
37487
- gain.connect(target.output);
37693
+ const output = outputStage(recipe2, target, gain);
37488
37694
  source.start();
37489
37695
  for (const lfo of lfos) lfo.start();
37490
- return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll([source, ...lfos]) };
37696
+ return { gain, ...output, source, ...frequency ? { frequency } : {}, ...filters[0] ? { cutoff: filters[0].frequency } : {}, stop: stopAll([source, ...lfos]) };
37491
37697
  }
37492
37698
  var INERT = { set() {
37493
37699
  }, stop() {
@@ -37505,12 +37711,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37505
37711
  return nodes !== void 0;
37506
37712
  },
37507
37713
  rewire() {
37508
- nodes?.gain.disconnect();
37509
- nodes?.gain.connect(host.output(loopRecipe.bus));
37714
+ nodes?.out.disconnect();
37715
+ nodes?.out.connect(host.output(loopRecipe.bus));
37510
37716
  },
37511
37717
  stop() {
37512
37718
  nodes?.stop();
37513
- nodes?.gain.disconnect();
37719
+ nodes?.out.disconnect();
37514
37720
  nodes = void 0;
37515
37721
  state = createLoopState(loopRecipe);
37516
37722
  host.unregister(entry2);
@@ -37530,16 +37736,250 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37530
37736
  state = createLoopState({ ...loopRecipe, volume: opening }, true);
37531
37737
  };
37532
37738
  if (isChain) open();
37739
+ let position;
37740
+ let cutoff;
37741
+ const placed = loopRecipe.falloff !== void 0 || loopRecipe.pan === true;
37533
37742
  return {
37534
37743
  set(next) {
37535
37744
  if (isChain && !nodes) open();
37536
37745
  const context = host.ready();
37537
37746
  if (!context) return;
37747
+ position = next.position ?? position;
37748
+ cutoff = next.cutoff ?? cutoff;
37538
37749
  const action = state.set(next);
37539
37750
  if (!action) return;
37540
37751
  nodes ?? (nodes = create4(context, 0));
37541
- nodes.gain.gain.setTargetAtTime(action.volume, context.currentTime, smoothing.volume);
37752
+ const listener = placed && position ? host.listener?.() : void 0;
37753
+ const distance3 = listener && position ? listenerDistance(listener, position, host.distance) : 0;
37754
+ const level = listener ? falloffGain(loopRecipe.falloff, action.volume, distance3) : action.volume;
37755
+ nodes.gain.gain.setTargetAtTime(level, context.currentTime, smoothing.volume);
37542
37756
  if (action.frequency !== void 0 && nodes.frequency) nodes.frequency.setTargetAtTime(action.frequency, context.currentTime, smoothing.frequency);
37757
+ if (cutoff !== void 0 && nodes.cutoff) nodes.cutoff.setTargetAtTime(cutoff / cutoffDivisor(loopRecipe.falloff, distance3), context.currentTime, smoothing.frequency);
37758
+ if (listener && position && nodes.panner) nodes.panner.pan.setTargetAtTime(panFor(listener, position), context.currentTime, smoothing.frequency);
37759
+ },
37760
+ stop: entry2.stop
37761
+ };
37762
+ }
37763
+ var DEFAULT_SAMPLE_FALLOFF = { model: "inverse_square", k: 2e-3, minGain: 2e-3 };
37764
+ var DEFAULT_CROSSOVER = { from: 30, to: 80 };
37765
+ var DEFAULT_SAMPLE_PITCH = 0.04;
37766
+ var DEFAULT_TAIL_GAIN = 0.6;
37767
+ var DEFAULT_SAMPLE_CUTOFF_K = 0.01;
37768
+ var OPEN_CUTOFF = 2e4;
37769
+ var LAYER_FLOOR = 1e-3;
37770
+ function pickTake(count, last, random) {
37771
+ if (count <= 1) return 0;
37772
+ if (last === void 0 || last < 0 || last >= count) return Math.min(count - 1, Math.floor(random() * count));
37773
+ const drawn = Math.min(count - 2, Math.floor(random() * (count - 1)));
37774
+ return drawn >= last ? drawn + 1 : drawn;
37775
+ }
37776
+ function farMixAt(crossover, distance3) {
37777
+ if (crossover.to <= crossover.from) return distance3 >= crossover.to ? 1 : 0;
37778
+ const t = (distance3 - crossover.from) / (crossover.to - crossover.from);
37779
+ return t <= 0 ? 0 : t >= 1 ? 1 : t;
37780
+ }
37781
+ function crossGains(farMix) {
37782
+ return { close: Math.cos(farMix * Math.PI / 2), far: Math.sin(farMix * Math.PI / 2) };
37783
+ }
37784
+ function sampleCutoff(cutoffK, distance3, occlusion = 0) {
37785
+ const byDistance = cutoffK > 0 ? OPEN_CUTOFF / (1 + distance3 * cutoffK) : OPEN_CUTOFF;
37786
+ const occluded = occlusion > 0 ? byDistance / (1 + occlusion * 9) : byDistance;
37787
+ return Math.max(80, Math.min(OPEN_CUTOFF, occluded));
37788
+ }
37789
+ function sampleUrls(sound3) {
37790
+ const tiers = (sound3.tiers ?? []).flatMap((tier) => [...tier.close, ...tier.far ?? []]);
37791
+ const all = [...sound3.close, ...sound3.far ?? [], ...tiers, ...sound3.tail?.indoor ?? [], ...sound3.tail?.outdoor ?? []].map((ref) => ref.url);
37792
+ return [...new Set(all)];
37793
+ }
37794
+ function tierFor(sound3, intensity) {
37795
+ if (intensity === void 0 || !sound3.tiers) return void 0;
37796
+ let chosen;
37797
+ for (const tier of sound3.tiers) if (tier.from <= intensity) chosen = tier;
37798
+ return chosen ?? sound3.tiers[0];
37799
+ }
37800
+ function planSample(sound3, input) {
37801
+ if (sound3.close.length === 0) throw new Error(`sample sound ${sound3.id} has no close take`);
37802
+ const listener = input.listener ?? DEFAULT_LISTENER;
37803
+ const distance3 = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0);
37804
+ const falloff = sound3.falloff ?? DEFAULT_SAMPLE_FALLOFF;
37805
+ const occlusion = Math.max(0, Math.min(1, input.occlusion ?? 0));
37806
+ const level = falloffGain(falloff, (input.volume ?? 1) * (sound3.gain ?? 1), distance3) * (1 - 0.45 * occlusion);
37807
+ if (isSilent(falloff, level) || level <= 0) return null;
37808
+ const pitch = sound3.pitch ?? DEFAULT_SAMPLE_PITCH;
37809
+ const rate = pitch > 0 ? 1 + (input.random() * 2 - 1) * pitch : 1;
37810
+ const environment = input.environment ?? "outdoor";
37811
+ const tier = tierFor(sound3, input.intensity);
37812
+ const close = tier?.close.length ? tier.close : sound3.close;
37813
+ const far = tier?.far?.length ? tier.far : sound3.far ?? [];
37814
+ const farMix = far.length > 0 ? farMixAt(sound3.crossover ?? DEFAULT_CROSSOVER, distance3) : 0;
37815
+ const gains = crossGains(farMix);
37816
+ const layers = [];
37817
+ const take = (role, refs, gain, at3) => {
37818
+ if (refs.length === 0 || gain < LAYER_FLOOR) return;
37819
+ const key2 = `${sound3.id}/${tier && role !== "tail" ? `${tier.name}/` : ""}${role}`;
37820
+ const index = pickTake(refs.length, input.memory?.get(key2), input.random);
37821
+ input.memory?.set(key2, index);
37822
+ const ref = refs[index];
37823
+ layers.push({ role, url: ref.url, take: index, gain: gain * (ref.gain ?? 1) * level, rate, at: at3 });
37824
+ };
37825
+ take("close", close, gains.close, 0);
37826
+ take("far", far, gains.far, 0);
37827
+ const tails = sound3.tail?.[environment] ?? [];
37828
+ take("tail", tails, sound3.tail?.gain ?? DEFAULT_TAIL_GAIN, sound3.tail?.at ?? 0);
37829
+ const pan = (sound3.pan ?? true) && input.position ? panFor(listener, input.position) : void 0;
37830
+ return {
37831
+ kind: "sample",
37832
+ id: sound3.id,
37833
+ bus: sound3.bus,
37834
+ level,
37835
+ distance: distance3,
37836
+ ...pan === void 0 ? {} : { pan },
37837
+ cutoff: sampleCutoff(sound3.cutoffK ?? DEFAULT_SAMPLE_CUTOFF_K, distance3, occlusion),
37838
+ rate,
37839
+ environment,
37840
+ ...tier ? { tier: tier.name } : {},
37841
+ farMix,
37842
+ layers,
37843
+ priority: sound3.priority ?? input.priority ?? busPriority(sound3.bus),
37844
+ ...sound3.group ? { group: sound3.group } : {}
37845
+ };
37846
+ }
37847
+ var DEFAULT_LOOP_CROSSFADE = 0.25;
37848
+ var LOOKAHEAD = 1.5;
37849
+ var VOLUME_SMOOTHING = 0.05;
37850
+ function loopCrossfade(sound3) {
37851
+ if (!sound3.loop) return void 0;
37852
+ return sound3.loop === true ? DEFAULT_LOOP_CROSSFADE : Math.max(0, sound3.loop.crossfade ?? DEFAULT_LOOP_CROSSFADE);
37853
+ }
37854
+ var CROSSFADE_STEPS = 8;
37855
+ var equalPowerIn = (t) => Math.sin(Math.max(0, Math.min(1, t)) * Math.PI / 2);
37856
+ var ramp2 = (param2, direction, at3, seconds) => {
37857
+ const value = (t) => direction === "in" ? equalPowerIn(t) : equalPowerIn(1 - t);
37858
+ param2.setValueAtTime(value(0), at3);
37859
+ for (let step = 1; step <= CROSSFADE_STEPS; step++) param2.linearRampToValueAtTime(value(step / CROSSFADE_STEPS), at3 + seconds * step / CROSSFADE_STEPS);
37860
+ };
37861
+ function createSampleLoopHandle(sound3, host, volume = 0) {
37862
+ const crossfade = loopCrossfade(sound3) ?? DEFAULT_LOOP_CROSSFADE;
37863
+ let level = volume;
37864
+ let stopped = false;
37865
+ let fallback2;
37866
+ let master;
37867
+ let segments = [];
37868
+ let last;
37869
+ let timer;
37870
+ const entry2 = {
37871
+ get created() {
37872
+ return master !== void 0;
37873
+ },
37874
+ rewire() {
37875
+ master?.disconnect();
37876
+ master?.connect(host.output(sound3.bus));
37877
+ },
37878
+ stop() {
37879
+ stopped = true;
37880
+ if (timer !== void 0) clearTimeout(timer);
37881
+ timer = void 0;
37882
+ for (const segment of segments) {
37883
+ try {
37884
+ segment.source.stop();
37885
+ } catch {
37886
+ }
37887
+ segment.source.disconnect();
37888
+ segment.gain.disconnect();
37889
+ }
37890
+ segments = [];
37891
+ master?.disconnect();
37892
+ master = void 0;
37893
+ fallback2?.stop();
37894
+ fallback2 = void 0;
37895
+ host.unregister(entry2);
37896
+ }
37897
+ };
37898
+ const nextBuffer = () => {
37899
+ const takes2 = sound3.close;
37900
+ const take = pickTake(takes2.length, last, host.random);
37901
+ last = take;
37902
+ const ref = takes2[take];
37903
+ return ref ? host.buffer(ref.url) : void 0;
37904
+ };
37905
+ const schedule = (context, output, at3, first) => {
37906
+ const buffer = nextBuffer();
37907
+ if (!buffer) return void 0;
37908
+ const source = context.createBufferSource();
37909
+ source.buffer = buffer;
37910
+ const gain = context.createGain();
37911
+ source.connect(gain);
37912
+ gain.connect(output);
37913
+ const fade = Math.min(crossfade, buffer.duration / 2);
37914
+ if (buffer.duration < 2 * crossfade || sound3.close.length === 1) {
37915
+ source.loop = true;
37916
+ if (!first) ramp2(gain.gain, "in", at3, Math.max(1e-3, fade));
37917
+ source.start(at3);
37918
+ segments.push({ source, gain, end: Number.POSITIVE_INFINITY });
37919
+ return void 0;
37920
+ }
37921
+ if (first) gain.gain.setValueAtTime(1, at3);
37922
+ else ramp2(gain.gain, "in", at3, fade);
37923
+ const end3 = at3 + buffer.duration;
37924
+ ramp2(gain.gain, "out", end3 - fade, fade);
37925
+ source.start(at3);
37926
+ source.stop(end3);
37927
+ segments.push({ source, gain, end: end3 });
37928
+ source.onended = () => {
37929
+ segments = segments.filter((segment) => segment.source !== source);
37930
+ source.disconnect();
37931
+ gain.disconnect();
37932
+ };
37933
+ return end3 - fade;
37934
+ };
37935
+ const pump = (context, output, at3, first) => {
37936
+ if (stopped) return;
37937
+ let next = schedule(context, output, at3, first);
37938
+ while (next !== void 0 && next < context.currentTime + LOOKAHEAD) next = schedule(context, output, Math.max(next, context.currentTime), false);
37939
+ if (next === void 0) return;
37940
+ const wait = Math.max(0, (next - context.currentTime - LOOKAHEAD / 2) * 1e3);
37941
+ timer = setTimeout(() => {
37942
+ timer = void 0;
37943
+ pump(context, output, next, false);
37944
+ }, wait);
37945
+ };
37946
+ const fileLevel = () => level * (sound3.gain ?? 1);
37947
+ const startFiles = () => {
37948
+ if (stopped || master || !host.isReady(sound3)) return master !== void 0;
37949
+ const context = host.ready();
37950
+ if (!context) return false;
37951
+ master = context.createGain();
37952
+ master.gain.setValueAtTime(fileLevel(), context.currentTime);
37953
+ master.connect(host.output(sound3.bus));
37954
+ host.register(entry2);
37955
+ pump(context, master, context.currentTime, true);
37956
+ if (fallback2) {
37957
+ fallback2.set({ volume: 0 });
37958
+ const old = fallback2;
37959
+ fallback2 = void 0;
37960
+ setTimeout(() => old.stop(), VOLUME_SMOOTHING * 6e3);
37961
+ }
37962
+ return true;
37963
+ };
37964
+ const ensure = () => {
37965
+ if (stopped || master) return;
37966
+ if (startFiles()) return;
37967
+ if (!fallback2) {
37968
+ fallback2 = host.fallback(level);
37969
+ void host.load(sound3).then(() => {
37970
+ if (level > 0 || master) startFiles();
37971
+ }).catch(() => void 0);
37972
+ }
37973
+ };
37974
+ if (volume > 0) ensure();
37975
+ return {
37976
+ set(next) {
37977
+ if (stopped) return;
37978
+ if (next.volume !== void 0) level = host.muted(sound3.bus) ? 0 : Math.max(0, next.volume);
37979
+ if (level > 0) ensure();
37980
+ const context = host.ready();
37981
+ if (master && context) master.gain.setTargetAtTime(fileLevel(), context.currentTime, VOLUME_SMOOTHING);
37982
+ fallback2?.set(next.volume === void 0 ? next : { ...next, volume: level });
37543
37983
  },
37544
37984
  stop: entry2.stop
37545
37985
  };
@@ -37818,7 +38258,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37818
38258
  return voiceOf(oscillators);
37819
38259
  }
37820
38260
  var end2 = (layer2, start) => start + layer2.duration + layer2.tail;
37821
- function outputStage(plan, layer2, target) {
38261
+ function outputStage2(plan, layer2, target) {
37822
38262
  const { context } = target;
37823
38263
  const level = gainNode(context, layer2.level);
37824
38264
  let node = level;
@@ -37850,7 +38290,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37850
38290
  let tail = source;
37851
38291
  for (const filter of filters) tail = tail.connect(filter);
37852
38292
  tail.connect(envelope2);
37853
- envelope2.connect(outputStage(plan, layer2, target));
38293
+ envelope2.connect(outputStage2(plan, layer2, target));
37854
38294
  source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
37855
38295
  source.stop(end2(layer2, time));
37856
38296
  return [source];
@@ -37867,7 +38307,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37867
38307
  const envelope2 = context.createGain();
37868
38308
  applyEnvelope(envelope2.gain, layer2.envelope, time);
37869
38309
  oscillator.connect(envelope2);
37870
- envelope2.connect(outputStage(plan, layer2, target));
38310
+ envelope2.connect(outputStage2(plan, layer2, target));
37871
38311
  oscillator.start(time);
37872
38312
  oscillator.stop(end2(layer2, time));
37873
38313
  return [oscillator];
@@ -37899,7 +38339,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37899
38339
  const envelope2 = context.createGain();
37900
38340
  applyEnvelope(envelope2.gain, layer2.envelope, time);
37901
38341
  mix3.connect(envelope2);
37902
- envelope2.connect(outputStage(plan, layer2, target));
38342
+ envelope2.connect(outputStage2(plan, layer2, target));
37903
38343
  const stop = end2(layer2, time);
37904
38344
  oscillator.start(time);
37905
38345
  vibrato.start(time);
@@ -38071,6 +38511,135 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38071
38511
  reset: () => last.clear()
38072
38512
  };
38073
38513
  }
38514
+ function fetchLoader(baseUrl) {
38515
+ return async (url2) => {
38516
+ const target = baseUrl ? new URL(url2, baseUrl).toString() : url2;
38517
+ const response = await fetch(target);
38518
+ if (!response.ok) throw new Error(`${response.status} ${response.statusText} for ${target}`);
38519
+ return response.arrayBuffer();
38520
+ };
38521
+ }
38522
+ var decode = (context, bytes) => new Promise((resolve2, reject) => {
38523
+ const result2 = context.decodeAudioData(bytes, resolve2, reject);
38524
+ result2?.catch?.(reject);
38525
+ });
38526
+ function createSampleStore(loader = fetchLoader()) {
38527
+ const buffers = /* @__PURE__ */ new Map();
38528
+ const pending = /* @__PURE__ */ new Map();
38529
+ const failed = /* @__PURE__ */ new Map();
38530
+ let generation = 0;
38531
+ const loadOne = (url2, context) => {
38532
+ const existing = buffers.get(url2);
38533
+ if (existing) return Promise.resolve(existing);
38534
+ const inFlight = pending.get(url2);
38535
+ if (inFlight) return inFlight;
38536
+ const started = generation;
38537
+ failed.delete(url2);
38538
+ const promise = loader(url2).then((bytes) => decode(context, bytes)).then((buffer) => {
38539
+ if (started === generation) buffers.set(url2, buffer);
38540
+ return buffer;
38541
+ }).catch((error) => {
38542
+ if (started === generation) failed.set(url2, error instanceof Error ? error.message : String(error));
38543
+ throw error;
38544
+ }).finally(() => {
38545
+ if (pending.get(url2) === promise) pending.delete(url2);
38546
+ });
38547
+ pending.set(url2, promise);
38548
+ return promise;
38549
+ };
38550
+ return {
38551
+ status(url2) {
38552
+ if (buffers.has(url2)) return "ready";
38553
+ if (pending.has(url2)) return "loading";
38554
+ if (failed.has(url2)) return "failed";
38555
+ return "none";
38556
+ },
38557
+ buffer: (url2) => buffers.get(url2),
38558
+ ready: (urls) => urls.every((url2) => buffers.has(url2)),
38559
+ seconds: (url2) => buffers.get(url2)?.duration,
38560
+ async load(urls, context) {
38561
+ const results = await Promise.allSettled(urls.map((url2) => loadOne(url2, context)));
38562
+ const loaded = [];
38563
+ const errors = [];
38564
+ results.forEach((result2, index) => {
38565
+ const url2 = urls[index];
38566
+ if (result2.status === "fulfilled") loaded.push(url2);
38567
+ else errors.push({ url: url2, error: result2.reason instanceof Error ? result2.reason.message : String(result2.reason) });
38568
+ });
38569
+ return { loaded, failed: errors };
38570
+ },
38571
+ clear() {
38572
+ generation++;
38573
+ buffers.clear();
38574
+ pending.clear();
38575
+ failed.clear();
38576
+ }
38577
+ };
38578
+ }
38579
+ function realizeSample(plan, target) {
38580
+ const context = target.context;
38581
+ let head = target.output;
38582
+ if (plan.pan !== void 0 && typeof context.createStereoPanner === "function") {
38583
+ const panner = context.createStereoPanner();
38584
+ panner.pan.value = plan.pan;
38585
+ panner.connect(head);
38586
+ head = panner;
38587
+ }
38588
+ if (plan.cutoff < OPEN_CUTOFF) {
38589
+ const lowpass = context.createBiquadFilter();
38590
+ lowpass.type = "lowpass";
38591
+ lowpass.frequency.value = plan.cutoff;
38592
+ lowpass.Q.value = 0.7;
38593
+ lowpass.connect(head);
38594
+ head = lowpass;
38595
+ }
38596
+ const voice2 = context.createGain();
38597
+ voice2.connect(head);
38598
+ head = voice2;
38599
+ const sources = [];
38600
+ for (const layer2 of plan.layers) {
38601
+ const buffer = target.buffer(layer2.url);
38602
+ if (!buffer) continue;
38603
+ const source = context.createBufferSource();
38604
+ source.buffer = buffer;
38605
+ source.playbackRate.value = layer2.rate;
38606
+ const gain = gainNode(context, layer2.gain);
38607
+ source.connect(gain);
38608
+ gain.connect(head);
38609
+ source.start(target.time + layer2.at);
38610
+ sources.push(source);
38611
+ }
38612
+ const realized = voiceOf(sources);
38613
+ return {
38614
+ ...realized,
38615
+ fade(at3, seconds) {
38616
+ voice2.gain.setValueAtTime(1, at3);
38617
+ voice2.gain.linearRampToValueAtTime(0, at3 + seconds);
38618
+ for (const source of sources) {
38619
+ try {
38620
+ source.stop(at3 + seconds);
38621
+ } catch {
38622
+ }
38623
+ }
38624
+ }
38625
+ };
38626
+ }
38627
+ function sampleLength(plan, seconds) {
38628
+ let length2 = 0;
38629
+ for (const layer2 of plan.layers) length2 = Math.max(length2, layer2.at + (seconds(layer2.url) ?? 0) / layer2.rate);
38630
+ return length2;
38631
+ }
38632
+ function limiterCurve2(knee, ceiling, range3, points = 2049) {
38633
+ const curve = new Float32Array(points);
38634
+ const room = Math.max(1e-6, ceiling - knee);
38635
+ for (let index = 0; index < points; index++) {
38636
+ const value = (index / (points - 1) * 2 - 1) * range3;
38637
+ const size = Math.abs(value);
38638
+ const bent = size <= knee ? size : knee + room * Math.tanh((size - knee) / room);
38639
+ curve[index] = Math.sign(value) * bent;
38640
+ }
38641
+ return curve;
38642
+ }
38074
38643
  var usesNoise = (recipe2) => recipe2.layers.some((layer2) => layer2.source.kind !== "osc");
38075
38644
  var planUsesNoise = (plan) => plan.layers.some((layer2) => layer2.source.kind !== "osc");
38076
38645
  function createAudioEngine(options = {}) {
@@ -38090,6 +38659,43 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38090
38659
  let chain = null;
38091
38660
  let listener = DEFAULT_LISTENER;
38092
38661
  let lastTime = 0;
38662
+ let environment = options.environment ?? "outdoor";
38663
+ const sampleLimiters = /* @__PURE__ */ new Map();
38664
+ const sampleOutput = (bus) => {
38665
+ const setting = options.samples?.limiter;
38666
+ if (setting === false || !context) return outputFor(bus);
38667
+ const existing = sampleLimiters.get(bus);
38668
+ if (existing) return existing;
38669
+ const range3 = 4;
38670
+ const input = context.createGain();
38671
+ input.gain.value = 1 / range3;
38672
+ const shaper = context.createWaveShaper();
38673
+ shaper.curve = limiterCurve2(setting?.knee ?? 0.5, setting?.ceiling ?? 0.891, range3);
38674
+ ensureBus(bus);
38675
+ input.connect(shaper);
38676
+ shaper.connect(outputFor(bus));
38677
+ sampleLimiters.set(bus, input);
38678
+ return input;
38679
+ };
38680
+ const chokes = /* @__PURE__ */ new Map();
38681
+ const choked = (id) => {
38682
+ const list2 = (chokes.get(id) ?? []).filter((entry2) => active.has(entry2.id));
38683
+ chokes.set(id, list2);
38684
+ return list2;
38685
+ };
38686
+ const watchers = /* @__PURE__ */ new Set();
38687
+ const announce = (event) => {
38688
+ options.onVoiceStart?.(event);
38689
+ for (const watcher of watchers) watcher(event);
38690
+ };
38691
+ const sources = [];
38692
+ const baseLoader = options.samples?.loader ?? fetchLoader(options.samples?.baseUrl);
38693
+ const sampleStore = createSampleStore((url2) => (sources.find((source) => source.accepts(url2))?.loader ?? baseLoader)(url2));
38694
+ const memory = /* @__PURE__ */ new Map();
38695
+ const latencyOf = (audio) => {
38696
+ const live = audio;
38697
+ return (live.baseLatency ?? 0) + (live.outputLatency ?? 0);
38698
+ };
38093
38699
  const outputFor = (bus) => busNodes.get(bus) ?? chain?.master;
38094
38700
  const watched = /* @__PURE__ */ new Set();
38095
38701
  const meterOf = (bus) => {
@@ -38146,17 +38752,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38146
38752
  ...options.occlusion && playOptions.position && recipe2.falloff?.model !== "none" ? { occlusion: options.occlusion(playOptions.position) } : {}
38147
38753
  });
38148
38754
  const startQueue = options.deferredStart ? createStartQueue(options.deferredStart) : null;
38149
- const build = (plan, store, delay) => {
38755
+ const build = (plan, store, delay, requestedAt, fallbackOf) => {
38150
38756
  const audio = context;
38151
- return realize(plan, {
38757
+ const time = audio.currentTime + delay;
38758
+ const realized = realize(plan, {
38152
38759
  context: audio,
38153
38760
  output: outputFor(plan.bus),
38154
38761
  ...chain?.wet ? { wet: chain.wet } : {},
38155
38762
  noise: () => noiseBuffer(store, audio, mix3.noise.seconds),
38156
- time: audio.currentTime + delay
38763
+ time
38764
+ });
38765
+ const recipeBank = registry.bankOf(plan.id);
38766
+ announce({
38767
+ id: plan.id,
38768
+ kind: "recipe",
38769
+ ...fallbackOf ? { fallbackOf } : {},
38770
+ ...recipeBank ? { bank: recipeBank } : {},
38771
+ requestedAt,
38772
+ startAt: time,
38773
+ outputLatency: latencyOf(audio),
38774
+ level: plan.level,
38775
+ distance: plan.distance,
38776
+ ...plan.pan === void 0 ? {} : { pan: plan.pan }
38157
38777
  });
38778
+ return realized;
38158
38779
  };
38159
- const deferVoice = (plan, store, voiceId, delay) => {
38780
+ const deferVoice = (plan, store, voiceId, delay, fallbackOf) => {
38781
+ const requestedAt = context?.currentTime ?? 0;
38160
38782
  let built2 = null;
38161
38783
  let cancelled = false;
38162
38784
  const pending = {
@@ -38170,7 +38792,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38170
38792
  };
38171
38793
  startQueue.push({
38172
38794
  run() {
38173
- if (!cancelled && context) built2 = build(plan, store, delay);
38795
+ if (!cancelled && context) built2 = build(plan, store, delay, requestedAt, fallbackOf);
38174
38796
  },
38175
38797
  drop() {
38176
38798
  if (voiceId === void 0) return;
@@ -38180,13 +38802,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38180
38802
  });
38181
38803
  return pending;
38182
38804
  };
38183
- const start = (plan, store, voiceId, delay = 0) => {
38184
- const realized = startQueue ? deferVoice(plan, store, voiceId, delay) : build(plan, store, delay);
38805
+ const start = (plan, store, voiceId, delay = 0, fallbackOf, urgent = false) => {
38806
+ const realized = startQueue && !urgent ? deferVoice(plan, store, voiceId, delay, fallbackOf) : build(plan, store, delay, context?.currentTime ?? 0, fallbackOf);
38185
38807
  if (voiceId === void 0) return null;
38186
38808
  voices.setLength(voiceId, plan.length + delay);
38187
38809
  active.set(voiceId, realized);
38188
38810
  return {
38189
38811
  id: voiceId,
38812
+ kind: "recipe",
38190
38813
  plan,
38191
38814
  stop() {
38192
38815
  realized.stop();
@@ -38210,7 +38833,85 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38210
38833
  const set2 = registry.set(id);
38211
38834
  return set2 ? chooser.pick(set2, random) : { id, rate: 1 };
38212
38835
  };
38213
- const playWith = (asked, playOptions, streamRandom, store) => {
38836
+ const loadSounds = (sounds) => {
38837
+ const audio = ready();
38838
+ if (!audio) return Promise.resolve({ loaded: [], failed: sounds.flatMap(sampleUrls).map((url2) => ({ url: url2, error: "no audio context" })) });
38839
+ return sampleStore.load([...new Set(sounds.flatMap(sampleUrls))], audio);
38840
+ };
38841
+ const sampleInput = (playOptions, random, withMemory) => ({
38842
+ random,
38843
+ listener,
38844
+ environment: playOptions.environment ?? environment,
38845
+ ...withMemory ? { memory } : {},
38846
+ ...options.distance ? { distanceMode: options.distance } : {},
38847
+ ...playOptions.volume === void 0 ? {} : { volume: playOptions.volume },
38848
+ ...playOptions.intensity === void 0 ? {} : { intensity: playOptions.intensity },
38849
+ ...playOptions.distance === void 0 ? {} : { distance: playOptions.distance },
38850
+ ...playOptions.position ? { position: playOptions.position } : {},
38851
+ ...playOptions.occlusion !== void 0 ? { occlusion: playOptions.occlusion } : options.occlusion && playOptions.position ? { occlusion: options.occlusion(playOptions.position) } : {}
38852
+ });
38853
+ const playSample = (sample, playOptions, random, store) => {
38854
+ if (busSettings.get(sample.bus)?.muted) return null;
38855
+ if (!sampleStore.ready(sampleUrls(sample))) {
38856
+ if (options.samples?.autoLoad !== false && ready()) void loadSounds([sample]).catch(() => void 0);
38857
+ if (sample.fallback && sample.fallback !== sample.id) return playWith(sample.fallback, playOptions, random, store, sample.id);
38858
+ return registry.sound(sample.id) ? void 0 : null;
38859
+ }
38860
+ const plan = planSample(sample, sampleInput(playOptions, playOptions.seed === void 0 ? random : createRng3(playOptions.seed), true));
38861
+ if (!plan) return null;
38862
+ const audio = ready();
38863
+ if (!audio) return null;
38864
+ unlockContext(audio);
38865
+ advance();
38866
+ const voiceId = admit(plan, plan.level, plan.distance);
38867
+ if (voiceId === null) return null;
38868
+ const requestedAt = audio.currentTime;
38869
+ const time = requestedAt + (playOptions.delay ?? 0);
38870
+ const chokeList = sample.choke ? choked(sample.id) : void 0;
38871
+ const realized = realizeSample(plan, { context: audio, output: sampleOutput(plan.bus), buffer: (url2) => sampleStore.buffer(url2), time });
38872
+ const sampleBank = registry.bankOf(plan.id);
38873
+ announce({
38874
+ id: plan.id,
38875
+ kind: "sample",
38876
+ ...sampleBank ? { bank: sampleBank } : {},
38877
+ requestedAt,
38878
+ startAt: time,
38879
+ outputLatency: latencyOf(audio),
38880
+ level: plan.level,
38881
+ distance: plan.distance,
38882
+ ...plan.pan === void 0 ? {} : { pan: plan.pan },
38883
+ layers: plan.layers.map((layer2) => ({ role: layer2.role, url: layer2.url, take: layer2.take, rate: layer2.rate })),
38884
+ ...plan.tier ? { tier: plan.tier } : {}
38885
+ });
38886
+ voices.setLength(voiceId, sampleLength(plan, (url2) => sampleStore.seconds(url2)) + (playOptions.delay ?? 0));
38887
+ active.set(voiceId, realized);
38888
+ if (chokeList && sample.choke) {
38889
+ chokeList.push({ id: voiceId, voice: realized });
38890
+ while (chokeList.length > Math.max(1, sample.choke.voices)) {
38891
+ const oldest = chokeList.shift();
38892
+ if (!oldest) break;
38893
+ oldest.voice.fade(time, sample.choke.fade ?? 0.03);
38894
+ voices.release(oldest.id);
38895
+ active.delete(oldest.id);
38896
+ }
38897
+ }
38898
+ return {
38899
+ id: voiceId,
38900
+ kind: "sample",
38901
+ plan,
38902
+ stop() {
38903
+ realized.stop();
38904
+ voices.release(voiceId);
38905
+ active.delete(voiceId);
38906
+ }
38907
+ };
38908
+ };
38909
+ const playWith = (asked, playOptions, streamRandom, store, fallbackOf) => {
38910
+ const sample = fallbackOf === void 0 ? registry.sample(asked) : void 0;
38911
+ if (sample) {
38912
+ const played = playSample(sample, playOptions, streamRandom, store);
38913
+ if (played !== void 0) return played;
38914
+ }
38214
38915
  const seeded = playOptions.seed === void 0 ? null : createRng3(playOptions.seed);
38215
38916
  const { id, rate } = resolve2(asked, seeded ?? streamRandom);
38216
38917
  const random = seeded ?? streamRandom;
@@ -38228,7 +38929,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38228
38929
  if (voiceId === null) return null;
38229
38930
  if (!silent && usesNoise(recipe2)) prepareNoise(store, audio, mix3.noise.seconds);
38230
38931
  const plan = planSound(recipe2, inputFor(recipe2, playOptions, random));
38231
- return plan ? start(retunePlan(plan, rate), store, voiceId, playOptions.delay) : null;
38932
+ return plan ? start(retunePlan(plan, rate), store, voiceId, playOptions.delay, fallbackOf, playOptions.urgent) : null;
38232
38933
  };
38233
38934
  const randomFor = (playOptions) => {
38234
38935
  if (playOptions.channel === void 0) return engineRandom;
@@ -38250,12 +38951,42 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38250
38951
  prepareNoise: () => context && prepareNoise(shared2, context, mix3.noise.seconds),
38251
38952
  noise: () => noiseBuffer(shared2, context, mix3.noise.seconds),
38252
38953
  register: (loop) => void loops.add(loop),
38253
- unregister: (loop) => void loops.delete(loop)
38954
+ unregister: (loop) => void loops.delete(loop),
38955
+ listener: () => listener,
38956
+ ...options.distance ? { distance: options.distance } : {}
38254
38957
  };
38255
38958
  return {
38256
38959
  registry,
38257
38960
  register: (bank) => registry.register(bank),
38258
38961
  plan: (id, playOptions = {}) => planWith(id, playOptions, createRng3(playOptions.seed ?? seed)),
38962
+ planSample(id, playOptions = {}) {
38963
+ const sample = registry.sample(id);
38964
+ return sample ? planSample(sample, sampleInput(playOptions, createRng3(playOptions.seed ?? seed), false)) : null;
38965
+ },
38966
+ loadSamples(ids) {
38967
+ const chosen = (ids ?? registry.sampleIds()).map((id) => registry.sample(id)).filter((sample) => sample !== void 0);
38968
+ return loadSounds(chosen);
38969
+ },
38970
+ sampleStatus(id) {
38971
+ const sample = registry.sample(id);
38972
+ if (!sample) return "none";
38973
+ const statuses = sampleUrls(sample).map((url2) => sampleStore.status(url2));
38974
+ if (statuses.every((status) => status === "ready")) return "ready";
38975
+ if (statuses.includes("failed")) return "failed";
38976
+ if (statuses.includes("loading")) return "loading";
38977
+ return statuses.includes("ready") ? "loading" : "none";
38978
+ },
38979
+ setEnvironment(next) {
38980
+ environment = next;
38981
+ },
38982
+ output: () => chain?.master ?? null,
38983
+ addSampleSource(accepts, loader) {
38984
+ sources.push({ accepts, loader });
38985
+ },
38986
+ watchVoices(listener2) {
38987
+ watchers.add(listener2);
38988
+ return () => void watchers.delete(listener2);
38989
+ },
38259
38990
  play: (id, playOptions = {}) => playWith(id, playOptions, randomFor(playOptions), shared2),
38260
38991
  playPlan(plan, playOptions = {}) {
38261
38992
  const audio = ready();
@@ -38267,7 +38998,26 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38267
38998
  if (planUsesNoise(plan)) prepareNoise(shared2, audio, mix3.noise.seconds);
38268
38999
  return start(plan, shared2, voiceId, playOptions.delay);
38269
39000
  },
38270
- loop: (id, loopOptions = {}) => createLoopHandle(registry.loop(id), loopHost, loopOptions.volume),
39001
+ loop(id, loopOptions = {}) {
39002
+ const sample = registry.sample(id);
39003
+ if (!sample?.loop) return createLoopHandle(registry.loop(id), loopHost, loopOptions.volume);
39004
+ return createSampleLoopHandle(
39005
+ sample,
39006
+ {
39007
+ ready,
39008
+ output: sampleOutput,
39009
+ buffer: (url2) => sampleStore.buffer(url2),
39010
+ isReady: (sound3) => sampleStore.ready(sampleUrls(sound3)),
39011
+ load: (sound3) => options.samples?.autoLoad === false ? Promise.resolve() : loadSounds([sound3]),
39012
+ fallback: (volume) => createLoopHandle(registry.loop(sample.fallback ?? sample.id) ?? registry.loop(sample.id), loopHost, volume),
39013
+ random: engineRandom,
39014
+ register: loopHost.register,
39015
+ unregister: loopHost.unregister,
39016
+ muted: (bus) => busSettings.get(bus)?.muted === true
39017
+ },
39018
+ loopOptions.volume
39019
+ );
39020
+ },
38271
39021
  channel(channelOptions) {
38272
39022
  const random = channelOptions.random ?? (channelOptions.seed === void 0 ? engineRandom : createRng3(channelOptions.seed));
38273
39023
  const store = channelOptions.ownNoise ? createNoiseStore(random) : shared2;
@@ -38348,6 +39098,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38348
39098
  busNodes.clear();
38349
39099
  meters.clear();
38350
39100
  shared2.reset();
39101
+ sampleStore.clear();
39102
+ memory.clear();
39103
+ sampleLimiters.clear();
39104
+ chokes.clear();
38351
39105
  }
38352
39106
  };
38353
39107
  }
@@ -38396,6 +39150,420 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38396
39150
  }
38397
39151
  ]
38398
39152
  };
39153
+ var entries = (category, list2) => list2.map(([id, priority, current]) => ({ id, category, priority, ...current ? { current } : {} }));
39154
+ var SFX_CATALOG = [
39155
+ ...entries("armas", [
39156
+ ["pistol_22lr", 2, "sfx_pistol"],
39157
+ ["pistol_9mm", 1, "sfx_pistol"],
39158
+ ["pistol_45", 1, "sfx_pistol"],
39159
+ ["revolver_38_357", 2, "sfx_pistol"],
39160
+ ["revolver_44_50ae", 3, "sfx_pistol"],
39161
+ ["smg_9mm_auto", 1, "sfx_pistol"],
39162
+ ["shotgun_12", 1, "sfx_shotgun"],
39163
+ ["rifle_5_56", 1, "sfx_rifle"],
39164
+ ["rifle_7_62x39", 1, "sfx_rifle"],
39165
+ ["rifle_auto_burst", 1, "sfx_rifle"],
39166
+ ["rifle_308_3006", 2, "sfx_rifle"],
39167
+ ["sniper_338_300wm", 2, "sfx_rifle"],
39168
+ ["antimateriel_50bmg", 3, "sfx_rifle"],
39169
+ ["suppressed_pistol", 2],
39170
+ ["suppressed_rifle", 2],
39171
+ ["gunshot_far_pistol", 1],
39172
+ ["gunshot_far_rifle", 1],
39173
+ ["gunshot_far_shotgun", 1],
39174
+ ["gunshot_far_heavy", 2],
39175
+ ["gunshot_tail_indoor", 1],
39176
+ ["gunshot_tail_outdoor", 1],
39177
+ ["bullet_crack_by", 2],
39178
+ ["bullet_whiz", 2],
39179
+ ["ricochet_metal", 2, "ping"],
39180
+ ["dry_fire", 1],
39181
+ ["mag_out", 1],
39182
+ ["mag_in", 1],
39183
+ ["slide_pull_release", 1],
39184
+ ["pump_cycle", 1],
39185
+ ["casing_drop_concreto_pistol", 1],
39186
+ ["casing_drop_concreto_rifle", 1],
39187
+ ["casing_drop_concreto_shell", 1],
39188
+ ["casing_drop_madeira_pistol", 1],
39189
+ ["casing_drop_madeira_rifle", 1],
39190
+ ["casing_drop_madeira_shell", 1],
39191
+ ["casing_drop_metal_pistol", 1],
39192
+ ["casing_drop_metal_rifle", 1],
39193
+ ["casing_drop_metal_shell", 1],
39194
+ ["casing_drop_terra_pistol", 1],
39195
+ ["casing_drop_terra_rifle", 1],
39196
+ ["casing_drop_terra_shell", 1],
39197
+ ["rocket_fire", 2],
39198
+ ["smg_45_auto", 2, "sfx_pistol"],
39199
+ ["weapon_draw", 2],
39200
+ ["suppressor_attach", 3],
39201
+ ["suppressor_detach", 3]
39202
+ ]),
39203
+ ...entries("explosoes-fogo", [
39204
+ ["explosion_grenade", 1, "boom"],
39205
+ ["explosion_big", 1, "boom_big"],
39206
+ ["explosion_car", 2, "boom_big"],
39207
+ ["explosion_far", 1],
39208
+ ["debris_rain_stone", 1],
39209
+ ["molotov_break_ignite", 1, "glass_shatter"],
39210
+ ["fire_ignite", 1, "hiss"],
39211
+ ["fire_loop_small", 1],
39212
+ ["fire_loop_large", 1],
39213
+ ["fire_extinguish", 2, "hiss"],
39214
+ ["gas_leak_hiss", 2],
39215
+ ["structure_collapse", 2, "crumble"],
39216
+ ["grenade_bounce", 1],
39217
+ ["c4_plant", 2],
39218
+ ["c4_beep", 2],
39219
+ ["c4_disarm", 2]
39220
+ ]),
39221
+ ...entries("personagem", [
39222
+ ["footstep_concrete_walk", 1, "sfx_step"],
39223
+ ["footstep_wood_walk", 1, "sfx_step"],
39224
+ ["footstep_dirt_walk", 1, "sfx_step"],
39225
+ ["footstep_grass_walk", 1, "sfx_step"],
39226
+ ["footstep_gravel_walk", 1, "sfx_step"],
39227
+ ["land_concrete", 1, "sfx_step"],
39228
+ ["punch_swing", 1],
39229
+ ["punch_hit_body", 1, "sfx_flesh"],
39230
+ ["kick_hit", 1, "sfx_flesh"],
39231
+ ["knife_stab", 1],
39232
+ ["blunt_hit", 1],
39233
+ ["bite", 1, "sfx_flesh"],
39234
+ ["body_fall", 1],
39235
+ ["player_hurt", 2],
39236
+ ["footstep_tile_walk", 1, "sfx_step"],
39237
+ ["footstep_water_walk", 1, "sfx_step"],
39238
+ ["footstep_grate_walk", 2, "sfx_step"],
39239
+ ["footstep_duct_walk", 3, "sfx_step"],
39240
+ ["ladder_climb", 2],
39241
+ ["swim", 2],
39242
+ ["zombie_groan", 1],
39243
+ ["zombie_alert", 1],
39244
+ ["zombie_attack", 1],
39245
+ ["zombie_pain", 1],
39246
+ ["zombie_claw_hit", 1, "sfx_flesh"],
39247
+ ["zombie_claw_miss", 2]
39248
+ ]),
39249
+ ...entries("veiculos", [
39250
+ ["engine_loop_car", 1],
39251
+ ["engine_start", 1],
39252
+ ["crash_light", 1, "crash"],
39253
+ ["crash_heavy", 1, "crash"],
39254
+ ["car_door_open", 1, "latch"],
39255
+ ["car_door_close", 1, "shut_metal"],
39256
+ ["horn_car", 2, "horn"],
39257
+ ["tire_skid_asphalt", 2]
39258
+ ]),
39259
+ ...entries("itens-portas", [
39260
+ ["item_pickup_metal", 1],
39261
+ ["can_open", 1],
39262
+ ["can_crush", 1],
39263
+ ["door_wood_open", 1, "latch"],
39264
+ ["door_wood_close", 1, "shut_wood"],
39265
+ ["door_creak", 1, "creak_wood"],
39266
+ ["door_locked_rattle", 1, "rattle"],
39267
+ ["drawer_open", 2, "slide"],
39268
+ ["fridge_close", 2, "shut_metal"]
39269
+ ]),
39270
+ ...entries("ambiente", [
39271
+ ["rain_loop", 2],
39272
+ ["wind_loop", 2],
39273
+ ["thunder_near", 2],
39274
+ ["thunder_far", 2],
39275
+ ["water_splash", 2],
39276
+ ["helicopter_loop", 3]
39277
+ ])
39278
+ ];
39279
+ var SFX_MATERIALS = [
39280
+ ["steel", "metal"],
39281
+ ["aluminium", "metal"],
39282
+ ["wood", "wood"],
39283
+ ["plywood", "wood"],
39284
+ ["mdf", "wood"],
39285
+ ["glass", "glass"],
39286
+ ["tempered_glass", "glass"],
39287
+ ["ceramic", "glass"],
39288
+ ["concrete", "masonry"],
39289
+ ["brick", "masonry"],
39290
+ ["plaster", "masonry"],
39291
+ ["plastic", "soft"],
39292
+ ["fabric", "soft"],
39293
+ ["paper", "soft"],
39294
+ ["foliage", "soft"],
39295
+ ["soil", "soft"],
39296
+ ["flesh", "soft"]
39297
+ ];
39298
+ var SFX_BLOWS = ["bullet", "blunt", "blade", "break", "drag"];
39299
+ var CURRENT_BY_GROUP = {
39300
+ metal: { bullet: "clang", blunt: "clang", blade: "clang", break: "clang", drag: "drag_metal" },
39301
+ wood: { bullet: "wood_hit", blunt: "wood_hit", blade: "wood_hit", break: "crack", drag: "drag_wood" },
39302
+ glass: { bullet: "ping", blunt: "ping", break: "glass_shatter", drag: "drag_glass" },
39303
+ masonry: { bullet: "chip", blunt: "chip", blade: "chip", break: "crumble", drag: "drag_masonry" },
39304
+ soft: { bullet: "thud", blunt: "thud", blade: "thud", break: "thud", drag: "drag_soft" }
39305
+ };
39306
+ var impactSoundId = (material, blow3) => `impact_${material}_${blow3}`;
39307
+ var SFX_IMPACTS = SFX_MATERIALS.flatMap(
39308
+ ([material, group]) => SFX_BLOWS.map((blow3) => {
39309
+ const current = CURRENT_BY_GROUP[group]?.[blow3];
39310
+ return { id: impactSoundId(material, blow3), category: "impactos", priority: ["steel", "wood", "glass", "concrete", "brick", "soil", "flesh"].includes(material) && blow3 === "bullet" ? 1 : 2, ...current ? { current } : {} };
39311
+ })
39312
+ );
39313
+ var CALIBRATION_ID = "calibration_shot";
39314
+ var PREFIX = "calibration/";
39315
+ var url = (name) => `${PREFIX}${name}`;
39316
+ var CALIBRATION_SOUND = {
39317
+ id: CALIBRATION_ID,
39318
+ bus: "weapons",
39319
+ close: ["close_1.wav", "close_2.wav", "close_3.wav", "close_4.wav"].map((name) => ({ url: url(name) })),
39320
+ far: ["far_1.wav", "far_2.wav"].map((name) => ({ url: url(name) })),
39321
+ tiers: [
39322
+ { name: "light", from: 0, close: ["light_1.wav", "light_2.wav"].map((name) => ({ url: url(name) })) },
39323
+ { name: "medium", from: 0.34, close: ["close_1.wav", "close_2.wav", "close_3.wav", "close_4.wav"].map((name) => ({ url: url(name) })) },
39324
+ { name: "heavy", from: 0.67, close: ["heavy_1.wav", "heavy_2.wav"].map((name) => ({ url: url(name) })) }
39325
+ ],
39326
+ tail: { indoor: [{ url: url("tail_in.wav") }], outdoor: [{ url: url("tail_out.wav") }] },
39327
+ pitch: 0.04,
39328
+ choke: { voices: 2 },
39329
+ fallback: "sfx_pistol",
39330
+ sources: ["sdk: calibration takes synthesized by the harness (measure/calibration.ts), no file, no licence"]
39331
+ };
39332
+ var CALIBRATION_LOOP_ID = "calibration_loop";
39333
+ var CALIBRATION_LOOP_SOUND = {
39334
+ id: CALIBRATION_LOOP_ID,
39335
+ bus: "ambience",
39336
+ close: ["loop_1.wav", "loop_2.wav", "loop_3.wav"].map((name) => ({ url: url(name) })),
39337
+ loop: { crossfade: 0.15 },
39338
+ sources: ["sdk: calibration takes synthesized by the harness (measure/calibration.ts), no file, no licence"]
39339
+ };
39340
+ var CRITERIA_TARGETS = {
39341
+ /** C1: event to the first sample, and onset to the peak (one 60 fps frame). */
39342
+ syncMs: 20,
39343
+ peakAfterOnsetMs: 1e3 / 60,
39344
+ /** C3: distances measured, metres. */
39345
+ distances: [1, 10, 50, 200],
39346
+ /** C5: plays, distinct takes and the pitch range (fraction). */
39347
+ plays: 10,
39348
+ minTakes: 4,
39349
+ pitch: [0.03, 0.05],
39350
+ /** C6: the highest peak, dBFS. */
39351
+ peakDb: -1,
39352
+ /** C8: the end of each file against its peak, dB. */
39353
+ endDropDb: -40,
39354
+ /** C9: a burst of rapid fire. */
39355
+ burst: 20,
39356
+ burstSpacing: 0.06
39357
+ };
39358
+ var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
39359
+ function splitTier(id) {
39360
+ const match = /^(.*)_(light|medium|heavy)$/.exec(id);
39361
+ return match ? { base: match[1], tier: match[2] } : { base: id };
39362
+ }
39363
+ var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry2) => ({ url: `${baseUrl}${entry2.file}` }));
39364
+ function sampleBankFromManifest(manifest, options) {
39365
+ const baseUrl = options.baseUrl ?? "";
39366
+ const samples = [];
39367
+ const tiersOf = /* @__PURE__ */ new Map();
39368
+ for (const sound3 of manifest.sounds) {
39369
+ const { base, tier } = splitTier(sound3.id);
39370
+ if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound3]);
39371
+ }
39372
+ const bases = /* @__PURE__ */ new Map();
39373
+ for (const sound3 of manifest.sounds) {
39374
+ if (!splitTier(sound3.id).tier) bases.set(sound3.id, sound3);
39375
+ }
39376
+ for (const [base, tiers] of tiersOf) {
39377
+ if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
39378
+ }
39379
+ for (const sound3 of bases.values()) {
39380
+ const numbers = options.sounds?.[sound3.id] ?? options.defaults?.(sound3);
39381
+ const close = refsOf(sound3.layers.close, baseUrl);
39382
+ if (!numbers || close.length === 0) continue;
39383
+ const tiers = (tiersOf.get(sound3.id) ?? []).map((tier) => {
39384
+ const name = splitTier(tier.id).tier;
39385
+ const tierFar = refsOf(tier.layers.far, baseUrl);
39386
+ return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
39387
+ }).sort((a, b) => a.from - b.from);
39388
+ const far = refsOf(sound3.layers.far, baseUrl);
39389
+ const indoor = refsOf(sound3.layers.tail_in, baseUrl);
39390
+ const outdoor = refsOf(sound3.layers.tail_out, baseUrl);
39391
+ const files = [sound3, ...tiersOf.get(sound3.id) ?? []].flatMap((entry2) => Object.values(entry2.layers).flatMap((entries2) => entries2 ?? []));
39392
+ samples.push({
39393
+ id: sound3.id,
39394
+ bus: numbers.bus,
39395
+ close,
39396
+ ...far.length > 0 ? { far } : {},
39397
+ ...tiers.length > 0 ? { tiers } : {},
39398
+ ...numbers.crossover ? { crossover: numbers.crossover } : {},
39399
+ ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
39400
+ ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
39401
+ ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
39402
+ ...numbers.falloff ? { falloff: numbers.falloff } : {},
39403
+ ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
39404
+ ...numbers.group ? { group: numbers.group } : {},
39405
+ ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
39406
+ ...numbers.fallback ? { fallback: numbers.fallback } : {},
39407
+ sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
39408
+ });
39409
+ }
39410
+ return { id: options.id, samples };
39411
+ }
39412
+ var PT = {
39413
+ group: "Som",
39414
+ name: "Painel de som (arquivos e C1\u2013C10)",
39415
+ controls: {
39416
+ category: "1 \xB7 Categoria",
39417
+ sound: "1 \xB7 Som",
39418
+ play: "1 \xB7 \u25B6 Tocar",
39419
+ play_user: "1 \xB7 \u25B6 Tocar o seu",
39420
+ material: "2 \xB7 Material",
39421
+ blow: "2 \xB7 Golpe",
39422
+ strike: "2 \xB7 \u25B6 Tocar golpe",
39423
+ distance: "3 \xB7 Dist\xE2ncia",
39424
+ bearing: "3 \xB7 Dire\xE7\xE3o",
39425
+ environment: "3 \xB7 Lugar",
39426
+ strength: "3 \xB7 For\xE7a",
39427
+ wall: "3 \xB7 Atr\xE1s da parede",
39428
+ burst: "3 \xB7 Rajada 20\xD7",
39429
+ play_a: "4 \xB7 \u25B6 A (atual)",
39430
+ play_b: "4 \xB7 \u25B6 B (novo)",
39431
+ ab_cycle: "4 \xB7 Alternar A/B",
39432
+ blind: "4 \xB7 \xC0s cegas (X)",
39433
+ reveal: "4 \xB7 Revelar X",
39434
+ prefer_a: "4 \xB7 Prefiro A",
39435
+ prefer_b: "4 \xB7 Prefiro B",
39436
+ measure: "5 \xB7 Medir C1\u2013C10",
39437
+ record: "5 \xB7 Gravar 3 s",
39438
+ approve: "5 \xB7 Aprovo (C10)"
39439
+ },
39440
+ hints: {
39441
+ play_user: "Toca a sua grava\xE7\xE3o deste som (dos seus pacotes, fora do git), mesmo quando o SDK tem uma melhor e toca na frente.",
39442
+ sound: "A = arquivo, S = sintetizado, F = faltando. O que tem arquivo toca o arquivo; o resto toca o som atual.",
39443
+ strike: "Toca impact_<material>_<golpe>: o arquivo se existir, sen\xE3o o som atual daquele material.",
39444
+ wall: "P\xF5e a parede de casa entre voc\xEA e a fonte: o som fica abafado (C4).",
39445
+ burst: "Vinte vezes a cada 60 ms, como uma rajada (C9).",
39446
+ ab_cycle: "A, B, A, B, um por segundo.",
39447
+ blind: "Toca A ou B sem dizer qual; depois revele.",
39448
+ measure: "Mede o som escolhido no motor real (contexto offline): sincronia, som certo, dist\xE2ncia, lado, varia\xE7\xE3o, volume, for\xE7a, cauda e rajada.",
39449
+ record: "Grava 3 s da sa\xEDda com o som tocando e baixa o arquivo (C10).",
39450
+ approve: "Marca o C10 do som escolhido como aprovado pelo seu ouvido."
39451
+ },
39452
+ categories: {
39453
+ armas: "Armas",
39454
+ impactos: "Impactos",
39455
+ "explosoes-fogo": "Explos\xF5es e fogo",
39456
+ personagem: "Personagem",
39457
+ veiculos: "Ve\xEDculos",
39458
+ "itens-portas": "Itens e portas",
39459
+ ambiente: "Ambiente",
39460
+ calibracao: "Calibra\xE7\xE3o (SDK)"
39461
+ },
39462
+ blows: { bullet: "tiro", blunt: "soco/chute", blade: "faca", break: "quebra", drag: "arrasto/queda" },
39463
+ materials: {
39464
+ steel: "a\xE7o",
39465
+ aluminium: "alum\xEDnio",
39466
+ wood: "madeira",
39467
+ plywood: "compensado",
39468
+ mdf: "MDF",
39469
+ glass: "vidro",
39470
+ tempered_glass: "vidro temperado",
39471
+ ceramic: "cer\xE2mica",
39472
+ concrete: "concreto",
39473
+ brick: "tijolo",
39474
+ plaster: "gesso",
39475
+ plastic: "pl\xE1stico",
39476
+ fabric: "tecido",
39477
+ paper: "papel\xE3o",
39478
+ foliage: "folhagem",
39479
+ soil: "terra",
39480
+ flesh: "carne"
39481
+ },
39482
+ environments: { indoor: "interior", outdoor: "exterior" },
39483
+ strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
39484
+ states: { file: "A", synth: "S", missing: "F" },
39485
+ 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" },
39486
+ 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" },
39487
+ run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
39488
+ words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
39489
+ sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
39490
+ probe: "Fonte do som",
39491
+ wall: "Parede (oclus\xE3o)",
39492
+ scenarios: { pipeline: "Arquivo \u2192 decodifica \u2192 toca (round-robin, cauda)", criteria: "Crit\xE9rios C1\u2013C9 no som de calibra\xE7\xE3o", loop: "Loop de arquivo: emenda sem buraco (crossfade)", panel: "Painel: categorias, matriz, A/B e parede" }
39493
+ };
39494
+ var EN2 = {
39495
+ ...PT,
39496
+ group: "Sound",
39497
+ name: "Sound panel (files and C1\u2013C10)",
39498
+ controls: {
39499
+ category: "1 \xB7 Category",
39500
+ sound: "1 \xB7 Sound",
39501
+ play: "1 \xB7 \u25B6 Play",
39502
+ play_user: "1 \xB7 \u25B6 Play yours",
39503
+ material: "2 \xB7 Material",
39504
+ blow: "2 \xB7 Blow",
39505
+ strike: "2 \xB7 \u25B6 Play blow",
39506
+ distance: "3 \xB7 Distance",
39507
+ bearing: "3 \xB7 Bearing",
39508
+ environment: "3 \xB7 Place",
39509
+ strength: "3 \xB7 Strength",
39510
+ wall: "3 \xB7 Behind the wall",
39511
+ burst: "3 \xB7 Burst 20\xD7",
39512
+ play_a: "4 \xB7 \u25B6 A (current)",
39513
+ play_b: "4 \xB7 \u25B6 B (new)",
39514
+ ab_cycle: "4 \xB7 Alternate A/B",
39515
+ blind: "4 \xB7 Blind (X)",
39516
+ reveal: "4 \xB7 Reveal X",
39517
+ prefer_a: "4 \xB7 Prefer A",
39518
+ prefer_b: "4 \xB7 Prefer B",
39519
+ measure: "5 \xB7 Measure C1\u2013C10",
39520
+ record: "5 \xB7 Record 3 s",
39521
+ approve: "5 \xB7 Approve (C10)"
39522
+ },
39523
+ hints: {
39524
+ play_user: "Plays your own take of this sound (from your packs, kept out of git), even where the SDK has a better one that plays first.",
39525
+ sound: "A = file, S = synthesized, F = missing. A file plays the file; the rest plays the current sound.",
39526
+ strike: "Plays impact_<material>_<blow>: the file if there is one, else the current sound of that material.",
39527
+ wall: "Puts the house wall between you and the source: the sound is muffled (C4).",
39528
+ burst: "Twenty times every 60 ms, like a burst (C9).",
39529
+ ab_cycle: "A, B, A, B, one a second.",
39530
+ blind: "Plays A or B without saying which; reveal it after.",
39531
+ measure: "Measures the chosen sound through the real engine (offline context): sync, right sound, distance, side, variation, level, strength, tail and burst.",
39532
+ record: "Records 3 s of the output with the sound playing and downloads it (C10).",
39533
+ approve: "Marks C10 of the chosen sound as approved by your ear."
39534
+ },
39535
+ categories: { armas: "Weapons", impactos: "Impacts", "explosoes-fogo": "Explosions and fire", personagem: "Character", veiculos: "Vehicles", "itens-portas": "Items and doors", ambiente: "Ambience", calibracao: "Calibration (SDK)" },
39536
+ blows: { bullet: "bullet", blunt: "punch/kick", blade: "knife", break: "break", drag: "drag/fall" },
39537
+ materials: {
39538
+ steel: "steel",
39539
+ aluminium: "aluminium",
39540
+ wood: "wood",
39541
+ plywood: "plywood",
39542
+ mdf: "MDF",
39543
+ glass: "glass",
39544
+ tempered_glass: "tempered glass",
39545
+ ceramic: "ceramic",
39546
+ concrete: "concrete",
39547
+ brick: "brick",
39548
+ plaster: "plaster",
39549
+ plastic: "plastic",
39550
+ fabric: "fabric",
39551
+ paper: "cardboard",
39552
+ foliage: "foliage",
39553
+ soil: "soil",
39554
+ flesh: "flesh"
39555
+ },
39556
+ environments: { indoor: "indoor", outdoor: "outdoor" },
39557
+ strengths: { light: "light", medium: "medium", heavy: "heavy" },
39558
+ 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" },
39559
+ 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" },
39560
+ run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
39561
+ words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
39562
+ sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
39563
+ probe: "Sound source",
39564
+ wall: "Wall (occlusion)",
39565
+ scenarios: { pipeline: "File \u2192 decode \u2192 play (round-robin, tail)", criteria: "Criteria C1\u2013C9 on the calibration sound", loop: "File loop: seamless joins (crossfade)", panel: "Panel: categories, matrix, A/B and wall" }
39566
+ };
38399
39567
 
38400
39568
  // ../game-audio/dist/index.js
38401
39569
  var ATTACK = 2e-3;
@@ -38460,6 +39628,570 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
38460
39628
  ]
38461
39629
  };
38462
39630
  }
39631
+ var sceneMany = (count, make3) => Array.from({ length: count }, make3);
39632
+ var FOLEY_FALLOFF = {
39633
+ model: "inverse_square",
39634
+ k: 0.02,
39635
+ cutoffK: 0.04,
39636
+ minGain: 4e-3
39637
+ };
39638
+ var vary = (f, pct = 0.04) => [Math.round(f * (1 - pct)), Math.round(f * (1 + pct))];
39639
+ var foley = (id, layers, note2) => ({
39640
+ id,
39641
+ bus: "world",
39642
+ pan: true,
39643
+ layers,
39644
+ falloff: FOLEY_FALLOFF,
39645
+ sources: [`sdk: ${note2}`]
39646
+ });
39647
+ var click = (f, vol, delay = 0) => [
39648
+ sceneNoise({
39649
+ type: "bandpass",
39650
+ f: vary(f, 0.08),
39651
+ q: 4,
39652
+ d: [0.018, 0.03],
39653
+ vol: [vol * 0.85, vol * 1.1],
39654
+ delay
39655
+ }),
39656
+ sceneTone({
39657
+ f: vary(f * 0.8, 0.06),
39658
+ f2: Math.round(f * 0.62),
39659
+ d: [0.015, 0.025],
39660
+ vol: vol * 0.18,
39661
+ delay
39662
+ })
39663
+ ];
39664
+ var meet = (body, panel, vol, latch) => [
39665
+ sceneTone({
39666
+ f: vary(body),
39667
+ f2: Math.round(body * 0.5),
39668
+ d: [0.12, 0.16],
39669
+ vol: [vol * 0.9, vol * 1.1]
39670
+ }),
39671
+ sceneNoise({
39672
+ type: "bandpass",
39673
+ f: vary(panel, 0.06),
39674
+ q: 1.2,
39675
+ d: [0.08, 0.12],
39676
+ vol: [vol * 0.9, vol * 1.1],
39677
+ wet: 0.25
39678
+ }),
39679
+ ...click(latch, vol * 0.45, [0.012, 0.028])
39680
+ ];
39681
+ var LEAF_FOLEY = [
39682
+ // a wooden house door or wardrobe door: latch, a short creak, the leaf on the frame (the hard slam adds the frame rattling)
39683
+ foley("door_wood_open", click(2400, 0.4), "the latch of a wooden door giving"),
39684
+ foley(
39685
+ "door_creak",
39686
+ [
39687
+ sceneTone({ f: [190, 240], f2: 310, d: [0.4, 0.55], vol: [0.04, 0.06] }),
39688
+ sceneNoise({
39689
+ type: "bandpass",
39690
+ f: [480, 560],
39691
+ q: 6,
39692
+ a: 0.05,
39693
+ d: [0.35, 0.5],
39694
+ vol: [0.1, 0.14]
39695
+ })
39696
+ ],
39697
+ "a wooden hinge turning"
39698
+ ),
39699
+ foley("door_wood_close", meet(120, 600, 0.55, 2400), "a wooden leaf meeting its frame"),
39700
+ foley(
39701
+ "door_wood_slam",
39702
+ [
39703
+ ...meet(110, 560, 0.85, 2300),
39704
+ ...sceneMany(
39705
+ 3,
39706
+ () => sceneNoise({
39707
+ type: "bandpass",
39708
+ f: [900, 1500],
39709
+ q: 3,
39710
+ d: 0.03,
39711
+ vol: [0.08, 0.14],
39712
+ delay: [0.05, 0.18]
39713
+ })
39714
+ )
39715
+ ],
39716
+ "a wooden door slammed: the frame and the hinges rattle after the hit"
39717
+ ),
39718
+ // a metal door or gate: a hollow panel that rings a little
39719
+ foley("door_metal_open_close", [...meet(160, 900, 0.55, 2e3), sceneTone({ f: vary(1250), f2: 1150, d: [0.1, 0.16], vol: 0.06, wet: 0.3 })], "a metal door or gate meeting its frame"),
39720
+ foley(
39721
+ "door_creak_metal",
39722
+ [
39723
+ sceneTone({ f: [620, 760], f2: 900, d: [0.3, 0.4], vol: [0.03, 0.045] }),
39724
+ sceneNoise({
39725
+ type: "bandpass",
39726
+ f: [1300, 1500],
39727
+ q: 8,
39728
+ a: 0.04,
39729
+ d: [0.25, 0.35],
39730
+ vol: [0.07, 0.09]
39731
+ })
39732
+ ],
39733
+ "a metal hinge turning"
39734
+ ),
39735
+ // the handle of a locked door shaken: the latch knocking in its keeper, five to seven times in half a second
39736
+ foley(
39737
+ "door_locked_rattle",
39738
+ [
39739
+ ...sceneMany(
39740
+ 6,
39741
+ () => sceneNoise({
39742
+ type: "bandpass",
39743
+ f: [1600, 2600],
39744
+ q: 3,
39745
+ d: [0.025, 0.04],
39746
+ vol: [0.22, 0.34],
39747
+ delay: [0, 0.45]
39748
+ })
39749
+ ),
39750
+ ...sceneMany(
39751
+ 3,
39752
+ () => sceneTone({
39753
+ f: [170, 210],
39754
+ f2: 120,
39755
+ d: 0.05,
39756
+ vol: [0.12, 0.18],
39757
+ delay: [0.02, 0.4]
39758
+ })
39759
+ )
39760
+ ],
39761
+ "the handle of a locked door shaken: latch knocks and the leaf thumping in its frame"
39762
+ ),
39763
+ // a fridge: the rubber seal lets go with a soft suck, the bottles in the door clink; closing it is a soft thump and the seal pulling shut
39764
+ foley(
39765
+ "fridge_open",
39766
+ [
39767
+ sceneNoise({
39768
+ type: "lowpass",
39769
+ f: [320, 380],
39770
+ a: 0.03,
39771
+ d: [0.14, 0.2],
39772
+ vol: [0.32, 0.4]
39773
+ }),
39774
+ sceneNoise({
39775
+ type: "bandpass",
39776
+ f: [800, 950],
39777
+ f2: 1400,
39778
+ q: 2,
39779
+ a: 0.02,
39780
+ d: 0.12,
39781
+ vol: [0.08, 0.12]
39782
+ }),
39783
+ ...sceneMany(
39784
+ 2,
39785
+ () => sceneTone({
39786
+ f: [2600, 4200],
39787
+ d: [0.04, 0.08],
39788
+ vol: [0.015, 0.03],
39789
+ delay: [0.08, 0.22]
39790
+ })
39791
+ )
39792
+ ],
39793
+ "a fridge door: the seal letting go and the bottles in the door"
39794
+ ),
39795
+ foley(
39796
+ "fridge_close",
39797
+ [
39798
+ sceneTone({ f: vary(85), f2: 45, d: [0.1, 0.13], vol: [0.38, 0.46] }),
39799
+ sceneNoise({
39800
+ type: "lowpass",
39801
+ f: [520, 620],
39802
+ d: [0.08, 0.12],
39803
+ vol: [0.3, 0.36]
39804
+ }),
39805
+ sceneNoise({
39806
+ type: "lowpass",
39807
+ f: [260, 320],
39808
+ a: 0.02,
39809
+ d: 0.1,
39810
+ vol: [0.12, 0.18],
39811
+ delay: [0.04, 0.07]
39812
+ }),
39813
+ ...sceneMany(
39814
+ 2,
39815
+ () => sceneTone({
39816
+ f: [2600, 4200],
39817
+ d: [0.04, 0.07],
39818
+ vol: [0.015, 0.03],
39819
+ delay: [0.03, 0.12]
39820
+ })
39821
+ )
39822
+ ],
39823
+ "a fridge door shut: a soft thump, the seal pulling and the bottles"
39824
+ ),
39825
+ // a microwave: the spring catch of the door, plastic on plastic
39826
+ foley(
39827
+ "microwave_open",
39828
+ [
39829
+ ...click(1800, 0.34),
39830
+ sceneTone({
39831
+ f: [3e3, 3400],
39832
+ f2: 2800,
39833
+ d: 0.05,
39834
+ vol: 0.025,
39835
+ delay: [0.01, 0.02]
39836
+ })
39837
+ ],
39838
+ "the spring catch of a microwave door"
39839
+ ),
39840
+ foley(
39841
+ "microwave_close",
39842
+ [
39843
+ sceneNoise({
39844
+ type: "bandpass",
39845
+ f: [1200, 1450],
39846
+ q: 2.5,
39847
+ d: [0.04, 0.06],
39848
+ vol: [0.36, 0.44]
39849
+ }),
39850
+ sceneTone({ f: vary(180), f2: 120, d: 0.05, vol: [0.16, 0.22] }),
39851
+ ...click(2200, 0.2, [8e-3, 0.016])
39852
+ ],
39853
+ "a microwave door clacking shut"
39854
+ ),
39855
+ foley("microwave_beep", [sceneTone({ wave: "square", f: 2e3, d: 0.12, vol: 0.05 })], "the beep of a microwave"),
39856
+ // drawers and sliding leaves: the runners, then the stop
39857
+ foley(
39858
+ "drawer_open",
39859
+ [
39860
+ sceneNoise({
39861
+ type: "bandpass",
39862
+ f: [620, 780],
39863
+ q: 2,
39864
+ a: 0.04,
39865
+ d: [0.28, 0.36],
39866
+ vol: [0.18, 0.24]
39867
+ }),
39868
+ sceneNoise({ type: "bandpass", f: [2e3, 2400], q: 5, d: 0.28, vol: 0.04 }),
39869
+ ...click(1400, 0.16, [0.28, 0.34])
39870
+ ],
39871
+ "a drawer on its runners and its stop"
39872
+ ),
39873
+ foley(
39874
+ "drawer_close",
39875
+ [
39876
+ sceneNoise({
39877
+ type: "bandpass",
39878
+ f: [620, 780],
39879
+ q: 2,
39880
+ a: 0.03,
39881
+ d: [0.2, 0.28],
39882
+ vol: [0.18, 0.24]
39883
+ }),
39884
+ sceneTone({
39885
+ f: vary(150),
39886
+ f2: 90,
39887
+ d: 0.08,
39888
+ vol: [0.22, 0.3],
39889
+ delay: [0.2, 0.26]
39890
+ })
39891
+ ],
39892
+ "a drawer pushed home"
39893
+ )
39894
+ ];
39895
+ var ITEM_FOLEY = [
39896
+ // the hand closing on the item: a touch of skin and cloth, then the item's own small sound
39897
+ foley(
39898
+ "item_pickup_metal",
39899
+ [
39900
+ sceneNoise({ type: "bandpass", f: [700, 900], q: 1, d: 0.06, vol: 0.18 }),
39901
+ sceneTone({
39902
+ f: [1900, 2600],
39903
+ f2: 1700,
39904
+ d: [0.05, 0.08],
39905
+ vol: [0.06, 0.09],
39906
+ delay: [0.01, 0.03]
39907
+ })
39908
+ ],
39909
+ "picking up a metal thing"
39910
+ ),
39911
+ foley(
39912
+ "item_pickup_plastic",
39913
+ [
39914
+ sceneNoise({ type: "bandpass", f: [700, 900], q: 1, d: 0.06, vol: 0.18 }),
39915
+ sceneNoise({
39916
+ type: "bandpass",
39917
+ f: [1500, 1900],
39918
+ q: 3,
39919
+ d: 0.03,
39920
+ vol: [0.15, 0.21],
39921
+ delay: [0.01, 0.03]
39922
+ })
39923
+ ],
39924
+ "picking up a plastic thing"
39925
+ ),
39926
+ foley(
39927
+ "item_pickup_glass",
39928
+ [
39929
+ sceneNoise({ type: "bandpass", f: [700, 900], q: 1, d: 0.06, vol: 0.15 }),
39930
+ sceneTone({
39931
+ f: [3200, 4200],
39932
+ d: [0.05, 0.08],
39933
+ vol: [0.038, 0.06],
39934
+ delay: [0.01, 0.03]
39935
+ })
39936
+ ],
39937
+ "picking up a glass thing"
39938
+ ),
39939
+ foley(
39940
+ "item_pickup_paper",
39941
+ [
39942
+ sceneNoise({
39943
+ type: "highpass",
39944
+ f: [2600, 3400],
39945
+ a: 0.01,
39946
+ d: [0.1, 0.14],
39947
+ vol: [0.12, 0.18]
39948
+ }),
39949
+ sceneNoise({ type: "bandpass", f: [800, 1e3], q: 1, d: 0.05, vol: 0.12 })
39950
+ ],
39951
+ "picking up paper or cardboard"
39952
+ ),
39953
+ foley(
39954
+ "item_pickup_fabric",
39955
+ [
39956
+ sceneNoise({
39957
+ type: "bandpass",
39958
+ f: [900, 1300],
39959
+ q: 0.8,
39960
+ a: 0.02,
39961
+ d: [0.12, 0.16],
39962
+ vol: [0.15, 0.21]
39963
+ })
39964
+ ],
39965
+ "picking up cloth"
39966
+ ),
39967
+ foley(
39968
+ "item_pickup_wood",
39969
+ [
39970
+ sceneNoise({ type: "bandpass", f: [700, 900], q: 1, d: 0.06, vol: 0.18 }),
39971
+ sceneNoise({
39972
+ type: "bandpass",
39973
+ f: [900, 1100],
39974
+ q: 2,
39975
+ d: 0.04,
39976
+ vol: [0.15, 0.21],
39977
+ delay: [0.01, 0.03]
39978
+ })
39979
+ ],
39980
+ "picking up a wooden thing"
39981
+ ),
39982
+ foley(
39983
+ "item_pickup_food",
39984
+ [
39985
+ sceneNoise({ type: "bandpass", f: [700, 900], q: 1, d: 0.06, vol: 0.18 }),
39986
+ sceneNoise({
39987
+ type: "lowpass",
39988
+ f: [500, 650],
39989
+ d: 0.05,
39990
+ vol: 0.12,
39991
+ delay: [0.01, 0.03]
39992
+ })
39993
+ ],
39994
+ "picking up food"
39995
+ ),
39996
+ foley(
39997
+ "item_throw",
39998
+ [
39999
+ sceneNoise({
40000
+ type: "bandpass",
40001
+ f: [450, 650],
40002
+ f2: 1400,
40003
+ q: 1.5,
40004
+ a: 0.05,
40005
+ d: 0.18,
40006
+ vol: [0.1, 0.14]
40007
+ })
40008
+ ],
40009
+ "the arm throwing something: air"
40010
+ ),
40011
+ // a can: the tab cracks, the gas hisses out; drinking is gulps; crushing it crumples the sheet
40012
+ foley(
40013
+ "can_open",
40014
+ [
40015
+ ...click(3200, 0.36),
40016
+ sceneNoise({
40017
+ type: "highpass",
40018
+ f: [3600, 4400],
40019
+ a: 5e-3,
40020
+ d: [0.22, 0.3],
40021
+ vol: [0.16, 0.22],
40022
+ delay: [0.012, 0.02]
40023
+ }),
40024
+ ...sceneMany(5, () => sceneTone({ f: [4e3, 7e3], d: 0.02, vol: 0.01, delay: [0.05, 0.4] }))
40025
+ ],
40026
+ "the tab of a can and the gas hissing out"
40027
+ ),
40028
+ foley(
40029
+ "can_crush",
40030
+ [
40031
+ sceneNoise({
40032
+ type: "bandpass",
40033
+ f: [1600, 2200],
40034
+ q: 1.2,
40035
+ d: [0.18, 0.25],
40036
+ vol: [0.3, 0.38]
40037
+ }),
40038
+ ...sceneMany(
40039
+ 6,
40040
+ () => sceneTone({
40041
+ f: [1200, 3400],
40042
+ f2: 900,
40043
+ d: 0.03,
40044
+ vol: [0.03, 0.06],
40045
+ delay: [0, 0.2]
40046
+ })
40047
+ )
40048
+ ],
40049
+ "a can crushed in the hand"
40050
+ ),
40051
+ foley(
40052
+ "can_puncture_spray",
40053
+ [
40054
+ sceneNoise({
40055
+ type: "highpass",
40056
+ f: [2400, 3e3],
40057
+ a: 5e-3,
40058
+ d: [0.5, 0.7],
40059
+ vol: [0.2, 0.26]
40060
+ }),
40061
+ sceneNoise({ type: "bandpass", f: [600, 800], q: 2, d: 0.4, vol: 0.06 })
40062
+ ],
40063
+ "a can holed: the jet hissing out"
40064
+ ),
40065
+ foley(
40066
+ "bottle_open",
40067
+ [
40068
+ ...sceneMany(
40069
+ 5,
40070
+ () => sceneNoise({
40071
+ type: "bandpass",
40072
+ f: [2200, 3e3],
40073
+ q: 4,
40074
+ d: 8e-3,
40075
+ vol: [0.1, 0.16],
40076
+ delay: [0, 0.18]
40077
+ })
40078
+ ),
40079
+ sceneNoise({
40080
+ type: "highpass",
40081
+ f: 3800,
40082
+ a: 5e-3,
40083
+ d: 0.12,
40084
+ vol: [0.06, 0.1],
40085
+ delay: [0.18, 0.22]
40086
+ })
40087
+ ],
40088
+ "the screw cap of a bottle turning off"
40089
+ ),
40090
+ foley(
40091
+ "drink_gulp",
40092
+ [
40093
+ sceneNoise({
40094
+ type: "lowpass",
40095
+ f: [280, 360],
40096
+ a: 0.02,
40097
+ d: [0.1, 0.14],
40098
+ vol: [0.24, 0.3]
40099
+ }),
40100
+ sceneTone({ f: [140, 170], f2: 85, d: [0.08, 0.11], vol: [0.12, 0.16] })
40101
+ ],
40102
+ "a gulp"
40103
+ ),
40104
+ foley(
40105
+ "eat_crunch",
40106
+ [
40107
+ ...sceneMany(
40108
+ 4,
40109
+ () => sceneNoise({
40110
+ type: "bandpass",
40111
+ f: [1800, 3200],
40112
+ q: 1.5,
40113
+ d: [0.02, 0.04],
40114
+ vol: [0.12, 0.2],
40115
+ delay: [0, 0.12]
40116
+ })
40117
+ )
40118
+ ],
40119
+ "a bite of something crunchy"
40120
+ ),
40121
+ foley(
40122
+ "eat_chew",
40123
+ [
40124
+ sceneNoise({
40125
+ type: "lowpass",
40126
+ f: [450, 600],
40127
+ a: 0.03,
40128
+ d: [0.1, 0.14],
40129
+ vol: [0.12, 0.16]
40130
+ }),
40131
+ sceneNoise({
40132
+ type: "bandpass",
40133
+ f: [1100, 1400],
40134
+ q: 2,
40135
+ d: 0.04,
40136
+ vol: 0.04,
40137
+ delay: [0.03, 0.06]
40138
+ })
40139
+ ],
40140
+ "chewing"
40141
+ ),
40142
+ foley(
40143
+ "bandage_wrap",
40144
+ [
40145
+ sceneNoise({
40146
+ type: "bandpass",
40147
+ f: [1400, 1800],
40148
+ f2: 2400,
40149
+ q: 1,
40150
+ a: 0.04,
40151
+ d: [0.3, 0.4],
40152
+ vol: [0.12, 0.16]
40153
+ })
40154
+ ],
40155
+ "a bandage unrolled round a limb"
40156
+ ),
40157
+ foley(
40158
+ "pills_shake",
40159
+ [
40160
+ ...sceneMany(
40161
+ 8,
40162
+ () => sceneTone({
40163
+ f: [2400, 3600],
40164
+ d: 0.015,
40165
+ vol: [0.03, 0.05],
40166
+ delay: [0, 0.25]
40167
+ })
40168
+ ),
40169
+ sceneNoise({ type: "bandpass", f: [1500, 1900], q: 2, d: 0.25, vol: 0.05 })
40170
+ ],
40171
+ "pills shaken in their bottle"
40172
+ ),
40173
+ foley(
40174
+ "medkit_use",
40175
+ [
40176
+ ...click(1700, 0.24),
40177
+ sceneNoise({
40178
+ type: "bandpass",
40179
+ f: [1e3, 1400],
40180
+ q: 1,
40181
+ a: 0.03,
40182
+ d: [0.25, 0.32],
40183
+ vol: [0.08, 0.12],
40184
+ delay: [0.05, 0.08]
40185
+ })
40186
+ ],
40187
+ "a first aid kit opened and rummaged"
40188
+ ),
40189
+ foley("flashlight_click", click(2600, 0.3), "the switch of a flashlight")
40190
+ ];
40191
+ var FOLEY_BANK = {
40192
+ id: "foley",
40193
+ sounds: [...LEAF_FOLEY, ...ITEM_FOLEY]
40194
+ };
38463
40195
  var TAKES = [
38464
40196
  { f: 1, t: 1, v: 1 },
38465
40197
  { f: 1.08, t: 0.9, v: 0.92 },
@@ -39548,7 +41280,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39548
41280
  if (energy < 5e3) return "rifle_308_3006";
39549
41281
  return energy < 12e3 ? "sniper_338_300wm" : "antimateriel_50bmg";
39550
41282
  };
39551
- var pickTake = (last, random, takes2 = GUNSHOT_TAKES) => {
41283
+ var pickTake2 = (last, random, takes2 = GUNSHOT_TAKES) => {
39552
41284
  if (takes2 <= 1) return 0;
39553
41285
  if (last === void 0) return Math.floor(random() * takes2) % takes2;
39554
41286
  const next = Math.floor(random() * (takes2 - 1)) % (takes2 - 1);
@@ -39690,11 +41422,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
39690
41422
  if (surface !== "soft") voices.push({ kind: "tone", frequency: 2900 * pitch, to: 2800 * pitch, gain: 0.025 * level, seconds: surface === "metal" ? 0.06 : 0.025 });
39691
41423
  } else {
39692
41424
  voices.push({ kind: "noise", filter: surface === "soft" || surface === "wood" ? "lowpass" : "highpass", frequency: knock2.frequency * pitch, q: 0.7, gain: knock2.gain * level, seconds: knock2.seconds });
39693
- const ringSeconds = RING_SECONDS[surface] * (kind === "rifle" ? 1.3 : 1);
39694
- if (ringSeconds > 0) {
41425
+ const ringSeconds2 = RING_SECONDS[surface] * (kind === "rifle" ? 1.3 : 1);
41426
+ if (ringSeconds2 > 0) {
39695
41427
  const f = RING[kind] * pitch;
39696
- voices.push({ kind: "tone", frequency: f, to: f * 0.985, gain: 0.07 * level, seconds: ringSeconds });
39697
- voices.push({ kind: "tone", frequency: f * PARTIAL, to: f * PARTIAL * 0.99, gain: 0.03 * level, seconds: ringSeconds * 0.6 });
41428
+ voices.push({ kind: "tone", frequency: f, to: f * 0.985, gain: 0.07 * level, seconds: ringSeconds2 });
41429
+ voices.push({ kind: "tone", frequency: f * PARTIAL, to: f * PARTIAL * 0.99, gain: 0.03 * level, seconds: ringSeconds2 * 0.6 });
39698
41430
  }
39699
41431
  }
39700
41432
  const surfaceName = surface === "hard" ? "concreto" : surface === "soft" ? "terra" : surface === "wood" ? "madeira" : "metal";
@@ -40455,6 +42187,72 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40455
42187
  });
40456
42188
  };
40457
42189
 
42190
+ // src/data/reload-sample-hits.json
42191
+ var reload_sample_hits_default = {
42192
+ $comment: "Seconds from the start of each recorded reload step to its hit (median of its takes), measured on the converted files by tools/sfx-sources/hits.mjs. The reload clip starts the sound that much early, so the hit lands on the frame the part reaches its stop.",
42193
+ hits: {
42194
+ bolt_lift: 0.072,
42195
+ bolt_lock: 0.078,
42196
+ bolt_pull: 0.128,
42197
+ bolt_push: 0.054,
42198
+ charging_handle_pull: 0.074,
42199
+ charging_handle_release: 0.048,
42200
+ cylinder_close: 0.024,
42201
+ cylinder_eject: 0.394,
42202
+ cylinder_open: 0.07,
42203
+ lever_close: 0.147,
42204
+ lever_open: 0.095,
42205
+ mag_out: 0.012,
42206
+ mag_seat: 0.094,
42207
+ pump_back: 0.094,
42208
+ pump_forward: 0.118,
42209
+ shell_insert: 0.21,
42210
+ slide_release: 0.031,
42211
+ speedloader: 0.089
42212
+ }
42213
+ };
42214
+
42215
+ // src/audio/samples.ts
42216
+ var GUNSHOT_SAMPLE_IDS = [...Object.keys(gunshot_profiles_default.profiles), "suppressed_pistol", "suppressed_rifle"];
42217
+ var GUNSHOT_SAMPLE = {
42218
+ bus: "weapons",
42219
+ gain: 1,
42220
+ pitch: 0.04,
42221
+ crossover: { from: 20, to: 90 },
42222
+ falloff: { model: "inverse_square", k: 5e-4, minGain: 6e-4 },
42223
+ cutoffK: 0.04,
42224
+ priority: 3
42225
+ };
42226
+ var SUPPRESSED_SAMPLE = { ...GUNSHOT_SAMPLE, gain: 0.55, crossover: { from: 10, to: 50 }, falloff: { model: "inverse_square", k: 4e-3, minGain: 8e-4 } };
42227
+ var RELOAD_SAMPLE = { bus: "weapons", gain: 0.32, pitch: 0.05, falloff: { model: "inverse_square", k: 0.05, minGain: 2e-3 }, cutoffK: 0.02, priority: 2 };
42228
+ var CASING_SAMPLE = { bus: "weapons", gain: 0.22, pitch: 0.06, falloff: { model: "inverse_square", k: 0.08, minGain: 3e-3 }, cutoffK: 0.03, priority: 1 };
42229
+ var casingSampleId = (sample) => sample.replace(/_(pistol|rifle|shell)$/, "");
42230
+ var RELOAD_SAMPLE_HITS = reload_sample_hits_default.hits;
42231
+ var WEIGHT_INTENSITY = { light: 0.1, medium: 0.5, heavy: 0.9 };
42232
+ var CUES_IN_SAMPLE = { mag_release: "mag_out", mag_insert: "mag_seat" };
42233
+ var isGunshot = (id) => GUNSHOT_SAMPLE_IDS.includes(id);
42234
+ function weaponSampleDefaults(sound3, overrides = {}) {
42235
+ if (sound3.category !== "armas") return void 0;
42236
+ const base = sound3.id.startsWith("suppressed_") ? SUPPRESSED_SAMPLE : isGunshot(sound3.id) ? GUNSHOT_SAMPLE : sound3.id.startsWith("casing_drop_") ? CASING_SAMPLE : RELOAD_SAMPLE;
42237
+ return { ...base, ...overrides[sound3.id] };
42238
+ }
42239
+ function weaponSampleBank(options) {
42240
+ const bank = sampleBankFromManifest(options.manifest, { id: "weapons_three_samples", ...options.url ? {} : { baseUrl: options.baseUrl ?? "" }, defaults: (sound3) => weaponSampleDefaults(sound3, options.sounds) });
42241
+ const url2 = options.url;
42242
+ const remap = (refs) => refs && url2 ? refs.map((ref) => ({ ...ref, url: url2(ref.url) })) : refs ? [...refs] : void 0;
42243
+ const samples = (bank.samples ?? []).map((sample) => {
42244
+ const tiers = sample.tiers?.map((tier) => ({ ...tier, close: remap(tier.close) ?? [], ...tier.far ? { far: remap(tier.far) ?? [] } : {} }));
42245
+ return {
42246
+ ...sample,
42247
+ close: remap(sample.close) ?? [],
42248
+ ...sample.far ? { far: remap(sample.far) ?? [] } : {},
42249
+ ...tiers ? { tiers } : {},
42250
+ ...isGunshot(sample.id) ? { choke: { voices: 3, fade: 0.06 } } : {}
42251
+ };
42252
+ });
42253
+ return { id: bank.id, samples };
42254
+ }
42255
+
40458
42256
  // src/audio.ts
40459
42257
  var NOISE_SECONDS = 1.5;
40460
42258
  var DEFAULT_SEED = 1;
@@ -40475,6 +42273,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40475
42273
  const channel = shared2 && options.seed !== void 0 ? shared2.channel({ seed }) : engine.channel({});
40476
42274
  engine.register({ id: `weapons_three@${classBurstId(distanceCurve)}`, sounds: weaponRecipes(distanceCurve) });
40477
42275
  const classBurst = classBurstId(distanceCurve);
42276
+ const sampleBank = options.samples ? weaponSampleBank(options.samples) : null;
42277
+ if (sampleBank) engine.register(sampleBank);
42278
+ const sampleIds = new Set((sampleBank?.samples ?? []).map((sample) => sample.id));
42279
+ let sampleLoad = null;
42280
+ const loadSamples = () => sampleLoad ?? (sampleLoad = sampleIds.size > 0 ? engine.loadSamples([...sampleIds]).then(() => void 0, () => void 0) : Promise.resolve());
42281
+ const sampleReady = (id) => {
42282
+ if (!sampleIds.has(id)) return false;
42283
+ if (engine.sampleStatus(id) === "ready") return true;
42284
+ void loadSamples();
42285
+ return false;
42286
+ };
40478
42287
  const burst2 = (config) => {
40479
42288
  const id = config.filter === "highpass" ? BURST_IDS.highpass : BURST_IDS.lowpass;
40480
42289
  const params = { frequency: config.frequency, q: config.q ?? 0.8, gain: Math.max(config.gain, 2e-4), seconds: config.seconds };
@@ -40523,8 +42332,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40523
42332
  };
40524
42333
  const casing2 = (input) => {
40525
42334
  const sound3 = computeCasingSound({ ...input, pitch: input.pitch ?? 1 + (channel.random() * 2 - 1) * 0.06 });
42335
+ const recorded = sound3.kind === "shell" ? "" : casingSampleId(sound3.sample);
42336
+ if (recorded && sampleReady(recorded)) {
42337
+ if ((input.bounce ?? 0) === 0) channel.play(recorded, { volume: Math.min(1, Math.max(0.2, (input.speed ?? 1.5) / 2.5)) });
42338
+ options.onCasing?.(sound3, "sample");
42339
+ return;
42340
+ }
40526
42341
  for (const voice2 of sound3.voices) playCasingVoice(voice2);
40527
- options.onCasing?.(sound3);
42342
+ options.onCasing?.(sound3, "synth");
40528
42343
  };
40529
42344
  const lastTake = /* @__PURE__ */ new Map();
40530
42345
  const pitchSpread = options.pitchSpread ?? 0.04;
@@ -40542,12 +42357,29 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40542
42357
  if (options.shotModel !== "game") {
40543
42358
  const base = { energyJ, category, ...extras, space: extras.space ?? (extras.indoors ? "indoor" : "outdoor") };
40544
42359
  const profile = computeGunshot(base, options.gunshot).profile;
40545
- const take = pickTake(lastTake.get(profile), channel.random);
42360
+ const take = pickTake2(lastTake.get(profile), channel.random);
40546
42361
  lastTake.set(profile, take);
40547
42362
  const pitch = 1 + (channel.random() * 2 - 1) * pitchSpread;
40548
42363
  const shot = computeGunshot({ ...base, take, pitch }, options.gunshot);
42364
+ const recorded = shot.samples.close;
42365
+ if (shot.audible && sampleReady(recorded)) {
42366
+ const occlusion = Math.min(1, Math.max(0, extras.occlusion ?? 0));
42367
+ const handle = channel.play(recorded, {
42368
+ volume: (extras.volume ?? 1) * (1 - 0.5 * occlusion),
42369
+ environment: base.space === "indoor" ? "indoor" : "outdoor",
42370
+ ...extras.position ? { position: [extras.position[0], extras.position[1], extras.position[2]] } : {},
42371
+ ...extras.distance === void 0 ? {} : { distance: extras.distance },
42372
+ ...shot.delay > 0 ? { delay: shot.delay } : {}
42373
+ });
42374
+ if (base.space === "indoor") {
42375
+ for (const voice2 of shot.voices) if (voice2.group === "tail") playGunshotVoice(voice2, extras);
42376
+ }
42377
+ const layers = handle?.kind === "sample" ? handle.plan.layers.map((layer2) => ({ role: layer2.role, take: layer2.take, rate: layer2.rate })) : [];
42378
+ options.onGunshot?.(shot, { ...base, take, pitch }, { kind: "sample", id: recorded, layers });
42379
+ return;
42380
+ }
40549
42381
  for (const voice2 of shot.voices) playGunshotVoice(voice2, extras);
40550
- if (shot.audible) options.onGunshot?.(shot, { ...base, take, pitch });
42382
+ if (shot.audible) options.onGunshot?.(shot, { ...base, take, pitch }, { kind: "synth" });
40551
42383
  return;
40552
42384
  }
40553
42385
  const sound3 = shotSound({ ...extras, energyJ, category });
@@ -40562,9 +42394,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
40562
42394
  mechanical("dry_fire");
40563
42395
  },
40564
42396
  mechanical,
40565
- cue(id, profile) {
40566
- for (const entry2 of reloadCueSteps(id, profile, cueTable)) step(entry2);
42397
+ cue(id, profile, lead = 0) {
42398
+ const carriedBy = CUES_IN_SAMPLE[id];
42399
+ if (carriedBy && sampleReady(carriedBy)) return "carried";
42400
+ if (sampleReady(id)) {
42401
+ channel.play(id, { intensity: WEIGHT_INTENSITY[(profile ?? DEFAULT_SOUND_PROFILE).weight] });
42402
+ return "sample";
42403
+ }
42404
+ for (const entry2 of reloadCueSteps(id, profile, cueTable)) step(lead > 0 ? { ...entry2, at: entry2.at + lead } : entry2);
42405
+ return "synth";
40567
42406
  },
42407
+ loadSamples,
40568
42408
  event(event) {
40569
42409
  const id = mechanicalSoundOf(event);
40570
42410
  if (id) mechanical(id);
@@ -47576,17 +49416,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
47576
49416
  if (!builder) throw new Error(`game-items-three: no builder for the model "${entry2.id}"`);
47577
49417
  return { entry: entry2, builder };
47578
49418
  });
47579
- var registryOver = (entries, builders) => ({
49419
+ var registryOver = (entries2, builders) => ({
47580
49420
  has: (id) => builders.has(id),
47581
- entry: (id) => entries.get(id),
49421
+ entry: (id) => entries2.get(id),
47582
49422
  builder: (id) => builders.get(id),
47583
- list: () => [...entries.keys()],
49423
+ list: () => [...entries2.keys()],
47584
49424
  register(entry2, builder) {
47585
- entries.set(entry2.id, entry2);
49425
+ entries2.set(entry2.id, entry2);
47586
49426
  builders.set(entry2.id, builder);
47587
49427
  },
47588
49428
  unregister(id) {
47589
- entries.delete(id);
49429
+ entries2.delete(id);
47590
49430
  builders.delete(id);
47591
49431
  }
47592
49432
  });
@@ -51275,6 +53115,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51275
53115
  }
51276
53116
 
51277
53117
  // src/animation/clip-builder.ts
53118
+ var RELOAD_SAMPLE_HITS2 = reload_sample_hits_default.hits;
51278
53119
  var ROOT = "root";
51279
53120
  var AXIS_INDEX = { x: 0, y: 1, z: 2 };
51280
53121
  var EASE_STEPS = 10;
@@ -51282,7 +53123,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51282
53123
  var move = (part, axis2, start, duration, from, to) => ({ part, channel: "position", axis: axis2, start, duration, from, to });
51283
53124
  var turn = (part, axis2, start, duration, from, to) => ({ part, channel: "rotation", axis: axis2, start, duration, from, to });
51284
53125
  var cue = (t, type, data) => ({ t, type, ...data ? { data } : {} });
51285
- var sound2 = (t, id) => ({ t, type: "sound", data: { id } });
53126
+ var sound2 = (t, id) => {
53127
+ const lead = RELOAD_SAMPLE_HITS2[id];
53128
+ return lead ? { t, type: "sound", data: { id }, lead } : { t, type: "sound", data: { id } };
53129
+ };
51286
53130
  var stage = (id, t) => ({ t, type: STAGE_EVENT, data: { stage: id } });
51287
53131
  var valueAt = (tweens, t, includeInstants) => {
51288
53132
  let value = 0;
@@ -54613,7 +56457,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
54613
56457
  var ARC_HEIGHT = 2.2;
54614
56458
  var GLASS_GREEN = 3828282;
54615
56459
  var OBJECT_ID_DRAWS = 12;
54616
- var at = (offset) => vadd(TARGET, offset);
56460
+ var at2 = (offset) => vadd(TARGET, offset);
54617
56461
  var MOLOTOV = {
54618
56462
  id: "molotov",
54619
56463
  pageId: "molotov",
@@ -54635,17 +56479,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
54635
56479
  when(not(variable("landed")), [
54636
56480
  set("landed", true),
54637
56481
  cue2("bottle_visible", { visible: false }),
54638
- repeat(scaled(12), debris({ p: at([0, 0.1, 0]), dir: UP6, cone: 1.3, sp: [1, 3], size: [0.04, 0.01, 0.04], c: GLASS_GREEN, life: 12 })),
54639
- burst("add", 45, { p: at([0, 0.15, 0]), dir: UP6, cone: 1.35, sp: [2, 4], drag: 2, life: [0.3, 0.6], size: [0.3, 0.5], c0: 16765562, c1: 10493960, fi: 0.05 }),
54640
- flash(at([0, 0.6, 0]), 16747056, 6, 0.3, 12, true),
56482
+ repeat(scaled(12), debris({ p: at2([0, 0.1, 0]), dir: UP6, cone: 1.3, sp: [1, 3], size: [0.04, 0.01, 0.04], c: GLASS_GREEN, life: 12 })),
56483
+ burst("add", 45, { p: at2([0, 0.15, 0]), dir: UP6, cone: 1.35, sp: [2, 4], drag: 2, life: [0.3, 0.6], size: [0.3, 0.5], c0: 16765562, c1: 10493960, fi: 0.05 }),
56484
+ flash(at2([0, 0.6, 0]), 16747056, 6, 0.3, 12, true),
54641
56485
  decal(TARGET[0], 0, TARGET[2], 4.2, 854793, { grow: 0.8, life: 60 })
54642
56486
  ])
54643
56487
  ]),
54644
56488
  when(and(gt(TIME, FLIGHT), lt(TIME, 8.2)), [
54645
56489
  set("poolRadius", min(2.2, add(0.5, mul(sub(TIME, 1), 2.1)))),
54646
56490
  set("poolFade", ite(gt(TIME, 7), max(0.1, sub(1, div(sub(TIME, 7), 1.2))), 1)),
54647
- call2("fire_emit", "mol", at([0, 0.05, 0]), variable("poolRadius"), mul(1.6, variable("poolFade")), { smoke: 16 }),
54648
- fireLight(0, at([0, 1, 0]), mul(3.2, variable("poolFade")), TIME)
56491
+ call2("fire_emit", "mol", at2([0, 0.05, 0]), variable("poolRadius"), mul(1.6, variable("poolFade")), { smoke: 16 }),
56492
+ fireLight(0, at2([0, 1, 0]), mul(3.2, variable("poolFade")), TIME)
54649
56493
  ], [
54650
56494
  when(ge(TIME, 8.2), [
54651
56495
  fireLightOff(0),
@@ -55143,11 +56987,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
55143
56987
  ])
55144
56988
  ]
55145
56989
  };
55146
- var at2 = (x, y, z) => [SPOT[0] + x, SPOT[1] + y, SPOT[2] + z];
55147
- var FRONT_LEG = at2(-0.52, 0.2, 0.32);
55148
- var TOP_CORNER = at2(0.3, 0.78, 0.2);
55149
- var TOP_PIECE = at2(-0.3, 0.78, -0.2);
55150
- var FAR_CORNER = at2(0.3, 0.78, -0.2);
56990
+ var at22 = (x, y, z) => [SPOT[0] + x, SPOT[1] + y, SPOT[2] + z];
56991
+ var FRONT_LEG = at22(-0.52, 0.2, 0.32);
56992
+ var TOP_CORNER = at22(0.3, 0.78, 0.2);
56993
+ var TOP_PIECE = at22(-0.3, 0.78, -0.2);
56994
+ var FAR_CORNER = at22(0.3, 0.78, -0.2);
55151
56995
  var HOLE_OBJECT_ID_DRAWS = 12;
55152
56996
  var HOLES = 9;
55153
56997
  var GLOCK_FROM = [-0.4, 1, 6];
@@ -56647,35 +58491,35 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
56647
58491
  var NOOP = () => {
56648
58492
  };
56649
58493
  function createSchedule() {
56650
- const entries = [];
58494
+ const entries2 = [];
56651
58495
  const spare = [];
56652
58496
  return {
56653
58497
  at(time, action) {
56654
58498
  const entry2 = spare.pop() ?? { time: 0, action: NOOP };
56655
58499
  entry2.time = time;
56656
58500
  entry2.action = action;
56657
- entries.push(entry2);
58501
+ entries2.push(entry2);
56658
58502
  },
56659
58503
  update(now) {
56660
- for (let index = entries.length - 1; index >= 0; index--) {
56661
- const entry2 = entries[index];
58504
+ for (let index = entries2.length - 1; index >= 0; index--) {
58505
+ const entry2 = entries2[index];
56662
58506
  if (!entry2 || entry2.time > now) continue;
56663
58507
  const action = entry2.action;
56664
- entries.splice(index, 1);
58508
+ entries2.splice(index, 1);
56665
58509
  entry2.action = NOOP;
56666
58510
  spare.push(entry2);
56667
58511
  action();
56668
58512
  }
56669
58513
  },
56670
58514
  clear() {
56671
- for (const entry2 of entries) {
58515
+ for (const entry2 of entries2) {
56672
58516
  entry2.action = NOOP;
56673
58517
  spare.push(entry2);
56674
58518
  }
56675
- entries.length = 0;
58519
+ entries2.length = 0;
56676
58520
  },
56677
58521
  get pending() {
56678
- return entries.length;
58522
+ return entries2.length;
56679
58523
  }
56680
58524
  };
56681
58525
  }
@@ -57430,12 +59274,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
57430
59274
  var SCORCH_VARIANTS = 3;
57431
59275
  var RIM_COLORS = [2829359, 3815478];
57432
59276
  function createMarkStore() {
57433
- const entries = [];
59277
+ const entries2 = [];
57434
59278
  let version = 0;
57435
59279
  let bloodCount = 0;
57436
59280
  const listeners = /* @__PURE__ */ new Set();
57437
59281
  return {
57438
- entries,
59282
+ entries: entries2,
57439
59283
  get version() {
57440
59284
  return version;
57441
59285
  },
@@ -57444,13 +59288,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
57444
59288
  },
57445
59289
  lift: 0,
57446
59290
  add(mark) {
57447
- entries.push(mark);
59291
+ entries2.push(mark);
57448
59292
  if (mark.kind === "blood") bloodCount++;
57449
59293
  version++;
57450
59294
  for (const listener of listeners) listener({ type: "add", mark });
57451
59295
  },
57452
59296
  clear() {
57453
- entries.length = 0;
59297
+ entries2.length = 0;
57454
59298
  bloodCount = 0;
57455
59299
  this.lift = 0;
57456
59300
  version++;
@@ -61741,7 +63585,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
61741
63585
  breechRound();
61742
63586
  break;
61743
63587
  case "sound":
61744
- world.sound(session.id, String(data.id), session.soundProfile);
63588
+ world.sound(session.id, String(data.id), session.soundProfile, typeof data.lead === "number" ? data.lead : void 0);
61745
63589
  break;
61746
63590
  default:
61747
63591
  break;
@@ -62314,9 +64158,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
62314
64158
  audio,
62315
64159
  layers,
62316
64160
  groundAt,
62317
- sound: (weapon, cue3, profile) => {
62318
- audio?.cue(cue3, profile);
62319
- options.onSound?.({ weapon, cue: cue3, profile });
64161
+ sound: (weapon, cue3, profile, lead) => {
64162
+ const played = audio?.cue(cue3, profile, lead);
64163
+ options.onSound?.({ weapon, cue: cue3, profile, ...played ? { played } : {} });
62320
64164
  },
62321
64165
  schedule: (delay, seconds, step, done) => void tasks.push({ delay, seconds, elapsed: 0, step: (progress) => step(ease(progress)), done })
62322
64166
  };
@@ -63711,7 +65555,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63711
65555
  reloadStart: "Reload started: {weapon} ({ammo}/{capacity})",
63712
65556
  reloadClip: "Reload of {weapon}: {kind}, clip {clip} ({seconds} s)",
63713
65557
  soundCue: "Sound: {cue} ({weapon})",
65558
+ cueRecorded: " \xB7 recorded",
65559
+ cueCarried: " \xB7 inside the next recording",
63714
65560
  gunshotSound: "Shot sound: {profile}, take {take}/4, pitch {pitch}, {space}, {distance} m{far}",
65561
+ gunshotRecorded: " \xB7 recorded take {takes}",
63715
65562
  casingSound: "Case lands: {case} on {surface}",
63716
65563
  spaces: { indoor: "indoors", outdoor: "outdoors" },
63717
65564
  surfaces: { hard: "concrete", wood: "wood", metal: "metal", soft: "dirt" },
@@ -63743,7 +65590,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63743
65590
  reloadStart: "Recarga iniciada: {weapon} ({ammo}/{capacity})",
63744
65591
  reloadClip: "Recarga de {weapon}: {kind}, clipe {clip} ({seconds} s)",
63745
65592
  soundCue: "Som: {cue} ({weapon})",
65593
+ cueRecorded: " \xB7 grava\xE7\xE3o",
65594
+ cueCarried: " \xB7 dentro da grava\xE7\xE3o seguinte",
63746
65595
  gunshotSound: "Som do tiro: {profile}, tomada {take}/4, tom {pitch}, {space}, {distance} m{far}",
65596
+ gunshotRecorded: " \xB7 grava\xE7\xE3o, tomada {takes}",
63747
65597
  casingSound: "C\xE1psula caiu: {case} em {surface}",
63748
65598
  spaces: { indoor: "ambiente fechado", outdoor: "ao ar livre" },
63749
65599
  surfaces: { hard: "concreto", wood: "madeira", metal: "metal", soft: "terra" },
@@ -63865,7 +65715,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63865
65715
  const accessoryText = accessoryTextOf(locale);
63866
65716
  const seedOption = options.seed === void 0 ? {} : { seed: options.seed };
63867
65717
  let audio;
63868
- const ownAudio = () => audio === void 0 ? audio = options.sound === false ? null : createWeaponAudio(void 0, seedOption) : audio;
65718
+ const ownAudio = () => audio === void 0 ? audio = options.sound === false ? null : createWeaponAudio(void 0, { ...seedOption, ...options.samples ? { samples: options.samples } : {} }) : audio;
63869
65719
  const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
63870
65720
  const thumbnailBase = options.thumbnailBase ?? DEFAULT_THUMBNAIL_BASE;
63871
65721
  const withThumbnail = (item, key2) => {
@@ -63952,18 +65802,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
63952
65802
  audio = createWeaponAudio(void 0, {
63953
65803
  ...seedOption,
63954
65804
  engine: lentEngine,
63955
- onGunshot: (shot, input) => lent.log(
63956
- 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)` : "" }),
63957
- { 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 },
63958
- "sound"
63959
- ),
63960
- onCasing: (sound3) => lent.log(fill(text.casingSound, { case: sound3.kind, surface: text.surfaces[sound3.surface] }), { case: sound3.kind, surface: sound3.surface, sample: sound3.sample }, "sound")
65805
+ ...options.samples ? { samples: options.samples } : {},
65806
+ onGunshot: (shot, input, played) => {
65807
+ const recorded = played.kind === "sample" && played.layers.length > 0 ? fill(text.gunshotRecorded, { takes: played.layers.map((layer2) => `${layer2.role} ${layer2.take + 1}`).join(", ") }) : "";
65808
+ lent.log(
65809
+ 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,
65810
+ { 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 },
65811
+ "sound"
65812
+ );
65813
+ },
65814
+ onCasing: (sound3, played) => lent.log(fill(text.casingSound, { case: sound3.kind, surface: text.surfaces[sound3.surface] }) + (played === "sample" ? text.cueRecorded : ""), { case: sound3.kind, surface: sound3.surface, sample: sound3.sample, played }, "sound")
63961
65815
  });
63962
65816
  }
63963
65817
  let simulatedMs = 0;
63964
65818
  const offClock = lent.onFrame((dt) => simulatedMs += dt * 1e3);
63965
65819
  const created = createWeaponFx({
63966
- onSound: ({ weapon, cue: cue3, profile }) => lent.log(fill(text.soundCue, { weapon: arsenal.name(weapon), cue: text.soundCues[cue3] ?? cue3 }), { weapon, cue: cue3, weight: profile.weight, material: profile.material }, "sound"),
65820
+ onSound: ({ weapon, cue: cue3, profile, played }) => lent.log(fill(text.soundCue, { weapon: arsenal.name(weapon), cue: text.soundCues[cue3] ?? cue3 }) + (played === "sample" ? text.cueRecorded : played === "carried" ? text.cueCarried : ""), { weapon, cue: cue3, weight: profile.weight, material: profile.material, ...played ? { played } : {} }, "sound"),
63967
65821
  THREE: lent.THREE,
63968
65822
  scene: lent.scene,
63969
65823
  camera: lent.camera,
@@ -64130,6 +65984,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64130
65984
  push_outward: "Every body inside the reach moved away from the blast ({moved} of {n}, {least} m at the least)",
64131
65985
  push_falloff: "{what}: the nearer one moved more than the farther one ({near} m at {nearDistance} m, {far} m at {farDistance} m)",
64132
65986
  push_person: "{who} at {distance} m was pushed {moved} m away",
65987
+ push_sheltered: "{what} at {distance} m stood behind cover ({cover}% of the blast got through) and moved only {moved} m",
64133
65988
  push_heavy: "{what} (too heavy to grab, not fixed) was moved {moved} m by the blast"
64134
65989
  }
64135
65990
  },
@@ -64215,6 +66070,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64215
66070
  push_outward: "Todo corpo dentro do alcance se afastou da explos\xE3o ({moved} de {n}, no m\xEDnimo {least} m)",
64216
66071
  push_falloff: "{what}: o mais perto andou mais que o mais longe ({near} m a {nearDistance} m, {far} m a {farDistance} m)",
64217
66072
  push_person: "{who} a {distance} m foi empurrado {moved} m para longe",
66073
+ push_sheltered: "{what} a {distance} m estava atr\xE1s de cobertura ({cover}% da explos\xE3o passou) e andou s\xF3 {moved} m",
64218
66074
  push_heavy: "{what} (pesado demais para pegar, mas solto) foi movido {moved} m pela explos\xE3o"
64219
66075
  }
64220
66076
  }
@@ -64760,6 +66616,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64760
66616
  ];
64761
66617
  var PUSH_READ_AT = 0.8;
64762
66618
  var MOVED = 0.15;
66619
+ var SHELTERED = 0.5;
66620
+ var SHELTERED_MOST = 1;
66621
+ var CHEST = 1.1;
66622
+ var MIDDLE = 0.3;
64763
66623
  var HEAVY_ITEMS = ["camp_table"];
64764
66624
  var flat = (a, b) => Math.hypot(a[0] - b[0], a[2] - b[2]);
64765
66625
  var bearing = (centre, distance3, degrees) => [centre[0] + Math.sin(degrees * Math.PI / 180) * distance3, 0, centre[2] + Math.cos(degrees * Math.PI / 180) * distance3];
@@ -64776,9 +66636,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64776
66636
  name: mode === "p13" ? text.push_p13 : text.push_grenade,
64777
66637
  run: ({ room, step, expect }) => {
64778
66638
  let centre;
66639
+ let source;
64779
66640
  if (mode === "p13") {
64780
66641
  room.openArea(EXPLOSIVES_AREA_ID);
64781
66642
  const cylinder2 = room.spawned().find((spawned) => spawned.itemId === CYLINDER_ITEM);
66643
+ source = cylinder2?.objectId;
64782
66644
  const at3 = cylinder2?.root?.position;
64783
66645
  centre = [at3?.x ?? 0, 0, at3?.z ?? -10.66];
64784
66646
  } else {
@@ -64829,6 +66691,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64829
66691
  const blast = bench.lastBlast();
64830
66692
  expect(!!blast, text.push_told, { waited });
64831
66693
  if (!blast) return;
66694
+ const coverOf = new Map(
66695
+ placed.map((body) => {
66696
+ const from = origin.get(body.objectId) ?? body.from;
66697
+ const person = body.kind === "zombie" || body.kind === "survivor";
66698
+ const ignore = source ? [source, body.objectId] : [body.objectId];
66699
+ return [body.objectId, room.world.blastReach?.(blast.point, [from[0], from[1] + (person ? CHEST : MIDDLE), from[2]], { ignore }) ?? 1];
66700
+ })
66701
+ );
64832
66702
  step(PUSH_READ_AT);
64833
66703
  const rows = placed.map((body) => {
64834
66704
  const from = origin.get(body.objectId) ?? body.from;
@@ -64836,10 +66706,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64836
66706
  const distance3 = flat(from, blast.point);
64837
66707
  const away = Math.max(1e-6, distance3);
64838
66708
  const outward = ((now[0] - from[0]) * (from[0] - blast.point[0]) + (now[2] - from[2]) * (from[2] - blast.point[2])) / away;
64839
- return { ...body, distance: distance3, outward, moved: flat(now, from) };
66709
+ return { ...body, distance: distance3, outward, moved: flat(now, from), cover: coverOf.get(body.objectId) ?? 1 };
64840
66710
  });
64841
66711
  const torn = (row) => (PUSH_KINDS.some((entry2) => entry2.kind === row.kind) || HEAVY_ITEMS.includes(row.kind)) && partsOf(row.objectId) < (partsBefore.get(row.objectId) ?? 0);
64842
- const reached = rows.filter((row) => row.distance < blast.radius * 0.85 && !torn(row));
66712
+ const inReach = rows.filter((row) => row.distance < blast.radius * 0.85 && !torn(row));
66713
+ const sheltered = inReach.filter((row) => row.cover < SHELTERED);
66714
+ for (const row of sheltered) {
66715
+ 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 } });
66716
+ }
66717
+ const reached = inReach.filter((row) => row.cover >= SHELTERED);
64843
66718
  const movedAway = reached.filter((row) => row.outward >= MOVED);
64844
66719
  const least = reached.length === 0 ? 0 : Math.min(...reached.map((row) => row.outward));
64845
66720
  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) });
@@ -65013,12 +66888,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65013
66888
  // src/weapon-props.ts
65014
66889
  function createWeaponProps(options = {}) {
65015
66890
  const entryOf = ([id, model]) => ({ id, model: model ?? { item: "" } });
65016
- const entries = mergeById(SHIPPED_WEAPON_ITEM_MODELS.map(entryOf), (options.packs ?? []).flatMap((pack2) => Object.entries(pack2).map(entryOf)));
66891
+ const entries2 = mergeById(SHIPPED_WEAPON_ITEM_MODELS.map(entryOf), (options.packs ?? []).flatMap((pack2) => Object.entries(pack2).map(entryOf)));
65017
66892
  const patches = {};
65018
66893
  for (const [id, model] of Object.entries(options.patch ?? {})) patches[id] = { model };
65019
- applyPatches(entries, patches);
65020
- removeIds(entries, options.remove);
65021
- return weaponPropsOver(new Map([...entries].map(([id, entry2]) => [id, entry2.model])));
66894
+ applyPatches(entries2, patches);
66895
+ removeIds(entries2, options.remove);
66896
+ return weaponPropsOver(new Map([...entries2].map(([id, entry2]) => [id, entry2.model])));
65022
66897
  }
65023
66898
  return __toCommonJS(index_exports);
65024
66899
  })();