Story phase 4: help when stuck, the arcade opens with the story
- From the third attempt at a checkpoint, the death screen offers TRY AGAIN · WITH HELP, at most two tiers, said openly. In the cup the help is fresher legs and a longer swing; in gore, one more heart and a slower director and horde. Every checkpoint passed resets the offer. - The arcade shows only what the story has opened. Training and Classic open after act I, the Climb after II, Splatter Gore after III, and hosting a friend after IV (joining stays free). Locked doors are shown greyed with the act that opens them. - The non-story endings point at the story instead of the cup. - Store switches for the free tier (act I's card points at the full game) and a premium arcade, both off. - The prologue's HUD no longer overlaps the kill pips. PROJECT.md logs phases 1-4. One new test. v382, staging only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ce27bbc48b
commit
789bf41a9a
10 files changed
+191
-34
No files matched your search
+13
-8
@@ -1,7 +1,7 @@
|
||||
// Camera, viewport, sprites and all drawing. Owns the crowd dots (visual-only state).
|
||||
|
||||
import { cv, ctx } from './dom.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, racketStage, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS, HEAD_CROP, TINT, SHOT_DEPTH, matchFoe, cupFixture, cupRoundName , STORY, ROMAN, storyFixture, storyBossArt } from './config.js';
|
||||
import { BENCH, BENCH_STAGES, ZFAMS, famFrames, racketStage, TUNE, UI, EATEN_BURST, EATEN_SUMMARY, EATEN_RESTART, DONE_RESTART, COURT, P_BOUNDS, PLAYER_H, BOY_H, UMP_H, UMP_X, MACH_H, BOSS_ART, ZWOUND, woundStage, BOUNCE_IDLE, SPRITE_H, zombieH , WEAPONS, RACK_ROW, TOSS, HEAD_CROP, TINT, SHOT_DEPTH, matchFoe, cupFixture, cupRoundName , STORY, ROMAN, storyFixture, storyBossArt, STORY_HELP } from './config.js';
|
||||
import { P, O, ball, score, crowd, G, court, tour, kit, skin } from './state.js';
|
||||
import { RAISERS, FRAMES } from './crowd.js';
|
||||
import { predictLanding, previewServe, slotId, slotStock, serveApexY, rallyFeel, swayOf } from './gameplay.js';
|
||||
@@ -3028,14 +3028,15 @@ export function render() {
|
||||
: G.classic ? `SET ${G.sets[0] + G.sets[1] + 1} · FIRST TO ${TUNE.classicSets}`
|
||||
// THE STORY's chapter counts its own ten (its games run on the arcade's scale underneath)
|
||||
: G.story && G.story.part === 'morph' ? `ACT ${ROMAN[G.story.act]} · THE CUP IS YOURS`
|
||||
: G.story && G.story.part === 'pro' ? 'PROLOGUE · WIN A GAME'
|
||||
: G.story && G.story.part === 'pro' ? 'PROLOGUE'
|
||||
: G.story && G.story.part === 'chap'
|
||||
? `ACT ${ROMAN[G.story.act]} · GAME ${Math.min(STORY.length, G.runGames - G.story.base + 1)} / ${STORY.length}`
|
||||
: `GAME ${G.runGames + 1} · ${G.sets[0]} SET${G.sets[0] === 1 ? '' : 'S'}`, bW - 3, bH + 8);
|
||||
}
|
||||
// ...and NOT during a match: nothing on that court is killed for points, so the pips were both a
|
||||
// lie and a collision — 'NEXT ○○' ran straight into the OPPONENT line sharing the panel.
|
||||
if (splat && !G.match && !G.classic && !benchOn && TUNE.killPips && TUNE.killsPerPoint > 1) { // kills-to-next-point pips, on the panel above
|
||||
if (splat && !G.match && !G.classic && !benchOn && TUNE.killPips && TUNE.killsPerPoint > 1
|
||||
&& !(G.story && G.story.part !== 'chap')) { // the story's prologue and its scene have no kills to count // kills-to-next-point pips, on the panel above
|
||||
ctx.textAlign = 'left'; ctx.font = F(7, 0); ctx.fillStyle = bd.dim; ctx.fillText('NEXT', 4, bH + 8);
|
||||
for (let k = 0; k < TUNE.killsPerPoint; k++) {
|
||||
ctx.beginPath(); ctx.arc(34 + k * 8, bH + 8, 2.4, 0, 7);
|
||||
@@ -3102,7 +3103,7 @@ export function render() {
|
||||
// THE FIXTURE. A won cup parks a ceremony (the promotion) — that IS the handover, so the
|
||||
// summary points at it instead of at the next draw.
|
||||
const promo = hasPromo();
|
||||
const fx = cupFixture(prof.lg, prof.ch);
|
||||
const fx = storyFixture(prof.story); // THE STORY is the main path: every ending points back at it
|
||||
const xd = runDelta(), pr = progress();
|
||||
const ready = dead ? G.eatenT > EATEN_RESTART : G.done.t > DONE_RESTART;
|
||||
|
||||
@@ -3126,7 +3127,11 @@ export function render() {
|
||||
// 2026-09-11). Netplay used to end on THE CLUBHOUSE alone, which meant a fresh room code and
|
||||
// a second trip through the lobby to play the same person again.
|
||||
const online = !promo && !!(G.net && G.net.ready && !G.net.dead);
|
||||
const fxLines = ready ? wrapText(st ? sf.line : promo ? 'THE CLUB WANTS A WORD'
|
||||
// THE HELP OFFER (config.STORY_HELP): from the third attempt at a checkpoint, said openly
|
||||
const help = st && dead && prof.story.tries >= STORY_HELP.offerAt && prof.story.help < STORY_HELP.max;
|
||||
const offer = !help ? '' : cup ? 'THE TRAINER OFFERS FRESHER LEGS AND A LONGER SWING'
|
||||
: 'THE CLUB DOCTOR OFFERS ' + STORY_HELP.heart + ' MORE HEART AND A SLOWER HORDE';
|
||||
const fxLines = ready ? wrapText(help ? offer : st ? sf.line : promo ? 'THE CLUB WANTS A WORD'
|
||||
: online ? 'YOUR FRIEND IS STILL ON THE LINE'
|
||||
: G.net ? 'THE CLUB IS WAITING' : fx.line, F(8, 0), inner) : [];
|
||||
let h = pad + headLines.length * 24 + lineLines.length * 13 + 6;
|
||||
@@ -3210,17 +3215,17 @@ export function render() {
|
||||
const againLabel = G.classic ? 'CLASSIC AGAIN'
|
||||
: G.match && G.match.train ? 'TRAIN AGAIN'
|
||||
: G.match ? 'CLIMB AGAIN' : 'GORE AGAIN';
|
||||
const second = st ? ['club', 'THE CLUBHOUSE']
|
||||
const second = st ? (help ? ['story', 'TRY AGAIN'] : ['club', 'THE CLUBHOUSE'])
|
||||
: solo ? (cup || (G.done && G.done.cup) ? ['club', 'THE CLUBHOUSE']
|
||||
: ['again', againLabel])
|
||||
: online ? ['club', 'THE CLUBHOUSE'] : null;
|
||||
const bw = second && !stack ? Math.round((inner - 8) / 2) : inner;
|
||||
// "THE DRAW" was tennis for the field of entrants — and nothing is drawn any more since the
|
||||
// cup went deterministic (2026-08-29). The button names what it opens instead.
|
||||
const label = st ? (dead ? 'TRY AGAIN' : 'CONTINUE') : promo ? 'TAKE THE FLOOR' : solo ? 'ENTER THE CUP'
|
||||
const label = st ? (help ? 'TRY AGAIN · WITH HELP' : dead ? 'TRY AGAIN' : 'CONTINUE') : promo ? 'TAKE THE FLOOR' : solo ? 'THE STORY'
|
||||
: online ? 'REMATCH' : 'THE CLUBHOUSE';
|
||||
drawButton(inX, y, bw, MODAL_BTN, label, UI.pick, true);
|
||||
endBtns.push({ id: st ? 'story' : solo ? 'next' : online ? 'rematch' : 'club', x: inX, y, w: bw, h: MODAL_BTN });
|
||||
endBtns.push({ id: st ? (help ? 'help' : 'story') : solo ? 'next' : online ? 'rematch' : 'club', x: inX, y, w: bw, h: MODAL_BTN });
|
||||
if (second) {
|
||||
const sx = stack ? inX : inX + bw + 8, sy = stack ? y + MODAL_BTN + 8 : y;
|
||||
drawButton(sx, sy, bw, MODAL_BTN, second[1], UI.warm, false);
|
||||
|
||||
Reference in new issue
Block a user