Route gameCommand traffic through WebRTC unreliable DataChannel

Socket.IO (TCP) holds back later packets while it retransmits a lost
one, which stalls worldUpdate delivery on lossy long-distance links —
exactly the pattern game state suffers worst from. WebRTC DataChannels
in unreliable mode (ordered:false, maxRetransmits:0) drop late packets
instead of queueing them, which is what we want for high-frequency
state sync.

Adds a per-user WebRTCTransport on top of the existing Socket.IO
connection. Socket.IO stays in charge of bootstrap, signaling
(SDP/ICE exchange), and control messages — only gameCommand payloads
get routed onto the unreliable channel once it's open. If WebRTC
fails to negotiate, gameCommand transparently falls back to
Socket.IO, so the game keeps working unchanged.

A new StatsLogger writes per-session JSONL events (session_start,
webrtc_ready with negotiation time, per-second stats with transport,
RTT samples, recv/send rates, seq gaps) so we can compare real-world
runs (e.g. Germany server <-> Korea client) instead of guessing.
URL flag ?webrtc=0 forces fallback for A/B testing.

scripts/webrtc-browser-test.js spins up a headless Chromium against
a freshly-started server and asserts the unreliable channel opens
and gameCommand traffic actually rides it.
This commit is contained in:
Jeena 2026-05-11 00:38:18 +00:00
parent 47faae81e5
commit a0481ed867
9 changed files with 1412 additions and 138 deletions

38
app/Server/StatsLogger.js Normal file
View file

@ -0,0 +1,38 @@
define([
"fs",
"path"
],
function (fs, path) {
"use strict";
var runsDir = path.join(process.cwd(), "runs");
if (!fs.existsSync(runsDir)) {
fs.mkdirSync(runsDir, { recursive: true });
}
var runFile = path.join(
runsDir,
new Date().toISOString().replace(/[:.]/g, "-") + ".jsonl"
);
var location = process.env.POC_LOCATION || "unknown";
fs.writeFileSync(runFile, JSON.stringify({
type: "run_start",
t: Date.now(),
location: location,
pid: process.pid
}) + "\n");
console.log("[stats] logging to " + runFile);
return {
log: function (obj) {
obj.t = obj.t || Date.now();
obj.location = obj.location || location;
fs.appendFile(runFile, JSON.stringify(obj) + "\n", function () {});
},
getFile: function () { return runFile; },
getLocation: function () { return location; }
};
});