Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

dumduel: add synth sounds and version auto-reload

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

+66 -4
+66 -4
system/public/aesthetic.computer/disks/dumduel.mjs
··· 19 19 let myId = null; 20 20 let sw = 0, sh = 0; 21 21 let frameCount = 0; 22 + let synth = null; // sound.synth reference 23 + let sendFn = null; // send() for window:reload 24 + let updateAvailable = false; 22 25 23 26 let roster = []; // [{ id, handle }] — first two duel, rest wait 24 27 let phase = "waiting"; // waiting | countdown | fight | roundover ··· 93 96 function startFight() { 94 97 phase = "fight"; 95 98 if (isDueling() && !me) spawnDuelists(); 99 + // Fight start sound — short rising tone 100 + synth?.({ type: "square", tone: 440, volume: 0.15, attack: 0.01, decay: 0.15, duration: 0.2 }); 96 101 } 97 102 98 103 function endRound(winnerHandle) { 99 104 roundWinner = winnerHandle; 100 105 phase = "roundover"; 101 106 roundOverTimer = ROUND_OVER_FRAMES; 107 + // Death sound 108 + const won = winnerHandle === myHandle; 109 + if (won) { 110 + synth?.({ type: "triangle", tone: 660, volume: 0.2, attack: 0.01, decay: 0.3, duration: 0.35 }); 111 + } else { 112 + synth?.({ type: "sawtooth", tone: 120, volume: 0.15, attack: 0.01, decay: 0.4, duration: 0.5 }); 113 + } 102 114 } 103 115 104 116 function advanceStack() { ··· 120 132 return { nx: dx / len, ny: dy / len }; 121 133 } 122 134 123 - function boot({ wipe, screen, net: { socket, udp }, handle }) { 135 + function boot({ wipe, screen, net: { socket, udp }, handle, sound, send, net }) { 124 136 sw = screen.width; 125 137 sh = screen.height; 126 138 myHandle = handle?.() || "guest_" + Math.floor(Math.random() * 9999); 139 + synth = sound.synth; 140 + sendFn = send; 141 + 142 + // Version polling — auto-reload on new deploy 143 + const pollVersion = async () => { 144 + try { 145 + const res = await fetch("/api/version"); 146 + if (!res.ok) return; 147 + const info = await res.json(); 148 + const current = info.deployed; 149 + // Long-poll for changes 150 + while (true) { 151 + try { 152 + const r = await fetch(`/api/version?current=${current}`); 153 + if (!r.ok) break; 154 + const data = await r.json(); 155 + if (data.changed !== false) { 156 + updateAvailable = true; 157 + break; 158 + } 159 + } catch { break; } 160 + } 161 + } catch {} 162 + }; 163 + pollVersion(); 127 164 128 165 udpChannel = udp((type, content) => { 129 166 const d = typeof content === "string" ? JSON.parse(content) : content; ··· 218 255 219 256 if (phase === "countdown") { 220 257 countdownTimer--; 258 + // Tick each second 259 + if (countdownTimer > 0 && countdownTimer % 60 === 0) { 260 + synth?.({ type: "sine", tone: 330, volume: 0.1, attack: 0.005, decay: 0.1, duration: 0.12 }); 261 + } 221 262 if (countdownTimer <= 0) startFight(); 222 263 } 223 264 ··· 287 328 const bx = me.x + nx * 6; 288 329 const by = me.y + ny * 6; 289 330 bullets.push({ x: bx, y: by, vx: nx * BULLET_SPEED, vy: ny * BULLET_SPEED, owner: "me" }); 331 + synth?.({ type: "square", tone: 800, volume: 0.06, attack: 0.001, decay: 0.06, duration: 0.07 }); 290 332 server?.send("duel:fire", { 291 333 handle: myHandle, x: bx, y: by, vx: nx * BULLET_SPEED, vy: ny * BULLET_SPEED, 292 334 }); ··· 341 383 sw = screen.width; 342 384 sh = screen.height; 343 385 344 - if (e.is("touch") && phase === "fight" && (isDueling() || dummy) && me?.alive) { 386 + if (e.is("touch")) { 345 387 const ox = Math.floor(sw / 2 - ARENA_W / 2); 346 388 const oy = Math.floor(sh / 2 - ARENA_H / 2); 347 - me.targetX = Math.max(6, Math.min(ARENA_W - 6, e.x - ox)); 348 - me.targetY = Math.max(6, Math.min(ARENA_H - 6, e.y - oy)); 389 + 390 + // Tap update banner to reload 391 + if (updateAvailable && e.y >= oy + ARENA_H + 14 && e.y < oy + ARENA_H + 38 && e.x >= ox && e.x < ox + ARENA_W) { 392 + sendFn?.({ type: "window:reload" }); 393 + return; 394 + } 395 + 396 + // Tap arena to move 397 + if (phase === "fight" && (isDueling() || dummy) && me?.alive) { 398 + me.targetX = Math.max(6, Math.min(ARENA_W - 6, e.x - ox)); 399 + me.targetY = Math.max(6, Math.min(ARENA_H - 6, e.y - oy)); 400 + } 349 401 } 350 402 } 351 403 ··· 438 490 ink(200, 195, 185).write("practice", { 439 491 x: ox + Math.floor(ARENA_W / 2 - 24), 440 492 y: oy - 12, 493 + }); 494 + } 495 + 496 + // Update available banner 497 + if (updateAvailable) { 498 + ink(50, 160, 80).write("update ready", { 499 + x: ox, y: oy + ARENA_H + 16, 500 + }); 501 + ink(140, 135, 125).write("tap here to reload", { 502 + x: ox, y: oy + ARENA_H + 26, 441 503 }); 442 504 } 443 505