// All mutable game state in one place, plus the point-reset that re-arms it. import { HOME } from './config.js'; export const P = { x: 1.3, z: 22.8, side: 'P', pose: 'idle', poseT: 0, swingT: -1, facing: 1, runT: 0 }; export const O = { x: -1.3, z: 1.0, side: 'O', pose: 'idle', poseT: 0, swingT: -1, facing: 1, runT: 0 }; export const ball = { x: 0, y: 1, z: 20, vx: 0, vy: 0, vz: 0, g: 9.2, active: false, lastHit: 'P', bounces: 0, firstLand: null, collected: false }; export const score = { pts: [0, 0], games: [0, 0], server: 'P' }; // pts index 0=P 1=O export const crowd = { cheer: 0, dots: [] }; // dots are (re)built by render.buildCrowd // everything that used to be loose top-level lets export const G = { mode: 'tennis', // tennis | transition | splatter transT: 0, // transition timeline clock tFlags: null, // one-shot markers within the transition hideO: false, // the opponent has... left the match everTransitioned: false, phase: 'pretoss', // pretoss | toss | rally | over serveNum: 1, overT: 0, hitstop: 0, flash: null, // {text, t} idleT: 0, outMarker: null, // {x, z, out, t} — where your last shot lands boy: null, // ball boy sprinting across: {x, z, dir, runT} aiServeT: 0, aiSwingCool: 0, aiReactT: 9, // time since the player's last hit; CPU reads the shot after TUNE.aiReaction wasServe: false, // the ball in flight is a serve (first bounce must hit the box) splatT: 0, // time survived in splatter mode — drives the difficulty ramp eatenT: 0, // time since you were eaten — gates the restart tap }; export const other = s => s === 'P' ? 'O' : 'P'; export const deuceSide = () => (score.pts[0] + score.pts[1]) % 2 === 0; // even points → deuce (right) side export function setupPoint() { G.phase = 'pretoss'; ball.active = false; ball.bounces = 0; ball.firstLand = null; if (G.mode === 'splatter') { // always your serve, from wherever you're standing score.server = 'P'; P.pose = 'idle'; P.swingT = -1; P.facing = 1; return; } const sv = score.server === 'P' ? P : O, rc = score.server === 'P' ? O : P; const sx = deuceSide() ? 1.3 : -1.3; sv.x = sv.side === 'P' ? sx : -sx; // deuce side mirrors across the net sv.z = sv.side === 'P' ? 24.3 : -0.5; rc.x = -sv.x; rc.z = rc.side === 'P' ? 22.5 : HOME.O.z - 1.3; P.pose = O.pose = 'idle'; P.swingT = O.swingT = -1; P.facing = O.facing = 1; // canonical righty stance — bounce/serve sprites must not render mirrored G.aiServeT = score.server === 'O' ? 1.4 : 0; } export function sendBoy(tx = ball.x, tz = ball.z) { // fetch a dead ball — or mop a splat if (tz < -3 || tz > 27) return; // target left the field lengthwise, let it go const dir = tx > 0 ? 1 : -1; // enter from the opposite side so the pickup is mid-run G.boy = { x: dir > 0 ? -13 : 13, z: tz, dir, runT: 0 }; }