Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

feat: add arena piece — quake-style arena with tessellated ground + speedometer

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

+157
+157
system/public/aesthetic.computer/disks/arena.mjs
··· 1 + // arena, 2025.4.7 2 + // Quake-style arena with large tessellated ground, player shadow, and speedometer. 3 + 4 + /* #region 🏁 TODO 5 + - [] Add more arena geometry (walls, pillars) 6 + - [] Strafe-jumping / bunny-hop acceleration 7 + + Done 8 + - [x] Fork from fps.mjs 9 + - [x] Large pre-tessellated ground plane 10 + - [x] Speed meter HUD + FPS counter 11 + #endregion */ 12 + 13 + let groundPlane; 14 + let penLocked = false; 15 + 16 + // Speed tracking 17 + let prevX = 0, prevY = 0, prevZ = 0; 18 + let speedSmoothed = 0; 19 + const SPEED_SMOOTH = 0.15; 20 + 21 + // FPS tracking 22 + let frameTimes = []; 23 + let lastFrameTime = 0; 24 + 25 + // Ground config 26 + const GROUND_SIZE = 14; 27 + const GRID = 14; 28 + const GROUND_Y = -1.5; 29 + const FOG_START_SQ = 7 * 7; 30 + const FOG_END_SQ = 14 * 14; 31 + 32 + const BG = [45 / 255, 48 / 255, 55 / 255]; 33 + const COLOR_A = [0.38, 0.35, 0.30, 1.0]; 34 + const COLOR_B = [0.22, 0.20, 0.19, 1.0]; 35 + 36 + function fogColor(base, distSq) { 37 + if (distSq <= FOG_START_SQ) return base; 38 + if (distSq >= FOG_END_SQ) return [BG[0], BG[1], BG[2], 0.0]; 39 + const t = (distSq - FOG_START_SQ) / (FOG_END_SQ - FOG_START_SQ); 40 + return [ 41 + base[0] + (BG[0] - base[0]) * t, 42 + base[1] + (BG[1] - base[1]) * t, 43 + base[2] + (BG[2] - base[2]) * t, 44 + base[3] * (1 - t), 45 + ]; 46 + } 47 + 48 + function boot({ Form, penLock, system }) { 49 + penLock(); 50 + 51 + const cam = system?.fps?.doll?.cam; 52 + if (cam) { prevX = cam.x; prevY = cam.y; prevZ = cam.z; } 53 + lastFrameTime = performance.now(); 54 + 55 + // --- Vertex-colored checkerboard ground with fog --- 56 + const positions = []; 57 + const colors = []; 58 + const step = (GROUND_SIZE * 2) / GRID; 59 + 60 + for (let row = 0; row < GRID; row++) { 61 + for (let col = 0; col < GRID; col++) { 62 + const x0 = -GROUND_SIZE + col * step; 63 + const z0 = -GROUND_SIZE + row * step; 64 + const x1 = x0 + step; 65 + const z1 = z0 + step; 66 + 67 + const cx = (x0 + x1) / 2; 68 + const cz = (z0 + z1) / 2; 69 + const dSq = cx * cx + cz * cz; 70 + 71 + const base = (row + col) % 2 === 0 ? COLOR_A : COLOR_B; 72 + const c = fogColor(base, dSq); 73 + 74 + positions.push( 75 + [x0, GROUND_Y, z0, 1], [x0, GROUND_Y, z1, 1], [x1, GROUND_Y, z1, 1], 76 + [x0, GROUND_Y, z0, 1], [x1, GROUND_Y, z1, 1], [x1, GROUND_Y, z0, 1], 77 + ); 78 + colors.push(c, c, c, c, c, c); 79 + } 80 + } 81 + 82 + groundPlane = new Form( 83 + { type: "triangle", positions, colors }, 84 + { pos: [0, 0, 0], rot: [0, 0, 0], scale: 1 }, 85 + ); 86 + groundPlane.noFade = true; 87 + } 88 + 89 + function sim({ system }) { 90 + const cam = system?.fps?.doll?.cam; 91 + if (!cam) return; 92 + 93 + const dx = cam.x - prevX; 94 + const dy = cam.y - prevY; 95 + const dz = cam.z - prevZ; 96 + const speed = Math.sqrt(dx * dx + dy * dy + dz * dz); 97 + speedSmoothed += (speed - speedSmoothed) * SPEED_SMOOTH; 98 + prevX = cam.x; prevY = cam.y; prevZ = cam.z; 99 + } 100 + 101 + function paint({ wipe, ink, screen, write, box }) { 102 + // FPS calc 103 + const now = performance.now(); 104 + const dt = now - lastFrameTime; 105 + lastFrameTime = now; 106 + frameTimes.push(dt); 107 + if (frameTimes.length > 60) frameTimes.shift(); 108 + const avgDt = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length; 109 + const fps = Math.round(1000 / avgDt); 110 + 111 + // Render scene 112 + wipe(45, 48, 55).form(groundPlane); 113 + 114 + // --- HUD (top-right) --- 115 + const font = "MatrixChunky8"; 116 + const margin = 4; 117 + const lineH = 10; 118 + const rX = screen.width - margin; // right edge 119 + 120 + // FPS 121 + ink(fps >= 30 ? "lime" : fps >= 15 ? "yellow" : "red"); 122 + write(`${fps} FPS`, { x: rX - 7 * 4, y: margin }, undefined, undefined, false, font); 123 + 124 + // Frame time 125 + ink(180, 180, 180); 126 + write(`${avgDt.toFixed(1)}ms`, { x: rX - 7 * 4, y: margin + lineH }, undefined, undefined, false, font); 127 + 128 + // Speed meter (bottom-center) 129 + const ups = speedSmoothed * 60; 130 + const barMaxUPS = 15; 131 + const barFill = Math.min(1, ups / barMaxUPS); 132 + 133 + const barW = Math.min(160, Math.floor(screen.width * 0.4)); 134 + const barH = 6; 135 + const barX = Math.floor((screen.width - barW) / 2); 136 + const barY = screen.height - 16; 137 + 138 + ink(0, 0, 0, 140); 139 + box(barX - 1, barY - 1, barW + 2, barH + 2); 140 + 141 + const cr = barFill > 0.5 ? Math.floor(255 * ((barFill - 0.5) * 2)) : 0; 142 + const cg = barFill < 0.5 ? 255 : Math.floor(255 * (1 - (barFill - 0.5) * 2)); 143 + ink(cr, cg, 50, 220); 144 + box(barX, barY, Math.floor(barW * barFill), barH); 145 + 146 + ink("white"); 147 + write(`${ups.toFixed(1)} u/s`, { x: barX + barW + 4, y: barY - 1 }, undefined, undefined, false, font); 148 + } 149 + 150 + function act({ event: e, penLock }) { 151 + if (e.is("pen:locked")) penLocked = true; 152 + if (e.is("pen:unlocked")) penLocked = false; 153 + if (!penLocked && e.is("touch")) penLock(); 154 + } 155 + 156 + export const system = "fps"; 157 + export { boot, sim, paint, act };