The back room: five taps on the logo, not three on the scoreboard
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
04ee0174ce
commit
1bb528f7e0
3 files changed
+100
-31
No files matched your search
+13
-10
@@ -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 });
|
||||
|
||||
+34
-2
@@ -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 @@
|
||||
<h3>BEST SINGLE RUN<span>KILLS · GAMES</span></h3>
|
||||
<div id="rkList"></div>
|
||||
</div>
|
||||
<!-- THE BACK ROOM. Five taps on the logo (main.js) and nothing else — no button anywhere hints
|
||||
at it, which is the whole point: it replaced a triple-tap on the in-play scoreboard that
|
||||
players kept tripping by accident. -->
|
||||
<div id="secret">
|
||||
<div class="hdr"><button class="chev" id="scBack">‹</button><h2>BACK ROOM</h2></div>
|
||||
<div class="foot bare">
|
||||
<button class="btn big" id="scWeapons">WEAPON TEST</button>
|
||||
<button class="btn big" id="scBosses">BOSS TEST</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- one tile per chapter boss, built from config.bossArt in main.js: adding a design to the
|
||||
roster puts it on this screen and never touches this markup -->
|
||||
<div id="bosses">
|
||||
<div class="hdr"><button class="chev" id="bsBack">‹</button><h2>BOSS TEST</h2></div>
|
||||
<h3>PICK A CHAPTER<span>FULL ARSENAL</span></h3>
|
||||
<div id="bossList"></div>
|
||||
</div>
|
||||
<!-- shown only when a run beats your own best kill count — see main.js askName -->
|
||||
<div id="newbest">
|
||||
<div class="mid">
|
||||
@@ -359,6 +391,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="main.js?v=139"></script>
|
||||
<script type="module" src="main.js?v=140"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 = `<img src="assets/${art.walk[0]}.png?v=${SPRV}" alt="">`
|
||||
+ `<b>${art.name}</b><i>CHAPTER ${ch} · GAME ${ch * TUNE.bossEvery}</i>`;
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user