Four pages of tiles, in bits that were already there

A tile number is a byte and a byte reaches 256, which is not many once a
font has taken 135 of them and a game wants a character, a background and
a wall. Bits 4 and 5 of the attribute now say which page of 256 the
number is in - bits already written on every cell and every sprite, and
reserved for this since the attribute was defined.

Four pages of 16K is 64K, which is the whole atlas, so THE FOURTH PAGE IS
THE MEMORY THE SPRITE TABLE AND THE PALETTE ARE IN. That is not a hole in
the design; it is the answer shared video memory has always given, and it
is checked rather than forbidden. The atlas is 1024 tiles, and what a
program spends on sprites and colours comes out of them: no sprites means
page 3 is art, and sprites means 768 tiles and a reason.

The page is a property of the CELL and not a mode, so one screen shows
tiles from all four at once and nothing has to decide which page it is in.

Both places a tile is drawn from now ask one function where the art is.
They would otherwise drift: the sprite pass was written days after the map
pass and neither is where the other is looked at.

Nothing in CosmOS changes. The shell draws from page 0, which the screen
save covers; a tile left in another page is invisible unless a map cell
names that page, and the map is given back or cleared.

Both breaks were tried and both failed the checks - and the second had to
be tried twice, because the constant it needed lives in video.h and the
harness was only editing video.c. That is the same silent no-op as
yesterday's uncompiled break, in a different disguise.

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-02 12:59:02 -04:00
co-authored by Claude Opus 5
parent a916103a7f
commit 9eed23120f
5 changed files with 132 additions and 14 deletions
+63
View File
@@ -158,6 +158,13 @@ solidTile() {
pokeAtlasRun "$(( $1 * 64 ))" "$2" 64
}
# The same, in one of the four pages. Page p begins at p times 16K, so a tile number means a
# different 64 bytes in each of them.
pageTile() {
# pageTile <page> <tile> <index>
pokeAtlasRun "$(( $1 * 0x4000 + $2 * 64 ))" "$3" 64
}
# The top half one index and the bottom half another, which is how a tile gets a hole in it:
# index nought is what a sprite does not draw.
halfTile() {
@@ -399,6 +406,48 @@ echo "Checking what the video device draws."
&& result ok "the map is a ring" "row 0 follows row 127" \
|| result no "the map is a ring" "got $(pixel ring 0 8)"
# ---- Four pages of tiles ----
#
# ONE TILE NUMBER, four different pictures. Every cell below names tile 1 and they differ only
# in the attribute's page bits, which is the whole of what this adds: a byte reaches 256 tiles
# and two bits that were already being written on every cell reach 1024.
#
# The fourth page is the memory the sprite table and the palette are in, and that is checked
# here rather than forbidden - it is the same answer shared video memory has always given.
# Page 3 tile 8 lands at 0xC200, which is sprite entries 64 to 71, and 64 bytes of index four
# read as a size byte of 0x04: no width, so no sprite. The art works and nothing is drawn.
{ prologue
spriteColours
solidTile 1 0x01
pageTile 1 1 0x02
pageTile 2 1 0x03
pageTile 3 8 0x04
pokeScreen 0x4304 0x01; pokeScreen 0x4305 0x00
pokeScreen 0x4306 0x01; pokeScreen 0x4307 0x10
pokeScreen 0x4308 0x01; pokeScreen 0x4309 0x20
pokeScreen 0x430A 0x08; pokeScreen 0x430B 0x30
epilogue
} | run pages || exit 1
[ "$(pixel pages 16 24)" = "255,0,0" ] && [ "$(pixel pages 24 24)" = "0,255,0" ] \
&& [ "$(pixel pages 32 24)" = "0,0,255" ] \
&& result ok "the attribute says which page" "the same tile number, three pictures" \
|| result no "the attribute says which page" "$(pixel pages 16 24) $(pixel pages 24 24) $(pixel pages 32 24)"
[ "$(pixel pages 40 24)" = "255,255,0" ] \
&& result ok "and the fourth page is the sprite table" "art read out of the memory sprites are in" \
|| result no "and the fourth page is the sprite table" "got $(pixel pages 40 24)"
# The page bits and the scheme nibble are in one byte and must not disturb each other. Tile 2
# of page 1 is index one everywhere; scheme one adds sixteen, and entry 17 is cyan.
{ prologue
spriteColours
pageTile 1 2 0x01
pokeScreen 0x4304 0x02; pokeScreen 0x4305 0x11
epilogue
} | run pagescheme || exit 1
[ "$(pixel pagescheme 16 24)" = "0,255,255" ] \
&& result ok "a page and a scheme in one byte" "page one, scheme one, and both applied" \
|| result no "a page and a scheme in one byte" "got $(pixel pagescheme 16 24)"
# ---- Sprites ----
#
# A thing put at a PIXEL rather than in a cell. Tile 1 is solid index one, which the palette
@@ -538,6 +587,20 @@ echo "Checking what the video device draws."
&& result ok "no width or no height draws nothing" "and the one beside them still does" \
|| result no "no width or no height draws nothing" "$(pixel spritenone 16 24) $(pixel spritenone 40 24) $(pixel spritenone 64 24)"
# A sprite reads its page from the same bits, because a sprite's attribute IS a cell's
# attribute - which is what lets the same art be a wall in one place and a moving thing in
# another with nothing rewritten.
{ prologue
spriteColours
solidTile 1 0x01
pageTile 1 1 0x02
spriteAt 0 1 0x10 16 24 0x11 0x00
epilogue
} | run spritepage || exit 1
[ "$(pixel spritepage 16 24)" = "0,255,0" ] \
&& result ok "a sprite has pages too" "tile one of page one, not of page nought" \
|| result no "a sprite has pages too" "got $(pixel spritepage 16 24)"
# ---- Two screens, and the flip between them ----
#
# One red cell in each screen, in different rows: row one of the screen being shown, and the