223 lines
12 KiB
JavaScript
223 lines
12 KiB
JavaScript
// Full user-flow repro with REAL CDP clicks on index.html:
|
|
// menu -> New Game -> (ack comms) -> save slot 3 -> fly away
|
|
// -> in-game Load Game (confirm) -> observe
|
|
// -> (if menu) menu Load Game (confirm) -> observe ship vs save
|
|
import { launch, CDP } from './cdpshot.mjs';
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const { url: wsUrl, kill } = await launch('http://localhost:8080/index.html', undefined, 1280, 720, 5000);
|
|
const cdp = new CDP(wsUrl);
|
|
await cdp.open();
|
|
await cdp.send('Runtime.enable');
|
|
await cdp.send('Page.enable');
|
|
const errors = [];
|
|
cdp.on('Runtime.consoleAPICalled', (p) => {
|
|
if (p.type === 'error' || p.type === 'warning') {
|
|
errors.push(`${p.type}: ${(p.args ?? []).map((a) => a.value ?? a.description ?? '').join(' ')}`.slice(0, 300));
|
|
}
|
|
});
|
|
cdp.on('Runtime.exceptionThrown', (p) => errors.push('EXC: ' + (p.exceptionDetails?.exception?.description ?? p.exceptionDetails?.text ?? '').slice(0, 300)));
|
|
|
|
const ev = async (expression) => {
|
|
const r = await cdp.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true });
|
|
if (r.exceptionDetails) throw new Error('eval: ' + (r.exceptionDetails.exception?.description ?? r.exceptionDetails.text));
|
|
return r.result?.value;
|
|
};
|
|
async function click(cx, cy) {
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: cx, y: cy });
|
|
await sleep(70);
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mousePressed', x: cx, y: cy, button: 'left', clickCount: 1 });
|
|
await sleep(100);
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: cx, y: cy, button: 'left', clickCount: 1 });
|
|
await sleep(70);
|
|
}
|
|
const toClient = (x, y) =>
|
|
`(() => { const c = window.game.canvas; const r = c.getBoundingClientRect(); const z = r.width / c.width; return [r.left + ${x} * z, r.top + ${y} * z]; })()`;
|
|
async function clickGame(x, y) {
|
|
const [cx, cy] = await ev(toClient(x, y));
|
|
await click(cx, cy);
|
|
}
|
|
const log = (...a) => console.log(...a);
|
|
const G = 'window.game.scene.getScene("GameScene")';
|
|
const sceneState = () =>
|
|
ev(`(() => {
|
|
const g = window.game;
|
|
const out = { active: g.scene.getScenes(true).map((s) => s.scene.key) };
|
|
const gs = g.scene.getScene('GameScene');
|
|
if (gs && gs.scene.isActive() && gs.ship) {
|
|
out.ship = [Math.round(gs.ship.x * 10) / 10, Math.round(gs.ship.y * 10) / 10];
|
|
out.seed = gs.galaxy ? gs.galaxy.seed : null;
|
|
out.sys = gs.systemRecord ? gs.systemRecord.id : null;
|
|
}
|
|
return out;
|
|
})()`);
|
|
|
|
// ---- boot to the menu, New Game
|
|
let menuUp = false;
|
|
for (let i = 0; i < 120 && !menuUp; i++) { await sleep(500); menuUp = await ev(`!!(window.game && window.game.scene.isActive('MenuScene'))`); }
|
|
if (!menuUp) throw new Error('menu never came up');
|
|
await sleep(4600);
|
|
const mb = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); return [m.newGameBtn.x, m.newGameBtn.y]; })()`);
|
|
await clickGame(mb[0], mb[1]);
|
|
let gameUp = false;
|
|
for (let i = 0; i < 240 && !gameUp; i++) { await sleep(500); gameUp = await ev(`!!(window.game && window.game.scene.isActive('GameScene') && window.game.scene.getScene('GameScene').ship)`); }
|
|
if (!gameUp) throw new Error('GameScene never booted');
|
|
log('game up:', JSON.stringify(await sceneState()));
|
|
|
|
// ---- ack any comms holding the screen until the deck's slots are live
|
|
async function deckLive() {
|
|
return await ev(`(() => {
|
|
const gs = window.game.scene.getScene('GameScene');
|
|
if (!gs || !gs.scene.isActive()) return null;
|
|
const s = gs.actionBar.slots.find((x) => x.id === 'menu');
|
|
return { live: !!(s && s.panel.input && s.panel.input.enabled), commBox: !!(gs.comms?.box), paused: !!(gs.comms?.paused) };
|
|
})()`);
|
|
}
|
|
async function ensureDeck(maxRounds = 12) {
|
|
for (let i = 0; i < maxRounds; i++) {
|
|
const st = await deckLive();
|
|
if (!st) return false;
|
|
if (st.live) return true;
|
|
const btn = await ev(`(() => { const gs = ${G}; const b = gs.comms?.box; return b && b.button ? [b.x + b.button.x, b.y + b.button.y] : null; })()`);
|
|
if (btn) {
|
|
log(` ACK comm button at`, JSON.stringify(btn));
|
|
await clickGame(btn[0], btn[1]);
|
|
}
|
|
await sleep(1500);
|
|
}
|
|
return !!(await deckLive())?.live;
|
|
}
|
|
log('waiting for the deck to be live (acking comms as they hold)…');
|
|
let live = false;
|
|
for (let round = 0; round < 10 && !live; round++) {
|
|
await sleep(2000);
|
|
live = await ensureDeck(2);
|
|
}
|
|
log('deck live:', live);
|
|
if (!live) { log(' state:', JSON.stringify(await deckLive())); }
|
|
|
|
// ---- MENU -> Save Game -> slot 3
|
|
const menuSlot = await ev(`(() => { const gs = ${G}; const s = gs.actionBar.slots.find((s) => s.id === 'menu'); return [s.slot.x, s.slot.y]; })()`);
|
|
async function openSubBar() {
|
|
for (let i = 0; i < 6; i++) {
|
|
await clickGame(menuSlot[0], menuSlot[1]);
|
|
await sleep(600);
|
|
if (await ev(`${G}.menuSubBar?.isOpen`)) return true;
|
|
// maybe a comm grabbed the deck — ack it and retry
|
|
const btn = await ev(`(() => { const gs = ${G}; const b = gs.comms?.box; return b && b.button ? [b.x + b.button.x, b.y + b.button.y] : null; })()`);
|
|
if (btn) { await clickGame(btn[0], btn[1]); await sleep(1200); }
|
|
else await sleep(500);
|
|
}
|
|
return false;
|
|
}
|
|
await clickGame(menuSlot[0], menuSlot[1]);
|
|
await sleep(600);
|
|
log('subbar open:', await ev(`${G}.menuSubBar?.isOpen`));
|
|
if (!(await ev(`${G}.menuSubBar?.isOpen`))) {
|
|
log('retry MENU click');
|
|
await clickGame(menuSlot[0], menuSlot[1]);
|
|
await sleep(600);
|
|
}
|
|
const sub = await ev(`(() => { const gs = ${G}; const bar = gs.menuSubBar; return bar.isOpen ? bar.buttons.map((b) => [b.id, bar.x + b.btn.x, bar.y + b.btn.y, b.btn.disabled]) : null; })()`);
|
|
log('sub-bar:', JSON.stringify(sub));
|
|
const saveBtn = sub.find((b) => b[0] === 'save');
|
|
await clickGame(saveBtn[1], saveBtn[2]);
|
|
// wait for the panel to be fully shown (cards up)
|
|
let shown = false;
|
|
for (let i = 0; i < 40 && !shown; i++) { await sleep(250); shown = (await ev(`${G}.savePanel.state`)) === 'shown'; }
|
|
log('panel state:', await ev(`${G}.savePanel.state`));
|
|
const card = await ev(`(() => { const gs = ${G}; const c = gs.savePanel.cards[2]; return [gs.savePanel.x + c.x, gs.savePanel.y + c.y, c.disabled]; })()`);
|
|
log('slot-3 card (abs):', JSON.stringify(card));
|
|
await clickGame(card[0], card[1]);
|
|
await sleep(900);
|
|
const saved = await ev(`(() => { const gs = ${G}; const r = gs.saveManager.get(3); return r ? { x: r.ship.x, y: r.ship.y, seed: r.seed, savedAt: r.savedAt } : null; })()`);
|
|
log('slot 3 holds:', JSON.stringify(saved));
|
|
if (!saved) throw new Error('save did not land in slot 3');
|
|
// close the panel with CANCEL
|
|
const cancel = await ev(`(() => { const gs = ${G}; const b = gs.savePanel.cancelBtn; return [gs.savePanel.x + b.x, gs.savePanel.y + b.y]; })()`);
|
|
await clickGame(cancel[0], cancel[1]);
|
|
let closed = false;
|
|
for (let i = 0; i < 30 && !closed; i++) { await sleep(200); closed = (await ev(`${G}.savePanel.state`)) === 'hidden'; }
|
|
log('panel closed:', closed);
|
|
await sleep(400);
|
|
|
|
// ---- fly the ship away
|
|
const before = await ev(`(() => { const gs = ${G}; return [gs.ship.x, gs.ship.y]; })()`);
|
|
await ev(`(() => { const gs = ${G}; gs.ship.x += 500; gs.ship.y -= 320; return true; })()`);
|
|
const after = await ev(`(() => { const gs = ${G}; return [gs.ship.x, gs.ship.y]; })()`);
|
|
log(`ship: before=${JSON.stringify(before)} after=${JSON.stringify(after)}`);
|
|
await sleep(500);
|
|
|
|
// ---- in-game LOAD: MENU -> Load Game -> slot 3 -> confirm LOAD
|
|
if (!(await openSubBar())) throw new Error('sub-bar would not open (load step)');
|
|
const sub2 = await ev(`(() => { const gs = ${G}; const bar = gs.menuSubBar; return bar.isOpen ? bar.buttons.map((b) => [b.id, bar.x + b.btn.x, bar.y + b.btn.y, b.btn.disabled]) : null; })()`);
|
|
log('sub-bar:', JSON.stringify(sub2));
|
|
const loadBtn = sub2 && sub2.find((b) => b[0] === 'load');
|
|
if (!loadBtn) throw new Error('no load button in sub-bar');
|
|
if (loadBtn[3]) throw new Error('Load Game DISABLED in sub-bar');
|
|
await clickGame(loadBtn[1], loadBtn[2]);
|
|
let shown2 = false;
|
|
for (let i = 0; i < 40 && !shown2; i++) { await sleep(250); shown2 = (await ev(`${G}.savePanel.state`)) === 'shown'; }
|
|
log('panel state:', await ev(`${G}.savePanel.state`));
|
|
const card3 = await ev(`(() => { const gs = ${G}; const c = gs.savePanel.cards[2]; return [gs.savePanel.x + c.x, gs.savePanel.y + c.y, c.disabled]; })()`);
|
|
await clickGame(card3[0], card3[1]);
|
|
await sleep(900);
|
|
const conf = await ev(`(() => { const gs = ${G}; const c = gs.savePanel.confirm; if (!c || c.state === 'hidden') return null; return { state: c.state, title: c.title?.text, x: gs.savePanel.x + c.x + c.confirmBtn.x, y: gs.savePanel.y + c.y + c.confirmBtn.y }; })()`);
|
|
log('confirm overlay:', JSON.stringify(conf));
|
|
if (!conf) throw new Error('confirm overlay did not open');
|
|
await clickGame(conf.x, conf.y);
|
|
// wait for the outcome: the scene RESTARTS IN PLACE with the staged
|
|
// restore (reloadRun → scene.restart) — ship back at the saved spot.
|
|
let restoredInPlace = false;
|
|
for (let i = 0; i < 60 && !restoredInPlace; i++) {
|
|
await sleep(250);
|
|
const st = await ev(`(() => {
|
|
const gs = window.game.scene.getScene('GameScene');
|
|
return {
|
|
active: window.game.scene.isActive('GameScene'),
|
|
ship: gs && gs.ship ? [gs.ship.x, gs.ship.y] : null,
|
|
};
|
|
})()`);
|
|
restoredInPlace = st.active && st.ship && Math.abs(st.ship[0] - saved.x) < 0.01 && Math.abs(st.ship[1] - saved.y) < 0.01;
|
|
}
|
|
log('AFTER in-game LOAD confirm (in-place restart):', restoredInPlace ? 'RESTORED — ship back at the saved spot' : 'NOT RESTORED', JSON.stringify(await sceneState()));
|
|
log(' errors so far:', errors.slice(-4));
|
|
if (!restoredInPlace) throw new Error('in-game LOAD did not restore the game in place');
|
|
|
|
// ---- now leave to the menu (sub-bar: Return to Main Menu) ---------------
|
|
if (!(await openSubBar())) throw new Error('sub-bar would not open (return step)');
|
|
const subR = await ev(`(() => { const gs = ${G}; const bar = gs.menuSubBar; return bar.buttons.map((b) => [b.id, bar.x + b.btn.x, bar.y + b.btn.y]); })()`);
|
|
const mm = subR.find((b) => b[0] === 'mainMenu');
|
|
if (!mm) throw new Error('no mainMenu button in sub-bar');
|
|
await clickGame(mm[1], mm[2]);
|
|
let atMenu = false;
|
|
for (let i = 0; i < 30 && !atMenu; i++) { await sleep(250); atMenu = await ev(`window.game.scene.isActive('MenuScene')`); }
|
|
if (!atMenu) throw new Error('Return to Main Menu did not land on the menu');
|
|
log('back at the menu (via the sub-bar)');
|
|
|
|
// ---- menu Load Game -> slot 3 -> confirm (the second door) --------------
|
|
await sleep(2500); // menu intro beats
|
|
const mb2 = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); return { lg: [m.loadGameBtn.x, m.loadGameBtn.y, m.loadGameBtn.disabled] }; })()`);
|
|
log('menu Load Game button:', JSON.stringify(mb2.lg));
|
|
if (mb2.lg[2]) throw new Error('menu Load Game DISABLED');
|
|
await clickGame(mb2.lg[0], mb2.lg[1]);
|
|
let shown3 = false;
|
|
for (let i = 0; i < 60 && !shown3; i++) { await sleep(250); shown3 = await ev(`window.game.scene.getScene('MenuScene').savePanel?.state === 'shown'`); }
|
|
log('menu panel state:', await ev(`window.game.scene.getScene('MenuScene').savePanel?.state`));
|
|
const mcard = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); const c = m.savePanel.cards[2]; return [m.savePanel.x + c.x, m.savePanel.y + c.y, c.disabled]; })()`);
|
|
log('menu slot-3 card (abs):', JSON.stringify(mcard));
|
|
await clickGame(mcard[0], mcard[1]);
|
|
await sleep(900);
|
|
const mconf = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); const c = m.savePanel.confirm; if (!c || c.state === 'hidden') return null; return { state: c.state, title: c.title?.text, x: m.savePanel.x + c.x + c.confirmBtn.x, y: m.savePanel.y + c.y + c.confirmBtn.y }; })()`);
|
|
log('menu confirm:', JSON.stringify(mconf));
|
|
if (!mconf) throw new Error('menu confirm overlay did not open');
|
|
await clickGame(mconf.x, mconf.y);
|
|
await sleep(2500);
|
|
log('AFTER menu LOAD confirm:', JSON.stringify(await sceneState()));
|
|
|
|
log('== FINAL ==', JSON.stringify(await sceneState()));
|
|
log(' saved ship was:', JSON.stringify({ x: saved.x, y: saved.y }));
|
|
log(' all console errors/warnings:', errors.length ? errors.slice(-12) : '(none)');
|
|
|
|
cdp.close();
|
|
kill();
|