orbit/dev/escort-compass-click.mjs

141 lines
6.3 KiB
JavaScript

// dev/escort-compass-click.mjs — REPRO: the escort's FINAL-STOP compass box,
// clicked in a REAL BROWSER (Playwright + vendored Phaser, true per-frame
// order). Run:
//
// node dev/server.mjs 8123 &
// node dev/escort-compass-click.mjs
//
// Sets an escort contract whose destination is the CURRENT system's planet
// (off-screen), waits for the compass to stand its magenta entry, then
// CLICKS the chip with a real mouse event and asserts the ship retargets
// to the planet's rim.
const PORT = process.env.ORBIT_PORT || '8123';
const BASE = `http://127.0.0.1:${PORT}`;
const { pathToFileURL } = await import('node:url');
const { chromium } = await import(pathToFileURL('/home/brianfertig/.npm/_npx/e41f203b7505f1fb/node_modules/playwright/index.mjs').href);
const consoleErrors = [];
const browser = await chromium.launch({
headless: true,
executablePath: '/home/brianfertig/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome',
args: ['--no-sandbox', '--use-gl=swiftshader', '--enable-unsafe-swiftshader'],
});
const page = await browser.newPage();
page.on('console', (m) => { if (m.type() === 'error') consoleErrors.push(m.text()); });
page.on('pageerror', (e) => consoleErrors.push(`pageerror: ${e.message}`));
await page.goto(`${BASE}/index.html`, { waitUntil: 'load' });
await page.waitForFunction(() => !!(window.game && window.game.scene && window.game.scene.getScene && window.game.scene.getScene('MenuScene')), null, { timeout: 20000 });
await page.waitForFunction(() => {
const m = window.game?.scene?.getScene?.('MenuScene');
return !!(m && m.overlay && m.title && m.seedValue && !m.dead);
}, null, { timeout: 20000 });
await page.evaluate(() => {
const m = window.game.scene.getScene('MenuScene');
if (!m.seedValue) m.seedValue = 'ESCORT01';
m.startNewGame();
});
await page.waitForFunction(() => {
const s = window.game.scene.getScene('GameScene');
return !!(s && s.systemRecord && s.ship);
}, null, { timeout: 40000 });
console.log(' ✓ game booted, GameScene active');
// ---- stand the escort contract: destination = a planet of the current system
const setup = await page.evaluate(() => {
const s = window.game.scene.getScene('GameScene');
const sysId = s.systemRecord.id;
const p = (s.systemPlanets ?? [])[0];
if (!p) return { error: 'no planets in the current system' };
// Park the ship far from the planet so the planet is off-screen.
const away = Math.hypot(p.x, p.y) > 10 ? 1 : -1;
s.ship.x = p.x + away * 3000; s.ship.y = p.y + 3000;
s.ship.target = null;
s.ship.thrustDir = null;
const st = s.sideQuestState;
const rec = (s.systemContent.planets ?? []).find((o) => o && o.name === p.discoveryName) ?? null;
st.accept('repro-job');
const route = {
kind: 'escort',
targetSystemId: sysId,
waypoints: [[10, 10]],
credits: 50, xp: 5,
escort: {
phase: 'transit',
originSystemId: 'sys-elsewhere',
destSystemId: sysId,
destObjectType: 'planet',
destObjectId: rec ? rec.id : '',
destObjectName: p.discoveryName,
hops: 1,
transportMaxHull: 1200, transportHull: 1200,
transportMaxShields: 500, transportShields: 500,
escortCount: 1, name: 'Repro Convoy',
},
};
st.setRoute('repro-job', route);
st.setActive('repro-job');
return { planet: { x: p.x, y: p.y, name: p.discoveryName, discoveryId: p.discoveryId }, ship: { x: s.ship.x, y: s.ship.y }, activeId: st.activeId };
});
console.log(' setup:', JSON.stringify(setup));
// Wait for the compass to stand the escort's entry.
await page.waitForFunction(() => {
const s = window.game.scene.getScene('GameScene');
const keys = [...s.compass.entries.keys()];
return keys.some((x) => x.startsWith('escort:obj:'));
}, null, { timeout: 15000 }).catch(() => null);
const entry = await page.evaluate(() => {
const s = window.game.scene.getScene('GameScene');
const k = [...s.compass.entries.keys()].find((x) => x.startsWith('escort:obj:'));
if (!k) return null;
const e = s.compass.entries.get(k);
return { id: k, chip: { x: e.chipRoot.x, y: e.chipRoot.y }, target: { x: e.x, y: e.y } };
});
if (!entry) {
const diag = await page.evaluate(() => {
const s = window.game.scene.getScene('GameScene');
return { keys: [...s.compass.entries.keys()], t: s.escortCompassTarget ? s.escortCompassTarget() : null };
});
console.log(' ✗ NO COMPASS ENTRY', JSON.stringify(diag), '\n CONSOLE', consoleErrors.slice(0, 10));
await browser.close();
process.exit(1);
}
console.log(` ✓ compass entry stands: ${entry.id} at chip (${Math.round(entry.chip.x)}, ${Math.round(entry.chip.y)})`);
// ---- CLICK the chip with a real mouse event -------------------------------
// Isolate the chain from comm timing: in transit the escort lane can fire a
// wave → an OAC comm starts and (by design) owns the input, so a click in
// that window is a legitimate no-op. Stub the gate to false to test the
// COMPASS → AUTOPILOT chain itself.
await page.evaluate(() => {
const s = window.game.scene.getScene('GameScene');
s._commInputBlocked = () => false;
s.__setTargetCalls = [];
const oSet = s.ship.setTarget.bind(s.ship);
s.ship.setTarget = (x, y) => { s.__setTargetCalls.push({ x, y }); return oSet(x, y); };
});
await page.mouse.move(entry.chip.x, entry.chip.y);
await page.mouse.down();
await page.mouse.up();
await page.waitForTimeout(300);
// The REGRESSION assertion for the user's bug: the chip click must REACH
// autopilotTo and issue a setTarget (before the fix the whole chain was a
// silent no-op — the compass stored the content RECORD id and the
// discovery lookup missed). Whether the target SURVIVES a frame later is
// up to the rest of the game (a comm's pause contract legitimately stops
// the ship — that is orthogonal to this chain).
const result = await page.evaluate(() => {
const s = window.game.scene.getScene('GameScene');
return { calls: s.__setTargetCalls, target: s.ship.target, state: s.ship.state };
});
console.log(' click result:', JSON.stringify(result));
const ok = result.calls.length > 0;
console.log(ok
? ` ✓ chip click drove the autopilot → setTarget(${result.calls.at(-1).x?.toFixed(1)}, ${result.calls.at(-1).y?.toFixed(1)}) — the destination world's rim (tether-clamped)`
: ' ✗ NO RETARGET — the click did nothing');
if (consoleErrors.length) console.log(' CONSOLE ERRORS\n ' + consoleErrors.slice(0, 10).join('\n '));
await browser.close();
process.exit(ok ? 0 : 1);