From 4656a1003226709e9b3c804ec32b4913fca4c7f4 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 16 Jul 2026 13:54:23 +0200 Subject: term: 40-column text terminal on the CGB LCD (4x8 dynamic tiles) Add a background-tile text terminal that packs TWO 4px-wide characters into each 8x8 tile, giving a 40x18 grid instead of the 20x18 you'd get from an 8x8 font. The tilemap is static (one dedicated VRAM tile per screen position); we rebuild a tile's 16 bytes from two glyphs whenever a character changes. 360 tiles exceed the 256 a single tilemap can address, so positions 256-359 live in VRAM bank 1 via the CGB tilemap attribute bank-bit -- hence CGB-only. - Switch the ROM to CGB (rgbfix -C). Safe for the FS: every process has PROC_WRAMB=1, so SVBK stays on bank 1 and the WRAMX FS caches don't move. - CGB BG palette 0 = white bg / black text via BCPS/BCPD. - src/term.asm: term_init (palette + static tilemap + clear buffer), build_tile (combine two 4px glyphs -> one 8x8 tile, correct VRAM bank), term_redraw (rebuild all tiles), term_puts, term_show, term_test. - src/font.asm: 96-glyph 3x5-in-4x8 font, generated by tools/genfont.py. - 40x18 text buffer at $D600 (WRAMX, above the FS caches). - tools/ppmview.py: render a --shot PPM as ASCII so the LCD is inspectable from the shell during development. Verified via emulator screenshot: "GBOS TERMINAL", the alphabet, and a bank-1 row all render legibly at 40 columns. Serial shell + filesystem still work unchanged. Note: a full term_redraw builds 360 tiles and takes ~8 frames; fine for incremental single-char updates, but scrolling needs a smarter path (next milestone). Input (no keyboard) is also still TODO. --- tools/ppmview.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tools/ppmview.py (limited to 'tools/ppmview.py') diff --git a/tools/ppmview.py b/tools/ppmview.py new file mode 100644 index 0000000..1e54bd4 --- /dev/null +++ b/tools/ppmview.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# Render a PPM screenshot as ASCII so we can "read" the GB LCD. +import sys +def load(path): + f=open(path,'rb'); assert f.readline().strip()==b'P6' + w,h=map(int,f.readline().split()); f.readline() + d=f.read(w*h*3); return w,h,d +w,h,d=load(sys.argv[1] if len(sys.argv)>1 else 'shot.ppm') +y0=int(sys.argv[2]) if len(sys.argv)>2 else 0 +y1=int(sys.argv[3]) if len(sys.argv)>3 else h +for y in range(y0,min(y1,h)): + row=[] + for x in range(w): + i=(y*w+x)*3; b=(d[i]+d[i+1]+d[i+2])//3 + row.append('#' if b<128 else '.') + print(''.join(row)) -- cgit v1.3.1-sl0p