diff --git a/session-server/arena-manager.mjs b/session-server/arena-manager.mjs index f46bd40ca4..1f0cf1bf86 100644 --- a/session-server/arena-manager.mjs +++ b/session-server/arena-manager.mjs @@ -206,6 +206,11 @@ export class ArenaManager { receiveCmd(handle, frame) { const rec = this.players.get(handle); if (!rec) return; + if (!rec._firstCmdLogged) { + console.log(`🏟️ cmd:first handle=${handle} cmds=${(frame.cmds || []).length}`); + rec._firstCmdLogged = true; + } + rec._cmdRxCount = (rec._cmdRxCount || 0) + 1; rec.lastSeenMs = this.now(); // Snap-ack: client tells us which snap they last saw. @@ -282,6 +287,20 @@ export class ArenaManager { if (this.tick % TICK_RATE === 0) this.sweepStale(nowMs); if (this.tick % SNAP_EVERY === 0) this.broadcastSnapshots(); + + // Periodic rate log (every 5s at TICK_RATE=60). + if (this.tick % (TICK_RATE * 5) === 0 && this.players.size > 0) { + const rows = []; + for (const rec of this.players.values()) { + const cmdRx = rec._cmdRxCount || 0; + const snapTx = rec._snapTxCount || 0; + const transport = rec.udpChannelId != null ? "UDP" : "WS"; + rows.push(`${rec.handle}(${transport} rx=${cmdRx} tx=${snapTx})`); + rec._cmdRxCount = 0; + rec._snapTxCount = 0; + } + console.log(`🏟️ stats[5s] ${rows.join(" ")}`); + } } sweepStale(nowMs) { @@ -403,6 +422,7 @@ export class ArenaManager { if (!ok && rec.wsId != null && this.sendWS) { this.sendWS(rec.wsId, "arena:snap", snap); } + if (ok || rec.wsId != null) rec._snapTxCount = (rec._snapTxCount || 0) + 1; } // Probes: always WS, full snap, messageNum=0 (they don't ack). diff --git a/system/public/aesthetic.computer/lib/udp.mjs b/system/public/aesthetic.computer/lib/udp.mjs index 6bc7280e61..8486da22ec 100644 --- a/system/public/aesthetic.computer/lib/udp.mjs +++ b/system/public/aesthetic.computer/lib/udp.mjs @@ -114,7 +114,17 @@ function connect(port = 8889, url = undefined, send, explicitTurnHost = null) { } function respond(name, content) { - content = JSON.parse(content); + // Geckos auto-parses JSON envelopes: if the sender emitted an object, + // `content` already arrives as an object; if the sender emitted a + // pre-stringified payload (the pattern client→server uses), it arrives + // as a string that still needs parsing. Handle both. + if (typeof content === "string") { + try { content = JSON.parse(content); } + catch (err) { + console.warn(`🩰 UDP ${name}: JSON.parse failed`, err?.message); + return; + } + } if (logs.udp) console.log(`🩰 UDP Received:`, content); send({ type: "udp:receive", diff --git a/system/public/sw.js b/system/public/sw.js index d2c42380cd..80d859a7d7 100644 --- a/system/public/sw.js +++ b/system/public/sw.js @@ -1,7 +1,7 @@ // Aesthetic Computer Service Worker // Caches JavaScript modules for faster subsequent loads -const CACHE_NAME = 'ac-modules-v6'; // Bump to invalidate cached disk.mjs/bios.mjs after the piece-runs telemetry shape fix +const CACHE_NAME = 'ac-modules-v7'; // Bump to force fresh udp.mjs with robust respond() parsing (fixes arena:snap drops) const CACHE_DURATION = 60 * 60 * 1000; // 1 hour in ms (dev-friendly) // Critical modules to precache on install