128 lines
6.0 KiB
JavaScript
128 lines
6.0 KiB
JavaScript
/**
|
|
* PROBE 8 (minimal, CDP-safe): in the phase-B state (neutrals-only, escorts
|
|
* parked at centre, sim frozen), what is RED at the bottom-centre edge?
|
|
* Returns ONLY the red-pixel count + a coarse colour histogram — no Phaser
|
|
* objects (avoids the CDP "reference chain too long" serialisation error).
|
|
* Run: node dev/enc-probe8.mjs
|
|
*/
|
|
const CDP = process.env.CDP_PORT ? `127.0.0.1:${process.env.CDP_PORT}` : '127.0.0.1:9333';
|
|
const BASE = process.env.BASE_URL ?? 'http://127.0.0.1:8080';
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
function connect(target) {
|
|
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
let id = 0;
|
|
const pending = new Map();
|
|
ws.onmessage = (x) => {
|
|
const m = JSON.parse(x.data);
|
|
if (m.id && pending.has(m.id)) {
|
|
const { res, rej } = pending.get(m.id);
|
|
pending.delete(m.id);
|
|
m.error ? rej(new Error(m.error.message)) : res(m.result);
|
|
}
|
|
};
|
|
const open = new Promise((r) => { ws.onopen = r; ws.onerror = () => r(); });
|
|
const send = (method, params = {}) => new Promise((res, rej) => {
|
|
const mid = ++id; pending.set(mid, { res, rej });
|
|
ws.send(JSON.stringify({ id: mid, method, params }));
|
|
});
|
|
return { ws, send, open };
|
|
}
|
|
async function evalJs(c, expression, awaitPromise = true) {
|
|
const r = await c.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise });
|
|
if (r.exceptionDetails) throw new Error(JSON.stringify(r.exceptionDetails, null, 2));
|
|
return r.result?.value;
|
|
}
|
|
async function openPage(path) {
|
|
const url = `${BASE}${path}`;
|
|
let target;
|
|
try {
|
|
target = await (await fetch(`http://${CDP}/json/new?${encodeURIComponent(url)}`, { method: 'PUT' })).json();
|
|
} catch {
|
|
target = await (await fetch(`http://${CDP}/json/new?${encodeURIComponent(url)}`, { method: 'POST' })).json();
|
|
}
|
|
const c = connect(target);
|
|
await c.open;
|
|
await c.send('Runtime.enable');
|
|
return { c, target };
|
|
}
|
|
|
|
const PAGE_CODE = `
|
|
(async () => {
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
const gs = window.game && window.game.scene ? window.game.scene.getScene('GameScene') : null;
|
|
if (!gs || !gs.combat || !gs.ship) return { err: 'no game' };
|
|
const sys = gs.combat; const ship = gs.ship;
|
|
const ren = window.game.renderer; const cv = ren.canvas;
|
|
const off = document.createElement('canvas'); off.width = cv.width; off.height = cv.height;
|
|
const octx = off.getContext('2d', { willReadFrequently: true });
|
|
if (!window.__p8hook) {
|
|
window.__p8hook = true; const orig = ren.render;
|
|
ren.render = function (...a) { const ret = orig.apply(this, a); try { octx.drawImage(cv, 0, 0); } catch {} return ret; };
|
|
}
|
|
const vw = gs.scale.width, vh = gs.scale.height;
|
|
for (const e of sys.enemies.slice()) e.dead = true;
|
|
for (const a of (sys.allies || [])) a.dead = true;
|
|
gs._spawnEncounter({ kind: 'neutrals', pirates: 0, neutrals: 2 });
|
|
const NB = (sys.allies || []).filter((a) => !a.dead);
|
|
for (const a of NB) { a.maxHull = 1e9; a.hull = 1e9; a.maxShields = 1e9; a.shields = 1e9; }
|
|
const setPos = (o, x, y) => { o.x = x; o.y = y; if (o.body) { o.body.velocity.x = 0; o.body.velocity.y = 0; } };
|
|
setPos(NB[0], ship.x - 130, ship.y + 90);
|
|
setPos(NB[1], ship.x + 110, ship.y - 70);
|
|
if (gs.comms) gs.comms._setPaused(true);
|
|
await sleep(250);
|
|
const RED = (r, g, b) => r > 130 && r - g > 45 && r - b > 25;
|
|
const hist = () => {
|
|
const x = Math.round(vw/2 - 22), y = Math.round(vh - 28 - 22), w = 44, h = 44;
|
|
const d = octx.getImageData(x, y, w, h).data;
|
|
const h2 = {}; let n = 0;
|
|
for (let i = 0; i < d.length; i += 4) {
|
|
const r = d[i], g = d[i+1], b = d[i+2];
|
|
if (RED(r, g, b)) { n++; const k = r + ',' + g + ',' + b; h2[k] = (h2[k] || 0) + 1; }
|
|
}
|
|
return { n, h2 };
|
|
};
|
|
let best = { n: 0, h2: {} };
|
|
for (let f = 0; f < 12; f++) { await sleep(22); const c = hist(); if (c.n > best.n) best = c; }
|
|
// Also sample the OTHER three edges for context.
|
|
const edge = (cx, cy) => {
|
|
const x = Math.round(cx - 22), y = Math.round(cy - 22), w = 44, h = 44;
|
|
const d = octx.getImageData(x, y, w, h).data; let n = 0;
|
|
for (let i = 0; i < d.length; i += 4) { const r = d[i], g = d[i+1], b = d[i+2]; if (RED(r, g, b)) n++; }
|
|
return n;
|
|
};
|
|
const out = { vw, vh, bottom: best, top: edge(vw/2, 28), right: edge(vw-28, vh/2), left: edge(28, vh/2), escortCount: NB.length };
|
|
// Report the two most common red colours as plain strings.
|
|
out.topColors = Object.entries(best.h2).sort((a, b) => b[1] - a[1]).slice(0, 4).map(([k, v]) => k + ' x' + v);
|
|
if (gs.comms) gs.comms._setPaused(false);
|
|
return out;
|
|
})()
|
|
`;
|
|
|
|
async function main() {
|
|
const { c } = await openPage('/index.html');
|
|
try {
|
|
let ready = false;
|
|
for (let i = 0; i < 80; i++) { await sleep(500); try { ready = await evalJs(c, 'Boolean(window.game)', false); } catch { ready = false; } if (ready) break; }
|
|
if (!ready) throw new Error('game never booted');
|
|
const started = await evalJs(c, `(async () => {
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
for (let i = 0; i < 120; i++) {
|
|
const m = window.game && window.game.scene && window.game.scene.getScene('MenuScene');
|
|
if (m && typeof m.seedValue === 'string') { m.startNewGame(); return 'started'; }
|
|
await sleep(500);
|
|
}
|
|
return 'menu never ready';
|
|
})()`);
|
|
if (started !== 'started') throw new Error('start: ' + started);
|
|
let scoped = false;
|
|
for (let i = 0; i < 60; i++) { await sleep(500); try { scoped = await evalJs(c, 'Boolean(window.__ORBIT_COMBAT__ && window.game.scene.getScene("GameScene").ship && window.game.scene.getScene("GameScene").combat)', false); } catch { scoped = false; } if (scoped) break; }
|
|
if (!scoped) throw new Error('combat scope never appeared');
|
|
await sleep(800);
|
|
const out = await evalJs(c, PAGE_CODE);
|
|
if (typeof out === 'string') throw new Error('drive failed: ' + out);
|
|
console.log(JSON.stringify(out, null, 2));
|
|
} finally { try { c.ws.close(); } catch {} }
|
|
}
|
|
main().catch((e) => { console.error('probe8 error:', e.message); process.exit(1); });
|