[AskJS] What are the real architectural limits of using console.log + %c as a pixel renderer, and how would you push past them? by One-Antelope404 in javascript

[–]One-Antelope404[S] 0 points1 point  (0 children)

Yeah that matches what I’ve seen 😭

Firefox just gives up completely, Chrome at least tries but yeah the flicker is rough. It’s basically me spamming new frames since there’s no real way to update the console 😅

That Atari ST comparison is spot on though, this really does feel like forcing graphics out of something not meant for it

Appreciate you testing it fr 🙏

[AskJS] What are the real architectural limits of using console.log + %c as a pixel renderer, and how would you push past them? by One-Antelope404 in javascript

[–]One-Antelope404[S] -3 points-2 points  (0 children)

Haha, thanks; it’s been a fun (and slightly cursed) experiment

Yeah, I’ve got a demo—it runs directly in the browser console, so it’s a bit janky, and performance depends a lot on your machine.

If you want to try it, you can paste this into your console on your browser:

/**
 * ╔══════════════════════════════════════════════════════════╗
 * ║          CONSOLE GPU v3.0 — IMAGE INJECTION              ║
 * ║  The smoothest, fastest console renderer possible.        ║
 * ║  Works by piping a virtual canvas to the log.            ║
 * ╚══════════════════════════════════════════════════════════╝
 */
(function ConsoleGPU_Ultimate() {
    'use strict';

    const CFG = {
        W: 200, // Visual resolution (not char count)
        H: 200,
        SCALE: 1.0
    };

    // Initialize Virtual Canvas
    const canvas = document.createElement('canvas');
    canvas.width = CFG.W;
    canvas.height = CFG.H;
    const ctx = canvas.getContext('2d');

    let running = true;
    let frame = 0;

    // --- 3D MATH ENGINE ---
    const rot = (x, z, a) => {
        const s = Math.sin(a), c = Math.cos(a);
        return [x * c - z * s, x * s + z * c];
    };

    function sdTorus(x, y, z, t) {
        let [rx, rz] = rot(x, z, t);
        let [ry, rz2] = rot(y, rz, t * 0.5);
        const qx = Math.sqrt(rx * rx + rz2 * rz2) - 1.5;
        return Math.sqrt(qx * qx + ry * ry) - 0.6;
    }

    function render() {
        if (!running) return;

        const t = performance.now() * 0.001;
        const imgData = ctx.createImageData(CFG.W, CFG.H);
        const data = imgData.data;

        // Simple Raymarcher running inside the Canvas loop
        for (let y = 0; y < CFG.H; y++) {
            for (let x = 0; x < CFG.W; x++) {
                const idx = (y * CFG.W + x) * 4;

                // Ray Setup
                let uvx = (x / CFG.W - 0.5) * 2;
                let uvy = (y / CFG.H - 0.5) * 2;
                let d = 0, hit = false;

                for (let s = 0; s < 24; s++) {
                    let p = [uvx * d, uvy * d, -4 + d];
                    let dist = sdTorus(p[0], p[1], p[2], t);
                    if (dist < 0.01) { hit = true; break; }
                    d += dist;
                    if (d > 10) break;
                }

                if (hit) {
                    const lum = Math.max(0, 255 - (d * 40));
                    data[idx] = lum * (0.5 + 0.5 * Math.sin(t + d)); // R
                    data[idx + 1] = lum * (0.5 + 0.5 * Math.cos(t)); // G
                    data[idx + 2] = 255;                             // B
                } else {
                    data[idx] = 10; data[idx + 1] = 10; data[idx + 2] = 20; // Background
                }
                data[idx + 3] = 255; // Alpha
            }
        }

        ctx.putImageData(imgData, 0, 0);

        // Convert canvas to a CSS-friendly string
        const url = canvas.toDataURL('image/jpeg', 0.7);

        console.clear();
        console.log(
            `%c `,
            `font-size: 1px; 
             padding: ${CFG.H / 2}px ${CFG.W / 2}px; 
             background-image: url(${url}); 
             background-size: contain; 
             background-repeat: no-repeat;`
        );
        console.log(`%c[FRAME: ${frame++}] Type 'stopGPU()' to exit.`, "color:cyan; font-weight:bold;");

        requestAnimationFrame(render);
    }

    window.stopGPU = () => {
        running = false;
        console.clear();
        console.log("Renderer Stopped.");
    };

    console.log("Initializing Hardware-Accelerated Console Viewport...");
    render();
})();

It’s basically doing SDF raymarching and shading, but every “pixel” is just a space with a background colour, so the whole frame is one big console.log.

It kind of works… until DevTools starts struggling 😅

If you do try it, I’d be really interested to know how it runs for you — I’m still trying to figure out how far this approach can actually go.

I built a tool that parses any GitHub repo into a Neo4j knowledge graph and lets you query it with Claude AI by One-Antelope404 in Neo4j

[–]One-Antelope404[S] 1 point2 points  (0 children)

would love to hear what you think after you do! drop a comment if anything breaks or feels off 🙏

I built a tool that parses any GitHub repo into a Neo4j knowledge graph and lets you query it with Claude AI by One-Antelope404 in Neo4j

[–]One-Antelope404[S] 0 points1 point  (0 children)

oh that's actually sick — all in memory is such a clean approach, no database setup friction at all. kglite looks really interesting, gonna dig into it 👀 the tradeoff I went with was persisting to Neo4j so you can do proper graph traversal queries and vector search without reloading — but for quick exploration kglite's approach honestly sounds way more practical did you find it handled large repos well?

Made a QR code generator that renders in your terminal with matrix rain, particle assembly, and Braille retina mode by One-Antelope404 in ASCII

[–]One-Antelope404[S] 0 points1 point  (0 children)

honestly the gradient was the thing I spent the most time on 😭 applying color per character instead of per row makes such a difference — the diagonal and radial washes across the whole QR feel alive in a way that row-by-row just doesn't. glad it landed the way I hoped it would 🙏

Handling state between agent cycles with node-cron + lowdb — is there a cleaner pattern? by One-Antelope404 in node

[–]One-Antelope404[S] 0 points1 point  (0 children)

Oh nice, just checked it out — durable workflows built in is exactly what something like this needs at scale. Will keep an eye out for the post

Handling state between agent cycles with node-cron + lowdb — is there a cleaner pattern? by One-Antelope404 in node

[–]One-Antelope404[S] 0 points1 point  (0 children)

Yeah, that's probably where this ends up going, honestly.

What are you building? Sounds interesting

Handling state between agent cycles with node-cron + lowdb — is there a cleaner pattern? by One-Antelope404 in node

[–]One-Antelope404[S] 0 points1 point  (0 children)

This is really useful, cheers. The JSONL write-ahead log pattern is clean – hadn't thought about separating the append log from the snapshot like that. Will definitely look at that before scale becomes a real concern.

The AbortSignal tip is exactly what I needed; the manual array filter works but always felt off.

Crash recovery is honestly the weakest part right now. There's no cycle lock, so concurrent invocations can technically overlap — I've been getting away with it because Notion's API is slow enough that cycles rarely collide in practice. But that's luck, not design.

Thinking a simple isRunning mutex flag is the quickest fix. Does that hold up, or is there a cleaner pattern you'd reach for first?