Give the run strip a panel, round the rack, fix the duel's hint

The NEXT/GAME row was bare text on the court. That was legible over one dark
green lawn and is not over a sunlit clay crowd or a floodlit blue one, so it
gets its own rounded panel — the same thing the scoreboard art above it already
relies on. It states the sets banked now too; they had only ever appeared in the
scoreboard's own per-row column.

The weapon rack's five slots are rounded, which is what they are: buttons.

The duel is played under tennis rules — no slingshot, no rack, serve to the box
(see the !G.interlude guards in solveShot and the aim reticle) — but the bottom
hint was still telling you to pull back to aim, which is the one thing that does
nothing there. It reads as a tennis serve now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-01 12:21:53 +02:00
1 parent af30db2ff3
commit 60f7ce2cf1
2 files changed
+28 -8

No files matched your search

+27 -7
View File
@@ -208,6 +208,14 @@ function quadXZ(x1, z1, x2, z2, color) { // filled court-plane rect (screen trap
ctx.fillStyle = color; ctx.beginPath();
ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.lineTo(c.x, c.y); ctx.lineTo(d.x, d.y); ctx.fill();
}
// Rounded panel. ctx.roundRect is native everywhere this game runs (Safari 16.4+), and the square
// fallback only matters on a browser that could not render the rest of this anyway.
function panel(x, y, w, h, r, fill, stroke) {
ctx.beginPath();
if (ctx.roundRect) ctx.roundRect(x, y, w, h, r); else ctx.rect(x, y, w, h);
if (fill) { ctx.fillStyle = fill; ctx.fill(); }
if (stroke) { ctx.strokeStyle = stroke; ctx.stroke(); }
}
function drawShadow(px, py, rx, ry, alpha = 0.28) {
ctx.fillStyle = `rgba(0,0,0,${alpha})`;
ctx.beginPath(); ctx.ellipse(px, py, rx, Math.max(1, ry), 0, 0, 7); ctx.fill();
@@ -601,8 +609,10 @@ function drawRack() {
ctx.save(); ctx.lineWidth = 1;
for (let i = 0; i < g.n; i++) {
const x = g.x0 + i * g.tw, sel = G.weapon === i, dim = stock[i] <= 0 && i > 0;
ctx.fillStyle = sel ? '#123a24' : '#0a2418'; ctx.fillRect(x + 1, g.y0 + 1, g.tw - 2, g.h - 2); // sunk slot
ctx.strokeStyle = sel ? UI.pick : '#05160e'; ctx.strokeRect(x + 1.5, g.y0 + 1.5, g.tw - 3, g.h - 3); // gold frame when live
// rounded slots: five separate tiles read as five buttons, which is what they are
panel(x + 1.5, g.y0 + 1.5, g.tw - 3, g.h - 3, 4,
sel ? '#123a24' : '#0a2418', // sunk slot
sel ? UI.pick : '#05160e'); // gold frame when live
// icon: left, vertically centred, padded off the edge
const box = g.h - pad * 2, icx = x + pad + box / 2, icy = g.y0 + g.h / 2;
ctx.globalAlpha = dim ? 0.35 : 1;
@@ -1009,12 +1019,19 @@ export function render() {
ctx.font = F(8); ctx.fillStyle = bd.cream; ctx.fillText(String(score.games[i]), 90, cy); // games
ctx.font = F(8); ctx.fillStyle = svr ? bd.gold : bd.cream; ctx.fillText(ptTxt(i), 126, cy); // points, in the art's slot
}
if (splat) { // which game you're in, permanently — the 1.1s GAME N flash was the only place it showed
if (splat) {
// This row used to be bare text on the court. That was fine over one dark green lawn and is
// unreadable over a sunlit clay crowd or a floodlit blue one, so it gets its own panel — the
// same trick the scoreboard art above it already relies on.
ctx.lineWidth = 1;
panel(0.5, bH + 1.5, bW - 1, 12, 3, 'rgba(8,26,18,0.82)', hexA(bd.hi, 0.35));
// which game you're in, and the sets banked, permanently — the 1.1s GAME N flash and the
// scoreboard's own per-row sets column were the only places either showed.
ctx.textAlign = 'right'; ctx.font = F(6, 0); ctx.fillStyle = bd.dim;
ctx.fillText('GAME ' + (G.runGames + 1), bW, bH + 7);
ctx.fillText(`GAME ${G.runGames + 1} · ${G.sets[0]} SET${G.sets[0] === 1 ? '' : 'S'}`, bW - 3, bH + 7);
}
if (splat && TUNE.killPips && TUNE.killsPerPoint > 1) { // kills-to-next-point pips, just under the board — no box
ctx.textAlign = 'left'; ctx.font = F(6, 0); ctx.fillStyle = bd.dim; ctx.fillText('NEXT', 2, bH + 7);
if (splat && TUNE.killPips && TUNE.killsPerPoint > 1) { // kills-to-next-point pips, on the panel above
ctx.textAlign = 'left'; ctx.font = F(6, 0); ctx.fillStyle = bd.dim; ctx.fillText('NEXT', 4, bH + 7);
for (let k = 0; k < TUNE.killsPerPoint; k++) {
ctx.beginPath(); ctx.arc(32 + k * 8, bH + 7, 2.4, 0, 7);
if (k < G.killCount) { ctx.fillStyle = bd.gold; ctx.fill(); }
@@ -1068,7 +1085,10 @@ export function render() {
ctx.fillStyle = UI.warm; ctx.font = F(8);
const touch = IS_TOUCH, switchHint = touch ? 'TAP THE RACK TO SWITCH' : '1-5 TO SWITCH';
const sel = ['PLAIN', 'BOMB', 'TRIPLE', 'FIRE', 'GUN'][G.weapon];
const txt = G.mode === 'splatter'
// The DUEL is played under tennis rules — no slingshot, no rack, serve to the box (see the
// !G.interlude guards in solveShot and the aim reticle). It was still being told to pull back
// to aim, which is the one thing that does nothing there.
const txt = G.mode === 'splatter' && !G.interlude
? (G.weapon === 4 ? (touch ? 'TAP TO UNLOAD THE GUN' : 'SPACE UNLOADS THE GUN')
: G.aim ? 'PULL BACK: FURTHER = HARDER'
: G.weapon > 0 ? `SERVE FIRES: ${sel} · ${switchHint}`