Add industrial metal soundtrack with seamless loop
- assets/audio/music/zombie_industrial_loop.ogg: 57.60 s Ogg Vorbis loop generated locally with YuE2-3B on the Tesla V100 (score written for this track; vocals removed with HTDemucs). The loop is spliced at the track's structural repeat with a 0.6 s crossfade: measured seam dip <= 1.6 dB. - scripts/main.gd: "Music" AudioStreamPlayer at -11 dB, started at boot on desktop and on the start click on Web (browser autoplay policy). It forces stream.loop at runtime because the Web staging step re-imports the OGG with loop=false. - tools/test_music.gd: soundtrack test (loop, length, volume, playback, still playing after player death). - docs/MUSIC.md: source, loop construction, verification, licence. - docs/WEB-DEPLOY.md: release ab6054111f5919a8 notes. Initial commit of the project.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Death and blood effects
|
||||
|
||||
- Zombie idle/attack/death use AudioStreamPlayer3D with attenuation (unit_size 4m, max_distance 30m).
|
||||
- No original `zo_death*.wav` is available. `zombie/zo_pain2.wav` is deliberately used for the death event; living hurt uses `zo_pain1.wav`.
|
||||
- Player death uses the original Steam `valve/sound/player/pl_pain7.wav`, copied unchanged. This is a pain vocal assigned to death, not a dedicated death sample.
|
||||
- Corpses remain until scene restart. Collision and AI stop, death animation retains its final pose. Window-entry tweens are cancelled on death.
|
||||
- Blood: GPUParticles3D burst and original generated RGBA decal mask. Decals project downward onto static world layer 1. Characters/viewmodel are on visual layer 2. At most 160 stains retained; bodies have no automatic deletion.
|
||||
- Player death is idempotent: gun hidden, shooting/mouse-look blocked, camera falls to 0.22m and rolls; black fade finishes after 2.5s. Restart label is above black overlay. R reloads scene.
|
||||
|
||||
## Verification
|
||||
|
||||
Use Godot 4.7.1:
|
||||
|
||||
```
|
||||
godot --headless --path . --script res://tools/test_death_effects.gd
|
||||
godot --path . --script res://tools/test_death_effects.gd
|
||||
```
|
||||
|
||||
Tests assert corpse survival beyond old four-second lifetime, collision off, one decal, death only once, disabled shooting, camera at floor height and fade alpha 1. Graphical test writes `/tmp/zombie-blood.png`, `/tmp/zombie-corpse.png`, `/tmp/zombie-death-fade.png`.
|
||||
|
||||
Valve assets are local extracted copyrighted game data, not licensed for redistribution by this project.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Grenades and growing waves
|
||||
|
||||
- Right mouse button: throw while mouse is captured and player alive.
|
||||
- Cooldown: 15 seconds from launch, displayed in HUD; cannot be bypassed by repeated clicks.
|
||||
- Physical RigidBody3D: sphere radius 0.12m, mass0.4kg, bounce0.45, friction0.65, gravity, CCD; launch velocity forward12m/s + up3m/s + player velocity. Thrower excluded from projectile collision but not blast damage.
|
||||
- Fuse: four physics seconds from spawn; one deferred detonation.
|
||||
- Blast radius5m, maximum damage160 diminishing linearly with distance; world collision ray blocks damage through walls. Includes thrower if alive. CPU particles/flash and original Half-Life explode3–5.wav.
|
||||
- Original Valve grenade.mdl contains two embedded textures; copied unchanged and exported to native grenade.scn for Web. Provenance in assets/half_life_manifest.json; extraction script tools/extract_grenade_assets.py.
|
||||
- Wave size: 4 + 2*wave (6,8,10,...), no old24 limit. Next wave three seconds after all enemies die and spawning completes. Scheduling guard prevents duplicate wave timers. Corpses remain.
|
||||
|
||||
## Tests
|
||||
|
||||
Godot4.7.1:
|
||||
|
||||
```
|
||||
--headless --path . --script res://tools/test_grenade.gd
|
||||
--headless --path . --script res://tools/test_wave_growth.gd
|
||||
```
|
||||
|
||||
Grenade test PASS: movement/gravity, fuse3.997s, actual15s cooldown, visible/blocked/out-of-radius damage, self-damage and formula26 enemies for wave11. Wave test PASS: six zombies cleared, single next wave spawns eight, six corpses retained; an existing shutdown resource warning remains in the wave test, not a gameplay error.
|
||||
|
||||
Published Web release4e181f0fe5d02cc9 at https://enne2.net/pub/zombie-fps/; gzip+immutable cache retained. Browser right-click test showed HUD countdown10s after throw. Backup prior root HTML: /home/debian/zombie-fps-backups/index-before-grenade.html. Previous releases kept; no nginx changes.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Soundtrack — industrial metal loop
|
||||
|
||||
Endless survival loop played from the first frame, at `-11 dB`, independent of gameplay state.
|
||||
|
||||
## Source
|
||||
|
||||
Generated locally (2026-10-10) with **YuE2-3B Q8_0** through the audio.cpp CUDA build for the
|
||||
Tesla V100 (`~/dev/audio.cpp`, `cmake -DCMAKE_CUDA_ARCHITECTURES=70`), seed 31337, 16 steps,
|
||||
`cot=melody` + an external ABC score written for this track (K:Em Phrygian, `Q:1/4=168`,
|
||||
96 bars; the model rendered it at a perceived 112.5 BPM). Style prompt: instrumental
|
||||
industrial metal, Nine Inch Nails meets the Quake 2 soundtrack, machine drums, palm-muted
|
||||
downtuned guitars, atonal synth stabs, no vocals.
|
||||
|
||||
`--lyrics "[instrumental]"` does **not** suppress singing (verified: male shouts 0:52-1:17 and
|
||||
1:37-2:35). The instrumental was therefore obtained by **HTDemucs** vocal removal
|
||||
(`--task sep --family htdemucs`, input resampled to 44.1 kHz, RTF 0.049) and remixed as
|
||||
`drums + bass + other`.
|
||||
|
||||
Working files: `~/yue2-out/zombie-industrial/`.
|
||||
|
||||
## Loop construction
|
||||
|
||||
The track has a structural repeat (bars 24-30 ≈ 46-52, spectral similarity 0.95), so the loop
|
||||
splices at matching material instead of cutting arbitrarily:
|
||||
|
||||
- region 51.37 s → 108.97 s of the instrumental master = 27 bars (bar = 2.1333 s, 112.5 BPM);
|
||||
- rotation by one tail segment plus a **0.60 s** equal-power crossfade;
|
||||
- result: **57.60 s**, seam dip ≤ 1.6 dB in 20 ms windows, sample step 0.04 (inaudible);
|
||||
- perceptual check on a 3 s + 3 s excerpt around the seam: no click, no level hole, natural
|
||||
continuation.
|
||||
|
||||
A longer variant (132 s) was rejected: its crossfade cancels (a -48 dB hole) because the two
|
||||
overlapped sections are out of phase.
|
||||
|
||||
## Asset and import
|
||||
|
||||
`assets/audio/music/zombie_industrial_loop.ogg` — Ogg Vorbis q6, 48 kHz stereo, 1.0 MB,
|
||||
-14 LUFS / -1.0 dBTP. Import: `loop=true`, `bpm=112.5`, `beat_count=108`, `bar_beats=4`.
|
||||
|
||||
The Web staging step (`tools/prepare_web.py`) copies the asset but ignores `*.import`, and the
|
||||
staging import regenerates it with `loop=false`. `main.gd` therefore sets `stream.loop = true`
|
||||
at runtime: keep that line, it is what makes the browser build loop.
|
||||
|
||||
## Runtime wiring
|
||||
|
||||
`scripts/main.gd`: `_setup_music()` creates the `AudioStreamPlayer` named `Music` (group
|
||||
`music`), `_start_music()` starts it once, `_restart_music()` restarts it if the loop flag is
|
||||
ever absent. On Web the start is deferred to the click that captures the mouse, because
|
||||
browsers only allow audio after a user gesture. Music keeps playing while the player is dead;
|
||||
`R` reloads the scene and the track restarts with it.
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
godot --headless --path . --script res://tools/test_music.gd
|
||||
godot --display-driver headless --audio-driver PulseAudio --path . --script res://tools/test_music.gd
|
||||
godot --headless --path . -- --smoke
|
||||
```
|
||||
|
||||
Both runs PASS: node present, `loop=true`, length 57.60 s, volume -11 dB, playback advancing
|
||||
(confirmed on the real PulseAudio device, not only the Dummy driver), music still playing after
|
||||
player death. Grenade, wave, death and damage-feedback tests also pass unchanged.
|
||||
|
||||
## License
|
||||
|
||||
YuE2-3B weights are **CC BY-NC 4.0** (non-commercial): this track is for personal/experimental
|
||||
use. It is an AI-generated render, not a recording of a third-party work.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Web build and compressed deployment
|
||||
|
||||
URL: https://enne2.net/pub/zombie-fps/
|
||||
Server: debian@enne2.net (bastion).
|
||||
Web root: /home/debian/public_html/zombie-fps/.
|
||||
|
||||
## Build
|
||||
|
||||
Use installed Godot 4.7.1 executable, not the Godot 4.3 on PATH:
|
||||
|
||||
```bash
|
||||
G="$HOME/.local/opt/godot-4.7.1/Godot_v4.7.1-stable_linux.x86_64"
|
||||
"$G" --headless --path /home/enne2/dev/zombie-fps --script res://tools/export_web_assets.gd
|
||||
python3 /home/enne2/dev/zombie-fps/tools/prepare_web.py
|
||||
"$G" --headless --path /home/enne2/dev/zombie-fps/build/web-stage --import
|
||||
mkdir -p /home/enne2/dev/zombie-fps/build/web
|
||||
"$G" --headless --path /home/enne2/dev/zombie-fps/build/web-stage --export-release Web /home/enne2/dev/zombie-fps/build/web/index.html
|
||||
python3 /home/enne2/dev/zombie-fps/tools/package_web_release.py
|
||||
```
|
||||
|
||||
Staging excludes native addons, source MDL assets, unused world weapon scenes, extraction tools. Runtime uses native SCN models. Web renderer is Compatibility, with CPU particles and floor blood patches instead of Forward+ decals. Browser click starts waves and captures mouse. Single-thread Web export does not require cross-origin isolation server headers.
|
||||
|
||||
## Versioned compression
|
||||
|
||||
`package_web_release.py` computes a content hash, writes build/web-deploy/releases/<hash>/ and gzip level 9 sidecars, verifying decompression. Original and gzip mtimes match. Root index.html uses a base href to the immutable release directory; original fileSizes remain uncompressed sizes as expected by Godot. Originals remain available for clients rejecting gzip.
|
||||
|
||||
Upload new immutable directory first, never overwrite an existing hash. Then atomically replace root index.html. Keep previous releases for open tabs/rollback. Do not delete them during routine deployment.
|
||||
|
||||
Nginx snippet source: tools/nginx-zombie-fps.conf. Installed at /etc/nginx/snippets/zombie-fps.conf and included only by enne2.net's HTTPS server in /etc/nginx/conf.d/enne2.conf. gzip_static on, Vary Accept-Encoding, correct application/wasm MIME. Root HTML no-cache; versioned files public,max-age=31536000,immutable. Other /pub/ sites unchanged. No Brotli module installed.
|
||||
|
||||
Protected configuration backup: /etc/nginx/zombie-fps-backup-9d706e724b0a/.
|
||||
Original root HTML backup: /home/debian/zombie-fps-backups/index-before-compression.html.
|
||||
Tools/apply_nginx_compression.py validates nginx -t, reloads, and restores config on failure. Always obtain approval for server changes.
|
||||
|
||||
## Verified release 7d3c1fb9d96da215
|
||||
|
||||
- Main text/binary payloads: 42,303,791 -> 11,035,976 bytes (gzip, ~74% reduction).
|
||||
- WASM: 39,513,091 -> 10,074,664 bytes.
|
||||
- PCK: 2,495,216 -> 887,112 bytes.
|
||||
- JS: 279,815 -> 68,657 bytes.
|
||||
- HTTPS downloaded gzip responses decompress exactly to local files; hashes match.
|
||||
- identity and gzip;q=0 clients receive originals. Vary header present.
|
||||
- Browser first load: WASM transfer 10,074,964 bytes including resource-timing overhead; PCK 887,412. Resource durations ~513/522ms on frigate's connection (not universal startup time).
|
||||
- Browser reload: WASM/PCK/JS transferSize 0; cache reads ~16/4/4ms. Godot loading overlay removed, game initialized on both loads.
|
||||
|
||||
This reduces network transfer, not engine compile/initialization cost. Further gains would require a custom stripped Godot Web template; not built here. Valve copyrighted assets remain subject to redistribution rights; user explicitly stated the necessary rights for public deployment.
|
||||
|
||||
## Verified release ab6054111f5919a8 (soundtrack)
|
||||
|
||||
First release that ships the looping industrial metal soundtrack (see docs/MUSIC.md).
|
||||
|
||||
- PCK: 2,568,000 -> 3,632,524 bytes (+1,064,524, i.e. the 1.0 MB Ogg Vorbis was the only new payload).
|
||||
- All payloads: 43,441,099 -> 12,089,938 bytes gzip.
|
||||
- Uploaded 14 files as a new immutable directory; remote sha256 identical to local for all 14; previous releases kept (4e181f0fe5d02cc9, 7d3c1fb9d96da215, 7d55da56077f8eee).
|
||||
- Root index.html replaced by atomic rename; backup at /home/debian/zombie-fps-backups/index-before-music.html.
|
||||
- HTTPS: root 200 text/html no-cache with the new base href; PCK byte-identical after gzip decode (3,632,524 bytes); versioned files application/wasm, Vary, immutable one year.
|
||||
- Browser: Godot 4.7.1, WebGL 2.0 Compatibility, single-threaded; the click starts wave 1 (HUD `ONDATA 1`, 250 points) with zombies, blood decals and the damage vignette.
|
||||
|
||||
Staging trap: `prepare_web.py` ignores `*.import`, so the staging project re-imports the OGG with `loop=false`. `main.gd` sets `stream.loop` at runtime; this was verified in exactly that condition by running `test_music` inside `build/web-stage` with the PulseAudio driver (loop false at load -> true after setup, playback advancing).
|
||||
|
||||
Bastion disk: 1.4 GB free on / (97% used). Do not accumulate releases without checking space, and delete old ones only with approval.
|
||||
Reference in New Issue
Block a user