aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 13:54:23 +0200
committeruser <user@clank>2026-07-16 13:54:23 +0200
commit4656a1003226709e9b3c804ec32b4913fca4c7f4 (patch)
tree516185fb1827495f0fe747b6365eb963ad5cf031 /tools
parentfs: reject open()/read() on a directory (EISDIR) (diff)
downloadgbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.tar.gz
gbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.tar.xz
gbos-4656a1003226709e9b3c804ec32b4913fca4c7f4.zip
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.
Diffstat (limited to 'tools')
-rw-r--r--tools/genfont.py105
-rw-r--r--tools/ppmview.py16
2 files changed, 121 insertions, 0 deletions
diff --git a/tools/genfont.py b/tools/genfont.py
new file mode 100644
index 0000000..7fe7689
--- /dev/null
+++ b/tools/genfont.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Generate a 4x8 font (3x5 glyphs, left-aligned in a 4px cell) as RGBDS asm.
+# Output: 96 glyphs (ASCII 32..127), 8 bytes each; each byte = one 4-px row,
+# bit3=leftmost pixel of the cell, bits 3..1 = the 3px glyph, bit0 = gap.
+G = {}
+def d(ch, rows): # rows: 5 strings of width 3 ('#'/' ')
+ G[ch] = rows
+# space
+d(' ', [" "," "," "," "," "])
+# letters (uppercase; lowercase mapped to these)
+d('A',["## ","# #","###","# #","# #"])
+d('B',["## ","# #","## ","# #","## "])
+d('C',[" ##","# ","# ","# "," ##"])
+d('D',["## ","# #","# #","# #","## "])
+d('E',["###","# ","## ","# ","###"])
+d('F',["###","# ","## ","# ","# "])
+d('G',[" ##","# ","# #","# #"," ##"])
+d('H',["# #","# #","###","# #","# #"])
+d('I',["###"," # "," # "," # ","###"])
+d('J',[" #"," #"," #","# #"," # "])
+d('K',["# #","# #","## ","# #","# #"])
+d('L',["# ","# ","# ","# ","###"])
+d('M',["# #","###","###","# #","# #"])
+d('N',["# #","###","###","###","# #"])
+d('O',[" # ","# #","# #","# #"," # "])
+d('P',["## ","# #","## ","# ","# "])
+d('Q',[" # ","# #","# #"," # "," #"])
+d('R',["## ","# #","## ","# #","# #"])
+d('S',[" ##","# "," # "," #","## "])
+d('T',["###"," # "," # "," # "," # "])
+d('U',["# #","# #","# #","# #"," ##"])
+d('V',["# #","# #","# #"," # "," # "])
+d('W',["# #","# #","###","###","# #"])
+d('X',["# #","# #"," # ","# #","# #"])
+d('Y',["# #","# #"," # "," # "," # "])
+d('Z',["###"," #"," # ","# ","###"])
+# digits
+d('0',[" # ","# #","# #","# #"," # "])
+d('1',[" # ","## "," # "," # ","###"])
+d('2',["## "," #"," # ","# ","###"])
+d('3',["## "," #"," # "," #","## "])
+d('4',["# #","# #","###"," #"," #"])
+d('5',["###","# ","## "," #","## "])
+d('6',[" ##","# ","## ","# #"," # "])
+d('7',["###"," #"," # "," # "," # "])
+d('8',[" # ","# #"," # ","# #"," # "])
+d('9',[" # ","# #"," ##"," #","## "])
+# punctuation
+d('.',[" "," "," "," "," # "])
+d(',',[" "," "," "," # ","# "])
+d(':',[" "," # "," "," # "," "])
+d(';',[" "," # "," "," # ","# "])
+d('!',[" # "," # "," # "," "," # "])
+d('?',["## "," #"," # "," "," # "])
+d('-',[" "," ","###"," "," "])
+d('+',[" "," # ","###"," # "," "])
+d('=',[" ","###"," ","###"," "])
+d('*',["# #"," # ","###"," # ","# #"])
+d('/',[" #"," #"," # ","# ","# "])
+d('\\',["# ","# "," # "," #"," #"])
+d('(',[" #"," # "," # "," # "," #"])
+d(')',["# "," # "," # "," # ","# "])
+d('[',["## ","# ","# ","# ","## "])
+d(']',[" ##"," #"," #"," #"," ##"])
+d('<',[" #"," # ","# "," # "," #"])
+d('>',["# "," # "," #"," # ","# "])
+d('#',["# #","###","# #","###","# #"])
+d('$',[" # "," ##","## "," ##","## "]) # rough
+d('%',["# #"," #"," # ","# ","# #"])
+d('&',[" # ","# #"," # ","# #"," ##"])
+d("'",[" # "," # "," "," "," "])
+d('"',["# #","# #"," "," "," "])
+d('@',[" # ","# #","###","# "," ##"])
+d('_',[" "," "," "," ","###"])
+d('~',[" "," ##","## "," "," "])
+d('^',[" # ","# #"," "," "," "])
+d('|',[" # "," # "," # "," # "," # "])
+d('{',[" #"," # ","## "," # "," #"])
+d('}',["# "," # "," ##"," # ","# "])
+d('`',["# "," # "," "," "," "])
+
+def glyph_bytes(rows):
+ out=[]
+ # 5 glyph rows in the top; cell is 4px wide, glyph 3px left-aligned (bit3..1)
+ for r in range(8):
+ if r < 5:
+ s = rows[r]
+ v = 0
+ for i,px in enumerate(s): # i=0 leftmost -> bit3
+ if px=='#': v |= (1 << (3-i))
+ out.append(v)
+ else:
+ out.append(0)
+ return out
+
+lines=["; auto-generated by tools/genfont.py - 4x8 font (3x5 glyphs), 96 glyphs",
+ "SECTION \"font\", ROM0", "FontData::"]
+for code in range(32,128):
+ ch = chr(code)
+ key = ch.upper() if ch.isalpha() else ch
+ rows = G.get(key, G.get(ch, ["###","# #","# #","# #","###"])) # box for unknown
+ b = glyph_bytes(rows)
+ lines.append(" db " + ",".join("$%02X"%x for x in b) + " ; '%s'"%(ch if ch!='\\' and code!=127 else '?'))
+open("/home/user/dev/gbos/src/font.asm","w").write("\n".join(lines)+"\n")
+print("wrote src/font.inc:", (128-32), "glyphs")
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))