Thumb-scale draw meter, looser aim magnet, a bomb worth the tin
- The draw meter is a fixed bar across the bottom, ~90% of the width and 10px tall, with the kill notch on it. It was a 26x4 stub floating on the apron by the player's feet, which is nothing under a thumb at arm's length. The serve hint is suppressed while pulling since they share the slot. pullFull 95 -> 130: at 95 every pull maxed out by accident, so the meter never meant anything. - The aim magnet took the nearest body ALONG the lane, so a close shambler a little off-axis stole shots clearly aimed past it. It now picks the best ALIGNED body, with TUNE.aimTie of slack in which a nearer one still wins — a straight-ahead lane still eats the nearest body first, which is the settled feel. aimTie: 1 restores the old behaviour exactly, for A/B. - Bomb radius 2.5 -> 4.2 (2 kills -> 5 in a scattered ten-wave), a 120-particle eruption, a ring of thrown stains and a full-size pile. The blinding white frame is now a brief warm bloom — it was hiding the very gore it announced. - Rack tiles 22 -> 30 tall, and the touch band is padded above and runs to the screen edge below. At 22 logical px they were ~30 CSS px on a phone, well under the ~44 a thumb expects; now ~62. 26-game soak clean with heavy bomb use, arrays stay bounded. Serve returns 136/150 = 90.7% +/-2.4%, 30 human-tapped points = 50 hits / 0 whiffs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
55ddf5932e
commit
21b9dd53fa
7 files changed
+90
-35
No files matched your search
@@ -320,15 +320,20 @@ function drawMgRacket() { // horde: the player braces the racket forward and up-
|
||||
// the courtside weapon rack along the bottom: PLAIN + the four tins. Tap a tile (or 1–5) to
|
||||
// pick which one your next serve throws; empty tins dim out. Geometry is shared with input
|
||||
// via rackHit so a tap lands on the tile you see.
|
||||
const RACK_BOT = 10; // px of breathing room below the rack (floats it up off the screen edge)
|
||||
const RACK_BOT = 8; // px of breathing room below the rack (floats it up off the screen edge)
|
||||
const RACK_GRAB = 7; // px the TOUCH band extends above the drawn rack (below, it runs to the screen edge)
|
||||
export const METER_Y = () => H - TUNE.rackH - RACK_BOT - 17; // the draw meter's fixed bottom slot
|
||||
function rackGeom() {
|
||||
const h = TUNE.rackH, n = 5, tw = Math.min(52, (W - 8) / n), rw = tw * n;
|
||||
const h = TUNE.rackH, n = 5, tw = Math.min(64, (W - 4) / n), rw = tw * n;
|
||||
return { x0: Math.round((W - rw) / 2), y0: H - h - RACK_BOT, tw, h, n, rw };
|
||||
}
|
||||
export function rackHit(nx, ny) { // normalized [0,1] canvas coords -> slot index, or -1
|
||||
if (G.mode !== 'splatter' || G.interlude) return -1; // no rack during the tennis-rules duel
|
||||
const g = rackGeom(), cx = nx * W, cy = ny * H;
|
||||
if (cy < g.y0 || cy > g.y0 + g.h || cx < g.x0 || cx >= g.x0 + g.rw) return -1;
|
||||
// The touch band is deliberately BIGGER than the art: up by RACK_GRAB and all the way down to
|
||||
// the screen edge, reclaiming the dead margin below. At 22px tall the tiles were ~30 CSS px on a
|
||||
// phone — well under the 44px everyone's thumb expects — which is why switching tins felt fiddly.
|
||||
if (cy < g.y0 - RACK_GRAB || cx < g.x0 || cx >= g.x0 + g.rw) return -1;
|
||||
return Math.max(0, Math.min(g.n - 1, ((cx - g.x0) / g.tw) | 0));
|
||||
}
|
||||
function drawRack() {
|
||||
@@ -354,9 +359,9 @@ function drawRack() {
|
||||
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
||||
ctx.globalAlpha = dim ? 0.5 : 1;
|
||||
ctx.font = F(8); ctx.fillStyle = sel ? UI.pick : '#e8f0dc';
|
||||
ctx.fillText(i === 0 ? '∞' : '×' + stock[i], tx, g.y0 + pad + 5); // ball = ∞, else ×count
|
||||
ctx.fillText(i === 0 ? '∞' : '×' + stock[i], tx, g.y0 + g.h * 0.33); // ball = ∞, else ×count
|
||||
ctx.font = F(6, 0); ctx.fillStyle = sel ? '#f2ead6' : '#8fa89a';
|
||||
ctx.fillText(labels[i], tx, g.y0 + g.h - pad - 3); // weapon label, under its count
|
||||
ctx.fillText(labels[i], tx, g.y0 + g.h * 0.72); // weapon label, under its count
|
||||
ctx.globalAlpha = 1;
|
||||
if (i === 4 && G.mgT > 0) { ctx.fillStyle = UI.pick; ctx.fillRect(x + 2, g.y0 + g.h - 2, (g.tw - 4) * Math.min(1, G.mgT / TUNE.mgPickupLen), 1.5); } // burst countdown
|
||||
}
|
||||
@@ -521,18 +526,18 @@ export function render() {
|
||||
ctx.globalAlpha = 1;
|
||||
drawRing(tx, tz, col);
|
||||
}
|
||||
// the draw meter: it fills as you pull and the notch marks the kill line. Anchored on the
|
||||
// apron JUST IN FRONT of your feet — over the head it sat right in the lane and the chevrons
|
||||
// ran straight through it, and down here there is never anything else to collide with.
|
||||
const hd = proj(P.x, 0, P.z + 1.2);
|
||||
const mw = 26, mh = 4, mx = Math.round(hd.x - mw / 2);
|
||||
// advanced up-court the apron runs out, so pin the meter clear of the rack and the hint line
|
||||
const my = Math.min(Math.round(hd.y), H - TUNE.rackH - RACK_BOT - 28);
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.55)'; ctx.fillRect(mx - 1, my - 1, mw + 2, mh + 2);
|
||||
// The draw meter: it fills as you pull and the notch marks the kill line. A fixed BAR ACROSS
|
||||
// THE BOTTOM, ~90% of the width — it used to be a 26x4 px stub floating on the apron by your
|
||||
// feet, which is nothing at arm's length with a thumb over it. Fixed position also means it
|
||||
// never wanders behind the chevrons or off the top of the court.
|
||||
const mw = Math.round(W * 0.9), mh = 10, mx = Math.round((W - mw) / 2);
|
||||
const my = METER_Y();
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.62)'; ctx.fillRect(mx - 2, my - 2, mw + 4, mh + 4);
|
||||
ctx.fillStyle = 'rgba(255,255,255,0.16)'; ctx.fillRect(mx, my, mw, mh); // empty channel
|
||||
ctx.fillStyle = col; ctx.fillRect(mx, my, Math.round(mw * ft.f), mh);
|
||||
const notch = Math.round(mw * (TUNE.zSplatQ - TUNE.pullQMin) / (1 - TUNE.pullQMin));
|
||||
ctx.fillStyle = '#ffffff'; ctx.fillRect(mx + notch, my - 2, 1, mh + 4); // the kill line
|
||||
if (ft.f >= 0.999 && Math.floor(G.idleT * 12) % 2) { // fully drawn: the meter flashes
|
||||
ctx.fillStyle = '#ffffff'; ctx.fillRect(mx + notch, my - 3, 2, mh + 6); // the kill line
|
||||
if (ft.f >= 0.999 && Math.floor(G.idleT * 12) % 2) { // fully drawn: the whole bar flashes
|
||||
ctx.fillStyle = '#ffffff'; ctx.fillRect(mx, my, mw, mh);
|
||||
}
|
||||
}
|
||||
@@ -597,7 +602,9 @@ export function render() {
|
||||
if ((G.horde || G.mgT > 0) && G.mode === 'splatter') drawMgRacket(); // rambo racket over the player
|
||||
|
||||
ctx.setTransform(RES, 0, 0, RES, 0, 0); // un-shake: the HUD and overlays stay rock steady
|
||||
if (G.boomFlash > 0) { ctx.fillStyle = `rgba(255,255,255,${Math.min(1, G.boomFlash / 0.08) * 0.85})`; ctx.fillRect(0, 0, W, H); }
|
||||
// bomb bloom: a warm blood-orange wash, not a white-out. At 0.85 white it blinded you on a phone
|
||||
// and hid the very gore eruption it was announcing.
|
||||
if (G.boomFlash > 0) { ctx.fillStyle = `rgba(255,150,70,${Math.min(1, G.boomFlash / 0.06) * 0.3})`; ctx.fillRect(0, 0, W, H); }
|
||||
|
||||
// HUD — Wimbledon manual scoreboard as a 16-bit panel: bevelled bottle-green box (light
|
||||
// top/left, dark bottom/right), a purple club spine, cream serif names, and the LIVE game
|
||||
@@ -665,7 +672,7 @@ export function render() {
|
||||
if (G.eatenT > EATEN_RESTART) { ctx.fillStyle = UI.warm; ctx.font = F(8); ctx.fillText('TAP TO PLAY AGAIN', W / 2, H / 2 + 26); }
|
||||
ctx.restore();
|
||||
}
|
||||
if (G.phase === 'pretoss' && score.server === 'P' && G.mode !== 'eaten' && !G.horde && !(G.mgT > 0)) {
|
||||
if (G.phase === 'pretoss' && score.server === 'P' && G.mode !== 'eaten' && !G.horde && !(G.mgT > 0) && !G.aim) { // while pulling, the draw meter owns this slot
|
||||
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];
|
||||
@@ -676,7 +683,7 @@ export function render() {
|
||||
: touch ? 'TAP TO SERVE · PULL BACK TO AIM' : 'SPACE SERVES · ARROWS AIM')
|
||||
: G.serveNum === 2 ? (touch ? 'SECOND SERVE — TAP' : 'SECOND SERVE — SPACE')
|
||||
: (touch ? 'TAP TO TOSS, TAP TO SERVE' : 'SPACE TO TOSS, SPACE TO SERVE');
|
||||
ctx.fillText(txt, W / 2, H - TUNE.rackH - RACK_BOT - 17); // sit clear above the rack
|
||||
ctx.fillText(txt, W / 2, METER_Y()); // same slot the draw meter uses
|
||||
}
|
||||
if (G.flash) {
|
||||
ctx.font = F(12); ctx.fillStyle = '#ffffff';
|
||||
|
||||
Reference in new issue
Block a user