0xa2c3…c101

All memos sent from and to 0xa2c3…c101.

if (audioStarted) return; var Ctx = (typeof window !== "undefined") && (window.AudioContext || window.webkitAudioContext); if (!Ctx) return; AC = new Ctx(); master = AC.createGain(); master.gain.value = muted ? 0 : 0.5; master.connect(AC.destination); try { var b = AC.createBuffer(1, 1, AC.sampleRate); var s = AC.createBufferSource(); s.buffer = b; s.connect(AC.destination); s.start(0); } catch (e) {} buildSeq(); audioStarted = true; var begin = function () { stepIdx = 0; visT0 = nowSec(); nextStepTime = AC.currentTime + 0.06; }; begin(); if (AC.state === "suspended" && AC.resume) { try { AC.resume().then(begin).catch(function () {}); } catch (e) {} } schedTimer = setInterval(scheduler, TICK_MS); } function resumeAudio() { if (AC && AC.state === "suspended" && AC.resume) { try { AC.resume(); } catch (e) {} } } function scheduler() { if (!AC || !seq) return; if (AC.state !== "running") { resumeAudio(); return; } if (nextStepTime < AC.currentTime) nextStepTime = AC.currentTime + 0.02; while (nextStepTime < AC.currentTime + LOOKAHEAD) { playStep(stepIdx, nextStepTime); nextStepTime += seq.stepSeconds; stepIdx = (stepIdx + 1) % seq.steps; } } function blip(freq, time, dur, type, gain) { var o = AC.createOscillator(), g = AC.createGain(); o.type = type; o.frequency.setValueAtTime(freq, time); g.gain.setValueAtTime(0.0001, time); g.gain.linearRampToValueAtTime(gain, time + 0.006); g.gain.exponentialRampToValueAtTime(0.0001, time + dur); o.connect(g); g.connect(master); o.start(time); o.stop(time + dur + 0.02); } function kick(time) { var o = AC.createOscillator(), g = AC.createGain(); o.type = "sine"; o.frequency.setValueAtTime(150, time); o.frequency.exponentialRampToValueAtTime(45, time + 0.12); g.gain.setValueAtTime(0.6, time); g.gain.exponentialRampToValueAtTime(0.0001, time + 0.15); o.connect(g); g.connect(master); o.start(time); o.stop(time + 0.17); } function noiseBurst(time, dur, hp, gain) { var n = Math.floor(AC.sampleRate * dur); var buf = AC.createBuffer(1, n, AC.sampleRate), d = buf.getChannelData(0); for (var i = 0; i < n; i++) d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / n, 2); var src = AC.createBufferSource(); src.buffer = buf; var f = AC.createBiquadFilter(); f.type = "highpass"; f.frequency.value = hp; var g = AC.createGain(); g.gain.value = gain; src.connect(f); f.connect(g); g.connect(master); src.start(time); } function playStep(i, time) { var w = (seq.waves && seq.waves[i]) || seq.leadWave || "square"; var L = seq.lead[i]; if (L != null) blip(midiToFreq(L), time, seq.stepSeconds * 0.85, w, (seq.vel && seq.vel[i]) || 0.16); var B = seq.bass[i]; if (B != null) blip(midiToFreq(B), time, seq.stepSeconds * 0.85, seq.bassWave || "square", 0.24); var p = seq.perc[i]; if (p & 2) kick(time); if (p & 1) noiseBurst(time, 0.025, 7000, 0.14); if (p & 8) noiseBurst(time, 0.11, 5000, 0.13); if (p & 4) noiseBurst(time, 0.12, 1400, 0.30); } function toggleMute() { muted = !muted; if (master) master.gain.value = muted ? 0 : 0.5; } function playChime(freq) { if (!AC || AC.state !== "running") return; var t = AC.currentTime; blip(freq || 880, t, 0.10, "square", 0.18); blip((freq || 880) * 1.5, t + 0.06, 0.12, "square", 0.16); } function playBoom(count, colorHex) { if (!AC || AC.state !== "running") return; var t = AC.currentTime; noiseBurst(t, 0.18, 700, 0.30); var m = metaFor(colorHex), f = midiToFreq(m.note); var o = AC.createOscillator(), g = AC.createGain(); o.type = m.wave; o.frequency.setValueAtTime(f, t); o.frequency.exponentialRampToValueAtTime(Math.max(40, f * 0.45), t + 0.16); g.gain.setValueAtTime(0.26, t); g.gain.exponentialRampToValueAtTime(0.0001, t + 0.2); o.connect(g); g.connect(master); o.start(t); o.stop(t + 0.22); if (count >= 3) for (var k = 0; k < 3; k++) blip(f * Math.pow(2, (k / 12) * 2), t + 0.05 + k * 0.06, 0.09, m.wave, 0.16); } /* ============================ GAME LOGIC ============================ */ function enterDemo(now) { roundState = "demo"; score = 0; multUntil = 0; flashUntil = 0; lastAuto = now; } function startRound(now) { score = 0; multUntil = 0; flashUntil = 0; roundState = "run"; roundStart = now; lastInteraction = now; } function nextPiece() { curHash = E.makeHash(); grid = E.buildGrid(curHash); if (audioStarted) buildSeq(); } function findMatchCell() { var cols = grid.cols, rows = grid.rows; function col(r, c) { return grid.cells[r * cols + c].creature.colors.stroke; } for (var r = 0; r < rows; r++) { var run = 1; for (var c = 1; c < cols; c++) { if (col(r, c) === col(r, c - 1)) { if (++run >= 3) return r * cols + c; } else run = 1; } } for (var c2 = 0; c2 < cols; c2++) { var run2 = 1; for (var r2 = 1; r2 < rows; r2++) { if (col(r2, c2) === col(r2 - 1, c2)) { if (++run2 >= 3) return r2 * cols + c2; } else run2 = 1; } } return -1; } function autoPlayStep(now) { if (!grid) return; var idx = (Math.random() < 0.6) ? findMatchCell() : -1; if (idx < 0) idx = (Math.random() * grid.cells.length) | 0; if (grid.cells[idx].fx) return; popCell(idx, (idx / grid.cols) | 0, idx % grid.cols, now); } function explodeSet(indices, now) { var out = []; for (var i = 0; i < indices.length; i++) if (E.explode(grid, indices[i], now)) out.push(indices[i]); return out; } function popCell(idx, row, col, now) { var cr = grid.cells[idx].creature, bonus = cr.bonus, tapColor = cr.colors.stroke; var mult = (now < multUntil) ? 2 : 1; if (bonus) { var NAMES = { bomb: "BOMB", gold: "GOLD", time: "TIME", x2: "X2", rainbow: "COLOR" }; flashText = NAMES[bonus] || bonus.toUpperCase(); flashUntil = now + 1.3; flashCol = (E.BONUS && E.BONUS[bonus] && E.BONUS[bonus].col) || "#ffffff"; } var done = [], extra = 0, i; if (bonus === "bomb") { var set = []; for (var c = 0; c < grid.cols; c++) set.push(row * grid.cols + c); for (var r = 0; r < grid.rows; r++) set.push(r * grid.cols + col); done = explodeSet(set, now); } else if (bonus === "rainbow") { var color = cr.colors.stroke, set2 = []; for (i = 0; i < grid.cells.length; i++) if (grid.cells[i].creature.colors.stroke === color) set2.push(i); done = explodeSet(set2, now); } else if (bonus === "gold") { done = E.explodeMatch(grid, idx, now); extra = 100; } else if (bonus === "time") { roundStart += 4; done = E.explodeMatch(grid, idx, now); playChime(880); } else if (bonus === "x2") { multUntil = now + 6; done = E.explodeMatch(grid, idx, now); playChime(1046); } else { done = E.explodeMatch(grid, idx, now); if (!done.length) return; } score += mult * (scoreForCells(done) + extra); playBoom(done.length, tapColor); } /* ============================ INPUT ============================ */ function onPointer(ev) { startAudio(); resumeAudio(); if (!grid) return; var now = nowSec(); var rect = canvas.getBoundingClientRect(); var cx = (typeof ev.clientX === "number") ? ev.clientX : (ev.touches && ev.touches[0] ? ev.touches[0].clientX : 0); var cy = (typeof ev.clientY === "number") ? ev.clientY : (ev.touches && ev.touches[0] ? ev.touches[0].clientY : 0); var mx = (cx - rect.left) * (canvas.width / rect.width); var my = (cy - rect.top) * (canvas.height / rect.height); var bx = (mx - dispOX) / dispScale, by = (my - dispOY) / dispScale; lastInteraction = now; if (roundState !== "run") startRound(now); if (bx < 0 || by < 0 || bx >= CW || by >= CH) return; var cwN = grid.caw * E.BLK, chN = grid.cah * E.BLK; var col = Math.floor(bx / cwN), row = Math.floor(by / chN); if (col < 0 || col >= grid.cols || row < 0 || row >= grid.rows) return; popCell(row * grid.cols + col, row, col, now); } function onKey(e) { startAudio(); resumeAudio(); lastInteraction = nowSec(); var k = (e.key || "").toLowerCase(); if (k === "m") toggleMute(); else if (k === "n") { nextPiece(); enterDemo(nowSec()); } else if (k === "r") startRound(nowSec()); else if (k === "s") downloadImage(); else if (k === "p") shareScore(); } if (typeof addEventListener !== "undefined") { addEventListener("pointerdown", onPointer); addEventListener("keydown", onKey); } if (typeof document !== "undefined" && document.addEventListener) document.addEventListener("visibilitychange", function () { if (!document.hidden) resumeAudio(); }); /* ============================ SHARE / SAVE ============================ */ function makeShareImage() { var scale = 3, pad = 10, titleH = 44, scoreH = 60; var gW = CW * scale, W = gW + pad * 2, H = titleH + CH * scale + scoreH + pad * 2; var cv = document.createElement("canvas"); cv.width = W; cv.height = H; var c2 = cv.getContext("2d"); c2.fillStyle = "#05060f"; c2.fillRect(0, 0, W, H); drawText(c2, "PXL BITS", W / 2, pad + 14, 3, "#00e5ff", "center"); c2.imageSmoothingEnabled = false; c2.drawImage(off, pad, titleH, gW, CH * scale); drawText(c2, score + " BITS", W / 2, titleH + CH * scale + pad + 22, 6, "#ffe600", "center"); return cv; } function downloadURL(url, name) { try { var a = document.createElement("a"); a.href = url; a.download = name; (document.body || document.documentElement).appendChild(a); a.click(); try { a.remove(); } catch (e) {} } catch (e) {} } function downloadImage() { var cv = makeShareImage(), name = "pxl-bits-" + score + "bits.png"; if (cv.toBlob) cv.toBlob(function (b) { if (b) downloadURL(URL.createObjectURL(b), name); else downloadURL(cv.toDataURL("image/png"), name); }, "image/png"); else downloadURL(cv.toDataURL("image/png"), name); } function shareScore() { var cv = makeShareImage(), name = "pxl-bits-" + score + "bits.png"; var text = "PXL BITS — I scored " + score + " BITS"; var withBlob = function (blob) { if (typeof window !== "undefined" && typeof window.ARTSUNAMI_SHARE === "function") { try { window.ARTSUNAMI_SHARE(blob, text); return; } catch (e) {} } try { var file = new File([blob], name, { type: "image/png" }); if (navigator.share && navigator.canShare && navigator.canShare({ files: [file] })) { navigator.share({ files: [file], title: "PXL BITS", text }).catch(function () { downloadURL(URL.createObjectURL(blob), name); }); return; } } catch (e) {} downloadURL(URL.createObjectURL(blob), name); }; if (cv.toBlob) cv.toBlob(function (b) { if (b) withBlob(b); else downloadURL(cv.toDataURL("image/png"), name); }, "image/png"); else downloadURL(cv.toDataURL("image/png"), name); } /* ============================ RENDER LOOP ============================ */ function render() { var rawT = nowSec(); var t = audioStarted ? (rawT - visT0) : rawT; E.paint(fb, grid, t, rawT); var src = fb.d, px = imgData.data; for (var i = 0, j = 0; i < src.length; i++, j += 4) { var col = src[i]; if (col) { var r = rgbOf(col); px[j] = r[0]; px[j + 1] = r[1]; px[j + 2] = r[2]; } else { px[j] = BG[0]; px[j + 1] = BG[1]; px[j + 2] = BG[2]; } px[j + 3] = 255; } offctx.putImageData(imgData, 0, 0); var W = canvas.width, H = canvas.height, s = W / CW, headerPx = HDR * s; ctx.fillStyle = "#05060f"; ctx.fillRect(0, 0, W, H); ctx.imageSmoothingEnabled = false; ctx.drawImage(off, 0, headerPx, W, CH * s); dispScale = s; dispOX = 0; dispOY = headerPx; var now = rawT; if (roundState === "demo") { if (now - lastAuto > AUTO_EVERY) { autoPlayStep(now); lastAuto = now; } } else if (roundState === "run") { if (now - lastInteraction > DEMO_RETURN) enterDemo(now); else if (now - roundStart >= ROUND) { roundStart = now; score = 0; multUntil = 0; flashUntil = 0; } } var remain = roundState === "run" ? Math.max(0, ROUND - (now - roundStart)) : ROUND; var timeLabel = "" + Math.ceil(remain), scoreLabel = "" + score; var ps = Math.max(2, Math.floor(headerPx * 0.52 / 5)), cy = headerPx / 2, pad = Math.max(1, Math.round(2 * s)); if (roundState === "run") { drawText(ctx, scoreLabel, pad, cy, ps, "#ffe600", "left", "#0b1280", 2); drawText(ctx, timeLabel, W - pad, cy, ps, remain <= 10 ? "#ff2740" : "#ffffff", "right", "#0b1280", 2); } if (now < flashUntil) drawText(ctx, flashText, W / 2, cy, ps, flashCol, "center", "#0b1280", 2); else if (roundState === "run" && now < multUntil) drawText(ctx, "X2", W / 2, cy, ps, "#ff17c6", "center", "#0b1280", 2); else if (roundState === "demo") drawText(ctx, "PXL BITS", W / 2, cy, ps, "#ffe600", "center", "#0b1280", 2); (typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : function (f) { setTimeout(f, 16); })(render); } render();
e", 6], ["dblue", 3]]); // always a visible frame const frameNative = caw >= 12 ? BLK : 1; const cells = []; const n = cols * rows; for (let i = 0; i < n; i++) { const cr = makeRng(String(hash) + ":" + i); // independent, reproducible per case const creature = buildCreature(cr, caw, cah, theme); const col = i % cols, row = (i / cols) | 0; cells.push({ i, col, row, ox: col * caw, oy: row * cah, creature }); } return { hash, cols, rows, caw, cah, cells, border, frameNative, theme }; } /* ===================================================================== 7. PAINT (reads spec + time only; seamless loop) ===================================================================== */ function phase(t, period, off) { let p = (t / period + off) % 1; if (p < 0) p += 1; return p; } const TAU = Math.PI * 2; function paintBack(fb, ox, oy, cr, t) { const b = cr.back; if (b.type === "plain") return; const put = (x, y) => { if (x >= 1 && x < cr.cw - 1 && y >= 1 && y < cr.ch - 1) fb.blk(ox + x, oy + y, b.col); }; if (b.type === "scan") { const shift = b.anim ? Math.floor(phase(t, b.period, b.off) * 3) : 0; for (let y = 1 + ((shift) % 3); y < cr.ch - 1; y += 3) for (let x = 1; x < cr.cw - 1; x += 2) put(x, y); } else if (b.type === "dots") { for (let y = 2; y < cr.ch - 1; y += 3) for (let x = 2; x < cr.cw - 1; x += 3) put(x, y); } else if (b.type === "bricks") { for (let y = 2; y < cr.ch - 1; y += 3) { for (let x = 1; x < cr.cw - 1; x++) put(x, y); } for (let y = 2; y < cr.ch - 1; y += 3) { const o = ((y / 3) | 0) % 2 ? 2 : 0; for (let x = 1 + o; x < cr.cw - 1; x += 4) for (let k = 1; k < 3 && y + k < cr.ch - 1; k++) put(x, y + k); } } else if (b.type === "stars") { for (const s of b.items) { const on = !s.tw || phase(t, b.period, s.ph) > 0.5; if (on) put(s.x, s.y); } } } function paintCreature(fb, ox, oy, cr, t) { const C = cr.colors, A = cr.anim, bb = cr.bb; paintBack(fb, ox, oy, cr, t); // ----- motion ----- let dy = 0, dx = 0, shadow = 0; const pm = phase(t, A.period, A.off); const s = Math.sin(pm * TAU); if (A.motion === "drift") { const d = A.dir || [0, -1]; dx = Math.round(A.amp * s * d[0]); dy = Math.round(A.amp * s * d[1]); } else if (A.motion === "float") { dy = Math.round(A.amp * s); shadow = 1; } // wiggle / march / wave / burst handled at their parts below // shared rhythmic hop: lifts every creature on the beat dy -= Math.round((cr.hopAmt || 1) * hopEnv(t)); const put = (x, y, c) => fb.blk(ox + x + dx, oy + y + dy, c); // shadow under a floating creature (width shrinks as it rises) if (shadow) { const fy = bb.y0 + bb.h + 1; const half = Math.max(1, Math.round(bb.w * 0.3) - (dy < 0 ? 1 : 0)); for (let x = -half; x <= half; x++) fb.blk(ox + Math.round((cr.cw - 1) / 2) + x, oy + fy, C.bg === NEON.bg ? "#0a0c1a" : shade(C.bg, 0.25)); } // ----- feet (march alternates) ----- if (cr.feet.length) { const marchP = phase(t, A.period, A.off); const up = Math.sin(marchP * TAU) > 0 ? 0 : 1; cr.feet.forEach((f, idx) => { const lift = A.motion === "march" ? ((idx % 2 === 0) === (up === 0) ? -1 : 0) : 0; put(f.x, f.y + lift, C.outline); }); } // ----- body: 1px outline only (hollow, like the sheets) ----- for (const p of cr.outPx) put(p[0], p[1], C.stroke); // ----- pixel explosion: single 1px pixels fly out from the centre, loop-safe ----- if (A.motion === "burst" && A.burst) { const cxp = (cr.cw - 1) / 2, cyp = bb.y0 + bb.h / 2; for (const pt of A.burst) { const p = phase(t, A.period, (A.off + pt.off) % 1); const r = p * pt.maxR; const x = Math.round(cxp + Math.cos(pt.ang) * r), y = Math.round(cyp + Math.sin(pt.ang) * r); if (x >= 1 && x < cr.cw - 1 && y >= 1 && y < cr.ch - 1) put(x, y, pt.col); // 1px, clipped inside the case } } // ----- arms (wave alternates) ----- if (cr.arms.length) { const wp = phase(t, A.period, A.off); cr.arms.forEach((a, idx) => { const lift = A.motion === "wave" ? Math.round(Math.sin((wp + idx * 0.5) * TAU)) : 0; put(a.x, a.y + lift, C.body); put(a.x, a.y + lift, C.body); }); } // ----- antennae (wiggle sways the tip) ----- for (const an of cr.antennae) { const wp = phase(t, A.period, A.off); const sway = A.motion === "wiggle" ? Math.round(Math.sin(wp * TAU)) : 0; for (let k = 1; k <= an.h; k++) put(an.x, an.baseY - k, C.outline); put(an.x + sway, an.baseY - an.h - 1, an.tip); } // ----- face features (with expression, features always stay visible) ----- const ep = phase(t, A.exprPeriod || MASTER_SECONDS, A.exprOff || 0); if (cr.face === "skull") { for (const e of cr.eyes) for (let ax = -1; ax <= 0; ax++) for (let ay = 0; ay <= 1; ay++) put(e.x + ax, e.y + ay, C.eye); // yellow eye holes put(Math.round((cr.cw - 1) / 2), cr.eyes[0].y + 2, C.stroke); // nose const mo = cr.mouth, open = hopEnv(t) > 0.5 ? 1 : 0; // jaw chatters on the beat for (let x = mo.x0; x <= mo.x1; x += 2) put(x, mo.y + open, C.stroke); // teeth marks } else if (cr.face === "alien") { const blink = A.eyeExpr === "blink" && ep > 0.9; const lookX = A.eyeExpr === "look" ? Math.round(Math.cos(ep * TAU)) : 0; for (const e of cr.eyes) { const dir = e.x < (cr.cw - 1) / 2 ? 1 : -1; if (blink) { for (let dx2 = -1; dx2 <= 1; dx2++) put(e.x + dx2, e.y, C.eye); } // lid line (still visible) else { const bx = e.x + lookX; for (let ay = -1; ay <= 1; ay++) { put(bx, e.y + ay, C.eye); put(bx - dir, e.y + ay, C.eye); } put(bx - dir, e.y - 1, C.eye); put(bx, e.y - 1, "#ffffff"); // glint } } const mo = cr.mouth, open = A.mouthExpr === "open" && Math.sin(ep * TAU) > 0.4 ? 1 : 0; for (let x = mo.x0; x <= mo.x1; x++) { put(x, mo.y, C.mouthCol); if (open) put(x, mo.y + 1, C.mouthCol); } } else { // ----- generic eyes with expression ----- const blink = A.eyeExpr === "blink" && ep > 0.9; const squint = A.eyeExpr === "squint" && ((ep % 0.5) > 0.32); const lookX = A.eyeExpr === "look" ? Math.round(Math.cos(ep * TAU)) : 0; const lookY = A.eyeExpr === "look" ? Math.round(Math.sin(ep * TAU) * 0.6) : 0; cr.eyes.forEach((e, idx) => { const wink = A.eyeExpr === "wink" && idx === cr.eyes.length - 1 && ep > 0.85; if (blink || squint || wink) { put(e.x - 1, e.y, C.eye); put(e.x, e.y, C.eye); } // closed dash (visible) else { put(e.x + lookX, e.y + lookY, C.eye); if (cr.bigEyes) put(e.x + lookX, e.y - 1 + lookY, "#ffffff"); } }); // ----- generic mouth with expression ----- const mo = cr.mouth, mid = (mo.x0 + mo.x1) / 2, halfW = Math.max(1, (mo.x1 - mo.x0) / 2); let cs = mo.smile ? 1 : -1, open = 0; if (A.mouthExpr === "smileshift") cs = Math.sin(ep * TAU); // smile <-> frown else if (A.mouthExpr === "talk") open = hopEnv(t) > 0.5 ? 1 : 0; // opens on the beat else if (A.mouthExpr === "open") open = Math.sin(ep * TAU) > 0.4 ? 1 : 0; for (let x = mo.x0; x <= mo.x1; x++) { const md = Math.abs(x - mid) / halfW; put(x, mo.y - Math.round((1 - md) * cs), C.mouthCol); if (open) put(x, mo.y + 1, C.mouthCol); // open mouth (second row) } } // bonus badge (static, top-left corner of the case) if (cr.bonus && BONUS[cr.bonus]) { const bc = BONUS[cr.bonus], ic = bc.icon; for (let r = 0; r < 3; r++) for (let c = 0; c < 3; c++) if (ic[r][c] === "1") fb.blk(ox + 1 + c, oy + 1 + r, bc.col); } // rarity pips (static, top-right corner) — common shows none, keeping the art clean if (cr.rarity && cr.rarity !== "common") { const pips = cr.rarity === "rare" ? 1 : cr.rarity === "epic" ? 2 : 3; const rc = cr.rarity === "rare" ? "#00e5ff" : cr.rarity === "epic" ? "#ff17c6" : "#ffe600"; for (let p = 0; p < pips; p++) fb.blk(ox + cr.cw - 2 - p * 2, oy + 1, rc); } } /* interactive explosion: shatter a touched monster into 1px pixels, then respawn */ const FX_DUR = 0.55; // seconds function explode(grid, idx, now) { const cell = grid.cells[idx]; if (!cell || cell.fx) return; const cr = cell.creature; const cxp = (cr.cw - 1) / 2, cyp = cr.bb.y0 + cr.bb.h / 2; const parts = []; for (const p of cr.outPx) { const ang = Math.atan2(p[1] - cyp, p[0] - cxp) + (Math.random() - 0.5) * 0.7; const spd = 6 + Math.random() * 12; // art px / second parts.push({ x: p[0], y: p[1], vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd - 3, col: cr.colors.stroke }); } cell.fx = { start: now, parts }; return true; } /* match-style chain: exploding a monster also clears a contiguous same-colour run of 3+ on its row and/or its column, then each cell respawns at random. Returns how many cells were triggered. No score, just a satisfying pop. */ function explodeMatch(grid, idx, now) { const cell = grid.cells[idx]; if (!cell) return 0; const cols = grid.cols, rows = grid.rows; const color = cell.creature.colors.stroke; const r0 = (idx / cols) | 0, c0 = idx % cols; const same = (i) => grid.cells[i] && grid.cells[i].creature.colors.stroke === color; const hit = new Set([idx]); const runH = [idx]; for (let c = c0 - 1; c >= 0 && same(r0 * cols + c); c--) runH.unshift(r0 * cols + c); for (let c = c0 + 1; c < cols && same(r0 * cols + c); c++) runH.push(r0 * cols + c); if (runH.length >= 3) runH.forEach((i) => hit.add(i)); const runV = [idx]; for (let r = r0 - 1; r >= 0 && same(r * cols + c0); r--) runV.unshift(r * cols + c0); for (let r = r0 + 1; r < rows && same(r * cols + c0); r++) runV.push(r * cols + c0); if (runV.length >= 3) runV.forEach((i) => hit.add(i)); let out = []; for (const i of hit) if (explode(grid, i, now)) out.push(i); return out; } function paint(fb, grid, t, now) { if (now == null) now = t; fb.clear(NEON.bg); for (const cell of grid.cells) { if (cell.fx) { const e = now - cell.fx.start; if (e >= FX_DUR) { // explosion done -> respawn a fresh monster cell._resp = (cell._resp || 0) + 1; cell.creature = buildCreature(makeRng(String(grid.hash) + ":" + cell.i + ":r" + cell._resp + ":" + Math.floor(now * 1000)), grid.caw, grid.cah, grid.theme); if (Math.random() < 0.16) cell.creature.bonus = randomBonus(); // bonuses only emerge during play cell.fx = null; paintCreature(fb, cell.ox, cell.oy, cell.creature, t); } else { // draw flying 1px shards const g = 22; for (const pr of cell.fx.parts) { const px = pr.x + pr.vx * e, py = pr.y + pr.vy * e + 0.5 * g * e * e; if (px >= 1 && px < grid.caw - 1 && py >= 1 && py < grid.cah - 1) fb.blk(cell.ox + Math.round(px), cell.oy + Math.round(py), pr.col); } } } else { paintCreature(fb, cell.ox, cell.oy, cell.creature, t); } } // case grid: single 1px lines at every boundary (no doubling at shared edges) if (grid.border !== "none") { const col = NEON[grid.border]; const cwN = grid.caw * BLK, chN = grid.cah * BLK; for (let c = 0; c <= grid.cols; c++) { const x = Math.min(CW - 1, c * cwN); for (let y = 0; y < CH; y++) fb.set(x, y, col); } for (let r = 0; r <= grid.rows; r++) { const y = Math.min(CH - 1, r * chN); for (let x = 0; x < CW; x++) fb.set(x, y, col); } } // bonus cases blink a 2px-wide border in the bonus colour if (Math.floor(now * 3) % 2 === 0) { const cwN = grid.caw * BLK, chN = grid.cah * BLK; for (const cell of grid.cells) { const b = cell.creature.bonus; if (!b || !BONUS[b] || cell.fx) continue; const x0 = cell.ox * BLK, y0 = cell.oy * BLK, c = BONUS[b].col; for (let x = 0; x < cwN; x++) for (let d = 0; d < 2; d++) { fb.set(x0 + x, y0 + d, c); fb.set(x0 + x, Math.min(CH - 1, y0 + chN - 1 - d), c); } for (let y = 0; y < chN; y++) for (let d = 0; d < 2; d++) { fb.set(x0 + d, y0 + y, c); fb.set(Math.min(CW - 1, x0 + cwN - 1 - d), y0 + y, c); } } } } /* ===================================================================== 8. AUDIO (deterministic chiptune sequence, one MASTER-second loop) --------------------------------------------------------------------- Pure data: notes as MIDI numbers (or null for a rest). The harness turns this into sound with Web Audio. 16 steps across MASTER_SECONDS. ===================================================================== */ const SCALES = { minPent: [0, 3, 5, 7, 10], majPent: [0, 2, 4, 7, 9], dorian: [0, 2, 3, 5, 7, 9, 10], lydian: [0, 2, 4, 6, 7, 9, 11], }; const STYLES = ["acid", "stab", "rolling", "arp", "pluck"]; const SONG_BARS = 36; // 8 steps/bar at 0.25s = 2s bar (120 BPM) -> 72s const PROGS = [[0, 0, 4, 3], [0, 3, 4, 2], [0, 4, 2, 3], [0, 2, 4, 5], [0, 0, 3, 4], [0, 5, 3, 4]]; function buildAudio(hash) { const R = makeRng(String(hash) + "|audio"); const SPB = 8; // steps per bar, kick every 2 steps = 120 BPM four-on-the-floor const scaleName = R.pick(["minPent", "dorian", "minPent"]); // darker, driving scales const scale = SCALES[scaleName], SL = scale.length; const root = R.pick([43, 45, 47, 48, 50]); const style = R.weighted([["acid", 4], ["stab", 3], ["rolling", 3], ["arp", 3], ["pluck", 3]]); const prog = R.pick(PROGS); const leadWave = style === "stab" || style === "pluck" ? "square" : "sawtooth"; const bassWave = R.pick(["square", "triangle"]); const noteAt = (deg) => root + 12 + scale[((deg % SL) + SL) % SL] + 12 * Math.floor(deg / SL); // an acid pattern of scale-degree offsets, lightly mutated per bar for variety const basePat = []; for (let i = 0; i < SPB; i++) basePat.push(R.int(0, SL)); const lead = [], bass = [], perc = [], waves = [], vel = []; for (let b = 0; b < SONG_BARS; b++) { const phrase = (b / 4) | 0; const barDeg = prog[phrase % prog.length]; const intense = Math.min(3, phrase); // song structure -> energy dynamics + variation const mode = b < 2 ? "intro" : (phrase % 4 === 3 ? "break" : (b % 8 < 1 ? "build" : "main")); const pat = basePat.map((d) => (R.bool(0.18) ? R.int(0, SL) : d)); // per-bar mutation for (let s = 0; s < SPB; s++) { const beat = s % 2 === 0; // on-beat (quarter) // ---- drums: driving four-on-the-floor ---- let v = 0; if (mode !== "break" && beat) v |= 2; // kick every beat else if (mode === "break" && s === 0) v |= 2; // sparse kick in the break if (!beat) v |= 8; // offbeat open hat (the techno "tss") if ((mode === "main" && intense >= 2)) v |= 1; // driving 16th closed hats when intense if ((mode === "main" || mode === "build") && (s === 2 || s === 6)) v |= 4; // clap on 2 & 4 perc.push(v); // ---- bass: syncopated, driving ---- let bn = null; if (mode !== "intro") { if (!beat || intense >= 2) bn = root - 12 + scale[barDeg % SL]; } bass.push(bn == null ? null : Math.max(24, Math.min(60, bn))); // ---- lead ---- let ln = null, gv = 0.15; if (mode === "main" || mode === "build") { if (style === "stab") { if (!beat) ln = noteAt(barDeg + [0, 2, 4][s % 3]); } else if (style === "rolling") { ln = noteAt(barDeg + basePat[0] + (s % 4 === 0 ? SL : 0)); } else { ln = noteAt(barDeg + pat[s] + (R.bool(0.12) ? SL : 0)); } // acid / arp / pluck if (beat) gv = 0.2; // accent on beats for punch } lead.push(ln == null ? null : Math.max(24, Math.min(96, ln))); vel.push(gv); waves.push(leadWave); } } const STEPS = SONG_BARS * SPB; if (perc.filter((p) => p & 2).length < SONG_BARS) for (let b = 0; b < SONG_BARS; b++) { perc[b * SPB] |= 2; perc[b * SPB + 4] |= 2; } return { steps: STEPS, stepSeconds: MASTER_SECONDS / 16, loopSeconds: STEPS * (MASTER_SECONDS / 16), root, scaleName, style, leadWave, bassWave, waves, vel, lead, bass, perc, }; } /* ===================================================================== 9. EXPORTS ===================================================================== */ return { CW, CH, BLK, MASTER_SECONDS, NEON, makeHash, makeRng, FB, buildGrid, buildCreature, paint, paintCreature, buildAudio, explode, explodeMatch, BONUS, hexToRgb, }; })(); /* ===================== PXL BITS — vanilla harness ===================== Pure Canvas 2D + DOM + Web Audio. No libraries. The engine above (E) is deterministic from the token hash; this drives display, input, the contemplative demo, the 69s game, sound and sharing. */ /* ---- token hash (Art Blocks / Artsunami style) with a local fallback ---- */ var FX_ALPHABET = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; function randomToken() { var s = "oo"; for (var i = 0; i < 49; i++) s += FX_ALPHABET[(Math.random() * FX_ALPHABET.length) | 0]; return s; } var TOKEN_HASH = (typeof tokenData !== "undefined" && tokenData && tokenData.hash) ? tokenData.hash : (typeof fxhash !== "undefined" && fxhash) ? fxhash : (typeof $fx !== "undefined" && $fx && $fx.hash) ? $fx.hash : randomToken(); var CW = E.CW, CH = E.CH, HDR = 24; // header (title/score/timer) native height /* ---- canvas: use the platform's canvas if present, else create one ---- */ var canvas = (typeof window !== "undefined" && window.canvas && window.canvas.getContext) ? window.canvas : (typeof document !== "undefined" && document.querySelector && document.querySelector("canvas")) || (function () { var c = document.createElement("canvas"); (document.body || document.documentElement).appendChild(c); return c; })(); var ctx = canvas.getContext("2d"); try { canvas.style.position = "absolute"; canvas.style.left = "50%"; canvas.style.top = "50%"; canvas.style.transform = "translate(-50%,-50%)"; canvas.style.imageRendering = "pixelated"; } catch (e) {} var off = document.createElement("canvas"); off.width = CW; off.height = CH; var offctx = off.getContext("2d"); var imgData = offctx.createImageData(CW, CH); var fb = new E.FB(); var curHash = TOKEN_HASH; var grid = E.buildGrid(curHash); /* ---- colour cache ---- */ var RGB = {}; function rgbOf(h) { return RGB[h] || (RGB[h] = E.hexToRgb(h)); } var BG = E.hexToRgb(E.NEON.bg); /* ---- clock (monotonic seconds) ---- */ var clock = (typeof performance !== "undefined" && performance.now) ? function () { return performance.now() / 1000; } : function () { return Date.now() / 1000; }; var T0 = clock(); function nowSec() { return clock() - T0; } /* ---- game state ---- */ var score = 0, ROUND = 69, roundState = "demo", roundStart = 0; var lastInteraction = 0, lastAuto = 0, DEMO_RETURN = 18, AUTO_EVERY = 0.5; var multUntil = 0, flashText = "", flashUntil = 0, flashCol = "#ffffff"; var dispScale = 1, dispOX = 0, dispOY = 0; /* ---- per-colour identity (destruction note + timbre + base points) ---- */ var COLOR_META = { green: { note: 64, base: 5, wave: "triangle" }, cyan: { note: 67, base: 6, wave: "triangle" }, blue: { note: 69, base: 7, wave: "sawtooth" }, pink: { note: 72, base: 8, wave: "sawtooth" }, yellow: { note: 62, base: 6, wave: "square" }, orange: { note: 60, base: 7, wave: "square" }, red: { note: 57, base: 9, wave: "square" }, white: { note: 76, base: 10, wave: "square" } }; var META_BY_HEX = {}; for (var _nm in COLOR_META) if (E.NEON[_nm]) META_BY_HEX[String(E.NEON[_nm]).toLowerCase()] = COLOR_META[_nm]; function metaFor(hex) { return (hex && META_BY_HEX[String(hex).toLowerCase()]) || { note: 60, base: 6, wave: "square" }; } var RARITY_MULT = { common: 1, rare: 2, epic: 4, legendary: 8 }; function scoreForCells(idxs) { var s = 0; for (var k = 0; k < idxs.length; k++) { var c = grid.cells[idxs[k]].creature; s += metaFor(c.colors.stroke).base * (RARITY_MULT[c.rarity] || 1); } return s; } /* ---- 3x5 pixel font ---- */ var PXFONT = { "0": ["111", "101", "101", "101", "111"], "1": ["010", "110", "010", "010", "111"], "2": ["111", "001", "111", "100", "111"], "3": ["111", "001", "111", "001", "111"], "4": ["101", "101", "111", "001", "001"], "5": ["111", "100", "111", "001", "111"], "6": ["111", "100", "111", "101", "111"], "7": ["111", "001", "010", "010", "010"], "8": ["111", "101", "111", "101", "111"], "9": ["111", "101", "111", "001", "111"], "A": ["010", "101", "111", "101", "101"], "B": ["110", "101", "110", "101", "110"], "C": ["111", "100", "100", "100", "111"], "D": ["110", "101", "101", "101", "110"], "E": ["111", "100", "111", "100", "111"], "G": ["111", "100", "101", "101", "111"], "H": ["101", "101", "111", "101", "101"], "I": ["111", "010", "010", "010", "111"], "L": ["100", "100", "100", "100", "111"], "M": ["101", "111", "111", "101", "101"], "N": ["101", "111", "111", "111", "101"], "O": ["111", "101", "101", "101", "111"], "P": ["111", "101", "111", "100", "100"], "R": ["110", "101", "110", "101", "101"], "S": ["111", "100", "111", "001", "111"], "T": ["111", "010", "010", "010", "010"], "U": ["101", "101", "101", "101", "111"], "V": ["101", "101", "101", "101", "010"], "X": ["101", "101", "010", "101", "101"], ":": ["000", "010", "000", "010", "000"], " ": ["000", "000", "000", "000", "000"] }; function textW(str, ps) { return str.length * (4 * ps) - ps; } function drawText(cx, str, x, cy, ps, col, align, outlineCol, ob) { var total = textW(str, ps); var sx0 = Math.round(align === "left" ? x : align === "right" ? x - total : x - total / 2); var y0 = Math.round(cy - (5 * ps) / 2); var cells = [], sx = sx0; for (var ci = 0; ci < str.length; ci++) { var g = PXFONT[str[ci]] || PXFONT[" "]; for (var r = 0; r < 5; r++) for (var c = 0; c < 3; c++) if (g[r][c] === "1") cells.push([sx + c * ps, y0 + r * ps]); sx += 4 * ps; } if (outlineCol) { var o = ob || 2; cx.fillStyle = outlineCol; for (var p = 0; p < cells.length; p++) cx.fillRect(cells[p][0] - o, cells[p][1] - o, ps + 2 * o, ps + 2 * o); } cx.fillStyle = col; for (var q = 0; q < cells.length; q++) cx.fillRect(cells[q][0], cells[q][1], ps, ps); } /* ---- sizing: fill the frame at the game's portrait aspect ---- */ function fitSize() { var NW = CW, NH = CH + HDR; var W = (typeof innerWidth === "number" && innerWidth) || 0; var H = (typeof innerHeight === "number" && innerHeight) || 0; if (W < 2 || H < 2) { W = 540; H = 1080; } var s = Math.min(W / NW, H / NH); return { w: Math.max(1, Math.round(NW * s)), h: Math.max(1, Math.round(NH * s)) }; } function resize() { var sz = fitSize(); canvas.width = sz.w; canvas.height = sz.h; ctx.imageSmoothingEnabled = false; } resize(); if (typeof addEventListener !== "undefined") addEventListener("resize", resize); /* ============================ AUDIO (Web Audio) ============================ */ var AC = null, master = null, muted = false, audioStarted = false; var seq = null, nextStepTime = 0, stepIdx = 0, visT0 = 0, schedTimer = null; var LOOKAHEAD = 0.10, TICK_MS = 25; function midiToFreq(m) { return 440 * Math.pow(2, (m - 69) / 12); } function buildSeq() { seq = E.buildAudio(curHash); } function startAudio() {
/* PXL BITS — on-chain generative pixel-monster artwork + game. VANILLA CANVAS (no libraries). Deterministic from tokenData.hash. Paste into the Artsunami editor. */ /* ===================================================================== PXL GRID - CREATURE GRID --------------------------------------------------------------------- A 256 x 512 (portrait 1:2) native canvas, divided into cases (cells). Each case holds one generative abstract pixel-art character that plays a seamless looping animation. Determinism contract (unchanged from the base engine) - (hash) -> grid. Same hash, same 32/8/128 creatures, same colours. - Every creature is frozen in buildCreature(); paintCreature() reads only the frozen spec + a time value, so animation never touches the RNG. Playback is stable and reproducible. - Seamless global loop: every creature period divides MASTER_SECONDS, so the whole board returns to its exact start every loop. - Backend agnostic: writes into the same abstract FB used by the base engine, so a Node verifier can rasterise identical frames to PNG. Native resolution: 256 x 512. Art block: 4 native px (chunky pixels). ===================================================================== */ const E = (function () { "use strict"; /* ---------- native canvas + chunk size ---------- */ const CW = 256, CH = 512; // native pixels const BLK = 4; // one "art pixel" = 4x4 native px const MASTER_SECONDS = 4; // global loop length /* ---------- neon palette (locked to the reference sheets) ---------- */ const NEON = { blue: "#1f2bff", green: "#00ff2f", pink: "#ff17c6", yellow: "#ffe600", white: "#ffffff", cyan: "#00e5ff", orange: "#ff7a00", red: "#ff2740", dblue: "#0b1280", bg: "#05060f", }; const BODY_POOL = [ ["green", 5], ["pink", 5], ["cyan", 4], ["yellow", 4], ["orange", 3], ["red", 3], ["blue", 3], ["white", 4], ]; /* ===================================================================== 1. DETERMINISTIC RNG (xmur3 -> sfc32) [reused, proven] ===================================================================== */ function xmur3(str) { let h = 1779033703 ^ str.length; for (let i = 0; i < str.length; i++) { h = Math.imul(h ^ str.charCodeAt(i), 3432918353); h = (h << 13) | (h >>> 19); } return function () { h = Math.imul(h ^ (h >>> 16), 2246822507); h = Math.imul(h ^ (h >>> 13), 3266489909); h ^= h >>> 16; return h >>> 0; }; } function sfc32(a, b, c, d) { return function () { a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; let t = (a + b) | 0; a = b ^ (b >>> 9); b = (c + (c << 3)) | 0; c = (c << 21) | (c >>> 11); d = (d + 1) | 0; t = (t + d) | 0; c = (c + t) | 0; return (t >>> 0) / 4294967296; }; } function makeRng(hash) { const seed = xmur3(String(hash)); const rand = sfc32(seed(), seed(), seed(), seed()); for (let i = 0; i < 15; i++) rand(); return { f: rand, int: (a, b) => a + Math.floor(rand() * (b - a + 1)), range: (a, b) => a + rand() * (b - a), bool: (p = 0.5) => rand() < p, pick: (arr) => arr[Math.floor(rand() * arr.length)], weighted: (pairs) => { let tot = 0; for (const p of pairs) tot += p[1]; let x = rand() * tot; for (const p of pairs) { if ((x -= p[1]) < 0) return p[0]; } return pairs[pairs.length - 1][0]; }, shuffle: (arr) => { const a = arr.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(rand() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }, }; } function makeHash(seed) { const alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; let base; if (seed == null) { base = (typeof crypto !== "undefined" && crypto.getRandomValues) ? Array.from(crypto.getRandomValues(new Uint32Array(8))).join("") : (Date.now() + "" + Math.random()); } else base = String(seed); const gen = xmur3(base + "|fx"); let out = "oo"; for (let i = 0; i < 49; i++) out += alphabet[gen() % alphabet.length]; return out; } /* ===================================================================== 2. FRAMEBUFFER (abstract raster) [reused, proven] ===================================================================== */ class FB { constructor(w = CW, h = CH) { this.w = w; this.h = h; this.d = new Array(w * h).fill(null); } clear(c = null) { this.d.fill(c); } inside(x, y) { return x >= 0 && x < this.w && y >= 0 && y < this.h; } set(x, y, c) { x |= 0; y |= 0; if (this.inside(x, y) && c) this.d[y * this.w + x] = c; } get(x, y) { x |= 0; y |= 0; return this.inside(x, y) ? this.d[y * this.w + x] : null; } hline(x, y, len, c) { for (let i = 0; i < len; i++) this.set(x + i, y, c); } vline(x, y, len, c) { for (let i = 0; i < len; i++) this.set(x, y + i, c); } fill(x, y, w, h, c) { for (let j = 0; j < h; j++) for (let i = 0; i < w; i++) this.set(x + i, y + j, c); } // one art pixel = BLK x BLK native block, snapped to the art grid blk(ax, ay, c) { this.fill(ax * BLK, ay * BLK, BLK, BLK, c); } } /* ===================================================================== 3. MASK HELPERS (build a creature silhouette on a small art grid) ===================================================================== */ function Mask(w, h) { return { w, h, d: new Uint8Array(w * h) }; } function mset(m, x, y, v = 1) { if (x >= 0 && x < m.w && y >= 0 && y < m.h) m.d[y * m.w + x] = v; } function mget(m, x, y) { return (x >= 0 && x < m.w && y >= 0 && y < m.h) ? m.d[y * m.w + x] : 0; } function ellipseMask(m, cx, cy, rx, ry) { for (let y = 0; y < m.h; y++) for (let x = 0; x < m.w; x++) { const dx = (x - cx) / (rx + 0.0001), dy = (y - cy) / (ry + 0.0001); if (dx * dx + dy * dy <= 1) mset(m, x, y, 1); } } function rectMask(m, x0, y0, w, h) { for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) mset(m, x0 + x, y0 + y, 1); } function roundRectMask(m, x0, y0, w, h, r) { for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { let on = true; // knock out the four corners with a quarter-circle test const corners = [[x0 + r - 1, y0 + r - 1, x, y, -1, -1], [x0 + w - r, y0 + r - 1, x, y, 1, -1], [x0 + r - 1, y0 + h - r, x, y, -1, 1], [x0 + w - r, y0 + h - r, x, y, 1, 1]]; const gx = x0 + x, gy = y0 + y; if (x < r && y < r) { const dx = r - 1 - x, dy = r - 1 - y; if (dx * dx + dy * dy > r * r) on = false; } else if (x >= w - r && y < r) { const dx = x - (w - r), dy = r - 1 - y; if (dx * dx + dy * dy > r * r) on = false; } else if (x < r && y >= h - r) { const dx = r - 1 - x, dy = y - (h - r); if (dx * dx + dy * dy > r * r) on = false; } else if (x >= w - r && y >= h - r) { const dx = x - (w - r), dy = y - (h - r); if (dx * dx + dy * dy > r * r) on = false; } if (on) mset(m, gx, gy, 1); void corners; } } function diamondMask(m, cx, cy, rx, ry) { for (let y = 0; y < m.h; y++) for (let x = 0; x < m.w; x++) { if (Math.abs(x - cx) / (rx + 0.0001) + Math.abs(y - cy) / (ry + 0.0001) <= 1) mset(m, x, y, 1); } } // per-row half widths for a bell / trapezoid, mirrored for symmetry function columnMask(m, cx, y0, h, topHalf, botHalf) { for (let j = 0; j < h; j++) { const t = h <= 1 ? 0 : j / (h - 1); const half = Math.round(topHalf + (botHalf - topHalf) * t); for (let x = cx - half; x <= cx + half; x++) mset(m, x, y0 + j, 1); } } function hexMask(m, cx, cy, rx, ry) { for (let y = 0; y < m.h; y++) { const t = Math.abs(y - cy) / (ry + 0.0001); if (t > 1) continue; const half = t < 0.5 ? rx : Math.round(rx * (1 - (t - 0.5) / 0.5 * 0.85)); for (let x = cx - half; x <= cx + half; x++) mset(m, x, y, 1); } } function crossMask(m, cx, cy, rx, ry, arm) { for (let y = cy - ry; y <= cy + ry; y++) for (let x = cx - arm; x <= cx + arm; x++) mset(m, x, y, 1); for (let x = cx - rx; x <= cx + rx; x++) for (let y = cy - arm; y <= cy + arm; y++) mset(m, x, y, 1); } function sparkleMask(m, cx, cy, rx, ry) { // 4-point star: tapered vertical + horizontal spikes + centre diamond for (let y = cy - ry; y <= cy + ry; y++) { const half = Math.max(0, Math.round((1 - Math.abs(y - cy) / (ry + 0.0001)) * 1)); for (let x = cx - half; x <= cx + half; x++) mset(m, x, y, 1); } for (let x = cx - rx; x <= cx + rx; x++) { const half = Math.max(0, Math.round((1 - Math.abs(x - cx) / (rx + 0.0001)) * 1)); for (let y = cy - half; y <= cy + half; y++) mset(m, x, y, 1); } diamondMask(m, cx, cy, Math.max(1, rx * 0.4), Math.max(1, ry * 0.4)); } // outline = filled cell that touches empty/edge on a 4-neighbour function outlineOf(m) { const out = []; for (let y = 0; y < m.h; y++) for (let x = 0; x < m.w; x++) { if (!mget(m, x, y)) continue; if (!mget(m, x - 1, y) || !mget(m, x + 1, y) || !mget(m, x, y - 1) || !mget(m, x, y + 1)) out.push([x, y]); } return out; } function fillPixels(m) { const px = []; for (let y = 0; y < m.h; y++) for (let x = 0; x < m.w; x++) if (mget(m, x, y)) px.push([x, y]); return px; } function bounds(m) { let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9; for (let y = 0; y < m.h; y++) for (let x = 0; x < m.w; x++) if (mget(m, x, y)) { if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y; } if (x1 < x0) return { x0: 0, y0: 0, w: 0, h: 0 }; return { x0, y0, w: x1 - x0 + 1, h: y1 - y0 + 1 }; } /* ===================================================================== 4. COLOUR UTILITIES ===================================================================== */ function hexToRgb(hx) { return [parseInt(hx.slice(1, 3), 16), parseInt(hx.slice(3, 5), 16), parseInt(hx.slice(5, 7), 16)]; } function rgbToHex(r, g, b) { const h = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0"); return "#" + h(r) + h(g) + h(b); } function shade(hx, f) { const [r, g, b] = hexToRgb(hx); return f >= 0 ? rgbToHex(r + (255 - r) * f, g + (255 - g) * f, b + (255 - b) * f) : rgbToHex(r * (1 + f), g * (1 + f), b * (1 + f)); } function luminance(hx) { const [r, g, b] = hexToRgb(hx); return (0.299 * r + 0.587 * g + 0.114 * b) / 255; } /* ===================================================================== 5. CREATURE GENERATOR (abstract character, frozen form) --------------------------------------------------------------------- cw, ch = art-grid size of the cell (e.g. 16x16 for a 64px cell). Returns a spec whose paint cost is a few short pixel loops. ===================================================================== */ const PERIODS = [MASTER_SECONDS / 2, MASTER_SECONDS / 4, MASTER_SECONDS / 8, MASTER_SECONDS / 4]; const BODY_TEMPLATES = ["blob", "orb", "bell", "tower", "ghost", "diamond", "bug", "drop"]; // shared rhythmic hop: every creature pops on the 8th-note grid (8 per loop) const HOP_SECONDS = MASTER_SECONDS / 8; function hopEnv(t) { const p = (((t / HOP_SECONDS) % 1) + 1) % 1; return Math.exp(-p * 5); } /* ---- gameplay bonuses (only ever assigned on respawn, never in the frozen art) ---- */ const BONUS = { bomb: { col: "#ff7a00", icon: ["010", "111", "111"] }, // clears row + column gold: { col: "#ffe600", icon: ["111", "101", "111"] }, // extra BITS time: { col: "#00e5ff", icon: ["010", "111", "010"] }, // +time x2: { col: "#ff17c6", icon: ["101", "010", "101"] }, // score x2 for a few seconds rainbow: { col: "#ffffff", icon: ["111", "111", "111"] }, // clears every monster of its colour }; const BONUS_KEYS = ["bomb", "gold", "time", "x2", "rainbow"]; function randomBonus() { return BONUS_KEYS[(Math.random() * BONUS_KEYS.length) | 0]; } function buildCreature(R, cw, ch, theme) { const margin = 1; // keep off the case border const aw = cw - margin * 2; // available art width const ah = ch - margin * 2; // ----- template + optional face type ----- const tmpl = R.weighted([ ["blob", 5], ["orb", 4], ["bell", 3], ["tower", 3], ["ghost", 4], ["diamond", 3], ["bug", 3], ["drop", 3], ["mushroom", 3], ["capsule", 3], ["hex", 3], ["cross", 2], ["stack", 2], ["crystal", 2], ["slime", 3], ["star", 2], ["skull", 4], ["alien", 4], ]); const face = (tmpl === "skull" || tmpl === "alien") ? tmpl : null; // ----- palette : OUTLINE style (1px neon stroke on black, like the sheets) ----- const bodyName = R.pick(theme.bodies); let body = NEON[bodyName]; // 1px stroke colour (hollow body) if (face === "skull") body = NEON.white; // white skull outline else if (face === "alien") body = R.pick([NEON.white, NEON.green, NEON.cyan]); let feat = NEON[R.pick(theme.accents)]; // eyes / mouth neon if (feat === body) feat = (body === NEON.white) ? NEON.cyan : NEON.white; const eyeCol = face === "skull" ? NEON.yellow : feat; // skull: yellow eye holes like the sheets const mouthCol = feat; const outline = body; // arms / feet / antennae render in the stroke colour const glow = shade(body, -0.4); // dim halo for the pulse expression const cellBg = NEON.bg; // cases are always black, never tinted // ----- silhouette ----- const m = Mask(cw, ch); // pick a body box that leaves head-room for antennae and feet const bw = Math.max(3, Math.min(aw, R.int(Math.ceil(aw * 0.55), aw))); const bh = Math.max(3, Math.min(ah - 2, R.int(Math.ceil(ah * 0.55), ah - 2))); const cx = (cw - 1) / 2; // centre the body vertically, with a small deterministic jitter const slack = Math.max(0, ah - bh); const topY = margin + Math.max(0, Math.min(slack, Math.round(slack / 2) + R.int(-1, 1))); const cyBody = topY + bh / 2; switch (tmpl) { case "orb": ellipseMask(m, cx, cyBody, bw / 2, bh / 2); break; case "diamond": diamondMask(m, cx, cyBody, bw / 2, bh / 2); break; case "bell": columnMask(m, Math.round(cx), topY, bh, Math.max(1, Math.round(bw * 0.28)), Math.round(bw / 2)); break; case "tower": roundRectMask(m, Math.round(cx - bw / 2), topY, bw, bh, Math.min(3, Math.floor(bw / 2))); break; case "drop": { ellipseMask(m, cx, cyBody + bh * 0.12, bw / 2, bh * 0.42); columnMask(m, Math.round(cx), topY, Math.round(bh * 0.55), 1, Math.round(bw / 2)); break; } case "ghost": { ellipseMask(m, cx, topY + bw / 2, bw / 2, bw / 2); rectMask(m, Math.round(cx - bw / 2), Math.round(topY + bw / 2), bw, Math.round(bh - bw / 2)); const by = topY + bh - 1; for (let x = 0; x < bw; x++) if (x % 2 === 0) mset(m, Math.round(cx - bw / 2) + x, by, 0); break; } case "bug": ellipseMask(m, cx, cyBody, bw / 2, bh / 2.4); break; case "mushroom": { const capH = Math.max(2, Math.round(bh * 0.5)); ellipseMask(m, cx, topY + capH, bw / 2, capH); // keep only the dome (flat under the cap), then add a centred stem for (let y = topY + capH; y < ch; y++) for (let x = 0; x < cw; x++) mset(m, x, y, 0); const stemW = Math.max(1, Math.round(bw * 0.32)); rectMask(m, Math.round(cx - stemW / 2), topY + capH, stemW, Math.max(2, bh - capH)); break; } case "capsule": roundRectMask(m, Math.round(cx - bw / 2), topY, bw, bh, Math.floor(Math.min(bw, bh) / 2)); break; case "hex": hexMask(m, cx, cyBody, bw / 2, bh / 2); break; case "cross": crossMask(m, Math.round(cx), Math.round(cyBody), Math.round(bw / 2), Math.round(bh / 2), Math.max(1, Math.round(bw * 0.22))); break; case "stack": { const gap = 1, hEach = Math.max(2, Math.round((bh - gap) / 2)); const wTop = Math.max(3, Math.round(bw * 0.7)); roundRectMask(m, Math.round(cx - wTop / 2), topY, wTop, hEach, 1); roundRectMask(m, Math.round(cx - bw / 2), topY + hEach + gap, bw, hEach, 1); break; } case "crystal": diamondMask(m, cx, cyBody, Math.max(2, bw / 2.6), bh / 2); break; case "slime": { ellipseMask(m, cx, topY + bh, bw / 2, bh); // dome for (let y = topY + bh; y < ch; y++) for (let x = 0; x < cw; x++) mset(m, x, y, 0); // flat bottom for (let y = 0; y < topY; y++) for (let x = 0; x < cw; x++) mset(m, x, y, 0); break; } case "star": sparkleMask(m, Math.round(cx), Math.round(cyBody), Math.round(bw / 2), Math.round(bh / 2)); break; case "skull": { const cranH = Math.max(3, Math.round(bh * 0.62)); roundRectMask(m, Math.round(cx - bw / 2), topY, bw, cranH, Math.min(4, Math.floor(bw / 3))); const jawW = Math.max(3, Math.round(bw * 0.6)), jawH = Math.max(2, bh - cranH); roundRectMask(m, Math.round(cx - jawW / 2), topY + cranH, jawW, jawH, 1); break; } case "alien": { const crown = Math.max(2, Math.round(bw * 0.42)); ellipseMask(m, cx, topY + crown, bw / 2, crown); // rounded crown columnMask(m, Math.round(cx), topY + crown, Math.max(2, bh - crown), Math.round(bw / 2), 1); // taper to a chin point break; } default: roundRectMask(m, Math.round(cx - bw / 2), topY, bw, bh, Math.min(3, Math.floor(Math.min(bw, bh) / 3))); } let bb = bounds(m); let fillPx = fillPixels(m); let outPx = outlineOf(m); if (fillPx.length === 0) { // safety: never leave a case empty roundRectMask(m, Math.round(cx - bw / 2), topY, bw, bh, 2); bb = bounds(m); fillPx = fillPixels(m); outPx = outlineOf(m); } // ----- eyes + mouth (always present) ----- let eyes = [], mouth = null, bigEyes = false; if (face === "skull") { const s = Math.max(1, Math.round(bb.w * 0.22)); const ey = Math.round(bb.y0 + bb.h * 0.34); eyes = [{ x: Math.round(cx - s), y: ey }, { x: Math.round(cx + s), y: ey }]; const mwid = Math.max(2, Math.round(bb.w * 0.32)); mouth = { y: Math.round(bb.y0 + bb.h * 0.76), x0: Math.round(cx - mwid), x1: Math.round(cx + mwid), smile: false }; } else if (face === "alien") { const s = Math.max(2, Math.round(bb.w * 0.24)); const ey = Math.round(bb.y0 + bb.h * 0.32); eyes = [{ x: Math.round(cx - s), y: ey }, { x: Math.round(cx + s), y: ey }]; const mwid = Math.max(1, Math.round(bb.w * 0.16)); mouth = { y: Math.round(bb.y0 + bb.h * 0.64), x0: Math.round(cx - mwid), x1: Math.round(cx + mwid), smile: false }; } else { const nEyes = R.weighted([[2, 7], [1, 1], [3, 1]]); const eyeRowY = Math.round(bb.y0 + bb.h * R.range(0.28, 0.44)); if (nEyes === 1) eyes.push({ x: Math.round(cx), y: eyeRowY }); else if (nEyes === 2) { const s = Math.max(1, Math.round(bb.w * 0.2)); eyes.push({ x: Math.round(cx - s), y: eyeRowY }, { x: Math.round(cx + s), y: eyeRowY }); } else { const s = Math.max(1, Math.round(bb.w * 0.24)); eyes.push({ x: Math.round(cx - s), y: eyeRowY }, { x: Math.round(cx), y: eyeRowY }, { x: Math.round(cx + s), y: eyeRowY }); } bigEyes = bb.w >= 12; const my = Math.min(bb.y0 + bb.h - 2, eyeRowY + Math.max(2, Math.round(bb.h * 0.22))); const mw = Math.max(1, Math.round(bb.w * R.range(0.22, 0.4))); mouth = { y: my, x0: Math.round(cx - mw), x1: Math.round(cx + mw), smile: R.bool(0.7) }; } // ----- antennae (top) ----- const antennae = []; if (!face && R.bool(0.5)) { const h = R.int(1, Math.max(1, Math.min(3, topY - margin + 1))); if (h >= 1) { const off = R.bool(0.6) ? Math.max(1, Math.round(bb.w * 0.22)) : 0; const list = off ? [-off, off] : [0]; for (const o of list) antennae.push({ x: Math.round(cx + o), baseY: bb.y0, h, tip: feat }); } } // ----- feet (bottom) ----- const feet = []; if (!face && R.bool(0.55) && bb.y0 + bb.h + 1 < ch) { const s = Math.max(1, Math.round(bb.w * 0.22)); feet.push({ x: Math.round(cx - s), y: bb.y0 + bb.h }, { x: Math.round(cx + s), y: bb.y0 + bb.h }); } // ----- arms (sides) ----- const arms = []; if (!face && R.bool(0.4)) { const ay = Math.round(bb.y0 + bb.h * 0.55); arms.push({ x: bb.x0 - 1, y: ay, side: -1 }, { x: bb.x0 + bb.w, y: ay, side: 1 }); } // ----- animation channels (all periods divide MASTER) ----- // drift = travels along a direction (up-down, down-up, left-right, right-left, diagonals) const DIRS = [[0, -1], [0, 1], [-1, 0], [1, 0], [1, 1], [-1, 1], [1, -1], [-1, -1]]; const motion = R.weighted([ ["drift", 8], ["float", 2], ["burst", 3], ["wiggle", antennae.length ? 3 : 0], ["march", feet.length ? 3 : 0], ["wave", arms.length ? 3 : 0], ]); const anim = { motion, dir: R.pick(DIRS), period: R.pick(PERIODS), off: R.range(0, 1), amp: R.int(1, 2), }; // pixel explosion: 1px particles fly out from the centre, loop-safe (frozen here) if (motion === "burst") { const N = R.int(9, 15), reach = Math.max(4, Math.round(Math.max(bb.w, bb.h) * 0.95)); anim.burst = []; for (let i = 0; i < N; i++) anim.burst.push({ ang: (i / N) * Math.PI * 2 + R.range(-0.35, 0.35), maxR: R.int(3, reach), off: R.range(0, 0.18), col: R.bool(0.5) ? body : feat, }); } // ----- expression (eyes + mouth), loop-safe periods, features stay visible ----- const EXPR_PERIODS = [MASTER_SECONDS, MASTER_SECONDS / 2]; anim.eyeExpr = face === "skull" ? "still" : face === "alien" ? R.pick(["blink", "look", "still"]) : R.pick(["blink", "look", "squint", "wink", "still"]); anim.mouthExpr = face === "skull" ? "chatter" : face === "alien" ? R.pick(["still", "open"]) : R.pick(["talk", "smileshift", "open", "still"]); anim.exprPeriod = R.pick(EXPR_PERIODS); anim.exprOff = R.range(0, 1); const hopAmt = cw <= 8 ? 1 : 2; // dense cells hop less // ----- background: always plain black (no patterns, no tint) ----- const back = { type: "plain", col: NEON.bg, items: [], anim: false, period: PERIODS[0], off: 0 }; const rarity = R.weighted([["common", 70], ["rare", 20], ["epic", 8], ["legendary", 2]]); return { cw, ch, bb, fillPx, outPx, eyes, bigEyes, mouth, antennae, feet, arms, back, tmpl, hopAmt, face, bonus: null, rarity, colors: { body, outline, stroke: body, feat, eye: eyeCol, mouthCol, glow, bg: cellBg }, anim, }; } /* ===================================================================== 6. GRID SPEC (choose layout + theme, build one creature per case) ===================================================================== */ /* Fixed grid for the whole collection: same cases for every piece. */ const FIXED = { cols: 4, rows: 8 }; function buildGrid(hash) { const R = makeRng(hash); const cols = FIXED.cols, rows = FIXED.rows; const cellW = CW / cols, cellH = CH / rows; // native px per cell const caw = Math.floor(cellW / BLK), cah = Math.floor(cellH / BLK); // art px per cell // cohesive theme: a small shared palette for the whole board const bodies = R.shuffle(BODY_POOL.map((p) => p[0])).slice(0, R.int(3, 5)); const accents = R.shuffle(["white", "green", "pink", "yellow", "cyan", "orange", "red", "blue"]).slice(0, 4); const theme = { bodies, accents }; const border = R.weighted([["blu
0xa2c3bdc9…c101·#25,827,687·0x350ea298…ef49cb
V[o","attributes":[`{"trait_type":"Artist","value":"`{"trait_type":"Library","value":`{"trait_type":"License","value":`{"trait_type":"Hash","value":"0x`data:application/json;base64,Rmdata too large`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef`ghijklmnopqrstuvwxyz0123456789+/`?R` PXL BIT is a fully on-chain generative game by crypto artist Pixel Sacrifice. A vibrant pixel universe where characters, code and playful mechanics come together on-chain. Every bit is generated from code, creating a unique cast of digital creatures and unexpected combinations. A nostalgic arcade experience rebuilt for the blockchain, one pixel at a time.https://artsunami.com/gen/1https://artsunami.com/api/gen/preview/1data:application/json;base64,eyJuYW1lIjoiUFhMIEJJVCIsImRlc2NyaXB0aW9uIjoiUFhMIEJJVCBpcyBhIGZ1bGx5IG9uLWNoYWluIGdlbmVyYXRpdmUgZ2FtZSBieSBjcnlwdG8gYXJ0aXN0IFBpeGVsIFNhY3JpZmljZS5cbkEgdmlicmFudCBwaXhlbCB1bml2ZXJzZSB3aGVyZSBjaGFyYWN0ZXJzLCBjb2RlIGFuZCBwbGF5ZnVsIG1lY2hhbmljcyBjb21lIHRvZ2V0aGVyIG9uLWNoYWluLlxuRXZlcnkgYml0IGlzIGdlbmVyYXRlZCBmcm9tIGNvZGUsIGNyZWF0aW5nIGEgdW5pcXVlIGNhc3Qgb2YgZGlnaXRhbCBjcmVhdHVyZXMgYW5kIHVuZXhwZWN0ZWQgY29tYmluYXRpb25zLlxuQSBub3N0YWxnaWMgYXJjYWRlIGV4cGVyaWVuY2UgcmVidWlsdCBmb3IgdGhlIGJsb2NrY2hhaW4sIG9uZSBwaXhlbCBhdCBhIHRpbWUuIiwiaW1hZ2UiOiJkYXRhOmltYWdlL3N2Zyt4bWw7YmFzZTY0LFBITjJaeUI0Yld4dWN6MGlhSFIwY0RvdkwzZDNkeTUzTXk1dmNtY3ZNakF3TUM5emRtY2lJSGRwWkhSb1BTSTFNVElpSUdobGFXZG9kRDBpTlRFeUlpQjJhV1YzUW05NFBTSXdJREFnTlRFeUlEVXhNaUkrUEdSbFpuTStQR3hwYm1WaGNrZHlZV1JwWlc1MElHbGtQU0ppWnlJZ2VERTlJakFpSUhreFBTSXdJaUI0TWowaU1TSWdlVEk5SWpFaVBqeHpkRzl3SUc5bVpuTmxkRDBpTUNJZ2MzUnZjQzFqYjJ4dmNqMGlhSE5zS0RVZ05ESWxJRGdsS1NJdlBqeHpkRzl3SUc5bVpuTmxkRDBpTVNJZ2MzUnZjQzFqYjJ4dmNqMGlhSE5zS0RRMUlEUTJKU0F4TlNVcElpOCtQQzlzYVc1bFlYSkhjbUZrYVdWdWRENDhMMlJsWm5NK1BISmxZM1FnZDJsa2RHZzlJalV4TWlJZ2FHVnBaMmgwUFNJMU1USWlJSEo0UFNJNU5pSWdabWxzYkQwaWRYSnNLQ05pWnlraUx6NDhjR0YwYUNCa1BTSk5JRFF3SURFek1DQk1JRFF3SURnd0xqZ2dUQ0ExTWlBNE5DNDBJRXdnTmpRZ09EZ3VPQ0JNSURjMklEa3pMallnVENBNE9DQTVPQzQ1SUV3Z01UQXdJREV3TkM0MUlFd2dNVEV5SURFeE1DNHlJRXdnTVRJMElERXhOUzQ1SUV3Z01UTTJJREV5TVM0MUlFd2dNVFE0SURFeU5pNDNJRXdnTVRZd0lERXpNUzQySUV3Z01UY3lJREV6TlM0NElFd2dNVGcwSURFek9TNDFJRXdnTVRrMklERTBNaTR6SUV3Z01qQTRJREUwTkM0MElFd2dNakl3SURFME5TNDJJRXdnTWpNeUlERTBOUzQ0SUV3Z01qUTBJREUwTlM0eUlFd2dNalUySURFME15NDNJRXdnTWpZNElERTBNUzR6SUV3Z01qZ3dJREV6T0M0eElFd2dNamt5SURFek5DNHlJRXdnTXpBMElERXlPUzQzSUV3Z016RTJJREV5TkM0MklFd2dNekk0SURFeE9TNHlJRXdnTXpRd0lERXhNeTQySUV3Z016VXlJREV3Tnk0NUlFd2dNelkwSURFd01pNHlJRXdnTXpjMklEazJMamNnVENBek9EZ2dPVEV1TmlCTUlEUXdNQ0E0Tnk0d0lFd2dOREV5SURneUxqa2dUQ0EwTWpRZ056a3VOU0JNSURRek5pQTNOaTQ1SUV3Z05EUTRJRGMxTGpFZ1RDQTBOakFnTnpRdU1pQk1JRFEzTWlBM05DNHpJRXdnTkRjeUlERTFNQ0JNSURRd0lERTFNQ0JhSWlCbWFXeHNQU0pvYzJ3b05qVWdOemdsSURZeUpTa2lJRzl3WVdOcGRIazlJakF1TlRNaUx6NDhjR0YwYUNCa1BTSk5JRFF3SURFNU9DQk1JRFF3SURFNU5DNDFJRXdnTlRJZ01Ua3dMakVnVENBMk5DQXhPRFV1TlNCTUlEYzJJREU0TUM0MklFd2dPRGdnTVRjMUxqWWdUQ0F4TURBZ01UY3dMamdnVENBeE1USWdNVFkyTGpFZ1RDQXhNalFnTVRZeExqY2dUQ0F4TXpZZ01UVTNMamNnVENBeE5EZ2dNVFUwTGpJZ1RDQXhOakFnTVRVeExqUWdUQ0F4TnpJZ01UUTVMaklnVENBeE9EUWdNVFEzTGpnZ1RDQXhPVFlnTVRRM0xqRWdUQ0F5TURnZ01UUTNMaklnVENBeU1qQWdNVFE0TGpFZ1RDQXlNeklnTVRRNUxqZ2dUQ0F5TkRRZ01UVXlMakVnVENBeU5UWWdNVFUxTGpJZ1RDQXlOamdnTVRVNExqZ2dUQ0F5T0RBZ01UWXlMamtnVENBeU9USWdNVFkzTGpRZ1RDQXpNRFFnTVRjeUxqRWdUQ0F6TVRZZ01UYzNMakVnVENBek1qZ2dNVGd5TGpBZ1RDQXpOREFnTVRnMkxqZ2dUQ0F6TlRJZ01Ua3hMalFnVENBek5qUWdNVGsxTGpjZ1RDQXpOellnTVRrNUxqVWdUQ0F6T0RnZ01qQXlMamdnVENBME1EQWdNakExTGpRZ1RDQTBNVElnTWpBM0xqUWdUQ0EwTWpRZ01qQTRMallnVENBME16WWdNakE1TGpBZ1RDQTBORGdnTWpBNExqWWdUQ0EwTmpBZ01qQTNMalFnVENBME56SWdNakExTGpVZ1RDQTBOeklnTWpFNElFd2dOREFnTWpFNElGb2lJR1pwYkd3OUltaHpiQ2d6TWpVZ09ESWxJRFU0SlNraUlHOXdZV05wZEhrOUlqQXVNekVpTHo0OGNHRjBhQ0JrUFNKTklEUXdJREkyTmlCTUlEUXdJREl4TlM0NUlFd2dOVElnTWpFNExqWWdUQ0EyTkNBeU1qSXVNU0JNSURjMklESXlOaTR4SUV3Z09EZ2dNak13TGpZZ1RDQXhNREFnTWpNMUxqWWdUQ0F4TVRJZ01qUXdMamdnVENBeE1qUWdNalEyTGpFZ1RDQXhNellnTWpVeExqUWdUQ0F4TkRnZ01qVTJMallnVENBeE5qQWdNall4TGpVZ1RDQXhOeklnTWpZMkxqRWdUQ0F4T0RRZ01qY3dMakVnVENBeE9UWWdNamN6TGpVZ1RDQXlNRGdnTWpjMkxqSWdUQ0F5TWpBZ01qYzRMakVnVENBeU16SWdNamM1TGpJZ1RDQXlORFFnTWpjNUxqVWdUQ0F5TlRZZ01qYzRMamtnVENBeU5qZ2dNamMzTGpVZ1RDQXlPREFnTWpjMUxqTWdUQ0F5T1RJZ01qY3lMak1nVENBek1EUWdNalk0TGpjZ1RDQXpNVFlnTWpZMExqUWdUQ0F6TWpnZ01qVTVMamdnVENBek5EQWdNalUwTGpjZ1RDQXpOVElnTWpRNUxqVWdUQ0F6TmpRZ01qUTBMakVnVENBek56WWdNak00TGpnZ1RDQXpPRGdnTWpNekxqY2dUQ0EwTURBZ01qSTRMamtnVENBME1USWdNakkwTGpVZ1RDQTBNalFnTWpJd0xqY2dUQ0EwTXpZZ01qRTNMalVnVENBME5EZ2dNakUxTGpFZ1RDQTBOakFnTWpFekxqUWdUQ0EwTnpJZ01qRXlMallnVENBME56SWdNamcySUV3Z05EQWdNamcySUZvaUlHWnBiR3c5SW1oemJDZ3hNallnTnpBbElEY3dKU2tpSUc5d1lXTnBkSGs5SWpBdU5qSWlMejQ4Y0dGMGFDQmtQU0pOSURRd0lETXpOQ0JNSURRd0lETXhNUzQzSUV3Z05USWdNekE0TGpnZ1RDQTJOQ0F6TURZdU1DQk1JRGMySURNd015NDFJRXdnT0RnZ016QXhMaklnVENBeE1EQWdNams1TGpJZ1RDQXhNVElnTWprM0xqWWdUQ0F4TWpRZ01qazJMalFnVENBeE16WWdNamsxTGpjZ1RDQXhORGdnTWprMUxqUWdUQ0F4TmpBZ01qazFMallnVENBeE56SWdNamsyTGpNZ1RDQXhPRFFnTWprM0xqUWdUQ0F4T1RZZ01qazVMakFnVENBeU1EZ2dNekF3TGprZ1RDQXlNakFnTXpBekxqSWdUQ0F5TXpJZ016QTFMamNnVENBeU5EUWdNekE0TGpRZ1RDQXlOVFlnTXpFeExqTWdUQ0F5TmpnZ016RTBMak1nVENBeU9EQWdNekUzTGpNZ1RDQXlPVElnTXpJd0xqRWdUQ0F6TURRZ016SXlMamtnVENBek1UWWdNekkxTGpNZ1RDQXpNamdnTXpJM0xqVWdUQ0F6TkRBZ016STVMalFnVENBek5USWdNek13TGprZ1RDQXpOalFnTXpNeExqa2dUQ0F6TnpZZ016TXlMalVnVENBek9EZ2dNek15TGpZZ1RDQTBNREFnTXpNeUxqSWdUQ0EwTVRJZ016TXhMalFnVENBME1qUWdNek13TGpFZ1RDQTBNellnTXpJNExqUWdUQ0EwTkRnZ016STJMalFnVENBME5qQWdNekkwTGpBZ1RDQTBOeklnTXpJeExqUWdUQ0EwTnpJZ016VTBJRXdnTkRBZ016VTBJRm9pSUdacGJHdzlJbWh6YkNnMk5TQTNPQ1VnTmpJbEtTSWdiM0JoWTJsMGVUMGlNQzR6T0NJdlBqeHdZWFJvSUdROUlrMGdOREFnTkRBeUlFd2dOREFnTXpjNExqUWdUQ0ExTWlBek9EQXVPQ0JNSURZMElETTRNeTR6SUV3Z056WWdNemcxTGpjZ1RDQTRPQ0F6T0RndU1DQk1JREV3TUNBek9UQXVNaUJNSURFeE1pQXpPVEl1TWlCTUlERXlOQ0F6T1RNdU9TQk1JREV6TmlBek9UVXVNeUJNSURFME9DQXpPVFl1TkNCTUlERTJNQ0F6T1RjdU1TQk1JREUzTWlBek9UY3VOQ0JNSURFNE5DQXpPVGN1TXlCTUlERTVOaUF6T1RZdU9DQk1JREl3T0NBek9UWXVNQ0JNSURJeU1DQXpPVFF1T0NCTUlESXpNaUF6T1RNdU15Qk1JREkwTkNBek9URXVOU0JNSURJMU5pQXpPRGt1TkNCTUlESTJPQ0F6T0RjdU1pQk1JREk0TUNBek9EUXVPQ0JNSURJNU1pQXpPREl1TkNCTUlETXdOQ0F6TnprdU9TQk1JRE14TmlBek56Y3VOU0JNSURNeU9DQXpOelV1TWlCTUlETTBNQ0F6TnpNdU1TQk1JRE0xTWlBek56RXVNaUJNSURNMk5DQXpOamt1TmlCTUlETTNOaUF6TmpndU15Qk1JRE00T0NBek5qY3VOQ0JNSURRd01DQXpOall1T0NCTUlEUXhNaUF6TmpZdU5pQk1JRFF5TkNBek5qWXVPQ0JNSURRek5pQXpOamN1TkNCTUlEUTBPQ0F6TmpndU5DQk1JRFEyTUNBek5qa3VOeUJNSURRM01pQXpOekV1TXlCTUlEUTNNaUEwTWpJZ1RDQTBNQ0EwTWpJZ1dpSWdabWxzYkQwaWFITnNLRE15TlNBNE1pVWdOVGdsS1NJZ2IzQmhZMmwwZVQwaU1DNDFOQ0l2UGp3dmMzWm5QZz09Iiwic2VsbGVyX2ZlZV9iYXNpc19wb2ludHMiOjc1MCwiZmVlX3JlY2lwaWVudCI6IjB4YTJDM2JkYzkxM2U5NTRjNDQ5NjhFNDlDNjEyNDRmNzIyNDkzQzEwMSIsIm5mdHppbGxhIjp7InNsdWciOiJwIiwibGF1bmNoIjp7Im1pbnRTdGFydCI6MTc5NDU5MTU0MDAwMCwibWludEVuZCI6MjAxNTUxNDAwMDAwMH19fQ==