You are browsing as a guest. Sign up (or log in) to start making projects!

16h 49m 41s logged

TUIEmu devlog #1 - two consoles, one terminal, zero GPU

the cores i did NOT write

writing a NES or Game Boy core from scratch is months of work. i am not
doing months of work in a weekend. so TUIEmu runs two vendored cores and
the whole job was the glue around them:

  • PeakRacing/nes for the NES - hooks for memory-mapped ROM loading,
    ARGB8888 framebuffer output, sound off, every mapper family enabled
  • deltabeard/peanut-gb for the Game Boy - clean, tiny, fast to bind

each core needs a port layer: boot the console once, hand it a
framebuffer, feed it a joypad every frame, run one frame, read pixels
back. that’s the whole engine loop in main.c, and honestly the fastest
way to “have two emulators” is to steal the two hardest parts and write
the fun glue yourself.

the render engine - this is where the real work happened

the cores give us a raw framebuffer (256x240 NES, 160x144 GB). the job:
get that onto a terminal without a GPU. the version history is basically
a pyramid of terminal graphics tricks:

  1. half-blocks - every cell becomes a with the top half in fg
    color and the bottom in bg. 1x2 pixels per cell. the baseline.
  2. quadrants - a 2x2 truecolor pixel per cell using block-element
    glyphs (▖▗▘▝▀▄▌▐█). every 1/2-color pattern maps to an exact glyph,
    and messy 3-4 color cells fall back to a diagonal or full block. no
    black holes: empty sub-squares inherit their neighbor’s color.
  3. kitty graphics protocol - real pixels, base64-uploaded per frame
    and replaced in place. the terminal does the scaling.
  4. DEC sixel - the OG 80s-era raster format. palette capped at 256
    sorted colors, 6-row bands, run-length slices, one DCS per frame.

the launch screen fires a capability probe: kitty q=1/q=2 plus the DA1
query (“what raster do you speak, terminal?”) and each mode quietly
auto-skips if the terminal can’t do it. mode keys cycle
half-block -> quadrant -> kitty -> sixel, everything fit-to-screen and
zoomable, nothing ever cropped or distorted.

the infinite hang

this project has one legendary bug. when all three probe replies landed
in a single read burst, the DA1 parser spun forever - the param scan
ended with q += strcspn(q, ";0123456789"), the digits are in the stop
set, so it advanced zero bytes, forever. the emulator just froze
silently. fixed with an explicit digit-walk loop. terminal reply inputs
now also can’t masquerade as keys (a stray kitty “OK” used to hit zoom).

input, the boring-but-vital part

keys -> gamepad: Q=A, E=B, arrows/WASD = directions, Enter = Start,
Tab = Select. directions are latched continuously while held - the core
reads the pad every frame and gets “pressed” every frame. no stutter.

heres contra running

0
62

Comments 0

No comments yet. Be the first!