Files
deepseek fc1fa2d51e Diablo2D — Shadows of Tristram: complete browser ARPG
- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting)
- 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X
- Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash
- Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage
- NPC quest chain (accept -> hunt -> turn in) with rewards & gating
- Procedural WebAudio SFX + generative music, EN/VI localization
- Saves, settings, waypoints, hardcore mode, PWA manifest
- 93-assertion headless suite + browser E2E via CDP
2026-08-23 06:59:36 +00:00

63 lines
2.3 KiB
JavaScript

/* CDP probe: launch nothing; attach to an already-running chrome
with --remote-debugging-port and interrogate the drive page.
usage: node tools/cdp_probe.mjs [port] */
const port = process.argv[2] || '9333';
const targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json();
console.log('targets:', targets.map(t => `${t.type}:${t.title}`).join(' | '));
const page = targets.find(t => t.type === 'page');
if (!page) { console.log('no page target'); process.exit(1); }
const ws = new WebSocket(page.webSocketDebuggerUrl);
let id = 0;
const pending = new Map();
function send(method, params = {}) {
return new Promise((res, rej) => {
const mid = ++id;
pending.set(mid, { res, rej });
ws.send(JSON.stringify({ id: mid, method, params }));
});
}
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.id && pending.has(msg.id)) {
const { res, rej } = pending.get(msg.id);
pending.delete(msg.id);
msg.error ? rej(new Error(msg.error.message)) : res(msg.result);
}
};
await new Promise(r => { ws.onopen = r; });
async function evalJs(expr) {
const r = await send('Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true, timeout: 8000 });
return r.result ? r.result.value : JSON.stringify(r);
}
const alive = await evalJs('1 + 1');
console.log('alive:', alive);
const info = await evalJs(`(() => {
const f = document.getElementById('g');
const w = f && f.contentWindow;
if (!w || !w.D2) return 'no game';
const D = w.D2;
return JSON.stringify({
state: D.game.state,
time: +(D.game.time||0).toFixed(2),
mons: D.game.monsters ? D.game.monsters.length : -1,
camx: +D.render.cam.x.toFixed(2),
status: document.getElementById('status').textContent.slice(-300),
});
})()`);
console.log('info:', info);
/* try one manual render with a hard timeout guard */
const rend = await Promise.race([
evalJs(`(() => { const f=document.getElementById('g'); const D=f.contentWindow.D2;
const t0=performance.now(); try { D.render.frame(D.game, 1/30); return 'frame ok in '+(performance.now()-t0).toFixed(1)+'ms'; } catch(e){ return 'frame threw: '+e.message; } })()`),
new Promise(r => setTimeout(() => r('FRAME HUNG (>8s)'), 8000)),
]);
console.log('render:', rend);
ws.close();
process.exit(0);