Files
splatter-tennis/main.js
T
wuwiandClaude Fable 5 69e3a0c310 Zombie town: the match continues
Win your first game and the applause cuts off mid-clap. The palette drains,
the umpire lowers his voice ('Quiet... please'), your opponent turns — torn
red polo, dragging his racket — and shamblers come over the billboard wall.
The scoreboard rewrites CPU to THEM. 'Play.'

- zombies.js: shamblers walk your way; quality shots splatter them (gore
  decal + particle burst + wet synth splat), weak sitters only stagger them
  back; one crossing your baseline is a point for THEM
- tennis scoring machinery keeps running untouched: fifteen, thirty, deuce,
  and 'Game, set... massacre' on a game win, all in recorded umpire English
- ball boys mop the gore they pass (their destiny fulfilled)
- transition: palette lerp, scripted spawns incl. the turned opponent,
  new recorded lines (quiet_dark, remain_seated, play, new_balls)
- moan ambience, drained splatter palette, all zombie params in TUNE

Verified: transition triggers on first game win, splat/stagger/baseline
scoring all correct, dead-ball flow feeds fresh serves, tennis mode
regression clean (6/6 returns, 6/6 points resolved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:26:49 +02:00

39 lines
1.6 KiB
JavaScript

// Splatter Tennis — boot, main loop, intro overlay, and the window.T test hooks.
// World units: meters. z axis = court depth, 0 at FAR baseline, 23.77 at NEAR baseline.
// Module map: config (tuning/constants) · state (mutable game state) · gameplay (logic/update)
// render (camera/drawing) · input (keys/touch) · audio (sfx/umpire) · panel (T tuning UI)
import { TUNE } from './config.js';
import { P, O, ball, score, G, setupPoint } from './state.js';
import { update, keyPressed, solveShot, inServeBox, inCourt } from './gameplay.js';
import { fitViewport, render, proj } from './render.js';
import { voice } from './audio.js';
import { zombies, spawnZombie } from './zombies.js';
import './input.js';
let last = performance.now();
function frame(now) {
const dt = Math.min(0.033, (now - last) / 1000); last = now;
update(dt); render();
requestAnimationFrame(frame);
}
fitViewport();
setupPoint();
// intro overlay: first tap dismisses it, unlocks audio (iOS needs the gesture) and starts play
const intro = document.getElementById('intro');
function dismissIntro() {
if (!intro.parentNode) return;
intro.remove(); voice('quiet_please');
}
intro.addEventListener('pointerdown', e => { e.preventDefault(); dismissIntro(); });
addEventListener('keydown', () => dismissIntro(), { once: true });
requestAnimationFrame(frame);
// exposed for in-browser sanity checks and simulated playtests
window.T = { TUNE, ball, P, O, score, G, zombies, spawnZombie, solveShot, inServeBox, inCourt, proj, setupPoint,
step: dt => update(dt), press: keyPressed,
get phase() { return G.phase; }, set phase(v) { G.phase = v; } };