diffs / #6
feat(#6): presence
Requested-by: SEED (built by us pre-launch)
+ BUILD+ UNIT TESTS+ LINT+ DIFF GATE+ PAGE CLASSIFIER+ PLAYWRIGHT
src/presence.jsNEW+20 −0
| 1 | +// Seed #6: presence. Other players show up as ghost cats (max 40). Positions and 4 fixed emotes only, never free text. | ||
| 2 | +export function connect(w) { | ||
| 3 | + let ws = null, tick = null, backoff = 1000; | ||
| 4 | + const open = () => { | ||
| 5 | + try { ws = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/arcade/ws`); } catch { return retry(); } | ||
| 6 | + ws.onopen = () => { backoff = 1000; tick = setInterval(send, 200); }; | ||
| 7 | + ws.onmessage = (m) => { | ||
| 8 | + let d; try { d = JSON.parse(m.data); } catch { return; } | ||
| 9 | + if (d.t === "snap" && Array.isArray(d.p)) w.ghosts = d.p.slice(0, 40).map(([id, room, x, y, f, a, e]) => ({ id, room, x, y, f, a, e })); | ||
| 10 | + }; | ||
| 11 | + ws.onclose = () => { clearInterval(tick); w.ghosts = []; retry(); }; | ||
| 12 | + ws.onerror = () => ws.close(); | ||
| 13 | + }; | ||
| 14 | + const retry = () => { setTimeout(open, backoff); backoff = Math.min(30000, backoff * 2); }; | ||
| 15 | + const send = () => { | ||
| 16 | + const p = w.player; if (!p || ws.readyState !== 1) return; | ||
| 17 | + ws.send(JSON.stringify({ t: "pos", room: w.room, x: Math.round(p.x), y: Math.round(p.y), f: p.f, a: p.walk > 0 ? 1 : 0, e: p.emote })); | ||
| 18 | + }; | ||
| 19 | + open(); | ||
| 20 | +} |
1+// Seed #6: presence. Other players show up as ghost cats (max 40). Positions and 4 fixed emotes only, never free text.
2+export function connect(w) {
3+ let ws = null, tick = null, backoff = 1000;
4+ const open = () => {
5+ try { ws = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/arcade/ws`); } catch { return retry(); }
6+ ws.onopen = () => { backoff = 1000; tick = setInterval(send, 200); };
7+ ws.onmessage = (m) => {
8+ let d; try { d = JSON.parse(m.data); } catch { return; }
9+ if (d.t === "snap" && Array.isArray(d.p)) w.ghosts = d.p.slice(0, 40).map(([id, room, x, y, f, a, e]) => ({ id, room, x, y, f, a, e }));
10+ };
11+ ws.onclose = () => { clearInterval(tick); w.ghosts = []; retry(); };
12+ ws.onerror = () => ws.close();
13+ };
14+ const retry = () => { setTimeout(open, backoff); backoff = Math.min(30000, backoff * 2); };
15+ const send = () => {
16+ const p = w.player; if (!p || ws.readyState !== 1) return;
17+ ws.send(JSON.stringify({ t: "pos", room: w.room, x: Math.round(p.x), y: Math.round(p.y), f: p.f, a: p.walk > 0 ? 1 : 0, e: p.emote }));
18+ };
19+ open();
20+}


CI log tail
$ ci:build (658 ms, exit 0) $ ci:test (148 ms, exit 0) TAP version 13 # Subtest: palette: valid hex, at most 40 colours ok 1 - palette: valid hex, at most 40 colours --- duration_ms: 0.73159 type: 'test' ... # Subtest: sprites: rectangular-ish rows of known keys ok 2 - sprites: rectangular-ish rows of known keys --- duration_ms: 1.032724 type: 'test' ... # Subtest: levels: every room has a name, platforms and a draw function ok 3 - levels: every room has a name, platforms and a draw function --- duration_ms: 2.484121 type: 'test' ... # Subtest: bosses: every file exports a class with update/draw ok 4 - bosses: every file exports a class with update/draw --- duration_ms: 0.893293 type: 'test' ... # Subtest: features: default export with a unique id and only known hooks ok 5 - features: default export with a unique id and only known hooks --- duration_ms: 1.695976 type: 'test' ... # Subtest: no network, storage or parent access anywhere in src ok 6 - no network, storage or parent access anywhere in src --- duration_ms: 5.904195 type: 'test' ... 1..6 # tests 6 # suites 0 # pass 6 # fail 0 # cancelled 0 # skipped 0 # todo 0 # duration_ms 94.309779 $ ci:lint (632 ms, exit 0) $ playwright 960x540 fps 60 long tasks 0 console errors 0