The court keeps no score: the landing ring is gone

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Fable 5 committed 2026-08-10 22:05:32 +02:00
1 parent 8307a05f79
commit 9fab26eec9
4 files changed
+6 -19

No files matched your search

+3 -15
View File
@@ -163,17 +163,9 @@ function doHit(ch, quality, aim) {
ball.bomb = false; ball.flame = false; ball.wid = 'ball'; // only a consuming serve arms these; any other hit clears them ball.bomb = false; ball.flame = false; ball.wid = 'ball'; // only a consuming serve arms these; any other hit clears them
ball.chain = 0; // ...and the slice dies with the shot that earned it ball.chain = 0; // ...and the slice dies with the shot that earned it
ball.praise = null; // a re-hit is a new shot: a stale armed word must not pop on ITS impact ball.praise = null; // a re-hit is a new shot: a stale armed word must not pop on ITS impact
if (ch.side === 'P') { // shot marker: where this will land — red means it's going out // (the landing ring that used to be stamped here confused testers — the umpire calls the ball,
// TENNIS ONLY. It answers "did that land in?", and splatter does not ask: `out` could never be // the court stays clean)
// true there, so it was a permanent yellow ring on the grass carrying no information — and if (ch.side === 'P') G.aiReactT = 0; // CPU hasn't read the shot yet
// sitting under the shambler for 1.3 s, which is longer than the gap before you aim again.
// That ring, not the new mark, is the circle that seemed to live under their feet.
if (G.mode === 'tennis') {
const ok = Math.abs(tx) <= COURT.W / 2 + 0.02 && tz >= -0.02 && tz <= COURT.netZ;
G.outMarker = { x: tx, z: tz, out: !ok, t: 1.3 };
}
G.aiReactT = 0; // CPU hasn't read the shot yet
}
sfx(ch.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded racket strike, per side sfx(ch.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded racket strike, per side
if (quality > 0.999 && ch.side === 'P') { // the rally hit: a real 45 ms window (perfectMs) if (quality > 0.999 && ch.side === 'P') { // the rally hit: a real 45 ms window (perfectMs)
G.hitstop = 0.05; pock(0.5, 1600); G.hitstop = 0.05; pock(0.5, 1600);
@@ -367,9 +359,6 @@ function hitServe(sv, quality, aim = null, perfect = quality > 0.999) {
} }
} }
// (spendSlot already hands you back plain balls when the last one goes) // (spendSlot already hands you back plain balls when the last one goes)
// tennis only, same reason as doHit's: a splatter serve lands at a shambler's feet, and the ring
// sat there in yellow for 1.3 s saying nothing — right where the aim mark is trying to be read.
if (sv.side === 'P' && G.mode === 'tennis') G.outMarker = { x: boxX, z: tzs, out: !inServeBox(boxX, tzs), t: 1.3 };
G.phase = 'rally'; sfx(sv.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded serve strike, per side G.phase = 'rally'; sfx(sv.side === 'P' ? 'tennis-player' : 'tennis-opponent', 0.9, { reverb: true }); // recorded serve strike, per side
sv.pose = 'serve'; sv.poseT = 0; sv.pose = 'serve'; sv.poseT = 0;
if (sv.side === 'P') G.tossDrops = 0; // a served toss ends the apex lesson if (sv.side === 'P') G.tossDrops = 0; // a served toss ends the apex lesson
@@ -1657,7 +1646,6 @@ export function update(dt) {
// ball boy: sprint across, scoop the ball in passing, despawn off the far side // ball boy: sprint across, scoop the ball in passing, despawn off the far side
const boy = G.boy; const boy = G.boy;
if (boy) boy.mop ? mopRound(boy, dt) : fetchRound(boy, dt); if (boy) boy.mop ? mopRound(boy, dt) : fetchRound(boy, dt);
if (G.outMarker && (G.outMarker.t -= dt) <= 0) G.outMarker = null;
if (G.phase === 'over') { if (G.phase === 'over') {
G.overT -= dt; G.overT -= dt;
+1 -1
View File
@@ -398,6 +398,6 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="main.js?v=143"></script> <script type="module" src="main.js?v=144"></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -1620,14 +1620,14 @@ export function render() {
} }
drawPiles(false); // far-side piles (occluded by the net); near-side piles drawn after the net drawPiles(false); // far-side piles (occluded by the net); near-side piles drawn after the net
// landing rings: white = incoming ball, yellow/red = your shot (red lands out) // landing ring: white = incoming ball (a PREDICTION, gone the moment the ball touches down —
// the ring that lingered where the ball had already landed confused testers and is removed)
if (G.phase === 'rally' && ball.active && ball.bounces === 0 && ball.lastHit === 'O') { if (G.phase === 'rally' && ball.active && ball.bounces === 0 && ball.lastHit === 'O') {
const l = predictLanding(); const l = predictLanding();
// the ring CONTRACTS toward touchdown (predictLanding's closed-form t), so the static marker // the ring CONTRACTS toward touchdown (predictLanding's closed-form t), so the static marker
// becomes a timing cue: wide while the ball is high, snapping to its true size as it arrives // becomes a timing cue: wide while the ball is high, snapping to its true size as it arrives
if (l) drawRing(l.x, l.z, 'rgba(255,255,255,0.85)', Math.min(2.2, 0.55 + l.t * 1.5)); if (l) drawRing(l.x, l.z, 'rgba(255,255,255,0.85)', Math.min(2.2, 0.55 + l.t * 1.5));
} }
if (G.outMarker) drawRing(G.outMarker.x, G.outMarker.z, G.outMarker.out ? UI.hot : UI.warm);
// SLINGSHOT AIM: the shot's actual flight, drawn as a band through the air. // SLINGSHOT AIM: the shot's actual flight, drawn as a band through the air.
// It replaces a chevron trail on the ground plus a draw meter across the bottom. The trail was // It replaces a chevron trail on the ground plus a draw meter across the bottom. The trail was
// a RAY — it pointed at the target but said nothing about the ball going over the net — and the // a RAY — it pointed at the target but said nothing about the ball going over the net — and the
-1
View File
@@ -51,7 +51,6 @@ export const G = {
boomFlash: 0, // s of white flash frame left (bomb impact) boomFlash: 0, // s of white flash frame left (bomb impact)
lastOrderT: -9, // idleT of the last 'Order' call — bomb chains mustn't spam the umpire lastOrderT: -9, // idleT of the last 'Order' call — bomb chains mustn't spam the umpire
idleT: 0, idleT: 0,
outMarker: null, // {x, z, out, t} — where your last shot lands
boy: null, // ball boy sprinting across: {x, z, dir, runT} boy: null, // ball boy sprinting across: {x, z, dir, runT}
aiServeT: 0, aiServeT: 0,
aiSwingCool: 0, aiSwingCool: 0,