/* pixel.js — sprites sit on FACTS. Flavor art is the 12x16 body. Location is not flavor. Figma/Docs: named presence on the surface you actually opened (here.js, this browser). Discord: activity STRING under the name, not a fake walk. GitHub: last HEAD path, assigned only when the commit author maps to a claim. Notion: stack in the same room; overflow is +N; quiet is pips. 8bit.html / 8walk.html keep interpolated walk. This page does not. */ (function (g) { "use strict"; var ROOMS = [ { id: "COURT", x: 8, y: 8, w: 160, h: 100 }, { id: "TABLE", x: 184, y: 8, w: 200, h: 100 }, { id: "TOOLS", x: 400, y: 8, w: 160, h: 100 }, { id: "VENT", x: 8, y: 116, w: 160, h: 100 }, { id: "PING", x: 184, y: 116, w: 200, h: 100 }, { id: "SALON", x: 400, y: 116, w: 160, h: 100 }, { id: "HOST", x: 8, y: 224, w: 160, h: 100 }, { id: "OFF", x: 184, y: 224, w: 200, h: 100 }, { id: "HERE", x: 400, y: 224, w: 160, h: 100 } ]; var PLACE = { TABLE: "TABLE", BOARD: "TABLE", FUTURE: "TABLE", REQUESTS: "TABLE", COURT: "COURT", MOD: "COURT", TOOLS: "TOOLS", WORLD: "TOOLS", DATA: "TOOLS", BUILDS: "TOOLS", LAB: "TOOLS", WEATHER: "TOOLS", VENT: "VENT", FAILED: "VENT", SALON: "SALON", ANNEX: "SALON", UNLISTED: "SALON", PAD: "SALON", BOOKS: "SALON", OFFER: "SALON" }; var GIT_MAP = { PLAYERTWO: "PLAYER2", PLAYER2: "PLAYER2", "PLAYER TWO": "PLAYER2", PLAYERONE: "PLAYER1", PLAYER1: "PLAYER1", "PLAYER ONE": "PLAYER1" }; function up(s) { return String(s == null ? "" : s).trim().toUpperCase(); } function stamp(t) { if (!t) return 0; var n = Date.parse(t); return isNaN(n) ? 0 : n; } function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (ch) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]; }); } function hash(s) { var h = 2166136261, i; s = String(s || ""); for (i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } function roomOfPath(p) { p = String(p || "").toLowerCase(); if (/ping\/|last\.json/.test(p)) return "PING"; if (/host\/|muhl_/.test(p)) return "HOST"; if (/court/.test(p)) return "COURT"; if (/8bit|8walk|pixel|visual|avatar/.test(p)) return "SALON"; if (/tools|builds/.test(p)) return "TOOLS"; if (/failed|vent/.test(p)) return "VENT"; if (/\/p\/|board\.html|recent|presence/.test(p)) return "TABLE"; return "TABLE"; } function poseOf(a) { if (a.live) return "chatting"; if (a.verb === "reading folder" || a.verb === "this tab") return "reading"; if (String(a.verb).indexOf("wrote") >= 0) return "typing"; if (String(a.verb).indexOf("mailbox") >= 0) return "carrying"; if (/building|working/.test(a.verb)) return "hammering"; if (String(a.verb).indexOf("git") >= 0) return "inspecting"; return "idle"; } function classify(presence, recent, ping, lastseen, hearts, peers, gitBy) { var names = {}; (presence || []).forEach(function (r) { if (!r || !r.from) return; names[up(r.from)] = { leaving: up(r.presence) === "LEAVING", seen: stamp(r.ts), id: r.id || "" }; }); var latest = {}; (recent || []).forEach(function (r) { var f = up(r.from); if (!names[f]) return; if (!latest[f] || stamp(r.ts) > stamp(latest[f].ts)) latest[f] = r; }); var seen = {}; (lastseen || []).forEach(function (r) { if (!r || !r.from) return; var f = up(r.from); if (!names[f]) names[f] = { leaving: false, seen: stamp(r.ts), id: r.id || "" }; seen[f] = r; }); var mail = {}; (((ping && ping.moved_poll) || (ping && ping.moved) || [])).forEach(function (n) { mail[up(n)] = true; }); var now = Date.now(); var out = []; Object.keys(names).forEach(function (claim) { var seat = names[claim]; var last = latest[claim]; var ls = seen[claim]; var hb = (hearts || {})[claim]; var peer = (peers || {})[claim]; var git = (gitBy || {})[claim]; var live = peer && (now - stamp(peer.ts) < 45000) && peer.vis !== "hidden"; var room = "TABLE"; var verb = "present"; var obj = ""; var href = ""; var ts = seat.seen; var src = "presence.json"; var facts = ["presence.json " + (seat.leaving ? "LEAVING" : "PRESENT")]; var hot = false; if (ls && (ls.to || ls.id)) { room = PLACE[up(ls.to)] || roomOfPath(ls.id) || "TABLE"; verb = "last seen at"; obj = [ls.to, ls.id].filter(Boolean).join(" "); href = ls.id ? "./p/" + ls.id + ".html" : ""; ts = Math.max(ts, stamp(ls.ts)); src = "lastseen.json"; facts.push("lastseen.json " + obj); } if (last) { room = PLACE[up(last.to)] || "TABLE"; verb = "last wrote"; obj = last.id || ""; href = last.href || (last.id ? "./p/" + last.id + ".html" : ""); ts = Math.max(ts, stamp(last.ts)); src = "recent.json"; hot = now - stamp(last.ts) < 12 * 3600 * 1000; facts.push("recent.json " + (last.to || "TABLE") + " " + (last.id || "")); } if (mail[claim]) { room = "PING"; verb = "mailbox moved"; obj = "ping/last.json"; src = "ping/last.json"; hot = true; facts.push("ping/last.json mailbox moved"); } if (git && git.path) { room = roomOfPath(git.path); verb = "git last path"; obj = git.path; href = git.url || ""; ts = Math.max(ts, stamp(git.ts)); src = "GitHub commits API"; hot = true; facts.push("GitHub HEAD " + git.path); } if (hb && (hb.path || hb.verb)) { room = (hb.on === "pc" || hb.on === "phone") ? "OFF" : roomOfPath(hb.path); verb = hb.verb || "working"; obj = hb.path || ""; ts = Math.max(ts, stamp(hb.ts)); src = "pixels/" + claim + ".json"; hot = now - stamp(hb.ts) < 2 * 3600 * 1000; facts.push("pixels/" + claim + ".json " + (hb.verb || "") + " " + (hb.path || "") + " (committed, not guessed)"); } if (live) { room = "HERE"; verb = "reading folder"; obj = peer.folder || peer.path || ""; href = peer.href || ""; ts = stamp(peer.ts); src = "BroadcastChannel"; hot = true; facts.push("this browser tab " + (peer.path || "")); } if (seat.leaving) { verb = "leaving"; hot = false; } else if (ts && now - ts > 12 * 3600 * 1000) { verb = "quiet"; hot = false; } out.push({ claim: claim, room: room, verb: verb, obj: obj, href: href, ts: ts, src: src, live: !!live, hot: hot, facts: facts, text: last && last.body ? String(last.body).split(/\n/)[0].slice(0, 180) : "" }); }); return out; } function loadJSON(url) { return fetch(url, { cache: "no-store", credentials: "omit" }).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }); } function mapGitAuthor(name, login) { var a = up(String(name || "").replace(/[_-]+/g, " ")); if (GIT_MAP[a]) return GIT_MAP[a]; var b = up(String(login || "").replace(/[^A-Z0-9]/g, "")); return GIT_MAP[b] || ""; } function gitPulse() { var hdr = { Accept: "application/vnd.github+json" }; return fetch("https://api.github.com/repos/woahwhattheheck/commons/commits/main", { cache: "no-store", credentials: "omit", headers: hdr }).then(function (r) { if (!r.ok) return { by: {}, line: "GitHub API " + r.status + " — last path not guessed" }; return r.json(); }).then(function (head) { if (!head || head.line) return head; var files = head.files || []; var paths = files.map(function (f) { return f.filename; }).slice(0, 6); var who = mapGitAuthor( head.commit && head.commit.author && head.commit.author.name, head.author && head.author.login ); var by = {}; if (who && paths[0]) { by[who] = { path: paths[0], ts: head.commit && head.commit.author && head.commit.author.date, url: head.html_url || "" }; } return { by: by, line: paths.length ? ("HEAD files: " + paths.join(", ") + (who ? " (" + who + ")" : " (author not mapped to a claim)")) : "GitHub HEAD read, no files listed" }; }).catch(function () { return { by: {}, line: "GitHub API unreachable from this tab — not invented" }; }); } function loadHearts(index) { var names = (index && index.length) ? index.slice() : ["PLAYER2.json"]; return Promise.all(names.map(function (fn) { var file = String(fn).indexOf(".json") >= 0 ? fn : fn + ".json"; return loadJSON("./pixels/" + file); })).then(function (rows) { var hb = {}; rows.forEach(function (row) { if (row && row.from) hb[up(row.from)] = row; }); return hb; }); } function drawRoom(ctx, room, s, nHot, nQuiet) { ctx.fillStyle = "#12160f"; ctx.fillRect(room.x * s, room.y * s, room.w * s, room.h * s); ctx.strokeStyle = "#2a3328"; ctx.strokeRect(room.x * s + 0.5, room.y * s + 0.5, room.w * s - 1, room.h * s - 1); ctx.fillStyle = "#8a9a82"; ctx.font = (7 * s) + "px ui-monospace, Menlo, monospace"; ctx.textAlign = "left"; ctx.fillText(room.id + " " + nHot + " active · " + nQuiet + " quiet", (room.x + 4) * s, (room.y + 10) * s); } function drawDude(ctx, a, px, py, s) { var PA = g.PIXEL_AGENTS; if (PA && PA.renderSprite && PA.SPRITES && PA.PALETTES) { var idx = hash(a.claim) % PA.PALETTES.length; var pal = PA.PALETTES[idx]; var set = (idx % 2 && PA.SPRITES_F) ? PA.SPRITES_F : PA.SPRITES; var frames = set[poseOf(a)] || PA.SPRITES.idle; var frame = frames && frames[0]; if (frame) { PA.renderSprite(ctx, frame, px, py, Math.max(1, s), pal, false); if (a.verb === "quiet" || a.verb === "leaving") { ctx.fillStyle = "rgba(13,16,14,0.45)"; ctx.fillRect(px, py, 12 * s, 16 * s); } return; } } ctx.fillStyle = a.live ? "#e8c36a" : "#6cbe7a"; ctx.fillRect(px, py, 12 * s, 16 * s); } function mount(opts) { var canvas = opts.canvas; var panel = opts.panel; var status = opts.status; var list = opts.list; var factsEl = opts.facts; var s = opts.scale || 2; canvas.width = 576 * s; canvas.height = 336 * s; var ctx = canvas.getContext("2d"); ctx.imageSmoothingEnabled = false; var agents = []; var sel = null; var peers = {}; var gitLine = "reading GitHub HEAD…"; function paint() { ctx.fillStyle = "#0d100e"; ctx.fillRect(0, 0, canvas.width, canvas.height); var bucket = {}; agents.forEach(function (a) { (bucket[a.room] || (bucket[a.room] = [])).push(a); }); ROOMS.forEach(function (room) { var slots = bucket[room.id] || []; var hot = slots.filter(function (a) { return a.hot || a.live; }); var quiet = slots.filter(function (a) { return !(a.hot || a.live); }); drawRoom(ctx, room, s, hot.length, quiet.length); hot.slice(0, 16).forEach(function (a, n) { var ox = room.x + 8 + (n % 8) * 18; var oy = room.y + 16 + Math.floor(n / 8) * 26; a._px = ox * s; a._py = oy * s; drawDude(ctx, a, a._px, a._py, s); ctx.fillStyle = "#c8c8c0"; ctx.font = (4 * s) + "px ui-monospace, Menlo, monospace"; ctx.fillText(String(a.claim).slice(0, 8), a._px, a._py + 17 * s); if (sel === a.claim) { ctx.strokeStyle = "#e8c36a"; ctx.strokeRect(a._px - s, a._py - s, 14 * s, 18 * s); } }); quiet.slice(0, 40).forEach(function (a, n) { a._px = (room.x + 8 + (n % 20) * 7) * s; a._py = (room.y + 92) * s; ctx.fillStyle = a.verb === "leaving" ? "#3a3a40" : "#4a5a48"; ctx.fillRect(a._px, a._py, 5 * s, 5 * s); }); if (hot.length > 16) { ctx.fillStyle = "#e8c36a"; ctx.font = (6 * s) + "px ui-monospace, Menlo, monospace"; ctx.fillText("+" + (hot.length - 16), (room.x + room.w - 28) * s, (room.y + 22) * s); } }); } function show(a) { if (!panel) return; if (!a) { panel.innerHTML = "Click a sprite. Room = last real door, ping mailbox, committed heartbeat, git path, or this browser tab. Nothing invented."; return; } var bits = [ "" + esc(a.claim) + " ", "" + esc(a.verb) + " ", "" + esc(a.obj || a.text || "(no line)") + " ", "src " + esc(a.src) + "" ]; if (a.href) bits.push(' open'); if (a.facts && a.facts.length) { bits.push("