The staff learn your habits, the walk-on waits for your tap, and the club opens a practice court
Four answers to one phone report: - THE READ: repeat a lane and the opponent anticipates it; break the habit and it is wrong-footed for that ball — scaled by rung, so surprise is the weapon that opens the ladder past the HEAD COACH (TUNE block THE READ). - The opponent walk-on is a real modal now (head, punchline, tap to dismiss) in arcade, tournament and training — readable without a clock. - THE TRAINING COURT: third card under ZOMBIE TENNIS, pick any rung, one game at a time, the felled body walks straight back on, nothing banks, bleeding out is a popup instead of a death. mode_train.png rolled in the banner style. - MOVEMENT READS: sprinting feet kick per-court dust and fast bodies leave a two-ghost speed streak (trailMin gates both; feel verdict pending). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a42dc6fc81
commit
43784c5b33
9 files changed
+360
-43
No files matched your search
+26
-3
@@ -1,7 +1,7 @@
|
||||
// Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state).
|
||||
|
||||
import { cv, ctx } from './dom.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, racketStage, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS, HEAD_CROP, CUP_ROUNDS, TINT, SHOT_DEPTH } from './config.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, racketStage, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS, HEAD_CROP, CUP_ROUNDS, TINT, SHOT_DEPTH, matchFoe } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, kit, skin } from './state.js';
|
||||
import { RAISERS, FRAMES } from './crowd.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock, serveApexY, rallyFeel, swayOf } from './gameplay.js';
|
||||
@@ -805,9 +805,21 @@ function drawMachine(zb) {
|
||||
}
|
||||
|
||||
const p0 = zb => proj(zb.x, 0, zb.z).s; // projected scale, for effects that need pixels not metres
|
||||
function drawZombie(zb) {
|
||||
function drawZombie(zb, ghost) {
|
||||
if (zb.variant === 'o') return drawBarrel(zb);
|
||||
if (zb.variant === 'm') return drawMachine(zb);
|
||||
// MOVEMENT READS: a body genuinely sprinting (only racketAI writes vx/vz — the shamble never
|
||||
// crosses trailMin) leaves two ghosts sampled back along its own velocity, the modal masthead's
|
||||
// speed streak applied to a body. Ghosts draw first so the live sprite lands on top, and never
|
||||
// during a named frame — a collapsing corpse must not smear on stale velocity.
|
||||
if (!ghost && TUNE.trailMin && !zb.showFrame
|
||||
&& Math.hypot(zb.vx || 0, zb.vz || 0) > TUNE.trailMin) {
|
||||
for (const [k, a] of [[2, 0.10], [1, 0.22]]) {
|
||||
ctx.save(); ctx.globalAlpha *= a;
|
||||
drawZombie({ ...zb, x: zb.x - zb.vx * TUNE.trailK * k, z: zb.z - (zb.vz || 0) * TUNE.trailK * k }, true);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
const rk = zb.variant === 'r';
|
||||
// THE MATCH OPPONENT HAS ITS OWN SPRITES NOW. It used to borrow the turned CPU's ('c') — a body
|
||||
// drawn to SHAMBLE at the camera with its racket hanging at its side, which is what was playing
|
||||
@@ -1782,10 +1794,19 @@ export function drawLook(cv2, t, k, sk) {
|
||||
const bounceDx = () => tour.dx ?? BOUNCE_IDLE.dx;
|
||||
const bounceApex = () => tour.apex ?? BOUNCE_IDLE.apex;
|
||||
|
||||
function drawChar(ch) {
|
||||
function drawChar(ch, ghost) {
|
||||
// No split-step. The receiver used to be slid 0.12 m side to side while waiting on a serve, on top
|
||||
// of a swap to the run frames; both are gone. Sliding a sprite sideways is not footwork — it reads
|
||||
// as the man skating — and the idle pair's own weight shift is the whole of what he should be doing.
|
||||
// MOVEMENT READS: the same two-ghost speed streak the bodies wear (see drawZombie) — gameplay's
|
||||
// mover writes P.vx/vz, so only a genuine sprint (auto-run chase, horde steer) ever draws one.
|
||||
if (!ghost && TUNE.trailMin && Math.hypot(ch.vx || 0, ch.vz || 0) > TUNE.trailMin) {
|
||||
for (const [k, a] of [[2, 0.10], [1, 0.22]]) {
|
||||
ctx.save(); ctx.globalAlpha *= a;
|
||||
drawChar({ ...ch, x: ch.x - ch.vx * TUNE.trailK * k, z: ch.z - ch.vz * TUNE.trailK * k }, true);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
const p = proj(ch.x, 0, ch.z);
|
||||
// the readability boost is what makes the MATCH OPPONENT legible: at the far baseline he was
|
||||
// ~34 logical px of faithful anatomy, which is mush. The near player anchors the curve (farB
|
||||
@@ -2822,6 +2843,8 @@ export function render() {
|
||||
// the cup names the ROUND — its opp is a drawn rung, so the deathmatch arithmetic lied here
|
||||
ctx.fillText(G.match && G.match.cup
|
||||
? `${CUP_ROUNDS[G.match.cup.round].name} · ${G.match.cup.round} FELLED`
|
||||
// training: the ladder is parked, so "opponent N · felled" would lie — name the rep instead
|
||||
: G.match && G.match.train ? `TRAINING · ${matchFoe(G.match.opp).short}`
|
||||
: G.match ? `OPPONENT ${G.match.opp} · ${G.match.opp - 1} FELLED`
|
||||
: G.classic ? `SET ${G.sets[0] + G.sets[1] + 1} · FIRST TO ${TUNE.classicSets}`
|
||||
: `GAME ${G.runGames + 1} · ${G.sets[0]} SET${G.sets[0] === 1 ? '' : 'S'}`, bW - 3, bH + 8);
|
||||
|
||||
Reference in new issue
Block a user