Lunar Porter, rung one: it flies
A lander over a moon that wraps. Landing, crashing, fuel, cargo and bases are not here - this rung exists to answer whether it FEELS right, because everything after it is bookkeeping and none of it is worth building on a lander that is no fun to fly. The moon comes for free. The map's column origin is a ring in hardware, so 128 cells is 1024 pixels of surface with no edge and no seam to cross. Position and velocity are sixteen bit in SIXTEENTHS OF A PIXEL, and the unit is the design: gravity is a small number added to a velocity and a velocity is a number added to a position, with no multiply or divide anywhere. 1024 pixels is 16,384 sixteenths, which is 2^14 - so going all the way round is an AND with 0x3FFF rather than a comparison, and it is never wrong at the seam. The lander never moves sideways. The world scrolls under it and it sits at the middle of the screen, which is a byte a frame instead of two and is also what makes the wrap invisible: there is no moment where it jumps. One key is one burn. The console says which key went down and there is no such thing as a key coming up, so a thruster cannot be held - a press adds to the velocity once. That is a property of the machine rather than a choice this program made, and it reads as pumping the engine. Four bugs found by running it, all worth keeping written down: B CANNOT BE A LOOP COUNTER here. Every comparison is an INIB, so the count was overwritten by whichever bound was last tested and the loop reset itself for ever. Counters that outlive arithmetic live in memory. A subroutine answers in Q, and the AND after it read A. The moon came out flat because it was testing the height against 1 instead of the random number. Row minus height, not height minus row: they are equal at the surface, equal does not borrow, and the surface row has to be ground. And the shift register has A as its HIGH half. Written the other way, the view scrolled by 256 cells for every one it should have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
5222c85100
commit
d6c81fa32c
@@ -0,0 +1,595 @@
|
||||
; Lunar Porter. Rung one: it flies.
|
||||
;
|
||||
; A lander over a moon that wraps. Thrust, gravity, and a surface that comes back round if you
|
||||
; keep going one way - there is no edge to fall off and no wall to hit, because the map's
|
||||
; column origin is a ring in hardware and 128 cells of it is 1024 pixels of moon.
|
||||
;
|
||||
; ---- What is not here yet ----
|
||||
;
|
||||
; Landing, crashing, fuel, cargo, bases. This rung exists to answer whether it FEELS right,
|
||||
; because everything after it is bookkeeping by comparison and none of it is worth building
|
||||
; on a lander that is no fun to fly.
|
||||
;
|
||||
; ---- Sixteenths of a pixel ----
|
||||
;
|
||||
; Position and velocity are sixteen bit, in sixteenths of a pixel. That is the unit that makes
|
||||
; the whole thing work with adds alone: gravity is a small number added to a velocity, and a
|
||||
; velocity is a number added to a position, and there is no multiply or divide anywhere.
|
||||
;
|
||||
; The moon is 1024 pixels round, which is 16,384 sixteenths, which is 2^14 - so GOING ALL THE
|
||||
; WAY ROUND IS AN AND WITH 0x3FFF. Not a comparison, not a subtraction, and never wrong at the
|
||||
; seam. Picking the units so the wrap is a mask is most of the reason this is short.
|
||||
;
|
||||
; Written by Anachronaut
|
||||
|
||||
#Include services.asm
|
||||
|
||||
#Program
|
||||
|
||||
#Base 0x5000
|
||||
|
||||
start:
|
||||
; The atlas holds the tiles and the sprite table; the screen holds the map.
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0x30
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8
|
||||
INIA 0d5
|
||||
OUTA 0xE3
|
||||
INIA 0x3A
|
||||
OUTA 0xE2
|
||||
INIA 0x03
|
||||
OUTA 0xE8
|
||||
|
||||
; The screen back afterwards. This one really does need it: it redefines tiles, fills every
|
||||
; cell of the map and changes the mode, and screenGive is what puts the MODE back - the
|
||||
; floor the system gives every program does not.
|
||||
SWI osTakeScreen
|
||||
|
||||
RSTA
|
||||
OUTA 0x31 ; Forty columns. A moon 320 pixels across reads better than 640.
|
||||
|
||||
CALL putTiles
|
||||
CALL makeMoon
|
||||
CALL drawMoon
|
||||
CALL putLander
|
||||
|
||||
INIA 0x01
|
||||
OUTA 0x02 ; Key mode.
|
||||
|
||||
everyFrame:
|
||||
CALL waitFrame
|
||||
CALL readControls
|
||||
CALL fall
|
||||
CALL move
|
||||
CALL follow
|
||||
CALL showLander
|
||||
SETD.0 Flying
|
||||
LDA.0
|
||||
BNA everyFrame
|
||||
|
||||
RSTA
|
||||
OUTA 0x02
|
||||
SWI osExit
|
||||
|
||||
; ---- Tiles ----
|
||||
;
|
||||
; The ground is a solid block, which is a Fill and needs no art. The lander has a shape and
|
||||
; comes out of the Data Segment. Both are well above the 135 glyphs the character generator
|
||||
; copies back, so neither costs the shell a letter.
|
||||
putTiles:
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0x32
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5 ; Tile 200 begins at 0x3200.
|
||||
INIA 0x01
|
||||
OUTA 0xE2
|
||||
RSTA
|
||||
OUTA 0xE6
|
||||
INIA 0x40
|
||||
OUTA 0xE7
|
||||
INIA 0x02
|
||||
OUTA 0xE8 ; Fill: 64 pixels of index one.
|
||||
|
||||
INIA 0x01
|
||||
OUTA 0xE0
|
||||
SETD.1 LanderArtAt
|
||||
SETD.0 LanderArt
|
||||
STD.0.1
|
||||
LDA.1
|
||||
OUTA 0xE1
|
||||
INCD.1
|
||||
LDA.1
|
||||
OUTA 0xE2
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0x32
|
||||
OUTA 0xE4
|
||||
INIA 0x40
|
||||
OUTA 0xE5 ; Tile 201, one tile on from 200.
|
||||
RSTA
|
||||
OUTA 0xE6
|
||||
INIA 0x40
|
||||
OUTA 0xE7
|
||||
INIA 0x01
|
||||
OUTA 0xE8 ; Blit.
|
||||
RET
|
||||
|
||||
; ---- A moon, one column at a time ----
|
||||
;
|
||||
; A random walk over 128 columns, clamped so there is always sky above and ground below. The
|
||||
; numbers come from an eight bit shift register with feedback, which is the cheapest thing
|
||||
; that is not obviously a pattern: a byte, shifted right, exclusive-ored with a constant when
|
||||
; the bit that fell off was set.
|
||||
;
|
||||
; The SEED IS FIXED, so the moon is the same moon every time. That is worth more than variety
|
||||
; while the physics is being tuned - a landing that was too hard yesterday should be too hard
|
||||
; today - and a seed is one byte to change later.
|
||||
makeMoon:
|
||||
INIA 0d18
|
||||
SETD.1 Height
|
||||
STA.1
|
||||
SETD.2 Terrain
|
||||
RSTA
|
||||
SETD.1 Column
|
||||
STA.1
|
||||
makeColumn:
|
||||
SETD.1 Height
|
||||
LDA.1
|
||||
STA.2 ; This column's height, as it stands.
|
||||
INCD.2
|
||||
|
||||
CALL nextRandom
|
||||
MVQA ; The answer is in Q, because Q is what survives a RET. Without
|
||||
; this the AND below tests whatever A happened to hold - which
|
||||
; was the height itself, so the moon oscillated by one row and
|
||||
; looked flat.
|
||||
INIB 0x01
|
||||
AND
|
||||
BNQ makeUp
|
||||
|
||||
; Down a row, unless that is already as low as the ground goes.
|
||||
SETD.1 Height
|
||||
LDA.1
|
||||
INIB 0d22
|
||||
CCF
|
||||
SUB
|
||||
BRQ makeNext ; Equal, so it is already at the floor.
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
BRI makeNext
|
||||
|
||||
makeUp:
|
||||
; And up a row, unless that is as high as it goes.
|
||||
SETD.1 Height
|
||||
LDA.1
|
||||
INIB 0d10
|
||||
CCF
|
||||
SUB
|
||||
BRQ makeNext
|
||||
LDA.1
|
||||
DECA
|
||||
STA.1
|
||||
|
||||
makeNext:
|
||||
; ---- The counter is in memory, and has to be ----
|
||||
;
|
||||
; B is the obvious place for a loop counter and it is the wrong one here: every comparison
|
||||
; below is an INIB, so the count was overwritten by whichever bound was last tested and the
|
||||
; loop reset itself for ever. This machine has two registers and a dozen uses for them; a
|
||||
; counter that has to survive arithmetic lives in memory.
|
||||
SETD.1 Column
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
INIB 0d128
|
||||
CCF
|
||||
SUB
|
||||
BNQ makeColumn
|
||||
RET
|
||||
|
||||
; The shift register. Q comes back the new value, because Q is what survives a RET.
|
||||
nextRandom:
|
||||
SETD.1 Seed
|
||||
LDA.1
|
||||
INIB 0x01
|
||||
AND
|
||||
PSHQ ; The bit that is about to fall off.
|
||||
LDA.1
|
||||
RSTB
|
||||
SHR ; A:B right one, and B was nought, so A is the byte halved.
|
||||
POPB
|
||||
BNB randomTap
|
||||
STA.1 ; DP1 is still Seed, from reading it above.
|
||||
RSTB
|
||||
CCF
|
||||
ADD ; Into Q, which is where a subroutine answers.
|
||||
RET
|
||||
randomTap:
|
||||
INIB 0xB8
|
||||
XOR
|
||||
SETD.1 Seed
|
||||
STQ.1
|
||||
RET
|
||||
|
||||
; ---- The moon, drawn ----
|
||||
;
|
||||
; Rows outside and columns inside, which is the only affordable way round: cells along a row
|
||||
; are next to each other, so the address is named once and the controller's Data port steps
|
||||
; through 128 of them. Cells down a COLUMN are a page apart, and doing it that way would mean
|
||||
; naming an address for every one of the 3,200 cells.
|
||||
drawMoon:
|
||||
RSTA
|
||||
SETD.1 Row
|
||||
STA.1
|
||||
drawRow:
|
||||
INIA 0d5
|
||||
OUTA 0xE3
|
||||
SETD.1 Row
|
||||
LDA.1
|
||||
INIB 0x40
|
||||
CCF
|
||||
ADD
|
||||
OUTQ 0xE4 ; The map starts at 0x4000 and a row is a page.
|
||||
RSTA ; A held the row; DestLow has to be nought.
|
||||
OUTA 0xE5
|
||||
|
||||
SETD.2 Terrain
|
||||
SETD.1 Column
|
||||
STA.1 ; A is still the nought that went to DestLow.
|
||||
drawCell:
|
||||
; ---- Ground at the surface row and everything under it ----
|
||||
;
|
||||
; ROW MINUS HEIGHT and not the other way round, which matters at the surface itself: the
|
||||
; two are equal there, equal does not borrow, and the row a column's surface is on has to
|
||||
; be ground rather than the last of the sky.
|
||||
SETD.1 Row
|
||||
LDA.1
|
||||
LDB.2
|
||||
CCF
|
||||
SUB
|
||||
BRC drawSky ; Borrowed: this row is above the surface, so it is sky.
|
||||
INIA 0xC8
|
||||
OUTA 0xE9 ; Tile 200, the ground block.
|
||||
RSTA
|
||||
OUTA 0xE9 ; Attribute nought: grey on black, which is a fine moon.
|
||||
BRI drawNextCell
|
||||
drawSky:
|
||||
RSTA ; A is still this column's height, from the comparison above.
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9 ; Tile nought is the space, and no colour is needed for nothing.
|
||||
drawNextCell:
|
||||
INCD.2
|
||||
SETD.1 Column
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
INIB 0d128
|
||||
CCF
|
||||
SUB
|
||||
BNQ drawCell
|
||||
|
||||
SETD.1 Row
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
INIB 0d25
|
||||
CCF
|
||||
SUB
|
||||
BNQ drawRow
|
||||
RET
|
||||
|
||||
; ---- The lander, as a sprite ----
|
||||
;
|
||||
; Sprite nought, and it never moves horizontally: the world scrolls under it and it stays at
|
||||
; the middle of the screen. That is one byte a frame instead of two and it is also what makes
|
||||
; a moon that wraps invisible to the player - there is no moment where the lander jumps.
|
||||
putLander:
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0xC0
|
||||
OUTA 0xE4
|
||||
RSTA
|
||||
OUTA 0xE5
|
||||
INIA 0xC9
|
||||
OUTA 0xE9 ; Tile 201.
|
||||
INIA 0x03
|
||||
OUTA 0xE9 ; Attribute three, so it is yellow against a grey moon.
|
||||
INIA 0d156
|
||||
OUTA 0xE9
|
||||
RSTA
|
||||
OUTA 0xE9 ; X: the middle of a 320 pixel screen, less half a tile.
|
||||
OUTA 0xE9
|
||||
OUTA 0xE9 ; Y, which every frame overwrites.
|
||||
INIA 0x11
|
||||
OUTA 0xE9 ; One tile by one.
|
||||
RSTA
|
||||
OUTA 0xE9 ; No flags, natural size, no depth.
|
||||
RET
|
||||
|
||||
; ---- The controls ----
|
||||
;
|
||||
; ONE KEY IS ONE BURN. The console says which key was pressed and there is no such thing as a
|
||||
; key being released, so a thruster cannot be held down - what a press does is add to the
|
||||
; velocity once. It reads as pumping the engine rather than leaning on it, which is a choice
|
||||
; this machine makes rather than one this program did.
|
||||
readControls:
|
||||
INA 0x01
|
||||
INIB 0x01 ; READY
|
||||
AND
|
||||
BRQ noKey
|
||||
INA 0x00
|
||||
|
||||
INIB 0x80 ; Up
|
||||
CCF
|
||||
SUB
|
||||
BRQ burnUp
|
||||
SETD.1 KeyHeld
|
||||
STA.1
|
||||
LDA.1
|
||||
INIB 0x82 ; Left
|
||||
CCF
|
||||
SUB
|
||||
BRQ burnLeft
|
||||
LDA.1
|
||||
INIB 0x83 ; Right
|
||||
CCF
|
||||
SUB
|
||||
BRQ burnRight
|
||||
LDA.1
|
||||
INIB 0x71 ; q
|
||||
CCF
|
||||
SUB
|
||||
BRQ quit
|
||||
noKey:
|
||||
RET
|
||||
|
||||
quit:
|
||||
RSTA
|
||||
SETD.1 Flying
|
||||
STA.1
|
||||
RET
|
||||
|
||||
burnUp:
|
||||
SETD.0 SpeedDown
|
||||
SETD.1 ThrustUp
|
||||
CALL addWord
|
||||
RET
|
||||
burnLeft:
|
||||
SETD.0 SpeedAcross
|
||||
SETD.1 ThrustLeft
|
||||
CALL addWord
|
||||
RET
|
||||
burnRight:
|
||||
SETD.0 SpeedAcross
|
||||
SETD.1 ThrustRight
|
||||
CALL addWord
|
||||
RET
|
||||
|
||||
; Gravity, which is the same small number every frame and the reason this is a game.
|
||||
fall:
|
||||
SETD.0 SpeedDown
|
||||
SETD.1 Gravity
|
||||
CALL addWord
|
||||
RET
|
||||
|
||||
; ---- Where it is now ----
|
||||
;
|
||||
; Across first, and the wrap is an AND rather than a comparison: the moon is 2^14 sixteenths
|
||||
; round, so falling off one side is the top bits going away.
|
||||
move:
|
||||
SETD.0 AcrossLow
|
||||
SETD.1 SpeedAcross
|
||||
CALL addWord
|
||||
SETD.0 AcrossHigh
|
||||
LDA.0
|
||||
INIB 0x3F
|
||||
AND
|
||||
STQ.0
|
||||
|
||||
SETD.0 DownLow
|
||||
SETD.1 SpeedDown
|
||||
CALL addWord
|
||||
RET
|
||||
|
||||
; ---- The view, which follows it ----
|
||||
;
|
||||
; The lander is at the middle of the screen, so the view starts half a screen behind it. In
|
||||
; PIXELS: the position is sixteenths, so it is shifted down four first.
|
||||
follow:
|
||||
SETD.0 AcrossLow
|
||||
CALL toPixels
|
||||
; Half a screen back, and round the moon if that went below nought.
|
||||
SETD.0 PixelLow
|
||||
SETD.1 HalfScreen
|
||||
CALL subWord
|
||||
SETD.0 PixelHigh
|
||||
LDA.0
|
||||
INIB 0x03
|
||||
AND
|
||||
STQ.0 ; 1024 pixels round, so three bits of high byte.
|
||||
|
||||
; The whole cells are the column origin and the remainder is the fine offset.
|
||||
SETD.0 PixelLow
|
||||
LDA.0
|
||||
INIB 0x07
|
||||
AND
|
||||
OUTQ 0x37 ; Fine X.
|
||||
SETD.0 PixelHigh
|
||||
LDA.0
|
||||
SETD.0 PixelLow
|
||||
LDB.0
|
||||
CALL eighth
|
||||
OUTA 0x36 ; The column origin.
|
||||
RET
|
||||
|
||||
; ---- The lander, put where it now is ----
|
||||
showLander:
|
||||
SETD.0 DownLow
|
||||
CALL toPixels
|
||||
INIA 0d4
|
||||
OUTA 0xE3
|
||||
INIA 0xC0
|
||||
OUTA 0xE4
|
||||
INIA 0x04
|
||||
OUTA 0xE5 ; Y is bytes four and five of the entry.
|
||||
SETD.0 PixelLow
|
||||
LDA.0
|
||||
OUTA 0xE9
|
||||
SETD.0 PixelHigh
|
||||
LDA.0
|
||||
OUTA 0xE9
|
||||
RET
|
||||
|
||||
; ---- Sums ----
|
||||
;
|
||||
; DP0 names the two byte value being changed, low half first, and DP1 the one being added to
|
||||
; it. The carry runs from one half to the other, which is what ADD taking the carry flag is
|
||||
; for, and CCF at the top is what stops the last sum leaking into this one.
|
||||
addWord:
|
||||
CCF
|
||||
LDA.0
|
||||
LDB.1
|
||||
ADD
|
||||
STQ.0
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.0
|
||||
LDB.1
|
||||
ADD
|
||||
STQ.0
|
||||
RET
|
||||
|
||||
subWord:
|
||||
CCF
|
||||
LDA.0
|
||||
LDB.1
|
||||
SUB
|
||||
STQ.0
|
||||
INCD.0
|
||||
INCD.1
|
||||
LDA.0
|
||||
LDB.1
|
||||
SUB
|
||||
STQ.0
|
||||
RET
|
||||
|
||||
; ---- Sixteenths into pixels ----
|
||||
;
|
||||
; DP0 names a two byte value, low half first. A and B are one sixteen bit register and it
|
||||
; ROTATES rather than shifts, so four turns to the right bring the bottom four bits round into
|
||||
; the top - which is why the high byte is masked afterwards and the low byte is not.
|
||||
toPixels:
|
||||
LDB.0
|
||||
INCD.0
|
||||
LDA.0
|
||||
SHR
|
||||
SHR
|
||||
SHR
|
||||
SHR
|
||||
SETD.1 PixelHigh
|
||||
STA.1 ; Kept whole for a moment, because B has to come out of B first.
|
||||
RSTA
|
||||
CCF
|
||||
ADD ; Nothing plus B is B, in Q, which can be moved.
|
||||
MVQA
|
||||
SETD.1 PixelLow
|
||||
STA.1
|
||||
SETD.1 PixelHigh
|
||||
LDA.1
|
||||
INIB 0x0F
|
||||
AND
|
||||
STQ.1
|
||||
RET
|
||||
|
||||
; ---- A pixel column, from a pixel ----
|
||||
;
|
||||
; A IS THE HIGH HALF and B the low, which is the way round the shift register is - it was
|
||||
; written the other way first and the view scrolled by 256 cells for every one it should have.
|
||||
; Three turns right is a divide by eight, and a moon 1024 pixels round is 128 columns, so the
|
||||
; whole answer is in the low half and the high half is the bits that rotated out of it.
|
||||
;
|
||||
; B cannot be read. Adding nothing to it puts it in Q, which can be moved.
|
||||
eighth:
|
||||
SHR
|
||||
SHR
|
||||
SHR
|
||||
RSTA
|
||||
CCF
|
||||
ADD
|
||||
MVQA
|
||||
RET
|
||||
|
||||
waitFrame:
|
||||
INA 0x30
|
||||
INIB 0x01
|
||||
AND
|
||||
BRQ waitFrame
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
#Base 0x3000
|
||||
|
||||
; Sixteenths of a pixel, low half first, because that is the order the sums above walk in.
|
||||
AcrossLow:
|
||||
0x00
|
||||
AcrossHigh:
|
||||
0x0F ; Somewhere near the middle of the moon to start.
|
||||
DownLow:
|
||||
0x00
|
||||
DownHigh:
|
||||
0x02 ; A little way down from the top.
|
||||
SpeedAcross:
|
||||
0x00 0x00
|
||||
SpeedDown:
|
||||
0x00 0x00
|
||||
|
||||
Gravity:
|
||||
0x01 0x00
|
||||
ThrustUp:
|
||||
0xEC 0xFF ; Twenty sixteenths upwards, which is minus twenty.
|
||||
ThrustLeft:
|
||||
0xF8 0xFF ; Eight sixteenths to the left.
|
||||
ThrustRight:
|
||||
0x08 0x00
|
||||
HalfScreen:
|
||||
0xA0 0x00 ; 160 pixels, which is half of a forty column screen.
|
||||
|
||||
PixelLow:
|
||||
0x00
|
||||
PixelHigh:
|
||||
0x00
|
||||
Height:
|
||||
0x00
|
||||
Row:
|
||||
0x00
|
||||
Column:
|
||||
0x00
|
||||
Seed:
|
||||
0x5D
|
||||
KeyHeld:
|
||||
0x00
|
||||
Flying:
|
||||
0x01
|
||||
|
||||
Terrain:
|
||||
#Reserve 0d128
|
||||
|
||||
LanderArt:
|
||||
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
|
||||
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
|
||||
0x00 0x01 0x00 0x00 0x00 0x00 0x01 0x00
|
||||
0x01 0x01 0x00 0x00 0x00 0x00 0x01 0x01
|
||||
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x01
|
||||
|
||||
LanderArtAt:
|
||||
#Reserve 0d2
|
||||
@@ -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. |
|
||||
| Lander | Lunar Porter, rung one: a lander over a moon that wraps. The map's column origin is a ring in hardware, so 128 cells of it is 1024 pixels of surface with no edge and no seam. Position and velocity are sixteen bit in sixteenths of a pixel, which is what makes going all the way round an AND rather than a comparison. |
|
||||
| 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. |
|
||||
|
||||
@@ -25,6 +25,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -41,7 +42,7 @@ outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
crossed.txt 560
|
||||
28 files, 1 directory
|
||||
29 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -15,6 +15,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -30,7 +31,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> drive 1
|
||||
> dir
|
||||
other.txt 28
|
||||
|
||||
@@ -32,6 +32,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -47,7 +48,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -22,6 +22,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -37,7 +38,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -22,6 +22,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -37,7 +38,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -108,6 +108,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -123,7 +124,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -27,6 +27,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -42,7 +43,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -15,6 +15,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -30,7 +31,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> load
|
||||
load what?
|
||||
> load nosuch.sbx
|
||||
|
||||
@@ -13,6 +13,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -28,7 +29,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> load Say.sbx
|
||||
loaded, starting at 5000
|
||||
> run the disk took its time
|
||||
|
||||
@@ -22,6 +22,7 @@ Mode.sbx 48
|
||||
Flip.sbx 173
|
||||
Sprite.sbx 442
|
||||
Depth.sbx 672
|
||||
Lander.sbx 984
|
||||
Crash.sbx 632
|
||||
vars.script 50
|
||||
blocks.script 343
|
||||
@@ -37,7 +38,7 @@ nonl.script 38
|
||||
outer.script 376
|
||||
inner.script 44
|
||||
loop.script 35
|
||||
27 files, 1 directory
|
||||
28 files, 1 directory
|
||||
> exit
|
||||
halted
|
||||
Execution halted.
|
||||
|
||||
@@ -144,6 +144,11 @@ 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/Depth.asm" -o "$WORK/Depth.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Depth.sbx" >/dev/null
|
||||
# Lander.sbx is Lunar Porter, and the biggest program on the disk. It takes the screen, the
|
||||
# mode and the map, so it is also the one that says a program can have all three back.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
"$ROOT/Programs/CosmOS/Apps/Lander.asm" -o "$WORK/Lander.sbx" >/dev/null
|
||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" >/dev/null
|
||||
# Crash.sbx breaks in each of the four ways the system now catches, which is the only way to
|
||||
# reach a fault screen from a recorded test.
|
||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||
|
||||
@@ -1519,6 +1519,49 @@ EOT
|
||||
&& result ok "and the near pillar hides the middle" "sprite nought, behind sprite one" \
|
||||
|| result no "and the near pillar hides the middle" "$BALLOVER ball pixels across a pillar of $PILLAR"
|
||||
|
||||
# ---- Lunar Porter, flying ----
|
||||
#
|
||||
# The biggest program on the disk, and the one that uses the most of the machine at once: it
|
||||
# takes the screen, changes the mode, redefines tiles, fills all 3,200 cells of the map from a
|
||||
# terrain it generated, and moves a sprite over it every frame.
|
||||
#
|
||||
# The keys are a burn of the lifting thruster every sixteenth frame, which is more than the
|
||||
# gravity and so keeps the lander up long enough to be looked at. The cycle count is tuned to
|
||||
# catch it before it climbs off the top; if CosmOS's size shifts the boot far enough, the
|
||||
# lander check fails saying it is not on screen, which is a request to re-tune.
|
||||
python3 -c "
|
||||
keys = b'Lander\n'
|
||||
for i in range(400):
|
||||
keys += b'\x80' + b'\x00' * 15
|
||||
open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 4000)
|
||||
"
|
||||
timeout 30 "$EMU" --fast --cycles 12000000 --keyboard "$BUILD/lander.keys" \
|
||||
--screen "$BUILD/lander.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/lander.out" 2>&1 || true
|
||||
read -r MOON SKY SHIP SHIPLEFT SHIPRIGHT <<EOT
|
||||
$(python3 -c "
|
||||
d = open('$BUILD/lander.ppm', 'rb').read()
|
||||
px = d[d.index(b'255\n') + 4:]
|
||||
w = 320
|
||||
moon, ship = bytes.fromhex('d8d8d8'), bytes.fromhex('d8c048')
|
||||
at = [px[o:o + 3] for o in range(0, len(px), 3)]
|
||||
shipAt = [i % w for i, c in enumerate(at) if c == ship]
|
||||
print(sum(1 for c in at if c == moon), sum(1 for c in at if c == bytes(3)), len(shipAt),
|
||||
min(shipAt) if shipAt else -1, max(shipAt) if shipAt else -1)
|
||||
" 2>/dev/null || echo "0 0 0 -1 -1")
|
||||
EOT
|
||||
# A moon that filled the screen or left it empty would be a terrain generator that had run
|
||||
# off one end of its clamp, which is the way that kind of loop usually fails.
|
||||
[ "$MOON" -gt 10000 ] && [ "$SKY" -gt 10000 ] \
|
||||
&& result ok "a moon with a sky over it" "$MOON pixels of ground and $SKY of sky" \
|
||||
|| result no "a moon with a sky over it" "$MOON ground, $SKY sky"
|
||||
# It never moves sideways: the world scrolls under it, so it is at the middle of a 320 pixel
|
||||
# screen every frame of its life. Forty pixels is the whole of the shape, so none of it has
|
||||
# been clipped or drawn twice.
|
||||
[ "$SHIP" = "40" ] && [ "$SHIPLEFT" = "156" ] && [ "$SHIPRIGHT" = "163" ] \
|
||||
&& result ok "and a lander in the middle of it" "all forty pixels, at the screen's centre" \
|
||||
|| result no "and a lander in the middle of it" "$SHIP pixels, x $SHIPLEFT to $SHIPRIGHT"
|
||||
|
||||
# ---- 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
|
||||
|
||||
Reference in New Issue
Block a user