#!/usr/bin/env node
import assert from 'node:assert/strict';
import { randomBytes } from 'node:crypto';
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { chromium } from 'playwright-core';
import { PNG } from 'pngjs';
import {
  extractDelimitedLogBlock,
  firstSnapshotEvidence,
  makeReleaseIdentity,
  parseServerInfoBlock,
  readReleaseIdentity,
  retainCurrentPhaseEvidence,
  sameReleaseIdentity
} from './e2e-evidence.mjs';

const baseUrl = (process.env.Q4_E2E_URL || 'http://127.0.0.1:3280').replace(/\/$/, '');
const profile = path.resolve(process.env.Q4_E2E_PROFILE || '.selfcheck/full-profile');
const outDir = path.resolve('.selfcheck/shots');
const gamePort = Number(process.env.Q4_E2E_GAME_PORT || 28004);
const chrome = process.env.Q4_CHROME || '/usr/bin/google-chrome';
const engineTimeout = Number(process.env.Q4_E2E_ENGINE_TIMEOUT_MS || 30 * 60_000);
assert.ok(Number.isInteger(engineTimeout) && engineTimeout >= 60_000, 'Q4_E2E_ENGINE_TIMEOUT_MS must be an integer of at least 60000');
const replayIdOverride = String(process.env.Q4_E2E_REPLAY_ID || '').trim() || null;
assert.ok(replayIdOverride === null || /^[A-Za-z0-9_-]{1,512}$/.test(replayIdOverride),
  'Q4_E2E_REPLAY_ID must be a base64url replay id');
const multiplayerMap = String(process.env.Q4_E2E_MULTIPLAYER_MAP || 'mp/q4web_arena').trim();
assert.match(multiplayerMap, /^mp\/[a-z0-9_-]+$/,
  'Q4_E2E_MULTIPLAYER_MAP must be a canonical multiplayer map id');
const validPhases = Object.freeze(['campaign', 'multiplayer', 'replay', 'mobile']);
const phases = (() => {
  if (process.env.Q4_E2E_PHASES === undefined) return [...validPhases];
  const requested = process.env.Q4_E2E_PHASES.split(',').map((phase) => phase.trim()).filter(Boolean);
  assert.ok(requested.length > 0, 'Q4_E2E_PHASES must contain at least one phase');
  const unknown = requested.filter((phase) => !validPhases.includes(phase));
  assert.deepEqual(unknown, [], `unknown Q4_E2E_PHASES values: ${unknown.join(', ')}`);
  return [...new Set(requested)];
})();
const evidencePath = path.resolve('.selfcheck/full-e2e.json');
const multiplayerNicks = Object.freeze(['Q4E2E-1', 'Q4E2E-2']);
const releaseIdentity = await readReleaseIdentity();

function assertVersionedEngineUrl(url, expectedPath, expectedSha256) {
  const parsed = new URL(url, baseUrl);
  assert.equal(parsed.pathname, expectedPath);
  assert.deepEqual([...parsed.searchParams], [['v', expectedSha256]]);
}

async function engineResources(page) {
  return page.evaluate(() => performance.getEntriesByType('resource').map((entry) => {
    const url = new URL(entry.name);
    return `${url.pathname}${url.search}`;
  }));
}

function assertVariantResourceSet(resources, variant, label) {
  const other = variant === 'sp' ? 'mp' : 'sp';
  for (const path of [`/engine/openq4-${variant}.wasm`, `/engine/game-${variant}_wasm.wasm`]) {
    assert.ok(resources.some((resource) => resource.startsWith(`${path}?v=`)),
      `${label} did not fetch versioned ${path}: ${JSON.stringify(resources)}`);
  }
  for (const path of [`/engine/openq4-${other}.wasm`, `/engine/game-${other}_wasm.wasm`]) {
    assert.ok(!resources.some((resource) => resource.startsWith(path)),
      `${label} fetched the isolated ${other.toUpperCase()} artifact ${path}: ${JSON.stringify(resources)}`);
  }
}

const launchArgs = [
  '--no-sandbox',
  '--disable-dev-shm-usage',
  '--enable-unsafe-swiftshader',
  '--use-angle=swiftshader',
  '--use-fake-device-for-media-stream',
  '--use-fake-ui-for-media-stream',
  '--autoplay-policy=no-user-gesture-required'
];

await fs.mkdir(outDir, { recursive: true });
await fs.mkdir(path.dirname(profile), { recursive: true });

function pageDiagnostics(page, label, failures = []) {
  page.on('pageerror', (error) => failures.push(`${label} pageerror: ${error.stack || error}`));
  page.on('console', (message) => {
    if (message.type() === 'error' && !message.text().startsWith('Q4_ENGINE_STDERR ')) {
      failures.push(`${label} console: ${message.text()}`);
    }
  });
  return failures;
}

function assertRenderedPng(buffer, label) {
  const png = PNG.sync.read(buffer);
  const colors = new Set();
  let opaque = 0;
  let nonDark = 0;
  let luminanceSum = 0;
  let luminanceSquareSum = 0;
  let samples = 0;
  // Fixed HTML navigation, voice, and touch controls are composited over the
  // canvas. Sample its center so those overlays cannot make a black renderer
  // look healthy.
  const region = {
    left: Math.floor(png.width * 0.2),
    top: Math.floor(png.height * 0.18),
    right: Math.ceil(png.width * 0.8),
    bottom: Math.ceil(png.height * 0.82)
  };
  const regionPixels = (region.right - region.left) * (region.bottom - region.top);
  const stride = Math.max(1, Math.floor(regionPixels / 60_000));
  let regionPixel = 0;
  for (let y = region.top; y < region.bottom; y += 1) {
    for (let x = region.left; x < region.right; x += 1, regionPixel += 1) {
      if (regionPixel % stride !== 0) continue;
      const offset = (y * png.width + x) * 4;
      const red = png.data[offset];
      const green = png.data[offset + 1];
      const blue = png.data[offset + 2];
      const alpha = png.data[offset + 3];
      if (alpha > 240) opaque += 1;
      const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
      if (luminance > 12) nonDark += 1;
      luminanceSum += luminance;
      luminanceSquareSum += luminance * luminance;
      colors.add(`${red >> 3}:${green >> 3}:${blue >> 3}`);
      samples += 1;
    }
  }
  const mean = luminanceSum / samples;
  const deviation = Math.sqrt(Math.max(0, luminanceSquareSum / samples - mean * mean));
  const evidence = {
    label, width: png.width, height: png.height, region,
    samples, opaque, nonDark, colors: colors.size, mean, deviation
  };
  assert.ok(png.width >= 640 && png.height >= 360, `small canvas: ${JSON.stringify(evidence)}`);
  assert.ok(opaque > samples * 0.98, `transparent canvas: ${JSON.stringify(evidence)}`);
  assert.ok(nonDark > samples * 0.02, `blank/black canvas: ${JSON.stringify(evidence)}`);
  assert.ok(colors.size > 30 && deviation > 4, `flat canvas: ${JSON.stringify(evidence)}`);
  return evidence;
}

async function saveCanvas(page, name) {
  const locator = page.locator('#gameCanvas');
  await locator.waitFor({ state: 'visible' });
  const buffer = await locator.screenshot({ path: path.join(outDir, name), animations: 'disabled' });
  return assertRenderedPng(buffer, name);
}

async function assertAutomaticRetailInstalled(page) {
  const installed = await page.evaluate(async () => {
    const [active, catalog] = await Promise.all([
      window.Q4App?.importer?.getActiveImport(),
      window.Q4App?.importer?.serverRetailCatalog()
    ]);
    return { active, catalog };
  });
  assert.equal(installed.active?.state, 'ready',
    `automatic server retail install is not ready: ${JSON.stringify(installed.active)}`);
  assert.equal(installed.active?.source, 'operator-server');
  assert.equal(installed.active?.revision, installed.catalog?.revision);
  assert.equal(installed.active?.serverFingerprint, installed.catalog?.fingerprint);
  assert.equal(installed.active?.totalBytes, installed.catalog?.totalBytes);
  assert.equal(installed.catalog?.revision, releaseIdentity.retail.revision);
  assert.equal(installed.catalog?.fingerprint, releaseIdentity.retail.fingerprint);
  assert.equal(installed.catalog?.totalBytes, releaseIdentity.retail.totalBytes);
  return installed.active;
}

async function waitForLauncher(page, view) {
  const topbarButton = {
    campaign: 'campaignButton',
    multiplayer: 'multiplayerButton',
    replays: 'replayButton'
  }[view];
  assert.ok(topbarButton, `unsupported launcher view: ${view}`);
  await page.locator('#launcher').waitFor({ state: 'visible', timeout: 120_000 });
  await page.locator(`#${topbarButton}`).click();
  await page.locator(`[data-launcher-panel="${view}"]`).waitFor({ state: 'visible' });
}

async function externallyTimed(promise, timeout, label) {
  let timer;
  try {
    return await Promise.race([
      promise,
      new Promise((_, reject) => {
        timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeout} ms (Node watchdog)`)), timeout);
      })
    ]);
  } finally {
    clearTimeout(timer);
  }
}

async function waitForEngine(page, timeout = engineTimeout, diagnosticScreenshot = null) {
  const deadline = Date.now() + timeout;
  let pendingProbe = null;
  let lastSnapshot = null;
  let lastSignature = null;
  let lastUnresponsiveReport = 0;
  let screenshotCaptured = false;

  while (Date.now() < deadline) {
    if (!pendingProbe) {
      pendingProbe = page.evaluate(() => ({
        active: document.body.classList.contains('engine-active'),
        badge: document.querySelector('#runtimeBadge')?.dataset.level || null,
        title: document.querySelector('#runtimeTitle')?.textContent || null,
        message: document.querySelector('#runtimeMessage')?.textContent || null,
        details: document.querySelector('#runtimeDetails')?.textContent || null,
        logsReady: typeof window.Q4Shell?.logs === 'function',
        logs: typeof window.Q4Shell?.logs === 'function' ? window.Q4Shell.logs().slice(-100) : []
      }));
    }

    const outcome = await Promise.race([
      pendingProbe.then(
        (snapshot) => ({ state: 'resolved', snapshot }),
        (error) => ({ state: 'rejected', error })
      ),
      new Promise((resolve) => setTimeout(() => resolve({ state: 'pending' }), 5_000))
    ]);

    if (outcome.state === 'pending') {
      if (Date.now() - lastUnresponsiveReport >= 30_000) {
        lastUnresponsiveReport = Date.now();
        process.stdout.write(`[engine] page event loop has not answered the heartbeat; last snapshot ${JSON.stringify(lastSnapshot)}\n`);
      }
      continue;
    }
    if (outcome.state === 'rejected') throw outcome.error;

    pendingProbe = null;
    lastSnapshot = outcome.snapshot;
    const signature = JSON.stringify({
      active: lastSnapshot.active,
      badge: lastSnapshot.badge,
      title: lastSnapshot.title,
      message: lastSnapshot.message,
      logsReady: lastSnapshot.logsReady,
      lastLog: lastSnapshot.logs.at(-1) || null
    });
    if (signature !== lastSignature) {
      lastSignature = signature;
      process.stdout.write(`[engine] heartbeat ${signature}\n`);
    }
    if (diagnosticScreenshot && !screenshotCaptured) {
      screenshotCaptured = true;
      try {
        await externallyTimed(
          page.screenshot({ path: diagnosticScreenshot, animations: 'disabled', timeout: 0 }),
          5_000,
          'engine diagnostic screenshot'
        );
      } catch (error) {
        process.stdout.write(`[engine] diagnostic screenshot unavailable: ${error.message}\n`);
      }
    }
    if (lastSnapshot.badge === 'error') {
      throw new Error(`engine reported an error: ${JSON.stringify(lastSnapshot)}`);
    }
    if (lastSnapshot.active && lastSnapshot.logsReady) {
      await assertTextDecoderMemoryCompatibility(page);
      return lastSnapshot;
    }
    await new Promise((resolve) => setTimeout(resolve, 1_000));
  }

  throw new Error(`engine readiness timed out after ${timeout} ms (Node watchdog); last snapshot: ${JSON.stringify(lastSnapshot)}`);
}

async function assertTextDecoderMemoryCompatibility(page) {
  const probe = await page.evaluate(() => {
    const text = 'OpenQ4 fixed memory TextDecoder probe';
    const encoded = new TextEncoder().encode(text);
    const pointer = Module._malloc(encoded.length + 1);
    if (!pointer) throw new Error('could not allocate TextDecoder compatibility probe');
    try {
      Module.HEAPU8.set(encoded, pointer);
      Module.HEAPU8[pointer + encoded.length] = 0;
      return {
        decoded: Module.UTF8ArrayToString(Module.HEAPU8, pointer, encoded.length + 1),
        expected: text,
        resizable: Module.HEAPU8.buffer.resizable === true,
        growable: Module.HEAPU8.buffer.growable === true
      };
    } finally {
      Module._free(pointer);
    }
  });
  assert.equal(probe.decoded, probe.expected, `engine TextDecoder probe failed: ${JSON.stringify(probe)}`);
  assert.equal(probe.resizable, false, `engine heap is resizable: ${JSON.stringify(probe)}`);
  assert.equal(probe.growable, false, `engine heap is growable: ${JSON.stringify(probe)}`);
}

async function assertNoMemoryLockSyscallWarnings(page) {
  const warnings = await page.evaluate(() => window.Q4Shell.logs().filter(
    (line) => /unsupported syscall: __syscall_m(?:un)?lock/.test(line)
  ));
  assert.deepEqual(warnings, [], `web engine called unsupported memory-lock syscalls: ${JSON.stringify(warnings)}`);
}

async function assertNoMissingRuntimeAssets(page, label) {
  const missing = await page.evaluate(() => window.Q4Shell.logs().filter((line) =>
    /InvalidGUI:|Couldn't load (?:gui|model|image)/i.test(String(line))
  ));
  assert.deepEqual(missing, [], `${label} used missing GUI/render assets: ${JSON.stringify(missing)}`);
}

async function status() {
  const response = await fetch(`${baseUrl}/api/status`, { cache: 'no-store' });
  assert.equal(response.status, 200);
  return response.json();
}

async function waitForStatus(predicate, timeout = 30_000) {
  const deadline = Date.now() + timeout;
  let snapshot = null;
  while (Date.now() < deadline) {
    snapshot = await status();
    if (predicate(snapshot)) return snapshot;
    await new Promise((resolve) => setTimeout(resolve, 250));
  }
  throw new Error(`server status condition timed out after ${timeout} ms: ${JSON.stringify(snapshot)}`);
}

async function deployedReleaseIdentity() {
  const [engineResponse, contentResponse, retailResponse, statusResponse] = await Promise.all([
    fetch(`${baseUrl}/engine/engine-manifest.json`, { cache: 'no-store' }),
    fetch(`${baseUrl}/content/baseoq4/manifest.json`, { cache: 'no-store' }),
    fetch(`${baseUrl}/retail/manifest.json`, { cache: 'no-store' }),
    fetch(`${baseUrl}/api/status`, { cache: 'no-store' })
  ]);
  assert.equal(engineResponse.status, 200, 'deployed engine manifest is unavailable');
  assert.equal(contentResponse.status, 200, 'deployed content manifest is unavailable');
  assert.equal(retailResponse.status, 200, 'deployed retail manifest is unavailable');
  assert.equal(statusResponse.status, 200, 'deployed native identity is unavailable');
  const [engineBytes, contentBytes, deployedRetailManifest, deployedStatus] = await Promise.all([
    engineResponse.arrayBuffer(),
    contentResponse.arrayBuffer(),
    retailResponse.json(),
    statusResponse.json()
  ]);
  // The gateway serializes its validated catalog; reconstruct the deterministic
  // staging representation used by tools/stage-retail-manifest.mjs.
  const retailBytes = Buffer.from(`${JSON.stringify(deployedRetailManifest, null, 2)}\n`);
  return makeReleaseIdentity(engineBytes, contentBytes, retailBytes, deployedStatus.native);
}

async function assertReleaseIdentityCurrent() {
  const [localIdentity, deployedIdentity] = await Promise.all([
    readReleaseIdentity(),
    deployedReleaseIdentity()
  ]);
  assert.ok(sameReleaseIdentity(localIdentity, releaseIdentity),
    `release artifacts changed while E2E was running: ${JSON.stringify({ expected: releaseIdentity, actual: localIdentity })}`);
  assert.ok(sameReleaseIdentity(deployedIdentity, releaseIdentity),
    `deployed release does not match this checkout: ${JSON.stringify({ expected: releaseIdentity, actual: deployedIdentity })}`);
}

async function enableFirstSnapshotEvidence(page) {
  await page.addInitScript(() => {
    let moduleValue = globalThis.Module;
    Object.defineProperty(globalThis, 'Module', {
      configurable: true,
      enumerable: true,
      get() {
        return moduleValue;
      },
      set(value) {
        const args = value?.arguments;
        const connectIndex = Array.isArray(args) ? args.indexOf('+connect') : -1;
        const hasVerbose = Array.isArray(args) && args.some((entry, index) => entry === '+set'
          && args[index + 1] === 'net_verbose' && args[index + 2] === '1');
        if (connectIndex >= 0 && !hasVerbose) args.splice(connectIndex, 0, '+set', 'net_verbose', '1');
        moduleValue = value;
      }
    });
  });
}

async function enableStrictTextDecoderGate(context) {
  await context.addInitScript(() => {
    performance.setResourceTimingBufferSize(4096);
    if (!globalThis.TextDecoder) return;
    const originalDecode = TextDecoder.prototype.decode;
    TextDecoder.prototype.decode = function decode(input, options) {
      const buffer = ArrayBuffer.isView(input) ? input.buffer : input;
      if (buffer && (!(buffer instanceof ArrayBuffer) || buffer.resizable || buffer.growable)) {
        throw new TypeError('E2E strict decoder rejects resizable or shared ArrayBuffer input');
      }
      return originalDecode.call(this, input, options);
    };
  });
}

async function waitForFirstSnapshot(page, label, failures = []) {
  // In pinned AsyncClient this exact line follows CS_CONNECTED -> CS_INGAME;
  // AsyncServer only sends the snapshot while its slot is SCS_INGAME.
  await page.waitForFunction(() => typeof window.Q4Shell?.logs === 'function'
    && window.Q4Shell.logs().some((line) => {
      const text = String(line).trim();
      return /^received first snapshot, gameInitId = -?\d+, gameFrame -?\d+ gameTime -?\d+$/.test(text)
        || /^Aborted\(/.test(text)
        || /^Sys_Error:/.test(text)
        || /^ERROR:/.test(text)
        || /^Fatal error:/i.test(text);
    }),
  undefined, { timeout: engineTimeout });
  const logs = await page.evaluate(() => window.Q4Shell.logs());
  const snapshot = firstSnapshotEvidence(logs);
  if (!snapshot) {
    const slug = String(label).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
    await fs.writeFile(path.join(outDir, `q4-${slug}-pre-snapshot.log`), `${logs.join('\n')}\n`);
  }
  assert.ok(snapshot, `${label} aborted before the first-snapshot in-game transition: ${JSON.stringify({
    failures,
    logs: logs.slice(-1000)
  })}`);
  return snapshot;
}

async function captureServerInfo(page) {
  const nonce = `${Date.now()}_${randomBytes(10).toString('hex').toUpperCase()}`;
  const begin = `Q4_E2E_SERVERINFO_${nonce}_BEGIN`;
  const end = `Q4_E2E_SERVERINFO_${nonce}_END`;

  assert.equal(await page.evaluate((marker) => window.Q4EngineAdapter.execute(`echo ${marker}`), begin), true);
  await page.waitForFunction((marker) => window.Q4Shell.logs().some((line) => String(line).trim() === marker), begin,
    { timeout: 30_000 });
  assert.equal(await page.evaluate((port) => window.Q4EngineAdapter.execute(`serverInfo 127.0.0.1:${port}`), gamePort), true);
  await page.waitForFunction((marker) => {
    const logs = window.Q4Shell.logs().map((line) => String(line).trim());
    const beginIndex = logs.lastIndexOf(marker);
    return beginIndex >= 0 && logs.slice(beginIndex + 1)
      .some((line) => /^-?\d+: server\s+\S+\s+- protocol\s+\d+\.\d+\s+-\s+.+$/i.test(line));
  }, begin, { timeout: 45_000 });
  assert.equal(await page.evaluate((marker) => window.Q4EngineAdapter.execute(`echo ${marker}`), end), true);
  await page.waitForFunction(({ beginMarker, endMarker }) => {
    const logs = window.Q4Shell.logs().map((line) => String(line).trim());
    const beginIndex = logs.lastIndexOf(beginMarker);
    return beginIndex >= 0 && logs.slice(beginIndex + 1).includes(endMarker);
  }, { beginMarker: begin, endMarker: end }, { polling: 100, timeout: engineTimeout });

  const logs = await page.evaluate(() => window.Q4Shell.logs());
  const block = extractDelimitedLogBlock(logs, begin, end);
  const parsed = parseServerInfoBlock(block);
  assert.ok(parsed.terminal, `fresh serverInfo response is incomplete: ${JSON.stringify(block)}`);
  return { markers: { begin, end }, block, ...parsed };
}

async function captureClientPlayState(page) {
  const nonce = `${Date.now()}_${randomBytes(10).toString('hex').toUpperCase()}`;
  const begin = `Q4_E2E_CLIENTSTATE_${nonce}_BEGIN`;
  const end = `Q4_E2E_CLIENTSTATE_${nonce}_END`;
  const names = ['ui_autoJoin', 'ui_joined', 'ui_spectate'];

  assert.equal(await page.evaluate((marker) => window.Q4EngineAdapter.execute(`echo ${marker}`), begin), true);
  for (const name of names) {
    assert.equal(await page.evaluate((command) => window.Q4EngineAdapter.execute(command), name), true);
  }
  assert.equal(await page.evaluate((marker) => window.Q4EngineAdapter.execute(`echo ${marker}`), end), true);
  await page.waitForFunction(({ beginMarker, endMarker }) => {
    const logs = window.Q4Shell.logs().map((line) => String(line).trim());
    const beginIndex = logs.lastIndexOf(beginMarker);
    return beginIndex >= 0 && logs.slice(beginIndex + 1).includes(endMarker);
  }, { beginMarker: begin, endMarker: end }, { polling: 100, timeout: engineTimeout });

  const logs = await page.evaluate(() => window.Q4Shell.logs());
  const block = extractDelimitedLogBlock(logs, begin, end);
  const cvars = {};
  for (const line of block) {
    const match = String(line).match(/^"([^"]+)" is:"([^"]*)".*\bdefault:"([^"]*)"/);
    if (match && names.includes(match[1])) cvars[match[1]] = match[2];
  }
  assert.deepEqual(cvars, {
    ui_autoJoin: '1',
    ui_joined: '1',
    ui_spectate: 'Play'
  }, `browser client did not enter active play: ${JSON.stringify({ block, cvars })}`);
  return { markers: { begin, end }, cvars, block };
}

async function campaignSmoke(context) {
  const page = await context.newPage();
  const failures = pageDiagnostics(page, 'campaign');
  let lastEngineStatus = null;
  const engineOutput = [];
  const campaignSignals = {
    map: false,
    bufferedSave: false,
    autosave: false,
    ready: false
  };
  page.on('console', (message) => {
    const marker = 'Q4_E2E_STATUS ';
    if (message.text().startsWith(marker)) {
      lastEngineStatus = message.text().slice(marker.length);
      process.stdout.write(`[campaign] ${lastEngineStatus}\n`);
    }
    if (/^Q4_ENGINE_STD(OUT|ERR) /.test(message.text())) {
      const text = message.text();
      campaignSignals.map ||= /Q4_ENGINE_STDOUT Map:\s+game\/airdefense1/i.test(text);
      campaignSignals.bufferedSave ||= /Q4_ENGINE_STDOUT Browser save staged \d+ bytes in one filesystem write/.test(text);
      campaignSignals.autosave ||= /Q4_ENGINE_STDOUT Saved 'Autosave game\/airdefense1'/.test(text);
      campaignSignals.ready ||= text.includes('Q4_ENGINE_STDOUT Q4_E2E_MAP_READY');
      engineOutput.push(text);
      if (engineOutput.length > 500) engineOutput.shift();
      process.stdout.write(`[campaign] ${text}\n`);
    }
  });
  page.on('response', (response) => {
    const url = new URL(response.url());
    const { pathname } = url;
    if (!/^\/engine\/(engine-manifest\.json|openq4-sp\.(mjs|wasm)|game-sp_wasm\.wasm)$/.test(pathname)) return;
    process.stdout.write(`[campaign] HTTP ${response.status()} ${pathname}${url.search}\n`);
  });
  await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'domcontentloaded', timeout: 120_000 });
  assert.equal(await page.evaluate(() => crossOriginIsolated), true);
  const manifest = await page.evaluate(async () => {
    const response = await fetch('/engine/engine-manifest.json', { cache: 'no-store' });
    if (!response.ok) throw new Error(`engine manifest HTTP ${response.status}`);
    return response.json();
  });
  assert.equal(manifest.schema, 2, `campaign requires schema-2 engine manifest: ${JSON.stringify(manifest)}`);
  const singleplayer = manifest.variants?.singleplayer;
  assert.match(singleplayer?.sha256?.script || '', /^[a-f0-9]{64}$/);
  assert.match(singleplayer?.sha256?.wasm || '', /^[a-f0-9]{64}$/);
  assertVersionedEngineUrl(singleplayer?.script, '/engine/openq4-sp.mjs', singleplayer.sha256.script);
  assertVersionedEngineUrl(singleplayer?.wasm, '/engine/openq4-sp.wasm', singleplayer.sha256.wasm);
  assert.equal(singleplayer?.gameLib?.filename, 'game-sp_wasm.wasm');
  assert.match(singleplayer?.gameLib?.sha256 || '', /^[a-f0-9]{64}$/);
  assertVersionedEngineUrl(singleplayer?.gameLib?.url, '/engine/game-sp_wasm.wasm', singleplayer.gameLib.sha256);
  await page.evaluate(() => {
    const existingModule = window.Module || {};
    window.Module = {
      ...existingModule,
      print(...parts) {
        console.log(`Q4_ENGINE_STDOUT ${parts.join(' ')}`);
        existingModule.print?.(...parts);
      },
      printErr(...parts) {
        console.error(`Q4_ENGINE_STDERR ${parts.join(' ')}`);
        existingModule.printErr?.(...parts);
      }
    };
    const loader = window.Q4App.engineLoader;
    const launch = loader.launch.bind(loader);
    loader.launch = (options) => launch({
      ...options,
      onStatus(status) {
        console.info(`Q4_E2E_STATUS ${JSON.stringify(status)}`);
        options.onStatus?.(status);
      }
    });
  });
  await waitForLauncher(page, 'campaign');
  await page.locator('[data-difficulty="2"]').click();
  await page.locator('#campaignPlayButton').click();
  try {
    await waitForEngine(page, engineTimeout, path.join(outDir, 'q4-campaign-engine-heartbeat.png'));
  } catch (error) {
    error.message += lastEngineStatus ? `; last engine status: ${lastEngineStatus}` : '; no engine status received';
    error.message += `; engine output tail: ${JSON.stringify(engineOutput.slice(-100))}`;
    throw error;
  }
  const campaignLaunch = await page.evaluate(() => window.Q4Shell.launch);
  assert.equal(campaignLaunch.type, 'singleplayer');
  assert.equal(campaignLaunch.difficulty, 2, 'the HTML difficulty selection did not reach the engine launch');
  const imported = await assertAutomaticRetailInstalled(page);
  const commandProbeAccepted = await page.evaluate(() => window.Q4EngineAdapter.execute('echo Q4_E2E_COMMAND_READY'));
  assert.equal(commandProbeAccepted, true);
  await page.waitForFunction(
    () => window.Q4Shell.logs().some((line) => line.includes('Q4_E2E_COMMAND_READY')),
    undefined,
    { timeout: engineTimeout }
  );
  const mapReadyProbeAccepted = await page.evaluate(() => window.Q4EngineAdapter.execute('echo Q4_E2E_MAP_READY'));
  assert.equal(mapReadyProbeAccepted, true);
  await page.waitForFunction(
    () => window.Q4Shell.logs().some((line) => line.includes('Q4_E2E_MAP_READY')),
    undefined,
    { timeout: engineTimeout }
  );
  await assertNoMemoryLockSyscallWarnings(page);
  await page.waitForTimeout(5_000);
  const render = await saveCanvas(page, 'q4-campaign-airdefense1.png');
  const logs = await page.evaluate(() => window.Q4Shell.logs().slice(-300));
  assert.ok(campaignSignals.map, `campaign map signal missing; recent logs:\n${logs.join('\n')}`);
  assert.ok(campaignSignals.bufferedSave,
    `campaign autosave was not flushed as one browser filesystem write:\n${logs.join('\n')}`);
  assert.ok(campaignSignals.autosave,
    `campaign autosave did not complete:\n${logs.join('\n')}`);
  assert.ok(campaignSignals.ready, `campaign completion marker missing from logs:\n${logs.join('\n')}`);
  const resources = await engineResources(page);
  assertVariantResourceSet(resources, 'sp', 'campaign');
  assert.ok(resources.includes(singleplayer.gameLib.url), `versioned SP GameLib was not fetched: ${JSON.stringify(resources)}`);
  assert.ok(resources.includes(singleplayer.wasm), `versioned SP main module was not fetched: ${JSON.stringify(resources)}`);
  assert.deepEqual(failures, [], failures.join('\n'));
  await page.close();
  return {
    import: { revision: imported.revision, state: imported.state },
    engine: {
      schema: manifest.schema,
      build: manifest.build,
      script: singleplayer.script,
      wasm: singleplayer.wasm,
      gameLib: singleplayer.gameLib
    },
    map: 'game/airdefense1',
    signals: campaignSignals,
    engineOutputTail: engineOutput.slice(-100),
    render
  };
}

async function multiplayerSmoke(context) {
  const pages = [];
  const failures = [];
  const inGameClients = [];
  for (let index = 0; index < 2; index += 1) {
    const page = await context.newPage();
    pageDiagnostics(page, `mp${index + 1}`, failures);
    await enableFirstSnapshotEvidence(page);
    pages.push(page);
    const launchUrl = new URL('/', `${baseUrl}/`);
    if (index > 0) launchUrl.searchParams.set('connect', String(gamePort));
    launchUrl.searchParams.set('nick', multiplayerNicks[index]);
    launchUrl.searchParams.set('nodirect', '1');
    await page.goto(launchUrl.href, { waitUntil: 'domcontentloaded', timeout: 120_000 });
    await page.bringToFront();
    await page.waitForFunction(() => document.visibilityState === 'visible', null,
      { polling: 100, timeout: 10_000 });
    let launcherSelection = null;
    let matchResponse = null;
    if (index === 0) {
      await waitForLauncher(page, 'multiplayer');
      await page.waitForFunction(() => {
        const model = document.querySelector('#multiplayerModel');
        const play = document.querySelector('#multiplayerPlayButton');
        return model && model.options.length > 1 && play && !play.disabled;
      }, null, { timeout: 120_000 });
      await page.locator('[data-mode="DM"]').click();
      await page.locator(`[data-map="${multiplayerMap}"]`).click();
      await page.evaluate(() => {
        for (const [selector, value] of [
          ['#multiplayerBots', '8'],
          ['#multiplayerFragLimit', '10'],
          ['#multiplayerTimeLimit', '0']
        ]) {
          const input = document.querySelector(selector);
          input.value = value;
          input.dispatchEvent(new Event('input', { bubbles: true }));
        }
      });
      launcherSelection = await page.evaluate(() => {
        const model = document.querySelector('#multiplayerModel');
        const selected = [...model.options].find((option) => option.value !== 'auto');
        if (!selected) throw new Error('the HTML launcher has no staged player models');
        model.value = selected.value;
        model.dispatchEvent(new Event('change', { bubbles: true }));
        return {
          model: selected.value,
          label: selected.textContent,
          match: window.Q4App.launcher.getState().match
        };
      });
      assert.deepEqual(launcherSelection.match, {
        map: multiplayerMap,
        mode: 'DM',
        botCount: 8,
        fragLimit: 10,
        timeLimit: 0
      }, `HTML launcher did not establish the deterministic MP fixture: ${JSON.stringify(launcherSelection)}`);
      const responsePromise = page.waitForResponse((response) => {
        const url = new URL(response.url());
        return url.pathname === '/api/match' && response.request().method() === 'POST';
      }, { timeout: 180_000 });
      await page.locator('#multiplayerPlayButton').click();
      const response = await responsePromise;
      matchResponse = { status: response.status(), body: await response.json() };
      assert.equal(matchResponse.status, 200, `HTML match request failed: ${JSON.stringify(matchResponse)}`);
    }
    await waitForEngine(page);
    if (index === 0) {
      const engineLaunch = await page.evaluate(() => window.Q4Shell.launch);
      assert.equal(engineLaunch.type, 'multiplayer');
      assert.equal(engineLaunch.profile?.model, launcherSelection.model,
        'the HTML character selection did not reach the engine launch');
      assert.deepEqual(engineLaunch.match, matchResponse.body.match,
        'the engine did not receive the server-normalized HTML match selection');
      assert.deepEqual(matchResponse.body.match, launcherSelection.match,
        'the server changed the deterministic HTML match fixture');
    }
    const firstSnapshot = await waitForFirstSnapshot(page, `multiplayer ${index + 1}`, failures);
    inGameClients.push({ nick: multiplayerNicks[index], ...firstSnapshot });
    assertVariantResourceSet(await engineResources(page), 'mp', `multiplayer ${index + 1}`);
  }

  assert.equal(inGameClients.length, 2, 'both browser clients must receive a first snapshot');
  assert.ok(inGameClients.every((client) => client.clientState === 'CS_INGAME'
    && client.serverStateRequiredToSendSnapshot === 'SCS_INGAME'),
  `both browser clients must reach the in-game snapshot path: ${JSON.stringify(inGameClients)}`);
  for (const page of pages) await assertNoMemoryLockSyscallWarnings(page);

  const activePlayers = [];
  // The Wasm main loop is RAF-driven, so interrogate one foreground tab at a time.
  for (const page of pages) {
    await page.bringToFront();
    await page.waitForFunction(() => document.visibilityState === 'visible', null,
      { polling: 100, timeout: 10_000 });
    activePlayers.push(await captureClientPlayState(page));
  }

  await pages[0].bringToFront();
  const pointerLockPoint = await pages[0].evaluate(() => {
    const canvas = document.querySelector('#gameCanvas');
    const candidates = [
      [0.5, 0.55],
      [0.35, 0.7],
      [0.65, 0.7],
      [0.5, 0.85]
    ];
    for (const [xRatio, yRatio] of candidates) {
      const x = Math.round(innerWidth * xRatio);
      const y = Math.round(innerHeight * yRatio);
      if (document.elementFromPoint(x, y) === canvas) return { x, y };
    }
    throw new Error('no unobscured canvas point is available for pointer lock');
  });
  await pages[0].mouse.click(pointerLockPoint.x, pointerLockPoint.y);
  await pages[0].waitForFunction(() => document.pointerLockElement === document.querySelector('#gameCanvas'), null,
    { timeout: 10_000 });
  await pages[0].keyboard.down('KeyW');
  await pages[0].waitForTimeout(600);
  await pages[0].keyboard.up('KeyW');
  await pages[0].mouse.move(480, 260);
  await pages[0].mouse.move(600, 280, { steps: 6 });
  await pages[0].waitForTimeout(4_000);

  const server = await status();
  const connections = server.connections.filter((entry) => entry.port === gamePort);
  assert.ok(connections.length >= 2, `expected two MP bridges: ${JSON.stringify(server)}`);
  assert.ok(connections.every((entry) => entry.rxFrames > 0 && entry.txFrames > 0), `idle MP bridge: ${JSON.stringify(connections)}`);

  const freshServerInfo = await captureServerInfo(pages[0]);
  const population = {
    ...freshServerInfo,
    botNames: freshServerInfo.clients.filter(({ name }) => /^major_*$/i.test(name)).map(({ name }) => name),
    humanNames: freshServerInfo.clients
      .filter(({ name }) => multiplayerNicks.includes(name))
      .map(({ name }) => name)
  };
  assert.ok(population.botNames.length >= 8, `expected at least eight bots: ${JSON.stringify(population)}`);
  assert.deepEqual(new Set(population.humanNames), new Set(multiplayerNicks),
    `both named browser clients must be present: ${JSON.stringify(population)}`);
  assert.equal(population.botMinPlayers, 8);
  assert.equal(population.maxPlayers, 12);

  let earlyVoiceStatus = null;
  for (let index = 0; index < pages.length; index += 1) {
    const page = pages[index];
    await page.bringToFront();
    await page.waitForFunction(() => document.visibilityState === 'visible', null,
      { polling: 100, timeout: 10_000 });
    await page.evaluate(() => document.exitPointerLock?.());
    await page.waitForFunction(() => document.pointerLockElement === null);
    await page.waitForFunction(() => !document.querySelector('#voiceButton').disabled, null, { timeout: 30_000 });
    try {
      const voicePoint = await page.evaluate(() => {
        const button = document.querySelector('#voiceButton');
        const rect = button.getBoundingClientRect();
        const candidates = [
          [0.5, 0.5],
          [0.25, 0.5],
          [0.75, 0.5],
          [0.5, 0.25],
          [0.5, 0.75]
        ];
        for (const [xRatio, yRatio] of candidates) {
          const x = Math.round(rect.left + rect.width * xRatio);
          const y = Math.round(rect.top + rect.height * yRatio);
          const target = document.elementFromPoint(x, y);
          if (target === button || button.contains(target)) return { x, y };
        }
        const center = document.elementFromPoint(
          Math.round(rect.left + rect.width / 2),
          Math.round(rect.top + rect.height / 2)
        );
        throw new Error(`voice button has no exposed click point: ${JSON.stringify({
          rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
          center: center?.id || center?.className || center?.tagName || null
        })}`);
      });
      await externallyTimed(
        page.mouse.click(voicePoint.x, voicePoint.y),
        35_000,
        `voice client ${index + 1} activation`
      );
      await externallyTimed(
        page.waitForFunction(() => window.Q4App.voice?.voice?.state?.enabled, null, { timeout: 30_000 }),
        35_000,
        `voice client ${index + 1} enabled state`
      );
    } catch (cause) {
      let voiceDiagnostic;
      try {
        voiceDiagnostic = await externallyTimed(page.evaluate(() => {
          const button = document.querySelector('#voiceButton');
          const session = window.Q4App.voice;
          return {
            available: session?.available ?? null,
            selfId: session?.selfId ?? null,
            state: session?.voice?.state ? {
              enabled: session.voice.state.enabled,
              permission: session.voice.state.permission,
              peers: session.voice.state.peers?.map((peer) => ({ ...peer })) || []
            } : null,
            button: button ? {
              disabled: button.disabled,
              state: button.dataset.state,
              pressed: button.getAttribute('aria-pressed')
            } : null,
            mediaDevices: Boolean(navigator.mediaDevices?.getUserMedia),
            visibility: document.visibilityState
          };
        }), 5_000, `voice client ${index + 1} diagnostics`);
      } catch (diagnosticCause) {
        voiceDiagnostic = { diagnosticError: diagnosticCause.message };
      }
      throw new Error(`voice client ${index + 1} did not enable: ${JSON.stringify(voiceDiagnostic)}`, { cause });
    }
    if (index === 0) {
      earlyVoiceStatus = await waitForStatus((snapshot) => {
        const voiceConnections = snapshot.connections.filter((entry) => entry.port === gamePort);
        return voiceConnections.some((entry) => entry.voiceRxFrames > 0)
          && voiceConnections.some((entry) => entry.voiceTxFrames > 0);
      });
      assert.equal(await pages[1].evaluate(() => window.Q4App.voice?.voice?.state?.enabled), false,
        'the second voice client must still be disabled while the early offer is relayed');
    }
  }
  const readVoiceState = (page) => page.evaluate(() => {
    const state = window.Q4App.voice?.voice?.state;
    return state ? {
      enabled: state.enabled,
      permission: state.permission,
      peers: state.peers.map((peer) => ({ ...peer }))
    } : null;
  });
  const projectVoiceServer = (snapshot) => snapshot.connections
    .filter((entry) => entry.port === gamePort)
    .map(({ id, voiceEligible, voiceRxFrames, voiceTxFrames }) => ({ id, voiceEligible, voiceRxFrames, voiceTxFrames }));
  try {
    await Promise.all(pages.map((page) => page.waitForFunction(() => {
      const peers = window.Q4App.voice?.voice?.state?.peers || [];
      return window.Q4App.voice?.voice?.state?.enabled && peers.some((peer) => peer.connected && peer.hasAudio);
    }, null, { polling: 100, timeout: 45_000 })));
  } catch (cause) {
    const [browserStates, serverState] = await Promise.all([
      Promise.all(pages.map(readVoiceState)),
      status()
    ]);
    throw new Error(`two-way voice mesh did not reach connected audio: ${JSON.stringify({
      browserStates,
      serverState: projectVoiceServer(serverState)
    })}`, { cause });
  }
  const [voiceStates, voiceServer] = await Promise.all([
    Promise.all(pages.map(readVoiceState)),
    status()
  ]);
  const voiceConnections = voiceServer.connections.filter((entry) => entry.port === gamePort);
  assert.equal(voiceConnections.length, 2, `expected two voice bridges: ${JSON.stringify(voiceServer)}`);
  assert.ok(voiceConnections.every((entry) => entry.voiceRxFrames > 0 && entry.voiceTxFrames > 0),
    `two-way voice signaling did not traverse both bridges: ${JSON.stringify(voiceConnections)}`);
  for (let index = 0; index < pages.length; index += 1) {
    await assertNoMissingRuntimeAssets(pages[index], `multiplayer ${index + 1}`);
  }

  const evidence = [];
  for (let index = 0; index < pages.length; index += 1) {
    evidence.push(await saveCanvas(pages[index], `q4-multiplayer-${index + 1}.png`));
  }
  const launcherEvidence = await pages[0].evaluate(() => {
    const launch = window.Q4Shell.launch;
    return { match: launch.match, model: launch.profile?.model || 'auto' };
  });
  await pages[0].bringToFront();
  const reloaded = pages[0].waitForEvent('framenavigated');
  await pages[0].keyboard.press('Escape');
  await reloaded;
  await pages[0].locator('#launcher').waitFor({ state: 'visible', timeout: 120_000 });
  const afterEscape = await waitForStatus((snapshot) =>
    snapshot.connections.filter((entry) => entry.port === gamePort).length === 1, 30_000);
  const escapeConnections = afterEscape.connections.filter((entry) => entry.port === gamePort);
  assert.equal(await pages[0].locator('body').evaluate((body) => body.classList.contains('engine-active')), false);
  assert.equal(await pages[0].locator('#launcher').getAttribute('aria-hidden'), 'false');
  assert.deepEqual(failures, [], failures.join('\n'));
  await Promise.all(pages.map((page) => page.close()));
  await waitForStatus((snapshot) =>
    snapshot.connections.filter((entry) => entry.port === gamePort).length === 0, 30_000);
  return {
    bridges: connections.map(({ id, port, rxFrames, txFrames }) => ({ id, port, rxFrames, txFrames })),
    inGameClients,
    activePlayers,
    launcher: launcherEvidence,
    escapeTeardown: { remainingBridges: escapeConnections.length },
    population,
    voice: {
      earlySignalRelayed: Boolean(earlyVoiceStatus),
      states: voiceStates,
      bridges: voiceConnections.map(({ id, voiceRxFrames, voiceTxFrames }) => ({ id, voiceRxFrames, voiceTxFrames }))
    },
    renders: evidence
  };
}

async function replaySmoke(context) {
  const listing = await fetch(`${baseUrl}/api/replays`, { cache: 'no-store' });
  assert.equal(listing.status, 200);
  const { replays } = await listing.json();
  assert.ok(replays?.length, 'the authoritative server has no finalized replay');
  const replayEligible = (entry) => entry?.metadata?.format === 'openq4-server-demo'
    && entry.metadata.version === 1
    && entry.metadata.complete === true
    && Number(entry.metadata.durationMs) >= 90_000;
  const replay = replayIdOverride
    ? replays.find((entry) => entry.id === replayIdOverride)
    : replays.find(replayEligible);
  assert.ok(replay, replayIdOverride
    ? `the requested finalized replay is unavailable: ${replayIdOverride}`
    : 'the authoritative server has no finalized replay lasting at least 90 seconds');
  assert.ok(replayEligible(replay), `the selected replay is incomplete or shorter than 90 seconds: ${JSON.stringify(replay)}`);
  const pages = [];
  const failures = [];

  const openViewer = async (index) => {
    const page = await context.newPage();
    pages.push(page);
    pageDiagnostics(page, `replay${index}`, failures);
    await enableFirstSnapshotEvidence(page);
    const target = index === 1
      ? `${baseUrl}/?nodirect=1`
      : `${baseUrl}/?replay=${encodeURIComponent(replay.id)}&nodirect=1`;
    await page.goto(target, {
      waitUntil: 'domcontentloaded', timeout: 120_000
    });
    await page.bringToFront();
    await page.waitForFunction(() => document.visibilityState === 'visible', null,
      { polling: 100, timeout: 10_000 });
    if (index === 1) {
      await waitForLauncher(page, 'replays');
      const replayItem = page.locator(`[data-replay-id="${replay.id}"]`);
      await replayItem.waitFor({ state: 'visible', timeout: 120_000 });
      await replayItem.click();
      await page.locator('#replayPlayButton').click();
    }
    await waitForEngine(page);
    const firstSnapshot = await waitForFirstSnapshot(page, `replay ${index}`, failures);
    assert.equal(firstSnapshot.clientState, 'CS_INGAME');
    assert.equal(firstSnapshot.serverStateRequiredToSendSnapshot, 'SCS_INGAME');
    assertVariantResourceSet(await engineResources(page), 'mp', `replay ${index}`);
    assert.equal(await page.locator('#voiceButton').isDisabled(), true, 'replay spectators must not join voice chat');
    return { page, firstSnapshot };
  };

  const firstViewer = await openViewer(1);
  const first = firstViewer.page;
  const firstLaunch = await first.evaluate(() => window.Q4Shell.launch);
  assert.equal(firstLaunch.type, 'replay');
  assert.ok(Number.isInteger(firstLaunch.port));
  await first.waitForTimeout(4_000);

  const secondViewer = await openViewer(2);
  const second = secondViewer.page;
  const secondLaunch = await second.evaluate(() => window.Q4Shell.launch);
  assert.equal(secondLaunch.port, firstLaunch.port, 'late viewer must reuse the active playback instance');
  assert.equal(secondLaunch.watch?.reused, true);
  assert.ok(secondViewer.firstSnapshot.gameTime >= firstViewer.firstSnapshot.gameTime + 3_000,
    `late viewer did not join the live replay timeline: ${JSON.stringify({
      first: firstViewer.firstSnapshot,
      second: secondViewer.firstSnapshot
    })}`);
  await second.waitForTimeout(2_000);

  const server = await status();
  const watchers = server.connections.filter((entry) => entry.port === firstLaunch.port);
  assert.ok(watchers.length >= 2, `expected two replay spectators: ${JSON.stringify(server)}`);
  assert.ok(watchers.every((entry) => entry.rxFrames > 0 && entry.txFrames > 0), `inactive replay spectator: ${JSON.stringify(watchers)}`);
  assert.ok(server.replayInstances?.some((entry) => entry.port === firstLaunch.port && entry.ready),
    `playback instance is not ready: ${JSON.stringify(server.replayInstances)}`);

  const evidence = [];
  for (const [page, name] of [
    [first, 'q4-replay-first-viewer.png'],
    [second, 'q4-replay-late-viewer.png']
  ]) {
    await page.bringToFront();
    await page.waitForFunction(() => document.visibilityState === 'visible', null,
      { polling: 100, timeout: 10_000 });
    await page.waitForTimeout(1_000);
    evidence.push(await saveCanvas(page, name));
  }
  for (const page of pages) await assertNoMemoryLockSyscallWarnings(page);
  assert.deepEqual(failures, [], failures.join('\n'));
  await Promise.all(pages.map((page) => page.close()));
  return {
    replay: replay.id,
    port: firstLaunch.port,
    firstSnapshots: [firstViewer.firstSnapshot, secondViewer.firstSnapshot],
    evidence
  };
}

async function mobileSmoke() {
  const context = await chromium.launchPersistentContext(profile, {
    executablePath: chrome,
    headless: true,
    args: launchArgs,
    viewport: { width: 844, height: 390 },
    screen: { width: 844, height: 390 },
    hasTouch: true,
    isMobile: true,
    deviceScaleFactor: 2,
    locale: 'pl-PL'
  });
  await enableStrictTextDecoderGate(context);
  try {
    const page = context.pages()[0] || await context.newPage();
    const failures = pageDiagnostics(page, 'mobile');
    const baselineConnectionIds = new Set((await status()).connections
      .filter((entry) => entry.port === gamePort)
      .map((entry) => entry.id));
    await page.goto(`${baseUrl}/?connect=${gamePort}&nodirect=1`, { waitUntil: 'domcontentloaded', timeout: 120_000 });
    await waitForEngine(page);
    assertVariantResourceSet(await engineResources(page), 'mp', 'mobile multiplayer');
    await page.waitForFunction(() => document.querySelector('#touchOverlay').getAttribute('aria-hidden') === 'false');

    const before = await waitForStatus((snapshot) => snapshot.connections.some(
      (entry) => entry.port === gamePort && !baselineConnectionIds.has(entry.id)
    ), 60_000);
    const connection = before.connections.find(
      (entry) => entry.port === gamePort && !baselineConnectionIds.has(entry.id)
    );
    assert.ok(connection, `mobile bridge did not appear: ${JSON.stringify(before)}`);
    const fire = page.locator('[data-action="fire"]');
    const box = await fire.boundingBox();
    assert.ok(box);
    await page.waitForFunction(() => typeof globalThis.Module?.ccall === 'function');
    const cdp = await context.newCDPSession(page);
    const touchPoint = {
      x: box.x + box.width / 2,
      y: box.y + box.height / 2,
      id: 1,
      radiusX: 4,
      radiusY: 4,
      force: 1
    };
    await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [touchPoint] });
    try {
      await page.waitForFunction(() => document.querySelector('[data-action="fire"]')?.dataset.active === 'true'
        && globalThis.Module.ccall('Q4_WebInputState', 'number', [], []) === 1);
    } finally {
      await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
      await cdp.detach();
    }
    await page.waitForFunction(() => document.querySelector('[data-action="fire"]')?.dataset.active === 'false'
      && globalThis.Module.ccall('Q4_WebInputState', 'number', [], []) === 0);
    await page.waitForTimeout(1_500);
    const after = await status();
    const updated = after.connections.find((entry) => entry.id === connection.id);
    assert.ok(updated?.rxFrames > connection.rxFrames, `touch input produced no network traffic: ${JSON.stringify({ connection, updated })}`);
    const evidence = await saveCanvas(page, 'q4-multiplayer-mobile.png');
    await assertNoMemoryLockSyscallWarnings(page);
    assert.deepEqual(failures, [], failures.join('\n'));
    return evidence;
  } finally {
    await context.close();
  }
}

async function existingEvidence() {
  try {
    const parsed = JSON.parse(await fs.readFile(evidencePath, 'utf8'));
    return parsed && typeof parsed === 'object' ? parsed : {};
  } catch (error) {
    if (error?.code === 'ENOENT') return {};
    throw error;
  }
}

const previous = await existingEvidence();
const retained = retainCurrentPhaseEvidence(previous, releaseIdentity, validPhases, { baseUrl, gamePort });
const evidence = retained.evidence;
const phaseChecks = retained.phaseChecks;
const invalidatedPhases = retained.invalidatedPhases;

const startedAt = new Date().toISOString();
const completedPhases = [];
let runStatus = 'running';
let runError = null;
let deployedReleaseVerified = false;

async function writeEvidence() {
  const checkedAt = new Date().toISOString();
  const releaseComplete = validPhases.every((phase) => Object.hasOwn(evidence, phase)
    && sameReleaseIdentity(phaseChecks[phase]?.releaseIdentity, releaseIdentity));
  const payload = {
    ...previous,
    schema: 4,
    checkedAt,
    baseUrl,
    gamePort,
    releaseIdentity,
    run: {
      startedAt,
      checkedAt,
      status: runStatus,
      releaseComplete,
      engineTimeoutMs: engineTimeout,
      releaseIdentitySha256: releaseIdentity.identitySha256,
      deployedReleaseVerified,
      requestedPhases: phases,
      completedPhases,
      skippedPhases: validPhases.filter((phase) => !phases.includes(phase)),
      invalidatedPhases,
      ...(runError ? { error: runError } : {})
    },
    phaseChecks,
    evidence
  };
  const temporary = `${evidencePath}.${process.pid}.tmp`;
  await fs.writeFile(temporary, `${JSON.stringify(payload, null, 2)}\n`);
  await fs.rename(temporary, evidencePath);
}

async function recordPhase(phase, result) {
  await assertReleaseIdentityCurrent();
  evidence[phase] = result;
  phaseChecks[phase] = { checkedAt: new Date().toISOString(), baseUrl, gamePort, releaseIdentity };
  completedPhases.push(phase);
  await writeEvidence();
}

let desktop = null;
async function desktopContext() {
  if (desktop) return desktop;
  desktop = await chromium.launchPersistentContext(profile, {
    executablePath: chrome,
    headless: true,
    args: launchArgs,
    viewport: { width: 1440, height: 900 },
    locale: 'pl-PL'
  });
  await enableStrictTextDecoderGate(desktop);
  for (const page of desktop.pages()) await page.close();
  return desktop;
}

try {
  await assertReleaseIdentityCurrent();
  deployedReleaseVerified = true;
  for (const phase of phases) {
    if (phase === 'campaign') await recordPhase(phase, await campaignSmoke(await desktopContext()));
    if (phase === 'multiplayer') await recordPhase(phase, await multiplayerSmoke(await desktopContext()));
    if (phase === 'replay') await recordPhase(phase, await replaySmoke(await desktopContext()));
    if (phase === 'mobile') {
      if (desktop) {
        await desktop.close();
        desktop = null;
      }
      await recordPhase(phase, await mobileSmoke());
    }
  }
  runStatus = 'passed';
} catch (error) {
  runStatus = 'failed';
  runError = error?.stack || String(error);
  throw error;
} finally {
  if (desktop) await desktop.close();
  await writeEvidence();
}
process.stdout.write(`Q4 engine E2E phases ${phases.join(',')} passed at ${baseUrl}\n`);
