Grid: the first program to use the screen as a screen

Everything drawn on this machine so far has been text or a bitmap. The tile
engine has been there since the screen was built and only the console had
touched it, and only ever to put a letter in a cell - the one thing it can
do that a plain character display could do too.

Grid redefines a tile, fills all 128 map rows with it, and scrolls by
writing ONE BYTE A FRAME. Nothing moves. The rows above and below the
screen are already drawn, so a screenful of movement costs one OUTA and the
rows that leave the top are still there.

Its tile goes at 200 because the machine wakes with the font in tile memory
- glyph n at tile n, for 135 of the 256 - so a program starting at zero
paints over the alphabet and the shell it is about to hand the machine back
to. Its sixteen colour bands are one tile and not sixteen: the attribute
nibble is added to every index in a cell, so the same 64 bytes come out in
sixteen colourings.

Three things it cost, all of them the same lesson about this machine:

  - "SETD.0 X" then "STD.0.1" stores through DP1, which had not been set
    yet. It assembles, and the blit then reads its 64 bytes from wherever
    DP1 was last left, so the tile came out as noise.
  - The palette entry for scheme n is at 0xFC00 + 64n, which reaches
    0xFFC0 - four pages, not one. And doubling A by adding B needs B to
    hold A, which RSTB is the opposite of. Both went away by writing all
    256 entries in order and letting the controller step the address, so
    nothing computes an address at all.
  - The screen it hands back had the right cells and the wrong colours,
    because restoring the map is not restoring the palette.

That last one is a gap in the machine rather than in this program, and is
written up in the CosmOS README. The console's colours live at exactly the
entries the attribute nibble lands on, so any program using the nibble
overwrites them and has nowhere else to write. Grid puts bank 0 back - grey
on black - and leaves the other fifteen. The real answer is a command to
the screen meaning "give me back what you woke up with", the way the
console has one for clearing. There is not one, and this is the first
program that ever wanted it.

Two checks in video.sh, which boots the whole system and reads the pixels
the renderer produced rather than trusting what the program believed.
Breaking the tile fails one and breaking the attribute fails the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-30 17:38:30 -04:00
co-authored by Claude Opus 5
parent 0e0731e2b1
commit 1a8a5efe03
7 changed files with 472 additions and 2 deletions
+2 -1
View File
@@ -7,6 +7,7 @@ Snake.sbx 2164
Keys.sbx 664
Say.sbx 156
Break.sbx 149
Grid.sbx 510
notes.txt 21
hi.script 121
bad.script 45
@@ -16,7 +17,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
16 files
17 files
> load what?
> no such file
> not a program
+2 -1
View File
@@ -6,6 +6,7 @@ Snake.sbx 2164
Keys.sbx 664
Say.sbx 156
Break.sbx 149
Grid.sbx 510
notes.txt 21
hi.script 121
bad.script 45
@@ -15,7 +16,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
16 files
17 files
> loaded, starting at 4000
> it says: the disk took its time
finished
+6
View File
@@ -106,6 +106,12 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Break.asm" -o "$WORK/Break.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Break.sbx" >/dev/null
# Grid.sbx is the first program that uses the screen as a screen rather than as somewhere to
# put letters: it redefines a tile, fills a map bigger than the display, and scrolls it by
# moving the origin. Tests/video.sh boots this disk and looks at the pixels.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Grid.asm" -o "$WORK/Grid.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Grid.sbx" >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
+36
View File
@@ -718,6 +718,42 @@ TOTAL="$(grep -oE 'after [0-9]+' "$BUILD/poller.out" | grep -oE '[0-9]+')"
&& result ok "and the flag comes down when looked at" "$TOTAL cycles, so three frames passed" \
|| result no "and the flag comes down when looked at" "$TOTAL cycles, too few to be three frames"
# ---- The tile engine, driven by a program rather than by the console ----
#
# Everything above drives the screen from a bare test program. This boots the whole system
# and runs Grid.sbx on it, because Grid is the first thing that uses the engine as an engine:
# it redefines a tile above the font, fills all 128 map rows, and scrolls by moving the
# origin. What is checked is what came out of the renderer, not what the program believed.
#
# The keyboard file is what makes it possible to catch it MID-SCROLL: "Grid" and a return,
# then a long silence, so the machine is still running when the cycle limit stops it and the
# picture is taken.
"$ASM" -I "$ROOT/Programs/CosmOS/Source" "$ROOT/Programs/CosmOS/Source/cosmos.asm" \
-o "$BUILD/cosmos.bin" > "$BUILD/cosmos.log" 2>&1
python3 -c "open('$BUILD/grid.keys','wb').write(b'Grid\n' + b'\x00'*4000)"
timeout 30 "$EMU" --fast --cycles 8000000 --keyboard "$BUILD/grid.keys" \
--screen "$BUILD/grid.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
"$BUILD/cosmos.bin" > "$BUILD/grid.out" 2>&1 || true
if [ -f "$BUILD/grid.ppm" ]; then
# A cell is eight by eight with a line along its top and down its left, so within one
# cell the corner is line and the middle is ground - and the cell to the right starts
# with a line again. That is a grid rather than a wash of colour.
corner="$(pixel grid 0 0)"; middle="$(pixel grid 4 4)"; nextcell="$(pixel grid 8 4)"
[ "$corner" != "$middle" ] && [ "$nextcell" = "$corner" ] \
&& result ok "a program drew a grid of its own tile" "line $corner, ground $middle" \
|| result no "a program drew a grid of its own tile" "corner $corner middle $middle next $nextcell"
# The attribute nibble adds sixteen to every index in the tile, so consecutive map rows
# come out in consecutive schemes. Two cell rows apart must not be the same colour.
one="$(pixel grid 0 0)"; two="$(pixel grid 0 8)"
[ "$one" != "$two" ] \
&& result ok "the attribute nibble recolours it" "row 0 $one, row 1 $two" \
|| result no "the attribute nibble recolours it" "both rows are $one"
else
result no "a program drew a grid of its own tile" "no picture came out"
fi
# ---- Clearing puts the cursor back at the top ----
#
# A screen with nothing on it and a cursor half way down it is not a cleared screen. This