Polish round: modal system, softened palette, pixel font, and the 14 plan items
The three foundations first, since everything else leans on them:
- G.modal is the only thing that stops the world. update() early-returns on it
so no zombie steps, no ball flight, no spawn timers run while a popup is on
screen — you can read it without being eaten. Dismissal swallows the gesture;
the pointerdown dismisser is registered only on pointer:fine, because on touch
pointerdown beats touchstart and a shared handler cleared the modal and then
served with the same thumb.
- UI in config.js is the accent palette. The arcade primaries were shouting over
the artwork; the gore reds deliberately stay loud.
- Every canvas font string now goes through F(). Bare `monospace` resolved to
Courier — a slab serif — which was the "generic serif" bug, not taste.
Then: weapons are only ever WON. deliverTin() has exactly one caller now
(resolveInterlude('P')); the old game schedule calls the DUEL instead, fired off
proceedAfterClear() where the court is guaranteed empty. Four popups (splatter
tutorial with a generated illustration, first duel, weapon received, horde),
wCap 9 with the tin amounts tunable, a 5-hp brute that comes apart over four
wound skins, fire that ignites the neighbours and chains outward, a three-stage
death where you become the biggest gore mound on the court, and a 7s changeover
with a ball boy who finally has a mop.
New art: assets/logo.png, x_howto, z_gw2-4, x_boymop1-4, plus assets/_key.py for
standalone art (chroma-key + crop, no frame splitting, no forced sprite height).
Benchmarks re-run and holding: CPU returns 23/24 serves, 30 human-tapped points
= 50 hits / 0 whiffs, 26 simulated games with no exceptions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5faaa99467
commit
a09a5d7559
23 files changed
+392
-103
No files matched your search
@@ -42,7 +42,8 @@ export const G = {
|
||||
mgT: 0, // s of machine-gun burst left — auto-fire, no serving (spent from G.guns)
|
||||
mgCool: 0, // s until the next pickup-mode pellet
|
||||
deliveries: 0, // tins delivered this run — indexes the weapon ladder
|
||||
tinDue: false, // the rules called for new balls — deliver at the next changeover
|
||||
duelDue: false, // the schedule called a duel — fire it at the next hand-cleared court
|
||||
afterDuel: null, // the game beat the scheduled duel interrupted ('changeover' | 'game'), resumed when it resolves
|
||||
pendingGame: false, // a game just ended — flash 'GAME N' when the next one arms
|
||||
aim: null, // live two-stage serve aim {fx, fy, mag} while the thumb is down (splatter pretoss)
|
||||
meleeT: 0, // s of racket-swish cooldown left
|
||||
@@ -58,11 +59,15 @@ export const G = {
|
||||
clearWinner: null, // 'P'|'O' — who took the game/set being cleared (for the milestone card)
|
||||
pendingHorde: false,// a set was won; the horde starts after the set card's pause
|
||||
card: null, // {title, sub1, sub2, color, t} — centered milestone card (set won, duel); the reusable 'block'
|
||||
shameGame: false, // they took a game — the next new-balls delivery is withheld
|
||||
// the modal: a real interruption. Unlike card/flash it FREEZES the world (update() early-returns)
|
||||
// and only a tap or key clears it, so a popup can be read without being eaten while you read it.
|
||||
modal: null, // {title, body: [lines], icon, iconH, color, onDismiss}
|
||||
seen: {}, // one-shot markers so a first-time-only modal fires once per run
|
||||
best: null, // best run {games, sets}, loaded from localStorage when you get eaten
|
||||
newBest: false, // this run beat the stored best — the eaten summary celebrates it
|
||||
lastScoreT: -9, // idleT of the last score voice — rate-limits umpire during a horde
|
||||
eatenT: 0, // time since you were eaten — gates the restart tap
|
||||
eatenT: 0, // time since you were eaten — the death-animation clock, and it gates the restart tap
|
||||
eatenAt: null, // {x, z} where you fell — the gore mound blooms there
|
||||
missLog: [], // swing diagnostics ring buffer (tennis mode) — inspect via T.G.missLog
|
||||
swingSnaps: null, // ball-approach samples during the live swing, feeds the miss log
|
||||
rallyNear: null, // closest the incoming ball got this rally (for never-swung passes)
|
||||
@@ -92,8 +97,10 @@ export function setupPoint() {
|
||||
G.aiServeT = score.server === 'O' ? 1.4 : 0;
|
||||
}
|
||||
|
||||
export function sendBoy(tx = ball.x, tz = ball.z) { // fetch a dead ball — or mop a splat
|
||||
// fetch a dead ball — or, with mop, come out to CLEAN: a different sprite sheet and a
|
||||
// slower walk, because mopping is not sprinting. The gore-fading pass is the same either way.
|
||||
export function sendBoy(tx = ball.x, tz = ball.z, mop = false) {
|
||||
if (tz < -3 || tz > 27) return; // target left the field lengthwise, let it go
|
||||
const dir = tx > 0 ? 1 : -1; // enter from the opposite side so the pickup is mid-run
|
||||
G.boy = { x: dir > 0 ? -13 : 13, z: tz, dir, runT: 0 };
|
||||
G.boy = { x: dir > 0 ? -13 : 13, z: tz, dir, runT: 0, mop };
|
||||
}
|
||||
Reference in new issue
Block a user