// Validates the client-side dead-reckoning: applies real server snapshots, // simulates at 60 fps like the browser, and measures prediction error // between the locally simulated head and the server's authoritative head. import WebSocket from 'ws'; import { applySnakeSnaps, simulateSnakes, State } from '../public/js/client/src/state.js'; const state = new State(); const ws = new WebSocket('ws://localhost:8080/ws'); let myAngle = 0.7; let myBoost = false; let samples = 0, sumErr = 0, maxErr = 0, resets = 0; ws.on('open', () => ws.send(JSON.stringify({ t: 'join', name: 'SimTest' }))); ws.on('message', (d) => { const m = JSON.parse(d); if (m.t === 'init') { state.myId = m.id; state.arena = m.arena; return; } if (m.t !== 'snap') return; const before = state.snakes.get(state.myId); const prevHead = before ? [before.pts[0], before.pts[1]] : null; applySnakeSnaps(state, m.snakes); if (m.you) state.you = m.you; for (const ev of m.ev) if (ev.t === 'die' && ev.id === state.myId) ws.send(JSON.stringify({ t: 'respawn' })); const me = state.snakes.get(state.myId); const snap = m.snakes.find((s) => s.id === state.myId); if (me && snap) { if (prevHead && Math.hypot(prevHead[0] - snap.p[0], prevHead[1] - snap.p[1]) > 140) resets++; const err = Math.hypot(me.pts[0] - snap.p[0], me.pts[1] - snap.p[1]); sumErr += err; samples++; if (err > maxErr) maxErr = err; } }); // mimic the browser: input at ~33 Hz, simulation at 60 fps setInterval(() => { myAngle += (Math.random() - 0.5) * 0.3; // wander like a mouse would myBoost = Math.random() < 0.1; // sometimes boost const me = state.snakes.get(state.myId); if (me && Math.hypot(me.pts[0], me.pts[1]) > 3000) { myAngle = Math.atan2(-me.pts[1], -me.pts[0]); // steer back to safety myBoost = false; } ws.send(JSON.stringify({ t: 'in', a: myAngle, b: myBoost })); }, 30); setInterval(() => simulateSnakes(state, 1 / 60, myAngle, myBoost && state.you.score > 20), 1000 / 60); setTimeout(() => { console.log(`samples=${samples} avgErr=${(sumErr / Math.max(1, samples)).toFixed(2)}px maxErr=${maxErr.toFixed(2)}px hardResets=${resets}`); if (samples > 100 && sumErr / samples < 25 && resets === 0) { console.log('SIM TEST PASSED (prediction tracks server closely)'); process.exit(0); } console.log('SIM TEST FAILED'); process.exit(1); }, 20000);