/* Verifies the stuck-mouse fix inside the live game page. * usage: node tools/cdp_mouse_test.mjs [port] */ const port = process.argv[2] || '9366'; const targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json(); const page = targets.find(t => t.type === 'page'); const ws = new WebSocket(page.webSocketDebuggerUrl); let id = 0; const pending = new Map(); const send = (m, p = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method: m, params: p })); }); 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; }); const ev = async expr => (await send('Runtime.evaluate', { expression: expr, returnByValue: true })).result.value; /* wait for game ready */ for (let i = 0; i < 60; i++) { const s = await ev(`(document.getElementById('status')||{textContent:''}).textContent`); if (/DONE|DRIVE_ERROR/.test(s)) break; await new Promise(r => setTimeout(r, 1000)); } console.log(await ev(`(() => { const w = document.getElementById('g').contentWindow; const D = w.D2, cv = w.document.getElementById('game-canvas'); if (!D || !D.game.player) return 'FAIL no game'; const r = cv.getBoundingClientRect(); const opts = { bubbles: true, button: 0, clientX: r.left + 500, clientY: r.top + 400 }; /* 1. press LMB and NEVER send mouseup (missed-release simulation) */ cv.dispatchEvent(new w.MouseEvent('mousedown', opts)); return 'pressed'; })()`)); await new Promise(r => setTimeout(r, 700)); const during = await ev(`(() => { const D = document.getElementById('g').contentWindow.D2; return JSON.stringify({ left: D.input.state.left, mt: !!D.game.player.moveTarget }); })()`); console.log('while held (no mouseup):', during); /* 2. user then just MOVES the mouse normally — real browsers attach e.buttons=0 once released; our resync must heal the stuck flag */ console.log(await ev(`(() => { const w = document.getElementById('g').contentWindow; const cv = w.document.getElementById('game-canvas'); const r = cv.getBoundingClientRect(); cv.dispatchEvent(new w.MouseEvent('mousemove', { bubbles: true, clientX: r.left + 520, clientY: r.top + 420, buttons: 0 })); return 'moved'; })()`)); await new Promise(r => setTimeout(r, 250)); const healed = await ev(`(() => { const D = document.getElementById('g').contentWindow.D2; return JSON.stringify({ left: D.input.state.left }); })()`); console.log('after normal mousemove:', healed); /* 3. full click still works afterwards */ console.log(await ev(`(() => { const w = document.getElementById('g').contentWindow; const D = w.D2, cv = w.document.getElementById('game-canvas'); const r = cv.getBoundingClientRect(); const o = { bubbles: true, button: 0, clientX: r.left + 640, clientY: r.top + 450 }; cv.dispatchEvent(new w.MouseEvent('mousemove', o)); cv.dispatchEvent(new w.MouseEvent('mousedown', o)); w.dispatchEvent(new w.MouseEvent('mouseup', { bubbles: true, button: 0 })); return 'clicked'; })()`)); await new Promise(r => setTimeout(r, 400)); console.log(await ev(`(() => { const D = document.getElementById('g').contentWindow.D2; const p = D.game.player; return JSON.stringify({ newTargetAccepted: !!p.moveTarget, left: D.input.state.left }); })()`)); ws.close();