@adatechnology/game-test-room 0.23.0 → 0.24.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.d.ts CHANGED
@@ -751,6 +751,11 @@ interface TestRoomOptions {
751
751
  audio?: AudioEngine | false;
752
752
  /** Where the kit's recorded sound files (game-audio `assets/sfx/`) are served, for the room's engine (default `./sfx/`); `null` plays the recipes only. */
753
753
  samplesBaseUrl?: string | null;
754
+ /**
755
+ * Where the owner's own sounds are served (`sfx:user` of game-audio writes them to `.sfx-local/`, kept out of git), default `./sfx-local/`; `null`
756
+ * leaves them out. They load after the kit's: a sound the kit lacks plays the owner's file, and one the kit has keeps the kit's in front.
757
+ */
758
+ userSamplesBaseUrl?: string | null;
754
759
  /** The environment store modules get as `host.environment` (the weather module writes it, the others read it). Default: a new one with a calm dry day. */
755
760
  environment?: EnvironmentBus;
756
761
  /** Show the built-in HUD and panels (default true). */
@@ -6657,7 +6657,7 @@ var GameTestRoom = (() => {
6657
6657
  };
6658
6658
  var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
6659
6659
 
6660
- // ../game-audio/dist/chunk-7TLFY7VO.js
6660
+ // ../game-audio/dist/chunk-KBTUANNG.js
6661
6661
  var DISTANCE_CURVES = {
6662
6662
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
6663
6663
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -8526,10 +8526,12 @@ var GameTestRoom = (() => {
8526
8526
  noise: () => noiseBuffer(store, audio, mix.noise.seconds),
8527
8527
  time
8528
8528
  });
8529
+ const recipeBank = registry.bankOf(plan.id);
8529
8530
  announce({
8530
8531
  id: plan.id,
8531
8532
  kind: "recipe",
8532
8533
  ...fallbackOf ? { fallbackOf } : {},
8534
+ ...recipeBank ? { bank: recipeBank } : {},
8533
8535
  requestedAt,
8534
8536
  startAt: time,
8535
8537
  outputLatency: latencyOf(audio),
@@ -8631,9 +8633,11 @@ var GameTestRoom = (() => {
8631
8633
  const time = requestedAt + (playOptions.delay ?? 0);
8632
8634
  const chokeList = sample.choke ? choked(sample.id) : void 0;
8633
8635
  const realized = realizeSample(plan, { context: audio, output: sampleOutput(plan.bus), buffer: (url2) => sampleStore.buffer(url2), time });
8636
+ const sampleBank = registry.bankOf(plan.id);
8634
8637
  announce({
8635
8638
  id: plan.id,
8636
8639
  kind: "sample",
8640
+ ...sampleBank ? { bank: sampleBank } : {},
8637
8641
  requestedAt,
8638
8642
  startAt: time,
8639
8643
  outputLatency: latencyOf(audio),
@@ -8954,7 +8958,11 @@ var GameTestRoom = (() => {
8954
8958
  ["casing_drop_terra_pistol", 1],
8955
8959
  ["casing_drop_terra_rifle", 1],
8956
8960
  ["casing_drop_terra_shell", 1],
8957
- ["rocket_fire", 2]
8961
+ ["rocket_fire", 2],
8962
+ ["smg_45_auto", 2, "sfx_pistol"],
8963
+ ["weapon_draw", 2],
8964
+ ["suppressor_attach", 3],
8965
+ ["suppressor_detach", 3]
8958
8966
  ]),
8959
8967
  ...entries("explosoes-fogo", [
8960
8968
  ["explosion_grenade", 1, "boom"],
@@ -8968,7 +8976,11 @@ var GameTestRoom = (() => {
8968
8976
  ["fire_loop_large", 1],
8969
8977
  ["fire_extinguish", 2, "hiss"],
8970
8978
  ["gas_leak_hiss", 2],
8971
- ["structure_collapse", 2, "crumble"]
8979
+ ["structure_collapse", 2, "crumble"],
8980
+ ["grenade_bounce", 1],
8981
+ ["c4_plant", 2],
8982
+ ["c4_beep", 2],
8983
+ ["c4_disarm", 2]
8972
8984
  ]),
8973
8985
  ...entries("personagem", [
8974
8986
  ["footstep_concrete_walk", 1, "sfx_step"],
@@ -8984,7 +8996,19 @@ var GameTestRoom = (() => {
8984
8996
  ["blunt_hit", 1],
8985
8997
  ["bite", 1, "sfx_flesh"],
8986
8998
  ["body_fall", 1],
8987
- ["player_hurt", 2]
8999
+ ["player_hurt", 2],
9000
+ ["footstep_tile_walk", 1, "sfx_step"],
9001
+ ["footstep_water_walk", 1, "sfx_step"],
9002
+ ["footstep_grate_walk", 2, "sfx_step"],
9003
+ ["footstep_duct_walk", 3, "sfx_step"],
9004
+ ["ladder_climb", 2],
9005
+ ["swim", 2],
9006
+ ["zombie_groan", 1],
9007
+ ["zombie_alert", 1],
9008
+ ["zombie_attack", 1],
9009
+ ["zombie_pain", 1],
9010
+ ["zombie_claw_hit", 1, "sfx_flesh"],
9011
+ ["zombie_claw_miss", 2]
8988
9012
  ]),
8989
9013
  ...entries("veiculos", [
8990
9014
  ["engine_loop_car", 1],
@@ -9012,7 +9036,8 @@ var GameTestRoom = (() => {
9012
9036
  ["wind_loop", 2],
9013
9037
  ["thunder_near", 2],
9014
9038
  ["thunder_far", 2],
9015
- ["water_splash", 2]
9039
+ ["water_splash", 2],
9040
+ ["helicopter_loop", 3]
9016
9041
  ])
9017
9042
  ];
9018
9043
  var SFX_MATERIALS = [
@@ -9094,6 +9119,175 @@ var GameTestRoom = (() => {
9094
9119
  burst: 20,
9095
9120
  burstSpacing: 0.06
9096
9121
  };
9122
+ var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
9123
+ function splitTier(id) {
9124
+ const match = /^(.*)_(light|medium|heavy)$/.exec(id);
9125
+ return match ? { base: match[1], tier: match[2] } : { base: id };
9126
+ }
9127
+ var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
9128
+ function sampleBankFromManifest(manifest, options) {
9129
+ const baseUrl = options.baseUrl ?? "";
9130
+ const samples = [];
9131
+ const tiersOf = /* @__PURE__ */ new Map();
9132
+ for (const sound of manifest.sounds) {
9133
+ const { base, tier } = splitTier(sound.id);
9134
+ if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
9135
+ }
9136
+ const bases = /* @__PURE__ */ new Map();
9137
+ for (const sound of manifest.sounds) {
9138
+ if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
9139
+ }
9140
+ for (const [base, tiers] of tiersOf) {
9141
+ if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
9142
+ }
9143
+ for (const sound of bases.values()) {
9144
+ const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
9145
+ const close = refsOf(sound.layers.close, baseUrl);
9146
+ if (!numbers || close.length === 0) continue;
9147
+ const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
9148
+ const name = splitTier(tier.id).tier;
9149
+ const tierFar = refsOf(tier.layers.far, baseUrl);
9150
+ return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
9151
+ }).sort((a, b) => a.from - b.from);
9152
+ const far = refsOf(sound.layers.far, baseUrl);
9153
+ const indoor = refsOf(sound.layers.tail_in, baseUrl);
9154
+ const outdoor = refsOf(sound.layers.tail_out, baseUrl);
9155
+ const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
9156
+ samples.push({
9157
+ id: sound.id,
9158
+ bus: numbers.bus,
9159
+ close,
9160
+ ...far.length > 0 ? { far } : {},
9161
+ ...tiers.length > 0 ? { tiers } : {},
9162
+ ...numbers.crossover ? { crossover: numbers.crossover } : {},
9163
+ ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
9164
+ ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
9165
+ ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
9166
+ ...numbers.falloff ? { falloff: numbers.falloff } : {},
9167
+ ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
9168
+ ...numbers.group ? { group: numbers.group } : {},
9169
+ ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
9170
+ ...numbers.fallback ? { fallback: numbers.fallback } : {},
9171
+ sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
9172
+ });
9173
+ }
9174
+ return { id: options.id, samples };
9175
+ }
9176
+ var KIT_CATEGORY_BUS = {
9177
+ armas: "weapons",
9178
+ impactos: "world",
9179
+ "explosoes-fogo": "world",
9180
+ personagem: "world",
9181
+ veiculos: "world",
9182
+ "itens-portas": "world",
9183
+ ambiente: "ambience"
9184
+ };
9185
+ var VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/;
9186
+ var KIT_SAMPLE_ALIASES = {
9187
+ // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners
9188
+ latch: "metal_latch",
9189
+ creak_wood: "door_creak",
9190
+ shut_wood: "door_wood_shut",
9191
+ rattle: "door_locked_rattle",
9192
+ // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull
9193
+ // an item taken in the hand (game-destruction-three grab_* keys)
9194
+ grab_hard: "item_pickup_generic",
9195
+ grab_soft: "item_pickup_leather"
9196
+ };
9197
+ function kitSampleDefaults(sound) {
9198
+ const bus = VOICE_IDS.test(sound.id) ? "voices" : KIT_CATEGORY_BUS[sound.category];
9199
+ if (!bus) return void 0;
9200
+ return { bus, pitch: sound.category === "ambiente" || /_loop/.test(sound.id) ? 0 : 0.04 };
9201
+ }
9202
+ function withAliases(manifest, aliases) {
9203
+ const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]));
9204
+ const extra = [];
9205
+ for (const [alias, target] of Object.entries(aliases)) {
9206
+ const sound = byId.get(target);
9207
+ if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias });
9208
+ }
9209
+ return { ...manifest, sounds: [...manifest.sounds, ...extra] };
9210
+ }
9211
+ function kitSampleBank(manifest, options = {}) {
9212
+ return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {
9213
+ id: options.id ?? "kit_samples",
9214
+ baseUrl: options.baseUrl ?? "",
9215
+ defaults: kitSampleDefaults
9216
+ });
9217
+ }
9218
+ var isFileUrl = (url2) => {
9219
+ const page = globalThis.location?.href;
9220
+ try {
9221
+ return new URL(url2, page).protocol === "file:";
9222
+ } catch {
9223
+ return false;
9224
+ }
9225
+ };
9226
+ var fetchJson = async (url2) => {
9227
+ if (isFileUrl(url2)) throw new Error(`${url2}: file:// is not fetched`);
9228
+ const response = await fetch(url2);
9229
+ if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9230
+ return await response.json();
9231
+ };
9232
+ async function loadKitSamples(engine, options = {}) {
9233
+ const baseUrl = options.baseUrl ?? "";
9234
+ let manifest;
9235
+ try {
9236
+ manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`);
9237
+ } catch {
9238
+ return null;
9239
+ }
9240
+ const bank = kitSampleBank(manifest, options);
9241
+ engine.register(bank);
9242
+ if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => void 0);
9243
+ return bank;
9244
+ }
9245
+ var USER_SAMPLES_BANK = "user_samples";
9246
+ var USER_SOUND_PREFIX = "user:";
9247
+ function userSampleBank(manifest, options = {}) {
9248
+ const playing = [];
9249
+ const behindKit = [];
9250
+ const sounds = [];
9251
+ for (const picked of manifest.sounds) {
9252
+ const sound = { ...picked, id: splitTier(picked.id).base };
9253
+ sounds.push({ ...sound, id: `${USER_SOUND_PREFIX}${sound.id}` });
9254
+ if (sound.role === "fallback" && options.kitHas?.(sound.id)) {
9255
+ behindKit.push(sound.id);
9256
+ continue;
9257
+ }
9258
+ sounds.push(sound);
9259
+ playing.push(sound.id);
9260
+ }
9261
+ const bank = sampleBankFromManifest(
9262
+ { sounds },
9263
+ {
9264
+ id: USER_SAMPLES_BANK,
9265
+ baseUrl: options.baseUrl ?? "",
9266
+ defaults: (sound) => kitSampleDefaults({ id: sound.id.replace(USER_SOUND_PREFIX, ""), category: sound.category })
9267
+ }
9268
+ );
9269
+ return { bank, playing, behindKit };
9270
+ }
9271
+ var fetchManifestJson = async (url2) => {
9272
+ const page = globalThis.location?.href;
9273
+ if (new URL(url2, page ?? "http://localhost/").protocol === "file:") throw new Error(`${url2}: file:// is not fetched`);
9274
+ const response = await fetch(url2);
9275
+ if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9276
+ return await response.json();
9277
+ };
9278
+ async function loadUserSamples(engine, options = {}) {
9279
+ const baseUrl = options.baseUrl ?? "./sfx-local/";
9280
+ let manifest;
9281
+ try {
9282
+ manifest = await (options.fetchManifest ?? fetchManifestJson)(`${baseUrl}manifest.json`);
9283
+ } catch {
9284
+ return null;
9285
+ }
9286
+ const kitHas = options.kitHas ?? ((id) => Boolean(engine.registry?.sample(id)));
9287
+ const result2 = userSampleBank(manifest, { baseUrl, kitHas });
9288
+ engine.register(result2.bank);
9289
+ return result2;
9290
+ }
9097
9291
  var PT = {
9098
9292
  group: "Som",
9099
9293
  name: "Painel de som (arquivos e C1\u2013C10)",
@@ -9101,6 +9295,7 @@ var GameTestRoom = (() => {
9101
9295
  category: "1 \xB7 Categoria",
9102
9296
  sound: "1 \xB7 Som",
9103
9297
  play: "1 \xB7 \u25B6 Tocar",
9298
+ play_user: "1 \xB7 \u25B6 Tocar o seu",
9104
9299
  material: "2 \xB7 Material",
9105
9300
  blow: "2 \xB7 Golpe",
9106
9301
  strike: "2 \xB7 \u25B6 Tocar golpe",
@@ -9122,6 +9317,7 @@ var GameTestRoom = (() => {
9122
9317
  approve: "5 \xB7 Aprovo (C10)"
9123
9318
  },
9124
9319
  hints: {
9320
+ 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.",
9125
9321
  sound: "A = arquivo, S = sintetizado, F = faltando. O que tem arquivo toca o arquivo; o resto toca o som atual.",
9126
9322
  strike: "Toca impact_<material>_<golpe>: o arquivo se existir, sen\xE3o o som atual daquele material.",
9127
9323
  wall: "P\xF5e a parede de casa entre voc\xEA e a fonte: o som fica abafado (C4).",
@@ -9166,9 +9362,10 @@ var GameTestRoom = (() => {
9166
9362
  strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
9167
9363
  states: { file: "A", synth: "S", missing: "F" },
9168
9364
  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" },
9169
- metrics: { 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" },
9365
+ 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" },
9170
9366
  run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
9171
9367
  words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
9368
+ sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
9172
9369
  probe: "Fonte do som",
9173
9370
  wall: "Parede (oclus\xE3o)",
9174
9371
  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" }
@@ -9181,6 +9378,7 @@ var GameTestRoom = (() => {
9181
9378
  category: "1 \xB7 Category",
9182
9379
  sound: "1 \xB7 Sound",
9183
9380
  play: "1 \xB7 \u25B6 Play",
9381
+ play_user: "1 \xB7 \u25B6 Play yours",
9184
9382
  material: "2 \xB7 Material",
9185
9383
  blow: "2 \xB7 Blow",
9186
9384
  strike: "2 \xB7 \u25B6 Play blow",
@@ -9202,6 +9400,7 @@ var GameTestRoom = (() => {
9202
9400
  approve: "5 \xB7 Approve (C10)"
9203
9401
  },
9204
9402
  hints: {
9403
+ 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.",
9205
9404
  sound: "A = file, S = synthesized, F = missing. A file plays the file; the rest plays the current sound.",
9206
9405
  strike: "Plays impact_<material>_<blow>: the file if there is one, else the current sound of that material.",
9207
9406
  wall: "Puts the house wall between you and the source: the sound is muffled (C4).",
@@ -9236,138 +9435,16 @@ var GameTestRoom = (() => {
9236
9435
  environments: { indoor: "indoor", outdoor: "outdoor" },
9237
9436
  strengths: { light: "light", medium: "medium", heavy: "heavy" },
9238
9437
  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" },
9239
- metrics: { 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" },
9438
+ 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" },
9240
9439
  run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
9241
9440
  words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
9441
+ sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
9242
9442
  probe: "Sound source",
9243
9443
  wall: "Wall (occlusion)",
9244
9444
  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" }
9245
9445
  };
9246
9446
 
9247
9447
  // ../game-audio/dist/index.js
9248
- var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
9249
- function splitTier(id) {
9250
- const match = /^(.*)_(light|medium|heavy)$/.exec(id);
9251
- return match ? { base: match[1], tier: match[2] } : { base: id };
9252
- }
9253
- var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
9254
- function sampleBankFromManifest(manifest, options) {
9255
- const baseUrl = options.baseUrl ?? "";
9256
- const samples = [];
9257
- const tiersOf = /* @__PURE__ */ new Map();
9258
- for (const sound of manifest.sounds) {
9259
- const { base, tier } = splitTier(sound.id);
9260
- if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
9261
- }
9262
- const bases = /* @__PURE__ */ new Map();
9263
- for (const sound of manifest.sounds) {
9264
- if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
9265
- }
9266
- for (const [base, tiers] of tiersOf) {
9267
- if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
9268
- }
9269
- for (const sound of bases.values()) {
9270
- const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
9271
- const close = refsOf(sound.layers.close, baseUrl);
9272
- if (!numbers || close.length === 0) continue;
9273
- const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
9274
- const name = splitTier(tier.id).tier;
9275
- const tierFar = refsOf(tier.layers.far, baseUrl);
9276
- return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
9277
- }).sort((a, b) => a.from - b.from);
9278
- const far = refsOf(sound.layers.far, baseUrl);
9279
- const indoor = refsOf(sound.layers.tail_in, baseUrl);
9280
- const outdoor = refsOf(sound.layers.tail_out, baseUrl);
9281
- const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
9282
- samples.push({
9283
- id: sound.id,
9284
- bus: numbers.bus,
9285
- close,
9286
- ...far.length > 0 ? { far } : {},
9287
- ...tiers.length > 0 ? { tiers } : {},
9288
- ...numbers.crossover ? { crossover: numbers.crossover } : {},
9289
- ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
9290
- ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
9291
- ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
9292
- ...numbers.falloff ? { falloff: numbers.falloff } : {},
9293
- ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
9294
- ...numbers.group ? { group: numbers.group } : {},
9295
- ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
9296
- ...numbers.fallback ? { fallback: numbers.fallback } : {},
9297
- sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
9298
- });
9299
- }
9300
- return { id: options.id, samples };
9301
- }
9302
- var KIT_CATEGORY_BUS = {
9303
- armas: "weapons",
9304
- impactos: "world",
9305
- "explosoes-fogo": "world",
9306
- personagem: "world",
9307
- veiculos: "world",
9308
- "itens-portas": "world",
9309
- ambiente: "ambience"
9310
- };
9311
- var VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/;
9312
- var KIT_SAMPLE_ALIASES = {
9313
- // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners
9314
- latch: "metal_latch",
9315
- creak_wood: "door_creak",
9316
- shut_wood: "door_wood_shut",
9317
- rattle: "door_locked_rattle",
9318
- // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull
9319
- // an item taken in the hand (game-destruction-three grab_* keys)
9320
- grab_hard: "item_pickup_generic",
9321
- grab_soft: "item_pickup_leather"
9322
- };
9323
- function kitSampleDefaults(sound) {
9324
- const bus = VOICE_IDS.test(sound.id) ? "voices" : KIT_CATEGORY_BUS[sound.category];
9325
- if (!bus) return void 0;
9326
- return { bus, pitch: sound.category === "ambiente" || /_loop/.test(sound.id) ? 0 : 0.04 };
9327
- }
9328
- function withAliases(manifest, aliases) {
9329
- const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]));
9330
- const extra = [];
9331
- for (const [alias, target] of Object.entries(aliases)) {
9332
- const sound = byId.get(target);
9333
- if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias });
9334
- }
9335
- return { ...manifest, sounds: [...manifest.sounds, ...extra] };
9336
- }
9337
- function kitSampleBank(manifest, options = {}) {
9338
- return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {
9339
- id: options.id ?? "kit_samples",
9340
- baseUrl: options.baseUrl ?? "",
9341
- defaults: kitSampleDefaults
9342
- });
9343
- }
9344
- var isFileUrl = (url2) => {
9345
- const page = globalThis.location?.href;
9346
- try {
9347
- return new URL(url2, page).protocol === "file:";
9348
- } catch {
9349
- return false;
9350
- }
9351
- };
9352
- var fetchJson = async (url2) => {
9353
- if (isFileUrl(url2)) throw new Error(`${url2}: file:// is not fetched`);
9354
- const response = await fetch(url2);
9355
- if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9356
- return await response.json();
9357
- };
9358
- async function loadKitSamples(engine, options = {}) {
9359
- const baseUrl = options.baseUrl ?? "";
9360
- let manifest;
9361
- try {
9362
- manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`);
9363
- } catch {
9364
- return null;
9365
- }
9366
- const bank = kitSampleBank(manifest, options);
9367
- engine.register(bank);
9368
- if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => void 0);
9369
- return bank;
9370
- }
9371
9448
  var ATTACK = 2e-3;
9372
9449
  function lengthOf(d, attack) {
9373
9450
  if (typeof d === "number") return attack + d;
@@ -22848,7 +22925,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
22848
22925
  var rendererCount = 0;
22849
22926
  var COLLAPSE_CLOUD_KG = 1e3;
22850
22927
  var CHIP_DECAL_EDGE = 0.05;
22851
- var OPAQUE_CRACK = 0.1;
22852
22928
  var WOOD_RIM = 1.5;
22853
22929
  var IMPACT_TAGS = ["metal", "glass", "wood"];
22854
22930
  var GLOW_FROM_C = 430;
@@ -23772,14 +23848,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23772
23848
  const pv = b?.parts.get(ev.part);
23773
23849
  if (!b || !pv) return;
23774
23850
  const def = this.world.materials.get(pv.material);
23775
- const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23776
- const mv = this.nearestMesh(pv, wp);
23777
- if (mv) {
23778
- const glass2 = !!def?.visual?.transparent;
23779
- const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
23780
- const color = glass2 ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
23781
- const length3 = glass2 ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
23782
- this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length3, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
23851
+ if (def?.visual?.transparent) {
23852
+ const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23853
+ const mv = this.nearestMesh(pv, wp);
23854
+ const seed = hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23855
+ if (mv) this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, ev.crack.length, 15267071, seed);
23783
23856
  }
23784
23857
  this.impactSound(pv.material, ev.worldPoint, 0.5, ev.blow);
23785
23858
  }
@@ -53203,7 +53276,11 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53203
53276
  const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
53204
53277
  const destructionSound = audio ? createDestructionSound(audio) : null;
53205
53278
  if (audio && options.samplesBaseUrl !== null) {
53206
- void loadKitSamples(audio, { baseUrl: options.samplesBaseUrl ?? "./sfx/" }).then((bank) => {
53279
+ void loadKitSamples(audio, { baseUrl: options.samplesBaseUrl ?? "./sfx/" }).then(async (bank) => {
53280
+ if (options.userSamplesBaseUrl !== null) {
53281
+ const own = await loadUserSamples(audio, { baseUrl: options.userSamplesBaseUrl ?? "./sfx-local/" });
53282
+ if (own) log.add({ channel: "event", type: "user_sounds", text: `sons do dono: ${own.playing.length} tocando, ${own.behindKit.length} atr\xE1s do SDK`, data: { playing: own.playing, behindKit: own.behindKit } });
53283
+ }
53207
53284
  if (!bank || typeof window === "undefined") return;
53208
53285
  const preload = () => {
53209
53286
  window.removeEventListener("keydown", preload, true);