Update


# Devlog: The "Oh God It's Real" Update

Posted: Aug 13, 2026

---

So the fart sounds were fake.

I've been dancing around this for a while. The original AudioManager synthesized

everything from oscillators and filtered noise — technically it made a sound,

functionally it sounded like a dial-up modem having a breakdown. Players noticed.

I noticed. Everyone noticed.

This update fixes that, plus a bunch of other stuff I'd been putting off.

## Real fart sounds (yes, really)

I pulled two recordings off Wikimedia Commons — one CC0, one CC BY 3.0 from the

Yle archive. The CC0 one is a 32-second pack of cartoonish farts. The Yle one is

a *290-second* Finnish public radio fart compilation and I'm not going to dwell

on why that exists but I'm grateful it does.

Wrote a fetcher script that downloads them, runs them through ffmpeg

(silence-split, trim, normalize, export MP3), and auto-classifies each clip into

a bank based on duration and spectral content. Ended up with 38 usable samples

across six banks: short, standard, long, wet, deep, squeaky.

The classification took three passes because my first threshold for "wet" was

too aggressive and everything ended up in the wet bank. 38 wet farts. Not great.

Recalibrated and got a reasonable spread.

The AudioManager itself got a full rewrite. Each bank has a shuffle bag so you

never hear the same sample twice in a row, every playback gets random pitch and

gain jitter, and there's a PannerNode on every voice so the sound actually comes

from behind the character instead of dead center. Built a procedural convolution

reverb out of a noise burst with exponential decay — no IR file needed, sounds

like a tiled bathroom. Fitting.

The synth is still there as a fallback and as a sub-bass layer under the

THUNDERCLAP type. Turns out layering a real sample with a synth rumble sounds

better than either one alone. Who knew.

## She's losing the fight

The desperation mechanics were always half-built. The pressure system had states

and events but nothing happened between CRITICAL and the game-over screen except

the number going up. Now stuff actually escapes.

Past CRITICAL (70), there's a leak timer that fires involuntary leaks. The

interval shrinks as pressure climbs — 9 seconds at CRITICAL down to 2.6 at

MELTDOWN. Standing still halves the rate because you're clenching. This is the

same trade-off as the main clench mechanic (holding still slows pressure

accumulation), so the two systems reinforce each other. Sprint to the toilet or

freeze and hold it in. Your call.

Each leak drops a small squeaky fart, bleeds a little pressure, bumps the soiling

up, and leaves a puddle on the floor. The puddles are pooled decals with

procedurally generated stain textures — canvas-drawn radial gradients with

random splatter droplets around the edge. They fade in, sit there for a while,

then fade out.

## The pants situation

The character mesh already had seat-vertex classification and butt inflation from

the original code, but it only fired once at game over. Now it's progressive.

I added a `setDesperation(0..1)` call that ramps with pressure across the

UNEASY-to-MELTDOWN band. The seat vertices inflate slightly as pressure climbs —

not enough to look like a full accident, just enough that she visibly strains.

Each leak bumps it a little more.

The stain is a shader injection via `onBeforeCompile`. The vertex stage forwards

the bind-pose position, the fragment stage builds a soft mask from a seat

center/radius uniform (derived from the recorded seat verts), and lerps the base

color toward wet brown. Darker saturated core, diffuse halo at the edge.

Roughness drops too, so the stained area reads as wet fabric. The whole thing is

animation-stable because it's in bind space — the patch stays welded to the same

cloth regardless of what clip is playing.

There's also a shiver now. Past 0.45 desperation, the holder group gets

subtle high-frequency jitter on its rotation. Fast, tiny, and only near the

limit. The kind of thing you feel before you consciously notice it.

## The accident

The old game-over was: freeze, play one shit sound, snap to DesperateIdle, wait

3 seconds, show results. Abrupt and unsatisfying.

The new one is a 7.5-second cinematic with six stages:

1. **TENSION** (0-1.5s) — Hitstop, screen flash, camera swings down to a low

   rear angle. A single squeaky leak escapes. She's trembling.

2. **FIRST_SPURT** (1.5-2.5s) — Wet squelch, soil bumps up, first puddle

   droplet appears on the floor.

3. **MAIN_RELEASE** (2.5-5.5s) — The long diarrhea sample plays. Soil ramps to

   full. The puddle grows. Nearby NPCs gag and flee. Gas plume pumps out.

4. **DRIBBLES** (5.5-6.5s) — Diminishing squelches every 0.4s. Soil settles.

5. **EXHALE** (6.5-7.5s) — A tired exhale. Camera holds. Then it's over.

6. **DONE** — Camera blends back, results screen takes over.

The camera work was the hard part. I had to add an `override` mode to CameraRig

that takes a position and target (either vectors or functions, so they can track

a moving character) and smoothly blends to them, then blends back when released.

While overridden, mouse input is ignored. The blend uses smoothstep so it eases

in and out instead of snapping.

## HUD stuff

Added a clench indicator — a small meter near the pressure gauge that fills when

you're standing still at high pressure. Shows you're actively holding it in.

Also added a brown vignette flash for leaks, separate from the red damage flash,

so you can tell the difference between "something escaped" and "someone saw you."

## What didn't make it

The toilet bowl cam climax is still disabled. That's a separate scope item and

frankly a different conversation. The win side of the game is unchanged.

## Technical notes

- 56 source files, all pass `node --check`.

- Puppeteer smoke test confirms: no console errors, 38 audio samples loaded,

  all 5 relief types fire, 0 leaked audio nodes after 60 rapid farts, leaks

  trigger at CRITICAL, soil/stain shader updates, accident runs all 6 stages.

- Total audio added: ~1 MB of MP3s. The 84 MB source file stays in a gitignored

  cache.

- Attribution is in `assets/sounds/ATTRIBUTION.md` (required by CC BY).

## Files

New: `tools/fetch-sounds.mjs`, `tools/index-sounds.mjs`, `tools/sound-lib.mjs`,

`src/systems/Decals.js`, `src/systems/AccidentSequence.js`, 38 MP3 clips,

`manifest.json`, `ATTRIBUTION.md`, `AGENTS.md`.

Modified: `AudioManager.js` (rewrite), `main.js`, `config.js`,

`PressureSystem.js`, `TacticalReliefSystem.js`, `Character.js`,

`CameraRig.js`, `CivilianAI.js`, `LevelDirector.js`, `HUD.js`, `styles.css`,

`package.json`, `.gitignore`.

---

Next up: I need to actually playtest this thing for more than 30 seconds without

the smoke test driving it. The leak pacing might need tuning — 9 seconds at

CRITICAL feels right on paper but might be too generous in practice. We'll see.

Files

CodeBr18.zip Play in browser
78 days ago

Leave a comment

Log in with itch.io to leave a comment.