diff --git a/Dockerfile b/Dockerfile index 7027d21..dff3b79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ FROM nginx:alpine # no-cache => browsers revalidate every request (ETag 304s are cheap), so a new # deploy is picked up on plain reload. ponytail: switch to hashed filenames if traffic ever matters. -RUN printf 'server {\n listen 80;\n root /usr/share/nginx/html;\n etag on;\n location / { add_header Cache-Control "no-cache, must-revalidate"; }\n}\n' > /etc/nginx/conf.d/default.conf -COPY index.html *.js /usr/share/nginx/html/ +# The exact-match location gives the manifest its own MIME type — nginx's default map has no +# .webmanifest entry and Chrome refuses to install a homescreen app served as octet-stream. +# (a bare `types {}` block would have REPLACED the whole map and broken .js/.png too.) +RUN printf 'server {\n listen 80;\n root /usr/share/nginx/html;\n etag on;\n location = /manifest.webmanifest { default_type application/manifest+json; add_header Cache-Control "no-cache, must-revalidate"; }\n location / { add_header Cache-Control "no-cache, must-revalidate"; }\n}\n' > /etc/nginx/conf.d/default.conf +COPY index.html manifest.webmanifest *.js /usr/share/nginx/html/ COPY assets /usr/share/nginx/html/assets diff --git a/HANDOFF.md b/HANDOFF.md index 027208e..30af9e2 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -134,8 +134,26 @@ New foundations you must know before touching anything: 0 whiffs** — but you MUST set `T.TUNE.autoAssist = 1.15` first, the Browser pane reports `pointer: fine` so the assist is off and every ball reads as "never reachable". - Brute is 5 hp: worst-case (max ramp) it still needs 22.5s to cross, vs ~7s of serves to kill. -- `TUNE.burnSpread` 1.3 m chains reliably through shoulder-to-shoulder shamblers (~1.0 m apart); - at 1.2 m spacing the tail of a line can survive. It is a T-panel knob if Oliver wants it hotter. + +## Feedback round on the polish (2026-07-27, same day) +- **Fire never spread in real play.** The flame ball has full pierce and instant-kill, so it wiped + its own lane and left nobody to catch. Fixed by `TUNE.burnBallR` — the ball ignites everything it + FLIES PAST, every frame, whether or not it hits — plus wider `burnIgniteR` 4.5 / `burnSpread` 2.2. + One flame serve into a realistic scattered 10-wave now kills 9, with 9 burning at once. + **When testing fire, fire a real ARC**: a flat ball from z=16 hits the net at y≈0.83 (netH 0.914) + and bounces back at you — the first "fire is broken" reading was my test hitting the tape. +- **Wounded brutes need their own animation.** Every wound stage is a single frame and the brute + now spends most of its life wounded, so it slid along like a decal. Rather than 8 more sprites + they get a procedural lumber in `drawZombie` (translate + squash, no rotation, so pixels stay on + the grid). Amplitudes MUST scale with `p.s` — at a fixed fraction the whole lurch was sub-pixel + at range and read as nothing moving. +- **The scoreboard "flicker" was the crowd.** The cheer bounce was `-Math.random()*2` per column + per frame, i.e. TV static, showing through the scoreboard art's transparent ornate edges. Now a + smooth per-column sine on `idleT`. Test for it: render twice WITHOUT stepping — identical pixels + means nothing in the draw path is rolling dice per frame. +- Ball boys: `sendBoy`'s `mop` now DEFAULTS to `G.mode === 'splatter'`. The running fetch-boy still + exists, but only in tennis mode where there is nothing on the grass but a ball. `BOY_H` 1.2 → 1.5. +- `TUNE.burnSpread` is a T-panel knob if Oliver wants fire hotter still. ## Gotchas learned the hard way - Browser-pane verification: python http.server sends no cache headers, so edited ES diff --git a/assets/_sheet_icon.png b/assets/_sheet_icon.png new file mode 100644 index 0000000..8263533 Binary files /dev/null and b/assets/_sheet_icon.png differ diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000..60861b7 Binary files /dev/null and b/assets/favicon.png differ diff --git a/assets/icon-180.png b/assets/icon-180.png new file mode 100644 index 0000000..72dc505 Binary files /dev/null and b/assets/icon-180.png differ diff --git a/assets/icon-192.png b/assets/icon-192.png new file mode 100644 index 0000000..56d10c3 Binary files /dev/null and b/assets/icon-192.png differ diff --git a/assets/icon-512.png b/assets/icon-512.png new file mode 100644 index 0000000..426480b Binary files /dev/null and b/assets/icon-512.png differ diff --git a/config.js b/config.js index daefb83..cf54b50 100644 --- a/config.js +++ b/config.js @@ -30,8 +30,9 @@ export const TUNE = { zSpawnStep: 0.22, // spawn-gap reduction per finished game zMax: 3, // shamblers on court in game 1 (cap grows +1 per game to zMaxCap; 0 = spawns off, for tests) zMaxCap: 14, // absolute shambler cap - changeoverT: 7.0, // s the changeover breather lasts (after odd games, as the rules demand) - boyMopSpeed: 4.0, // m/s a MOPPING ball boy crosses at (a fetching one still sprints at 7.5) + changeoverT: 5.0, // s the changeover breather lasts (after odd games, as the rules demand) + boyMopSpeed: 5.0, // m/s a MOPPING ball boy crosses at (a fetching one still sprints at 7.5). + // Sized so one crossing ~fills the changeover instead of stranding him mid-court. gameBeatT: 1.6, // s the game-end beat lasts on even (non-changeover) games eatenR: 0.9, // m a shambler must close to eat you — matches the visual sprite overlap zSnap: 0.9, // m of lateral slack at zero range — the aim cone's base width (fat-thumb forgiveness) @@ -85,8 +86,10 @@ export const TUNE = { // fire: the flame ball no longer just kills what it touches, it starts an epidemic. burnTime: 3.0, // s a body burns before the fire goes out burnDps: 2.0, // hp lost per second while burning — burnTime*burnDps must exceed bruteHp or tanks walk out of the fire - burnSpread: 1.3, // m — a burning body ignites anything this close, every frame it burns - burnIgniteR: 2.6, // m — the flame ball's ignition radius around the body it kills + burnSpread: 2.2, // m — a burning body ignites anything this close, every frame it burns + burnIgniteR: 4.5, // m — the flame ball's ignition radius around the body it kills + burnBallR: 2.4, // m — the flame ball also lights anything it FLIES PAST. Without this the ball's + // full pierce simply killed the whole lane outright and nothing was left to catch fire. burnPanic: 1.0, // walk-speed multiplier while burning (1 = off; crank it for panicked sprinting) setCardT: 2.0, // s the centered SET card holds (the 'kurze unterbrechung' before the horde) bruteHp: 5, // splat-quality hits (or pellets) a brute ('g') soaks before it dies — one wound skin per stage @@ -117,7 +120,7 @@ COURT.svcNear = COURT.netZ + 6.4; // near service line // world sizes (meters) export const PLAYER_H = 1.88; -export const BOY_H = 1.2; +export const BOY_H = 1.5; // was 1.2 — the mopping boy read as a distant speck at that scale export const UMP_H = 3.0; export const UMP_X = 5.6; // chair right beside the doubles line — x 7.1 projected off-screen in portrait diff --git a/gameplay.js b/gameplay.js index 918a461..8851bfd 100644 --- a/gameplay.js +++ b/gameplay.js @@ -328,7 +328,7 @@ function startChangeover() { // out with the mop, across the near court where the gore is — NOT to the ball: a dead ball // parked off the field (sendBoy's z guard) meant no boy came out at all, and the breather is // now long enough that an empty court reads as the game having stalled. - sendBoy(0, COURT.L * 0.62, true); + sendBoy(0, COURT.L * 0.75, true); // near enough the camera to actually watch him work G.flash = { text: 'CHANGEOVER', t: 1.4 }; // no tins here anymore — weapons are won in the duel, never handed out on a schedule } diff --git a/index.html b/index.html index 7bdbda6..d80b8ee 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,17 @@