Skip to content
Deasy Corporation
Logos
Theos
Work
Deasy Corporation
2026 AD
Games
Ping Wave Reveal POC
Back to Gaming Concepts
Press Space to emit a circular “sonar ping” that expands and briefly reveals objects it touches.
<html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>POC — Ping Wave Reveal (Fixed Loop)</title> <style> html, body { margin:0; width:100%; height:100%; overflow:hidden; background:#000; } canvas { display:block; outline:none; } .hint { position:absolute; left:12px; bottom:12px; color:#aaa; font:14px system-ui; user-select:none; } </style> </head> <body> <canvas id="c" width="1500" height="1500" tabindex="0"></canvas> <div class="hint">CLICK canvas once • Arrows move • Space pings</div> <script> const c = document.getElementById("c"); const x = c.getContext("2d"); const W = 1500, H = 1500; function focusCanvas(){ try { c.focus(); } catch(_){} } focusCanvas(); c.addEventListener("pointerdown", focusCanvas); // Key state (document inside iframe) const k = Object.create(null); function isBlocked(code, key){ return code === "ArrowUp" || code === "ArrowDown" || code === "ArrowLeft" || code === "ArrowRight" || code === "Space" || key === " "; } document.addEventListener("keydown", (e) => { if (isBlocked(e.code, e.key)) e.preventDefault(); if (e.code.startsWith("Arrow")) k[e.code] = true; // Space = ping on keydown edge if (e.code === "Space" || e.key === " ") { if (!k.__spaceDown) { k.__spaceDown = true; startPing(); } } }, { passive:false }); document.addEventListener("keyup", (e) => { if (isBlocked(e.code, e.key)) e.preventDefault(); if (e.code.startsWith("Arrow")) k[e.code] = false; if (e.code === "Space" || e.key === " ") k.__spaceDown = false; }, { passive:false }); // Player const p = { x: W/2, y: H/2, vx:0, vy:0 }; const ACCEL = 0.55, DRAG = 0.92, MAX = 14; // Dots (hidden until seen) const dots = Array.from({length: 90}, () => ({ x: Math.random()*W, y: Math.random()*H, r: 4 + Math.random()*10, seen: 0 })); // Pings const pings = []; const PING_SPEED = 18; // faster so you SEE it const PING_BAND = 26; // thicker reveal band const SEEN_DECAY = 0.90; // slower fade so it reads function clamp(v,a,b){ return Math.max(a, Math.min(b,v)); } function startPing(){ pings.push({ x:p.x, y:p.y, r:0, life:1 }); } function update(){ // movement if (k.ArrowUp) p.vy -= ACCEL; if (k.ArrowDown) p.vy += ACCEL; if (k.ArrowLeft) p.vx -= ACCEL; if (k.ArrowRight) p.vx += ACCEL; p.vx *= DRAG; p.vy *= DRAG; const sp = Math.hypot(p.vx, p.vy); if (sp > MAX){ p.vx = (p.vx/sp)*MAX; p.vy = (p.vy/sp)*MAX; } p.x = clamp(p.x + p.vx, 0, W); p.y = clamp(p.y + p.vy, 0, H); // expand pings for (const pg of pings){ pg.r += PING_SPEED; pg.life *= 0.985; } // cull for (let i=pings.length-1;i>=0;i--){ if (pings[i].r > 2600 || pings[i].life < 0.05) pings.splice(i,1); } // fade seen for (const d of dots) d.seen *= SEEN_DECAY; // reveal when ring passes dot for (const pg of pings){ for (const d of dots){ const dist = Math.hypot(d.x - pg.x, d.y - pg.y); if (Math.abs(dist - pg.r) <= PING_BAND) d.seen = 1; } } } function draw(){ x.clearRect(0,0,W,H); // grid x.strokeStyle = "rgba(255,255,255,0.05)"; for (let i=0;i<=W;i+=150){ x.beginPath(); x.moveTo(i,0); x.lineTo(i,H); x.stroke(); } for (let j=0;j<=H;j+=150){ x.beginPath(); x.moveTo(0,j); x.lineTo(W,j); x.stroke(); } // dots (only if seen) for (const d of dots){ if (d.seen < 0.02) continue; x.fillStyle = `rgba(255,255,255,${0.10 + d.seen*0.90})`; x.beginPath(); x.arc(d.x, d.y, d.r, 0, Math.PI*2); x.fill(); } // pings (make visible) for (const pg of pings){ x.strokeStyle = `rgba(255,255,255,${0.20 + pg.life*0.35})`; x.lineWidth = 3; x.beginPath(); x.arc(pg.x, pg.y, pg.r, 0, Math.PI*2); x.stroke(); } // player x.fillStyle = "#fff"; x.beginPath(); x.arc(p.x, p.y, 10, 0, Math.PI*2); x.fill(); // HUD const seenCount = dots.reduce((a,d)=>a+(d.seen>0.02?1:0),0); x.fillStyle = "rgba(255,255,255,0.6)"; x.font = "14px system-ui"; x.fillText(`pings=${pings.length} visibleDots=${seenCount}/${dots.length} (click canvas once)`, 12, 24); } function loop(){ update(); draw(); requestAnimationFrame(loop); } loop(); </script> </body> </html> '></iframe> </div>