Serve reads, the mop beat, the head-icon card, and Daniel benched
Oliver's batch, the code half: - The tennis serve lost its ghost fingers (idle tap demo + apex rider deleted with their counters; the rally-return hint survives). - The toss rises over the tossing HAND, measured off the toss frames' raised fingertips (TOSS.dx/oDx, women's tour override) — not over the head. - An uncaught toss returns to the bounce stance instead of flashing the smash frame the player never earned. - Between match games the court belongs to the mop boy: 5 s (TUNE.matchMopT), full job allowance, a hurry speed between stains, and the far-court reach limit lifted for the match (his stains are exactly where opponents die). The next opponent walks on only after — d.ending guards the empty court. - The walk-on card wears the opponent's own head: config.HEAD_CROP, straight crops measured off the family sheets, no new art. - The match intro is DEATH MATCH, counting the real blood pool. - Daniel is benched: every ump_* personality line routes through aside()'s early return; officiating calls keep speaking. Personality pass later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e01894f4d
commit
c084d53497
8 files changed
+141
-70
No files matched your search
+36
-9
@@ -93,7 +93,7 @@ export function startBossFight(chapter) {
|
||||
ball.active = false; setupPoint();
|
||||
spawnChapterBoss(chapter);
|
||||
zombieDuck(true); // the music drops out for it
|
||||
voice('ump_boss_arrives', 0, 0.9); voice('sudden_death', 2.6, 0.85);
|
||||
aside('ump_boss_arrives'); voice('sudden_death', 2.6, 0.85); // the stake is an event; the quip is benched (see aside())
|
||||
const art = bossArt(chapter);
|
||||
// THE WALK-ON IS AN OVERLAY, not a band card. A card announced the biggest fight in the game in
|
||||
// the same slot and size as a routine set, and it read as the boss simply being there. This is on
|
||||
@@ -337,8 +337,11 @@ export function startMatch() {
|
||||
voice('play_resume', 0.2);
|
||||
if (!G.seen.match) {
|
||||
G.seen.match = true;
|
||||
showModal({ title: 'EXHIBITION MATCH', color: UI.hot,
|
||||
body: ['WIN THE GAME — EVERY POINT WOUNDS IT', 'ITS POINTS DRAW YOUR BLOOD'] });
|
||||
// Oliver's copy direction: name the stake, count the blood. TUNE.matchPlayerHp is the real
|
||||
// number, so the modal can never drift from the pool it describes.
|
||||
showModal({ title: 'DEATH MATCH', color: UI.hot,
|
||||
body: ['WIN THE POINT — WOUND THE ZOMBIE',
|
||||
'ITS POINTS DRAW BLOOD — ' + TUNE.matchPlayerHp + ' AND YOU ARE EATEN'] });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -409,6 +412,17 @@ function startFall(from) {
|
||||
// OPPONENT'S SERVE POSES, because both are the same job — naming the frame while the body is doing
|
||||
// something the walk cycle has no picture for.
|
||||
export function tickMatchFall(dt) {
|
||||
// THE MOP BEAT runs here, on the same clock discipline as the fall: its own timer, never the
|
||||
// 'over' beat (see the section note above). When the boy's time is up the next opponent walks
|
||||
// on with its own intro card, exactly as it used to on the kill frame.
|
||||
const d = G.match;
|
||||
if (d && d.mopT > 0 && (d.mopT -= dt) <= 0) {
|
||||
d.mopT = 0;
|
||||
matchOpponent();
|
||||
d.ending = false; // play re-opens only now that a body is on court
|
||||
matchReady();
|
||||
G.phase = 'over'; G.overT = 2.2; // matchReady's setupPoint re-arms pretoss; keep the old beat
|
||||
}
|
||||
if (!G.matchFall) return tickMatchServe(dt);
|
||||
const f = G.matchFall;
|
||||
const zb = zombies.find(z => z.variant === 'r');
|
||||
@@ -492,7 +506,8 @@ function matchOpponent() {
|
||||
// so killZombie can file the kill under it without asking G which opponent we were on.
|
||||
zb.foe = matchFoe(G.match.opp);
|
||||
zb.fam = zb.foe.fam; // ...and the body it wears (config.ZFAMS)
|
||||
G.card = { title: zb.foe.name, sub1: zb.foe.line, color: UI.hot, t: 2.4 };
|
||||
// the walk-on card wears the opponent's own head (config.HEAD_CROP — a crop, not new art)
|
||||
G.card = { title: zb.foe.name, sub1: zb.foe.line, color: UI.hot, t: 2.4, head: zb.fam };
|
||||
zb.hp = zb.hpMax = TUNE.matchOppHp; // a FLAT pool: with regicide gone the body is pure
|
||||
// scoreboard (a quarter per point), so a deeper pool would
|
||||
// change nothing — the ladder escalates in racketAI instead
|
||||
@@ -569,15 +584,27 @@ function matchGameWon(zb, banked = false) {
|
||||
// the FAR side: that is where the body has been standing and bleeding, and it keeps him out of the
|
||||
// near court where the player is about to serve. Between games only — see sendBoy's match note.
|
||||
sendBoy(0, 4.5, true);
|
||||
// THE MOP BEAT (Oliver: "mopboy must mop for 5 seconds and really remove some gore visibly
|
||||
// before the new opponent arrives"). The fresh opponent used to spawn on the same frame as the
|
||||
// kill, so the boy always mopped behind the next game's back. Now the court belongs to him
|
||||
// first: the full job allowance (a between-games sendBoy books one stain), and matchOpponent
|
||||
// waits on d.mopT (ticked in tickMatchFall). A court with nothing worth mopping skips the wait —
|
||||
// an empty beat is a stall, not a show.
|
||||
if (G.boy && G.boy.mop) { G.boy.jobs = TUNE.boyMaxJobs; d.mopT = TUNE.matchMopT; }
|
||||
else d.mopT = 0.6;
|
||||
// Serving stays blocked for the whole beat: the match's permanent interlude carries it, and
|
||||
// keyPressed's introT bar sits ABOVE its over-beat re-arm, so a tap cannot serve at an empty
|
||||
// court. matchOpponent replaces this object and re-arms the normal 1.6 s intro when it runs.
|
||||
G.interlude = { introT: d.mopT + 1.6 };
|
||||
d.opp++;
|
||||
// ALTERNATING SERVICE, as the sport has it. The match calls advancePoint directly, which banks
|
||||
// the game but never touches the server — that flip lives in tennis's own pointTo, which the
|
||||
// match bypasses (the same shape as the G.serveNum bug this file already carries a note about).
|
||||
score.server = other(score.server);
|
||||
matchOpponent(); // fresh body, its own intro beat
|
||||
d.ending = false;
|
||||
matchReady();
|
||||
G.phase = 'over'; G.overT = 2.2;
|
||||
// d.ending STAYS TRUE for the whole beat — the empty-court check in resolveInterlude reads
|
||||
// "no body + not ending" as regicide, so a stray resolve during the beat would bank a phantom
|
||||
// game. The walk-on tick below is what re-opens play, exactly where the old code did.
|
||||
G.phase = 'over'; G.overT = d.mopT + 2.2; // ticks down through the beat; reset at the walk-on
|
||||
}
|
||||
|
||||
function matchGameLost() {
|
||||
@@ -610,7 +637,7 @@ export function resolveInterlude(side) {
|
||||
// of body text under a weapon icon it read as a footnote to the tin rather than as the prize.
|
||||
if (G.hp < G.hpMax) {
|
||||
G.hp = Math.min(G.hpMax, G.hp + TUNE.healPerDuel);
|
||||
voice('ump_trainer', 0.2, 0.95);
|
||||
aside('ump_trainer', 0.2); // benched with the rest of Daniel's personality — see aside()
|
||||
showModal({ title: 'WOUNDS TREATED', icon: 'x_cross', iconH: 52, color: UI.pick,
|
||||
body: ['THE TRAINER PATCHED YOU UP', G.hp + ' OF ' + G.hpMax + ' BACK'] });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user