191 lines
10 KiB
JavaScript
191 lines
10 KiB
JavaScript
// Reproduce the user's "Load Game after a started game" flow with REAL
|
|
// CDP input events on the actual index.html.
|
|
//
|
|
// node dev/load-repro.mjs [step=full]
|
|
//
|
|
// Steps:
|
|
// 1. load index.html, wait for the menu
|
|
// 2. click "New Game" -> GameScene boots
|
|
// 3. MENU -> sub-bar -> Save Game -> click slot 3 -> close (CANCEL)
|
|
// 4. fly the ship a bit (WASD held)
|
|
// 5. MENU -> sub-bar -> Load Game -> click slot 3 -> click LOAD (confirm)
|
|
// 6. report which scene is active; if Menu: click Load Game -> slot 3 -> confirm
|
|
// 7. report final scene + ship position vs the save
|
|
import { launch, CDP } from './cdpshot.mjs';
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
const URL = process.argv[2] || 'http://localhost:8080/index.html';
|
|
const { url: wsUrl, kill } = await launch(URL, 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) => {
|
|
const txt = (p.args ?? []).map((a) => a.value ?? a.description ?? '').join(' ');
|
|
if (p.type === 'error' || p.type === 'warning') errors.push(`${p.type}: ${txt}`.slice(0, 400));
|
|
});
|
|
cdp.on('Runtime.exceptionThrown', (p) => {
|
|
errors.push('EXCEPTION: ' + (p.exceptionDetails?.exception?.description ?? p.exceptionDetails?.text ?? '').slice(0, 400));
|
|
});
|
|
|
|
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;
|
|
};
|
|
|
|
// Real mouse click in CLIENT coords (the canvas rect space).
|
|
async function click(clientX, clientY) {
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: clientX, y: clientY });
|
|
await sleep(60);
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mousePressed', x: clientX, y: clientY, button: 'left', clickCount: 1 });
|
|
await sleep(90);
|
|
await cdp.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x: clientX, y: clientY, button: 'left', clickCount: 1 });
|
|
await sleep(60);
|
|
}
|
|
|
|
// Map a point in the game's 1280x720 design space to client (screen) coords.
|
|
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));
|
|
console.log(` click game(${x},${y}) -> client(${cx.toFixed(0)},${cy.toFixed(0)})`);
|
|
await click(cx, cy);
|
|
}
|
|
|
|
const log = (...a) => console.log(...a);
|
|
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()) {
|
|
out.ship = gs.ship ? { x: Math.round(gs.ship.x * 10) / 10, y: Math.round(gs.ship.y * 10) / 10 } : null;
|
|
out.minerals = gs.ship ? gs.ship.minerals : null;
|
|
out.seed = gs.galaxy ? gs.galaxy.seed : null;
|
|
out.sys = gs.systemRecord ? gs.systemRecord.id : null;
|
|
}
|
|
return out;
|
|
})()`);
|
|
|
|
log('== waiting for the menu ==');
|
|
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(4200); // intro beats (revealAt 3.2s + button rises)
|
|
|
|
log('== menu buttons ==');
|
|
const mb = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); return {
|
|
continue: [m.continueBtn.x, m.continueBtn.y, m.continueBtn.disabled],
|
|
newGame: [m.newGameBtn.x, m.newGameBtn.y, m.newGameBtn.disabled],
|
|
loadGame: [m.loadGameBtn.x, m.loadGameBtn.y, m.loadGameBtn.disabled],
|
|
}; })()`);
|
|
log(' ', JSON.stringify(mb));
|
|
log(' bank:', JSON.stringify(await ev(`new (require?null:null)`).catch(() => null) ?? ''));
|
|
const bank = await ev(`(() => { try { const raw = localStorage.getItem('orbit.saves.v1'); return raw ? JSON.parse(raw).slots && Object.keys(JSON.parse(raw).slots) : null; } catch(e) { return 'ERR ' + e.message; } })()`);
|
|
log(' save bank slots:', JSON.stringify(bank));
|
|
|
|
log('== 1) New Game ==');
|
|
await clickGame(mb.newGame[0], mb.newGame[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()));
|
|
|
|
const gs = () => 'window.game.scene.getScene("GameScene")';
|
|
|
|
log('== 2) save slot 3 (MENU -> Save Game -> slot 3 -> CANCEL) ==');
|
|
const menuBtn = await ev(`(() => { const gs = ${gs()}; const s = gs.actionBar?.slots?.find((s) => s.id === 'menu'); return s ? [s.slot.x, s.slot.y] : null; })()`);
|
|
log(' MENU slot at', JSON.stringify(menuBtn));
|
|
await clickGame(menuBtn[0], menuBtn[1]);
|
|
await sleep(700);
|
|
const sub = await ev(`(() => { const gs = ${gs()}; 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 buttons (abs):', JSON.stringify(sub));
|
|
const saveBtn = sub.find((b) => b[0] === 'save');
|
|
await clickGame(saveBtn[1], saveBtn[2]);
|
|
await sleep(1600); // crack open + stagger
|
|
const diag1 = await ev(`(() => { const gs = ${gs()}; return { sub: gs.menuSubBar.isOpen, panel: gs.savePanel.isOpen, state: gs.savePanel.state }; })()`);
|
|
log(' after Save-Game click:', JSON.stringify(diag1));
|
|
const card = await ev(`(() => { const gs = ${gs()}; const c = gs.savePanel.cards[2]; return [c.x, c.y, c.disabled, c.alpha]; })()`);
|
|
log(' slot-3 card at (panel-local)', JSON.stringify(card));
|
|
const panelPos = await ev(`(() => { const gs = ${gs()}; return [gs.savePanel.x, gs.savePanel.y]; })()`);
|
|
await clickGame(panelPos[0] + card[0], panelPos[1] + card[1]);
|
|
await sleep(700);
|
|
const saved = await ev(`(() => { const gs = ${gs()}; const r = gs.saveManager.get(3); return r ? { x: r.ship.x, y: r.ship.y, seed: r.seed } : null; })()`);
|
|
log(' slot 3 now holds:', JSON.stringify(saved));
|
|
if (!saved) { log(' DIAG panel:', JSON.stringify(await ev(`(() => { const gs = ${gs()}; return { state: gs.savePanel.state, mode: gs.savePanel.mode, cards: gs.savePanel.cards.map((c) => [c.alpha, c.disabled, c.input?.enabled ?? c.panel?.input?.enabled ]) }; })()`))); throw new Error('save did not land in slot 3'); }
|
|
const savedShip = { x: saved.x, y: saved.y };
|
|
// CANCEL closes the panel
|
|
const cancel = await ev(`(() => { const gs = ${gs()}; const b = gs.savePanel.cancelBtn; return [gs.savePanel.x + b.x, gs.savePanel.y + b.y, b.panel.input?.enabled]; })()`);
|
|
log(' CANCEL at', JSON.stringify(cancel));
|
|
await clickGame(cancel[0], cancel[1]);
|
|
await sleep(700);
|
|
log(' panel closed:', await ev(`!${gs()}.savePanel.isOpen`));
|
|
|
|
log('== 3) fly the ship away (hold W + A for ~2.5 s) ==');
|
|
await ev(`(() => { const gs = ${gs()}; gs.ship._reproThrust = true; })()`);
|
|
// simplest: nudge the ship directly (no flight model coupling)
|
|
await ev(`(() => { const gs = ${gs()}; gs.ship.x += 420; gs.ship.y -= 260; gs.ship.update?.(0, 16); return [gs.ship.x, gs.ship.y]; })()`);
|
|
log(' ship moved to:', JSON.stringify(await ev(`(() => { const gs = ${gs()}; return [Math.round(gs.ship.x), Math.round(gs.ship.y)]; })()`)));
|
|
await sleep(600);
|
|
|
|
log('== 4) in-game LOAD (MENU -> Load Game -> slot 3 -> confirm) ==');
|
|
await clickGame(menuBtn[0], menuBtn[1]);
|
|
await sleep(700);
|
|
const sub2 = await ev(`(() => { const gs = ${gs()}; 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 buttons (abs):', JSON.stringify(sub2));
|
|
const loadBtn = sub2.find((b) => b[0] === 'load');
|
|
if (loadBtn[3]) throw new Error('Load Game is DISABLED in the sub-bar (hasAny=false?)');
|
|
await clickGame(loadBtn[1], loadBtn[2]);
|
|
await sleep(1400);
|
|
const card3 = await ev(`(() => { const gs = ${gs()}; const c = gs.savePanel.cards[2]; return [c.x, c.y, c.disabled]; })()`);
|
|
await clickGame(panelPos[0] + card3[0], panelPos[1] + card3[1]);
|
|
await sleep(600);
|
|
const confirm = await ev(`(() => { const gs = ${gs()}; const c = gs.savePanel.confirm; if (!c.isOpen) return null; return { isOpen: c.isOpen, title: c.title?.text, x: c.x + c.confirmBtn.x, y: c.y + c.confirmBtn.y }; })()`);
|
|
log(' confirm overlay:', JSON.stringify(confirm));
|
|
if (confirm && confirm.x != null) {
|
|
await clickGame(confirm.x, confirm.y);
|
|
}
|
|
await sleep(1500);
|
|
log(' AFTER in-game load confirm -> scene state:', JSON.stringify(await sceneState()));
|
|
log(' console errors so far:', errors.slice(-6));
|
|
|
|
log('== 5) if we are at the menu: Load Game -> slot 3 -> confirm ==');
|
|
const atMenu = await ev(`window.game.scene.isActive('MenuScene')`);
|
|
if (atMenu) {
|
|
await sleep(1500); // menu intro
|
|
const mb2 = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); return [m.loadGameBtn.x, m.loadGameBtn.y, m.loadGameBtn.disabled]; })()`);
|
|
log(' menu Load Game at', JSON.stringify(mb2));
|
|
if (mb2[2]) throw new Error('menu Load Game DISABLED');
|
|
await clickGame(mb2[0], mb2[1]);
|
|
await sleep(1400);
|
|
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 at', JSON.stringify(mcard));
|
|
await clickGame(mcard[0], mcard[1]);
|
|
await sleep(600);
|
|
const mconf = await ev(`(() => { const m = window.game.scene.getScene('MenuScene'); const c = m.savePanel.confirm; if (!c.isOpen) return null; return { isOpen: c.isOpen, title: c.title?.text, x: c.x + c.confirmBtn.x, y: c.y + c.confirmBtn.y }; })()`);
|
|
log(' menu confirm:', JSON.stringify(mconf));
|
|
if (mconf && mconf.x != null) await clickGame(mconf.x, mconf.y);
|
|
await sleep(2000);
|
|
log(' AFTER menu load confirm -> scene state:', JSON.stringify(await sceneState()));
|
|
} else {
|
|
log(' (not at menu — checking we are in GameScene with the restored ship)');
|
|
}
|
|
|
|
const final = await sceneState();
|
|
log('== FINAL ==', JSON.stringify(final));
|
|
log(' saved ship was:', JSON.stringify(savedShip));
|
|
log(' console errors:', errors.slice(-10));
|
|
|
|
cdp.close();
|
|
kill();
|