121 lines
7.0 KiB
JavaScript
121 lines
7.0 KiB
JavaScript
/**
|
|
* Dev-only PROBE: dump the CombatHud graphics commandBuffer structure so I can
|
|
* verify exact draw geometry (arrow triangles / threat circles) in-browser —
|
|
* the robust "renders correctly" signal, less fragile than pixel-box matching.
|
|
* node dev/enc-probe3.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(t) {
|
|
const ws = new WebSocket(t.webSocketDebuggerUrl);
|
|
let id = 0; const p = new Map();
|
|
ws.onmessage = (x) => { const m = JSON.parse(x.data); if (m.id && p.has(m.id)) { const { res, rej } = p.get(m.id); p.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; p.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 t;
|
|
try { t = await (await fetch(`http://${CDP}/json/new?${encodeURIComponent(url)}`, { method: 'PUT' })).json(); }
|
|
catch { t = await (await fetch(`http://${CDP}/json/new?${encodeURIComponent(url)}`, { method: 'POST' })).json(); }
|
|
const c = connect(t); await c.open; await c.send('Runtime.enable');
|
|
return { c, target: t };
|
|
}
|
|
const PAGE = `(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 || !gs.combatHud) return 'no GameScene/combat/ship/combatHud';
|
|
const sys = gs.combat, hud = gs.combatHud;
|
|
const unblock = () => { try { if (gs.comms && gs.comms.paused) gs.comms._ack(); } catch {} };
|
|
unblock();
|
|
const live = () => sys.enemies.filter((e) => !e.dead);
|
|
const allies = () => sys.allies.filter((a) => !a.dead);
|
|
const tough = (o) => { o.maxHull = 1e9; o.hull = 1e9; o.maxShields = 1e9; o.shields = 1e9; };
|
|
const dump = (g) => {
|
|
if (!g) return 'null';
|
|
const cb = g.commandBuffer ?? g.commands ?? null;
|
|
if (!cb) return 'no commandBuffer';
|
|
return Array.from(cb).map((c) => {
|
|
const o = {};
|
|
for (const k of ['type','x','y','width','height','radius','red','green','blue','alpha','src','pt0','pt1','pt2']) if (k in c) o[k] = c[k];
|
|
// triangle vertices are often pt0/pt1/pt2 or inline
|
|
if (c.pt0) o.pt0 = [c.pt0.x, c.pt0.y];
|
|
if (c.pt1) o.pt1 = [c.pt1.x, c.pt1.y];
|
|
if (c.pt2) o.pt2 = [c.pt2.x, c.pt2.y];
|
|
return o;
|
|
});
|
|
};
|
|
const R = {};
|
|
// spawn one off-screen pirate (top) + one off-screen neutral (left) for arrows;
|
|
// then one on-screen pirate + one on-screen neutral for threat.
|
|
gs._spawnEncounter({ kind: 'mixed', pirates: 2, neutrals: 2 });
|
|
for (const e of live()) tough(e); for (const a of allies()) tough(a);
|
|
// put pirate[0] off-screen top, pirate[1] on-screen center; neutral[0] off-screen left, neutral[1] on-screen
|
|
const [p0, p1] = live(), [n0, n1] = allies();
|
|
const px = gs.ship.x, py = gs.ship.y;
|
|
if (p0) { p0.x = px; p0.y = py - 1400; } // off-screen above
|
|
if (p1) { p1.x = px + 60; p1.y = py + 40; } // on-screen
|
|
if (n0) { n0.x = px - 1400; n0.y = py; } // off-screen left (ally — should get NO arrow)
|
|
if (n1) { n1.x = px - 120; n1.y = py + 10; } // on-screen ally
|
|
await sleep(300); unblock();
|
|
// enable threat so the layer draws markers, then let the scene draw normally
|
|
const tOrig = hud.threat; hud.threat = { ...tOrig, enabled: true };
|
|
await sleep(300); unblock();
|
|
R.pirates = live().map((e) => ({ x: Math.round(e.x), y: Math.round(e.y), dead: !!e.dead }));
|
|
R.allies = allies().map((a) => ({ x: Math.round(a.x), y: Math.round(a.y), dead: !!a.dead }));
|
|
R.arrowG = dump(hud._arrowG);
|
|
R.threatG = dump(hud._threatG);
|
|
R.feedG = dump(hud._reticleG);
|
|
// sample a presented frame for pixel sanity (red + bright)
|
|
const ren = window.game.renderer, cv = ren.canvas;
|
|
const off = document.createElement('canvas'); off.width = cv.width; off.height = cv.height;
|
|
const octx = off.getContext('2d');
|
|
if (!window.__hooked3) { window.__hooked3 = true; const orig = ren.render; ren.render = function (...a) { const r = orig.apply(this, a); try { octx.drawImage(cv, 0, 0); } catch {} return r; }; }
|
|
await sleep(200);
|
|
const RED = (r, g, b) => r > 130 && r - g > 45 && r - b > 25;
|
|
const BRIGHT = (r, g, b) => r > 150 && g > 150 && b > 160;
|
|
const count = (x, y, w, h, test) => {
|
|
const X = Math.max(0, x), Y = Math.max(0, y), W = Math.min(w, cv.width - X), H = Math.min(h, Y + h > cv.height ? cv.height - Y : h);
|
|
if (W <= 0 || H <= 0) return 0;
|
|
const d = octx.getImageData(X, Y, W, H).data; let n = 0;
|
|
for (let i = 0; i < d.length; i += 4) if (test(d[i], d[i+1], d[i+2])) n++;
|
|
return n;
|
|
};
|
|
R.pixel = {
|
|
arrowTopEdge: count(0, 0, cv.width, 60, RED), // top edge band
|
|
feedBand: count(cv.width - 260, 50, 260, 40, BRIGHT), // feed top-right
|
|
};
|
|
hud.threat = tOrig;
|
|
return R;
|
|
})()`;
|
|
async function main() {
|
|
const { c, target } = 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 R = await evalJs(c, PAGE);
|
|
if (typeof R === 'string') throw new Error('probe: ' + R);
|
|
console.log(JSON.stringify(R, null, 2));
|
|
return true;
|
|
} catch (e) { console.error('enc-probe3: FAIL —', e.message); return false; }
|
|
finally { try { await fetch(`http://${CDP}/json/close/${target.id}`).catch(() => {}); } catch {} c.ws.close(); }
|
|
}
|
|
main().then((ok) => process.exit(ok ? 0 : 1)).catch((e) => { console.error('enc-probe3 error:', e.message); process.exit(2); });
|