Seven verdicts from the test group
- opening CPU beatable in a game or two (softer cap, more whiffs, rubber band) - modals are for calm courts only; live-court news is a card now - the transition howto is gone: a ghost hand teaches the pull, the toss, the apex and the swing, each on the exact struggle it answers - a shot barrel keeps your weapon and glows its slot instead - switching weapons clicks, the live tile is unmistakable - barrels come faster the deeper the run goes - the umpire triple-tap cheat is deleted Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f13cbef28a
commit
a89e8ff419
10 files changed
+242
-79
No files matched your search
+22
-14
@@ -6,7 +6,7 @@
|
||||
import { TUNE, UI, COURT, CONTACT, BOSS_ART, WEAPONS, BARREL_WEAPONS, woundStage, zombieH, EATEN_BURST, EATEN_SUMMARY, EATEN_FEED_R } from './config.js';
|
||||
import { G, P, ball, sendBoy, setupPoint } from './state.js';
|
||||
import { splat, moan, pock, thud, zap, voice, boom, swish, shriek, aww, sfx , whoosh } from './audio.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, showModal, endBossFight, aside, grantWeapon, weaponModal, endBurst } from './gameplay.js';
|
||||
import { splatPoint, cheer, spentBall, resolveInterlude, endBossFight, aside, grantWeapon, endBurst } from './gameplay.js';
|
||||
import { tally, multiKill, endRun } from './stats.js';
|
||||
|
||||
export const zombies = []; // {x, z, v, walkT, variant: 'a'|'b'|'c'}
|
||||
@@ -89,10 +89,13 @@ function pickBarrelWeapon() {
|
||||
// upside — it never attacks, never scores, and rolls off on its own if you ignore it.
|
||||
export function spawnBarrel() {
|
||||
spawnZombie((Math.random() * 2 - 1) * 3.2, 4.5 + Math.random() * 3, 'o');
|
||||
// a CARD, not a modal: it lands at a game-over beat that can have a standing wave on court, and
|
||||
// a world-stopping popup there is exactly the "popups get me killed" report. Same for the
|
||||
// machine's and the racket zombie's introductions below.
|
||||
if (!G.seen.barrel) {
|
||||
G.seen.barrel = true;
|
||||
showModal({ title: 'A GLOWING BARREL', color: UI.pink,
|
||||
body: ['THERE IS A WEAPON INSIDE IT', 'SHOOT IT AND IT IS YOURS', 'PINK MEANS TAKE THE SHOT'] });
|
||||
G.card = { title: 'A GLOWING BARREL', sub1: 'A WEAPON IS INSIDE — SHOOT IT',
|
||||
sub2: 'PINK MEANS TAKE THE SHOT', color: UI.pink, t: 2.6 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +106,8 @@ export function spawnMachine() {
|
||||
voice('ump_machine', 0.4, 0.95);
|
||||
if (!G.seen.mach) {
|
||||
G.seen.mach = true;
|
||||
showModal({ title: 'A BALL MACHINE', color: UI.warm,
|
||||
body: ['IT FIRES AT YOU ON A TIMER', 'SWING TO RETURN THE SHOT', 'OR SWISH TO BAT IT AWAY'] });
|
||||
G.card = { title: 'A BALL MACHINE', sub1: 'IT FIRES AT YOU ON A TIMER',
|
||||
sub2: 'SWING TO RETURN — OR SWISH IT AWAY', color: UI.warm, t: 2.6 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,19 +352,24 @@ const GORE_MUL = { a: 1, b: 1, e: 1, f: 1.8, s: 2.5, h: 2.5, g: 4, k: 5, r: 3, m
|
||||
// one synchronous frame: nothing interleaves between the stamp and the kill it causes.
|
||||
let CAUSE = 'ball';
|
||||
|
||||
// A barrel bursts and hands you what was inside, armed. Pink debris rather than gore — it is a
|
||||
// container, not a body — and the modal only fires the FIRST time you see each weapon, so the tenth
|
||||
// rocket does not stop the world to explain itself.
|
||||
// A barrel bursts and hands you what was inside. Pink debris rather than gore — it is a container,
|
||||
// not a body. It does NOT equip itself: shooting a barrel mid-plan used to yank the gun into your
|
||||
// hand and the next serve fired the wrong thing — the current weapon stays live and the new slot
|
||||
// pulses pink instead (render.drawRack reads G.rackGlow). The announcement is a CARD, never a
|
||||
// modal: a barrel pops mid-combat, and a popup that stops the world there costs lives, not reads.
|
||||
function popBarrel(zb) {
|
||||
boom(0.7); G.shake = { t: 0.22, mag: 4 }; G.boomFlash = 0.08;
|
||||
for (let k = 0; k < 30; k++) parts.push({
|
||||
x: zb.x, y: 0.7, z: zb.z,
|
||||
vx: (Math.random() * 2 - 1) * 4, vy: 1.2 + Math.random() * 3.5, vz: (Math.random() * 2 - 1) * 4,
|
||||
t: 0.35 + Math.random() * 0.5, pink: true });
|
||||
grantWeapon(zb.wid);
|
||||
// first sight only — the gate lives in weaponModal, for both callers — and the flash keeps quiet
|
||||
// when the modal spoke, so the same weapon isn't named twice in the same beat
|
||||
if (!weaponModal(zb.wid)) G.flash = { text: WEAPONS[zb.wid].label, t: 1.1 };
|
||||
const keep = G.weapon;
|
||||
const got = grantWeapon(zb.wid);
|
||||
G.weapon = keep;
|
||||
G.rackGlow = { slot: got, t: 2.2 };
|
||||
const w = WEAPONS[zb.wid];
|
||||
G.card = { title: w.label, sub1: (w.desc && w.desc[0]) || '', sub2: 'TAP ITS RACK SLOT TO ARM IT',
|
||||
icon: w.icon, color: UI.pink, t: TUNE.tinCardT };
|
||||
}
|
||||
|
||||
// dark smoke: one puff, drifting up and spreading. Not gravity-bound like gore (see the parts loop).
|
||||
@@ -671,8 +679,8 @@ export function spawnRacketZombie() { // one takes up residence on the far court
|
||||
hp, hpMax: hp });
|
||||
if (!G.seen.racket) { // it is not a shambler and it does not behave like one — say so, once
|
||||
G.seen.racket = true;
|
||||
showModal({ title: 'IT HAS A RACKET', color: UI.hot,
|
||||
body: ['IT RETURNS YOUR WEAK SHOTS', 'ONLY A FULL PULL WOUNDS IT'] });
|
||||
G.card = { title: 'IT HAS A RACKET', sub1: 'IT RETURNS YOUR WEAK SHOTS',
|
||||
sub2: 'ONLY A FULL PULL WOUNDS IT', color: UI.hot, t: 2.6 };
|
||||
}
|
||||
}
|
||||
function endHorde() {
|
||||
|
||||
Reference in new issue
Block a user