@adatechnology/game-test-room 0.22.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.
@@ -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;
@@ -23543,7 +23619,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23543
23619
  this.startFire(ev);
23544
23620
  this.activate(ev.object, ev.part);
23545
23621
  this.sound("ignite", ev.point, 0.7);
23546
- this.sound("burn_loop", ev.point, 0.5);
23622
+ this.sound("burn_loop", ev.point, 0.5, this.burning(ev.object, ev.part));
23547
23623
  break;
23548
23624
  case "primed":
23549
23625
  if (ev.trigger !== "explosion") {
@@ -23551,7 +23627,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23551
23627
  this.primedBurning.add(`${ev.object}|${ev.part}`);
23552
23628
  this.activate(ev.object, ev.part);
23553
23629
  this.sound("primed", ev.point, 0.7, { seconds: ev.delay });
23554
- this.sound("burn_loop", ev.point, 0.5);
23630
+ this.sound("burn_loop", ev.point, 0.5, { fuel: "gas" });
23555
23631
  }
23556
23632
  break;
23557
23633
  case "spall":
@@ -23577,15 +23653,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23577
23653
  this.stopFire(`${ev.object}|${ev.part}`);
23578
23654
  this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
23579
23655
  }
23580
- if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5);
23656
+ if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
23581
23657
  this.activate(ev.object, ev.part);
23582
23658
  this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
23583
23659
  break;
23584
23660
  }
23585
23661
  case "burnout": {
23586
23662
  this.stopFire(`${ev.object}|${ev.part}`);
23587
- this.primedBurning.delete(`${ev.object}|${ev.part}`);
23588
- this.sound("burn_end", ev.point, 0.5);
23663
+ const primed = this.primedBurning.delete(`${ev.object}|${ev.part}`);
23664
+ this.sound("burn_end", ev.point, 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
23589
23665
  this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
23590
23666
  this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
23591
23667
  const pv = this.partVis(ev.object, ev.part);
@@ -23653,7 +23729,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23653
23729
  break;
23654
23730
  }
23655
23731
  case "explode":
23656
- this.primedBurning.delete(`${ev.object}|${ev.part}`);
23732
+ if (this.primedBurning.delete(`${ev.object}|${ev.part}`)) this.sound("burn_end", ev.point, 0.5, { fuel: "gas" });
23657
23733
  this.onExplode(ev);
23658
23734
  break;
23659
23735
  case "destroyed":
@@ -23668,6 +23744,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23668
23744
  if (detail) this.o.onSound?.(key2, point, volume, detail);
23669
23745
  else this.o.onSound?.(key2, point, volume);
23670
23746
  }
23747
+ /** What a burning part is made of, for the fire's sound. */
23748
+ burning(objectId, partId) {
23749
+ const material = this.partVis(objectId, partId)?.material || this.world.part(objectId, partId)?.material;
23750
+ return material ? { material } : void 0;
23751
+ }
23671
23752
  partVis(objectId, partId) {
23672
23753
  return this.bounds.get(objectId)?.parts.get(partId) ?? null;
23673
23754
  }
@@ -23767,14 +23848,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23767
23848
  const pv = b?.parts.get(ev.part);
23768
23849
  if (!b || !pv) return;
23769
23850
  const def = this.world.materials.get(pv.material);
23770
- const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23771
- const mv = this.nearestMesh(pv, wp);
23772
- if (mv) {
23773
- const glass2 = !!def?.visual?.transparent;
23774
- const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
23775
- const color = glass2 ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
23776
- const length3 = glass2 ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
23777
- 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);
23778
23856
  }
23779
23857
  this.impactSound(pv.material, ev.worldPoint, 0.5, ev.blow);
23780
23858
  }
@@ -24536,6 +24614,47 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24536
24614
  volume: 0,
24537
24615
  sources: ["sdk: the roar under the crackles of a fire"]
24538
24616
  };
24617
+ var FIRE_FUEL_OF = {
24618
+ wood_soft: "wood",
24619
+ wood_hard: "wood",
24620
+ plywood: "wood",
24621
+ mdf: "wood",
24622
+ green_wood: "wood",
24623
+ foliage: "wood",
24624
+ cactus: "wood",
24625
+ paper: "fabric",
24626
+ fabric: "fabric",
24627
+ leather: "fabric",
24628
+ foam: "plastic",
24629
+ plastic: "plastic",
24630
+ rubber: "plastic",
24631
+ wax: "plastic"
24632
+ };
24633
+ var FIRE_LOOP_OF = {
24634
+ wood: "fire_loop",
24635
+ fabric: "fire_loop",
24636
+ plastic: "fire_loop_plastic",
24637
+ gas: "fire_loop_gas",
24638
+ liquid: "fire_loop_liquid"
24639
+ };
24640
+ var FIRE_CRACKLE_OF = { wood: 1, fabric: 0.5, plastic: 0.35, gas: 0, liquid: 0 };
24641
+ var roar = (id, bank, source) => ({
24642
+ id,
24643
+ bus: "world",
24644
+ order: "chain_loop",
24645
+ source: { kind: "noise", loop: true },
24646
+ filters: { mode: "serial", bank },
24647
+ volume: 0,
24648
+ sources: [source]
24649
+ });
24650
+ var FIRE_FUEL_LOOPS = [
24651
+ // a gas jet: a steady, broad roar, brighter than wood and without crackles
24652
+ roar("fire_loop_gas", [{ type: "highpass", frequency: { set: 220 }, q: 0.7 }, { type: "lowpass", frequency: { set: 2400 }, q: 0.6 }], "sdk: a gas jet burning (a vessel venting into the flame)"),
24653
+ // a fuel pool: a soft, low, rolling roar
24654
+ roar("fire_loop_liquid", [{ type: "highpass", frequency: { set: 90 }, q: 0.7 }, { type: "lowpass", frequency: { set: 650 }, q: 0.9 }], "sdk: a pool of fuel burning"),
24655
+ // plastic and rubber: a thin sizzle over a low burn
24656
+ roar("fire_loop_plastic", [{ type: "bandpass", frequency: { set: 1800 }, q: 0.6 }], "sdk: plastic and rubber melting in the fire")
24657
+ ];
24539
24658
  var BLAST_SOUND_OF = {
24540
24659
  grenade: "explosion_grenade",
24541
24660
  big: "explosion_big",
@@ -24680,7 +24799,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24680
24799
  impact2("plastic_crack", [sceneNoise({ type: "bandpass", f: 1800, q: 2, d: 0.06, vol: 0.75 }), sceneTone({ f: [450, 550], f2: 250, d: 0.05, vol: 0.22 })]),
24681
24800
  impact2("mdf_crumble", [sceneNoise({ type: "lowpass", f: 700, d: 0.25, vol: 0.75, wet: 0.2 }), ...sceneMany(4, () => sceneNoise({ type: "lowpass", f: 500, d: 0.05, vol: 0.32, delay: [0.05, 0.3] }))])
24682
24801
  ];
24683
- var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP] };
24802
+ var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP, ...FIRE_FUEL_LOOPS] };
24684
24803
  var IMPACT_SOUND_KEYS = {
24685
24804
  impact_metal_ballistic: { sound: "metal_bullet" },
24686
24805
  impact_metal_blunt: { sound: "metal_blunt" },
@@ -24829,31 +24948,52 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24829
24948
  const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
24830
24949
  const blastByKind = options.style !== "game" && !(options.keys && "explode" in options.keys);
24831
24950
  const channel = engine.channel({ seed: options.seed ?? 1 });
24832
- let loop;
24833
- let burning = 0;
24951
+ const fuelOf = { ...FIRE_FUEL_OF, ...options.fuels };
24952
+ const loopOf = { ...FIRE_LOOP_OF, ...options.fireLoop ? { wood: fireLoop, fabric: fireLoop } : {}, ...options.fireLoops };
24953
+ const defaultFuel = options.defaultFuel ?? "wood";
24954
+ const loops = /* @__PURE__ */ new Map();
24834
24955
  const fires = [];
24835
24956
  let nextCrackle = 0;
24957
+ const fuelFor = (detail) => detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0) ?? defaultFuel;
24836
24958
  const setFire = () => {
24837
24959
  if (options.burnLoop === false) return;
24838
- if (burning <= 0) {
24839
- loop?.stop();
24840
- loop = void 0;
24841
- return;
24960
+ const count = /* @__PURE__ */ new Map();
24961
+ for (const f of fires) {
24962
+ const id = loopOf[f.fuel];
24963
+ count.set(id, (count.get(id) ?? 0) + 1);
24964
+ }
24965
+ for (const [id, handle] of loops) {
24966
+ if (count.has(id)) continue;
24967
+ handle.stop();
24968
+ loops.delete(id);
24969
+ }
24970
+ for (const [id, n] of count) {
24971
+ let handle = loops.get(id);
24972
+ if (!handle) {
24973
+ handle = engine.loop(id);
24974
+ loops.set(id, handle);
24975
+ }
24976
+ handle.set({ volume: Math.min(fire2.max, fire2.perFire * n) });
24842
24977
  }
24843
- loop ?? (loop = engine.loop(fireLoop));
24844
- loop.set({ volume: Math.min(fire2.max, fire2.perFire * burning) });
24845
24978
  };
24846
- const forget = (point) => {
24979
+ const forget = (point, fuel) => {
24847
24980
  let best = -1;
24848
24981
  let bestD = Infinity;
24849
24982
  fires.forEach((f, i) => {
24850
- const d = (f[0] - point[0]) ** 2 + (f[1] - point[1]) ** 2 + (f[2] - point[2]) ** 2;
24983
+ if (fuel !== void 0 && f.fuel !== fuel) return;
24984
+ const d = (f.point[0] - point[0]) ** 2 + (f.point[1] - point[1]) ** 2 + (f.point[2] - point[2]) ** 2;
24851
24985
  if (d < bestD) {
24852
24986
  bestD = d;
24853
24987
  best = i;
24854
24988
  }
24855
24989
  });
24856
- if (best >= 0) fires.splice(best, 1);
24990
+ if (best < 0 && fuel !== void 0) forget(point, void 0);
24991
+ else if (best >= 0) fires.splice(best, 1);
24992
+ };
24993
+ const reset = () => {
24994
+ for (const handle of loops.values()) handle.stop();
24995
+ loops.clear();
24996
+ fires.length = 0;
24857
24997
  };
24858
24998
  const blastSound = (point, volume, detail) => {
24859
24999
  const named = detail?.effect === void 0 ? void 0 : kinds[detail.effect];
@@ -24865,14 +25005,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24865
25005
  return {
24866
25006
  onSound(key2, point, volume, detail) {
24867
25007
  if (key2 === "burn_loop") {
24868
- burning += 1;
24869
- fires.push(point);
25008
+ fires.push({ point, fuel: fuelFor(detail) });
24870
25009
  setFire();
24871
25010
  return;
24872
25011
  }
24873
25012
  if (key2 === "burn_end") {
24874
- if (burning > 0) burning -= 1;
24875
- forget(point);
25013
+ forget(point, detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0));
24876
25014
  setFire();
24877
25015
  return;
24878
25016
  }
@@ -24902,12 +25040,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24902
25040
  },
24903
25041
  update(dt) {
24904
25042
  if (!crackles || fires.length === 0 || options.burnLoop === false) return;
25043
+ const weight = fires.reduce((sum2, f) => sum2 + FIRE_CRACKLE_OF[f.fuel], 0);
25044
+ if (weight <= 0) return;
24905
25045
  nextCrackle -= dt;
24906
25046
  for (let i = 0; i < 4 && nextCrackle <= 0; i++) {
24907
- const rate = Math.min(crackles.max, crackles.base + crackles.perSqrtFire * Math.sqrt(fires.length));
25047
+ const rate = Math.min(crackles.max, crackles.base * Math.min(1, weight) + crackles.perSqrtFire * Math.sqrt(weight));
24908
25048
  nextCrackle += (0.4 + channel.random() * 1.2) / rate;
24909
- const at3 = fires[Math.floor(channel.random() * fires.length)];
24910
- engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at3, volume: crackles.volume });
25049
+ let pick3 = channel.random() * weight;
25050
+ const at3 = fires.find((f) => (pick3 -= FIRE_CRACKLE_OF[f.fuel]) <= 0) ?? fires[fires.length - 1];
25051
+ engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at3.point, volume: crackles.volume });
24911
25052
  }
24912
25053
  if (nextCrackle < 0) nextCrackle = 0;
24913
25054
  },
@@ -24916,13 +25057,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24916
25057
  for (const k of detail?.keys ?? []) if (k in rules) return rules[k] ?? void 0;
24917
25058
  return rules[key2] ?? void 0;
24918
25059
  },
24919
- burning: () => burning,
24920
- dispose() {
24921
- loop?.stop();
24922
- loop = void 0;
24923
- burning = 0;
24924
- fires.length = 0;
24925
- }
25060
+ burning: () => fires.length,
25061
+ burningBy: () => {
25062
+ const by = {};
25063
+ for (const f of fires) by[f.fuel] = (by[f.fuel] ?? 0) + 1;
25064
+ return by;
25065
+ },
25066
+ reset,
25067
+ dispose: reset
24926
25068
  };
24927
25069
  }
24928
25070
  var blast2 = (gain) => ({ bus: "world", gain, falloff: BLAST_FALLOFF, cutoffK: 6e-3, priority: 3 });
@@ -24941,7 +25083,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
24941
25083
  fire_extinguish: fire(0.7),
24942
25084
  gas_leak: fire(0.5),
24943
25085
  // the roar: recorded fires chained with a crossfade (the level comes from `createDestructionSound`, by how much burns)
24944
- fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } }
25086
+ fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } },
25087
+ // each fuel has its own roar: a gas jet (burners and torches), a pool of fuel, plastic and rubber sizzling under the flames
25088
+ fire_loop_gas: { bus: "world", gain: 6, loop: { crossfade: 1 } },
25089
+ fire_loop_liquid: { bus: "world", gain: 3.5, loop: { crossfade: 1 } },
25090
+ fire_loop_plastic: { bus: "world", gain: 1, loop: { crossfade: 0.6 } }
24945
25091
  };
24946
25092
 
24947
25093
  // src/body-sounds.ts
@@ -47790,6 +47936,7 @@ void main(){
47790
47936
  var LYING_EYE = 0.3;
47791
47937
  var LYING_PITCH = 1.05;
47792
47938
  var LYING_ROLL = 0.35;
47939
+ var LYING_PITCH_FACE_DOWN = -1.05;
47793
47940
  function createPlayer(THREE, o) {
47794
47941
  const cam = o.camera;
47795
47942
  let yaw = 0;
@@ -47828,11 +47975,12 @@ void main(){
47828
47975
  floorLevel += Math.max(-reach, Math.min(reach, gap));
47829
47976
  };
47830
47977
  let knocked = null;
47978
+ let knockedForward = false;
47831
47979
  let rollSide = 1;
47832
47980
  const apply = () => {
47833
47981
  const lying = knocked?.lying ?? 0;
47834
47982
  const fallen = lying * lying * (3 - 2 * lying);
47835
- euler.set(pitch + (LYING_PITCH - pitch) * fallen, yaw, LYING_ROLL * rollSide * fallen, "YXZ");
47983
+ euler.set(pitch + ((knockedForward ? LYING_PITCH_FACE_DOWN : LYING_PITCH) - pitch) * fallen, yaw, LYING_ROLL * rollSide * fallen, "YXZ");
47836
47984
  cam.quaternion.setFromEuler(euler);
47837
47985
  const standing = eye - CROUCH_DROP * crouch - bend;
47838
47986
  cam.position.set(px.x, floorLevel + y + standing + (LYING_EYE - standing) * fallen, px.z);
@@ -48110,6 +48258,7 @@ void main(){
48110
48258
  if (next.down) {
48111
48259
  const right = Math.cos(yaw) * next.velocity[0] - Math.sin(yaw) * next.velocity[2];
48112
48260
  rollSide = right >= 0 ? -1 : 1;
48261
+ knockedForward = -Math.sin(yaw) * next.velocity[0] - Math.cos(yaw) * next.velocity[2] > 0;
48113
48262
  crouchLatched = false;
48114
48263
  }
48115
48264
  if (knocked.airborne) vy2 = Math.max(vy2, knocked.velocity[1]);
@@ -48117,6 +48266,9 @@ void main(){
48117
48266
  get knocked() {
48118
48267
  return knocked;
48119
48268
  },
48269
+ get knockedForward() {
48270
+ return knockedForward;
48271
+ },
48120
48272
  update(dt) {
48121
48273
  if (knocked) {
48122
48274
  stepKnocked(dt);
@@ -53124,7 +53276,11 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53124
53276
  const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
53125
53277
  const destructionSound = audio ? createDestructionSound(audio) : null;
53126
53278
  if (audio && options.samplesBaseUrl !== null) {
53127
- 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
+ }
53128
53284
  if (!bank || typeof window === "undefined") return;
53129
53285
  const preload = () => {
53130
53286
  window.removeEventListener("keydown", preload, true);
@@ -53946,6 +54102,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53946
54102
  explosives.clear();
53947
54103
  fireZones.length = 0;
53948
54104
  burningStains.length = 0;
54105
+ destructionSound?.reset();
53949
54106
  terrain.setLayout(defaultGround);
53950
54107
  for (const id of [...entries2.keys()]) if (!entries2.get(id)?.arena) removeEntry(id);
53951
54108
  clearLoose();
@@ -54113,6 +54270,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54113
54270
  avatarState.running = !player.crouching && (player.keys.has("ShiftLeft") || player.keys.has("ShiftRight"));
54114
54271
  avatarState.crouch = crouchForced || player.crouching || push.isBent;
54115
54272
  avatarState.down = downForced || Boolean(player.knocked?.down || (player.knocked?.lying ?? 0) > 0.5);
54273
+ avatarState.fallForward = downForced ? void 0 : player.knockedForward;
54116
54274
  avatarState.armed = armedForced ?? tool.def.action.kind === "firearm";
54117
54275
  avatarState.pitch = player.pitch;
54118
54276
  avatarState.holding = heldPiece !== null;
@@ -54312,6 +54470,15 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54312
54470
  };
54313
54471
  const fireZones = [];
54314
54472
  const burningStains = [];
54473
+ const poolFire = (point, burning) => playSound(burning ? "burn_loop" : "burn_end", point, 0.5, { fuel: "liquid" });
54474
+ const dropZone = (i) => {
54475
+ const [zone] = fireZones.splice(i, 1);
54476
+ if (zone) poolFire(zone.point, false);
54477
+ };
54478
+ const dropStain = (i) => {
54479
+ const [stain] = burningStains.splice(i, 1);
54480
+ if (stain) poolFire(stain.center, false);
54481
+ };
54315
54482
  const stainRadius = (s) => {
54316
54483
  let sum2 = 0;
54317
54484
  for (const r of s.front) sum2 += r;
@@ -54328,6 +54495,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54328
54495
  if (stainBurning(s)) continue;
54329
54496
  const seconds = Math.max(3, Math.min(20, 4 + s.liters * 30 + stainRadius(s) * 6));
54330
54497
  burningStains.push({ center: [s.center[0], s.center[1], s.center[2]], remaining: seconds, acc: 0 });
54498
+ poolFire([s.center[0], s.center[1], s.center[2]], true);
54331
54499
  lit = true;
54332
54500
  log.add({
54333
54501
  channel: "event",
@@ -54358,7 +54526,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54358
54526
  const a = rnd2() * Math.PI * 2;
54359
54527
  const r = Math.sqrt(rnd2()) * z.radius;
54360
54528
  renderer.emit("fire", [z.point[0] + Math.cos(a) * r, z.point[1] + 0.05, z.point[2] + Math.sin(a) * r], [0, 1, 0], 2, 1.5);
54361
- if (z.remaining <= 0) fireZones.splice(i, 1);
54529
+ if (z.remaining <= 0) dropZone(i);
54362
54530
  }
54363
54531
  if (burningStains.length) {
54364
54532
  const stains = fluids.stains();
@@ -54379,7 +54547,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54379
54547
  if (b.remaining <= 0 || !st) {
54380
54548
  fluids.clearStains(b.center, r + 0.1);
54381
54549
  log.add({ channel: "event", type: "stain_burned", text: t("ev.stain_burned"), data: { point: b.center } });
54382
- burningStains.splice(i, 1);
54550
+ dropStain(i);
54383
54551
  }
54384
54552
  }
54385
54553
  }
@@ -54716,8 +54884,8 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54716
54884
  hit.add(r.object);
54717
54885
  world.extinguish(r.object, void 0, u.coolC * Math.max(u.duration, 0.05) * 6);
54718
54886
  fluids.clearStains(r.point, 0.5);
54719
- for (let i = burningStains.length - 1; i >= 0; i--) if (Math.hypot(burningStains[i].center[0] - r.point[0], burningStains[i].center[2] - r.point[2]) < 1.2) burningStains.splice(i, 1);
54720
- for (let i = fireZones.length - 1; i >= 0; i--) if (Math.hypot(fireZones[i].point[0] - r.point[0], fireZones[i].point[2] - r.point[2]) < 1.5) fireZones.splice(i, 1);
54887
+ for (let i = burningStains.length - 1; i >= 0; i--) if (Math.hypot(burningStains[i].center[0] - r.point[0], burningStains[i].center[2] - r.point[2]) < 1.2) dropStain(i);
54888
+ for (let i = fireZones.length - 1; i >= 0; i--) if (Math.hypot(fireZones[i].point[0] - r.point[0], fireZones[i].point[2] - r.point[2]) < 1.5) dropZone(i);
54721
54889
  }
54722
54890
  });
54723
54891
  log.coalesce(
@@ -54728,7 +54896,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54728
54896
  };
54729
54897
  const startFire = (weapon, at3, fire3, impact3) => {
54730
54898
  fluids.splash("fuel", [at3[0], Math.max(at3[1], groundAt(at3[0], at3[2])), at3[2]], 0.5);
54731
- fireZones.push({ point: [at3[0], Math.max(0.02, groundAt(at3[0], at3[2])), at3[2]], radius: fire3.radius, temperature: fire3.temperature, remaining: fire3.seconds, acc: 0 });
54899
+ const zone = { point: [at3[0], Math.max(0.02, groundAt(at3[0], at3[2])), at3[2]], radius: fire3.radius, temperature: fire3.temperature, remaining: fire3.seconds, acc: 0 };
54900
+ fireZones.push(zone);
54901
+ poolFire(zone.point, true);
54732
54902
  renderer.emit("fire", at3, [0, 1, 0], 24, 3);
54733
54903
  action("molotov_fire", t("act.molotov_fire", { weapon: toolName(weapon), seconds: num(fire3.seconds, 0), radius: num(fire3.radius, 1) }), { data: { weapon, impact: impact3 } });
54734
54904
  };
@@ -55866,6 +56036,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
55866
56036
  loading,
55867
56037
  addView: (view) => moduleViews.add(view),
55868
56038
  setViewCamera: (view) => moduleViews.setMain(view ?? null),
56039
+ holdPlayer: (held) => player.setWalkHeld(held),
55869
56040
  ...options.blastShove ? { blastShove: options.blastShove } : {},
55870
56041
  blastReach: (point, target, ignore) => world.blastReach(point, target, ignore ? { ignore } : {}),
55871
56042
  tool: () => ({