Skip to content

Semigraphics support ​

Every machine Basically targets has block-graphics ("semigraphics") characters — Sinclair quadrants, PETSCII shades, TRS-80 sextants, CPC mosaics, BBC MODE 7 teletext sixels. This page records, per machine, how much of that set the IDE can actually represent and type.

Most of it is generated: the matrix below is computed from each dialect's own CharsetMapping and KeyboardLayout by scripts/gen-semigraphics-audit.mts (npm run gen:semigraphics), and semigraphics-support.test.ts regenerates and diffs it, so it cannot drift from the code. Do not hand-edit between the markers; everything outside them is hand-written and preserved.

How to read it ​

Each dialect's charset is total and injective: every byte 0x00–0xFF has exactly one text form, and that form re-encodes to that byte. That invariant is why so much falls back to a numeric escape — where two of a machine's characters would need the same Unicode character, only one of them can have it, and the other is spelled {0xNN} (or \{NN}, or {$xx}) instead. It is also why "give every graphic a glyph" is not simply a matter of filling in a table.

The one fact the code cannot derive is which bytes the machine considers graphics — a charset only says what we currently map. That list is declared in SEMIGRAPHIC_CODES in src/dialects/semigraphicsAudit.ts, cited per dialect, and a machine whose range nobody has established from a primary source is reported as such rather than guessed at.

Support matrix ​

MachineIn scopeGraphics bytesUnicode glyph…astralNamed escapeRaw escape onlyTypeable
ZX81✅21 (0x01-0x0A, 0x80-0x8A)2150021/21
ZX80✅21 (0x02-0x0B, 0x80, 0x82-0x8B)2150021/21
Spectrum✅37 (0x80-0xA4)36210136/37
Spectrum 128✅35 (0x80-0xA2)34190134/35
BBC Micro✅64 (0xA0-0xBF, 0xE0-0xFF)63600163/64
BBC Master✅64 (0xA0-0xBF, 0xE0-0xFF)63600163/64
C64✅64 (0xA0-0xDF)57121558/64
PET✅64 (0xA0-0xDF)57121529/64
VIC-20✅64 (0xA0-0xDF)57121558/64
Atom✅64 (0xA0-0xDF)63600163/64
TRS-80✅64 (0x80-0xBF)63600163/64
CPC 464✅64 (0x80-0x9F, 0xC0-0xDF)63200163/64
CPC 664✅64 (0x80-0x9F, 0xC0-0xDF)63200163/64
CPC 6128✅64 (0x80-0x9F, 0xC0-0xDF)63200163/64
Altair 8800—none—————
PMD 85-2—none—————
Apple I—none—————
Apple II—none—————
Apple II Plus—none—————
Atari 800—27 (0x00-0x1A)2720027/27
Atari 400—27 (0x00-0x1A)2720027/27
HB-10P—29 (0xC0-0xD7, 0xDB-0xDF)29130029/29
GE-235—none—————
GE-635—none—————
SAM Coupé—41 (0x80-0xA8)40250140/41
Sorcerer—64 (0x80-0xBF)63180163/64

"Typeable" counts graphics bytes reachable by typing on the on-screen keyboard, including its graphics palette. "…astral" counts the characters outside the Basic Multilingual Plane, which almost no installed monospace font covers - those are the ones that need the bundled face.

Per-machine detail ​

ZX81 ​

Charset family zx81. Graphics bytes 0x01-0x0A, 0x80-0x8A.

Spelled as: 5 glyph-astral, 16 glyph-bmp.

No gaps: every graphics byte has its own character and can be typed.

ZX80 ​

Charset family zx80. Graphics bytes 0x02-0x0B, 0x80, 0x82-0x8B.

Spelled as: 5 glyph-astral, 16 glyph-bmp.

No gaps: every graphics byte has its own character and can be typed.

Spectrum ​

Charset family zxspectrum. Graphics bytes 0x80-0xA4.

Spelled as: 1 escape-raw, 21 glyph-astral, 15 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

Spectrum 128 ​

Charset family zxspectrum. Graphics bytes 0x80-0xA2.

Spelled as: 1 escape-raw, 19 glyph-astral, 15 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

BBC Micro ​

Charset family bbc. Graphics bytes 0xA0-0xBF, 0xE0-0xFF.

Spelled as: 1 escape-raw, 60 glyph-astral, 3 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0xA0.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0xA0.

BBC Master ​

Charset family bbc. Graphics bytes 0xA0-0xBF, 0xE0-0xFF.

Spelled as: 1 escape-raw, 60 glyph-astral, 3 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0xA0.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0xA0.

C64 ​

Charset family commodore. Graphics bytes 0xA0-0xDF.

Spelled as: 1 ascii, 1 escape-named, 5 escape-raw, 12 glyph-astral, 45 glyph-bmp.

Gap: 5 graphics bytes have no character of their own and render as raw escapes: 0xAA, 0xB4, 0xC3, 0xDD-0xDE.

Gap: 6 graphics bytes cannot be typed on the on-screen keyboard: 0xA0, 0xAA, 0xB4, 0xC3, 0xDD-0xDE.

PET ​

Charset family commodore. Graphics bytes 0xA0-0xDF.

Spelled as: 1 ascii, 1 escape-named, 5 escape-raw, 12 glyph-astral, 45 glyph-bmp.

Gap: 5 graphics bytes have no character of their own and render as raw escapes: 0xAA, 0xB4, 0xC3, 0xDD-0xDE.

Gap: 35 graphics bytes cannot be typed on the on-screen keyboard: 0xA0-0xA8, 0xAA-0xB9, 0xBB-0xBF, 0xC3, 0xDC-0xDF.

VIC-20 ​

Charset family commodore. Graphics bytes 0xA0-0xDF.

Spelled as: 1 ascii, 1 escape-named, 5 escape-raw, 12 glyph-astral, 45 glyph-bmp.

Gap: 5 graphics bytes have no character of their own and render as raw escapes: 0xAA, 0xB4, 0xC3, 0xDD-0xDE.

Gap: 6 graphics bytes cannot be typed on the on-screen keyboard: 0xA0, 0xAA, 0xB4, 0xC3, 0xDD-0xDE.

Atom ​

Charset family atom. Graphics bytes 0xA0-0xDF.

Spelled as: 1 escape-raw, 60 glyph-astral, 3 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0xA0.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0xA0.

TRS-80 ​

Charset family trs80. Graphics bytes 0x80-0xBF.

Spelled as: 1 escape-raw, 60 glyph-astral, 3 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

CPC 464 ​

Charset family cpc. Graphics bytes 0x80-0x9F, 0xC0-0xDF.

Spelled as: 1 escape-raw, 20 glyph-astral, 43 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

CPC 664 ​

Charset family cpc. Graphics bytes 0x80-0x9F, 0xC0-0xDF.

Spelled as: 1 escape-raw, 20 glyph-astral, 43 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

CPC 6128 ​

Charset family cpc. Graphics bytes 0x80-0x9F, 0xC0-0xDF.

Spelled as: 1 escape-raw, 20 glyph-astral, 43 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

Altair 8800 ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

PMD 85-2 ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

Apple I ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

Apple II ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

Apple II Plus ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

Atari 800 ​

Charset family atari. Graphics bytes 0x00-0x1A.

Spelled as: 2 glyph-astral, 25 glyph-bmp.

No gaps: every graphics byte has its own character and can be typed.

Atari 400 ​

Charset family atari. Graphics bytes 0x00-0x1A.

Spelled as: 2 glyph-astral, 25 glyph-bmp.

No gaps: every graphics byte has its own character and can be typed.

HB-10P ​

Charset family msx. Graphics bytes 0xC0-0xD7, 0xDB-0xDF.

Spelled as: 13 glyph-astral, 16 glyph-bmp.

No gaps: every graphics byte has its own character and can be typed.

GE-235 ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

GE-635 ​

This machine has no block graphics at all - not a range nobody has read off it yet, but a character set with no mosaic in it. Every byte it can display is an ordinary character; see the citation beside its entry in src/dialects/semigraphicsAudit.ts for how that was established.

SAM Coupé ​

Charset family samcoupe. Graphics bytes 0x80-0xA8.

Spelled as: 1 escape-raw, 25 glyph-astral, 15 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x80.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x80.

Sorcerer ​

Charset family sorcerer. Graphics bytes 0x80-0xBF.

Spelled as: 1 escape-raw, 18 glyph-astral, 45 glyph-bmp.

Gap: 1 graphics byte has no character of its own and renders as a raw escape: 0x8D.

Gap: 1 graphics byte cannot be typed on the on-screen keyboard: 0x8D.

Characters the machines need ​

333 distinct non-ASCII codepoints, 139 of them astral. This is the exact set the bundled character-graphics font is subset to.

CodepointCharacterPlaneUsed by
U+00A1¡BMPcpc464, cpc6128, cpc664, hb10p
U+00A2¢BMPhb10p
U+00A3£BMPbbcmaster, bbcmicro, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+00A5¥BMPhb10p
U+00A7§BMPcpc464, cpc6128, cpc664, hb10p
U+00A8¨BMPcpc464, cpc6128, cpc664
U+00A9©BMPcpc464, cpc6128, cpc664, samcoupe, zxspectrum, zxspectrum128
U+00AAªBMPhb10p
U+00AB«BMPhb10p
U+00AC¬BMPcpc464, cpc6128, cpc664, hb10p
U+00B0°BMPhb10p
U+00B1±BMPcpc464, cpc6128, cpc664, hb10p
U+00B2²BMPhb10p
U+00B4´BMPcpc464, cpc6128, cpc664
U+00B5µBMPhb10p
U+00B6BMPcpc464, cpc6128, cpc664, hb10p
U+00B7·BMPcpc464, cpc6128, cpc664, hb10p
U+00BAºBMPhb10p
U+00BB»BMPhb10p
U+00BC¼BMPcpc464, cpc6128, cpc664, hb10p
U+00BD½BMPcpc464, cpc6128, cpc664, hb10p
U+00BE¾BMPcpc464, cpc6128, cpc664, hb10p
U+00BF¿BMPcpc464, cpc6128, cpc664, hb10p
U+00C3ÃBMPhb10p
U+00C4ÄBMPhb10p
U+00C5ÅBMPhb10p
U+00C6ÆBMPhb10p
U+00C7ÇBMPhb10p
U+00C9ÉBMPhb10p
U+00D1ÑBMPhb10p
U+00D5ÕBMPhb10p
U+00D6ÖBMPhb10p
U+00DCÜBMPhb10p
U+00DFßBMPhb10p
U+00E0àBMPhb10p
U+00E1áBMPhb10p
U+00E2âBMPhb10p
U+00E3ãBMPhb10p
U+00E4äBMPhb10p
U+00E5åBMPhb10p
U+00E6æBMPhb10p
U+00E7çBMPhb10p
U+00E8èBMPhb10p
U+00E9éBMPhb10p
U+00EAêBMPhb10p
U+00EBëBMPhb10p
U+00ECìBMPhb10p
U+00EDíBMPhb10p
U+00EEîBMPhb10p
U+00EFïBMPhb10p
U+00F1ñBMPhb10p
U+00F2òBMPhb10p
U+00F3óBMPhb10p
U+00F4ôBMPhb10p
U+00F5õBMPhb10p
U+00F6öBMPhb10p
U+00F7÷BMPcpc464, cpc6128, cpc664, hb10p
U+00F9ùBMPhb10p
U+00FAúBMPhb10p
U+00FBûBMPhb10p
U+00FCüBMPhb10p
U+00FFÿBMPhb10p
U+0128ĨBMPhb10p
U+0129ĩBMPhb10p
U+0132IJBMPhb10p
U+0133ijBMPhb10p
U+0170ŰBMPhb10p
U+0171űBMPhb10p
U+0192ƒBMPhb10p
U+0393ΓBMPhb10p
U+0394ΔBMPhb10p
U+0398ΘBMPhb10p
U+03A3ΣBMPcpc464, cpc6128, cpc664, hb10p
U+03A6ΦBMPhb10p
U+03A9ΩBMPcpc464, cpc6128, cpc664, hb10p
U+03B1αBMPcpc464, cpc6128, cpc664, hb10p
U+03B2βBMPcpc464, cpc6128, cpc664
U+03B3γBMPcpc464, cpc6128, cpc664
U+03B4δBMPcpc464, cpc6128, cpc664, hb10p
U+03B5εBMPcpc464, cpc6128, cpc664
U+03B8θBMPcpc464, cpc6128, cpc664
U+03BBλBMPcpc464, cpc6128, cpc664
U+03BCμBMPcpc464, cpc6128, cpc664
U+03C0πBMPcommodore64, cpc464, cpc6128, cpc664, hb10p, pet, vic20
U+03C3σBMPcpc464, cpc6128, cpc664, hb10p
U+03C4τBMPhb10p
U+03C6φBMPcpc464, cpc6128, cpc664
U+03C7χBMPcpc464, cpc6128, cpc664
U+03C8ψBMPcpc464, cpc6128, cpc664
U+03C9ωBMPcpc464, cpc6128, cpc664, hb10p
U+2019’BMPcpc464, cpc6128, cpc664
U+2021‡BMPhb10p
U+2022•BMPatari400, atari800
U+2030‰BMPhb10p
U+207FⁿBMPhb10p
U+20A7₧BMPhb10p
U+2190←BMPcommodore64, ge635, pet, vic20
U+2191↑BMPcommodore64, cpc464, cpc6128, cpc664, ge235, ge635, pet, samcoupe, vic20, zxspectrum, zxspectrum128
U+2208∈BMPhb10p
U+2219∙BMPhb10p
U+221A√BMPhb10p
U+221E∞BMPhb10p
U+2229∩BMPhb10p
U+223D∽BMPhb10p
U+2248≈BMPhb10p
U+2261≡BMPhb10p
U+2264≤BMPhb10p
U+2265≥BMPhb10p
U+2300⌀BMPhb10p
U+2310⌐BMPhb10p
U+231C⌜BMPcommodore64, pet, vic20
U+231D⌝BMPcommodore64, pet, vic20
U+231E⌞BMPcommodore64, pet, vic20
U+231F⌟BMPcommodore64, pet, vic20
U+2320⌠BMPhb10p
U+2321⌡BMPhb10p
U+2500─BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2502│BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+250C┌BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2510┐BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2514└BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2518┘BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+251C├BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2524┤BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+252C┬BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2534┴BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+253C┼BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+256D╭BMPcommodore64, pet, sorcerer, vic20
U+256E╮BMPcommodore64, pet, sorcerer, vic20
U+256F╯BMPcommodore64, pet, sorcerer, vic20
U+2570╰BMPcommodore64, pet, sorcerer, vic20
U+2571╱BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2572╲BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2573╳BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2574╴BMPcpc464, cpc6128, cpc664
U+2575╵BMPcpc464, cpc6128, cpc664
U+2576╶BMPcpc464, cpc6128, cpc664
U+2577╷BMPcpc464, cpc6128, cpc664
U+2580▀BMPcpc464, cpc6128, cpc664, hb10p, samcoupe, sorcerer, zx80, zx81, zxspectrum, zxspectrum128
U+2581▁BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2582▂BMPatari400, atari800, commodore64, hb10p, pet, sorcerer, vic20
U+2583▃BMPcommodore64, pet, vic20
U+2584▄BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+2586▆BMPhb10p
U+2588█BMPatom, bbcmaster, bbcmicro, cpc464, cpc6128, cpc664, hb10p, pmd85, samcoupe, trs80, zx80, zx81, zxspectrum, zxspectrum128
U+258A▊BMPhb10p
U+258C▌BMPatari400, atari800, atom, bbcmaster, bbcmicro, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, trs80, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+258D▍BMPcommodore64, pet, vic20
U+258E▎BMPatari400, atari800, commodore64, hb10p, pet, sorcerer, vic20
U+258F▏BMPcpc464, cpc6128, cpc664, sorcerer
U+2590▐BMPatom, bbcmaster, bbcmicro, cpc464, cpc6128, cpc664, hb10p, samcoupe, sorcerer, trs80, zx80, zx81, zxspectrum, zxspectrum128
U+2592▒BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20, zx80, zx81
U+2594▔BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2595▕BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2596▖BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+2597▗BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+2598▘BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+2599▙BMPcpc464, cpc6128, cpc664, samcoupe, zx80, zx81, zxspectrum, zxspectrum128
U+259A▚BMPcommodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+259B▛BMPcpc464, cpc6128, cpc664, samcoupe, zx80, zx81, zxspectrum, zxspectrum128
U+259C▜BMPcpc464, cpc6128, cpc664, samcoupe, zx80, zx81, zxspectrum, zxspectrum128
U+259D▝BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, hb10p, pet, samcoupe, sorcerer, vic20, zx80, zx81, zxspectrum, zxspectrum128
U+259E▞BMPcpc464, cpc6128, cpc664, hb10p, samcoupe, sorcerer, zx80, zx81, zxspectrum, zxspectrum128
U+259F▟BMPcpc464, cpc6128, cpc664, samcoupe, zx80, zx81, zxspectrum, zxspectrum128
U+25A0■BMPcpc464, cpc6128, cpc664, hb10p
U+25A1□BMPcpc464, cpc6128, cpc664
U+25AC▬BMPhb10p
U+25B2▲BMPcpc464, cpc6128, cpc664
U+25B6▶BMPatari400, atari800, cpc464, cpc6128, cpc664
U+25BC▼BMPcpc464, cpc6128, cpc664
U+25C0◀BMPatari400, atari800, cpc464, cpc6128, cpc664
U+25C6◆BMPsorcerer
U+25CA◊BMPhb10p
U+25CB○BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+25CF●BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+25E2◢BMPatari400, atari800, cpc464, cpc6128, cpc664, sorcerer
U+25E3◣BMPatari400, atari800, cpc464, cpc6128, cpc664, sorcerer
U+25E4◤BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+25E5◥BMPcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2639☹BMPcpc464, cpc6128, cpc664
U+263A☺BMPcpc464, cpc6128, cpc664
U+263C☼BMPcpc464, cpc6128, cpc664
U+2640♀BMPcpc464, cpc6128, cpc664
U+2642♂BMPcpc464, cpc6128, cpc664
U+2660♠BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2663♣BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2665♥BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+2666♦BMPatari400, atari800, commodore64, cpc464, cpc6128, cpc664, pet, vic20
U+2669♩BMPcpc464, cpc6128, cpc664
U+266A♪BMPcpc464, cpc6128, cpc664
U+2B60⭠BMPcpc464, cpc6128, cpc664
U+2B61⭡BMPcpc464, cpc6128, cpc664
U+2B62⭢BMPcpc464, cpc6128, cpc664
U+2B63⭣BMPcpc464, cpc6128, cpc664
U+1F130🄰astralsamcoupe, zxspectrum, zxspectrum128
U+1F131🄱astralsamcoupe, zxspectrum, zxspectrum128
U+1F132🄲astralsamcoupe, zxspectrum, zxspectrum128
U+1F133🄳astralsamcoupe, zxspectrum, zxspectrum128
U+1F134🄴astralsamcoupe, zxspectrum, zxspectrum128
U+1F135🄵astralsamcoupe, zxspectrum, zxspectrum128
U+1F136🄶astralsamcoupe, zxspectrum, zxspectrum128
U+1F137🄷astralsamcoupe, zxspectrum, zxspectrum128
U+1F138🄸astralsamcoupe, zxspectrum, zxspectrum128
U+1F139🄹astralsamcoupe, zxspectrum, zxspectrum128
U+1F13A🄺astralsamcoupe, zxspectrum, zxspectrum128
U+1F13B🄻astralsamcoupe, zxspectrum, zxspectrum128
U+1F13C🄼astralsamcoupe, zxspectrum, zxspectrum128
U+1F13D🄽astralsamcoupe, zxspectrum, zxspectrum128
U+1F13E🄾astralsamcoupe, zxspectrum, zxspectrum128
U+1F13F🄿astralsamcoupe, zxspectrum, zxspectrum128
U+1F140🅀astralsamcoupe, zxspectrum, zxspectrum128
U+1F141🅁astralsamcoupe, zxspectrum, zxspectrum128
U+1F142🅂astralsamcoupe, zxspectrum, zxspectrum128
U+1F143🅃astralsamcoupe, zxspectrum, zxspectrum128
U+1F144🅄astralsamcoupe, zxspectrum, zxspectrum128
U+1F145🅅astralsamcoupe
U+1F146🅆astralsamcoupe
U+1F147🅇astralsamcoupe
U+1F148🅈astralsamcoupe
U+1FB00🬀astralatom, bbcmaster, bbcmicro, trs80
U+1FB01🬁astralatom, bbcmaster, bbcmicro, trs80
U+1FB02🬂astralatom, bbcmaster, bbcmicro, trs80
U+1FB03🬃astralatom, bbcmaster, bbcmicro, trs80
U+1FB04🬄astralatom, bbcmaster, bbcmicro, trs80
U+1FB05🬅astralatom, bbcmaster, bbcmicro, trs80
U+1FB06🬆astralatom, bbcmaster, bbcmicro, trs80
U+1FB07🬇astralatom, bbcmaster, bbcmicro, trs80
U+1FB08🬈astralatom, bbcmaster, bbcmicro, trs80
U+1FB09🬉astralatom, bbcmaster, bbcmicro, trs80
U+1FB0A🬊astralatom, bbcmaster, bbcmicro, trs80
U+1FB0B🬋astralatom, bbcmaster, bbcmicro, trs80
U+1FB0C🬌astralatom, bbcmaster, bbcmicro, trs80
U+1FB0D🬍astralatom, bbcmaster, bbcmicro, trs80
U+1FB0E🬎astralatom, bbcmaster, bbcmicro, trs80
U+1FB0F🬏astralatom, bbcmaster, bbcmicro, trs80
U+1FB10🬐astralatom, bbcmaster, bbcmicro, trs80
U+1FB11🬑astralatom, bbcmaster, bbcmicro, trs80
U+1FB12🬒astralatom, bbcmaster, bbcmicro, trs80
U+1FB13🬓astralatom, bbcmaster, bbcmicro, trs80
U+1FB14🬔astralatom, bbcmaster, bbcmicro, trs80
U+1FB15🬕astralatom, bbcmaster, bbcmicro, trs80
U+1FB16🬖astralatom, bbcmaster, bbcmicro, trs80
U+1FB17🬗astralatom, bbcmaster, bbcmicro, trs80
U+1FB18🬘astralatom, bbcmaster, bbcmicro, trs80
U+1FB19🬙astralatom, bbcmaster, bbcmicro, trs80
U+1FB1A🬚astralatom, bbcmaster, bbcmicro, trs80
U+1FB1B🬛astralatom, bbcmaster, bbcmicro, trs80
U+1FB1C🬜astralatom, bbcmaster, bbcmicro, trs80
U+1FB1D🬝astralatom, bbcmaster, bbcmicro, trs80
U+1FB1E🬞astralatom, bbcmaster, bbcmicro, trs80
U+1FB1F🬟astralatom, bbcmaster, bbcmicro, trs80
U+1FB20🬠astralatom, bbcmaster, bbcmicro, trs80
U+1FB21🬡astralatom, bbcmaster, bbcmicro, trs80
U+1FB22🬢astralatom, bbcmaster, bbcmicro, trs80
U+1FB23🬣astralatom, bbcmaster, bbcmicro, trs80
U+1FB24🬤astralatom, bbcmaster, bbcmicro, trs80
U+1FB25🬥astralatom, bbcmaster, bbcmicro, trs80
U+1FB26🬦astralatom, bbcmaster, bbcmicro, trs80
U+1FB27🬧astralatom, bbcmaster, bbcmicro, trs80
U+1FB28🬨astralatom, bbcmaster, bbcmicro, trs80
U+1FB29🬩astralatom, bbcmaster, bbcmicro, trs80
U+1FB2A🬪astralatom, bbcmaster, bbcmicro, trs80
U+1FB2B🬫astralatom, bbcmaster, bbcmicro, trs80
U+1FB2C🬬astralatom, bbcmaster, bbcmicro, trs80
U+1FB2D🬭astralatom, bbcmaster, bbcmicro, trs80
U+1FB2E🬮astralatom, bbcmaster, bbcmicro, trs80
U+1FB2F🬯astralatom, bbcmaster, bbcmicro, trs80
U+1FB30🬰astralatom, bbcmaster, bbcmicro, trs80
U+1FB31🬱astralatom, bbcmaster, bbcmicro, trs80
U+1FB32🬲astralatom, bbcmaster, bbcmicro, trs80
U+1FB33🬳astralatom, bbcmaster, bbcmicro, trs80
U+1FB34🬴astralatom, bbcmaster, bbcmicro, trs80
U+1FB35🬵astralatom, bbcmaster, bbcmicro, trs80
U+1FB36🬶astralatom, bbcmaster, bbcmicro, trs80
U+1FB37🬷astralatom, bbcmaster, bbcmicro, trs80
U+1FB38🬸astralatom, bbcmaster, bbcmicro, trs80
U+1FB39🬹astralatom, bbcmaster, bbcmicro, trs80
U+1FB3A🬺astralatom, bbcmaster, bbcmicro, trs80
U+1FB3B🬻astralatom, bbcmaster, bbcmicro, trs80
U+1FB6C🭬astralhb10p
U+1FB6D🭭astralhb10p
U+1FB6E🭮astralhb10p
U+1FB6F🭯astralhb10p
U+1FB70🭰astralcommodore64, pet, sorcerer, vic20
U+1FB71🭱astralcommodore64, pet, sorcerer, vic20
U+1FB72🭲astralsorcerer
U+1FB73🭳astralcommodore64, pet, vic20
U+1FB74🭴astralcommodore64, pet, sorcerer, vic20
U+1FB75🭵astralsorcerer
U+1FB76🭶astralcommodore64, pet, sorcerer, vic20
U+1FB77🭷astralcommodore64, pet, sorcerer, vic20
U+1FB78🭸astralsorcerer
U+1FB79🭹astralcommodore64, pet, vic20
U+1FB7A🭺astralsorcerer
U+1FB7B🭻astralsorcerer
U+1FB7C🭼astralsorcerer
U+1FB7D🭽astralsorcerer
U+1FB7E🭾astralsorcerer
U+1FB7F🭿astralsorcerer
U+1FB82🮂astralatari400, atari800, commodore64, hb10p, pet, sorcerer, vic20
U+1FB83🮃astralcommodore64, pet, vic20
U+1FB85🮅astralhb10p
U+1FB87🮇astralatari400, atari800, hb10p, sorcerer
U+1FB88🮈astralcommodore64, pet, vic20
U+1FB8A🮊astralhb10p
U+1FB8C🮌astralcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20
U+1FB8D🮍astralcpc464, cpc6128, cpc664
U+1FB8E🮎astralcpc464, cpc6128, cpc664, zx80, zx81
U+1FB8F🮏astralcommodore64, cpc464, cpc6128, cpc664, pet, sorcerer, vic20, zx80, zx81
U+1FB90🮐astralzx80, zx81
U+1FB91🮑astralzx80, zx81
U+1FB92🮒astralzx80, zx81
U+1FB95🮕astralcpc464, cpc6128, cpc664
U+1FB96🮖astralhb10p
U+1FB98🮘astralhb10p
U+1FB99🮙astralhb10p
U+1FB9A🮚astralhb10p
U+1FB9B🮛astralhb10p
U+1FB9C🮜astralcpc464, cpc6128, cpc664
U+1FB9D🮝astralcpc464, cpc6128, cpc664
U+1FB9E🮞astralcpc464, cpc6128, cpc664
U+1FB9F🮟astralcpc464, cpc6128, cpc664
U+1FBA0🮠astralcpc464, cpc6128, cpc664
U+1FBA1🮡astralcpc464, cpc6128, cpc664
U+1FBA2🮢astralcpc464, cpc6128, cpc664
U+1FBA3🮣astralcpc464, cpc6128, cpc664
U+1FBA4🮤astralcpc464, cpc6128, cpc664
U+1FBA5🮥astralcpc464, cpc6128, cpc664
U+1FBA6🮦astralcpc464, cpc6128, cpc664
U+1FBA7🮧astralcpc464, cpc6128, cpc664
U+1FBA8🮨astralcpc464, cpc6128, cpc664
U+1FBA9🮩astralcpc464, cpc6128, cpc664
U+1FBAE🮮astralcpc464, cpc6128, cpc664

Known wrinkles ​

Commodore characters that share a bitmap ​

A few Commodore graphics have character-ROM bitmaps identical to a lower code — C= H ($B4), C= N ($AA), SHIFT C ($C3) and SHIFT − ($DD) draw the same bars and lines as $A5, $A7, $C0 and $C2. Injectivity means they cannot have their own Unicode character, so their palette entries insert the canonical twin's byte. The palette still lists them under their real keys, because it is there to teach the physical keyboard; the difference is invisible on screen and is pinned as a named allowlist in semigraphicsRoundTrip.test.ts rather than left to be rediscovered.

The Sinclair grey escapes used to disagree between machines ​

The two-character escapes name the halves of a cell: ' upper, . lower, : full, space empty, with ! chequered and | inverse-chequered. The ZX81 had \!' and \!. the wrong way round — \!' produced the lower half — which also meant the two Sinclair machines spelled the same shape differently, and the ZX81's on-screen "grey top" key typed the grey bottom character.

Both now follow the ROM bitmaps, and sinclairGraphics.test.ts checks each spelling against the shape the ROM actually draws, on both machines, so they cannot drift apart again.

This does re-encode a ZX81 program saved with the old spelling: \!' and \!. (and their inverses \|' and \|.) now store the other half's byte. Nothing else changes — the Unicode characters, which are the canonical spelling, were correct throughout.

The Spectrum's user-defined graphics are letters in boxes ​

A UDG (0x90–0xA4) has no shape of its own: its bitmap lives in UDG RAM and is whatever the program pokes there. What identifies it is the letter of the key that types it, so each is written as that letter squared — 🄰–🅄 (U+1F130–U+1F144), the twenty-one characters of the Enclosed Alphanumeric Supplement that match the twenty-one UDGs exactly.

They were spelled \a–\u (the zmakebas convention) before. That is two editor characters for one machine byte, which made the palette lay them out as two cells and let a backspace take half of a graphic away and leave a stray letter behind in the program. The escapes are still accepted when reading a program, so anything written or imported with them still loads and encodes to the same bytes; they are simply no longer what the IDE writes.

The palette draws in the editor's polarity, not the machine's ​

The graphics palette is a preview of text that is about to land in the editor, and the editor is dark ink on light paper for every machine. So the palette cells are drawn that way too — for the Commodores as much as the Sinclairs, even though a real C64 screen is light on dark. Drawing them as light-on-dark tiles (which is how they started, matching the keycaps around them) makes a half-block cell read as the half its unlit band covers: the palette appears to say "top" where the program then shows "bottom".

e2e/virtual-input/graphics-palette.spec.ts walks every machine that has a palette and checks the character is the darker of the two; the machine list is pinned to the registry by src/dialects/graphicsPalette.test.ts.

The shapes come from a bundled font, in text proportions ​

Almost no monospace font ships the Symbols-for-Legacy-Computing block, so the IDE bundles two small unicode-range-gated subsets covering exactly the code points the audit says the dialects emit (src/assets/fonts/, with the licences and the subsetting commands in its ATTRIBUTION.md; fontCoverage.test.ts fails if a dialect starts emitting something no face covers).

They are text faces — half an em wide by one em tall — which is right for the editor's character cells but draws a machine's square 8×8 cell as a taller rectangle. The editor keeps the text metrics (columns have to line up, and the metrics are pinned to the primary font's so a graphic cannot change a row's height); the graphics palette scales its cells horizontally so the shape reads in the proportions the machine draws it.

Stacked graphics do not tile vertically ​

Seamless tiling needs the editor's line height to equal the character cell height, and it is larger. That is a property of the CSS line box rather than of the font, so no font choice fixes it: block characters render correctly but a picture built from stacked rows shows thin gaps between them.

The blank cell nobody can type ​

Five machine families declare a graphics code that draws an empty cell — the Spectrum's 0x80, the TRS-80's 0x80, the CPC's 0x80, the BBC's 0xA0 (the empty MODE 7 mosaic) and the Atom's 0xA0 (the empty Semigraphics 6 cell). Its only faithful text form is a space, and 0x20 already owns that, so injectivity leaves it spelled as a raw escape. The palette does not offer it: a cell that inserted a space would look like a bug, and printing the code has the same effect as printing a space anyway. That is the whole of the "1 raw escape / 63 of 64 typeable" line those machines show above.

A BBC mosaic byte is only sometimes a mosaic on the machine ​

In MODE 7 the SAA5050 shows a stored byte as a sixel mosaic only while a graphics-colour control code is in force on the row; the same byte after a text-colour code is a letter, and in modes 0–6 it is whatever the mode's font says. The editor cannot know that state — it depends on control codes, the mode and what the program pokes — and a spelling that changed when a distant byte was edited would break one-byte-one-character editing. So the graphics form is canonical, unconditionally: 0xA1–0xBF and 0xE0–0xFF always render as their sextants, the byte round-trips exactly either way, and a top-bit byte meant as MODE 7 text simply reads as its mosaic twin in the editor. 0xC0–0xDF are not mosaics at all — the SAA5050 treats a code as graphics only when bit 5 is set, and bit-5-clear codes "blast through" as capitals — so they stay raw escapes, as does everything a mosaic is not. src/dialects/bbcmicro/mode7Graphics.test.ts derives all of this from the glyph set of jsbeeb's own SAA5050, the one the emulator pane draws with, so the mapping cannot drift from what the user sees when the program runs.

The consequence is felt at authoring time: a palette of mosaics with no way to reach a graphics colour hands out characters that print as letters, which is exactly what it looked like when someone first tried it. So the palette leads with the graphics-colour codes (0x91–0x97) and says under the title that the mosaics need one earlier on the same screen row, and closes with the display attributes ({CONTIGUOUS}, {SEPARATED}, {HOLD GRAPHICS}, {RELEASE GRAPHICS}). The text colours 0x81–0x87 are deliberately not offered — they clear the chip's graphics flag — and the same test derives that set from handleControlCode so a later addition of one fails rather than quietly reintroducing the problem. Both surfaces draw a control code as a chip (src/dialects/controlChip.ts) rather than spelling its escape across sixteen columns of a forty-column line.

The Atom's second colour set has no character of its own ​

The MC6847's Semigraphics 6 mode draws each of its 64 cell patterns in one of two colour pairs, chosen by bit 7 of the screen code. The Atom kernel's write-character routine reaches both from program text: bytes 0xA0–0xDF are the full 64 patterns in one colour set, and 0xE0–0xFF repeat patterns 0x20–0x3F — the same shapes as 0xC0–0xDF — in the other. Editor text carries shape but not colour, and one character cannot re-encode to two different bytes, so only the first range gets the sextants; 0xE0–0xFF keep their {0xNN} escapes and still round-trip exactly. This is the first machine where the collision is between two of its own codes rather than between two machines' conventions, and it is why the declared range stops at 0xDF.

src/dialects/atom/semigraphics.test.ts pins both halves of that: the cell shapes against the MC6847 font the emulator draws with, and the byte-to-screen -code mapping against the real Atom kernel ROM, by PRINTing every graphics byte and reading the screen back.

Neither the CPC nor the TRS-80 has graphics keys ​

Both machines printed nothing but letters, digits and punctuation on their keycaps; a program reached the block graphics through PRINT CHR$(n). On the CPC this is confirmed by the firmware's key translation tables, which map no key — plain, shifted, or with CONTROL — into the graphics range: CONTROL yields the 0x00–0x1F control codes, and the only upper-range codes a key emits are the cursor arrows at 0xF0–0xF3, outside the graphics block.

So their palette cells are labelled with the character code rather than a key, which is what those machines' BASIC actually needs. The palette still teaches the machine — it is the machine that reaches its graphics differently.

Neither has the Atom, and it has no CHR$ either ​

The Atom's keycaps carry no graphics, and Atom BASIC has no character function to build one with — a program put the byte straight into a string literal. So its palette cells are labelled with the byte, and that byte is what a listing shows.

Released under GNU GPL v3.0. Some ROM images are third-party copyrighted works, separate to this project, strictly for personal/educational purposes.