From 1bb528f7e0f979a9e9e6121071ca60f1cdbc2083 Mon Sep 17 00:00:00 2001 From: Oliver Rieger Date: Sun, 9 Aug 2026 16:19:12 +0200 Subject: [PATCH] The back room: five taps on the logo, not three on the scoreboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The weapon-test cheat sat on the SCOREBOARD, where a single tap is the miss-log overlay — so the thing you tap to read a log was also the thing that opened a sandbox mid-run, three taps in, by accident. That tap does one job again. The cheats now live behind five taps on the splash logo, where nothing marks them and nothing in play can reach them: WEAPON TEST the old sandbox. It stocked only the BARREL weapons, on a screen called the weapon test — it stocks every weapon in the game now, eight of the nine slots. BOSS TEST a portrait of every chapter boss, built by asking bossArt() what each chapter gets, so the list cannot drift from what a run actually spawns. Pick one and you land in a real fight at that chapter's game number with the full arsenal at ×99. Neither is a special mode: both ride the transition out with laserScene's trick (force the turn, then step the ten-second cinematic out in one frame) and hand you the ordinary update loop, ordinary scoring, ordinary death. A test that plays by different rules tells you nothing about the game. Co-Authored-By: Claude Opus 5 --- gameplay.js | 23 +++++++++-------- index.html | 36 +++++++++++++++++++++++++-- main.js | 72 +++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 100 insertions(+), 31 deletions(-) diff --git a/gameplay.js b/gameplay.js index 32cf1d5..13efeee 100644 --- a/gameplay.js +++ b/gameplay.js @@ -683,26 +683,29 @@ export function deliverTin() { icon: WEAPONS[id].icon, color: UI.warm, t: TUNE.tinCardT }; } -// ---- TEST MODE. Triple-tap the scoreboard. A sandbox for trying the new content without playing -// twenty games to reach it: a steady medium wave and every barrel weapon on the rack, topped back up -// as you spend it. It does NOT stop the run — games, duels and bosses all still happen underneath; -// it only overrides the difficulty dials and keeps the rack stocked. +// ---- TEST MODE, the back room's WEAPON TEST (five taps on the splash logo — it used to be a +// triple-tap on the scoreboard). A sandbox for trying the new content without playing twenty games +// to reach it: a steady medium wave and EVERY weapon on the rack, topped back up as you spend it. +// It does NOT stop the run — games, duels and bosses all still happen underneath; it only overrides +// the difficulty dials and keeps the rack stocked. export function toggleTestMode() { G.testMode = !G.testMode; if (!G.testMode) { G.flash = { text: 'TEST MODE OFF', t: 1.4 }; return false; } if (G.mode === 'tennis') forceZombies(); // no zombies to test against in a tennis match stockTestRack(); G.flash = { text: 'TEST MODE', t: 1.6 }; - showModal({ title: 'TEST MODE', color: UI.pink, - body: ['A STEADY CROWD, MEDIUM SPEED', 'EVERY BARREL WEAPON, KEPT FULL', - '', 'TRIPLE-TAP THE SCOREBOARD AGAIN TO STOP'] }); + showModal({ title: 'WEAPON TEST', color: UI.pink, + body: ['A STEADY CROWD, MEDIUM SPEED', 'EVERY WEAPON, KEPT FULL', + '', 'IT RUNS UNTIL THE RUN DOES'] }); return true; } -// every barrel weapon on the rack at full stock. Called on entry and then kept topped up in update, -// so a burst you commit (which spends the whole tin) is available again a moment later. +// EVERY weapon on the rack at full stock — the four tins as well as the four barrel weapons, which +// is eight of the nine slots. It stocked only the barrel weapons before, on a screen called the +// weapon test. Called on entry and then kept topped up in update, so a burst you commit (which +// spends the whole tin) is available again a moment later. function stockTestRack() { - for (const id of Object.keys(BARREL_WEAPONS)) { + for (const id of Object.keys(WEAPONS)) { const have = G.rack.find(e => e.id === id); if (have) have.n = Math.max(have.n, WEAPONS[id].tin); else if (G.rack.length < RACK_MAX) G.rack.push({ id, n: WEAPONS[id].tin }); diff --git a/index.html b/index.html index 70ec242..df2f806 100644 --- a/index.html +++ b/index.html @@ -118,13 +118,15 @@ /* 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 { display: none; } + #splash, #setup, #stats, #ranks, #newbest, #secret, #bosses { 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 */ #intro[data-stage="setup"] #setup, #intro[data-stage="stats"] #stats, #intro[data-stage="ranks"] #ranks, + #intro[data-stage="secret"] #secret, + #intro[data-stage="bosses"] #bosses, #intro[data-stage="newbest"] #newbest { display: flex; flex-direction: column; flex: 1; animation: drop 0.3s ease-out; } /* the overlay is HIDDEN during play, not removed: a restart puts it straight back, and the @@ -180,6 +182,19 @@ #tours button.on { border-color: var(--acid); background: rgba(188,212,85,0.09); } #tours img { height: 88px; width: auto; display: block; image-rendering: pixelated; } #tours b { font: 400 11px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; } + /* THE BOSS PICKER (back room). Two columns of portraits, because you pick a boss by looking at + him — the tile is mostly art. Same plate as the tour tiles above; the grid is what makes ten + of them fit on a phone. */ + #bossList { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding-bottom: 14px; } + #bossList button { display: flex; flex-direction: column; align-items: center; gap: 5px; + padding: 8px 6px 10px; background: var(--panel); border: 2px solid var(--line); + color: inherit; font: inherit; cursor: pointer; + -webkit-tap-highlight-color: transparent; } + #bossList img { height: 78px; width: auto; display: block; image-rendering: pixelated; } + #bossList b { font: 400 9px Silkscreen, monospace; color: var(--cream); letter-spacing: 0.5px; + text-align: center; line-height: 1.35; } + #bossList i { font: 400 8px Silkscreen, monospace; color: var(--dim); font-style: normal; + letter-spacing: 1px; } /* a row, not a stacked tile: three 3:2 tiles stacked fill a phone twice over, and the art still reads at this size. -webkit-tap-highlight-color kills iOS's blue flash over the pixel art. */ #courtList button.court { display: flex; align-items: center; gap: 11px; width: 100%; @@ -343,6 +358,23 @@

BEST SINGLE RUNKILLS · GAMES

+ +
+

BACK ROOM

+
+ + +
+
+ +
+

BOSS TEST

+

PICK A CHAPTERFULL ARSENAL

+
+
@@ -359,6 +391,6 @@
- + diff --git a/main.js b/main.js index 9a51458..28176ed 100644 --- a/main.js +++ b/main.js @@ -3,7 +3,7 @@ // Module map: config (tuning/constants) · state (mutable game state) · gameplay (logic/update) // render (camera/drawing) · input (keys/touch) · audio (sfx/umpire) · panel (T tuning UI) -import { TUNE, COURTS, TOURS, EATEN_RESTART } from './config.js'; +import { TUNE, COURTS, TOURS, EATEN_RESTART, WEAPONS, bossArt } from './config.js'; import { P, O, ball, score, G, setupPoint, selectCourt, selectTour } from './state.js'; import { update, restartRun, keyPressed, solveShot, inServeBox, inCourt, forceZombies, splatPoint, deliverTin, selectWeapon, grantWeapon, showModal, dismissModal, startDuel, resolveInterlude, flickTarget, startBossFight, endBossFight , toggleTestMode } from './gameplay.js'; import { fitViewport, render, proj, RES, SPRV, LOAD } from './render.js'; @@ -186,8 +186,55 @@ document.getElementById('nbSend').addEventListener('click', async () => { }); nbName.addEventListener('keydown', e => { if (e.key === 'Enter') document.getElementById('nbSend').click(); }); document.getElementById('cStart').addEventListener('click', start); -for (const id of ['sBack', 'stBack', 'rkBack']) +for (const id of ['sBack', 'stBack', 'rkBack', 'scBack']) document.getElementById(id).addEventListener('click', () => go('splash')); +document.getElementById('bsBack').addEventListener('click', () => go('secret')); + +// ---- THE BACK ROOM: five taps on the logo, and nothing on the screen says so. It replaces the +// triple-tap on the in-play scoreboard, which sat on top of the miss-log tap and which the +// testteam kept opening by accident — a cheat you can trip mid-run is a bug. +let logoTaps = []; +document.querySelector('#splash .mid').addEventListener('pointerdown', () => { + const now = performance.now(); + logoTaps = logoTaps.filter(t => now - t < 2000); + logoTaps.push(now); + if (logoTaps.length >= 5) { logoTaps = []; go('secret'); } +}); + +// Both entries drop you into a REAL run rather than a special mode — same update loop, same +// scoring, same death — which is the only way a test tells you anything about the game. +// This is laserScene's trick: force the turn and then step the transition out in one go, because +// the cinematic is ten seconds you did not come here to watch. +function intoSplatter() { + if (G.mode === 'tennis') forceZombies(); + for (let i = 0; i < 900 && G.mode !== 'splatter'; i++) update(1 / 60); + G.modal = null; G.horde = null; G.interlude = false; G.changeover = 0; G.clearing = false; + G.mgT = 0; G.gunId = null; G.beam = null; G.hp = G.hpMax = TUNE.playerHp; + zombies.length = 0; shots.length = 0; pellets.length = 0; + score.server = 'P'; setupPoint(); +} +document.getElementById('scWeapons').addEventListener('click', () => { + start(); intoSplatter(); toggleTestMode(); +}); +document.getElementById('scBosses').addEventListener('click', () => go('bosses')); + +// The list is built by ASKING bossArt which design each chapter gets, so it can never drift from +// what the run actually spawns — including the champion sitting on chapter 10 rather than 7. +const bossList = document.getElementById('bossList'); +for (let ch = 1; ch <= 10; ch++) { + const art = bossArt(ch), b = document.createElement('button'); + b.innerHTML = `` + + `${art.name}CHAPTER ${ch} · GAME ${ch * TUNE.bossEvery}`; + b.addEventListener('click', () => { + start(); intoSplatter(); + G.runGames = ch * TUNE.bossEvery; // the roster, the HUD and the spawn ramp all read this one + G.bossDue = 0; // ...and the schedule must not queue a SECOND fight behind it + for (const id of Object.keys(WEAPONS)) grantWeapon(id, TUNE.wCap); // full arsenal, every tin + G.weapon = 0; + startBossFight(ch); + }); + bossList.appendChild(b); +} // the three period tabs are one listener on the strip — the button carries the period in data-p document.getElementById('stTabs').addEventListener('click', e => { const b = e.target.closest('button'); if (!b) return; @@ -278,27 +325,14 @@ const maybeRestart = () => { cv.addEventListener('pointerdown', maybeRestart); addEventListener('keydown', maybeRestart); -// hidden shortcut: three taps on the SCOREBOARD opens test mode (a medium wave plus every barrel -// weapon — see gameplay.toggleTestMode). The umpire triple-tap (skip to zombie mode) is REMOVED: -// the testteam found it, and a cheat a player can trip by accident is a bug. T.forceZombies() -// still exists for the console. -let boardTaps = []; +// A tap on the SCOREBOARD toggles the miss-log overlay, and that is now all it does. The triple-tap +// that opened test mode has moved to the back room on the splash (see below): a cheat you can trip +// by accident mid-run is a bug, and this one sat on the one thing you tap to read the log. cv.addEventListener('pointerdown', e => { const r = cv.getBoundingClientRect(); const x = (e.clientX - r.left) / r.width * (cv.width / RES); // logical coords const y = (e.clientY - r.top) / r.height * (cv.height / RES); - if (x >= 6 && x <= 138 && y >= 6 && y <= 36) { // the scoreboard - const now = performance.now(); - boardTaps = boardTaps.filter(t => now - t < 1600); - boardTaps.push(now); - if (boardTaps.length >= 3) { - boardTaps = []; - // taps one and two already toggled the miss-log twice, so it is back where it started — - // this third one must NOT toggle it again, or every trip through test mode leaves the - // overlay on behind you - toggleTestMode(); - } else G.logShow = !G.logShow; // a single tap is still the miss-log overlay - } + if (x >= 6 && x <= 138 && y >= 6 && y <= 36) G.logShow = !G.logShow; // the scoreboard });