// PROMO CLIP RIG — injected into index.html by tools/capture.py, never shipped (tools/ is not in // the Dockerfile COPY). It stages a highlight with the real game through window.T, records the // canvas + the WebAudio bus with MediaRecorder, POSTs the webm back to capture.py and reloads into // the next scene. ONE PAGE LOAD PER SCENE — a fresh boot is the only teardown that is guaranteed // correct, and it costs a second. (() => { const q = new URLSearchParams(location.search); const list = (q.get('clips') || '').split(',').filter(Boolean); if (!list.length) return; // ---- determinism: the same seed gives the same take, so a good clip can be re-shot ---------- let seed = (Number(q.get('seed')) || 20260818) >>> 0; Math.random = () => { seed ^= seed << 13; seed >>>= 0; seed ^= seed >> 17; seed ^= seed << 5; seed >>>= 0; return seed / 4294967296; }; // ---- the audio tap. Every voice in audio.js connects straight to `ac.destination`, so the whole // mix is grabbed by making `destination` a gain node that fans out to the real speakers AND to a // MediaStreamDestination. Patching the prototype here (this script runs before the game module) // means the game needs no master-bus refactor for a tool that ships to nobody. let tap = null; const AC = window.AudioContext || window.webkitAudioContext; // `destination` is defined on BaseAudioContext, not on AudioContext — walk the chain for it let proto = AC.prototype, desc = null; while (proto && !(desc = Object.getOwnPropertyDescriptor(proto, 'destination'))) proto = Object.getPrototypeOf(proto); const real = desc.get; Object.defineProperty(proto, 'destination', { configurable: true, get() { if (!this._promoBus) { this._promoBus = this.createGain(); this._promoBus.connect(real.call(this)); tap = this.createMediaStreamDestination(); this._promoBus.connect(tap); } return this._promoBus; } }); const sleep = ms => new Promise(r => setTimeout(r, ms)); const T = () => window.T; // ---- the two autopilots. The tennis one is the reachability soak's from test.js (auto-run plus a // fixed tap window); the gore one draws the slingshot at the nearest body. Both are mediocre on // purpose — a clip of a perfect player is a clip of nothing happening. let lastTap = 0; const pilotTennis = t => { const { G, ball, P, keyPressed } = T(); if (G.modal) { G.modal = null; G.modalQ.length = 0; } if (G.phase === 'pretoss') { if (t - lastTap > 0.6) { lastTap = t; keyPressed('Space'); } } else if (G.phase === 'toss') { if (ball.vy < 0.4 && ball.y > 2.4) keyPressed('Space'); } else if (G.phase === 'rally' && ball.active && ball.lastHit === 'O') { const d = Math.hypot(ball.x - P.x, (ball.z - P.z) * 0.8); if (d < 2.9 && d > 2.2) keyPressed('Space'); } }; const pilotGore = t => { const { G, P, zombies, TUNE, dragServe } = T(); if (G.modal) { G.modal = null; G.modalQ.length = 0; } if (G.horde || G.mgT > 0 || t - lastTap < 0.45) return; let best = null, bd = 1e9; for (const z of zombies) { const d = Math.hypot(z.x - P.x, z.z - P.z); if (d < bd) { bd = d; best = z; } } if (!best) return; lastTap = t; const dx = best.x - P.x, dz = best.z - P.z, len = Math.hypot(dx, dz) || 1; dragServe({ mag: TUNE.pullFull + 6, fx: dx / len, fy: dz / len }); }; // ---- THE SCENES. `secs` is the take; `at` fires staged events on the clock; `pilot` picks who // plays. Everything here is the real game reached through the real doors — nothing is faked for // the camera, which is the only way the footage stays honest when the game changes. const SCENES = { // SPLATTER GORE ----------------------------------------------------------------------------- transition: { secs: 17, pilot: 'tennis', at: [ // the flagship: ordinary tennis, then the turn [0, () => T().start(null)], [4.5, () => { T().forceZombies(); }], ] }, horde: { secs: 14, pilot: 'gore', at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.G.runGames = 6; }], [2.5, () => T().startHorde()], ] }, flamer: { secs: 12, pilot: null, at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.G.runGames = 8; t.grantWeapon('flamer', 3); }], [1.0, () => { const t = T(); for (let i = 0; i < 9; i++) t.spawnZombie(-5 + i * 1.25, -9 + (i % 3) * 1.2, i % 3 ? 'a' : 'd'); }], [3.0, () => { const t = T(); t.selectWeapon(1 + t.G.rack.findIndex(s => s.id === 'flamer')); t.dragServe({ mag: 140, fx: 0, fy: -1 }); }], ] }, // THE BALL BOY, calmly mopping a massacre — the joke lands only if the court is a mess first, // so the pilot shoots for six seconds and is then switched off to let him work in peace. // ...and they die on the NEAR court on purpose: mopWorthIt only sends him for a stain on his // own side of the net, so a massacre at the far baseline is a massacre nobody mops. mopboy: { secs: 15, pilot: 'gore', pilotUntil: 6.5, at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.G.runGames = 9; }], [0.8, () => { const t = T(); for (let i = 0; i < 8; i++) t.spawnZombie(-4.5 + i * 1.3, 15 + (i % 3) * 1.6, i % 4 ? 'a' : 'b'); }], [7.0, () => { const t = T(); t.G.boy = null; t.sendBoy(0, 18, true); }], ] }, death: { secs: 13, pilot: 'gore', at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.G.runGames = 11; }], [1.0, () => { const t = T(); for (let i = 0; i < 7; i++) t.spawnZombie(-4 + i * 1.4, -3 - (i % 2), 'a'); }], [7.0, () => T().killPlayer()], ] }, // ZOMBIE TENNIS ----------------------------------------------------------------------------- boss: { secs: 16, pilot: 'gore', at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.G.runGames = 3 * t.TUNE.bossEvery; t.G.bossDue = 0; for (const id of ['bomb', 'triple', 'beacon', 'flamer', 'rocket']) t.grantWeapon(id, 9); } ], [1.2, () => T().startBossFight(3)], ] }, // THE OPPONENT BURSTS WHEN HIS GAME IS TAKEN, and that burst is the mode's whole payoff — but a // mediocre autopilot needs a minute to earn one and the clip is eighteen seconds. So every serve // is game point: the rally is real, the wait is not. match: { secs: 18, pilot: 'tennis', each: () => { const s = T().score; if (T().G.phase === 'pretoss') s.pts[0] = 3; }, at: [ [0, () => { const t = T(); t.start(null); t.intoSplatter(); t.startMatch(); }], ] }, }; async function record(cv, secs) { const s = cv.captureStream(60); if (tap) for (const a of tap.stream.getAudioTracks()) s.addTrack(a); const rec = new MediaRecorder(s, { mimeType: 'video/webm;codecs=vp9,opus', videoBitsPerSecond: 12e6 }); const chunks = []; rec.ondataavailable = e => e.data.size && chunks.push(e.data); const done = new Promise(r => { rec.onstop = r; }); rec.start(); await sleep(secs * 1000); rec.stop(); await done; return new Blob(chunks, { type: 'video/webm' }); } // PORTRAIT IS THE FORMAT, AND THE WINDOW IS NOT ASKED. The clip is the canvas BACKING STORE, not // the window, so the frame is pinned by giving the canvas a 9:16 CSS box (render.js measures the // box, not the viewport) — no window manager, no title bar, no maximised-on-launch surprise. // 270x480 logical at RES 4 (capture.py forces device-scale-factor 3, the dpr tier that picks 4) // is exactly 1080x1920 native: the file needs no upscale, so no filter ever touches the pixels. function frame() { // FULL FAT FOR THE CAMERA. lowPower defaults ON (RES capped at 3) because it is invisible on a // phone and buys 44% of the fill rate — but the clip is the one place the sharpness is judged // on a big screen, and 0 is what makes the backing store exactly 1080x1920 at dpr 3. T().TUNE.lowPower = 0; T().TUNE.pullHintAfter = 1e9; // ...and no ghost hand: the SWIPE TO DODGE demo is for a player // who has stalled, and on film it is just a hint over the art const st = document.createElement('style'); st.textContent = 'canvas#c { width: 270px !important; height: 480px !important; top: 0 !important; }'; document.head.appendChild(st); dispatchEvent(new Event('resize')); // ...and the ResizeObserver in render.js catches the box return sleep(400); } async function run() { while (!window.T || !window.T.SPR) await sleep(100); await frame(); await sleep(1200); // let the boot settle: sprites decoded, theme running const name = list[0], sc = SCENES[name]; if (!sc) { console.warn('no such scene', name); return next(); } const cv = document.getElementById('c'); fetch('/log?' + encodeURIComponent(name + ': dpr ' + devicePixelRatio + ', viewport ' + innerWidth + 'x' + innerHeight + ', canvas ' + cv.width + 'x' + cv.height)); const pilot = sc.pilot === 'tennis' ? pilotTennis : sc.pilot === 'gore' ? pilotGore : null; const fired = new Set(); const t0 = performance.now(); const loop = () => { const t = (performance.now() - t0) / 1000; for (const [at, fn] of sc.at) if (t >= at && !fired.has(at)) { fired.add(at); try { fn(); } catch (e) { console.error(name, at, e); } } if (t > 0.2) try { if (pilot && !(sc.pilotUntil && t > sc.pilotUntil)) pilot(t); if (sc.each) sc.each(t); } catch (e) { console.error(e); } if (t < sc.secs + 1) requestAnimationFrame(loop); }; requestAnimationFrame(loop); sc.at[0][1](); // t=0 event before the recorder rolls: no black frames fired.add(sc.at[0][0]); await sleep(250); const blob = await record(cv, sc.secs); await fetch('/clip/' + name + '.webm', { method: 'POST', body: blob }); next(); } function next() { const rest = list.slice(1); if (!rest.length) { fetch('/done', { method: 'POST' }); return; } location.replace('?clips=' + rest.join(',') + (q.get('seed') ? '&seed=' + q.get('seed') : '')); } addEventListener('load', run); })();