Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ The full picture is in `README.md`. This is a Perry-compiled TypeScript
FPS) built on the Bloom engine. Seven alien kinds (two RANGED), three
waves, four weapons, TWO playable arenas (`arena_02` Outdoor Plaza and
`arena_03` Riverbend Ravine — see SH-040; a third is still owed),
textured arena, skeletal-animated enemies, ambient music — runnable with
textured arena, skeletal-animated enemies, ambient music, and a live 3D
soundscape (river/wind emitters + creature locomotion on EN-062 voices —
see SH-052) — runnable with
`perry compile src/main.ts -o main && ./main`.

## Where things live
Expand Down
14 changes: 14 additions & 0 deletions assets/sounds/SOURCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,22 @@ game-ready files are committed, never pack folders.
| `rifle_fire2.wav` | Dramatic Cat — SVD Dragunov, DESIGNED Single Shot Core Long |
| `blaster_fire.wav` | BluezoneCorp — Sci Fi Weapon, gun_shot_008 |
| `splash1.wav` | BluezoneCorp — Designed Water, impact_006 (first 1.2 s) |
| `river_loop.wav` | Bolt — Immersive Creek (Vermont), Babbling Brook XY pair; 32 s seamless loop cut at 49 s by `tools/convert-audio.ts` (SH-052b — window chosen by RMS/brightness variance scan, equal-power tail-over-head loop fade) |

## Pre-existing (provenance predates this file)

`rifle_fire.wav` (superseded by rifle_fire2 but kept), `dretch_attack.wav`,
`pickup.wav`, `menu.ogg`, `game.ogg`, `ambient.ogg`.

## Synthesised by `tools/gen-sfx.ts` (no external source — GPL-clean by construction)

Deterministic (seeded), so re-running the tool reproduces byte-identical
files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real
replacements.

`step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`,
`reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`,
`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless
loop), and the SH-052 set: `river_loop.wav` (14 s seamless),
`skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav`
(3.5 s seamless).
Comment on lines +58 to +67

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Correct the synthesized-asset provenance and durations.

This section incorrectly labels river_loop.wav as a 14-second synthesized asset despite Line 49 and tools/convert-audio.ts defining it as a 32-second recorded loop. It also lists the rebuilt wind loop as 16 seconds instead of 24 seconds. This makes the licensing/provenance record internally inconsistent.

Proposed documentation fix
-`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless
-loop), and the SH-052 set: `river_loop.wav` (14 s seamless),
-`skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav`
-(3.5 s seamless).
+`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (24 s seamless
+loop), and the SH-052 synthesized set: `skitter_light{1-4}.wav`,
+`skitter_heavy{1-4}.wav`, `crawl_loop.wav` (3.5 s seamless).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Deterministic (seeded), so re-running the tool reproduces byte-identical
files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real
replacements.
`step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`,
`reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`,
`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (16 s seamless
loop), and the SH-052 set: `river_loop.wav` (14 s seamless),
`skitter_light{1-4}.wav`, `skitter_heavy{1-4}.wav`, `crawl_loop.wav`
(3.5 s seamless).
Deterministic (seeded), so re-running the tool reproduces byte-identical
files. See docs/ASSET-TODO.md A1/A2/A3/A5/A6/A7/A8/A9 for the intended real
replacements.
`step_grass{1-4}.wav`, `step_dirt{1-4}.wav`, `step_heavy1.wav`,
`reload_start.wav`, `reload_end.wav`, `ui_move.wav`, `ui_select.wav`,
`{rifle,blaster,chain,cannon}_tail.wav`, `ambient_wind.wav` (24 s seamless
loop), and the SH-052 synthesized set: `skitter_light{1-4}.wav`,
`skitter_heavy{1-4}.wav`, `crawl_loop.wav` (3.5 s seamless).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/sounds/SOURCES.md` around lines 58 - 67, Update the synthesized-asset
listing in SOURCES.md to remove river_loop.wav from that section, preserving its
recorded-loop classification and 32-second duration from the existing source
definitions. Correct ambient_wind.wav’s listed duration from 16 seconds to 24
seconds, leaving the other asset entries unchanged.

Binary file modified assets/sounds/ambient_wind.wav
Binary file not shown.
Binary file added assets/sounds/crawl_loop.wav
Binary file not shown.
Binary file added assets/sounds/river_loop.wav
Binary file not shown.
Binary file added assets/sounds/skitter_heavy1.wav
Binary file not shown.
Binary file added assets/sounds/skitter_heavy2.wav
Binary file not shown.
Binary file added assets/sounds/skitter_heavy3.wav
Binary file not shown.
Binary file added assets/sounds/skitter_heavy4.wav
Binary file not shown.
Binary file added assets/sounds/skitter_light1.wav
Binary file not shown.
Binary file added assets/sounds/skitter_light2.wav
Binary file not shown.
Binary file added assets/sounds/skitter_light3.wav
Binary file not shown.
Binary file added assets/sounds/skitter_light4.wav
Binary file not shown.
5 changes: 4 additions & 1 deletion docs/ASSET-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ today, it just sounds/looks better once you replace them.
|---|---|---|---|
| A1 | Footstep SFX — grass ×4, dirt ×4, heavy ×1 | 🟡 | **Synthesised stand-ins shipped** (`bun tools/gen-sfx.ts`). A step is an *impact* plus a *scuff*: grass is nearly all scuff (bright, grainy), dirt is mostly impact (low thud + grit), the tyrant footfall is a 48 Hz body with debris on top. They are audible and they sit right in the mix — but they are synthesis, not recordings. Replace with the Sonniss GDC bundle (a documented source in `SOURCES.md`) as a straight file drop: same names, no code change. |
| A2 | Weapon tail layers — rifle, blaster, chaingun, cannon | 🟡 | **Synthesised stand-ins shipped.** The "BOOM" half of crack-BOOM: band-limited decaying noise + a low body, routed to the reverb send by the mixer. Cannon runs 2.4 s and is still audible when the next shot lands. Real recordings would be better; the *system* is no longer silent. |
| A3 | Wind / leaf-rustle ambience loop | 🟡 | **Synthesised stand-in shipped** (16 s, seamless — the tail is cross-faded over the head). SH-001 is now wired: three 3D sources placed on the forest's own centroids (computed from the world file, so moving the trees in the editor moves the sound), volume from distance **and from the live wind amplitude**, so the rustle swells with the same vector that bends the grass. |
| A3 | Wind / leaf-rustle ambience loop | 🟡 | **Synthesised stand-in shipped, rebuilt in SH-052c** (24 s, seamless). v1 was a fixed bandpass × volume LFO — filtered white noise that got louder and quieter, and the ear said so. v2 moves the SPECTRUM with the gust (dark bough murmur in lulls → bright leaf-sizzle in gusts, brightness ∝ gust^1.8, r=0.95 measured) + leaf-flutter grains planted only in strong gusts. Three 3D sources on the forest's own centroids, detuned 0.94/1.0/1.07 (EN-062 loops), volume from the live wind amplitude AND the restored 45 m authored window. A real recording (steady broadleaf canopy, no birds, no mic buffet) is still the endgame: straight drop as `ambient_wind.wav` — but the CUT must keep a seamless loop (use convert-audio's `loopFade`). |
| ~~A7~~ | ~~River loop~~ | ✅ | **Done 2026-07-16 (SH-052b) — real recording.** The synth stand-in read as fake (sine-chirp "babble" is the tell), so `river_loop.wav` is now a 32 s seamless cut of Bolt's *Immersive Creek* babbling-brook recording (Sonniss GDC 2024, same license line as `splash1.wav`). Cut is scripted and reproducible: `tools/convert-audio.ts` (`startAt`/`loopFade` — the window was picked by scanning for minimum RMS + brightness variance, not by ear). Runtime got width too: main emitter + two detuned flankers along the channel axis. |
| A8 | Chitin skitter steps — light ×4, heavy ×4 | 🟡 | **Synthesised stand-ins shipped** (SH-052) — leg-tap bursts; heavy adds body + scrape. Fired per stride by every non-tyrant kind (`KIND_STRIDE`). Real replacements: insect-on-stone foley, dry leaves off. Names `skitter_light1-4.wav` / `skitter_heavy1-4.wav`. |
| A9 | Crawl bed loop | 🟡 | **Synthesised stand-in shipped** (SH-052, 3.5 s seamless) — the continuous chitinous rustle a pooled voice carries on each nearby *moving* enemy, so something closing in is audible between its steps. Keep any replacement QUIET and broadband; it reads subliminally by design. `crawl_loop.wav`. |
| ~~A4~~ | ~~Music stems: calm + combat loops, and 3 stingers~~ | ✅ | **Done 2026-07-13.** Six tracks generated, mastered and shipped. Suno ignored the lengths (162 s beds, a 49 s "stinger") and the key (came back **A# minor**, not D minor) — the key was fine because all three beds agree with *each other*, which is the only thing that mattered, and that was **verified** rather than assumed (chroma + Krumhansl in `tools/audio.ts`). `bun tools/music-master.ts` finds each loop point by searching for self-similarity, crossfades the seam, and trims the stingers to windows read off the waveform. Raw renders kept in `assets/music/src/`. |
| A5 | Reload SFX (start / finish) | 🟡 | **Synthesised stand-ins shipped** — noise clack + two metallic partials; start is lower (mag out), finish is higher and harder (seated). |
| A6 | UI click / hover SFX | 🟡 | **Synthesised stand-ins shipped** — soft sine blips. Kept deliberately dull: they fire on every menu move, and anything with a sharp transient becomes torture inside a minute. |
Expand Down
98 changes: 98 additions & 0 deletions docs/tickets.md
Original file line number Diff line number Diff line change
Expand Up @@ -1675,3 +1675,101 @@ but it is a camping spot until the pathfinding lands.
3 m/10-step stairwell at (-15,-10). The shipped house v2 (`h_slab_a`,
`h_stair_a_*`, floors at 0.20/3.90/7.60) did not come from it. Do not "fix" the
world by re-running that tool.

## SH-052 — The living soundscape: river, wind loops, creature locomotion ✅ *(shipped 2026-07-16)*

**Why:** "we want 3D sounds for the wind, the river, the animals/spiders
crawling closer." The engine could only fire-and-forget (`playSound3D`), so
the wind was a retrigger timer, the river was silent, and every enemy but the
tyrant approached without a sound.

Built on EN-062 (live spatial voices — looping, moveable, per-voice
volume/pitch/low-pass, real distance model, equal-power pan, air absorption,
rear head-shadow cue, doppler; also FIXED mirrored stereo and the ~9%-sharp
44.1k-on-48k playback):

- **River** (`initRiverAmbience`/`updateRiverAmbience`): one looping emitter
per water volume, positioned every frame at the closest point of the
volume's rectangle to the player — a line source faked with a point source
that always stays abreast of you. Rectangles come from the world file:
move the river in the editor and its sound moves. refDist ≈ channel
half-width (the bank IS full volume).
- **Wind**: the three forest-centroid sources are true loops now (retrigger
timer deleted), detuned 0.94/1.0/1.07 so the treeline decorrelates. Volume
still rides the live wind amplitude; distance/pan/absorption are engine-side.
- **Creature locomotion**, two layers:
- *Steps*: every kind runs the tyrant's stride accumulator now
(`KIND_STRIDE` per kind, tyrant's 2.2 unchanged); non-tyrants fire
positional chitin skitters (light bank = dretch/mantis, heavy =
marauder/dragoon/advs), gated on actually moving.
- *Crawl bed*: a pool of 4 looping voices assigned to the nearest MOVING
enemies within 28 m (O(n²) top-k, allocation-free, slot-stable so voices
never teleport between owners). Pitch per kind (small = fast ticking).
The thing you haven't seen is audible closing in BETWEEN its steps.
Self-gating: no qualifying enemies → all slots silent, menus included.
- **Assets**: `river_loop`, `skitter_light1-4`, `skitter_heavy1-4`,
`crawl_loop` — synthesised stand-ins in `gen-sfx.ts` (appended AFTER the
existing emits: one global seeded noise stream, insertion order is the
diff-stability contract). Old files byte-identical. ASSET-TODO A7-A9.

Verified with an AITEST probe build (numeric counters, not ears): 351
skitter steps over 50 s of live combat, crawl slots assigning/releasing as
enemies moved and parked (a dretch standing at 1.5 m goes silent — the
moving gate at work), wind voices 5/6/7, river voice 8, zero errors, no
command-ring pressure. Probe reverted; shipped-config build boots clean.

## SH-052b — The river is a real creek now (and it has width) ✅ *(shipped 2026-07-16, follow-up to SH-052)*

**Why:** "the water sounds fake." It did — the SH-052 stand-in built its babble
from falling sine chirps, and synthetic water is the one ambience the ear
always catches. Water needed a recording, not a better formula.

- `river_loop.wav` is now a **32 s seamless cut of a real babbling brook**
(Bolt "Immersive Creek — Ambisonic Recordings of Undisturbed Creeks in
Vermont", Sonniss GDC 2024 bundle — the same accepted license line as
`splash1.wav`; source extracted to gitignored `vendor/sonniss/`).
- The cut is **scripted and reproducible**: `convert-audio.ts` grew
`startAt` + `loopFade` (equal-power tail-over-head cross-fade, chunk-aware
WAV patching — ffmpeg slips a LIST chunk before `data`). The 33.5 s window
at 49 s was picked by scanning every candidate window for minimum RMS +
brightness variance (level-constant, no birds, no handling) — score 0.144.
Loop verified numerically: seam first-difference 0.034 vs body max 0.31 —
the wrap is quieter than the material's own deltas. No clipping.
- **Width**: one point emitter reads as a speaker in a field. Each water
volume now runs a detuned trio — main at the closest bank point plus two
flankers ±8 m up/downstream along the channel axis (0.96/1.05, clamped to
the rectangle). Turn your head on the bank and the water has extent.
- `gen-sfx.ts` no longer emits a river. Removing it shifted the shared
seeded-noise stream, so skitter*/crawl_loop regenerated (equivalent
variants, committed together) — the "re-run reproduces the repo" contract
holds. A river emit must never be re-added there: it would clobber the
recording.

## SH-052c — "I still hear white noise": the wind bed, run to ground ✅ *(shipped 2026-07-17)*

**Diagnosis.** Two compounding causes, one a SH-052 regression:
1. `ambient_wind.wav` was a fixed 700-6500 Hz bandpass × a volume LFO —
filtered white noise that only got louder and quieter. A static spectrum
is exactly what the ear files under "white noise", however it is dressed.
2. SH-052 deleted the authored "gone by 40 m" window in favour of the
engine's physical rolloff — right call for a recording, wrong for a synth
bed: the inverse model's long tail spread a faint hiss carpet over the
WHOLE arena, including the open field that used to be silent.

**Fix, both halves:**
- The generator moves the SPECTRUM with the gust now: two decorrelated
registers (350-1800 bough murmur, 1800-8500 leaf sizzle) whose balance
rides a slow gust envelope (brightness ∝ gust^1.8), plus leaf-flutter
grains (9-17 Hz AM bursts) planted only where the gust is strong, plus a
seeded slow wander so passes never repeat. 24 s loop (was 16). Verified
numerically, not by ear: brightness-vs-level r = 0.95 across the loop
(v1 ≈ 0 by construction); seam delta 0.23 vs body max 0.53; no clipping.
- `updateWindAmbience` re-applies the authored window on top of the engine
rolloff: full inside ~27 m of a source, silent past 45 m. The engine keeps
pan/absorption/close-range; the game shapes the far field.

Sonniss part 1of9 has no usable wind-in-trees recording (checked), so the
stand-in stays a stand-in — but an honest one. ASSET-TODO A3 updated with
the drop-in requirements for a real recording (must keep a seamless loop —
cut it with convert-audio's loopFade). Skitter/crawl regenerated again
(shared seed stream; same determinism contract as SH-052b).
Loading