From dbfd0854936a6f8667ef503a2b3378429e10ebbe Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Sat, 29 Aug 2026 16:46:34 +0200 Subject: [PATCH] The draw screen retires: the league cup rolls inside the run, ENTER THE DRAW moves onto the setup button Co-Authored-By: Claude Fable 5 --- PROJECT.md | 13 ++++++++ index.html | 47 ++------------------------- src/beats.js | 13 ++++---- src/main.js | 89 +++++++--------------------------------------------- test.js | 2 +- 5 files changed, 34 insertions(+), 130 deletions(-) diff --git a/PROJECT.md b/PROJECT.md index 6c13e5d..cd064cc 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -491,6 +491,19 @@ zombie (now an occasional opponent, `TUNE.racketEvery`, not the every-game fixtu `RETURNED`, `DOUBLE KILL`) is a footnote to something you already watched happen. Sharing the loud FACE with the milestone cards is right; sharing their SIZE made every footnote read as a milestone. +## Session log: THE DRAW SCREEN RETIRES (2026-08-29, same day, STAGING ONLY) + +Oliver: "the draw ist doch jetzt eigentlich obsolet, oder?" — yes. The screen was built for the +18-rung ladder (his own 2026-08-15 ask), where four bands rolling out of a deep hat was a +ceremony; a league cup shuffles 2-3 faces the setup's league panel already shows face-up. Deleted: +the #bracket stage (DOM, CSS, buildBracket, its timers and routing). What survives: the shuffle +itself lives INSIDE the run (cupDraw, without replacement, the final settled — startMatch's `draw` +parameter stays for the suite's validation test, renamed accordingly), each round still announces +itself on the walk-on modal, and the cup's big setup button now says **ENTER THE DRAW** — all the +ritual the moment needs. The rules modal's copy went league-neutral ("UP YOUR LEAGUE'S DEAD +HIERARCHY — THE FINAL IS SET"; a 3-rung league plays three rounds, not four). The screen's +mechanics live in git history (like the lob) if the ceremony is ever asked back. + ## Session log: THE LEAGUES — the plaque moves mid-court, and the 94vw scrollbar (2026-08-29, same day, STAGING ONLY) Live review with Oliver on the pane, three finds: diff --git a/index.html b/index.html index 257a61a..c52b6ef 100644 --- a/index.html +++ b/index.html @@ -177,7 +177,7 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat /* ONE overlay, four panels, swapped by #intro[data-stage]. Keeping the backdrop and the card frame alive across every screen is the point — a screen that unmounts the whole overlay flashes the court through between menus. */ - #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #bracket, #vs, #train, #player, #promo, #card { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses, #stmode, #stsub, #vs, #train, #player, #promo, #card { display: none; } #intro[data-stage="splash"] #splash { display: flex; flex-direction: column; flex: 1; } /* flex COLUMN, not block: it is what lets the footer's margin-top:auto reach the bottom of a screen whose content does not fill it */ @@ -188,7 +188,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat #intro[data-stage="ranks"] #ranks, #intro[data-stage="secret"] #secret, #intro[data-stage="bosses"] #bosses, - #intro[data-stage="bracket"] #bracket, #intro[data-stage="train"] #train, #intro[data-stage="vs"] #vs, #intro[data-stage="player"] #player, @@ -411,35 +410,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat /* the training picker is 18 rungs deep — the tile carries the HEAD (the same z_*head crop the draw and the walk-on wear), which reads at a third of the boss portrait's height */ #trainList img { height: 44px; width: auto; display: block; image-rendering: pixelated; } - /* THE DRAW. Four rungs read top-down as a ladder, because that is what the mode is — the bracket - shape a real draw is printed in needs width a phone has not got, and half of it would be blank - anyway (you only ever play one side of it). The rows spin their portrait and their name while - the draw is being made, and .set is what a landed one wears: the placeholder look is on the - ROW, so a slot that has stopped is obvious at a glance and START stays out of reach until the - last of them lands. The final wears .set from the first frame — it was never in doubt. */ - #cupRows { display: flex; flex-direction: column; gap: 8px; } - .cupRow { display: grid; grid-template-columns: 54px 1fr auto; grid-template-rows: auto auto; - align-items: center; column-gap: 10px; row-gap: 2px; - padding: 7px 10px; background: var(--panel); border: 2px solid var(--line); - /* the connector: every round but the first hangs off the one above it */ - position: relative; } - .cupRow + .cupRow::before { content: ''; position: absolute; left: 35px; top: -10px; width: 2px; - height: 10px; background: var(--line); } - .cupRow img { grid-column: 1; grid-row: 1 / span 2; width: 54px; height: 54px; - object-fit: contain; object-position: center; image-rendering: pixelated; - /* mid-spin the faces flicker past unreadable; dimming them says "still rolling" - without a second element to keep in sync */ - opacity: 0.5; filter: grayscale(0.7); } - .cupRow u { grid-column: 2; grid-row: 1; font: 400 8px Silkscreen, monospace; color: var(--dim); - letter-spacing: 1.5px; text-decoration: none; text-align: left; } - .cupRow b { grid-column: 2; grid-row: 2; font: 400 11px Silkscreen, monospace; color: var(--dim); - letter-spacing: 0.5px; line-height: 1.35; text-align: left; } - .cupRow i { grid-column: 3; grid-row: 1 / span 2; font: 400 8px Silkscreen, monospace; - font-style: normal; color: var(--dim); letter-spacing: 1px; white-space: nowrap; } - .cupRow.set img { opacity: 1; filter: none; } - .cupRow.set b { color: var(--cream); } - .cupRow.fin { border-color: var(--red); } - .cupRow.fin.set b { color: var(--acid); } /* START is genuinely unreachable until the draw has landed — the wait IS the ceremony */ .btn[disabled] { opacity: 0.32; cursor: default; } /* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still @@ -774,19 +744,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat - -
-

THE DRAW

-

FOUR ROUNDSDRAWING…

-
-
-

- -
-
@@ -897,6 +854,6 @@ if (location.hostname !== 'splatter-tennis.wuwi.one' && !window.Capacitor?.isNat
- + diff --git a/src/beats.js b/src/beats.js index a3b4538..e3a464a 100644 --- a/src/beats.js +++ b/src/beats.js @@ -354,9 +354,9 @@ export function watchdog(dt) { // the match mode ends. Regicide is inherited for free: smashing the ball into the body still wounds // it, and killing it outright wins the game early (killZombie fires resolveInterlude('P'), which // lands in matchGameWon via the empty-court check). -// `draw` is the bracket screen's settled rungs, one per cup round (main.buildBracket). It is -// optional and null means "roll each round as it comes", which is what the mode did before the -// screen existed and is still what the #cup back door gets. +// `draw` is an optional pre-settled list of rungs, one per cup round — null (the normal case +// since the bracket screen was deleted, 2026-08-29) means "roll each round as it comes". The +// parameter stays: the suite uses it to pin the validation below. // `train` (2026-08-28) is a RUNG NUMBER and turns the run into the TRAINING COURT: that one // opponent, one game at a time, nothing banked. The ladder is parked — d.opp never advances, a // felled body walks straight back on, and bleeding out costs a modal instead of the run. All of @@ -395,7 +395,7 @@ export function startMatch(cup = false, draw = null, train = 0) { } else if (cup && !G.seen.cup) { G.seen.cup = true; showModal({ title: 'THE TOURNAMENT', color: UI.hot, - body: ['FOUR ROUNDS UP THE DEAD HIERARCHY', + body: ["UP YOUR LEAGUE'S DEAD HIERARCHY — THE FINAL IS SET", 'ITS POINTS DRAW BLOOD — ' + TUNE.matchPlayerHp + ' AND YOU ARE EATEN'] }); } else if (!cup && !train && !G.seen.match) { G.seen.match = true; @@ -414,9 +414,8 @@ export function startMatch(cup = false, draw = null, train = 0) { // The round's rung, drawn from its band. d.opp carries it so matchFoe, the ladder speed dials and // the felled arithmetic all read the drawn rung with no cup-awareness of their own. -// A DRAW MADE ON SCREEN IS THE DRAW THAT IS PLAYED. When the bracket screen settled the rungs they -// arrive here as cup.draw and this simply reads them off — rolling again would mean the faces the -// player watched land had nothing to do with what walks on, which is worse than showing nothing. +// A PASSED DRAW IS THE DRAW THAT IS PLAYED (it once arrived from the bracket screen; the suite +// still passes one) — this reads it off rather than rolling again. // Guarded on the band all the same: a saved or hand-passed draw that names a rung this round does // not contain falls back to an honest roll rather than dealing the champion in round one. function cupDraw() { diff --git a/src/main.js b/src/main.js index 73486be..6c63859 100644 --- a/src/main.js +++ b/src/main.js @@ -352,6 +352,7 @@ function buildLeaguePanel() { // on the card now — the setup should show the league and who of it you have already beaten). function syncSetup() { const cupMode = pickedMode === 'cup', climbMode = pickedMode === 'zombie'; + document.getElementById('cStart').textContent = cupMode ? 'ENTER THE DRAW' : 'SERVE UP'; document.getElementById('who').style.display = cupMode || climbMode ? 'none' : 'flex'; document.getElementById('lgPanel').style.display = cupMode || climbMode ? 'block' : 'none'; document.getElementById('s01').textContent = @@ -466,82 +467,17 @@ for (const c of COURTS) { // to the league's own court rather than serving up a court the player has not earned if (!courtUnlocked(picked)) picked = leagueOf(profile.prof.lg).court; -// ---- THE DRAW (Oliver, 2026-08-15: "when starting the tournament there should be a view of the -// tournament hierarchy but only the finale is settled — the game against the champion... random -// browsing through the roster in the individual spots which begins fast, gets slower and then -// settles on a random character, so that every tournament is new"). -// -// THE SCREEN IS THE DRAW, NOT A TEASER. `cupPicks` is what walks on: startMatch takes it and -// cupDraw reads it instead of rolling its own, so the face you watched land in ROUND ONE is the -// body across the net. A screen that showed a different draw from the one being played would be -// worse than no screen — it would teach the player that the ceremony is a lie. -// -// The roll is one recursive setTimeout per row whose gap grows with p^2 — fast, then visibly -// slowing, then stopped. No rAF: the menu deliberately parks the game loop (see menuUp), and three -// staggered timers are the whole animation. -const cupRows = document.getElementById('cupRows'), cupGo = document.getElementById('cupGo'); -let cupPicks = null, cupSpins = []; +// THE DRAW SCREEN IS GONE (2026-08-29, Oliver: "das ist doch jetzt eigentlich obsolet"). It was +// built for the 18-rung ladder, where four bands rolling out of a deep hat was a ceremony worth a +// screen; a league cup shuffles 2-3 faces the setup's league panel already shows face-up. The +// shuffle itself survives INSIDE the run (cupDraw rolls without replacement, the final settled) +// and each round still announces itself on the walk-on modal — only the screen is deleted. The +// cup's big button says ENTER THE DRAW (syncSetup), which is all the ritual the moment needs. +// The screen's mechanics live in git history (like the lob) if the ceremony is ever asked back. // the portrait: every family's own head crop (assets/_heads.py), the club pro's included — the -// same file the in-play walk-on card uses, so the draw and the court show the same face +// same file the in-play walk-on card uses, so the picker and the court show the same face const foeArt = f => `assets/z_${f.fam}head.png?v=${SPRV}`; -function buildBracket() { - for (const t of cupSpins) clearTimeout(t); - cupSpins = []; - const R = cupRoundsFor(profile.prof.lg); - // WITHOUT REPLACEMENT, like cupDraw: a league band is 3-4 rungs, so the non-final rounds are a - // shuffle of the sub-band — every tournament a new order, no body dealt twice, the final settled - cupPicks = []; - const used = new Set(); - for (const r of R) { - const pool = r.band.filter(x => !used.has(x)); - const pick = (pool.length ? pool : r.band)[Math.floor(Math.random() * (pool.length ? pool : r.band).length)]; - used.add(pick); cupPicks.push(pick); - } - // the header counts the league's own rounds (a 3-rung league plays three) - document.querySelector('#bracket h3').firstChild.nodeValue = - R.length === 4 ? 'FOUR ROUNDS' : 'THREE ROUNDS'; - cupRows.innerHTML = ''; - cupGo.disabled = true; - document.getElementById('brState').textContent = 'DRAWING…'; - const lg = leagueOf(profile.prof.lg); - document.getElementById('brSum').innerHTML = - `ONLY THE FINAL WAS EVER SETTLED${lg.name} · ${ - (COURTS.find(c => c.id === lg.court) || COURTS[0]).name}`; - R.forEach((r, i) => { - const fixed = r.band.length === 1; // the final: one rung, nothing to roll - const row = document.createElement('div'); - row.className = 'cupRow' + (fixed ? ' fin set' : ''); - row.innerHTML = `${r.name}${ - r.need === 1 ? 'ONE GAME' : r.need + ' GAMES'}`; - cupRows.appendChild(row); - const img = row.querySelector('img'), nm = row.querySelector('b'); - const show = n => { const f = matchFoe(n); img.src = foeArt(f); nm.textContent = f.name; }; - if (fixed) return show(cupPicks[i]); - const t0 = performance.now(), len = 1500 + i * 550; // 1.5 / 2.05 / 2.6 s — they land in order - (function roll() { - const p = Math.min(1, (performance.now() - t0) / len); - if (p >= 1) { - show(cupPicks[i]); - row.classList.add('set'); - if (cupRows.querySelectorAll('.set').length === R.length) { - cupGo.disabled = false; - document.getElementById('brState').textContent = 'THE DRAW IS MADE'; - } - return; - } - show(r.band[Math.floor(Math.random() * r.band.length)]); - cupSpins.push(setTimeout(roll, 45 + 330 * p * p)); - })(); - }); -} -document.getElementById('brBack').addEventListener('click', () => { - for (const t of cupSpins) clearTimeout(t); // a screen you walked out of must stop spinning - cupSpins = []; - go('setup'); -}); -document.getElementById('cupGo').addEventListener('click', () => start('cup')); - // ---- THE TRAINING PICKER (Oliver, 2026-08-28: "ein Trainingsbereich — Auswahl des Gegners, ein // Spiel, tap to replay, bis man keine Lust mehr hat"). The whole ladder face-up, one tile per rung, // wearing the same z_*head crop the draw and the walk-on wear. Tap = start — the wizard's own @@ -747,9 +683,9 @@ function start(modeId = pickedMode) { G.diff = G.classic ? (DIFFS.find(d => d.id === pickedDiff) || DIFFS[1]) : null; if (modeId === 'gore') startGore(); // no tennis prologue: the mode you picked is the mode you get if (modeId === 'zombie') { intoSplatter(); startMatch(); } - // ...and the cup carries the draw the bracket screen just made. Null (the #cup back door) still - // means "roll it yourself", which is what the mode did before the screen existed. - if (modeId === 'cup') { intoSplatter(); startMatch(true, cupPicks); } + // the cup rolls its own draw per round (cupDraw, without replacement) — the bracket screen + // that used to settle it on-screen is gone (2026-08-29, see the foeArt note above). + if (modeId === 'cup') { intoSplatter(); startMatch(true); } // cupDraw shuffles per round // training: the picked rung rides the match machinery with the ladder parked (beats.startMatch) if (modeId === 'train') { intoSplatter(); startMatch(false, null, trainRung); } } @@ -840,7 +776,6 @@ nbName.addEventListener('keydown', e => { if (e.key === 'Enter') document.getEle // SERVE UP starts the run — except in the cup, where the draw has to be made first. That screen // carries its own start button, so this stays the one place a run begins from the setup screen. document.getElementById('cStart').addEventListener('click', () => { - if (pickedMode === 'cup') { buildBracket(); go('bracket'); return; } if (pickedMode === 'train') { go('train'); return; } // the picker starts it — tap advances start(); }); diff --git a/test.js b/test.js index 6afc046..8a1422c 100644 --- a/test.js +++ b/test.js @@ -1233,7 +1233,7 @@ test('THE TOURNAMENT: bands draw, bodies deepen, rounds hold their body, the cha assert.ok(pr && pr.to === 2, 'the ceremony is parked for the menu to play'); }); -test('THE DRAW: a bracket settled on screen is the bracket that is played', () => { +test('THE DRAW: a passed draw is validated per round, honoured, or honestly re-rolled', () => { // The screen would be worse than nothing if it dealt a different tournament from the one it just // showed you settling (main.buildBracket hands its rungs straight to startMatch). The guard is // the other half: a rung that is not in the round's band falls back to an honest roll rather