Depth: a ball behind the near pillars and in front of the far ones

The demo for what V5 added. Four pillars at four distances, each ONE 8 by
8 tile stretched to its own width and height, and a ball walking past all
of them at a distance between two.

What it shows is the thing an ordering cannot. The ball is sprite NOUGHT
and every pillar is numbered after it, so table order puts the ball in
front of all four - and it is still hidden behind two of them, because the
depth buffer is asked per column. Caught mid-straddle in the checks: the
ball is 48 wide and the pillar 32, so it shows on both sides and nowhere
across the middle.

Writing it found the conceptual trap in the feature, which is now written
down where somebody will hit it. The pillars first carried their own
distance in their entries AND wrote that same distance into their columns,
so each was asked whether it was in front of itself - and 20 is not nearer
than 20, so all four vanished. THE BUFFER IS WHAT HAS BEEN DRAWN AND A
SPRITE'S DEPTH IS A QUESTION ASKED OF IT. Scenery writes it; it does not
ask.

Also found that a scheme only gives a colour to index one. The default
palette sets each scheme's paper and ink and nothing between them, so
art drawn in index two comes out black until a program writes a palette.

And the fixture disk's root directory was full: four blocks, 32 entries,
all taken, so adding an app failed the whole disk build. Loudly, which is
the right way round - but it is a wall that moves for free, so it is eight
blocks and 64 entries now.

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 15:58:34 -04:00
co-authored by Claude Opus 5
parent cb898450b5
commit fba1b553d2
14 changed files with 444 additions and 12 deletions
+377
View File
@@ -0,0 +1,377 @@
; A ball that goes behind some pillars and in front of others.
;
; Four pillars, each ONE 8 by 8 TILE stretched to its own width and height, at four different
; distances. One ball, moving across all of them. Which pillars it passes behind is decided
; per screen column by the depth buffer, and that is the thing worth watching: the ball is in
; front of the far pillars and behind the near ones IN THE SAME FRAME, without the program
; sorting anything or drawing anything twice.
;
; ---- Why that needs a buffer rather than an ordering ----
;
; Sprites are drawn in table order, so a program can put one in front of another by numbering
; them. That is enough when things are flat. It stops being enough the moment a thing is
; nearer than one part of the scenery and further than another - a ball halfway past a pillar
; is in front of the floor beside it and behind the pillar itself, and there is no order of
; two sprites that means "behind, on those columns only".
;
; So the scenery says how far away it is, a column at a time, and the ball says how far away
; IT is. The device compares them per pixel.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x5000
start:
; The atlas: the tiles, the sprite table and the depth buffer are all in it.
INIA 0d4
OUTA 0xE3
INIA 0x30
OUTA 0xE2
INIA 0x03
OUTA 0xE8
SETD.0 Message
SWI osPrintString
INIA 0x0A
OUTA 0x00
CALL putArt
CALL clearDepth
CALL putPillars
CALL putBall
INIA 0x01
OUTA 0x02 ; Key mode.
everyFrame:
CALL waitFrame
CALL stepBall
CALL moveBall
INA 0x01
INIB 0x01 ; READY
AND
BRQ everyFrame
INA 0x00
RSTA
OUTA 0x02 ; Line mode. The sprites and the buffer are the system's to clear.
SWI osExit
; ---- Two tiles ----
;
; Tile 200 is the ball, which has a shape and so comes out of the Data Segment. Tile 201 is
; the pillar, which is a solid block and so is a Fill: 64 bytes of index one, no art needed.
putArt:
INIA 0x01
OUTA 0xE0 ; SourceBank: Data Memory.
SETD.1 BallArtAt
SETD.0 BallArt
STD.0.1
LDA.1
OUTA 0xE1
INCD.1
LDA.1
OUTA 0xE2
INIA 0d4
OUTA 0xE3
INIA 0x32
OUTA 0xE4 ; Tile 200 is at 200 times 64, which is 0x3200.
RSTA
OUTA 0xE5
OUTA 0xE6
INIA 0x40
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
INIA 0x32
OUTA 0xE4
INIA 0x40
OUTA 0xE5 ; Tile 201 is 64 bytes further on, at 0x3240.
INIA 0x01
OUTA 0xE2 ; Index ONE, which is the only index a scheme gives a colour to:
; the default palette sets each scheme's paper and ink and
; nothing between them.
RSTA
OUTA 0xE6
INIA 0x40
OUTA 0xE7
INIA 0x02
OUTA 0xE8 ; Fill.
RET
; ---- The buffer, emptied ----
;
; Nought in a column means nothing is there. It is the program's buffer and nobody clears it
; between programs, so a program that means "nothing yet" has to say so.
clearDepth:
INIA 0d4
OUTA 0xE3
INIA 0xD0
OUTA 0xE4
RSTA
OUTA 0xE5
OUTA 0xE2 ; Fill takes the byte it writes from SourceLow.
INIA 0x02
OUTA 0xE6
INIA 0x80
OUTA 0xE7 ; 640 bytes, one a column.
INIA 0x02
OUTA 0xE8
RET
; ---- The pillars ----
;
; Four records of seven bytes: x, y, height, depth. Each becomes a sprite entry and a run of
; the depth buffer saying how far away that part of the scenery is.
putPillars:
INIA 0d4
SETD.0 PillarLeft
STA.0 ; Sprite one is at 0xC010; sprite nought is the ball.
INIA 0x10
SETD.0 EntryLow
STA.0
SETD.3 Pillars
putOnePillar:
; ---- The entry, written straight through ----
;
; Sixteen bytes out of one port with the address named once, because the controller's Data
; port steps on after every byte. DP3 walks the record and survives the calls below.
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
SETD.0 EntryLow
LDA.0
OUTA 0xE5
INIA 0xC9
OUTA 0xE9 ; Tile 201, the pillar block.
INIA 0x01
OUTA 0xE9 ; Attribute one.
; Kept as they go past, because putPillarDepth needs the column and a Data Pointer cannot
; be walked backwards to find it again.
LDA.3
OUTA 0xE9 ; X low.
SETD.0 PillarXLow
STA.0
INCD.3
LDA.3
OUTA 0xE9 ; X high.
SETD.0 PillarXHigh
STA.0
INCD.3
LDA.3
OUTA 0xE9 ; Y low.
INCD.3
LDA.3
OUTA 0xE9 ; Y high.
INCD.3
INIA 0x11
OUTA 0xE9 ; One tile by one, which is the art. The size below is the look.
RSTA
OUTA 0xE9 ; No flags.
INIA 0d32
OUTA 0xE9
RSTA
OUTA 0xE9 ; Thirty two pixels wide, whatever the tile is.
LDA.3
OUTA 0xE9 ; Height low.
INCD.3
LDA.3
OUTA 0xE9 ; Height high.
INCD.3
; ---- And a depth of NOUGHT, which is not the pillar's distance ----
;
; It is tempting to put the pillar's own distance here, and it is wrong: the depth buffer
; is what has already been DRAWN, and a sprite's depth is a QUESTION ASKED OF IT. A pillar
; that both wrote 20 into its own columns and carried 20 would be asked whether it was in
; front of itself, and 20 is not nearer than 20, so it would vanish. Which it did.
;
; Scenery writes the buffer. It does not ask.
RSTA
OUTA 0xE9
CALL putPillarDepth
; On to the next record and the next entry.
INCD.3
SETD.0 EntryLow
LDA.0
INIB 0d16
CCF
ADD
STQ.0
SETD.0 PillarLeft
LDA.0
DECA
STA.0
BNA putOnePillar
RET
; ---- How far away those thirty two columns are ----
;
; A Fill, because the whole run is one number: the pillar is flat on, so every column of it
; is the same distance. DP3 is on the depth byte of the record and stays there.
;
; The buffer begins at 0xD000, whose low byte is nought - so the column's low byte IS the
; address's low byte, and its high byte only has to be added to 0xD0. No sixteen bit sum.
putPillarDepth:
LDA.3
OUTA 0xE2 ; The byte to fill with, which is the depth.
INIA 0d4
OUTA 0xE3
SETD.0 PillarXHigh
LDA.0
INIB 0xD0
CCF
ADD
OUTQ 0xE4
SETD.0 PillarXLow
LDA.0
OUTA 0xE5
RSTA
OUTA 0xE6
INIA 0d32
OUTA 0xE7 ; Thirty two columns of it.
INIA 0x02
OUTA 0xE8
RET
; ---- The ball ----
;
; Sprite nought, so it is in front of every pillar as far as the TABLE is concerned. What
; puts it behind some of them is its depth and nothing else, which is the whole point.
putBall:
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
RSTA
OUTA 0xE5
INIA 0xC8
OUTA 0xE9 ; Tile 200.
INIA 0x02
OUTA 0xE9 ; Attribute two.
RSTA
OUTA 0xE9
OUTA 0xE9 ; X, low then high.
INIA 0d230
OUTA 0xE9
RSTA
OUTA 0xE9 ; Y.
INIA 0x11
OUTA 0xE9 ; One tile by one.
RSTA
OUTA 0xE9 ; No flags.
INIA 0d48
OUTA 0xE9
RSTA
OUTA 0xE9
INIA 0d48
OUTA 0xE9
RSTA
OUTA 0xE9 ; Forty eight by forty eight, from eight by eight of art.
INIA 0d45
OUTA 0xE9 ; And forty five away, which is between the pillars.
RET
waitFrame:
INA 0x30
INIB 0x01
AND
BRQ waitFrame
RET
; Two pixels to the right, wrapping past the far side. Sixteen bits in two bytes, so the high
; one steps only when the low one came round to nought.
stepBall:
SETD.0 BallX
LDA.0
INCA
INCA
STA.0
BNA stepCheck
SETD.0 BallXHigh
LDA.0
INCA
STA.0
stepCheck:
SETD.0 BallXHigh
LDA.0
INIB 0x02
CCF
SUB
BNQ stepDone ; Not past 0x0200, so nowhere near the end.
RSTA
SETD.0 BallX
STA.0
SETD.0 BallXHigh
STA.0
stepDone:
RET
moveBall:
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x02
OUTA 0xE5 ; X is bytes two and three of the entry.
SETD.0 BallX
LDA.0
OUTA 0xE9
SETD.0 BallXHigh
LDA.0
OUTA 0xE9
RET
#Data
#Base 0x3000
Message:
"A ball, behind the near pillars and in front of the far ones. Press a key."
; x, y, height, depth. Nearer pillars are taller, which is the only thing making this look
; like distance rather than like four bars.
Pillars:
0x50 0x00 0x64 0x00 0xF0 0x00 0d20 ; x 80, y 100, 240 tall, near
0xC8 0x00 0xDC 0x00 0x78 0x00 0d60 ; x 200, y 220, 120 tall, far
0x68 0x01 0xA0 0x00 0xB4 0x00 0d30 ; x 360, y 160, 180 tall, nearish
0xF4 0x01 0x04 0x01 0x50 0x00 0d80 ; x 500, y 260, 80 tall, furthest
; Where putPillarDepth reads the column from. DP3 is on the record's depth byte by then, and
; a Data Pointer cannot be read backwards, so the two halves are kept here as they go past.
PillarXLow:
0x00
PillarXHigh:
0x00
PillarLeft:
0x00
EntryLow:
0x00
BallArt:
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
BallArtAt:
#Reserve 0d2
BallX:
0x00
BallXHigh:
0x00
+1
View File
@@ -726,6 +726,7 @@ from every assembly file in it. Several are old programs written for the bare ma
| Break | Stops itself twice with SWI osBreak, so that the registers can be seen changing between one stop and the next. |
| Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. |
| Sprite | Moves a ball across the shell's own text, writing not one byte of the map to do it. It leaves the sprite in the table on the way out, because clearing them is the system's job - see below. |
| Depth | Four pillars at four distances and a ball walking past them, behind the near ones and in front of the far ones. The ball is sprite nought and every pillar is numbered after it, so table order puts it in front of all four - what actually decides is the depth buffer, asked a column at a time. |
| Flip | Draws a whole screen into the bank that is not being shown, waits, and then shows it in one byte out of one port. It writes nothing else at all - not a tile, not a colour - so it does not ask for the screen to be saved, and the line it printed is still there when it comes back. It deliberately does not put the displayed screen back either, because that is the system's to restore: a program that faulted while flipped could not have. |
| Edit | A line editor. |
| Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. |