Doom-style weapon audio batch + remove footsteps

- Regenerated all 13 weapon fire one-shots and 4 weapon loops via the audio
  specialist (ElevenLabs) with a 1993 Doom style brief: chunky, bass-heavy,
  crunchy 90s FPS sounds
- New clips wired into SfxBank: ui_active, ui_active_empty (active items),
  ui_curse (legendary pickups, replaces the normal item jingle), plr_revive
  (Second Skin revive)
- Footstep system removed entirely (never worked right): Sfx.footstep, 8
  plr_step* samples and bank entries, player.js call site, smoke test block
- Cache v=42
This commit is contained in:
MeatSlicer Developer committed 2026-07-30 17:41:31 +10:00
1 parent 4dad22d087
commit 64ac53434b
36 files changed
+37 -80

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21 -21
View File
@@ -6,31 +6,31 @@
<meta name="theme-color" content="#0a0305">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22><text y=%2252%22 x=%2250%22 text-anchor=%22middle%22 dominant-baseline=%22middle%22 font-size=%2252%22>🔪</text></svg>">
<title>MEATSLICER</title>
<link rel="stylesheet" href="css/style.css?v=41">
<link rel="stylesheet" href="css/style.css?v=42">
</head>
<body>
<div id="wrap">
<canvas id="game" width="960" height="640"></canvas>
</div>
<script src="js/utils.js?v=41"></script>
<script src="js/state.js?v=41"></script>
<script src="js/input.js?v=41"></script>
<script src="js/sfx.js?v=41"></script>
<script src="js/sfxbank.js?v=41"></script>
<script src="js/audio.js?v=41"></script>
<script src="js/sprites.js?v=41"></script>
<script src="js/particles.js?v=41"></script>
<script src="js/weapons.js?v=41"></script>
<script src="js/bullets.js?v=41"></script>
<script src="js/pickups.js?v=41"></script>
<script src="js/items.js?v=41"></script>
<script src="js/perks.js?v=41"></script>
<script src="js/enemies.js?v=41"></script>
<script src="js/bosses.js?v=41"></script>
<script src="js/rooms.js?v=41"></script>
<script src="js/player.js?v=41"></script>
<script src="js/hud.js?v=41"></script>
<script src="js/help.js?v=41"></script>
<script src="js/main.js?v=41"></script>
<script src="js/utils.js?v=42"></script>
<script src="js/state.js?v=42"></script>
<script src="js/input.js?v=42"></script>
<script src="js/sfx.js?v=42"></script>
<script src="js/sfxbank.js?v=42"></script>
<script src="js/audio.js?v=42"></script>
<script src="js/sprites.js?v=42"></script>
<script src="js/particles.js?v=42"></script>
<script src="js/weapons.js?v=42"></script>
<script src="js/bullets.js?v=42"></script>
<script src="js/pickups.js?v=42"></script>
<script src="js/items.js?v=42"></script>
<script src="js/perks.js?v=42"></script>
<script src="js/enemies.js?v=42"></script>
<script src="js/bosses.js?v=42"></script>
<script src="js/rooms.js?v=42"></script>
<script src="js/player.js?v=42"></script>
<script src="js/hud.js?v=42"></script>
<script src="js/help.js?v=42"></script>
<script src="js/main.js?v=42"></script>
</body>
</html>
+2 -2
View File
@@ -176,9 +176,9 @@ function giveItem(iid) {
spawnText(p.x, p.y - 14, 'UPGRADED!', '#ffd060');
} else {
addToast(item.name, item.desc);
spawnText(p.x, p.y - 14, item.name.toUpperCase(), '#c9a227');
spawnText(p.x, p.y - 14, item.name.toUpperCase(), ITEM_RARITY[item.rarity].color);
}
Sfx.item();
if (item.rarity === 'legendary') Sfx.curse(); else Sfx.item();
refreshOrbitals(p);
}
+2 -6
View File
@@ -5,7 +5,7 @@ function initPlayer() {
x: W / 2, y: H / 2, r: 20,
hp: 6, shieldHp: 0,
aim: 0, bodyFacing: 0, fireT: 0, charge: 0, invT: 0, frenzyT: 0,
step: 0, stepTick: 0, moveBlend: 0, recoil: 0, muzzleT: 0,
step: 0, moveBlend: 0, recoil: 0, muzzleT: 0,
animT: 0, attackT: 0, actionT: 0, hitT: 0, deathT: 0,
weapon: { id: 'bonepopper', ammo: Infinity },
holstered: null, // a special weapon set aside while the pistol is out
@@ -76,15 +76,11 @@ function updatePlayer(dt) {
p.x += (mx / len) * spd * dt;
p.y += (my / len) * spd * dt;
p.step += dt * 9 * Math.min(st.speedMul, 1.8);
const stepTick = Math.floor(p.step / Math.PI);
if (stepTick !== p.stepTick && p.hp > 0) Sfx.footstep(p.moveBlend, onGore(p.x, p.y));
p.stepTick = stepTick;
}
else {
// Finish on a planted contact frame instead of freezing halfway through a stride.
const planted = Math.round(p.step / Math.PI) * Math.PI;
p.step = lerp(p.step, planted, clamp(dt * 10, 0, 1));
p.stepTick = Math.round(p.step / Math.PI);
}
p.moveBlend = lerp(p.moveBlend, mx || my ? 1 : 0, clamp(dt * 12, 0, 1));
p.recoil = Math.max(0, p.recoil - dt * 7);
@@ -339,7 +335,7 @@ function hurtPlayer(dmg, ang, attacker) {
addShake(10);
spawnText(p.x, p.y - 20, 'SECOND SKIN', '#e2472f');
addToast('SECOND SKIN', 'you refuse to die — once per floor');
Sfx.shieldUp();
Sfx.revive();
return;
}
p.hp = 0;
+6 -34
View File
@@ -3,12 +3,12 @@ const Sfx = {
ctx: null, master: null, comp: null, glue: null, limiter: null, muted: false,
mixBus: null, musicBus: null, musicMute: null, duckGain: null, saturators: {},
buses: {}, active: [], procedural: [], lastPlayed: new Map(), currentWeaponLoop: null,
footstepN: 0, fleshN: 0, wallN: 0, goreN: 0,
fleshN: 0, wallN: 0, goreN: 0,
lastPunch: -999, lastPunchLevel: 0,
maxVoices: 24,
cooldowns: {
hit: 0.035, crit: 0.07, ricochet: 0.05, wallHit: 0.045, sawHit: 0.09, gem: 0.025,
enemyDie: 0.04, goreBurst: 0.055, footstep: 0.10, lifesteal: 0.12,
enemyDie: 0.04, goreBurst: 0.055, lifesteal: 0.12,
loop_wpnloop_bile: 0.12, loop_wpnloop_cauterizer: 0.12,
loop_wpnloop_redhand: 0.11, loop_wpnloop_spinaltap_charge: 0.12,
},
@@ -302,36 +302,6 @@ const Sfx = {
this.blip(260, .14, 'sine', .2, 180, 'voices');
},
playerDeath() { this.stopAllLoops(); if (this.sample('plr_death', 'voices', null, 1, 'playerDeath')) return; this.thump(80, .65, .7, 25, 'voices'); this.noise(.7, .45, 350, 'voices'); },
footstep(strength, wet) {
if (!this.canPlay('footstep')) return;
this.footstepN = (this.footstepN % 4) + 1;
const dryName = 'plr_step' + this.footstepN;
const wanted = wet ? 'plr_step_wet' + this.footstepN : dryName;
const force = strength === undefined ? 1 : clamp(strength, .55, 1);
const side = this.footstepN % 2 ? .96 : 1.04;
const gain = .66 * force * side;
let sampled = false;
if (typeof SfxBank !== 'undefined' && SfxBank.buffers.has(wanted)) {
this.enforceVoiceLimit();
sampled = SfxBank.play(wanted, this.output('impacts', null), {
gain: gain * rand(.96, 1.04), rate: rand(.965, 1.035),
});
} else if (typeof SfxBank !== 'undefined') {
SfxBank.want(wanted);
// Keep the gait audible while a wet variant is still decoding.
if (wet && SfxBank.buffers.has(dryName)) {
this.enforceVoiceLimit();
sampled = SfxBank.play(dryName, this.output('impacts', null), { gain: gain * .9, rate: rand(.97, 1.03) });
}
}
this.punch(.18 * force * side);
if (!sampled) {
if (wet) {
this.noise(.08, .17 * force, 360, 'impacts');
this.noise(.025, .05 * force, 1500, 'impacts', null, true);
} else this.noise(.055, .13 * force, 420, 'impacts');
}
},
ricochet(pos) { if (this.sample('imp_ricochet', 'impacts', pos, .5, 'ricochet')) return; this.blip(rand(1400, 1900), .045, 'triangle', .12, -500, 'impacts', pos); },
wallHit(pos) {
@@ -376,8 +346,10 @@ const Sfx = {
ammo() { this.ui('ammo', () => { this.click(.14, 1600, 'ui'); this.blip(520, .08, 'square', .12, -80, 'ui'); }); },
weapon() { this.ui('weapon', () => { this.thump(120, .14, .4, 50, 'ui'); this.blip(300, .1, 'square', .25, 200, 'ui'); this.blip(450, .12, 'square', .2, 250, 'ui'); }); },
item() { this.ui('item', () => { this.thump(100, .2, .4, 45, 'ui'); this.blip(350, .15, 'triangle', .3, 150, 'ui'); this.blip(525, .2, 'triangle', .25, 200, 'ui'); }); },
active() { this.thump(140, .22, .5, 60, 'ui'); this.blip(700, .12, 'sawtooth', .28, 500, 'ui'); this.noise(.1, .14, 2400, 'ui', null, true); },
activeEmpty() { this.blip(220, .12, 'square', .18, -90, 'ui'); },
active() { this.ui('active', () => { this.thump(140, .22, .5, 60, 'ui'); this.blip(700, .12, 'sawtooth', .28, 500, 'ui'); this.noise(.1, .14, 2400, 'ui', null, true); }); },
activeEmpty() { this.ui('active_empty', () => this.blip(220, .12, 'square', .18, -90, 'ui')); },
curse() { this.ui('curse', () => { this.thump(60, .5, .5, 30, 'ui'); this.blip(180, .4, 'sawtooth', .22, -60, 'ui'); }); },
revive() { if (this.sample('plr_revive', 'voices', null, .85, 'revive')) return; this.blip(500, .3, 'sine', .25, 300, 'ui'); this.thump(100, .3, .4, 50, 'ui'); },
levelup() { this.ui('levelup', () => { this.blip(400, .12, 'square', .25, 200, 'ui'); setTimeout(() => this.blip(600, .12, 'square', .25, 200, 'ui'), 100); setTimeout(() => this.blip(800, .2, 'square', .25, 200, 'ui'), 200); }); },
perk() { this.ui('perk', () => this.blip(500, .1, 'square', .25, 300, 'ui')); },
menu() { this.ui('menu', () => this.blip(620, .055, 'square', .11, 120, 'ui')); },
+5 -9
View File
@@ -79,6 +79,10 @@ const SfxBank = {
ui_perk: 'ui_perk.mp3',
ui_stairs: 'ui_stairs.mp3',
ui_menu: 'ui_menu.mp3',
ui_active: 'ui_active.mp3',
ui_active_empty: 'ui_active_empty.mp3',
ui_curse: 'ui_curse.mp3',
plr_revive: 'plr_revive.mp3',
imp_crit: 'imp_crit.mp3',
imp_ricochet: 'imp_ricochet.mp3',
@@ -88,14 +92,6 @@ const SfxBank = {
plr_death: 'plr_death.mp3',
plr_shield_break: 'plr_shield_break.mp3',
plr_shield_up: 'plr_shield_up.mp3',
plr_step1: 'plr_step1.mp3',
plr_step2: 'plr_step2.mp3',
plr_step3: 'plr_step3.mp3',
plr_step4: 'plr_step4.mp3',
plr_step_wet1: 'plr_step_wet1.mp3',
plr_step_wet2: 'plr_step_wet2.mp3',
plr_step_wet3: 'plr_step_wet3.mp3',
plr_step_wet4: 'plr_step_wet4.mp3',
imp_flesh1: 'imp_flesh1.mp3',
imp_flesh2: 'imp_flesh2.mp3',
imp_flesh3: 'imp_flesh3.mp3',
@@ -115,7 +111,7 @@ const SfxBank = {
if (this.buffers.has(name) || this.pending.has(name) || this.failed.has(name)) return;
const file = this.FILES[name];
if (!file || !this.ctx || typeof fetch !== 'function') return;
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=41')
const job = fetch('/assets/sfx/' + encodeURIComponent(file) + '?v=42')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.arrayBuffer(); })
.then(bytes => this.ctx.decodeAudioData(bytes))
.then(buffer => this.buffers.set(name, this.trim(buffer)))
+1 -1
View File
@@ -84,7 +84,7 @@ const Sprites = {
const img = new Image();
img.onload = () => { this.imgs[name] = img; if (--pending === 0) G.imagesLoaded = true; };
img.onerror = () => { if (--pending === 0) G.imagesLoaded = true; };
img.src = 'assets/' + name + '.png?v=41';
img.src = 'assets/' + name + '.png?v=42';
}
},
-7
View File
@@ -180,21 +180,14 @@ step(12, 16);
const aimDeltaBeforeIdle = Math.abs(Math.atan2(Math.sin(ctx.G.player.aim - idleBodyFacing), Math.cos(ctx.G.player.aim - idleBodyFacing)));
const aimDeltaAfterIdle = Math.abs(Math.atan2(Math.sin(ctx.G.player.aim - ctx.G.player.bodyFacing), Math.cos(ctx.G.player.aim - ctx.G.player.bodyFacing)));
check('legs rotate back toward aim after movement stops', aimDeltaAfterIdle < aimDeltaBeforeIdle);
let footstepCalls = 0, wetFootsteps = 0;
const realFootstep = ctx.Sfx.footstep;
const decalStart = ctx.G.cur.decals.length;
for (const offset of [45, 90, 135, 180]) {
ctx.G.cur.decals.push({ x: ctx.G.player.x + offset, y: ctx.G.player.y, s: 1.6, rot: 0, img: 'decal_blood1' });
}
check('gore proximity query detects blood underfoot', ctx.onGore(ctx.G.player.x + 45, ctx.G.player.y));
ctx.Sfx.footstep = (_strength, wet) => { footstepCalls++; if (wet) wetFootsteps++; };
press('d'); step(60, 16); release('d');
const movingFootsteps = footstepCalls;
step(30, 16);
ctx.Sfx.footstep = realFootstep;
ctx.G.cur.decals.splice(decalStart);
check('stride-9 cycle emits synchronized footsteps', movingFootsteps >= 2 && footstepCalls === movingFootsteps);
check('blood decals select wet footstep events', wetFootsteps >= 1);
ctx.Input.mdown = true; ctx.Input.mx = 700; ctx.Input.my = 300;
step(30, 16);
check('bullets fired', ctx.G.bullets.length > 0 || ctx.G.kills >= 0); // may have hit nothing