Monorepo for Aesthetic.Computer aesthetic.computer
at main 195 lines 5.9 kB view raw
1#!/usr/bin/env node 2// UDP Multiplayer Test Script (WebSocket Version) 3// Tests the 1v1:move channel via WebSocket on localhost session server 4// 5// Note: This tests via WebSocket since geckos.io client requires a browser. 6// The actual UDP path is tested by running the game in two browser windows. 7// 8// Usage: 9// cd session-server && npm run dev # Start session server first 10// node tests/udp-multiplayer-test.mjs 11 12import WebSocket from "ws"; 13 14const SESSION_URL = process.env.SESSION_URL || "wss://localhost:8889"; 15const NUM_FAKE_PLAYERS = 2; 16const UPDATE_INTERVAL_MS = 50; // 20 updates per second 17 18console.log("🧪 Multiplayer Position Test (WebSocket)"); 19console.log("========================================="); 20console.log(`📡 Connecting to: ${SESSION_URL}`); 21console.log(`👥 Simulating ${NUM_FAKE_PLAYERS} players\n`); 22console.log("ℹ️ Note: This tests WebSocket path. For UDP, test with two browser windows.\n"); 23 24const players = []; 25 26class FakePlayer { 27 constructor(id) { 28 this.id = id; 29 this.handle = `test_player_${id}`; 30 this.pos = { x: Math.random() * 10 - 5, y: 1.6, z: Math.random() * 10 - 5 }; 31 this.rot = { x: 0, y: Math.random() * 360, z: 0 }; 32 this.ws = null; 33 this.connected = false; 34 this.receivedMoves = 0; 35 this.wsId = null; 36 } 37 38 async connect() { 39 return new Promise((resolve, reject) => { 40 this.ws = new WebSocket(SESSION_URL); 41 42 const timeout = setTimeout(() => { 43 reject(new Error(`Player ${this.id} connection timeout`)); 44 }, 10000); 45 46 this.ws.on("open", () => { 47 clearTimeout(timeout); 48 this.connected = true; 49 console.log(`✅ Player ${this.id} (${this.handle}) WebSocket connected`); 50 }); 51 52 this.ws.on("message", (data) => { 53 try { 54 const msg = JSON.parse(data.toString()); 55 56 // Handle connection acknowledgment 57 if (msg.type === "connected" || msg.type === "connected:already") { 58 this.wsId = msg.id; 59 console.log(`🆔 Player ${this.id} assigned WebSocket ID: ${this.wsId}`); 60 61 // Send join message 62 this.send("1v1:join", { 63 handle: this.handle, 64 pos: this.pos, 65 rot: this.rot, 66 health: 100 67 }); 68 resolve(); 69 } 70 71 // Handle moves from other players 72 if (msg.type === "1v1:move" && msg.id !== this.wsId) { 73 this.receivedMoves++; 74 if (this.receivedMoves <= 3 || this.receivedMoves % 100 === 0) { 75 console.log(`📨 Player ${this.id} received move from ${msg.id}:`, 76 `pos(${msg.content.pos.x.toFixed(2)}, ${msg.content.pos.y.toFixed(2)}, ${msg.content.pos.z.toFixed(2)})`); 77 } 78 } 79 80 // Handle join from other players 81 if (msg.type === "1v1:join" && msg.id !== this.wsId) { 82 console.log(`👋 Player ${this.id} sees ${msg.content.handle} joined`); 83 } 84 } catch (e) { 85 // Ignore non-JSON messages 86 } 87 }); 88 89 this.ws.on("error", (err) => { 90 console.error(`❌ Player ${this.id} WebSocket error:`, err.message); 91 reject(err); 92 }); 93 94 this.ws.on("close", () => { 95 this.connected = false; 96 console.log(`🔌 Player ${this.id} disconnected`); 97 }); 98 }); 99 } 100 101 send(type, content) { 102 if (!this.connected || !this.ws) return; 103 this.ws.send(JSON.stringify({ type, content })); 104 } 105 106 sendPosition() { 107 if (!this.connected) return; 108 109 // Simulate movement 110 this.pos.x += (Math.random() - 0.5) * 0.1; 111 this.pos.z += (Math.random() - 0.5) * 0.1; 112 this.rot.y += Math.random() * 2; 113 114 this.send("1v1:move", { 115 pos: this.pos, 116 rot: this.rot 117 }); 118 } 119 120 disconnect() { 121 if (this.ws) { 122 this.ws.close(); 123 this.connected = false; 124 } 125 } 126 127 getStats() { 128 return { 129 id: this.id, 130 handle: this.handle, 131 wsId: this.wsId, 132 connected: this.connected, 133 receivedMoves: this.receivedMoves 134 }; 135 } 136} 137 138async function runTest() { 139 // Create fake players 140 for (let i = 0; i < NUM_FAKE_PLAYERS; i++) { 141 players.push(new FakePlayer(i)); 142 } 143 144 // Connect all players 145 console.log("🔗 Connecting players...\n"); 146 try { 147 await Promise.all(players.map(p => p.connect())); 148 } catch (err) { 149 console.error("❌ Failed to connect all players:", err.message); 150 process.exit(1); 151 } 152 153 console.log("\n✅ All players connected!\n"); 154 console.log("📤 Starting position updates...\n"); 155 156 // Start sending position updates 157 const updateInterval = setInterval(() => { 158 players.forEach(p => p.sendPosition()); 159 }, UPDATE_INTERVAL_MS); 160 161 // Run for 5 seconds then print stats 162 await new Promise(resolve => setTimeout(resolve, 5000)); 163 164 clearInterval(updateInterval); 165 166 console.log("\n📊 Test Results:"); 167 console.log("================"); 168 players.forEach(p => { 169 const stats = p.getStats(); 170 console.log(`Player ${stats.id} (${stats.handle}): ${stats.receivedMoves} moves received`); 171 }); 172 173 // Calculate expected moves 174 const expectedMovesPerPlayer = (5000 / UPDATE_INTERVAL_MS) * (NUM_FAKE_PLAYERS - 1); 175 console.log(`\n📈 Expected moves per player: ~${expectedMovesPerPlayer}`); 176 177 const avgReceived = players.reduce((sum, p) => sum + p.getStats().receivedMoves, 0) / players.length; 178 const successRate = (avgReceived / expectedMovesPerPlayer * 100).toFixed(1); 179 console.log(`📈 Average received: ${avgReceived.toFixed(0)} (${successRate}% success rate)`); 180 181 // Cleanup 182 console.log("\n🧹 Cleaning up..."); 183 players.forEach(p => p.disconnect()); 184 185 console.log("✅ Test complete!\n"); 186 process.exit(0); 187} 188 189// Handle errors 190process.on("unhandledRejection", (err) => { 191 console.error("❌ Unhandled error:", err); 192 process.exit(1); 193}); 194 195runTest();