orbit/dev/enc-probe6.mjs

128 lines
6.3 KiB
JavaScript

/** Focused probe: where do the threat markers land on screen, and do the
* pixels there read red? Dumps every coordinate in the chain so the
* worldToScreen → pixel mapping can be verified directly. */
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 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 'no GameScene/combat/ship';
const sys = gs.combat, hud = gs.combatHud, ship = gs.ship;
const unblock = () => { try { if (gs.comms && gs.comms.paused) gs.comms._ack(); } catch {} };
unblock();
sys.maxHull = 1e9; sys.hull = 1e9; sys.maxShields = 1e9; sys.shields = 1e9;
// pixel pipeline
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.__lf) { window.__lf = true; const o = ren.render; ren.render = function (...a) { const r = o.apply(this, a); try { octx.drawImage(cv, 0, 0); } catch {} return r; }; }
const RED = (r, g, b) => r > 130 && r - g > 45 && r - b > 25;
const redAt = (x, y, half) => {
const X = Math.max(0, Math.round(x - half)), Y = Math.max(0, Math.round(y - half));
const W = half * 2, H = half * 2;
const d = octx.getImageData(X, Y, W, H).data; let n = 0;
for (let i = 0; i < d.length; i += 4) if (RED(d[i], d[i+1], d[i+2])) n++;
return n;
};
// capture threat circles
const threatG = hud._threatG;
let cap = [];
const orig = threatG.fillCircle;
threatG.fillCircle = function (...a) { cap.push(a.slice(0, 3)); return orig.apply(this, a); };
const out = {};
gs._spawnEncounter({ kind: 'mixed', pirates: 2, neutrals: 2 });
const P = sys.enemies.filter((e) => !e.dead);
const N = sys.allies.filter((a) => !a.dead);
for (const e of P) { e.maxHull = 1e9; e.hull = 1e9; e.maxShields = 1e9; e.shields = 1e9; }
for (const a of N) { a.maxHull = 1e9; a.hull = 1e9; a.maxShields = 1e9; a.shields = 1e9; }
await sleep(200); unblock();
// camera + worldToScreen reference
const cam = gs.cameras ? gs.cameras.main : null;
out.cam = cam ? { wx: cam.worldView.x, wy: cam.worldView.y, w: cam.worldView.width, h: cam.worldView.height, zoom: cam.zoom } : null;
out.player = { x: Math.round(ship.x), y: Math.round(ship.y) };
// place P[0] at a KNOWN world point, zero velocity, freeze
const TX = 1000, TY = 500;
if (gs.comms) gs.comms._setPaused(true);
await sleep(320);
P[0].x = TX; P[0].y = TY;
if (P[0].body) { P[0].body.velocity.x = 0; P[0].body.velocity.y = 0; }
out.P0 = { x: P[0].x, y: P[0].y };
out.slotWorld = { x: TX, y: TY - 36 };
out.slotScreen = hud.worldToScreen(TX, TY - 36);
hud.threat.enabled = true;
// sample over several frames, track the max + the alpha at each
let best = 0, alphas = [];
for (let i = 0; i < 14; i++) {
const t = gs.time.now;
hud.update(t);
await sleep(24);
const pulse = 0.55 + 0.45 * Math.sin(t * 0.001 * 6.2831853 * 2.2);
alphas.push(Number(pulse.toFixed(2)));
best = Math.max(best, redAt(out.slotScreen.x, out.slotScreen.y, 16));
}
out.redMax = best;
out.alphas = alphas;
out.capturedCircles = cap.slice(-4);
out.threatCfg = { enabled: hud.threat.enabled, size: hud.threat.size, above: hud.threat.above, pulse: hud.threat.pulse };
out.worldToScreenFn = String(hud.worldToScreen).slice(0, 120);
return out;
})()`;
(async () => {
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)', false); } catch { scoped = false; } if (scoped) break; }
if (!scoped) throw new Error('combat scope never appeared');
await sleep(800);
const out = await evalJs(c, CODE);
console.log(JSON.stringify(out, null, 2));
} finally {
try { await fetch(`http://${CDP}/json/close/${target.id}`).catch(() => {}); } catch {}
c.ws.close();
}
})().catch((e) => { console.error('probe err:', e.message); process.exit(2); });