55 lines
2.3 KiB
JavaScript
55 lines
2.3 KiB
JavaScript
// 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);
|