WIP splatter feedback round + HANDOFF.md for session continuation
Done (unverified): faster serve cadence in splatter (overT 0.25, serve from where you stand, flick = instant aimed quick serve), aim-anywhere targeting relative to the player. Not done: zombie speed ramp (G.splatT exists but is never incremented), eaten-on-touch death + restart screen. Full state and plans in HANDOFF.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
60c5f72a0a
commit
e7aab295d4
4 files changed
+140
-15
No files matched your search
+25
-11
@@ -25,13 +25,16 @@ export function predictLanding() { // first-bounce point, closed form — exact,
|
||||
// the best-matching zombie anywhere on the court — including your own side of the net.
|
||||
function zombieTarget(aim) {
|
||||
if (!zombies.length) return null;
|
||||
const lane = (aim ? aim.ax : 0) * 3.3;
|
||||
const band = aim && aim.az !== 0 ? (aim.az < 0 ? 4 : 19) : null;
|
||||
const none = !aim || (!aim.ax && !aim.az);
|
||||
let best = null, bs = 1e9;
|
||||
for (const zb of zombies) {
|
||||
let s = Math.abs(zb.x - lane);
|
||||
if (band !== null) s += Math.abs(zb.z - band) * 0.5;
|
||||
if (!aim) s = COURT.L - zb.z; // no aim (serves): the nearest one is the most urgent
|
||||
let s;
|
||||
if (none) s = COURT.L - zb.z; // no direction: the most advanced one is the most urgent
|
||||
else {
|
||||
s = Math.abs(zb.x - (P.x + aim.ax * 4)); // lane relative to where YOU stand
|
||||
if (aim.az < 0) s += zb.z * 0.35; // flick up: prefer the far ones
|
||||
if (aim.az > 0) s += (COURT.L - zb.z) * 0.35; // flick down: the close ones on your side
|
||||
}
|
||||
if (s < bs) { bs = s; best = zb; }
|
||||
}
|
||||
return best;
|
||||
@@ -93,11 +96,11 @@ function tossBall(sv) {
|
||||
}
|
||||
export function serveApexY() { return TOSS.y0 + TOSS.vy * TOSS.vy / (2 * TUNE.gravity); } // ~2.95
|
||||
|
||||
function hitServe(sv, quality) {
|
||||
function hitServe(sv, quality, aim = null) {
|
||||
const toFar = sv.side === 'P';
|
||||
const s = TUNE.serveSpeed + quality * TUNE.serveQualitySpeed;
|
||||
let boxX, tzs;
|
||||
const zt = G.mode !== 'tennis' ? zombieTarget(null) : null;
|
||||
const zt = G.mode !== 'tennis' ? zombieTarget(aim) : null;
|
||||
if (zt) { // splatter serves auto-track the most advanced zombie
|
||||
boxX = zt.x + (Math.random() * 2 - 1) * (1 - quality) * 0.8;
|
||||
const T0 = Math.hypot(zt.x - ball.x, zt.z - ball.z) / s;
|
||||
@@ -290,7 +293,18 @@ function trySwing(ch, isAI) {
|
||||
export function keyPressed(code) {
|
||||
if (code !== 'Space') return;
|
||||
audio(); // unlock on first gesture
|
||||
if (G.phase === 'pretoss' && score.server === 'P') { tossBall(P); return; }
|
||||
if (G.phase === 'pretoss' && score.server === 'P') {
|
||||
if (G.mode === 'splatter') {
|
||||
const flick = takeFlickAim();
|
||||
if (flick) { // quick serve: no toss ritual, modest pace, aimed by the flick
|
||||
ball.active = true; ball.collected = false; ball.bounces = 0; ball.firstLand = null;
|
||||
ball.x = P.x + 0.35; ball.z = P.z; ball.y = 2.2; ball.g = TUNE.gravity;
|
||||
hitServe(P, 0.62, flick);
|
||||
return;
|
||||
}
|
||||
}
|
||||
tossBall(P); return;
|
||||
}
|
||||
if (G.phase === 'toss' && score.server === 'P') {
|
||||
// serve timing: distance from apex = quality
|
||||
const dy = Math.abs(ball.y - serveApexY());
|
||||
@@ -412,9 +426,9 @@ export function update(dt) {
|
||||
onBounce();
|
||||
if (ball.active && Math.abs(ball.vy) < 0.8) { // rolled out — dead ball
|
||||
ball.active = false;
|
||||
if (G.mode === 'splatter') { // hand the server a fresh one
|
||||
G.phase = 'over'; G.overT = 0.7; sendBoy();
|
||||
if (Math.random() < 0.2) voice('new_balls', 0.3);
|
||||
if (G.mode === 'splatter') { // hand the server a fresh one, fast — they're coming
|
||||
G.phase = 'over'; G.overT = 0.25; sendBoy();
|
||||
if (Math.random() < 0.12) voice('new_balls', 0.3);
|
||||
} else if (ball.bounces === 1) pointTo(ball.lastHit, null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user