Six reports from Oliver's first playtest of the boss build. Two mattered. A boss sprite turned up in the shambler crowd because z_B1.png and z_b1.png are the same file on a case-insensitive filesystem — writing the boss frames silently overwrote the 'b' shambler's first two frames, and `ls` only ever showed the original lowercase name. Restored from HEAD~1, boss frames renamed z_boss*, and the rule is now in the asset pipeline notes. The parry was unreachable, which killed the entire new mechanic: P.swingT is only set in keyPressed's `phase === 'rally'` branch, and splatter sits in pretoss almost all the time, so every tap aimed at an incoming shot tossed the serve ball instead of swinging. An incoming shot inside parryArm now owns the tap in every phase, with its own generous reach and window — this is a phone, one thumb, against something aimed at you. The three fail cases (too early, too late, no tap) are what keep it a timing skill. The swish no longer eats shots: it runs first in keyPressed and was swallowing the very ball the parry was about to return. One flame ball killed a 16 hp boss, and that was a bug rather than tuning. A flame ball is not consumed on contact (full pierce, so it can cut a lane) but a boss is not removed when hit, so the same ball re-wounded it every frame. Boss bodies now take a contact cooldown and flame spends itself on them. Chapter 1 costs 4 flame balls or 16 clean smashes, and bossHp goes 10 to 16. The boss had no wounded state while throwing — correct, the wound skins were standing poses only. New sheet gives all four damage states their own overarm pitch. The ball machine's four painted rects are replaced with a real sheet, idle and firing, keeping only the muzzle glow procedural because the telegraph has to read at 20px tall. And the player's blood no longer sits on him as a painted layer: live drip and the screen-edge wash, nothing else. Tennis untouched: 30 tapped points, 50 hits / 0 whiffs. 45-game soak clean, all four chapters firing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
66 lines
3.7 KiB
HTML
66 lines
3.7 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Splatter Tennis</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<!-- save-to-homescreen: a bloody tennis ball. iOS needs the apple-* tags (it ignores the
|
|
manifest's display/name); Android reads the manifest. -->
|
|
<link rel="icon" type="image/png" href="assets/favicon.png">
|
|
<link rel="apple-touch-icon" href="assets/icon-180.png">
|
|
<link rel="manifest" href="manifest.webmanifest">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-title" content="Splatter">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="theme-color" content="#2b2735">
|
|
<style>
|
|
/* pixel HUD font — bundled so it renders offline; used by the canvas scoreboard + rack */
|
|
@font-face { font-family: 'Silkscreen'; font-weight: 400; font-display: block;
|
|
src: url('assets/fonts/Silkscreen-Regular.ttf') format('truetype'); }
|
|
@font-face { font-family: 'Silkscreen'; font-weight: 700; font-display: block;
|
|
src: url('assets/fonts/Silkscreen-Bold.ttf') format('truetype'); }
|
|
html, body { margin: 0; height: 100%; background: #10141c; display: grid; place-items: center; }
|
|
canvas { image-rendering: pixelated; width: min(96vw, calc(96vh * 16 / 9)); aspect-ratio: 16/9; touch-action: none; }
|
|
#hint { position: fixed; bottom: 8px; width: 100%; text-align: center;
|
|
font: 400 10px Silkscreen, monospace; color: #6b7688; }
|
|
@media (pointer: coarse) { #hint, #intro .kb { display: none; } }
|
|
#intro { position: fixed; inset: 0; z-index: 10; touch-action: none; display: grid; place-items: center;
|
|
background: radial-gradient(circle at 50% 34%, rgba(34,16,20,0.92), rgba(8,10,16,0.96)); }
|
|
#intro .card { font: 400 11px Silkscreen, monospace; color: #dfe6f2; text-align: center; line-height: 1.9;
|
|
padding: 18px 26px; max-width: 92vw; animation: drop 0.5s ease-out; }
|
|
@keyframes drop { from { transform: translateY(-14px); opacity: 0; } }
|
|
/* the logo is generated pixel art now (assets/logo.png), not stacked CSS text */
|
|
#intro .logo { display: block; width: min(320px, 78vw); height: auto; margin: 0 auto 8px;
|
|
image-rendering: pixelated; filter: drop-shadow(0 3px 0 rgba(0,0,0,0.45)); }
|
|
#intro .slogan { margin: 8px 0 16px; font-size: 12px; color: #b6a0a4; letter-spacing: 0.5px; }
|
|
#intro .slogan b { color: #c9564a; }
|
|
#intro .how { display: inline-block; text-align: left; color: #cbd4e2; line-height: 2; }
|
|
#intro .how p { margin: 0; }
|
|
#intro .how span { display: inline-block; width: 30px; }
|
|
#intro em { color: #8a96ab; font-style: normal; font-size: 12px; }
|
|
#intro .start { margin-top: 18px; color: #e0cc72; font-weight: bold; letter-spacing: 1px; animation: blink 1.1s step-end infinite; }
|
|
@keyframes blink { 50% { opacity: 0.25; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="c" width="480" height="270"></canvas>
|
|
<div id="hint">move: WASD/arrows · serve/swing: SPACE · aim: hold a direction while swinging · T: tuning panel</div>
|
|
<div id="intro">
|
|
<div class="card">
|
|
<img class="logo" src="assets/logo.png" alt="Splatter Tennis">
|
|
|
|
<p class="slogan">Fifteen. Thirty. Forty. <b>Dead.</b></p>
|
|
<div class="how">
|
|
<p><span>👆</span> tap — serve + swing</p>
|
|
<p><span>👉</span> flick — aimed shot</p>
|
|
<p><span>✋</span> drag — run <em>(auto-run is on)</em></p>
|
|
<p class="kb"><span>⌨️</span> WASD / arrows + SPACE</p>
|
|
</div>
|
|
<p class="start">TAP TO START</p>
|
|
</div>
|
|
</div>
|
|
<script type="module" src="main.js?v=41"></script>
|
|
</body>
|
|
</html>
|