@adatechnology/game-weapons-three 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -290,16 +290,26 @@ var ACCESSORY_TEXT = {
290
290
  var accessoryTextOf = (locale) => ACCESSORY_TEXT[locale] ?? ACCESSORY_TEXT.en;
291
291
 
292
292
  // src/audio.ts
293
- import { createRng } from "@adatechnology/game-weapons";
293
+ import { MIX_PROFILES, createAudioEngine } from "@adatechnology/game-audio";
294
294
 
295
295
  // src/audio/mechanical-sounds.ts
296
296
  var metal = (at, frequency, gain) => ({ layer: "metal", at, frequency, gain });
297
297
  var sound = (at, soundClass, volume) => ({ layer: "class", at, soundClass, volume });
298
298
  var thud = (at, gain, frequency) => ({ layer: "thud", at, gain, frequency });
299
+ var sceneClick = (at, f, vol) => [
300
+ { layer: "scene_tone", at, wave: "square", f, f2: f * 0.7, d: 0.02, vol },
301
+ { layer: "scene_noise", at, type: "highpass", f: 3e3, q: 0.7, d: 0.03, vol: vol * 0.8 }
302
+ ];
303
+ var sceneNoise = (at, type, f, q, d, vol) => ({ layer: "scene_noise", at, type, f, q, d, vol });
304
+ var sceneEmpty = sceneClick(0, 3200, 0.35);
305
+ var sceneMagOut = [...sceneClick(0, 1600, 0.4), sceneNoise(0.03, "bandpass", 900, 2, 0.08, 0.3)];
306
+ var sceneMagIn = [...sceneClick(0, 1100, 0.5), { layer: "scene_tone", at: 0, wave: "sine", f: 320, f2: 200, d: 0.05, vol: 0.35 }];
307
+ var sceneBolt = [...sceneClick(0, 2200, 0.4), ...sceneClick(0.12, 2700, 0.4)];
308
+ var sceneShell = [...sceneClick(0, 1400, 0.35), sceneNoise(0.02, "bandpass", 1200, 3, 0.05, 0.25)];
299
309
  var slide = [metal(0, 900, 0.35), metal(0.14, 1300, 0.35)];
300
310
  var seat = [metal(0, 1100, 0.45), sound(0, "reload", 0.8)];
301
311
  var MECHANICAL_SOUNDS = {
302
- dry_fire: [sound(0, "empty", 1), metal(0, 2600, 0.25)],
312
+ dry_fire: [sound(0, "empty", 1), ...sceneEmpty],
303
313
  misfire: [sound(0, "empty", 0.8), metal(0, 2200, 0.2)],
304
314
  feed: [metal(0, 1900, 0.2)],
305
315
  cycle: [metal(0.22, 900, 0.35), metal(0.36, 1300, 0.35)],
@@ -312,11 +322,11 @@ var MECHANICAL_SOUNDS = {
312
322
  stock_fold: [metal(0, 800, 0.3), metal(0.18, 1200, 0.3), thud(0.3, 0.2, 600)],
313
323
  light_click: [metal(0, 2400, 0.2), metal(0.03, 1800, 0.12)],
314
324
  attach: [metal(0, 1400, 0.35), thud(0.04, 0.2, 700), metal(0.12, 1900, 0.25)],
315
- magazine_out: [metal(0, 1500, 0.35), sound(0, "reload", 0.6)],
316
- magazine_in: seat,
317
- charge: slide,
325
+ magazine_out: [...sceneMagOut, sound(0, "reload", 0.6)],
326
+ magazine_in: [...sceneMagIn, sound(0, "reload", 0.8)],
327
+ charge: sceneBolt,
318
328
  bolt_open: [metal(0, 900, 0.35)],
319
- round_in: [metal(0, 2e3, 0.2), sound(0, "reload", 0.6)],
329
+ round_in: [...sceneShell, sound(0, "reload", 0.6)],
320
330
  bolt_close: [metal(0, 1300, 0.35)],
321
331
  pump_cycle: slide,
322
332
  lever_cycle: [metal(0, 1e3, 0.35), metal(0.12, 1500, 0.35)],
@@ -371,6 +381,9 @@ var mechanicalSoundOf = (event) => {
371
381
  }
372
382
  };
373
383
 
384
+ // src/audio/recipes.ts
385
+ import { DISTANCE_CURVES } from "@adatechnology/game-audio";
386
+
374
387
  // src/audio/shot-sound.ts
375
388
  import { muzzleEnergy } from "@adatechnology/game-weapons";
376
389
  var GAME_SOUND_CLASSES = {
@@ -457,100 +470,185 @@ var computeShotSound = (input, options = {}) => {
457
470
  };
458
471
  };
459
472
 
473
+ // src/audio/recipes.ts
474
+ var CLASS_BURST_ID = "weapon_class_burst";
475
+ var BURST_IDS = { lowpass: "weapon_burst_lowpass", highpass: "weapon_burst_highpass" };
476
+ var SCENE_NOISE_IDS = { lowpass: "scene_noise_lowpass", highpass: "scene_noise_highpass", bandpass: "scene_noise_bandpass" };
477
+ var SCENE_TONE_IDS = { sine: "scene_tone_sine", square: "scene_tone_square" };
478
+ var TONE_IDS = { sine: "weapon_tone_sine", sine_sweep: "weapon_tone_sine_sweep", square: "weapon_tone_square" };
479
+ var SOURCE_CLASS_BURST = "jogo:L62097-62110 sfx: lowpass noise, gain set at once, exponential fall to 0.001, stopped 0.05 s later";
480
+ var SOURCE_SDK = "sdk: the weapon synth of weapons-three (burst and tone), moved onto game-audio with the same nodes";
481
+ var FIT = { kind: "noise", offset: "fit" };
482
+ function classBurstId(curve) {
483
+ const same = curve.gainFalloff === GAME_DISTANCE_CURVE.gainFalloff && curve.cutoffFalloff === GAME_DISTANCE_CURVE.cutoffFalloff && curve.minGain === GAME_DISTANCE_CURVE.minGain;
484
+ return same ? CLASS_BURST_ID : `${CLASS_BURST_ID}@${curve.gainFalloff},${curve.cutoffFalloff},${curve.minGain}`;
485
+ }
486
+ function classBurstRecipe(curve) {
487
+ return {
488
+ id: classBurstId(curve),
489
+ bus: "weapons",
490
+ order: "weapon",
491
+ duration: { param: "seconds" },
492
+ layers: [
493
+ {
494
+ gain: { param: "gain" },
495
+ source: FIT,
496
+ filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: { param: "cutoff" } } }] },
497
+ envelope: [
498
+ [0, 1, "set"],
499
+ ["1d", 1e-3, "exp", "fixed"]
500
+ ]
501
+ }
502
+ ],
503
+ falloff: { model: "inverse_square", k: curve.gainFalloff, cutoffK: curve.cutoffFalloff, minGain: curve.minGain },
504
+ sources: [SOURCE_CLASS_BURST]
505
+ };
506
+ }
507
+ var burstLayer = (type) => ({
508
+ gain: { param: "gain" },
509
+ source: FIT,
510
+ filters: { mode: "serial", bank: [{ type, frequency: { set: { param: "frequency" } }, q: { param: "q" } }] },
511
+ envelope: [
512
+ [0, 1e-4, "set", "fixed"],
513
+ [4e-3, 1, "exp"],
514
+ ["1d", 1e-4, "exp", "fixed"]
515
+ ]
516
+ });
517
+ var burstRecipe = (type) => ({
518
+ id: BURST_IDS[type],
519
+ bus: "weapons",
520
+ order: "weapon",
521
+ duration: { param: "seconds" },
522
+ layers: [burstLayer(type)],
523
+ falloff: { model: "none" },
524
+ sources: [SOURCE_SDK]
525
+ });
526
+ var toneLayer = (wave, sweep) => ({
527
+ gain: { param: "gain" },
528
+ source: { kind: "osc", wave, frequency: sweep ? { from: { param: "frequency" }, to: { param: "to" }, curve: "exp", over: "duration" } : { from: { param: "frequency" } } },
529
+ envelope: [
530
+ [0, 1e-4, "set", "fixed"],
531
+ [3e-3, 1, "exp"],
532
+ ["1d", 1e-4, "exp", "fixed"]
533
+ ]
534
+ });
535
+ var toneRecipe = (id, wave, sweep) => ({
536
+ id,
537
+ bus: "weapons",
538
+ order: "weapon",
539
+ duration: { param: "seconds" },
540
+ layers: [toneLayer(wave, sweep)],
541
+ falloff: { model: "none" },
542
+ sources: [SOURCE_SDK]
543
+ });
544
+ var SOURCE_SCENE = "cena:L1323-L1324 sNoise and sTone, played at the listener (distance 0), through the art scene output stage";
545
+ var SCENE_ATTACK = 2e-3;
546
+ var sceneNoiseRecipe = (type, slide2) => ({
547
+ id: slide2 ? `${SCENE_NOISE_IDS[type]}_slide` : SCENE_NOISE_IDS[type],
548
+ bus: "weapons",
549
+ order: "layered",
550
+ duration: { param: "d", add: SCENE_ATTACK },
551
+ pan: true,
552
+ layers: [
553
+ {
554
+ gain: { param: "vol" },
555
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
556
+ filters: { mode: "serial", bank: [{ type, frequency: slide2 ? { set: { param: "f" }, to: { param: "f2" }, curve: "exp", over: { param: "d" } } : { set: { param: "f" } }, q: { param: "q" } }] },
557
+ envelope: [
558
+ [0, 1e-4, "set", "fixed"],
559
+ [SCENE_ATTACK, 1, "exp"],
560
+ ["1d", 1e-4, "exp", "fixed"]
561
+ ]
562
+ }
563
+ ],
564
+ falloff: DISTANCE_CURVES.art_scene,
565
+ sources: [SOURCE_SCENE]
566
+ });
567
+ var sceneToneRecipe = (wave) => ({
568
+ id: SCENE_TONE_IDS[wave],
569
+ bus: "weapons",
570
+ order: "layered",
571
+ duration: { param: "d", add: SCENE_ATTACK },
572
+ pan: true,
573
+ layers: [
574
+ {
575
+ gain: { param: "vol" },
576
+ source: { kind: "osc", wave, frequency: { from: { param: "f" }, to: { param: "f2" }, curve: "exp", over: "duration" } },
577
+ envelope: [
578
+ [0, 1e-4, "set", "fixed"],
579
+ [SCENE_ATTACK, 1, "exp"],
580
+ ["1d", 1e-4, "exp", "fixed"]
581
+ ]
582
+ }
583
+ ],
584
+ falloff: DISTANCE_CURVES.art_scene,
585
+ sources: [SOURCE_SCENE]
586
+ });
587
+ function weaponRecipes(curve) {
588
+ return [
589
+ classBurstRecipe(curve),
590
+ burstRecipe("lowpass"),
591
+ burstRecipe("highpass"),
592
+ toneRecipe(TONE_IDS.sine, "sine", false),
593
+ toneRecipe(TONE_IDS.sine_sweep, "sine", true),
594
+ toneRecipe(TONE_IDS.square, "square", false),
595
+ ...["lowpass", "highpass", "bandpass"].flatMap((type) => [sceneNoiseRecipe(type, false), sceneNoiseRecipe(type, true)]),
596
+ sceneToneRecipe("sine"),
597
+ sceneToneRecipe("square")
598
+ ];
599
+ }
600
+
460
601
  // src/audio.ts
461
602
  var NOISE_SECONDS = 1.5;
462
- var globalContext = () => {
463
- const scope2 = globalThis;
464
- const Ctor = scope2.AudioContext ?? scope2.webkitAudioContext;
465
- return Ctor ? new Ctor() : null;
466
- };
603
+ var DEFAULT_SEED = 1;
467
604
  function createWeaponAudio(volume = 0.6, options = {}) {
468
- const makeContext = options.createContext ?? globalContext;
469
- const rng = createRng(options.seed ?? 1);
605
+ const seed = options.seed ?? DEFAULT_SEED;
470
606
  const classes = { ...GAME_SOUND_CLASSES, ...options.classes };
471
607
  const distanceCurve = { ...GAME_DISTANCE_CURVE, ...options.distance };
472
608
  const mechanicalTable = { ...MECHANICAL_SOUNDS, ...options.mechanical };
473
- let ctx = null;
474
- let master = null;
475
- let noise = null;
476
- let level = volume;
477
- const ready = () => {
478
- if (!ctx) {
479
- const created = makeContext();
480
- if (!created) return null;
481
- ctx = created;
482
- master = ctx.createGain();
483
- master.gain.value = level;
484
- master.connect(ctx.destination);
485
- noise = ctx.createBuffer(1, Math.floor(ctx.sampleRate * NOISE_SECONDS), ctx.sampleRate);
486
- const data = noise.getChannelData(0);
487
- for (let i = 0; i < data.length; i++) data[i] = rng() * 2 - 1;
488
- }
489
- if (ctx.state === "suspended") void ctx.resume();
490
- return ctx;
491
- };
609
+ const shared = options.engine;
610
+ const engine = shared ?? createAudioEngine({
611
+ seed,
612
+ mix: { ...MIX_PROFILES.game, master: volume, noise: { seconds: NOISE_SECONDS, create: "eager" } },
613
+ maxVoices: Number.POSITIVE_INFINITY,
614
+ ...options.createContext ? { createContext: options.createContext } : {}
615
+ });
616
+ const channel = shared && options.seed !== void 0 ? shared.channel({ seed }) : engine.channel({});
617
+ engine.register({ id: `weapons_three@${classBurstId(distanceCurve)}`, sounds: weaponRecipes(distanceCurve) });
618
+ const classBurst = classBurstId(distanceCurve);
492
619
  const burst = (config) => {
493
- const audio = ready();
494
- if (!audio || !noise || !master) return;
495
- const start = audio.currentTime + (config.at ?? 0);
496
- const source = audio.createBufferSource();
497
- source.buffer = noise;
498
- const filter = audio.createBiquadFilter();
499
- filter.type = config.filter;
500
- filter.frequency.value = config.frequency;
501
- filter.Q.value = config.q ?? 0.8;
502
- const gain = audio.createGain();
503
- gain.gain.setValueAtTime(1e-4, start);
504
- gain.gain.exponentialRampToValueAtTime(Math.max(config.gain, 2e-4), start + 4e-3);
505
- gain.gain.exponentialRampToValueAtTime(1e-4, start + config.seconds);
506
- source.connect(filter).connect(gain).connect(master);
507
- source.start(start, rng() * Math.max(0, NOISE_SECONDS - config.seconds));
508
- source.stop(start + config.seconds + 0.05);
620
+ const id = config.filter === "highpass" ? BURST_IDS.highpass : BURST_IDS.lowpass;
621
+ const params = { frequency: config.frequency, q: config.q ?? 0.8, gain: Math.max(config.gain, 2e-4), seconds: config.seconds };
622
+ channel.play(id, { params, ...config.at ? { delay: config.at } : {} });
509
623
  };
510
- const classBurst = (at, cutoff, gain, seconds) => {
511
- const audio = ready();
512
- if (!audio || !noise || !master) return;
513
- const start = audio.currentTime + at;
514
- const source = audio.createBufferSource();
515
- source.buffer = noise;
516
- const filter = audio.createBiquadFilter();
517
- filter.type = "lowpass";
518
- filter.frequency.value = cutoff;
519
- const envelope = audio.createGain();
520
- envelope.gain.setValueAtTime(gain, start);
521
- envelope.gain.exponentialRampToValueAtTime(1e-3, start + seconds);
522
- source.connect(filter).connect(envelope).connect(master);
523
- source.start(start, rng() * Math.max(0, NOISE_SECONDS - seconds));
524
- source.stop(start + seconds + 0.05);
624
+ const classBurstAt = (at, cutoff, gain, seconds) => {
625
+ channel.play(classBurst, { params: { cutoff, gain, seconds }, ...at ? { delay: at } : {} });
525
626
  };
526
627
  const tone = (config) => {
527
- const audio = ready();
528
- if (!audio || !master) return;
529
- const start = audio.currentTime + (config.at ?? 0);
530
- const osc = audio.createOscillator();
531
- osc.type = config.type ?? "sine";
532
- osc.frequency.setValueAtTime(config.frequency, start);
533
- if (config.to) osc.frequency.exponentialRampToValueAtTime(config.to, start + config.seconds);
534
- const gain = audio.createGain();
535
- gain.gain.setValueAtTime(1e-4, start);
536
- gain.gain.exponentialRampToValueAtTime(Math.max(config.gain, 2e-4), start + 3e-3);
537
- gain.gain.exponentialRampToValueAtTime(1e-4, start + config.seconds);
538
- osc.connect(gain).connect(master);
539
- osc.start(start);
540
- osc.stop(start + config.seconds + 0.05);
628
+ const id = config.to ? TONE_IDS.sine_sweep : config.type === "square" ? TONE_IDS.square : TONE_IDS.sine;
629
+ const params = { frequency: config.frequency, gain: Math.max(config.gain, 2e-4), seconds: config.seconds, ...config.to ? { to: config.to } : {} };
630
+ channel.play(id, { params, ...config.at ? { delay: config.at } : {} });
541
631
  };
542
632
  const metal2 = (at, frequency, gain) => {
543
633
  burst({ at, seconds: 0.05, gain, filter: "highpass", frequency, q: 2 });
544
634
  tone({ at, seconds: 0.04, gain: gain * 0.6, frequency: frequency * 0.6, type: "square" });
545
635
  };
546
636
  const play = (soundClass, classVolume = 1, distance = 0, at = 0) => {
547
- const attenuation = classVolume / (1 + distance * distance * distanceCurve.gainFalloff);
548
- if (attenuation < distanceCurve.minGain) return;
549
637
  const [cutoff, gain, seconds] = classes[soundClass] ?? FALLBACK_SOUND_CLASS;
550
- classBurst(at, cutoff / (1 + distance * distanceCurve.cutoffFalloff), gain * attenuation, seconds);
638
+ channel.play(classBurst, { volume: classVolume, distance, params: { cutoff, gain, seconds }, ...at ? { delay: at } : {} });
639
+ };
640
+ const sceneTone = (entry) => {
641
+ channel.play(SCENE_TONE_IDS[entry.wave], { params: { f: entry.f, f2: entry.f2 ?? entry.f, d: entry.d, vol: entry.vol }, ...entry.at ? { delay: entry.at } : {} });
642
+ };
643
+ const sceneNoise2 = (entry) => {
644
+ const slide2 = entry.f2 !== void 0;
645
+ const id = slide2 ? `${SCENE_NOISE_IDS[entry.type]}_slide` : SCENE_NOISE_IDS[entry.type];
646
+ channel.play(id, { params: { f: entry.f, ...slide2 ? { f2: entry.f2 } : {}, q: entry.q, d: entry.d, vol: entry.vol }, ...entry.at ? { delay: entry.at } : {} });
551
647
  };
552
648
  const step = (entry) => {
553
649
  if (entry.layer === "metal") metal2(entry.at, entry.frequency, entry.gain);
650
+ else if (entry.layer === "scene_tone") sceneTone(entry);
651
+ else if (entry.layer === "scene_noise") sceneNoise2(entry);
554
652
  else if (entry.layer === "class") play(entry.soundClass, entry.volume, 0, entry.at);
555
653
  else burst({ at: entry.at, seconds: 0.09, gain: entry.gain, filter: "lowpass", frequency: entry.frequency });
556
654
  };
@@ -562,7 +660,7 @@ function createWeaponAudio(volume = 0.6, options = {}) {
562
660
  shot(energyJ, category, extras = {}) {
563
661
  const sound2 = shotSound({ ...extras, energyJ, category });
564
662
  if (!sound2.audible) return;
565
- classBurst(0, sound2.cutoff, sound2.gain, sound2.duration);
663
+ classBurstAt(0, sound2.cutoff, sound2.gain, sound2.duration);
566
664
  if (sound2.crack.gain > 0) burst({ seconds: sound2.crack.duration, gain: sound2.crack.gain, filter: "highpass", frequency: sound2.crack.cutoff });
567
665
  tone({ seconds: sound2.body.duration, gain: sound2.body.gain, frequency: sound2.body.frequency, to: sound2.body.to });
568
666
  },
@@ -590,28 +688,26 @@ function createWeaponAudio(volume = 0.6, options = {}) {
590
688
  mechanical("cycle");
591
689
  },
592
690
  clink() {
593
- const f = 3200 + rng() * 2e3;
594
- tone({ seconds: 0.09, gain: 0.12, frequency: f });
595
- tone({ seconds: 0.06, gain: 0.06, frequency: f * 2.7 });
691
+ const f = 4200 + channel.random() * 1500;
692
+ const at = 0.25 + channel.random() * 0.2;
693
+ sceneTone({ layer: "scene_tone", at, wave: "sine", f, f2: 3800, d: 0.06, vol: 0.06 });
596
694
  },
597
695
  thud() {
598
696
  burst({ seconds: 0.09, gain: 0.35, filter: "lowpass", frequency: 380 });
599
697
  },
600
698
  setVolume(next) {
601
- level = Math.max(0, Math.min(1, next));
602
- if (master) master.gain.value = level;
699
+ const level = Math.max(0, Math.min(1, next));
700
+ if (shared) shared.setBus("weapons", { gain: level });
701
+ else engine.setMaster(level);
603
702
  },
604
703
  dispose() {
605
- void ctx?.close();
606
- ctx = null;
607
- master = null;
608
- noise = null;
704
+ if (!shared) engine.dispose();
609
705
  }
610
706
  };
611
707
  }
612
708
 
613
709
  // src/box-fx.ts
614
- import { createRng as createRng2, muzzleEnergy as muzzleEnergy2 } from "@adatechnology/game-weapons";
710
+ import { createRng, muzzleEnergy as muzzleEnergy2 } from "@adatechnology/game-weapons";
615
711
 
616
712
  // src/fx/layers.ts
617
713
  var createLayers = () => ({ casings: true, magazines: true, muzzleFlash: true, attachments: true, beams: true });
@@ -3862,7 +3958,7 @@ var GRAVITY = 9.81;
3862
3958
  function createBoxFx(options) {
3863
3959
  const { THREE, scene, camera, arsenal, groundAt } = options;
3864
3960
  const audio = options.audio ?? null;
3865
- const random = createRng2(options.seed ?? 1);
3961
+ const random = createRng(options.seed ?? 1);
3866
3962
  const layers = options.layers ?? createLayers();
3867
3963
  const bodies = [];
3868
3964
  const brass = new THREE.MeshStandardMaterial({ color: 13214282, metalness: 0.8, roughness: 0.35 });
@@ -4029,7 +4125,7 @@ function createBoxFx(options) {
4029
4125
 
4030
4126
  // src/fx/viewmodel.ts
4031
4127
  import { createToolClipPlayer, toolAnimationStep } from "@adatechnology/game-contracts";
4032
- import { createRng as createRng3, muzzleEnergy as muzzleEnergy3 } from "@adatechnology/game-weapons";
4128
+ import { createRng as createRng2, muzzleEnergy as muzzleEnergy3 } from "@adatechnology/game-weapons";
4033
4129
 
4034
4130
  // src/animation/clip-builder.ts
4035
4131
  import { STAGE_EVENT } from "@adatechnology/game-contracts";
@@ -5520,7 +5616,7 @@ var caliberOf = (arsenal, def) => def.action.kind === "firearm" ? arsenal.calibe
5520
5616
  function createViewmodel(options) {
5521
5617
  const { THREE, scene, camera, arsenal, groundAt, audio } = options;
5522
5618
  const kit = modelKit(THREE);
5523
- const random = createRng3(options.seed ?? 1);
5619
+ const random = createRng2(options.seed ?? 1);
5524
5620
  const layers = options.layers ?? createLayers();
5525
5621
  const distance = () => options.soundDistance?.() ?? 0;
5526
5622
  const particles = createParticles(THREE, scene, groundAt);
@@ -6769,7 +6865,8 @@ function createWeaponsTestModule(options = {}) {
6769
6865
  const arsenal = options.arsenal ?? createArsenal({ locale });
6770
6866
  const text = textOf(locale);
6771
6867
  const accessoryText = accessoryTextOf(locale);
6772
- const audio = options.sound === false ? null : createWeaponAudio(void 0, options.seed === void 0 ? {} : { seed: options.seed });
6868
+ const seedOption = options.seed === void 0 ? {} : { seed: options.seed };
6869
+ let audio = options.sound === false ? null : createWeaponAudio(void 0, seedOption);
6773
6870
  const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
6774
6871
  const thumbnailBase = options.thumbnailBase ?? DEFAULT_THUMBNAIL_BASE;
6775
6872
  const withThumbnail = (item, key) => {
@@ -6796,7 +6893,9 @@ function createWeaponsTestModule(options = {}) {
6796
6893
  id: "weapons_area",
6797
6894
  name: text.name,
6798
6895
  group: text.group,
6799
- audio,
6896
+ get audio() {
6897
+ return audio;
6898
+ },
6800
6899
  get fx() {
6801
6900
  return fx;
6802
6901
  },
@@ -6844,6 +6943,11 @@ function createWeaponsTestModule(options = {}) {
6844
6943
  ],
6845
6944
  attach(lent) {
6846
6945
  host = lent;
6946
+ const lentEngine = lent.audio?.engine;
6947
+ if (audio && lentEngine) {
6948
+ audio.dispose();
6949
+ audio = createWeaponAudio(void 0, { ...seedOption, engine: lentEngine });
6950
+ }
6847
6951
  const created = createWeaponFx({
6848
6952
  THREE: lent.THREE,
6849
6953
  scene: lent.scene,