The flick shows itself, the width is a dial, the blood stays on
Three asks. The placement ring: an aimed rally hit stamps a gold ring that fades where the shot is going — flick, watch it bite the corner, learn the cause. Not stamped for unaimed hits (marking scatter would teach noise), gone in 0.8s (a permanent ring reads as a target rather than feedback). The opponent's width is the ladder's third dial. Its returns scatter off aimError x (1 - quality), which painted lines from game one. matchWideBase 2.1 keeps opponent one inside what an auto-run player can cover — measured, 25 returns all inside +-2.2m — and matchWidePer earns 0.4m per rung, so line-painting is the late ladder. Speed, reliability, width: three dials. The blood vignette replaces the temporary border. The old wash flashed 0.3s and vanished, so between hits the screen said you were fine. A radial vignette reads G.hp directly now — its depth is your damage fraction, permanent until healed, hit pulse riding on top. Global: the hp channel is global. Same no-HP-bar philosophy one layer out; at 1 hp the frame is properly blood-rimmed and the court centre stays legible. 44 tests, 12 seeds clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d6df582cd4
commit
8ccf0a8fad
7 files changed
+81
-9
No files matched your search
@@ -146,6 +146,20 @@ function doHit(ch, quality, aim) {
|
||||
tz = (toFar ? tzBase : COURT.L - tzBase) + err() * 0.8;
|
||||
}
|
||||
tx = Math.max(-5, Math.min(5, tx));
|
||||
if (G.match) {
|
||||
// THE OPPONENT'S WIDTH IS THE LADDER'S THIRD DIAL. Its returns scatter off aimError x (1 - 0.55
|
||||
// quality), which paints lines an auto-run player cannot reach — "still a LOT of unreachable
|
||||
// balls". Opponent one keeps its returns inside matchWideBase of the centre line; each rung
|
||||
// earns matchWidePer more width, so the line-painting is the LATE ladder, not the opening one.
|
||||
if (ch.side !== 'P') {
|
||||
const wide = TUNE.matchWideBase + (G.match.opp - 1) * TUNE.matchWidePer;
|
||||
tx = Math.max(-wide, Math.min(wide, tx));
|
||||
}
|
||||
// THE PLACEMENT, MADE VISIBLE: your aimed rally hit stamps its landing point, and render draws a
|
||||
// fading ring there — flick, see the ring bite the corner, learn the cause. Only when a flick or
|
||||
// held direction actually aimed it: an unaimed hit marking its scatter would teach noise.
|
||||
else if (aim && (aim.ax || aim.az || aim.fx !== undefined)) G.placeMark = { x: tx, z: tz, t: 0.8 };
|
||||
}
|
||||
// topspin: quality shots dive under extra effective gravity, which is what lets real
|
||||
// pace arc over the net and still land in — the core tennis trade-off
|
||||
const g = TUNE.gravity * (1 + quality * TUNE.spinFactor);
|
||||
@@ -1235,6 +1249,7 @@ export function update(dt) {
|
||||
// died, so the biggest gore burst in the game happened underneath it and nobody ever saw it.
|
||||
// BELOW the modal return, like the flash: a card raised in the same beat as a popup used to
|
||||
// burn its whole life dimmed-out underneath it — now it holds and plays AFTER the dismissal.
|
||||
if (G.placeMark && (G.placeMark.t -= dt) <= 0) G.placeMark = null; // the placement ring fades
|
||||
if (G.card) {
|
||||
if (G.card.wait > 0) G.card.wait -= dt;
|
||||
else { G.card.t -= dt; if (G.card.t <= 0) G.card = null; }
|
||||
|
||||
Reference in new issue
Block a user