Files
splatter-tennis/config.js
T
wuwiandClaude Fable 5 e948e8b36e Aesthetics: 3x render res, flatter lens, 4-frame walk cycles, tiered gore
- RES 2 -> 3: phone screens now DOWNSCALE the canvas (crisp pixels) instead
  of upscaling it (soft). Umpire-tap coords un-hardcoded from RES=2.
- Flatter lens: camZ 36 -> 42 with camF scaled per orientation (406/580) so
  the PLAYER stays the same size — far-court zombies render ~25% bigger and
  finally read as individuals from the baseline.
- Smoother animation: 4-frame walk cycles for both classic shamblers and the
  runner's sprint (new sheets, same style anchor; z_a1-4, z_b1-4, z_f1-4).
  Same cycle frequency — frames subdivide the stride. Render falls back to
  2 frames per variant until its sheet loads.
- Tiered gore (GORE_MUL): the more advanced the zombie, the more absurd the
  mess — classics 1x (base bumped 24 -> 30 particles), runner 1.8x, shield
  2.5x, racket boss 3x, brute 4x (120 particles, a scatter of stains, an
  instant size-3 mound).

Verified: fresh origin, canvas 1440px, umpire triple-tap works at RES 3,
all 4 anim frames cycle at the old stride rate, brute kill = 120 parts /
2 stains / size-3 pile, far-court lineup screenshot clearly readable,
portrait layout numbers correct (810x1440, camF 580), tennis 11/12,
no console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:30:46 +02:00

93 lines
5.3 KiB
JavaScript

// All tuning and constants. TUNE is live-editable in game via the T panel;
// the exported consts below are structural values that code shares but the panel doesn't show.
export const TUNE = {
camZ: 42, camY: 9, camF: 406, horizon: 40, // camF/horizon are per-orientation, set in fitViewport
gravity: 9.2, // slightly floaty vs 9.8 for readability
spinFactor: 0.9, // extra dive on quality shots (fake topspin)
bounceY: 0.58, bounceXZ: 0.78,
playerSpeed: 5.4, aiSpeed: 4.3,
reach: 1.3, aiReach: 1.6, // tight contact radius: the assist positions you, the tap must be honest
swingWindup: 0.06, // s before contact window opens
swingWindow: 0.16, // s contact window length
baseShotSpeed: 16, qualityShotSpeed: 12, // horizontal m/s = base + q*quality; perfect timing = real pace
serveSpeed: 21, serveQualitySpeed: 7,
aimError: 2.4, // m of scatter at quality 0
perfectMs: 45, // |offset from window center| for PERFECT
aiQualityMin: 0.45, aiQualityMax: 0.85, aiFlub: 0.09,
aiReaction: 0.45, // s before the CPU reads your shot — well-aimed corners are winners
autoAssist: matchMedia('(pointer: coarse)').matches ? 1.15 : 0, // auto-run speed factor (mobile)
zSpeed: 0.7, // zombie shamble speed m/s
zSpawnEvery: 3.0, // s between spawns in game 1 — shrinks by zSpawnStep per finished game
zSpawnMin: 1.0, // spawn-gap floor late in a run
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: 4.5, // s the changeover breather lasts (after odd games, as the rules demand)
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: 1.4, // m free-aim magnetism: a zombie this close to your flick's landing point is the target
zSplatQ: 0.5, // min shot quality to splatter; below = stagger pushback
zPushback: 1.4, // m a staggered zombie is knocked back
splatterOverT: 0.1, // s the spent-ball 'over' beat lasts before the next serve is armed
zPierce: 0.75, // pace a ball keeps after a splatter — hot balls plow through lined-up pairs
ballChangeFirst: 3, // game of the first new-balls delivery (real rule: 7, then every 9 — too sparse here)
ballChangeEvery: 4, // games between deliveries after the first
bombMax: 3, // bomb balls in a fresh tin
bombRadius: 2.5, // m blast radius of a bomb ball
hordeLen: 12, // s a horde wave lasts
hordeRate: 2.5, // zombies spawned per second during a horde
mgRate: 6, // machine-gun pellets fired per second during a horde
mgPickupLen: 8, // s the machine-gun tin (4th delivery) auto-fires outside hordes
runnerSpeed: 3.2, // runner ('f') speed multiplier over the shamble
bruteHp: 2, // splat-quality hits (or pellets) a brute ('g') soaks before it dies
shieldDown: 1.5, // s a shield zombie's ('s') chair stays dropped after a block — the kill window
meleeRange: 1.7, // m — a racket swish cleaves shamblers this close to you
racketHp: 3, // splat-quality hits a racket zombie (mini-boss) takes before it dies
racketSpeed: 2.8, // m/s the racket zombie shambles to chase a ball
racketQMax: 0.55, // its return quality is capped here (slower, sloppier than the tennis CPU)
racketFlub: 0.2, // chance the racket zombie whiffs a return
missHud: 1, // show the last miss reason on screen (full log: T.G.missLog)
};
export const COURT = { L: 23.77, W: 8.23, dblW: 10.97, netZ: 11.885, netH: 0.914, netW: 10.06 };
COURT.svcFar = COURT.netZ - 6.4; // far service line
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 UMP_H = 3.0;
// contact rules: outside this height band the ball can't be hit (below = it's past you)
export const CONTACT = { minY: 0.24, maxY: 2.3 };
// volleys (pre-bounce contact) get extra forgiveness: the ball passes at full speed and
// there's no bounce cue to time against, so reach and swing window widen by these factors
export const VOLLEY = { reach: 1.4, window: 1.8 };
// how deep from the far/near baseline a player's interception zone extends
export const ZONE_DEPTH = 9.5;
// recovery positions between shots
export const HOME = { P: { x: 0, z: 22.8 }, O: { x: 0, z: 2.5 } };
// where the near player is allowed to move
export const P_BOUNDS = { x: 6.5, zMin: COURT.netZ + 1.2, zMax: 26 };
// serve toss: release height and upward speed (apex derives from these + gravity)
export const TOSS = { y0: 1.8, vy: 4.6 };
// shot target depth from the receiving baseline, by aim (up = deep, none = mid, down = short)
export const SHOT_DEPTH = { deep: 3.0, mid: 5.4, short: 8.6 };
// how long swing/serve poses hold before returning to idle (s)
export const POSE_TIME = 0.38;
// the server's idle ball bounce: sideways offset to the free hand, apex height, tempo.
// tempo drives the ball, the hand sprite AND the pock sound — one value, always in sync.
export const BOUNCE_IDLE = { dx: 0.5, apex: 0.55, tempo: 3.6 };
// generated sprite sheets: the idle pose is this many pixels tall; all scales derive from it
export const SPRITE_H = 112;