Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

dumduel: fix multiplayer sync — deterministic slots, self-relay guards, handle identity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+27 -11
+27 -11
system/public/aesthetic.computer/disks/dumduel.mjs
··· 56 56 targetX: myPos.x, targetY: myPos.y, 57 57 alive: true, wasMoving: false, 58 58 }; 59 + // Find opponent handle from roster (whoever isn't me among first two) 60 + const opHandle = roster.length >= 2 61 + ? (roster[0].handle === myHandle ? roster[1].handle : roster[0].handle) 62 + : "???"; 59 63 opponent = { 60 64 x: opPos.x, y: opPos.y, 61 65 targetX: opPos.x, targetY: opPos.y, 62 66 alive: true, 63 - handle: roster[mySlot === 0 ? 1 : 0]?.handle || "???", 67 + handle: opHandle, 64 68 }; 65 69 } 66 70 ··· 85 89 function startCountdown() { 86 90 phase = "countdown"; 87 91 countdownTimer = COUNTDOWN_FRAMES; 88 - const idx = myRosterIdx(); 89 - if (idx === 0) mySlot = 0; 90 - else if (idx === 1) mySlot = 1; 91 - else mySlot = -1; 92 + 93 + // Deterministic slot: lower handle alphabetically = slot 0 94 + if (roster.length >= 2) { 95 + const h0 = roster[0].handle; 96 + const h1 = roster[1].handle; 97 + if (myHandle === h0) mySlot = h0 < h1 ? 0 : 1; 98 + else if (myHandle === h1) mySlot = h1 < h0 ? 0 : 1; 99 + else mySlot = -1; // spectating 100 + } else { 101 + mySlot = -1; 102 + } 103 + 92 104 if (isDueling()) spawnDuelists(); 93 105 } 94 106 ··· 195 207 196 208 if (type === "duel:join" && msg.handle !== myHandle) { 197 209 if (dummy) stopPractice(); 198 - if (!roster.find((r) => r.handle === msg.handle)) { 210 + // Update ID if handle already exists (reconnect), otherwise add 211 + const existing = roster.find((r) => r.handle === msg.handle); 212 + if (existing) { 213 + existing.id = id; 214 + } else { 199 215 roster.push({ id, handle: msg.handle }); 200 216 } 201 217 server.send("duel:roster", { ··· 209 225 } 210 226 } 211 227 212 - if (type === "duel:roster") { 228 + if (type === "duel:roster" && msg.handle !== myHandle) { 213 229 if (roster.length <= 1) { 214 230 for (const r of msg.roster) { 215 231 if (!roster.find((x) => x.handle === r.handle)) { ··· 220 236 } 221 237 } 222 238 223 - if (type === "duel:countdown") { 239 + if (type === "duel:countdown" && msg.handle !== myHandle) { 224 240 if (roster.length >= 2 && phase === "waiting") startCountdown(); 225 241 } 226 242 ··· 241 257 else if (me && msg.winner !== myHandle) me.alive = false; 242 258 } 243 259 244 - if (type === "duel:advance") advanceStack(); 260 + if (type === "duel:advance" && msg.handle !== myHandle) advanceStack(); 245 261 }); 246 262 247 263 roster.push({ id: myId, handle: myHandle }); ··· 349 365 me.alive = false; 350 366 bullets.splice(i, 1); 351 367 endRound(opponent?.handle || "???"); 352 - server?.send("duel:roundover", { winner: opponent?.handle || "???" }); 368 + server?.send("duel:roundover", { handle: myHandle, winner: opponent?.handle || "???" }); 353 369 break; 354 370 } 355 371 } ··· 360 376 bullets.splice(i, 1); 361 377 endRound(myHandle); 362 378 server?.send("duel:hit", { handle: myHandle, victim: opponent.handle }); 363 - server?.send("duel:roundover", { winner: myHandle }); 379 + server?.send("duel:roundover", { handle: myHandle, winner: myHandle }); 364 380 break; 365 381 } 366 382 }