The short ball gets chased, intercept gets legs, the vignette gets corners

Second recorded session: 5 wins and a deuce war to 5-6 — the mode is a
contest — but all seven losses shared one signature: the ball died in court
1.4-2m short of him while he ran 0.2-0.5m. Three defects:

The pace cut created dying rollers. Slow returns bounce and die at z 18-20,
outside intercept's near zone, so nothing chased them, and the rolled-dead
rule hands the point to the zombie. They also arrive at his feet below legal
contact height — the "ball arrives right at the player" feel. intercept keeps
an anyHit candidate now: the earliest reachable point on his half where the
ball is still at hitting height, so the assist runs forward onto the short
ball. Verified: he sprints z 23 -> 16.4 and meets it at gap ~0.

intercept computed reachability with autoAssist — 0 on any fine pointer — so
a desktop match's planner answered "home" every time. He had legs, his
destination was always the baseline. assistK() is shared by mover and
planner now.

And the vignette was a radial gradient in a portrait canvas: the circle
reaches the corners but hits the side edges at fraction zero — "red pulsing
from below" was the bottom edge being the only place it got close. Four
linear edge fades now, hugging the rectangle on any aspect.

48 tests, 12 seeds clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-11 23:36:15 +02:00
1 parent 3a789eb2e9
commit f37fb93875
5 files changed
+93 -16

No files matched your search

+19 -9
View File
@@ -1971,16 +1971,26 @@ export function render() {
const dmg = 1 - Math.max(0, G.hp) / (G.hpMax || 1);
const pulse = G.hurtFlash > 0 ? Math.min(1, G.hurtFlash / 0.3) * 0.3 : 0;
if (dmg > 0 || pulse > 0) {
// SQRT of the damage, not the damage: the match pool is 8, so a linear curve made the first
// hit a 10% alpha nobody can see — "the vignette is basically invisible". sqrt front-loads it:
// hit one on 8 hp reads at ~0.3 alpha, and it still deepens all the way to the cap. The inner
// radius tightens with damage too, so deep wounds shrink the clean centre.
// FOUR EDGE FADES, not a radial. A radial gradient in a tall portrait canvas reaches the
// corners but arrives at the side edges at fraction ZERO — Oliver saw "a slight red pulsing
// from below" because the bottom edge was the only place the circle got close. Linear fades
// hug the actual rectangle on any aspect. sqrt(dmg) front-loads the curve (the match pool is
// 8, so linear made hit one an invisible 10%), and the inset DEEPENS with damage so wounds
// shrink the clean centre.
const a = Math.min(0.85, Math.sqrt(dmg) * 0.8 + pulse);
const g = ctx.createRadialGradient(W / 2, H / 2, Math.min(W, H) * (0.5 - dmg * 0.22),
W / 2, H / 2, Math.max(W, H) * 0.7);
g.addColorStop(0, 'rgba(120,8,12,0)');
g.addColorStop(1, `rgba(120,8,12,${a.toFixed(3)})`);
ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);
const col = k => `rgba(120,8,12,${(a * k).toFixed(3)})`;
const D = Math.round(Math.min(W, H) * (0.2 + 0.32 * dmg));
for (const [x0, y0, x1, y1, w, h, gx, gy] of [
[0, 0, 0, D, W, D, 0, 1], // top
[0, H, 0, H - D, W, D, 0, -1], // bottom
[0, 0, D, 0, D, H, 1, 0], // left
[W, 0, W - D, 0, D, H, -1, 0], // right
]) {
const g = ctx.createLinearGradient(x0, y0, x1, y1);
g.addColorStop(0, col(1)); g.addColorStop(1, col(0));
ctx.fillStyle = g;
ctx.fillRect(gx < 0 ? W - D : 0, gy < 0 ? H - D : 0, w, h);
}
}
}