The font comes from a chip, not from RAM that remembers

videoReset zeroed video memory and then wrote the font and the sixteen colour
schemes into it, and the comment above that said out loud what was wrong with
it: "everything here is ordinary video memory". RAM does not wake up with
anything in it. That was the last piece of magic in this device, and it looked
harmless until something wanted the font BACK - a program that redefines a glyph
had destroyed the only copy there was.

So the device has a character generator, the way the machines this one is
pretending to be really did, and the copy into RAM is a thing it DOES rather
than a state it mysteriously starts in. Command port 0x39: bit 0 for the font,
bit 1 for the schemes.

THE RAM IS STILL RAM. A program may overwrite every glyph and every colour and
should be able to, which is what makes this a tile engine rather than a text
display. What changed is that it is no longer a one way door.

NEITHER COMMAND CLEARS WHAT IT DOES NOT OWN. The font used to clear the whole of
tile memory before writing itself, which was harmless while it happened only at
reset and is wrong the moment a program can ask: a program that defined a tile
of its own and then wanted its text back would have paid for it with the tile.

The reason it is a chip rather than a file on the disk, which was the other
candidate: the boot chain prints before CosmOS exists. Stage one prints "?" when
there is nothing to boot, and if the font came off the disk then the message
about the disk having failed would be the one thing that could not be drawn. A
system that wants its own font still loads one over the top - the ROM is the
floor, not the policy.

Two things that had been worked around now simply work. The shell asks for both
whenever a program exits, so a program that redefined a letter no longer leaves
it unable to spell, and Grid no longer needs to have saved the screen to avoid
handing back green text on blue. And the fault screen asks for the glyphs first,
because a message spelled in somebody's tile graphics is no message at all.

Five video checks. Two runs each for the font and the schemes, since the map
holds a tile NUMBER and the glyph is looked up when the frame is drawn - so
restoring changes every cell using it, including ones drawn before, and what the
two runs differ by is the command. The third guards the decision not to clear:
tile 200 has to survive the font coming back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-01 16:21:24 -04:00
co-authored by Claude Opus 5
parent 925388c2f2
commit f97d15de08
7 changed files with 207 additions and 8 deletions
+82
View File
@@ -73,6 +73,16 @@ poke() {
$(( ($1 >> 8) & 0xFF )) $(( $1 & 0xFF )) $(( $2 & 0xFF ))
}
# Many bytes from one address, using the Data port's own stepping rather than naming the
# address again for each. What a run of tile memory is for.
pokeRun() {
# pokeRun <address> <byte> <count>
printf ' INIA 0x%02X\n OUTA 0xE4\n INIA 0x%02X\n OUTA 0xE5\n INIA 0x%02X\n' \
$(( ($1 >> 8) & 0xFF )) $(( $1 & 0xFF )) $(( $2 & 0xFF ))
local i
for (( i = 0; i < $3; i++ )); do printf ' OUTA 0xE9\n'; done
}
port() {
# port <port> <byte>
printf ' INIA 0x%02X\n OUTA 0x%02X\n' $(( $2 & 0xFF )) $(( $1 & 0xFF ))
@@ -911,6 +921,78 @@ inked scrollback 2 1 \
&& result ok "what scrolled off is still there" "the origin went back and found it" \
|| result no "what scrolled off is still there" "nothing at 2,1"
# ---- The character generator is a chip, not a memory that remembers ----
#
# The font and the sixteen schemes used to be written into video RAM at reset and existed
# nowhere else, so a program that overwrote a glyph had destroyed the only copy. They come
# from a ROM in the device now, and the Command port asks for either back.
#
# TWO RUNS RATHER THAN ONE PICTURE, because the map holds a tile NUMBER and the glyph is
# looked up when the frame is drawn - so restoring the font changes every cell using it,
# including the ones drawn before. What the two runs differ by is the command.
#
# The glyph for 'A' is filled with ink, which makes the cell a solid block, so the top left
# pixel of it is ink where a real 'A' has paper. That is a pixel no font disagrees about.
{ prologue
pokeRun 0x0840 0x01 64 # Tile 33, which is 'A', every pixel ink.
say "A"
epilogue
} | run fontwrecked || exit 1
[ "$(pixel fontwrecked 0 0)" = "216,216,216" ] \
&& result ok "a program can overwrite a glyph" "the wrecked A is a solid block" \
|| result no "a program can overwrite a glyph" "not ink at 0,0"
{ prologue
pokeRun 0x0840 0x01 64
port 0x39 0x01 # And ask the character generator for it back.
say "A"
epilogue
} | run fontback || exit 1
[ "$(pixel fontback 0 0)" = "0,0,0" ] \
&& result ok "and ask the device for it back" "the A has its own shape again" \
|| result no "and ask the device for it back" "still ink at 0,0"
# ---- And asking does not cost a program the tiles it defined ----
#
# The font used to clear the whole of tile memory before writing itself, which was harmless
# while it only happened at reset and is wrong the moment a program can ask for it: a program
# that defined a tile of its own and then wanted its text back would have paid for it with
# the tile. It writes the glyphs it has and stops.
{ prologue
pokeRun 0x3200 0x01 64 # Tile 200, well above anything the font occupies.
poke 0x4000 0xC8 # And that tile in the first cell of the map.
poke 0x4001 0x00
port 0x39 0x03 # Both the font and the palette back.
epilogue
} | run fontkeeps || exit 1
[ "$(pixel fontkeeps 0 0)" = "216,216,216" ] \
&& result ok "and leaves a program's own tiles alone" "tile 200 survived the font coming back" \
|| result no "and leaves a program's own tiles alone" "tile 200 was cleared"
# ---- The palette the same way ----
#
# Ink and paper made the same colour is a screen with writing on it that cannot be read,
# which is exactly what the fault screen has to survive. The device is asked for the sixteen
# schemes back and the writing returns.
{ prologue
poke 0xFC04 0x00; poke 0xFC05 0x00; poke 0xFC06 0x00 # Scheme 0's ink, made black.
say "A"
epilogue
} | run inkwrecked || exit 1
[ "$(pixel inkwrecked 3 1)" = "0,0,0" ] \
&& result ok "a program can overwrite a scheme" "ink and paper are the same colour" \
|| result no "a program can overwrite a scheme" "the A is still visible"
{ prologue
poke 0xFC04 0x00; poke 0xFC05 0x00; poke 0xFC06 0x00
port 0x39 0x02 # The schemes back, and only the schemes.
say "A"
epilogue
} | run inkback || exit 1
[ "$(pixel inkback 3 1)" = "216,216,216" ] \
&& result ok "and ask the device for those back too" "the A can be read again" \
|| result no "and ask the device for those back too" "still nothing at 3,1"
# ---- The fault screen, from a screen with nowhere to print on it ----
#
# Every other test of the fault screen reads what came down the serial line, and the serial