# Web build and compressed deployment ## Contact grenade / original Half-Life fireball: add7baaef2b022ef First-impact detonation (floor, props, walls, enemies and glass), no old timed fuse; original `zerogxplode.spr` converted to a portable additive PNG atlas (15 frames, 15 FPS), see `docs/GRENADES-WAVES.md`. Damage, blast radius and cooldown unchanged. - PCK 4,420,396 bytes; total gzip payload 12,866,911 bytes. - SHA256 verified on all 14 uploaded files; HTTPS-decoded PCK byte-identical to local. - Atomic root swap; backup `/home/debian/zombie-fps-backups/index-before-impact-add7baaef2b022ef.html`. - No nginx/API changes, no release deletion; previous `a961c96175bc644e` and older `209fa6b154fea8df` retained (three release directories after this deploy). - 24 grenade checks passed headless and with the real Compatibility renderer; all 15 Godot suites plus the 42-check API suite passed in their appropriate drivers. `test_start_screen` needs a real display (headless dummy misses its GUI click). - Captured five original fireball frames after a real floor impact in the office. Exported Web build booted in Firefox/WebGL2 and touch grenade launch worked on loopback (no production score submissions). Loopback has no score API, so its HTTP 404/501 and JSON parse errors on death are expected test-environment limitations. - Live root, PCK, WASM and API health verified; versioned WASM retains correct MIME and immutable/Vary headers. Browser production check stays at the menu to avoid recording test games in the global leaderboard. ## Global high scores release: a961c96175bc644e Start screen with global leaderboard, player name and score submission; new score API service on the bastion behind an nginx reverse proxy, see `docs/HIGHSCORES.md`. Backups on the root: `index-before-highscores.html`, `index-before-menu-fix.html`, `index-before-null-fix.html`, `index-before-repair.html`, `index-before-lb-guard.html`. Releases kept on the server (retention policy: live + previous): `a961c96175bc644e` (live) and `209fa6b154fea8df`. Nine older releases were removed with the guarded procedure (604 MB -> 110 MB, 4.6 GB free, deleted release 404, live PCK `cmp`-identical, previous release still 200). Intermediate builds of this same work, all superseded and now deleted: `dcf1260b1a673ef4` (first leaderboard build), `6e012ca9a54d7baa` (menu fix, stored the string "null" as name), `69a88b5cca18f009` (name repair, no leaderboard guard), `209fa6b154fea8df` (previous release, kept). ## Eight windows release: fbfc9bdc01e2b32d Two windows per wall (eight total) and random zombie entry from different openings, see `docs/WINDOWS-SPAWN.md`. PCK 4,379,092 bytes; gzip payload total 12,826,421 bytes. Backup `index-before-windows.html`. Commit `fcf298c692fe2dfd12932e0a983a23a9ccded0d0`. Obsolete releases: 097e6c64b42640ad, 1194f239fcbd91d6, cd86c85387c1c9f3 and 807d07fb6ced9fc8 are deletable under the retention policy (live + previous) once approved. ## Dynamic fire release: 807d07fb6ced9fc8 Noise-driven fire rig with a bounce light, hot core, embers and a high-threshold glow, see `docs/FIRE-LIGHT.md`. PCK 4,378,324 bytes; gzip payload total 12,825,562 bytes. Backup `index-before-dynfire.html`. Obsolete releases: 097e6c64b42640ad, 1194f239fcbd91d6 and cd86c85387c1c9f3 are now deletable under the retention policy (live + previous) once approved. ## Nerd glasses and subtitle release: f77b7ba7b89364dd The mascot now wears thick nerd glasses (third AI edit pass) and carries the subtitle `"...because he was bored..."` under the title, see `docs/BRANDING.md`. A 30 px band was left under the lettering because the shell progress bar crossed the subtitle in the first attempt, deployed minutes earlier as cd86c85387c1c9f3 and superseded by this one. PCK 4,373,568 bytes; gzip payload total 12,821,256 bytes. Backup `index-before-nerd-bar.html` (the earlier `index-before-nerd.html` holds the cd86c85387c1c9f3 shell). Obsolete releases after this deploy: 097e6c64b42640ad and 1194f239fcbd91d6, both deletable under the retention policy once approved. ## Bald mascot release: 1194f239fcbd91d6 Title is now the one-word `MatGames` and the mascot has a bald scalp with a side fringe (AI edit of the previous generation, see `docs/BRANDING.md`). PCK 4,347,536 bytes; gzip payload total 12,796,287 bytes. Backup `index-before-bald.html`. Cleanup after this deploy: the three older directories (ddba14dd80539f19, 9a376a3dbf674069, 7ebf03fe3c88be15) were removed with approval, leaving 1194f239fcbd91d6 (live) and 097e6c64b42640ad (previous): 272 MB -> 110 MB, free space 1,5 GB. The same guarded procedure was used (hash read from the live base href, hex validation, containment check, kept-list exclusion, `rm -rf --` with absolute paths) and the post-checks passed: root 200 with the new base href, live PCK byte-identical, previous release still served, removed release 404, and the live loading screen captured in the browser. ## Loading screen branding release: 097e6c64b42640ad See `docs/BRANDING.md`: black boot splash background and the "Mat Games" mascot in place of the Godot logo; the browser progress bar is restyled to a thin amber line at the bottom so it no longer crosses the artwork. PCK 4,330,608 bytes; gzip payload total 12,778,745 bytes. A first attempt without the bar fix was deployed as ddba14dd80539f19 and superseded minutes later; both deploys reused the backup name `index-before-loading.html`, so that file now holds the ddba14dd80539f19 shell. Releases on the bastion: 9a376a3dbf674069, 7ebf03fe3c88be15, ddba14dd80539f19, 097e6c64b42640ad (retention policy keeps the live one plus the previous one). ## Aligned office surfaces release: 9a376a3dbf674069 See `docs/OFFICE-SURFACES.md`: original FIFTIES_WALL14U and FIFTIES_CEIL5; explicit continuous wall/perimeter UVs and regular XZ ceiling panels. PCK 3,989,720 bytes; gzip payload total 12,440,335 bytes. Previous releases retained. Root backup `index-before-office-uv.html`; serial staging import retained. Disk before upload: about1.1GB free (98% used); old-release cleanup needs approval. ## Gameplay improvements release: 7ebf03fe3c88be15 See `docs/GAMEPLAY-IMPROVEMENTS.md`: amber HUD, automatic reload, shared furniture pathfinding and original Half-Life flatline. PCK 3,972,620 bytes; gzip payload total 12,424,176 bytes. Previous releases retained; root backup `index-before-gameplay.html`. Serial staging import workaround retained. ## Office atmosphere release: fd65770c62e811fe See `docs/OFFICE-ATMOSPHERE.md`: two desks, three chairs, two desk lamps and CPU-particle fire barrel; ceiling light removed, low cool ambient. PCK 3,946,516 bytes; gzip payload total 12,402,612 bytes. Previous releases retained. Root backup: `index-before-office.html`. Serial import workaround applied to staging. ## Health + starry sky release: 31564f79b9f5e299 See `docs/HEALTH-SKY.md`: +1 HP/second while alive, capped at 100; Half-Life `space` panorama visible outside windows. PCK 3,938,672 bytes; gzip payload total 12,395,646 bytes. Previous releases retained; root backup `index-before-health-sky.html`. Build encountered an import-thread crash (`propagate_notification` off caller thread, signal 11). Workaround applied only to generated staging `project.godot`: `[editor] import/use_multiple_threads=false`; serial re-import and export succeeded. Upstream similar issue: https://github.com/godotengine/godot/issues/96242 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// 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. Retention policy after cleanup on 2026-10-11: keep the live release plus the immediately preceding one; the eight older directories (fd65770c62e811fe, 31564f79b9f5e299, ee476ce70108b55d, 6de9bbe8915fd82e, ab6054111f5919a8, 4e181f0fe5d02cc9, 7d55da56077f8eee, 7d3c1fb9d96da215) were removed with explicit approval. Releases went 527 MB -> 108 MB and free space on the bastion 1.1 GB -> 1.5 GB. Deleting a release makes the matching `index-before-*.html` backup unreproducible; those files are references, not served pages. Verify after any cleanup: root still200 with the live base href, live PCK byte-identical, previous release still served, removed release returns404. 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. ## Verified release 6de9bbe8915fd82e (touch controls) Adds the on-screen touch controls (docs/TOUCH-CONTROLS.md). - PCK: 3,632,524 -> 3,641,096 bytes; all payloads 43,449,671 -> 12,098,440 gzip. - 14 files uploaded as a new immutable directory, remote sha256 identical to local; previous releases kept. Root index.html swapped by atomic rename, backup /home/debian/zombie-fps-backups/index-before-touch.html. - HTTPS: root 200 with the new base href; PCK byte-identical after gzip decode. - Browser (Android user agent): the touch layer is created automatically, the start overlay switches to "TOCCA PER INIZIARE" and the first tap starts the game; resources are fetched from the versioned directory (js 68,957 B, wasm 10,074,964 B, pck 1,949,875 B gzip on the wire). - Cannot be verified from this workstation: real finger input. Firefox desktop has no Touch/TouchEvent and no touch emulation (emulation.setTouchOverride is not implemented), so the touch event path was verified in Godot instead, which feeds the very same InputEventScreenTouch/Drag events that display_server_web.cpp produces.