Inital commit

This commit is contained in:
2026-07-22 00:34:09 -05:00
commit 0b24de2767
24 changed files with 2691 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
// 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);
+67
View File
@@ -0,0 +1,67 @@
// Smoke test: join the game, verify snapshots flow, movement works,
// boosting drains score, and steering toward the border kills us.
import WebSocket from 'ws';
const ws = new WebSocket('ws://localhost:8080/ws');
let init = null;
let snaps = 0;
let firstHead = null;
let lastHead = null;
let maxScore = 0;
let died = false;
let respawned = false;
let phase = 0;
ws.on('open', () => ws.send(JSON.stringify({ t: 'join', name: 'SmokeTest' })));
ws.on('message', (d) => {
const m = JSON.parse(d);
if (m.t === 'init') {
init = m;
console.log(`[init] id=${m.id} arena=${m.arena} pellets=${m.pellets.length}`);
ws.send(JSON.stringify({ t: 'in', a: 0, b: false }));
return;
}
if (m.t !== 'snap') return;
snaps++;
const me = m.snakes.find((s) => s.id === init.id);
if (me) {
lastHead = [me.p[0], me.p[1]];
if (!firstHead) firstHead = [...lastHead];
maxScore = Math.max(maxScore, m.you.score);
}
if (snaps === 30) {
const moved = Math.hypot(lastHead[0] - firstHead[0], lastHead[1] - firstHead[1]);
console.log(`[move] head moved ${moved.toFixed(1)}px over ~2s (${m.snakes.length} snakes, lb=${m.lb.length}, total=${m.total})`);
if (moved < 50) fail('snake did not move');
// start boosting toward the border to test boost drain + border death
phase = 1;
ws.send(JSON.stringify({ t: 'in', a: Math.atan2(lastHead[1], lastHead[0]), b: true }));
}
for (const ev of m.ev) {
if (ev.t === 'die' && ev.id === init.id && !died) {
died = true;
console.log(`[death] died as expected (score=${ev.s}, pellets sample pts=${ev.p.length / 2})`);
ws.send(JSON.stringify({ t: 'respawn' }));
}
}
if (died && me && m.you.alive && !respawned) {
respawned = true;
console.log(`[respawn] instant respawn OK, alive with score=${m.you.score}`);
console.log('SMOKE TEST PASSED');
process.exit(0);
}
if (snaps === 150 && phase === 1) {
console.log(`[boost] max score observed while boosting: ${maxScore}`);
}
});
function fail(msg) {
console.error('SMOKE TEST FAILED: ' + msg);
process.exit(1);
}
setTimeout(() => fail(`timeout (snaps=${snaps}, died=${died}, respawned=${respawned})`), 90000);