Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174// oskiewar netplay browser rig, 26.09.11// The parts two end-to-end scripts share: the real relay and the real shell on// a local port, and a browser seat pointed at them. Nothing here is stubbed —// `OskiewarLiveManager` is the class the session server runs, and the pages are// served the same `mac-test.html` and `oskiewar.js` that ship.//// Used by netplay-browser.mjs (two seats, one fight) and netplay-rejoin.mjs// (one seat that leaves and comes back).import { createServer } from "node:http";import { readFile } from "node:fs/promises";import { extname, join, resolve } from "node:path";import { fileURLToPath } from "node:url";import { WebSocketServer } from "ws";import puppeteer from "puppeteer";import { OskiewarLiveManager } from "../../../session-server/oskiewar-live-manager.mjs";import { validateDemo } from "../../../system/netlify/functions/oskiewar-replays.mjs";
const here = resolve(fileURLToPath(new URL(".", import.meta.url)), "..");const repo = resolve(here, "../..");const chrome = process.env.PUPPETEER_EXECUTABLE_PATH || "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";const mime = new Map([[".html", "text/html; charset=utf-8"], [".js", "text/javascript; charset=utf-8"], [".mjs", "text/javascript; charset=utf-8"], [".ttf", "font/ttf"], [".svg", "image/svg+xml"]]);
export const wait = (ms) => new Promise((done) => setTimeout(done, ms));
export function fileFor(pathname) { if (pathname === "/oskiewar.js") return join(here, "oskiewar.js"); if (/^\/(oskiewar-(sfx|voice|midi)|frame-driver|round-room|account)\.mjs$/.test(pathname)) return join(here, pathname.slice(1)); if (pathname === "/aesthetic.computer/dep/@akamfoad/qr/qr.mjs") return join(repo, "system/public/aesthetic.computer/dep/@akamfoad/qr/qr.mjs"); if (pathname.startsWith("/aesthetic.computer/lib/") || pathname.startsWith("/aesthetic.computer/cursors/")) return join(repo, "system/public", pathname.slice(1)); if (pathname === "/ComicRelief-Regular.ttf") return join(repo, "system/public/papers.aesthetic.computer/foundry/fonts/ComicRelief-Regular.ttf"); if (pathname === "/" || /^\/[a-z0-9-]+\/?$/.test(pathname)) return join(here, "mac-test.html"); return "";}
// The real relay, in this process, on a local port.export async function localStack() { const manager = new OskiewarLiveManager(); // Every round the fight files, and every one the store would have refused. const filed = { rounds: [], rejected: [] }; const server = createServer(async (request, response) => { const url = new URL(request.url, "http://127.0.0.1"); if (url.pathname === "/favicon.ico") { response.writeHead(204); response.end(); return; } if (url.pathname === "/api/product-analytics-config" || url.pathname === "/api/oskiewar-pops" || url.pathname === "/api/oskiewar-country") { response.writeHead(200, { "content-type": "application/json" }); response.end("{}"); return; } if (url.pathname === "/api/oskiewar-replays") { // Versus rounds are filed now, so this end of the wire has to be real: // a 404 on the POST would have the game logging upload errors through a // fight the rig is trying to measure. Each demo is validated by the // store's OWN reader and kept for the script to inspect. if (request.method === "POST") { const chunks = []; for await (const chunk of request) chunks.push(chunk); let demo = null; try { demo = JSON.parse(Buffer.concat(chunks).toString()); } catch {} const invalid = demo ? validateDemo(demo) : "Invalid JSON"; if (invalid) { filed.rejected.push({ id: demo?.roundId || "?", reason: invalid }); response.writeHead(400, { "content-type": "application/json" }); response.end(JSON.stringify({ error: invalid })); return; } filed.rounds.push(demo); response.writeHead(201, { "content-type": "application/json" }); response.end(JSON.stringify({ ok: true, id: demo.matchId, stored: true })); return; } response.writeHead(404, { "content-type": "application/json" }); response.end('{"replay":null}'); return; } const path = fileFor(url.pathname); if (!path) { response.writeHead(404); response.end("not found"); return; } try { const body = await readFile(path); response.writeHead(200, { "content-type": mime.get(extname(path)) || "application/octet-stream", "cache-control": "no-store" }); response.end(body); } catch (error) { response.writeHead(500); response.end(error.message); } }); const sockets = new WebSocketServer({ server }); sockets.on("connection", (ws, request) => { if (manager.handleConnection(ws, request)) return; ws.close(1008, "unknown endpoint"); }); await new Promise((ready) => server.listen(0, "127.0.0.1", ready)); const port = server.address().port; return { server, manager, filed, origin: `http://127.0.0.1:${port}`, wsOrigin: `ws://127.0.0.1:${port}` };}
// Everything or nothing. The game makes two kinds of sound and they leave by// different doors: drums and hits go through WebAudio, which headless Chrome// renders to no device at all, while "FIGHT", "K.O." and the fighters' names// go to `speechSynthesis`, which on macOS is the PLATFORM's voice and comes// out of the real speakers whatever Chrome is doing. So an automated run used// to announce every round out loud over a fight that was otherwise silent.// A test that runs in the background is muted on both doors; `--audio` opens// both, which means a real window, because WebAudio needs a device.export const launchBrowser = (left, { headful = false, audible = false } = {}) => puppeteer.launch({ executablePath: chrome, headless: headful || audible ? false : "new", args: ["--autoplay-policy=no-user-gesture-required", "--no-first-run", ...(audible ? [] : ["--mute-audio"]), "--window-size=900,560", `--window-position=${left},60`] });
// Each seat gets its OWN browser, not another tab: a background tab's frame// driver parks and settles its owed ticks in one-second bursts, so two tabs// in one window spend most of their ticks holding for each other and read// like a netcode fault that is really a harness artifact.//// The shell hardcodes the production relay, so each page is handed a local// one before any of its script runs.export async function openSeat(browser, origin, wsOrigin, path, label, { audible = false } = {}) { const page = (await browser.pages())[0] || await browser.newPage(); await page.setViewport({ width: 900, height: 560 }); page.on("pageerror", (error) => console.log(` [${label}] page error: ${error.message}`)); page.on("console", (message) => { const text = message.text(); if (/error|desync|NET_(END|BEGIN)/i.test(text)) console.log(` [${label}] ${text}`); }); await page.evaluateOnNewDocument((local) => { const Native = WebSocket; window.WebSocket = function Patched(url, ...rest) { return new Native(String(url) .replace("wss://session-server.aesthetic.computer", local), ...rest); }; window.WebSocket.prototype = Native.prototype; Object.assign(window.WebSocket, { CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3 }); window.__netplayTelemetry = []; }, wsOrigin); // `--mute-audio` silences Chrome's own graph but not the platform's voice, // which is a different service entirely; this is the other half of the // switch. The object stays in place so the voice module still finds it. if (!audible) await page.evaluateOnNewDocument(() => { const quiet = function speak() {}; try { SpeechSynthesis.prototype.speak = quiet; } catch (_) {} try { speechSynthesis.speak = quiet; } catch (_) {} }); await page.goto(origin + path, { waitUntil: "domcontentloaded" }); return page;}
export const readStats = (page) => page.evaluate(() => ({ net: globalThis.__oskiewarNetStats || null, room: globalThis.__oskiewarVersusRoom || "", touchScreen: globalThis.__oskiewarTouch?.screen || "", seat: globalThis.__oskiewarRoundBridge?.seat || "", live: globalThis.__oskiewarRoundBridge?.live === true, account: globalThis.__oskiewarAccount?.handle || "",}));
// Hold each key well past one frame: the shell samples at 60 Hz and a// sub-frame synthetic press is missed (this is documented behaviour).export async function play(page, script, beat = 180) { for (const keys of script) { for (const key of keys) await page.keyboard.down(key); await wait(beat); for (const key of keys) await page.keyboard.up(key); await wait(60); }}