Two halves of the same number. The warning is the moment it happened and the colour is how things stand: it fires on the way down through a quarter of a tank and then holds its peace, and the gauge stays red until a base fills the lander up, which also allows the warning again. Once, because a lander is at its most careful in the last few seconds before it touches, and something repeating in its ear through that is not a warning, it is a distraction. Checked where the fuel actually moves rather than once a frame - the tank only changes in takeFuel and payFuel, so there is nowhere else it can cross a threshold. Channel two, with the crash on three. Separate channels rather than one reused, because a crash while the warning is still sounding should not cut it off, and on a machine with four voices there is no reason to be clever. Half and a tenth are the obvious next thresholds and are deliberately not here: one is enough to find out whether being told at all is welcome. ---- And the crash sound is the one that was designed for it ---- Crash.json, which carries voice_gate itself, so the program's own trigger write is gone - it would have masked a deliberate choice rather than backing one up. The note moved from 24 to 60 by ear, and the comment saying it was low went with it. ---- A check that could not tell a beep from a nag ---- Counting bursts of sound cannot: without the latch the warning fires every tick the tank drops, far faster than the sound decays, so the beeps run into each other and a burst counter sees one long burst either way. It measures the LENGTH now - 26,944 samples against 490,348 - which is the difference between six tenths of a second and ten seconds of it.
4162 lines
112 KiB
NASM
4162 lines
112 KiB
NASM
; 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
|
|
|
|
CALL loadState ; Before the view, because a state may say which view to be in.
|
|
CALL setView ; The block every gauge below reads its screen numbers out of.
|
|
SETD.0 Wide
|
|
LDA.0
|
|
OUTA 0x31 ; Forty columns to start in. B swaps to eighty and back.
|
|
|
|
; ---- And the view put back to the top of the map ----
|
|
;
|
|
; THE SHELL SCROLLS. Its row origin is wherever the last command left it, and the map is a
|
|
; ring 128 rows tall that the screen shows 25 of - so a moon drawn into rows nought to 24
|
|
; while the screen is looking at row forty is a moon nobody can see. It came out as terrain
|
|
; that was missing, or half there, depending on how far down the prompt had got.
|
|
;
|
|
; Nothing here is being tidy: this is the difference between the rows this program WRITES
|
|
; and the rows the screen READS, and only one of them is under its control.
|
|
;
|
|
; The column origin and the fine offsets are set every frame by follow, so they need no
|
|
; help. The row origin is set once, here, because nothing scrolls vertically after this.
|
|
SETD.0 ScrollRow
|
|
LDA.0
|
|
OUTA 0x34
|
|
RSTA
|
|
OUTA 0x38 ; No fraction of a cell downwards either.
|
|
|
|
CALL mulReady ; The quarter square table, once. The orbit below needs a product.
|
|
CALL putTiles
|
|
CALL makeMoon
|
|
CALL carvePads
|
|
CALL drawMoon
|
|
CALL putLander
|
|
CALL putGauge
|
|
CALL putOrbital
|
|
CALL putBoom ; The instruments, once, the way the tiles are.
|
|
CALL putAlarm
|
|
|
|
INIA 0x01
|
|
OUTA 0x02 ; Key mode.
|
|
|
|
; ---- Is there a controller ----
|
|
;
|
|
; Asked once, and about all four. If there is one anywhere, the console's arrow keys are
|
|
; ignored: under a window the same keypress reaches BOTH - the pad as a level and the
|
|
; console as a byte - and a thruster that fired twice for one press would be a mystery to
|
|
; anybody tuning it.
|
|
INA 0x64
|
|
SETD.1 HasPad
|
|
STA.1 ; ANY bit, so any of the four counts as having one.
|
|
|
|
everyFrame:
|
|
CALL waitFrame
|
|
CALL readPad
|
|
CALL readControls
|
|
CALL zoomButton
|
|
CALL fall
|
|
CALL move
|
|
CALL moveStation
|
|
CALL follow
|
|
CALL touchdown
|
|
CALL dock
|
|
CALL showLander
|
|
CALL showFlames
|
|
CALL showStation
|
|
CALL showDrift
|
|
CALL showFall
|
|
CALL showFuel
|
|
CALL showHeight
|
|
CALL burstTick ; Whatever is in the air, one frame on.
|
|
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.
|
|
|
|
OUTA 0xE0 ; Bank one again, A still holding the one the blit above wanted.
|
|
SETD.1 StationArtAt
|
|
SETD.0 StationArt
|
|
STD.0.1
|
|
LDA.1
|
|
OUTA 0xE1
|
|
INCD.1
|
|
LDA.1
|
|
OUTA 0xE2
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0x32
|
|
OUTA 0xE4
|
|
INIA 0x80
|
|
OUTA 0xE5 ; Tile 202, one tile on again.
|
|
RSTA
|
|
OUTA 0xE6
|
|
INIA 0x40
|
|
OUTA 0xE7
|
|
INIA 0x01
|
|
OUTA 0xE8 ; Blit.
|
|
|
|
OUTA 0xE0
|
|
SETD.1 FlameDownArtAt
|
|
SETD.0 FlameDownArt
|
|
STD.0.1
|
|
LDA.1
|
|
OUTA 0xE1
|
|
INCD.1
|
|
LDA.1
|
|
OUTA 0xE2
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0x32
|
|
OUTA 0xE4
|
|
INIA 0xC0
|
|
OUTA 0xE5 ; Tile 203, the plume that hangs under a lifting lander.
|
|
RSTA
|
|
OUTA 0xE6
|
|
INIA 0x40
|
|
OUTA 0xE7
|
|
INIA 0x01
|
|
OUTA 0xE8
|
|
|
|
OUTA 0xE0
|
|
SETD.1 FlameSideArtAt
|
|
SETD.0 FlameSideArt
|
|
STD.0.1
|
|
LDA.1
|
|
OUTA 0xE1
|
|
INCD.1
|
|
LDA.1
|
|
OUTA 0xE2
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0x33
|
|
OUTA 0xE4
|
|
RSTA
|
|
OUTA 0xE5 ; Tile 204, the same thing lying on its side.
|
|
OUTA 0xE6
|
|
INIA 0x40
|
|
OUTA 0xE7
|
|
INIA 0x01
|
|
OUTA 0xE8
|
|
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
|
|
|
|
; ---- Somewhere to put it down ----
|
|
;
|
|
; A random walk does not leave flat ground, and a lander wants some. So four pads are CARVED
|
|
; after the moon is made rather than looked for in it: searching can fail, and a fallback that
|
|
; carves anyway is the carving plus a search nobody needed.
|
|
;
|
|
; Each is four columns levelled to the height the first of them happened to have, so the pads
|
|
; sit in the landscape rather than on a shelf above it - the moon decides where they are, and
|
|
; this only decides that they are flat.
|
|
;
|
|
; The columns are marked in an array of their own. Asking "is this a pad" while drawing has to
|
|
; be one lookup: four comparisons per column per row is 12,800 of them for a screen.
|
|
carvePads:
|
|
SETD.3 PadTable
|
|
INIA 0d4
|
|
SETD.1 PadsLeft
|
|
STA.1
|
|
|
|
carveOne:
|
|
; The height this pad is levelled to, which is whatever its first column already was.
|
|
LDA.3
|
|
SETD.1 Terrain
|
|
DPUA.1
|
|
LDA.1
|
|
SETD.1 PadHeight
|
|
STA.1
|
|
|
|
RSTA
|
|
SETD.1 PadWide
|
|
STA.1
|
|
carveColumn:
|
|
; This column of the pad: levelled, and marked as somewhere to land.
|
|
LDA.3
|
|
SETD.1 PadWide
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
MVQA ; The pad's first column plus how far along this is.
|
|
PSHA
|
|
SETD.1 Terrain
|
|
DPUA.1
|
|
SETD.0 PadHeight
|
|
LDA.0
|
|
STA.1
|
|
POPA
|
|
SETD.1 IsPad
|
|
DPUA.1
|
|
; ---- WHICH base, and not merely that there is one ----
|
|
;
|
|
; The number plus one, so nought still means no pad and the array is still one lookup. A
|
|
; flag would have to be followed by "and which of the four", which is the same walk done
|
|
; twice for an answer that was already in hand.
|
|
INIA 0d4
|
|
SETD.0 PadsLeft
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
MVQA ; Four less what is left to carve, which counts up from nought.
|
|
INCA
|
|
STA.1
|
|
|
|
SETD.1 PadWide
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
INIB 0d8 ; ---- Eight columns, not four ----
|
|
;
|
|
; Four was 32 pixels of pad in a moon 1024 round, which is a
|
|
; target somebody flying by feel misses over and over. Eight is
|
|
; still small and is somewhere a person can aim at.
|
|
CCF
|
|
SUB
|
|
BNQ carveColumn
|
|
|
|
INCD.3
|
|
SETD.1 PadsLeft
|
|
LDA.1
|
|
DECA
|
|
STA.1
|
|
BNA carveOne
|
|
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.
|
|
; ---- Drawn from twenty four rows above the world, not from the world's top ----
|
|
;
|
|
; The eighty column screen is fifty rows and the flyable band is thirty: the ceiling is eight
|
|
; rows above the origin and the deepest valley is twenty two below it. So the extra twenty
|
|
; rows a zoom buys have to go SOMEWHERE, and the first version gave them all to rock - it
|
|
; drew fifty rows down from the origin, which put the same sky on the screen as before with
|
|
; twice as much moon under it. Measured: 47 per cent moon zoomed in and 71 per cent zoomed
|
|
; out, which is a zoom that shows you less of what you are flying in.
|
|
;
|
|
; They go above instead. The rows run from minus twenty four to twenty five, which is the
|
|
; whole band with the ground near the bottom of it, and negative rows are sky by definition
|
|
; because nothing has a surface up there. In forty columns the ones above the origin are not
|
|
; on the screen at all, and drawing them anyway costs a startup and clears whatever the shell
|
|
; left in them.
|
|
drawMoon:
|
|
INIA 0xE8 ; Minus twenty four.
|
|
SETD.1 Row
|
|
STA.1
|
|
drawRow:
|
|
INIA 0d5
|
|
OUTA 0xE3
|
|
SETD.1 Row
|
|
LDA.1
|
|
INIB 0x7F
|
|
AND
|
|
MVQA ; Round the ring of 128, so minus twenty four is row 104.
|
|
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:
|
|
; Above the world's origin, where no column has a surface, so there is nothing to compare.
|
|
SETD.1 Row
|
|
LDA.1
|
|
INIB 0x80
|
|
AND
|
|
BNQ drawSky
|
|
|
|
; ---- 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.
|
|
LDA.1
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRC drawSky ; Borrowed: this row is above the surface, so it is sky.
|
|
BRQ drawSurface ; Equal: this row IS the surface, which may be somewhere to land.
|
|
drawGround:
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; Tile 200, the ground block.
|
|
RSTA
|
|
OUTA 0xE9 ; Attribute nought: grey on black, which is a fine moon.
|
|
BRI drawNextCell
|
|
|
|
; ---- The top of a pad, in a colour that says so ----
|
|
;
|
|
; The same solid tile in a different scheme, which costs no art at all: an attribute is a
|
|
; nibble added to every index in the tile, so one block is a grey moon and a cyan landing pad
|
|
; depending on the byte beside it.
|
|
drawSurface:
|
|
SETD.1 IsPad
|
|
SETD.0 Column
|
|
LDA.0
|
|
DPUA.1
|
|
LDA.1
|
|
BRA drawGround ; Not a pad, so it is ordinary moon.
|
|
DECA ; Back to the base's own number.
|
|
SETD.1 PadColour
|
|
DPUA.1
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block again.
|
|
LDA.1
|
|
OUTA 0xE9 ; And this base's scheme, which is the whole of its name.
|
|
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 0d26
|
|
CCF
|
|
SUB
|
|
BNQ drawRow ; Fifty rows, from minus twenty four up to and including 25.
|
|
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.
|
|
SETD.0 ShipX
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; X: the middle of the screen, less half a tile. Never moves.
|
|
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 gauge, which lives where the moon cannot scroll it away ----
|
|
;
|
|
; The window is a layer at a SCREEN position rather than a position in the world, so a bar
|
|
; drawn in it stays where it is put however far the moon turns underneath. One row, at the
|
|
; top, and the label written once because it never changes.
|
|
;
|
|
; The letters are ordinary tiles. The character generator starts at the space, so glyph n is
|
|
; character n plus thirty two - which makes F, U, E and L into 38, 53, 37 and 44.
|
|
putGauge:
|
|
INIA 0x02
|
|
OUTA 0x3D ; Two rows: the gauge, and whatever there is to say.
|
|
RSTA
|
|
OUTA 0x3E ; At the top of the screen.
|
|
|
|
INIA 0d5
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
RSTA
|
|
OUTA 0xE5 ; The window begins at 0xC000 of the screen bank.
|
|
INIA 0d38
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; F
|
|
INIA 0d53
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; U
|
|
INIA 0d37
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; E
|
|
INIA 0d44
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; L
|
|
RET
|
|
|
|
; ---- And how much of it is left ----
|
|
;
|
|
; A byte of fuel makes a bar of up to 31 cells, which is the byte over eight: a shift, because
|
|
; there is no divide. Thirty five cells of room after the label, so a full tank does not quite
|
|
; fill the row and there is somewhere for it to be seen to stop.
|
|
;
|
|
; Redrawn whole every frame. Thirty five cells is seventy bytes out of one port with the
|
|
; address named once, which is cheaper than working out which of them changed.
|
|
showFuel:
|
|
; ---- Green above a quarter, red below it ----
|
|
;
|
|
; The same number the alarm goes off at, so the sound and the picture agree. The sound is
|
|
; said once and this stays said, which is the division of labour: one of them is the moment
|
|
; it happened and the other is how things stand.
|
|
SETD.1 Fuel
|
|
LDA.1
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
BNC showFuelGreen
|
|
INIA 0x01
|
|
BRI showFuelColoured
|
|
showFuelGreen:
|
|
INIA 0x02
|
|
showFuelColoured:
|
|
SETD.1 GaugeColour
|
|
STA.1
|
|
|
|
RSTA
|
|
SETD.1 Fuel
|
|
LDB.1
|
|
CALL eighth ; Q is the fuel over eight, in the low half where it belongs.
|
|
MVQA
|
|
SETD.1 GaugeLeft
|
|
STA.1
|
|
|
|
INIA 0d5
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x0A
|
|
OUTA 0xE5 ; Cell five of the window row, just past the label.
|
|
RSTA
|
|
SETD.1 GaugeAt
|
|
STA.1
|
|
|
|
gaugeCell:
|
|
; Solid while there is bar left to draw, blank after it.
|
|
SETD.1 GaugeLeft
|
|
LDA.1
|
|
BRA gaugeEmpty
|
|
DECA
|
|
STA.1
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block, which is the ground's tile.
|
|
SETD.1 GaugeColour
|
|
LDA.1
|
|
OUTA 0xE9 ; Green, or red once a quarter is all that is left.
|
|
BRI gaugeNext
|
|
gaugeEmpty:
|
|
OUTA 0xE9
|
|
OUTA 0xE9 ; A is nought here: no tile and no colour.
|
|
gaugeNext:
|
|
SETD.1 GaugeAt
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
INIB 0d35
|
|
CCF
|
|
SUB
|
|
BNQ gaugeCell
|
|
RET
|
|
|
|
; ---- What the pad is holding ----
|
|
;
|
|
; One read, every button at once, and it does not go away when it is looked at. THIS IS THE
|
|
; THING THE CONSOLE CANNOT DO: a key that is down and staying down sends nothing, so a
|
|
; thruster driven by the console can only be pumped and never leaned on.
|
|
; ---- ANY of the four, not pad nought ----
|
|
;
|
|
; One person flies this, and which socket they plugged into is not a thing they should have
|
|
; to know. A controller does not always land on nought - the front end hands out the numbers
|
|
; the host gave it - so a game that reads only the first one works on some machines and
|
|
; silently does nothing on others, which is the worst of both.
|
|
;
|
|
; Four reads and three ORs. A port is an immediate byte inside the instruction that names it,
|
|
; so it cannot be computed and the four are written out.
|
|
readPad:
|
|
INA 0x60
|
|
SETD.1 Held
|
|
STA.1
|
|
INA 0x61
|
|
LDB.1
|
|
OR
|
|
STQ.1
|
|
INA 0x62
|
|
LDB.1
|
|
OR
|
|
STQ.1
|
|
INA 0x63
|
|
LDB.1
|
|
OR
|
|
STQ.1
|
|
RET
|
|
|
|
; ---- The console, which is still worth reading ----
|
|
;
|
|
; For q, always, because a pad has no letter for it. And for the arrows on a machine with no
|
|
; controller, where one press is one burn and that is the best that can be done - it reads as
|
|
; pumping the engine, which is a thing this machine makes true rather than a thing this
|
|
; program chose.
|
|
zoomKeyed:
|
|
CALL toggleView
|
|
RET
|
|
|
|
readControls:
|
|
INA 0x01
|
|
INIB 0x01 ; READY
|
|
AND
|
|
BRQ noKey
|
|
INA 0x00
|
|
|
|
SETD.1 KeyHeld
|
|
STA.1
|
|
|
|
INIB 0x71 ; q, whatever else is plugged in.
|
|
CCF
|
|
SUB
|
|
BRQ quit
|
|
|
|
; ---- And z swaps the zoom, pad or no pad ----
|
|
;
|
|
; ABOVE the test for a controller, unlike the arrows. Which way the lander is flown is a
|
|
; question the arrows have to ask, because a pad answers it better; how much of the moon is
|
|
; on the screen is not - somebody flying on a controller may still have a keyboard in front
|
|
; of them, and somebody without one would otherwise have no way to zoom at all.
|
|
;
|
|
; No edge to worry about here. The console delivers a key ONCE, which is the difference
|
|
; between a key and a button and the reason the pad below needs remembering and this does
|
|
; not.
|
|
LDA.1
|
|
INIB 0x7A ; z.
|
|
CCF
|
|
SUB
|
|
BRQ zoomKeyed
|
|
|
|
SETD.0 HasPad
|
|
LDA.0
|
|
BNA noKey ; There is a pad, so the arrows are its business and not this.
|
|
|
|
LDA.1 ; DP1 is still KeyHeld, from storing the key above.
|
|
INIB 0x80 ; Up
|
|
CCF
|
|
SUB
|
|
BRQ burnUp
|
|
LDA.1
|
|
INIB 0x81 ; Down
|
|
CCF
|
|
SUB
|
|
BRQ burnDown
|
|
LDA.1
|
|
INIB 0x82 ; Left
|
|
CCF
|
|
SUB
|
|
BRQ burnLeft
|
|
LDA.1
|
|
INIB 0x83 ; Right
|
|
CCF
|
|
SUB
|
|
BRQ burnRight
|
|
noKey:
|
|
RET
|
|
|
|
quit:
|
|
RSTA
|
|
SETD.1 Flying
|
|
STA.1
|
|
RET
|
|
|
|
burnUp:
|
|
SETD.0 SpeedDown
|
|
SETD.1 ThrustUp
|
|
CALL addWord
|
|
RET
|
|
burnDown:
|
|
; The same rule the pad's retro thruster has: nothing to push away from on the ground.
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BNA burnDownDone
|
|
SETD.0 SpeedDown
|
|
SETD.1 ThrustDown
|
|
CALL addWord
|
|
burnDownDone:
|
|
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 reason this is a game ----
|
|
;
|
|
; NOT EVERY FRAME. One sixteenth of a pixel per frame per frame is the smallest step this
|
|
; arithmetic can take and it is still far too much - it crossed the screen in a second and
|
|
; flew like a gas giant. So it is applied one frame in FallEvery, which divides the pull by
|
|
; that much and costs a byte and a compare.
|
|
;
|
|
; The alternative was a finer unit for velocity than for position, which means a shift every
|
|
; time one is added to the other, twice a frame, for ever. A counter is cheaper and it is one
|
|
; byte to change while the feel is being found.
|
|
fall:
|
|
SETD.1 FallTick
|
|
LDA.1
|
|
DECA
|
|
STA.1
|
|
BNA fallDone
|
|
SETD.1 FallEvery
|
|
LDA.1
|
|
SETD.1 FallTick
|
|
STA.1
|
|
; Gravity does not pull on a lander that is sitting on the ground or holding onto something.
|
|
SETD.0 Docked
|
|
LDA.0
|
|
BNA fallResting
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BNA fallResting
|
|
|
|
CALL exchange
|
|
|
|
; ---- Orbit, which is gravity MINUS the swing outwards ----
|
|
;
|
|
; The first version of this only ever SUBTRACTED from gravity, and that is why it never
|
|
; behaved like an orbit. Cancelling some of the pull can stop a fall from getting worse; it
|
|
; can never turn one round, because nothing in it ever pushes up. A lander a little too slow
|
|
; sank for ever and a lander a little too fast rose for ever, and both of them were right.
|
|
;
|
|
; What actually happens is a difference between two things. Gravity pulls in and going round
|
|
; throws out, and which of them wins depends on the sideways speed:
|
|
;
|
|
; below orbital speed the pull wins and the lander falls
|
|
; at orbital speed they cancel and the lander circles
|
|
; above orbital speed the swing wins and the lander climbs
|
|
;
|
|
; That sign change is the whole mechanic. Falling buys sideways speed (see the exchange
|
|
; above), so a fall carries the lander past orbital speed and the swing turns it into a
|
|
; climb: a periapse. Climbing spends that speed back, drops it below orbital, and the pull
|
|
; turns the climb into a fall: an apoapse. Round and round, which is what an orbit is.
|
|
;
|
|
; ---- A gradient out of integer arithmetic ----
|
|
;
|
|
; The step is one sixteenth of a pixel a tick and there is nothing between that and nothing,
|
|
; so the strength cannot be scaled down directly. Instead the distance from orbital speed is
|
|
; added into a byte every tick and the step is taken only when that byte carries - so the
|
|
; fraction of ticks that act is the distance over 256, smoothly, with no divide.
|
|
;
|
|
; Times four, because the speed alone put orbit at a lateral speed no lander would ever
|
|
; reach. At four it lands on 64 sixteenths - four pixels a frame, which crosses the moon in
|
|
; about three seconds and takes ten seconds of holding a thruster to build. Something worked
|
|
; up to rather than stumbled into.
|
|
SETD.0 SpeedAcross
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA fallOutFull ; A whole high byte of it: far past orbital, and thrown outwards.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
BRC fallInward ; Borrowed, so it is short of orbital speed and the pull wins.
|
|
|
|
; ---- Above orbital speed, so the swing wins ----
|
|
MVQA ; Q is how far past, from the comparison just above.
|
|
CCF
|
|
SUB ; B is still 64, from that same comparison.
|
|
BNC fallOutFull
|
|
RSTB
|
|
SHL
|
|
SHL ; Times four; A is the high half, so the answer comes out in A.
|
|
BRI fallOutStep
|
|
fallOutFull:
|
|
INIA 0xFF
|
|
fallOutStep:
|
|
SETD.1 OrbitStep
|
|
STA.1
|
|
INIA 0x01
|
|
SETD.1 Outward
|
|
STA.1
|
|
BRI fallSwing
|
|
|
|
; ---- Below orbital speed, so the pull wins ----
|
|
fallInward:
|
|
MVQA ; Q is the shortfall, less its sign.
|
|
NOTA
|
|
MVQA
|
|
INCA
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
BNC fallInFull
|
|
RSTB
|
|
SHL
|
|
SHL
|
|
BRI fallInStep
|
|
fallInFull:
|
|
INIA 0xFF ; Standing still, which is the whole of gravity and no swing.
|
|
fallInStep:
|
|
SETD.1 OrbitStep
|
|
STA.1
|
|
RSTA
|
|
SETD.1 Outward
|
|
STA.1
|
|
|
|
fallSwing:
|
|
SETD.0 OrbitStep
|
|
LDA.0
|
|
SETD.1 OrbitAt
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
STQ.1
|
|
BNC fallResting ; No carry, so this tick is one of the ones that does nothing.
|
|
SETD.0 Outward
|
|
LDA.0
|
|
BNA fallPushed
|
|
SETD.0 SpeedDown
|
|
SETD.1 Gravity
|
|
CALL addWord
|
|
BRI fallResting
|
|
fallPushed:
|
|
SETD.0 SpeedDown
|
|
SETD.1 Lift
|
|
CALL addWord
|
|
fallResting:
|
|
|
|
; ---- And whatever is being leaned on, on the same tick ----
|
|
;
|
|
; A held thruster fires here rather than every frame, for the reason gravity does: a
|
|
; sixteenth of a pixel is the smallest step this arithmetic takes, and applied sixty times a
|
|
; second it is an enormous acceleration. On the tick, thrust and gravity are two numbers
|
|
; whose RATIO is the whole feel of the thing, and both are one byte to change.
|
|
;
|
|
; Position still moves every frame. Only the acceleration is stepped, which nothing can see.
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x08 ; Up
|
|
AND
|
|
BRQ fallNotUp
|
|
CALL takeFuel
|
|
BRQ fallNotUp ; Nothing in the tank, so nothing out of the engine.
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BRA fallAlreadyUp
|
|
RSTA
|
|
STA.0 ; Lifting off, which is the only way to stop being landed.
|
|
SETD.0 EmptyText
|
|
CALL sayInWindow ; And whatever the base said goes with it.
|
|
fallAlreadyUp:
|
|
SETD.0 SpeedDown
|
|
SETD.1 PadUp
|
|
CALL addWord
|
|
fallNotUp:
|
|
; ---- And down, at half the strength of up ----
|
|
;
|
|
; Arresting a rise took a sideways burn and a wait for the orbit to come back round, which is
|
|
; how a rendezvous is really flown and is a lot to ask of somebody who has not flown one
|
|
; before. A retro thruster makes the same correction directly.
|
|
;
|
|
; HALF THE STRENGTH, deliberately. Up is two sixteenths a tick and this is one, which is the
|
|
; same as gravity - so it can stop a climb and it can hurry a descent, and it can never turn
|
|
; a landing approach into a crash faster than simply letting go would. The cheap way out of
|
|
; a mistake stays the expensive one.
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BNA fallNotDown ; Sitting on the ground, where there is no down to go.
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x04 ; Down
|
|
AND
|
|
BRQ fallNotDown
|
|
CALL takeFuel
|
|
BRQ fallNotDown ; Nothing in the tank, so nothing out of the engine.
|
|
SETD.0 SpeedDown
|
|
SETD.1 PadDown
|
|
CALL addWord
|
|
fallNotDown:
|
|
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x02 ; Left
|
|
AND
|
|
BRQ fallNotLeft
|
|
CALL takeFuel
|
|
BRQ fallNotLeft
|
|
SETD.0 SpeedAcross
|
|
SETD.1 PadLeft
|
|
CALL addWord
|
|
fallNotLeft:
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x01 ; Right
|
|
AND
|
|
BRQ fallDone
|
|
CALL takeFuel
|
|
BRQ fallDone
|
|
SETD.0 SpeedAcross
|
|
SETD.1 PadRight
|
|
CALL addWord
|
|
fallDone:
|
|
RET
|
|
|
|
; ---- Falling buys sideways speed; climbing spends it ----
|
|
;
|
|
; What the first orbit was missing. Cancelling gravity by speed lets a fast lander stop
|
|
; falling and gives it no way back: a little too slow and it sank for ever, faster and faster,
|
|
; because ALTITUDE DID NOTHING. Nothing turned height into speed or speed into height.
|
|
;
|
|
; A real orbit is that exchange. Falling towards periapse trades height for speed, which buys
|
|
; more relief, which turns the fall into a climb; climbing towards apoapse pays it back and
|
|
; the climb becomes a fall. That cycle is the whole character of an orbit.
|
|
;
|
|
; ---- And the rate is the PRODUCT, which is why this needed a multiply ----
|
|
;
|
|
; The first attempt traded at a rate set by the vertical speed alone, and it drained a climb
|
|
; all the way to nought: a lander that climbed long enough had no sideways speed left to buy
|
|
; relief with, and sank. Gating it above a minimum made it worse - the climb spent its way
|
|
; down to the gate and locked itself out, frozen there for ever.
|
|
;
|
|
; The honest rate is proportional to BOTH speeds, and that is what fixes it: as the sideways
|
|
; speed falls towards nought the trade stops by itself. The product is its own gate, and no
|
|
; threshold is needed or wanted.
|
|
exchange:
|
|
SETD.0 SpeedAcross
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA exchangeQuick ; A whole high byte of sideways speed: take the largest step.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 MulA
|
|
STA.1
|
|
SETD.0 DriftLeftward
|
|
LDA.0
|
|
SETD.1 AcrossWasLeft
|
|
STA.1 ; Which way it is going, before the next magnitude overwrites it.
|
|
|
|
SETD.0 SpeedDown
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA exchangeQuick
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 MulB
|
|
STA.1
|
|
|
|
CALL mul8
|
|
|
|
; ---- Half the product ----
|
|
;
|
|
; The product reaches 65,025 and the accumulator is a byte, so what is wanted is a window
|
|
; into the middle of it. The high half alone was tried first and it was both too weak and
|
|
; too coarse: a lander at 80 sideways falling at 2 has a product of 160, whose high half is
|
|
; nought, so the trade did not happen at all until the fall was already fast. A dead patch
|
|
; right where the turn is supposed to begin.
|
|
;
|
|
; Half keeps the low bits, so the trade starts as soon as there is any fall at all, and it
|
|
; is eight times stronger besides. That is what closes the loop inside the height of the
|
|
; screen rather than somewhere off the top of it: the whole sky here is about 145 pixels
|
|
; between the ground and the ceiling, and a quarter - tried in between - still ran the
|
|
; lander into the roof before the turn came round.
|
|
SETD.0 MulHigh
|
|
LDA.0
|
|
INIB 0d02
|
|
CCF
|
|
SUB
|
|
BNC exchangeQuick ; 512 or more, which saturates the step whatever the low half.
|
|
RSTB
|
|
SHL
|
|
SHL
|
|
SHL
|
|
SHL
|
|
SHL
|
|
SHL
|
|
SHL ; Times a hundred and twenty eight, which comes out in A.
|
|
SETD.1 ExchangeStep
|
|
STA.1
|
|
SETD.0 MulLow
|
|
LDA.0
|
|
RSTB
|
|
SHR ; And the low half halved, which is the rest of the window.
|
|
LDB.1 ; DP1 still names the step, from the store just above.
|
|
CCF
|
|
ADD ; At most 128 plus 127, so this cannot carry.
|
|
MVQA
|
|
BRI exchangeAdd
|
|
exchangeQuick:
|
|
INIA 0xFF
|
|
|
|
exchangeAdd:
|
|
SETD.1 ExchangeAt
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
STQ.1
|
|
BNC exchangeDone ; No carry, so there is nothing to trade this tick.
|
|
|
|
; ---- Which way the step goes ----
|
|
;
|
|
; Falling grows the sideways speed and climbing shrinks it, which is a step AWAY from nought
|
|
; or TOWARDS it depending on which way the lander is already going. Both signs are the top
|
|
; bit a magnitude left behind, so the exclusive or of them is the whole decision: the same
|
|
; means grow, different means shrink.
|
|
SETD.0 DriftLeftward
|
|
LDA.0
|
|
SETD.1 AcrossWasLeft
|
|
LDB.1
|
|
XOR
|
|
BNQ exchangeSlower
|
|
SETD.0 SpeedAcross
|
|
SETD.1 StepUp
|
|
CALL addWord
|
|
RET
|
|
exchangeSlower:
|
|
SETD.0 SpeedAcross
|
|
SETD.1 StepDown
|
|
CALL addWord
|
|
exchangeDone:
|
|
RET
|
|
|
|
; One sideways, towards standing still, whichever way it is going.
|
|
spendAcross:
|
|
SETD.0 SpeedAcross
|
|
CALL magnitude
|
|
SETD.0 DriftLeftward
|
|
LDA.0
|
|
BNA spendAcrossLeft
|
|
SETD.0 SpeedAcross
|
|
SETD.1 StepDown
|
|
CALL addWord
|
|
RET
|
|
spendAcrossLeft:
|
|
SETD.0 SpeedAcross
|
|
SETD.1 StepUp
|
|
CALL addWord
|
|
RET
|
|
|
|
; ---- One unit, if there is one ----
|
|
;
|
|
; Q comes back nought when the tank is empty, and the thruster that asked does not fire. There
|
|
; is no message and no ending: a lander with no fuel is still flying, it just cannot do
|
|
; anything about where. What happens next is gravity, and gravity is patient.
|
|
;
|
|
; Charged PER THRUSTER PER TICK, so holding two at once costs two - which is the honest price
|
|
; and makes a drift you corrected expensive in a way a drift you avoided is not.
|
|
takeFuel:
|
|
SETD.1 Fuel
|
|
LDA.1
|
|
BRA takeFuelNone
|
|
DECA
|
|
STA.1
|
|
CALL watchFuel ; Checked where the tank actually moves, not once a frame.
|
|
INIA 0x01
|
|
RSTB
|
|
CCF
|
|
ADD ; Q is one, which the caller reads as yes.
|
|
RET
|
|
takeFuelNone:
|
|
RSTB
|
|
CCF
|
|
ADD ; A is nought here, so Q is too, which is no.
|
|
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
|
|
|
|
; ---- The world has no lid, but a sixteen bit height does ----
|
|
;
|
|
; Climbing makes the height count down past nought and round to 65535, and a lander that
|
|
; kept going came back through the bottom and hit the ground FROM ABOVE. Two thousand pixels
|
|
; of climb is a long way and entirely reachable with a full tank.
|
|
;
|
|
; ---- And it is 192 pixels up, not 64, because the screen grew ----
|
|
;
|
|
; Sixty four was the whole of the sky a forty column screen had over the world's origin, so
|
|
; it was never a fact about the world - it was a fact about the view. The wide one starts
|
|
; twenty four rows higher, and a lander stopped at the old line was stopped a long way short
|
|
; of the top of its own picture for no reason it could see.
|
|
;
|
|
; Pinned rather than ended. Leaving upward is RECOVERABLE - gravity is always available and
|
|
; a lander with fuel can always come back - so stopping the run there would punish a state
|
|
; the player can fly out of. What kills you out here is running dry a long way from the
|
|
; ground, which is a death somebody flew into rather than one a boundary handed them.
|
|
;
|
|
; TWO DIFFERENT LINES, and they have to be. The warning covers being AT the ceiling or above
|
|
; it, or a pinned lander reads as back inside the world the next frame and the warning is
|
|
; written and wiped sixty times a second. The pin covers being STRICTLY above it, or the
|
|
; height is dragged back every frame and the lander can never descend at all.
|
|
SETD.0 DownHigh
|
|
LDA.0
|
|
INIB 0x80
|
|
AND
|
|
BRQ moveInside ; The height is positive, so it is somewhere below the top.
|
|
LDA.0 ; DP0 is still DownHigh, from the sign test.
|
|
INIB 0xF5
|
|
CCF
|
|
SUB
|
|
BRC moveAdrift ; Borrowed: at 0xF400 or above it, 192 pixels over the world's top.
|
|
|
|
moveInside:
|
|
; Back under the ceiling, so the warning goes if there was one.
|
|
SETD.0 Adrift
|
|
LDA.0
|
|
BRA moveDone
|
|
RSTA
|
|
STA.0
|
|
SETD.0 EmptyText
|
|
CALL sayInWindow
|
|
moveDone:
|
|
RET
|
|
|
|
moveAdrift:
|
|
; Strictly above? Then the height is pinned and the climb is spent. Exactly at the ceiling
|
|
; is left alone, so gravity has somewhere to move it to.
|
|
SETD.0 DownHigh
|
|
LDA.0
|
|
INIB 0xF4
|
|
CCF
|
|
SUB
|
|
BNC moveAdriftSaid ; No borrow, so it is at the ceiling and not past it.
|
|
|
|
RSTA
|
|
SETD.0 DownLow
|
|
STA.0
|
|
INIA 0xF4
|
|
SETD.0 DownHigh
|
|
STA.0
|
|
|
|
; ---- Only the climb is spent, never the fall ----
|
|
;
|
|
; Zeroing the speed outright pinned the lander here for ever: gravity adds to it once a
|
|
; tick, and a clamp that ran every frame wiped the pull nine times out of ten before it
|
|
; could become downward motion. A ceiling nobody can leave is worse than the wrap it
|
|
; replaced.
|
|
SETD.0 SpeedDownHigh
|
|
LDA.0
|
|
INIB 0x80
|
|
AND
|
|
BRQ moveAdriftSaid ; Positive: already coming down, so leave it be.
|
|
RSTA
|
|
SETD.0 SpeedDown
|
|
STA.0
|
|
SETD.0 SpeedDownHigh
|
|
STA.0
|
|
|
|
; ---- And the sideways speed that bought the climb ----
|
|
;
|
|
; Without this the ceiling was a trap with no way out. A lander held here has its climb
|
|
; wiped every time, so the exchange below sees no fall and no climb, trades nothing, and the
|
|
; sideways speed that is pushing it up NEVER CHANGES: above orbital speed the swing pushes
|
|
; up, the pin wipes it, and round again for ever. Measured: pinned at the top with 117
|
|
; sideways, unmoving, for the whole of a four minute flight.
|
|
;
|
|
; So the pin spends one sideways as well, which is only reached on the ticks the lander is
|
|
; actually trying to climb - about one frame in ten. Six seconds of scraping along the roof
|
|
; brings it back under orbital speed and it falls away on its own. Pushing against the top
|
|
; of the sky costs the speed that got you there.
|
|
CALL spendAcross
|
|
|
|
moveAdriftSaid:
|
|
; Said once rather than sixty times a second.
|
|
SETD.0 Adrift
|
|
LDA.0
|
|
BNA moveDone
|
|
INIA 0x01
|
|
STA.0
|
|
SETD.0 AdriftText
|
|
CALL sayInWindow
|
|
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
|
|
OUTQ 0x36 ; The column origin. IN Q, because RET puts A back.
|
|
RET
|
|
|
|
; ---- The lander, put where it now is ----
|
|
; ---- Round it goes, one orbital speed a frame ----
|
|
moveStation:
|
|
SETD.0 StationLow
|
|
SETD.1 StationSpeed
|
|
CALL addWord
|
|
SETD.0 StationHigh
|
|
LDA.0
|
|
INIB 0x3F
|
|
AND
|
|
STQ.0 ; The same wrap the lander has: 2^14 sixteenths round.
|
|
RET
|
|
|
|
; ---- Where that puts it on the screen ----
|
|
;
|
|
; The lander is always at the middle, so the station's column is the middle plus HOW FAR ROUND
|
|
; IT IS FROM THE LANDER. That difference is taken in the moon's own arithmetic and wrapped to
|
|
; fourteen bits, which measures it the long way round whenever the station is behind: over
|
|
; half a moon ahead IS under half a moon behind, so anything past the half way point is turned
|
|
; into a negative offset instead.
|
|
;
|
|
; Off the edge needs no test. A sprite's X is signed and sixteen bits, so a station three
|
|
; hundred pixels to the left is asked for at minus a hundred and forty and the device declines
|
|
; to draw it, which is a comparison this does not have to make.
|
|
; ---- How far round the moon the station is from the lander, and which way ----
|
|
;
|
|
; Wrapped to fourteen bits like everything else here, which measures the long way round
|
|
; whenever the station is behind - so anything past the half way point is turned into a
|
|
; distance BACKWARDS before anybody uses it. Three callers want this now: the one that draws
|
|
; the station, the one that decides whether it can be docked with, and the one that slides the
|
|
; lander in under it afterwards.
|
|
;
|
|
; Half a moon is 8,192 sixteenths, which is 512 pixels - and that fits in the twelve signed
|
|
; bits toPixelsSigned hands back, so the drawing side can take this straight.
|
|
stationGap:
|
|
SETD.0 StationLow
|
|
LDA.0
|
|
SETD.1 AcrossLow
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.1 GapLow
|
|
STQ.1
|
|
SETD.0 StationHigh
|
|
LDA.0
|
|
SETD.1 AcrossHigh
|
|
LDB.1
|
|
SUB ; And the borrow the half below left.
|
|
MVQA
|
|
INIB 0x3F
|
|
AND
|
|
SETD.1 GapHigh
|
|
STQ.1
|
|
LDA.1
|
|
INIB 0x20
|
|
AND
|
|
BRQ stationGapDone ; Under half a moon, so it is ahead and the gap stands.
|
|
SETD.0 GapHigh
|
|
LDA.0
|
|
INIB 0x40
|
|
CCF
|
|
SUB
|
|
STQ.0 ; Less a whole moon, which makes it a distance backwards.
|
|
stationGapDone:
|
|
RET
|
|
|
|
showStation:
|
|
CALL stationGap
|
|
SETD.0 GapLow
|
|
CALL toPixelsSigned
|
|
|
|
; ---- From the lander's own column, not from the middle ----
|
|
;
|
|
; ShipX is half a screen LESS HALF A TILE, because that is what centres an eight pixel lander
|
|
; on the middle. The station was drawn from the middle itself, so its left edge sat where the
|
|
; lander's centre was and a perfectly aligned dock still looked four pixels out. Same origin
|
|
; for both, and a gap of nothing puts one exactly above the other.
|
|
SETD.0 PixelLow
|
|
LDA.0
|
|
SETD.1 ShipX
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
SETD.1 StationAt
|
|
STQ.1
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
SETD.1 ShipX
|
|
INCD.1
|
|
LDB.1
|
|
ADD
|
|
SETD.1 StationAt
|
|
INCD.1
|
|
STQ.1
|
|
|
|
; And its row, which moves with the view exactly as the lander's does.
|
|
SETD.0 StationDownLow
|
|
CALL toPixelsSigned
|
|
SETD.0 PixelLow
|
|
LDA.0
|
|
SETD.1 ShipYAdd
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
SETD.1 StationTop
|
|
STQ.1
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
SETD.1 ShipYAdd
|
|
INCD.1
|
|
LDB.1
|
|
ADD
|
|
SETD.1 StationTop
|
|
INCD.1
|
|
STQ.1
|
|
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x60
|
|
OUTA 0xE5 ; Sprite six begins ninety six bytes in.
|
|
INIA 0xCA
|
|
OUTA 0xE9 ; Tile 202.
|
|
INIA 0x04
|
|
OUTA 0xE9 ; ---- Attribute four, blue ----
|
|
;
|
|
; Not taste. White is what the ceiling warning is counted in, cyan
|
|
; is what a landing pad is counted in, magenta is the instruments
|
|
; and yellow is the lander. Blue is the one ink no check measures,
|
|
; and picking a measured one has broken a test twice already.
|
|
SETD.0 StationAt
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; X.
|
|
SETD.0 StationTop
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; Y.
|
|
INIA 0x11
|
|
OUTA 0xE9 ; One tile by one.
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
OUTA 0xE9
|
|
OUTA 0xE9
|
|
OUTA 0xE9
|
|
OUTA 0xE9 ; No target size, which HERE means its natural one.
|
|
OUTA 0xE9 ; And no depth.
|
|
RET
|
|
|
|
; ---- A world position in pixels, with its sign kept ----
|
|
;
|
|
; toPixels shifts a sixteen bit position down four and masks what is left to twelve bits, so a
|
|
; position above the world's origin comes back as a large POSITIVE number rather than a small
|
|
; negative one. That was harmless while the top of the screen WAS the origin: anything up
|
|
; there was off the picture either way. Now that the wide view starts twenty four rows higher
|
|
; it is on the picture, and it has to be on the right part of it.
|
|
toPixelsSigned:
|
|
CALL toPixels
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
INIB 0x08
|
|
AND
|
|
BRQ toPixelsSignedDone
|
|
LDA.0
|
|
INIB 0xF0
|
|
OR
|
|
STQ.0 ; The twelfth bit was the sign, so the top nibble is too.
|
|
toPixelsSignedDone:
|
|
RET
|
|
|
|
showLander:
|
|
SETD.0 DownLow
|
|
CALL toPixelsSigned
|
|
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
|
|
SETD.1 ShipYAdd
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
SETD.1 ShipTopLow
|
|
STQ.1
|
|
OUTQ 0xE9
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
SETD.1 ShipYAdd
|
|
INCD.1
|
|
LDB.1
|
|
ADD ; And the carry, which is the other half of the shift.
|
|
SETD.1 ShipTopHigh
|
|
STQ.1
|
|
OUTQ 0xE9 ; Kept as well as sent: the flames hang off this row.
|
|
RET
|
|
|
|
; ---- Green if it would survive, red if it would not ----
|
|
;
|
|
; The magnitude is in DriftLow and DriftHigh and DP1 names the limit. Q comes back the
|
|
; attribute to draw with, which turns a bar from a number into an ANSWER: a person aiming at a
|
|
; pad does not want to know their speed, they want to know whether they can put it down.
|
|
barColour:
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA barColourFast ; A whole high byte of speed is past any limit worth having.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNC barColourFast ; No borrow, so it is at or over the limit.
|
|
INIA 0x02
|
|
RSTB
|
|
CCF
|
|
ADD ; Scheme two, green, which is within tolerance.
|
|
RET
|
|
barColourFast:
|
|
INIA 0x01
|
|
RSTB
|
|
CCF
|
|
ADD ; Scheme one, red, which is not.
|
|
RET
|
|
|
|
; ---- How fast sideways, as a bar ----
|
|
;
|
|
; A moon has no air, so a drift never stops by itself and stopping one means cancelling the
|
|
; velocity EXACTLY. That is not hard to do; it is hard to do BLIND, which is what it was - a
|
|
; number nothing on the screen said anything about.
|
|
;
|
|
; So sprite one is a bar whose width is the drift and which disappears at nought. It grows
|
|
; right from the middle of the screen for a rightward drift and left for a leftward one, so
|
|
; which way is as plain as how fast, and "stopped" is the one state with nothing drawn.
|
|
;
|
|
; The whole of it is the sprite's target width, which the device stretches one tile into. The
|
|
; program does no drawing at all: it works out one number a frame and writes it.
|
|
; ---- How big a signed pair is, without its sign ----
|
|
;
|
|
; DP0 names a two byte value, low half first. Leaves the size in DriftLow and DriftHigh and
|
|
; the sign in DriftLeftward, which is nought for a positive one.
|
|
;
|
|
; A two's complement pair is inverted and stepped, and the step's carry is what runs into the
|
|
; high half - the same carry that makes addWord work, used one instruction at a time because
|
|
; there is nothing to add.
|
|
magnitude:
|
|
LDA.0
|
|
SETD.1 DriftLow
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 DriftHigh
|
|
STA.1
|
|
INIB 0x80
|
|
AND
|
|
SETD.1 DriftLeftward
|
|
STQ.1 ; The top bit of the high half, which is the sign.
|
|
BRQ magnitudeDone
|
|
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
NOTA
|
|
MVQA
|
|
INCA
|
|
STA.0
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
NOTA
|
|
MVQA
|
|
RSTB
|
|
ADD ; Plus the carry the step above left, which is the other half.
|
|
STQ.0
|
|
magnitudeDone:
|
|
RET
|
|
|
|
showDrift:
|
|
SETD.0 SpeedAcross
|
|
CALL magnitude
|
|
SETD.1 GentleAcross
|
|
CALL barColour
|
|
SETD.1 DriftColour
|
|
STQ.1
|
|
; ---- Clamped, because a bar wider than the screen says nothing a full one does not ----
|
|
;
|
|
; And because a target width is sixteen bits: an unclamped one would be asking the device to
|
|
; draw a bar sixty thousand pixels wide, which it clips, but only after being asked.
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA driftWide ; Anything in the high half is already past the limit.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
INIB 0d120
|
|
CCF
|
|
SUB
|
|
BRC driftReady ; Borrowed, so it is under the limit and stands.
|
|
driftWide:
|
|
INIA 0d120
|
|
SETD.0 DriftLow
|
|
STA.0
|
|
|
|
driftReady:
|
|
; ---- Two zeros, meaning two different things ----
|
|
;
|
|
; A TARGET WIDTH OF NOUGHT IS THE NATURAL WIDTH, not an empty sprite. So a bar with no drift
|
|
; in it came out eight pixels wide - one whole tile, sitting at the middle of the screen,
|
|
; saying "stopped" in the same shape it says "drifting slightly". What draws nothing is a
|
|
; SIZE of nought, which is the other zero and the other byte.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
BRA driftEmpty
|
|
INIA 0x11 ; One tile by one, however wide it ends up drawn.
|
|
BRI driftSized2
|
|
driftEmpty:
|
|
RSTA ; No tiles at all, which is the off switch.
|
|
driftSized2:
|
|
SETD.1 DriftSize
|
|
STA.1
|
|
|
|
; ---- Where it starts, in sixteen bits ----
|
|
;
|
|
; The middle for a rightward drift and that much back for a leftward one, and it is a WORD
|
|
; now because the middle of an eighty column screen is 320 and will not go in a byte.
|
|
SETD.0 MiddleX
|
|
LDA.0
|
|
SETD.1 DriftAt
|
|
STA.1
|
|
INCD.0
|
|
LDA.0
|
|
INCD.1
|
|
STA.1
|
|
SETD.0 DriftLeftward
|
|
LDA.0
|
|
BRA driftAt ; Nought is rightward, so the bar starts at the middle.
|
|
SETD.0 DriftAt
|
|
LDA.0
|
|
SETD.1 DriftLow
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.1 DriftAt
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
SUB ; And the borrow, which is the other half of the step back.
|
|
STQ.0
|
|
driftAt:
|
|
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x10
|
|
OUTA 0xE5 ; Sprite one begins sixteen bytes in.
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The ground block, which is a solid tile.
|
|
SETD.0 DriftColour
|
|
LDA.0
|
|
OUTA 0xE9 ; Green while this drift could be landed with, red while not.
|
|
SETD.0 DriftAt
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; X.
|
|
SETD.0 BarY
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; Y, near the bottom and clear of the ground most places.
|
|
SETD.0 DriftSize
|
|
LDA.0
|
|
OUTA 0xE9 ; One tile by one, or nothing at all when there is no drift.
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; The width, which is the drift. NOUGHT DRAWS NOTHING.
|
|
INIA 0d3
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Three pixels tall.
|
|
OUTA 0xE9 ; And no depth.
|
|
RET
|
|
|
|
; ---- Has it arrived, and how hard ----
|
|
;
|
|
; The terrain is an array in Data Memory and not something read back out of the map, which is
|
|
; the whole reason this is cheap: the ground under the lander is one index into 128 bytes,
|
|
; where asking the screen would mean a transfer through the controller every frame.
|
|
;
|
|
; The column is the world position divided by eight, masked to the moon's 128. The surface is
|
|
; that column's row times eight, and the lander's feet are its top plus its eight pixels.
|
|
touchdown:
|
|
SETD.0 AcrossLow
|
|
CALL toPixels
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
SETD.0 PixelLow
|
|
LDB.0
|
|
CALL eighth
|
|
MVQA
|
|
INIB 0x7F
|
|
AND ; The moon is 128 columns, so seven bits of it.
|
|
MVQA
|
|
SETD.1 LandColumn
|
|
STA.1 ; Kept, because the verdict below wants it too.
|
|
SETD.1 Terrain
|
|
DPUA.1 ; Terrain plus the column, which is the row its surface is on.
|
|
LDA.1
|
|
|
|
; Times eight, into pixels. A is the high half of the shift register and B the low, so the
|
|
; row goes in B and three turns LEFT multiply it - and a row is at most 24, so 192 fits in
|
|
; the low half and the high one stays empty.
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
MVQB ; The row, in the low half.
|
|
RSTA
|
|
SHL
|
|
SHL
|
|
SHL
|
|
RSTA
|
|
CCF
|
|
ADD
|
|
SETD.1 SurfaceAt
|
|
STQ.1
|
|
|
|
; The feet: where the lander's top is, plus the eight pixels of it.
|
|
SETD.0 DownLow
|
|
CALL toPixels
|
|
SETD.0 PixelHigh
|
|
LDA.0
|
|
BNA touchdownDone ; Above the screen entirely, so nowhere near the ground.
|
|
SETD.0 PixelLow
|
|
LDA.0
|
|
INIB 0d8
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
SETD.1 SurfaceAt
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BRC touchdownDone ; Borrowed: the feet are still above the surface.
|
|
|
|
; Already sitting on it, so there is nothing new to say. Without this the arrival happens
|
|
; again every frame the lander rests, which is a base handing out cargo sixty times a
|
|
; second.
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BNA touchdownDone
|
|
|
|
; ---- Arrived. Now, how ----
|
|
;
|
|
; Both speeds, and both have to be gentle. A landing that was soft downwards and sliding
|
|
; sideways is a lander on its side, which is the interesting half of the difficulty: the
|
|
; drift bar is the thing that was blind about it until this rung.
|
|
SETD.0 SpeedDown
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA touchdownCrash ; Anything in the high half is far too fast to argue about.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 GentleDown
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNC touchdownCrash ; No borrow, so it is at or past the limit.
|
|
|
|
SETD.0 SpeedAcross
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA touchdownCrash
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 GentleAcross
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNC touchdownCrash
|
|
|
|
CALL landingDust ; Something to see for an arrival, as a crash has always had.
|
|
|
|
; ---- Down safely, which is not the end of anything ----
|
|
;
|
|
; The view settles, the arrival is dealt with, and the lander sits where it is until
|
|
; somebody opens the throttle again. A landing used to stop the program, which is fine for a
|
|
; game about landing once and wrong for one about carrying things between four places.
|
|
RSTA
|
|
OUTA 0x37
|
|
OUTA 0x38
|
|
CALL arrive
|
|
CALL waitKey
|
|
|
|
; Sitting still, exactly on the surface. Both speeds go, because a lander that kept a
|
|
; hundredth of a pixel a frame would drift off its pad while nobody was looking.
|
|
RSTA
|
|
SETD.0 SpeedDown
|
|
STA.0
|
|
INCD.0
|
|
STA.0
|
|
SETD.0 SpeedAcross
|
|
STA.0
|
|
INCD.0
|
|
STA.0
|
|
CALL restOnSurface
|
|
INIA 0x01
|
|
SETD.0 Landed
|
|
STA.0
|
|
RET
|
|
|
|
touchdownCrash:
|
|
SETD.0 CrashedText
|
|
touchdownStop:
|
|
CALL explode ; Before the verdict, so there is something to watch.
|
|
; ---- The view settled before anything is said ----
|
|
;
|
|
; The console draws into the map, and the map is what is being scrolled - so a message
|
|
; printed while the view is three pixels into a cell comes out three pixels off the top,
|
|
; with as much of its first row missing as the cell above it has lost.
|
|
;
|
|
; Nothing is being aligned for tidiness: the flying is over, so the fractional part of the
|
|
; view has no more work to do, and putting it back is what makes the whole message visible.
|
|
; A HUD that had to stay readable WHILE the map moved is a different problem - see the
|
|
; CosmOS README - and this is not it.
|
|
RSTA
|
|
OUTA 0x37
|
|
OUTA 0x38
|
|
SWI osPrintString
|
|
INIA 0x0A
|
|
OUTA 0x00
|
|
|
|
; ---- And a look at what happened ----
|
|
;
|
|
; The verdict was printed and the program then left immediately, taking the screen with it -
|
|
; so the one thing anybody wanted to see, the lander sitting on the ground it had just
|
|
; arrived at, was replaced by a shell prompt before it could be looked at.
|
|
CALL waitKey
|
|
RSTA
|
|
SETD.1 Flying
|
|
STA.1 ; Which ends the loop, and the program tidies up as it always did.
|
|
touchdownDone:
|
|
RET
|
|
|
|
; ---- Docking, which is landing on something that is moving ----
|
|
;
|
|
; The same shape as touchdown and for the same reason: close enough, and slow enough, or it is
|
|
; a wreck. What makes it a different skill is that "slow enough" is measured against the
|
|
; STATION rather than against nothing. Its speed is orbital speed, which is the number the
|
|
; marks on the drift bar point at - so the instrument for this was already on the screen
|
|
; before there was anything to dock with.
|
|
;
|
|
; The tolerances are the ground's own, deliberately, so there is one number to argue with if
|
|
; this turns out to be too kind.
|
|
dock:
|
|
SETD.0 Docked
|
|
LDA.0
|
|
BNA dockRiding
|
|
|
|
; Close enough, going round, which is the same gap the station is drawn from.
|
|
CALL stationGap
|
|
SETD.0 GapLow
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA dockAway ; 256 sixteenths or more, which is properly out of the way.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
INIB 0d128
|
|
CCF
|
|
SUB
|
|
BNC dockDone ; A tile away or more: too far to dock, not far enough to re-arm.
|
|
|
|
; ---- And close enough, up and down ----
|
|
SETD.0 StationDownLow
|
|
LDA.0
|
|
SETD.1 DownLow
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.1 DockRiseLow
|
|
STQ.1
|
|
SETD.0 StationDownHigh
|
|
LDA.0
|
|
SETD.1 DownHigh
|
|
LDB.1
|
|
SUB
|
|
SETD.1 DockRiseHigh
|
|
STQ.1
|
|
SETD.0 DockRiseLow
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA dockAway
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
INIB 0d128
|
|
CCF
|
|
SUB
|
|
BNC dockDone
|
|
|
|
; ---- Still in the doorway it just left by ----
|
|
;
|
|
; Letting go UPWARDS moves the lander towards the station, so without this it docked again on
|
|
; the very next frame - took the fuel again, said so again, and waited to be told the message
|
|
; had been read, which reads as the controls locking up the instant they are used.
|
|
;
|
|
; TWO DISTANCES, and they have to be different. Docking wants a tile; re-arming wants two,
|
|
; because a lander riding the station sits EXACTLY a tile under it - so a single distance
|
|
; re-armed on the very frame it let go, and the frame after that it was back on.
|
|
SETD.0 DockClear
|
|
LDA.0
|
|
BRA dockDone
|
|
|
|
; ---- Touching. Now, how fast, and RELATIVE TO WHAT ----
|
|
;
|
|
; The station is going at orbital speed, so a lander going at orbital speed is standing still
|
|
; beside it however fast the ground below says they are both travelling. That difference is
|
|
; the whole of the manoeuvre.
|
|
SETD.0 SpeedAcross
|
|
LDA.0
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
SETD.1 DockVxLow
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
SUB ; And the borrow, which is the other half of the difference.
|
|
SETD.1 DockVxHigh
|
|
STQ.1
|
|
SETD.0 DockVxLow
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA dockStruck
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 GentleAcross
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNC dockStruck
|
|
|
|
; Downwards, where the station has no speed at all, so the difference is simply the fall.
|
|
SETD.0 SpeedDown
|
|
CALL magnitude
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA dockStruck
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
SETD.1 GentleDown
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BNC dockStruck
|
|
|
|
; ---- Aboard ----
|
|
INIA 0x01
|
|
SETD.0 Docked
|
|
STA.0
|
|
CALL payFuel
|
|
SETD.0 DockedText
|
|
CALL sayInWindow
|
|
CALL waitKey
|
|
RET
|
|
|
|
dockAway:
|
|
INIA 0x01
|
|
SETD.0 DockClear
|
|
STA.0 ; Two tiles off, so the next arrival counts as an arrival.
|
|
dockDone:
|
|
RET
|
|
|
|
dockStruck:
|
|
SETD.0 StruckText
|
|
BRI touchdownStop ; Which settles the view, says it, and ends the run.
|
|
|
|
; ---- Riding, until something is leaned on ----
|
|
;
|
|
; Held to the station rather than merely paid by it, because that is what docking IS. The
|
|
; speeds are the station's, so the pair drift round together, and the lander sits one tile
|
|
; under it so both are still visible.
|
|
;
|
|
; ANY thruster lets go. It has to be checked before the speeds are put back, or a burn would
|
|
; be wiped by the snap on the very frame it was made and the lander could never leave.
|
|
dockRiding:
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x0F
|
|
AND
|
|
BNQ dockLetGo
|
|
|
|
; ---- Slid into place, not snapped to it ----
|
|
;
|
|
; A dock is allowed at up to eight pixels out in either direction, so putting the lander
|
|
; exactly under the station the instant it takes hold moved it a whole tile in one frame -
|
|
; a jump, right at the moment the player was being told they had been careful. Half a pixel
|
|
; a frame closes the worst of it in about half a second, which reads as the two of them
|
|
; settling together.
|
|
;
|
|
; The sideways one goes through the WRAPPED gap rather than the raw positions, because a
|
|
; dock made either side of the moon's seam has a raw difference of most of a moon.
|
|
CALL stationGap
|
|
CALL easeAcross
|
|
|
|
SETD.0 StationDownLow
|
|
LDA.0
|
|
INIB 0d128
|
|
CCF
|
|
ADD
|
|
SETD.1 DockTargetLow
|
|
STQ.1
|
|
SETD.0 StationDownHigh
|
|
LDA.0
|
|
RSTB
|
|
ADD ; One tile below it, carry included.
|
|
SETD.1 DockTargetHigh
|
|
STQ.1
|
|
SETD.0 DownLow
|
|
SETD.1 DockTargetLow
|
|
CALL easeToward
|
|
|
|
INIA 0d64
|
|
SETD.0 SpeedAcross
|
|
STA.0
|
|
RSTA
|
|
INCD.0
|
|
STA.0 ; Orbital speed exactly, which is what it is riding.
|
|
SETD.0 SpeedDown
|
|
STA.0
|
|
INCD.0
|
|
STA.0
|
|
RET
|
|
|
|
dockLetGo:
|
|
RSTA
|
|
SETD.0 Docked
|
|
STA.0
|
|
SETD.0 DockClear
|
|
STA.0 ; A is still nought: not clear until it has actually got away.
|
|
CALL undockGas ; Started and left running: the pilot is mid-burn.
|
|
RET
|
|
|
|
; ---- Half a pixel of the way there, or the whole of it if that is nearer ----
|
|
;
|
|
; DP0 names the word to move, DP1 the place it should end up. The gap is taken as a signed
|
|
; sixteen bit number, so "close enough to finish" is two cases and not one: a small gap
|
|
; forwards has a high byte of nought, and a small gap backwards has one of 0xFF.
|
|
easeToward:
|
|
LDA.1
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
SETD.2 EaseLow
|
|
STQ.2
|
|
INCD.0
|
|
INCD.1
|
|
LDA.1
|
|
LDB.0
|
|
SUB ; And the borrow, which is the other half of the gap.
|
|
SETD.2 EaseHigh
|
|
STQ.2
|
|
DECD.0
|
|
DECD.1 ; Both pointers back at the low bytes they came in on.
|
|
|
|
LDA.2 ; DP2 still names the high half of the gap.
|
|
BNA easeBack ; Not nought, so the gap runs backwards or is a long way off.
|
|
SETD.2 EaseLow
|
|
LDA.2
|
|
INIB 0d8
|
|
CCF
|
|
SUB
|
|
BRC easeArrive ; Borrowed: under a step, so put it exactly there.
|
|
SETD.1 EaseForward
|
|
CALL addWord
|
|
RET
|
|
easeBack:
|
|
SETD.2 EaseLow
|
|
LDA.2
|
|
INIB 0d248
|
|
CCF
|
|
SUB
|
|
BNC easeArrive ; No borrow: within a step of nought, going the other way.
|
|
SETD.1 EaseBackward
|
|
CALL addWord
|
|
RET
|
|
easeArrive:
|
|
LDA.1
|
|
STA.0
|
|
INCD.0
|
|
INCD.1
|
|
LDA.1
|
|
STA.0
|
|
RET
|
|
|
|
; ---- The same, for a position that has to go round a moon to get there ----
|
|
;
|
|
; The gap is already worked out and wrapped, in GapLow. Adding to a position and then wrapping
|
|
; it is the whole difference from the routine above: on a ring the target is "whatever closes
|
|
; that gap", not a place, because the place can be on the other side of the seam.
|
|
easeAcross:
|
|
SETD.0 GapHigh
|
|
LDA.0
|
|
BNA easeAcrossBack ; Not nought, so the gap runs backwards or is a long way off.
|
|
SETD.0 GapLow
|
|
LDA.0
|
|
INIB 0d8
|
|
CCF
|
|
SUB
|
|
BRC easeAcrossWhole ; Borrowed: under a step, so close the whole of it at once.
|
|
SETD.0 AcrossLow
|
|
SETD.1 EaseForward
|
|
CALL addWord
|
|
BRI easeAcrossWrap
|
|
easeAcrossBack:
|
|
SETD.0 GapLow
|
|
LDA.0
|
|
INIB 0d248
|
|
CCF
|
|
SUB
|
|
BNC easeAcrossWhole
|
|
SETD.0 AcrossLow
|
|
SETD.1 EaseBackward
|
|
CALL addWord
|
|
BRI easeAcrossWrap
|
|
easeAcrossWhole:
|
|
SETD.0 AcrossLow
|
|
SETD.1 GapLow
|
|
CALL addWord
|
|
easeAcrossWrap:
|
|
SETD.0 AcrossHigh
|
|
LDA.0
|
|
INIB 0x3F
|
|
AND
|
|
STQ.0 ; Round the moon, the way every other step along it is.
|
|
RET
|
|
|
|
; ---- Flames, so a watcher can see what the pilot is doing ----
|
|
;
|
|
; Every reading on this screen is a NUMBER drawn as a bar: how fast sideways, how fast down,
|
|
; how much sky, how much tank. All of it says what is happening TO the lander and none of it
|
|
; says what the pilot is doing about it, so somebody watching over a shoulder has to read
|
|
; gauges to work out that a thruster is even lit.
|
|
;
|
|
; ---- Shown while the button is held, not while the engine fires ----
|
|
;
|
|
; The engine fires one frame in ten, because that is the tick gravity is applied on. A flame
|
|
; that honest would be one frame of light six times a second, which is a fault lamp and not a
|
|
; rocket. What is drawn is the BUTTON BEING DOWN, and that is the truthful answer to "is the
|
|
; pilot burning" - the tick is how the sum gets done, not what is happening.
|
|
;
|
|
; An empty tank draws nothing, because then the button really is doing nothing.
|
|
showFlames:
|
|
INIA 0x70
|
|
SETD.0 FlameSlot
|
|
STA.0
|
|
INIA 0xCB
|
|
SETD.0 FlameTile
|
|
STA.0
|
|
SETD.0 ShipX
|
|
SETD.1 FlameXLow
|
|
CALL copyWord ; Straight under the lander, whichever way it is pushing.
|
|
|
|
SETD.0 Fuel
|
|
LDA.0
|
|
BRA flameNoLift
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x08 ; Up, so the plume is UNDER the lander, pushing it away.
|
|
AND
|
|
BRQ flameTryDown
|
|
SETD.0 FlameAway
|
|
SETD.1 FlameOffLow
|
|
CALL copyWord
|
|
CALL flameBelow
|
|
RSTA
|
|
BRI flameLiftFlagged
|
|
flameTryDown:
|
|
SETD.0 Landed
|
|
LDA.0
|
|
BNA flameNoLift ; The retro thruster does nothing on the ground, so it shows nothing.
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x04 ; Down, so it is over the lander and turned upside down.
|
|
AND
|
|
BRQ flameNoLift
|
|
SETD.0 FlameBack
|
|
SETD.1 FlameOffLow
|
|
CALL copyWord
|
|
CALL flameBelow
|
|
INIA 0x02 ; VFLIP.
|
|
flameLiftFlagged:
|
|
SETD.0 FlameFlags
|
|
STA.0
|
|
INIA 0x11
|
|
BRI flameLiftSized
|
|
flameNoLift:
|
|
RSTA
|
|
flameLiftSized:
|
|
SETD.0 FlameSize
|
|
STA.0
|
|
CALL putFlame
|
|
|
|
INIA 0x80
|
|
SETD.0 FlameSlot
|
|
STA.0
|
|
INIA 0xCC
|
|
SETD.0 FlameTile
|
|
STA.0
|
|
SETD.0 ShipTopLow
|
|
SETD.1 FlameYLow
|
|
CALL copyWord
|
|
|
|
SETD.0 Fuel
|
|
LDA.0
|
|
BRA flameNoSide
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x02 ; Left, so the plume is on the RIGHT, pushing that way.
|
|
AND
|
|
BRQ flameTryRight
|
|
SETD.0 FlameAway
|
|
SETD.1 FlameOffLow
|
|
CALL copyWord
|
|
CALL flameBeside
|
|
RSTA
|
|
BRI flameSideFlagged
|
|
flameTryRight:
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x01 ; Right, so it is on the left and turned about.
|
|
AND
|
|
BRQ flameNoSide
|
|
SETD.0 FlameBack
|
|
SETD.1 FlameOffLow
|
|
CALL copyWord
|
|
CALL flameBeside
|
|
INIA 0x01 ; HFLIP.
|
|
flameSideFlagged:
|
|
SETD.0 FlameFlags
|
|
STA.0
|
|
INIA 0x11
|
|
BRI flameSideSized
|
|
flameNoSide:
|
|
RSTA
|
|
flameSideSized:
|
|
SETD.0 FlameSize
|
|
STA.0
|
|
CALL putFlame
|
|
RET
|
|
|
|
; The lander's row, plus the offset - a whole word, so its sign comes along with it.
|
|
flameBelow:
|
|
SETD.0 ShipTopLow
|
|
LDA.0
|
|
SETD.1 FlameOffLow
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
SETD.1 FlameYLow
|
|
STQ.1
|
|
SETD.0 ShipTopHigh
|
|
LDA.0
|
|
SETD.1 FlameOffHigh
|
|
LDB.1
|
|
ADD ; And the carry, which is the other half of it.
|
|
SETD.1 FlameYHigh
|
|
STQ.1
|
|
RET
|
|
|
|
; And the lander's column, the same way.
|
|
flameBeside:
|
|
SETD.0 ShipX
|
|
LDA.0
|
|
SETD.1 FlameOffLow
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
SETD.1 FlameXLow
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
SETD.1 FlameOffHigh
|
|
LDB.1
|
|
ADD
|
|
SETD.1 FlameXHigh
|
|
STQ.1
|
|
RET
|
|
|
|
; One flame, out of the block above.
|
|
putFlame:
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
SETD.0 FlameSlot
|
|
LDA.0
|
|
OUTA 0xE5
|
|
SETD.0 FlameTile
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INIA 0x01
|
|
OUTA 0xE9 ; Scheme one, red, which is what a rocket looks like.
|
|
SETD.0 FlameXLow
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 FlameXHigh
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 FlameYLow
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 FlameYHigh
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 FlameSize
|
|
LDA.0
|
|
OUTA 0xE9 ; Nought when nothing is held, which is the off switch.
|
|
SETD.0 FlameFlags
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9
|
|
OUTA 0xE9
|
|
OUTA 0xE9
|
|
OUTA 0xE9 ; Its natural size.
|
|
OUTA 0xE9 ; And no depth.
|
|
RET
|
|
|
|
; ---- Six pieces of something, thrown out from somewhere ----
|
|
;
|
|
; One system, three uses: a lander coming apart, dust kicked up by a landing, and gas out of a
|
|
; docking port on release. What differs is where they start, how fast they go, what colour
|
|
; they are and how long they last, so all four of those are arguments and the rest is shared.
|
|
;
|
|
; ---- Ticked from the frame loop rather than run in place ----
|
|
;
|
|
; The explosion can afford to stop the world - there is nothing left to fly. The undocking puff
|
|
; CANNOT: it goes off on the frame a thruster is pressed, and freezing for a quarter of a
|
|
; second exactly then would be felt as the controls sticking. So the burst advances one frame
|
|
; at a time from the main loop, and the two that can afford to wait simply pump that same tick
|
|
; until it runs out.
|
|
startBurst:
|
|
; DP0 names six pairs of speeds; the origin, colour and length are already set.
|
|
SETD.1 BurstV
|
|
INIA 0d24
|
|
SETD.2 CopyLeft
|
|
STA.2
|
|
startBurstCopy:
|
|
LDA.0
|
|
STA.1
|
|
INCD.0
|
|
INCD.1
|
|
SETD.2 CopyLeft
|
|
LDA.2
|
|
DECA
|
|
STA.2
|
|
BNA startBurstCopy
|
|
|
|
RSTA
|
|
SETD.0 SparkAt
|
|
STA.0
|
|
INIA 0d6
|
|
SETD.0 SparkCount
|
|
STA.0
|
|
startBurstPlace:
|
|
; DP0 is what copyWord reads and DP1 what it writes, so the origin is DP0. Written the other
|
|
; way round first, which copied the empty pieces OVER ShipX - and since that is in the view
|
|
; block, the lander's own column went with them.
|
|
SETD.0 BurstOXLow
|
|
SETD.1 Spark
|
|
SETD.2 SparkAt
|
|
LDA.2
|
|
DPUA.1
|
|
CALL copyWord
|
|
SETD.0 BurstOYLow
|
|
SETD.1 Spark
|
|
SETD.2 SparkAt
|
|
LDA.2
|
|
INCA
|
|
INCA
|
|
DPUA.1
|
|
CALL copyWord
|
|
SETD.0 SparkAt
|
|
LDA.0
|
|
INIB 0d4
|
|
CCF
|
|
ADD
|
|
STQ.0
|
|
SETD.0 SparkCount
|
|
LDA.0
|
|
DECA
|
|
STA.0
|
|
BNA startBurstPlace
|
|
RET
|
|
|
|
; One frame of whatever is in the air. Nothing at all when nothing is.
|
|
burstTick:
|
|
SETD.0 BurstLeft
|
|
LDA.0
|
|
BRA burstTickDone
|
|
RSTA
|
|
SETD.0 SparkAt
|
|
STA.0
|
|
INIA 0x90
|
|
SETD.0 SparkSlot
|
|
STA.0
|
|
INIA 0d6
|
|
SETD.0 SparkCount
|
|
STA.0
|
|
burstOne:
|
|
SETD.0 Spark
|
|
SETD.2 SparkAt
|
|
LDA.2
|
|
DPUA.0
|
|
SETD.1 BurstV
|
|
DPUA.1
|
|
CALL addWord ; Across, by however fast this piece is going.
|
|
SETD.0 Spark
|
|
SETD.2 SparkAt
|
|
LDA.2
|
|
INCA
|
|
INCA
|
|
DPUA.0
|
|
SETD.1 BurstV
|
|
DPUA.1
|
|
CALL addWord ; And down.
|
|
CALL putSpark
|
|
|
|
SETD.0 SparkAt
|
|
LDA.0
|
|
INIB 0d4
|
|
CCF
|
|
ADD
|
|
STQ.0
|
|
SETD.0 SparkSlot
|
|
LDA.0
|
|
INIB 0x10
|
|
CCF
|
|
ADD
|
|
STQ.0
|
|
SETD.0 SparkCount
|
|
LDA.0
|
|
DECA
|
|
STA.0
|
|
BNA burstOne
|
|
|
|
SETD.0 BurstLeft
|
|
LDA.0
|
|
DECA
|
|
STA.0
|
|
BNA burstTickDone
|
|
CALL hideBurst ; The last frame of it, so they go rather than hang there.
|
|
burstTickDone:
|
|
RET
|
|
|
|
; Every piece off the screen, which is a size of nought six times.
|
|
hideBurst:
|
|
INIA 0x90
|
|
SETD.0 SparkSlot
|
|
STA.0
|
|
INIA 0d6
|
|
SETD.0 SparkCount
|
|
STA.0
|
|
hideBurstOne:
|
|
SETD.0 SparkSlot
|
|
LDA.0
|
|
CALL hideSprite
|
|
SETD.0 SparkSlot
|
|
LDA.0
|
|
INIB 0x10
|
|
CCF
|
|
ADD
|
|
STQ.0
|
|
SETD.0 SparkCount
|
|
LDA.0
|
|
DECA
|
|
STA.0
|
|
BNA hideBurstOne
|
|
RET
|
|
|
|
; For the two that can afford to wait: pump the tick until there is nothing left in the air.
|
|
runBurst:
|
|
CALL waitFrame
|
|
CALL burstTick
|
|
SETD.0 BurstLeft
|
|
LDA.0
|
|
BNA runBurst
|
|
RET
|
|
|
|
; ---- Coming apart, which is the only thing on this screen that is not a number ----
|
|
;
|
|
; A crash used to be a line of text and a lander still sitting there in one piece. The verdict
|
|
; was the whole of it, and somebody watching a recording had to read the words to know what had
|
|
; happened - the same problem the flames were for.
|
|
explode:
|
|
RSTA
|
|
CALL hideSprite ; The lander itself.
|
|
INIA 0x70
|
|
CALL hideSprite
|
|
INIA 0x80
|
|
CALL hideSprite ; And both flames, which would otherwise hang in the air.
|
|
SETD.0 ShipX
|
|
SETD.1 BurstOXLow
|
|
CALL copyWord
|
|
SETD.0 ShipTopLow
|
|
SETD.1 BurstOYLow
|
|
CALL copyWord
|
|
INIA 0x03
|
|
SETD.0 BurstColour
|
|
STA.0 ; Yellow: it is the lander that is coming apart.
|
|
INIA 0d48
|
|
SETD.0 BurstLeft
|
|
STA.0 ; Getting on for a second, which is long enough to watch.
|
|
SETD.0 SparkV
|
|
CALL startBurst
|
|
CALL boom
|
|
CALL runBurst
|
|
RET
|
|
|
|
; ---- Dust, for a landing that went right ----
|
|
;
|
|
; A crash has had something to watch since the pieces arrived, and an arrival did not. The
|
|
; puff comes off the LANDER'S FEET rather than its middle, which is where the ground is, and
|
|
; it goes sideways and up because that is where kicked dust goes.
|
|
;
|
|
; Not on docking. A dock is a catch and not a touchdown - there is nothing under it to kick.
|
|
landingDust:
|
|
SETD.0 ShipX
|
|
SETD.1 BurstOXLow
|
|
CALL copyWord
|
|
SETD.0 ShipTopLow
|
|
LDA.0
|
|
INIB 0d8
|
|
CCF
|
|
ADD
|
|
SETD.1 BurstOYLow
|
|
STQ.1
|
|
SETD.0 ShipTopHigh
|
|
LDA.0
|
|
RSTB
|
|
ADD ; A tile down from its top, which is where its feet are.
|
|
SETD.1 BurstOYHigh
|
|
STQ.1
|
|
INIA 0x07
|
|
SETD.0 BurstColour
|
|
STA.0 ; White, which is the one thing on this moon that is not grey.
|
|
INIA 0d16
|
|
SETD.0 BurstLeft
|
|
STA.0
|
|
SETD.0 DustV
|
|
CALL startBurst
|
|
CALL runBurst
|
|
RET
|
|
|
|
; ---- And gas, for letting go of a station ----
|
|
;
|
|
; Something has to come out of a docking port when it lets go, and this is a small symmetric
|
|
; puff of it. STARTED AND LEFT RUNNING, unlike the other two: it happens on the frame a
|
|
; thruster is pressed, and stopping the world exactly then would be felt as the controls
|
|
; sticking rather than as an effect.
|
|
undockGas:
|
|
SETD.0 ShipX
|
|
SETD.1 BurstOXLow
|
|
CALL copyWord
|
|
SETD.0 ShipTopLow
|
|
SETD.1 BurstOYLow
|
|
CALL copyWord
|
|
INIA 0x07
|
|
SETD.0 BurstColour
|
|
STA.0
|
|
INIA 0d16
|
|
SETD.0 BurstLeft
|
|
STA.0
|
|
SETD.0 GasV
|
|
CALL startBurst
|
|
RET
|
|
|
|
; One piece, where the pointers say it now is.
|
|
putSpark:
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
SETD.0 SparkSlot
|
|
LDA.0
|
|
OUTA 0xE5
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block, at two pixels by two.
|
|
SETD.0 BurstColour
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 Spark
|
|
SETD.2 SparkAt
|
|
LDA.2
|
|
DPUA.0
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; Across and down, four bytes in a row of them.
|
|
INIA 0x11
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
INIA 0d2
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9
|
|
INIA 0d2
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Two by two, which is a piece and not a lander.
|
|
OUTA 0xE9 ; And no depth.
|
|
RET
|
|
|
|
; Byte six of a sprite is its size, and nought there is the off switch.
|
|
hideSprite:
|
|
SETD.0 HideSlot
|
|
STA.0
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
LDA.0
|
|
INIB 0d6
|
|
CCF
|
|
ADD
|
|
OUTQ 0xE5
|
|
RSTA
|
|
OUTA 0xE9
|
|
RET
|
|
|
|
; ---- The bang, set up once and played in two writes ----
|
|
;
|
|
; A patch is forty odd writes and a note is two, which is the whole point of the selector and
|
|
; value registers - so the instrument is built at startup like the tiles are, and a crash only
|
|
; has to say "this channel, this note".
|
|
;
|
|
; ---- And the numbers came out of soundThing ----
|
|
;
|
|
; The first version of this was guessed at in bytes, and a byte is not seconds or hertz: a
|
|
; cutoff of 40 looked small and is 57 Hz, which made the bang a low gurgle. Voyager's sound
|
|
; device is soundThing's voice engine with the editor taken off, so a patch designed there -
|
|
; where it can be HEARD - means the same thing here. SoundPatch converts one into the table
|
|
; below. Change the sound in soundThing and convert it again; do not edit the numbers.
|
|
;
|
|
; ---- Channel three, counting down ----
|
|
;
|
|
; Effects take the top channel and work downwards, so music, if it ever arrives, can take
|
|
; nought and count up and the two never have to negotiate. There are four, and this is the
|
|
; first program to want any of them while also doing something else.
|
|
putBoom:
|
|
INIA 0x03
|
|
OUTA 0x41 ; Channel three. Every parameter write below lands on it.
|
|
SETD.0 BoomPatch
|
|
CALL playPatch
|
|
RET
|
|
|
|
; ---- A whole patch, out of a table ----
|
|
;
|
|
; DP0 names a count and then that many parameter and value pairs. Every sound after this one
|
|
; costs a table and a call rather than forty lines of its own.
|
|
playPatch:
|
|
LDA.0
|
|
SETD.1 PatchLeft
|
|
STA.1
|
|
INCD.0
|
|
playPatchOne:
|
|
LDA.0
|
|
OUTA 0x42
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0x43
|
|
INCD.0
|
|
SETD.1 PatchLeft
|
|
LDA.1
|
|
DECA
|
|
STA.1
|
|
BNA playPatchOne
|
|
RET
|
|
|
|
; Two writes, which is what a note is meant to cost.
|
|
boom:
|
|
INIA 0x03
|
|
OUTA 0x41
|
|
INIA 0d60 ; Middle C. The noise is not really pitched, but the filter is
|
|
; tuned off the note and this is where it was found by ear.
|
|
OUTA 0x44 ; Writing the note is what starts it.
|
|
RET
|
|
|
|
; ---- A quarter of a tank, said once ----
|
|
;
|
|
; ONCE, and that is the whole design of it. A lander is at its most careful in the last few
|
|
; seconds before it touches, and something repeating in its ear through that is not a warning,
|
|
; it is a distraction - so this fires on the way DOWN through a quarter and then holds its
|
|
; peace. Filling up at a base allows it again.
|
|
;
|
|
; A quarter is 64 of 255. Half and a tenth are the obvious next two and are not here yet: one
|
|
; threshold is enough to find out whether being told at all is welcome.
|
|
watchFuel:
|
|
SETD.0 AlarmSaid
|
|
LDA.0
|
|
BNA watchFuelDone ; Already said. Saying it again is what this exists to avoid.
|
|
SETD.0 Fuel
|
|
LDA.0
|
|
INIB 0d64
|
|
CCF
|
|
SUB
|
|
BNC watchFuelDone ; No borrow: a quarter or more, so there is nothing to say.
|
|
INIA 0x01
|
|
SETD.0 AlarmSaid
|
|
STA.0
|
|
CALL alarm
|
|
watchFuelDone:
|
|
RET
|
|
|
|
; ---- The alarm, on channel two ----
|
|
;
|
|
; Effects count down from the top: the bang has three and this has two. They are separate
|
|
; channels rather than one reused, because a crash while the warning is still sounding should
|
|
; not cut it off - and on a machine with four voices there is no reason to be clever about it.
|
|
putAlarm:
|
|
INIA 0x02
|
|
OUTA 0x41
|
|
SETD.0 AlarmPatch
|
|
CALL playPatch
|
|
RET
|
|
|
|
alarm:
|
|
INIA 0x02
|
|
OUTA 0x41
|
|
INIA 0d72 ; An octave over middle C, which is where a warning lives.
|
|
OUTA 0x44
|
|
RET
|
|
|
|
; ---- A key, or a button ----
|
|
;
|
|
; Somebody flying on a controller should not have to reach for the keyboard to say "yes, I
|
|
; read that". A or Start does it, and so does any key - asked for rather than waited on, so
|
|
; one already pressed counts.
|
|
waitKey:
|
|
CALL readPad
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x50 ; A, or Start.
|
|
AND
|
|
BNQ waitKeyDone
|
|
INA 0x01
|
|
INIB 0x01 ; READY
|
|
AND
|
|
BRQ waitKey
|
|
INA 0x00
|
|
waitKeyDone:
|
|
RET
|
|
|
|
; ---- Put down exactly on the ground ----
|
|
;
|
|
; The feet are the top plus eight, so the top is the surface less eight - times sixteen, which
|
|
; is what the position is measured in. Three turns left of the shift register with the pixels
|
|
; in the low half, and the answer needs both halves because a screen is taller than 255
|
|
; sixteenths.
|
|
restOnSurface:
|
|
CALL groundLevel
|
|
SETD.0 GroundLow
|
|
LDA.0
|
|
SETD.1 DownLow
|
|
STA.1
|
|
SETD.0 GroundHigh
|
|
LDA.0
|
|
SETD.1 DownHigh
|
|
STA.1
|
|
RET
|
|
|
|
; ---- How far above the ground, as a bar up the left edge ----
|
|
;
|
|
; The orbit put the lander OFF THE TOP OF THE SCREEN, and an instrument panel that only works
|
|
; while the ground is in sight is no use to a game whose best flying happens above it. Both
|
|
; other bars are rates: they say how fast, and neither of them says where.
|
|
;
|
|
; Height above the surface underneath rather than above some fixed line, so it reads NOUGHT
|
|
; the moment the lander is down - a bar that still showed a third of itself while sitting on a
|
|
; pad would be an altimeter nobody could trust. It moves as the moon does underneath, which is
|
|
; not noise: passing over a mountain really does leave less room below.
|
|
;
|
|
; Half a pixel of bar to a pixel of sky. The flyable band is about 256 pixels from the ceiling
|
|
; to the lowest ground and the screen is 200 tall, so a bar drawn pixel for pixel would run
|
|
; off the top of the very screen it is trying to describe.
|
|
showHeight:
|
|
CALL groundLevel
|
|
SETD.0 GroundLow
|
|
LDA.0
|
|
SETD.1 DownLow
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.0 HeightLow
|
|
STQ.0
|
|
SETD.0 GroundHigh
|
|
LDA.0
|
|
SETD.1 DownHigh
|
|
LDB.1
|
|
SUB ; And the borrow the half below left, which is the other half.
|
|
SETD.0 HeightHigh
|
|
STQ.0
|
|
|
|
LDA.0 ; DP0 is still HeightHigh, from the store above.
|
|
INIB 0x80
|
|
AND
|
|
BNQ showHeightNone ; Negative: the feet are under the surface, so there is no sky.
|
|
LDA.0 ; The AND left A alone, so it still holds the high half.
|
|
INIB 0d32
|
|
CCF
|
|
SUB
|
|
BNC showHeightFull ; 8,192 sixteenths or more, which is the whole bar and then some.
|
|
|
|
LDA.0 ; DP0 still names the high half, and the compare did not touch A.
|
|
SETD.0 HeightLow
|
|
LDB.0
|
|
SHL
|
|
SHL ; ---- Over sixty four, which comes out in A ----
|
|
;
|
|
; A is the HIGH half of the shift register, so two turns LEFT of
|
|
; the whole sixteen bits leaves bits six to thirteen in A - which
|
|
; is the height over sixty four, in one instruction each and no
|
|
; division anywhere.
|
|
;
|
|
; Over sixty four rather than thirty two because the ceiling moved:
|
|
; the band is 368 pixels now, and half a pixel of bar to a pixel of
|
|
; sky would stand 184 tall and run off the top of the forty column
|
|
; screen it is drawn beside.
|
|
BRI showHeightReady
|
|
showHeightFull:
|
|
INIA 0d127
|
|
BRI showHeightReady
|
|
showHeightNone:
|
|
RSTA
|
|
showHeightReady:
|
|
SETD.1 HeightBar
|
|
STA.1
|
|
|
|
; ---- Grown upwards, from a fixed foot ----
|
|
;
|
|
; A sprite is placed by its TOP, so a bar that grows up is one whose top moves as its height
|
|
; changes. Both come out of the same number and the foot stays put.
|
|
SETD.0 HeightFoot
|
|
LDA.0
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
SETD.1 HeightAt
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
SUB ; And the borrow, which is the other half of the foot.
|
|
INCD.1
|
|
STQ.1
|
|
|
|
; Nought is the off switch, and it is the SIZE that switches it: a target height of nought
|
|
; is the natural height, which would be a tile sitting on the ground pretending to be sky.
|
|
SETD.0 HeightBar
|
|
LDA.0
|
|
BRA showHeightEmpty
|
|
INIA 0x11
|
|
BRI showHeightSized
|
|
showHeightEmpty:
|
|
RSTA
|
|
showHeightSized:
|
|
SETD.1 HeightSize
|
|
STA.1
|
|
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x30
|
|
OUTA 0xE5 ; Sprite three begins forty eight bytes in.
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block.
|
|
INIA 0x05
|
|
OUTA 0xE9 ; ---- Scheme five, magenta ----
|
|
;
|
|
; Red and green are taken and they MEAN something here: how fast,
|
|
; and whether it can be landed with. Neither is a thing an altitude
|
|
; is, so magenta is the panel's other job - where the lander is
|
|
; rather than how it is going - and the marks below share it.
|
|
;
|
|
; Cyan was the first choice and it is the colour of a landing pad.
|
|
; The pads are checked as whole cells of it, so a three pixel bar
|
|
; in the same colour would have made that check count the panel.
|
|
INIA 0d8
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; X, hard against the left edge and clear of the moon's middle.
|
|
SETD.0 HeightAt
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; Y.
|
|
SETD.0 HeightSize
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
INIA 0d3
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Three pixels wide.
|
|
SETD.0 HeightBar
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; The height, which is the altitude. NOUGHT DRAWS NOTHING.
|
|
OUTA 0xE9 ; And no depth, A being nought already.
|
|
RET
|
|
|
|
; ---- A state, placed rather than flown to ----
|
|
;
|
|
; Sixteen bytes read straight over the numbers the program would otherwise start with. If the
|
|
; file is not there nothing happens and the defaults stand, so a disk without one is the game
|
|
; as it has always been.
|
|
;
|
|
; ---- Which exists because some things cannot be flown to ----
|
|
;
|
|
; A successful dock needs the lander alongside the station and matched, and NINETY SIX pad
|
|
; files failed to get there - not for want of trying, but because climbing spends sideways
|
|
; speed, so a lander cannot rise while matched and arrives slower than orbital every time.
|
|
; Reaching it wants two burns and a phase, which is a real manoeuvre rather than something a
|
|
; straight line of button presses stumbles into. The same was true of the orbit, whose check
|
|
; had to be deleted for measuring the boot time instead of the physics, and of the ride, and
|
|
; of the strike, whose window is nineteen frames wide. Four things queued behind one facility.
|
|
;
|
|
; BINARY, because that is what a file is on this machine. A tool to build one from readable
|
|
; text is a small job for another day; until then the tests write the bytes, which is plain
|
|
; enough while the fields are named.
|
|
loadState:
|
|
SETD.0 StateName
|
|
SETD.1 StateBuffer
|
|
SWI osFileRead
|
|
BNQ loadStateNone ; Not there, or would not read, and either way the defaults stand.
|
|
|
|
SETD.0 StateAcross
|
|
SETD.1 AcrossLow
|
|
CALL copyWord
|
|
SETD.0 StateDown
|
|
SETD.1 DownLow
|
|
CALL copyWord
|
|
SETD.0 StateSpeedAcross
|
|
SETD.1 SpeedAcross
|
|
CALL copyWord
|
|
SETD.0 StateSpeedDown
|
|
SETD.1 SpeedDown
|
|
CALL copyWord
|
|
SETD.0 StateStation
|
|
SETD.1 StationLow
|
|
CALL copyWord
|
|
SETD.0 StateStationDown
|
|
SETD.1 StationDownLow
|
|
CALL copyWord
|
|
|
|
SETD.0 StateFuel
|
|
LDA.0
|
|
SETD.1 Fuel
|
|
STA.1
|
|
SETD.0 StateWide
|
|
LDA.0
|
|
SETD.1 Wide
|
|
STA.1
|
|
SETD.0 StateLanded
|
|
LDA.0
|
|
SETD.1 Landed
|
|
STA.1
|
|
loadStateNone:
|
|
RET
|
|
|
|
; Two bytes, from where DP0 says to where DP1 does.
|
|
copyWord:
|
|
LDA.0
|
|
STA.1
|
|
INCD.0
|
|
INCD.1
|
|
LDA.0
|
|
STA.1
|
|
RET
|
|
|
|
; ---- Two zoom levels, which the device turns out to have already had ----
|
|
;
|
|
; Forty columns and eighty are the same map, the same 8x8 cells and the same engine; only how
|
|
; many of them fit on the screen differs. The map is 128 by 128 either way and the moon is
|
|
; exactly 128 columns of it, so forty shows under a third of the moon and eighty shows nearly
|
|
; two thirds - and the front end scales whatever it is handed up to the same window, so 320 by
|
|
; 200 at four times and 640 by 400 at twice fill the same glass.
|
|
;
|
|
; THAT IS A ZOOM, and nothing in the video device had to change to get it. What has to change
|
|
; is every screen coordinate in this program, because the middle of the screen is 160 on one
|
|
; and 320 on the other. They all live in one block.
|
|
setView:
|
|
SETD.0 ViewNarrow
|
|
SETD.1 Wide
|
|
LDA.1
|
|
BRA setViewFrom
|
|
SETD.0 ViewWide
|
|
setViewFrom:
|
|
SETD.1 View
|
|
INIA 0d17
|
|
SETD.2 ViewLeft
|
|
STA.2
|
|
setViewByte:
|
|
LDA.0
|
|
STA.1
|
|
INCD.0
|
|
INCD.1
|
|
SETD.2 ViewLeft
|
|
LDA.2
|
|
DECA
|
|
STA.2
|
|
BNA setViewByte
|
|
RET
|
|
|
|
; ---- Out for the orbit, in for the landing ----
|
|
;
|
|
; The moon is redrawn because it is filled as many rows deep as the mode shows, and a moon
|
|
; drawn 25 deep on a screen showing 50 is a moon floating over nothing. The fixed sprites go
|
|
; again because their places moved with the middle.
|
|
toggleView:
|
|
SETD.0 Wide
|
|
LDA.0
|
|
INIB 0x01
|
|
XOR
|
|
STQ.0
|
|
CALL setView
|
|
SETD.0 Wide
|
|
LDA.0
|
|
OUTA 0x31
|
|
SETD.0 ScrollRow
|
|
LDA.0
|
|
OUTA 0x34 ; The wide view starts higher up, which is where the sky is.
|
|
CALL drawMoon
|
|
CALL putLander
|
|
CALL putGauge
|
|
CALL putOrbital
|
|
RET
|
|
|
|
; ---- One press, one swap ----
|
|
;
|
|
; A pad is LEVEL and not an event: it says what is held NOW, sixty times a second. A view that
|
|
; swapped whenever B was down would swap sixty times a second, which is not a zoom, it is a
|
|
; strobe. So the swap happens on the frame the button goes down, and the frame it comes up is
|
|
; remembered for the comparison rather than acted on.
|
|
zoomButton:
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x20 ; B.
|
|
AND
|
|
MVQA
|
|
SETD.1 ZoomWas
|
|
LDB.1
|
|
STA.1 ; Now becomes what "last frame" means next time round.
|
|
CCF
|
|
SUB
|
|
BRQ zoomDone ; The same as last frame, so nothing has been pressed or let go.
|
|
LDA.1 ; DP1 still names it, and it now holds this frame's answer.
|
|
BRA zoomDone ; It changed to NOT held, which is the release and not the press.
|
|
CALL toggleView
|
|
zoomDone:
|
|
RET
|
|
|
|
; ---- Where orbital speed is, marked on the drift bar ----
|
|
;
|
|
; The bar says how fast sideways and the physics says 64 sixteenths is the speed at which the
|
|
; swing outwards cancels the pull. Without a mark on it that number is FOLKLORE: a pilot can
|
|
; feel that somewhere around here the falling stops, and has no way to see where. With one,
|
|
; the bar reaching the mark IS circular orbit, under it is falling and past it is climbing,
|
|
; and the whole mechanic becomes something read at a glance instead of guessed at.
|
|
;
|
|
; One each way, because a moon that wraps can be gone round in either direction.
|
|
;
|
|
; Placed so the BAR'S EDGE MEETS THE MARK at exactly orbital speed. The rightward bar starts
|
|
; at the middle and runs 64 pixels to 223, so its mark begins at 224; the leftward one ends at
|
|
; the middle and starts at 96, so its mark is the two pixels before that. Adjacent rather than
|
|
; overlapping, or a bar at orbital speed would hide the thing it is being measured against.
|
|
;
|
|
; Written once. They never move, and a sprite table is a memory rather than a display list.
|
|
putOrbital:
|
|
; The leftward mark: sixty four back from the middle, and two more for its own width, so the
|
|
; bar's leading edge meets it rather than the bar covering it up.
|
|
SETD.0 MiddleX
|
|
LDA.0
|
|
INIB 0d66
|
|
CCF
|
|
SUB
|
|
SETD.1 MarkAt
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
SUB ; And the borrow, because the middle can be 320.
|
|
INCD.1
|
|
STQ.1
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x40
|
|
OUTA 0xE5 ; Sprite four begins sixty four bytes in.
|
|
CALL putMark
|
|
|
|
; And the rightward one, sixty four on from it.
|
|
SETD.0 MiddleX
|
|
LDA.0
|
|
INIB 0d64
|
|
CCF
|
|
ADD
|
|
SETD.1 MarkAt
|
|
STQ.1
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
ADD
|
|
INCD.1
|
|
STQ.1
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x50
|
|
OUTA 0xE5 ; And sprite five, sixteen bytes after it.
|
|
CALL putMark
|
|
RET
|
|
|
|
; One mark, at the column in MarkAt, straddling the drift bar so the bar meets it end on.
|
|
putMark:
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block, which is what every bar here is made of.
|
|
INIA 0x05
|
|
OUTA 0xE9 ; Magenta, the same as the height bar: both say where, not how fast.
|
|
; White was the first choice and white is what TEXT is, so the
|
|
; ceiling check - which counts white to find its warning - started
|
|
; counting these instead and read a warning that was never up.
|
|
SETD.1 MarkAt
|
|
LDA.1
|
|
OUTA 0xE9
|
|
INCD.1
|
|
LDA.1
|
|
OUTA 0xE9 ; X.
|
|
SETD.0 BarY
|
|
LDA.0
|
|
INIB 0d4
|
|
CCF
|
|
SUB
|
|
OUTQ 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
RSTB
|
|
SUB
|
|
OUTQ 0xE9 ; Y, four pixels above the bar it brackets.
|
|
INIA 0x11
|
|
OUTA 0xE9 ; One tile by one, however small it ends up drawn.
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
INIA 0d2
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Two pixels wide.
|
|
INIA 0d11
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; And eleven tall, so it shows above and below a three pixel bar.
|
|
OUTA 0xE9 ; No depth, A being nought already.
|
|
RET
|
|
|
|
; ---- Where a lander standing on the ground below would have its top ----
|
|
;
|
|
; The same sum, wanted in two places: putting one down on the surface, and measuring how far
|
|
; above the surface one is. In sixteenths, because that is what a height is measured in.
|
|
groundLevel:
|
|
SETD.0 SurfaceAt
|
|
LDA.0
|
|
INIB 0d8
|
|
CCF
|
|
SUB
|
|
MVQB
|
|
RSTA
|
|
SHL
|
|
SHL
|
|
SHL
|
|
SHL ; Four turns, which is times sixteen.
|
|
SETD.0 GroundHigh
|
|
STA.0
|
|
RSTA
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
SETD.0 GroundLow
|
|
STA.0
|
|
RET
|
|
|
|
; ---- A line of text, into the window ----
|
|
;
|
|
; DP0 names a string. It goes into window row one and the rest of the row is BLANKED, so a
|
|
; short message never leaves the tail of a long one behind it.
|
|
;
|
|
; Into the window rather than out of the console, which is the whole of two bugs at once. The
|
|
; console draws into the map, so a message printed while flying was a message the lander then
|
|
; flew over - and printing scrolls, so every one of them moved the whole world up a row.
|
|
; The window is at a screen position and forty cells wide, and neither is true of it.
|
|
;
|
|
; The letters are ordinary tiles: the character generator starts at the space, so glyph n is
|
|
; character n less thirty two.
|
|
sayInWindow:
|
|
INIA 0d5
|
|
OUTA 0xE3
|
|
INIA 0xC1
|
|
OUTA 0xE4
|
|
RSTA
|
|
OUTA 0xE5 ; Window row one begins at 0xC100.
|
|
SETD.1 SayAt
|
|
STA.1
|
|
|
|
sayCell:
|
|
LDA.0
|
|
BRA sayBlank ; The string has ended, so the rest of the row is nothing.
|
|
INIB 0d32
|
|
CCF
|
|
SUB
|
|
OUTQ 0xE9
|
|
INIA 0x07
|
|
OUTA 0xE9 ; Scheme seven, white, which is nothing else on this screen.
|
|
INCD.0
|
|
BRI sayNext
|
|
sayBlank:
|
|
OUTA 0xE9
|
|
OUTA 0xE9 ; A is nought here, and DP0 stays put so it stays nought.
|
|
sayNext:
|
|
SETD.1 SayAt
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
SETD.2 RowCells
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BNQ sayCell
|
|
RET
|
|
|
|
; ---- What a base does when a lander arrives ----
|
|
;
|
|
; Empty and at a base: it hands over cargo for the base ACROSS THE MOON, two along, so the
|
|
; pairs are cyan with red and green with blue. Carrying and at the right base: it takes the
|
|
; cargo and pays in fuel. Carrying and at the wrong one: nothing, which is the whole reason
|
|
; the destination is on the screen.
|
|
arrive:
|
|
SETD.1 IsPad
|
|
SETD.0 LandColumn
|
|
LDA.0
|
|
DPUA.1
|
|
LDA.1
|
|
BRA arriveNowhere
|
|
DECA
|
|
SETD.1 AtBase
|
|
STA.1
|
|
|
|
SETD.0 Carrying
|
|
LDA.0
|
|
BRA arriveLoad
|
|
|
|
; Carrying something. Is this where it goes?
|
|
DECA
|
|
LDB.1 ; DP1 is still AtBase, from storing it above.
|
|
CCF
|
|
SUB
|
|
BNQ arriveWrong
|
|
|
|
RSTA
|
|
STA.0 ; And DP0 is still Carrying, from reading it.
|
|
CALL payFuel
|
|
SETD.0 DeliveredText
|
|
CALL sayInWindow
|
|
RET
|
|
|
|
arriveLoad:
|
|
; Two along, round a moon of four bases, which is the far side of it.
|
|
SETD.1 AtBase
|
|
LDA.1
|
|
INIB 0x02
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
INIB 0x03
|
|
AND
|
|
MVQA
|
|
INCA ; Plus one, so nought can go on meaning nothing.
|
|
SETD.0 Carrying
|
|
STA.0
|
|
SETD.0 LoadedText
|
|
CALL sayInWindow
|
|
RET
|
|
|
|
arriveWrong:
|
|
SETD.0 WrongText
|
|
CALL sayInWindow
|
|
RET
|
|
|
|
arriveNowhere:
|
|
SETD.0 LandedText
|
|
CALL sayInWindow
|
|
RET
|
|
|
|
; ---- Paid ----
|
|
;
|
|
; Eighty units, and a tank that will not take more than it holds. The carry from a byte's
|
|
; worth of adding is what says it went past, which is the same flag a sixteen bit sum uses to
|
|
; run one half into the other.
|
|
payFuel:
|
|
; Filled up, so the warning is allowed to come again. Eighty units always clears a quarter
|
|
; tank, so this needs no test of its own.
|
|
RSTA
|
|
SETD.0 AlarmSaid
|
|
STA.0
|
|
SETD.0 Fuel
|
|
LDA.0
|
|
INIB 0d80
|
|
CCF
|
|
ADD
|
|
BRC payFuelFull
|
|
STQ.0
|
|
RET
|
|
payFuelFull:
|
|
INIA 0xFF
|
|
STA.0
|
|
RET
|
|
|
|
; ---- How fast downwards, as a bar down the side ----
|
|
;
|
|
; The same idea stood on its end. Sprite two, three pixels wide, growing DOWN from the middle
|
|
; of the screen while falling and UP while climbing - so which way is as plain as how fast,
|
|
; and a lander holding its height has no bar at all.
|
|
;
|
|
; Two bars and two limits is the whole instrument panel: when both are green the lander can be
|
|
; put down, and that is a question a person can answer at a glance instead of by counting.
|
|
showFall:
|
|
SETD.0 SpeedDown
|
|
CALL magnitude
|
|
SETD.1 GentleDown
|
|
CALL barColour
|
|
SETD.1 FallColour
|
|
STQ.1
|
|
|
|
; Clamped, and to less than the drift bar because there is half a screen either way.
|
|
SETD.0 DriftHigh
|
|
LDA.0
|
|
BNA fallBarWide
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
INIB 0d90
|
|
CCF
|
|
SUB
|
|
BRC fallBarReady
|
|
fallBarWide:
|
|
INIA 0d90
|
|
SETD.0 DriftLow
|
|
STA.0
|
|
|
|
fallBarReady:
|
|
; No tiles at all when there is nothing to show, which is the off switch: a target size of
|
|
; nought is the NATURAL size, not an empty sprite.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
BRA fallBarEmpty
|
|
INIA 0x11
|
|
BRI fallBarSized
|
|
fallBarEmpty:
|
|
RSTA
|
|
fallBarSized:
|
|
SETD.1 FallSize
|
|
STA.1
|
|
|
|
; Where it starts: the middle going down, and that much back going up.
|
|
SETD.0 MiddleY
|
|
LDA.0
|
|
SETD.0 DriftLeftward
|
|
LDB.0
|
|
BRB fallBarAt ; Nought is downward, so it starts at the middle.
|
|
SETD.0 DriftLow
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
MVQA
|
|
fallBarAt:
|
|
SETD.1 FallAt
|
|
STA.1
|
|
|
|
INIA 0d4
|
|
OUTA 0xE3
|
|
INIA 0xC0
|
|
OUTA 0xE4
|
|
INIA 0x20
|
|
OUTA 0xE5 ; Sprite two begins thirty two bytes in.
|
|
INIA 0xC8
|
|
OUTA 0xE9 ; The solid block.
|
|
SETD.0 FallColour
|
|
LDA.0
|
|
OUTA 0xE9
|
|
SETD.0 FallX
|
|
LDA.0
|
|
OUTA 0xE9
|
|
INCD.0
|
|
LDA.0
|
|
OUTA 0xE9 ; X, in two bytes, because the screen is wider than one.
|
|
SETD.0 FallAt
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Y.
|
|
SETD.0 FallSize
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; No flags.
|
|
INIA 0x03
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; Three pixels wide.
|
|
SETD.0 DriftLow
|
|
LDA.0
|
|
OUTA 0xE9
|
|
RSTA
|
|
OUTA 0xE9 ; And as tall as the lander is fast.
|
|
OUTA 0xE9 ; No depth.
|
|
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.
|
|
;
|
|
; ---- And the answer comes back in Q ----
|
|
;
|
|
; It came back in A first, which is a lie a subroutine cannot tell: RET puts A back the way it
|
|
; found it, so the caller wrote out the high byte of the position it had passed in. The fine
|
|
; register was computed inline and was right, the coarse register was not, and the picture
|
|
; scrolled smoothly within a cell and never advanced one. Q is the only register that crosses
|
|
; a RET, which is why every answer in this program comes back in it.
|
|
;
|
|
; B cannot be read at all. Adding nothing to it puts it in Q, which is where this wanted to
|
|
; be anyway.
|
|
eighth:
|
|
SHR
|
|
SHR
|
|
SHR
|
|
RSTA
|
|
CCF
|
|
ADD
|
|
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.
|
|
; ---- Which is over a base, because that is where a day starts ----
|
|
;
|
|
; 0x14A0 sixteenths is 330 pixels, which is column 41 - inside the pad carved at 40. Starting
|
|
; in the middle of nowhere meant a straight descent landed in the middle of nowhere, which is
|
|
; a fine thing to be able to do and a poor thing to have to.
|
|
AcrossLow:
|
|
0xA0
|
|
AcrossHigh:
|
|
0x14
|
|
DownLow:
|
|
0x00
|
|
DownHigh:
|
|
0x02 ; A little way down from the top.
|
|
SpeedAcross:
|
|
0x00 0x00
|
|
SpeedDown:
|
|
0x00
|
|
SpeedDownHigh:
|
|
0x00
|
|
|
|
; ---- The four numbers the feel lives in ----
|
|
;
|
|
; Adjacent on purpose, because tuning them is what the first rung is for.
|
|
Gravity:
|
|
0x01 0x00
|
|
FallEvery:
|
|
0d10 ; ---- Gravity one frame in ten ----
|
|
;
|
|
; Every frame was Jupiter and one in six was still touchy: the
|
|
; ratio between thrust and gravity is the FEEL, and how often the
|
|
; tick comes round is how fast that feel arrives. Slower ticks are
|
|
; the same lander with more time to think in.
|
|
FallTick:
|
|
0d1
|
|
; A press on a machine with no pad, which has to be a whole burn because it happens once.
|
|
ThrustUp:
|
|
0xF8 0xFF ; Eight sixteenths upwards, which is minus eight.
|
|
ThrustDown:
|
|
0x04 0x00 ; Four down, which is half of the eight that lifts.
|
|
ThrustLeft:
|
|
0xFC 0xFF ; Four to the left.
|
|
ThrustRight:
|
|
0x04 0x00
|
|
|
|
; ---- And a held one, which happens on every tick it is held for ----
|
|
;
|
|
; TWO AGAINST GRAVITY'S ONE, which makes climbing and falling the same speed: hold it and you
|
|
; rise as fast as letting go drops you. Three was the first try and it left the moon in about
|
|
; a second of holding. The ratio between these and Gravity is the whole feel of the thing and
|
|
; it is one byte each.
|
|
PadUp:
|
|
0xFE 0xFF ; Minus two.
|
|
; ---- Sideways, at half the vertical ----
|
|
;
|
|
; ONE, not two. There is no air on a moon, so nothing slows a drift but the opposite thruster
|
|
; and stopping means cancelling the velocity exactly. At two a tick the smallest correction
|
|
; was twice as big as it needed to be and overshooting was the normal outcome.
|
|
PadDown:
|
|
0x01 0x00 ; Plus one: half of up, and exactly gravity's own step.
|
|
PadLeft:
|
|
0xFF 0xFF ; Minus one.
|
|
PadRight:
|
|
0x01 0x00
|
|
Held:
|
|
0x00
|
|
HasPad:
|
|
0x00
|
|
DriftLow:
|
|
0x00
|
|
DriftHigh:
|
|
0x00
|
|
DriftLeftward:
|
|
0x00
|
|
DriftAt:
|
|
0x00 0x00
|
|
DriftSize:
|
|
0x00
|
|
DriftColour:
|
|
0x00
|
|
FallColour:
|
|
0x00
|
|
FallSize:
|
|
0x00
|
|
FallAt:
|
|
0x00
|
|
|
|
; ---- The tank ----
|
|
;
|
|
; One byte, and a byte is enough: over eight it is a bar of up to 31 cells, and at one unit a
|
|
; thruster a tick it is about forty seconds of holding the engine open. Sixteen bits would be
|
|
; more arithmetic for a number nobody reads to the unit.
|
|
Fuel:
|
|
0xFF
|
|
GaugeColour:
|
|
0x00
|
|
; Whether the quarter tank warning has been given since the last time it was filled.
|
|
AlarmSaid:
|
|
0x00
|
|
GaugeLeft:
|
|
0x00
|
|
GaugeAt:
|
|
0x00
|
|
SurfaceAt:
|
|
0x00
|
|
|
|
; ---- What counts as gentle ----
|
|
;
|
|
; In sixteenths of a pixel a frame, so twelve is three quarters of a pixel a frame and eight
|
|
; is half of one. Sideways is the tighter of the two on purpose: a lander that arrives
|
|
; straight down at three quarters of a pixel is a landing, and one sliding at the same speed
|
|
; is a lander on its side.
|
|
GentleDown:
|
|
0d12
|
|
GentleAcross:
|
|
0d8
|
|
; ---- The numbers that change when the screen does ----
|
|
;
|
|
; Copied over from whichever table below matches the mode, so a gauge reads a variable and
|
|
; never has to know which screen it is drawing on. The order here and in both tables is the
|
|
; same order and has to stay that way: setView copies the block straight across.
|
|
View:
|
|
HalfScreen:
|
|
0xA0 0x00
|
|
ShipX:
|
|
0x9C 0x00
|
|
MiddleX:
|
|
0xA0 0x00
|
|
BarY:
|
|
0xBC 0x00
|
|
FallX:
|
|
0x2C 0x01
|
|
MiddleY:
|
|
0d100
|
|
HeightFoot:
|
|
0xB4 0x00
|
|
RowCells:
|
|
0d40
|
|
ScrollRow:
|
|
0d0
|
|
ShipYAdd:
|
|
0x00 0x00
|
|
ViewLeft:
|
|
0x00 ; How much of the block is still to copy.
|
|
|
|
; Forty columns: 320 by 200, and the moon 25 rows deep because that is all there is to see.
|
|
ViewNarrow:
|
|
0xA0 0x00 ; Half a screen, which is where the lander is held.
|
|
0x9C 0x00 ; And the lander itself, half a tile back from it.
|
|
0xA0 0x00 ; The middle, where both bars start.
|
|
0xBC 0x00 ; The drift bar's row, twelve pixels off the bottom.
|
|
0x2C 0x01 ; The fall bar's column, twenty pixels in from the right.
|
|
0d100 ; Half the height, which is where the fall bar starts.
|
|
0xB4 0x00 ; The height bar's foot.
|
|
0d40 ; Cells to a window row.
|
|
0d0 ; The map row the top of the screen is, and
|
|
0x00 0x00 ; how far the lander's own Y has to move with it.
|
|
|
|
; Eighty columns: 640 by 400, the same cells at half the size, so twice as much moon either
|
|
; way. Every screen number doubles except the ones measured from an edge, which are the edge
|
|
; less the same margin - a panel should sit the same distance in whichever screen it is on.
|
|
ViewWide:
|
|
0x40 0x01 ; 320.
|
|
0x3C 0x01 ; 316.
|
|
0x40 0x01 ; 320.
|
|
0x84 0x01 ; 388, which is twelve off the bottom of four hundred.
|
|
0x6C 0x02 ; 620, twenty in from the right of six hundred and forty.
|
|
0d200
|
|
0x7C 0x01 ; 380.
|
|
0d80
|
|
0d104 ; Twenty four rows ABOVE the world's origin, which is the point.
|
|
0xC0 0x00 ; So the lander's Y moves 192 pixels down to match.
|
|
|
|
; Which of the two is up. Nought is forty columns, which is what the program starts in.
|
|
Wide:
|
|
0x00
|
|
|
|
; ---- The sixteen bytes a state file holds, in the order it holds them ----
|
|
;
|
|
; Named rather than numbered, so whatever writes one and this that reads it are describing the
|
|
; same fields. Low byte first, the way every other word here is.
|
|
StateName:
|
|
"/lander.state"
|
|
StateBuffer:
|
|
StateAcross:
|
|
0x00 0x00 ; Where round the moon, in sixteenths.
|
|
StateDown:
|
|
0x00 0x00 ; And how far down, which is negative above the origin.
|
|
StateSpeedAcross:
|
|
0x00 0x00
|
|
StateSpeedDown:
|
|
0x00 0x00
|
|
StateStation:
|
|
0x00 0x00 ; Where the station is, so a rendezvous can start alongside one.
|
|
StateStationDown:
|
|
0x00 0x00
|
|
StateFuel:
|
|
0x00
|
|
StateWide:
|
|
0x00 ; Which screen to start in: some of this is only visible wide.
|
|
StateLanded:
|
|
0x00
|
|
StateSpare:
|
|
0x00 ; Room, because sixteen is a rounder number than fifteen.
|
|
|
|
; ---- And a whole block of room after them ----
|
|
;
|
|
; osFileRead lands a file in Data Memory and a file is stored in blocks of 256, so reading
|
|
; sixteen bytes into sixteen bytes of room writes over whatever follows. It did: the first
|
|
; version of this put the buffer in front of the view tables and the program read its state,
|
|
; wiped the numbers every gauge draws from, and left immediately - "finished" and back to the
|
|
; prompt, with nothing on the screen to say why.
|
|
#Reserve 0d240
|
|
; What B was doing last frame, because a pad says what is held and not what was pressed.
|
|
ZoomWas:
|
|
0x00
|
|
|
|
PixelLow:
|
|
0x00
|
|
PixelHigh:
|
|
0x00
|
|
Height:
|
|
0x00
|
|
Row:
|
|
0x00
|
|
Column:
|
|
0x00
|
|
Seed:
|
|
0x5D
|
|
KeyHeld:
|
|
0x00
|
|
Flying:
|
|
0x01
|
|
|
|
Terrain:
|
|
#Reserve 0d128
|
|
|
|
; One byte a column: whether a lander touching down there is at a base. Written by the carving
|
|
; and read while drawing and while landing, which is the whole reason it is an array rather
|
|
; than four comparisons done again every time somebody asks.
|
|
IsPad:
|
|
#Reserve 0d128
|
|
|
|
PadTable:
|
|
0d8 0d40 0d72 0d104 ; Four bases, evenly round a moon 128 columns about.
|
|
|
|
; ---- Their names, which are colours ----
|
|
;
|
|
; A base is told from a base by the scheme its pad is drawn in, so "the cyan one" is a thing a
|
|
; person can say and a thing the machine already knows. Yellow is missing on purpose: it is
|
|
; the lander, and a base the same colour as the thing landing on it would be a poor joke.
|
|
;
|
|
; Cargo goes to the base ACROSS THE MOON, two along, so the pairs are cyan with red and green
|
|
; with blue. That is what makes the wrapping surface a route rather than scenery.
|
|
PadColour:
|
|
0d2 0d6 0d4 0d1 ; Green, cyan, blue, red.
|
|
PadNames:
|
|
0x00 ; Room kept: the messages name colours in words below.
|
|
PadsLeft:
|
|
0x00
|
|
PadWide:
|
|
0x00
|
|
PadHeight:
|
|
0x00
|
|
LandColumn:
|
|
0x00
|
|
AtBase:
|
|
0x00
|
|
|
|
; Where the orbit sum has got to. The sideways speed goes in here every tick and the carry out
|
|
; of it is a tick of gravity that never happened.
|
|
OrbitAt:
|
|
0x00
|
|
|
|
; How hard the swing is pulling or pushing this tick, and which way. And one step upwards,
|
|
; which is the only thing here that gravity did not already need.
|
|
OrbitStep:
|
|
0x00
|
|
|
|
; Ground level under the lander, the height above it, and what the bar makes of that.
|
|
GroundLow:
|
|
0x00
|
|
GroundHigh:
|
|
0x00
|
|
HeightLow:
|
|
0x00
|
|
HeightHigh:
|
|
0x00
|
|
HeightBar:
|
|
0x00
|
|
HeightAt:
|
|
0x00 0x00
|
|
HeightSize:
|
|
0x00
|
|
MarkAt:
|
|
0x00 0x00
|
|
Outward:
|
|
0x00
|
|
Lift:
|
|
0xFF 0xFF
|
|
|
|
; Where the height-for-speed trade has got to, and which way the lander was going when the
|
|
; product was worked out. One step of sideways speed, either way.
|
|
ExchangeAt:
|
|
0x00
|
|
ExchangeStep:
|
|
0x00
|
|
AcrossWasLeft:
|
|
0x00
|
|
StepUp:
|
|
0x01 0x00
|
|
StepDown:
|
|
0xFF 0xFF
|
|
SayAt:
|
|
0x00
|
|
|
|
; ---- What is aboard, and whether the feet are down ----
|
|
;
|
|
; Carrying is the destination base plus one, so nought means an empty hold and the number is
|
|
; still one byte. Landed is what stops a base handing out cargo sixty times a second while a
|
|
; lander sits on it.
|
|
Carrying:
|
|
0x00
|
|
Landed:
|
|
0x00
|
|
; Where showLander put the lander this frame, which is where the flames have to meet it.
|
|
ShipTopLow:
|
|
0x00
|
|
ShipTopHigh:
|
|
0x00
|
|
|
|
; ---- Six pieces of lander, and where each is going ----
|
|
;
|
|
; Interleaved, four bytes to a piece: where it is across, then where it is down. The speeds
|
|
; beside them are a rough hexagon, so it comes apart evenly rather than in a line.
|
|
Spark:
|
|
#Reserve 0d24
|
|
; The speeds in use this burst, copied over from one of the three below.
|
|
BurstV:
|
|
#Reserve 0d24
|
|
|
|
; A lander coming apart: a rough hexagon at two pixels a frame, so it goes evenly.
|
|
SparkV:
|
|
0x02 0x00 0x00 0x00
|
|
0x01 0x00 0x02 0x00
|
|
0xFF 0xFF 0x02 0x00
|
|
0xFE 0xFF 0x00 0x00
|
|
0xFF 0xFF 0xFE 0xFF
|
|
0x01 0x00 0xFE 0xFF
|
|
|
|
; Dust off a landing: sideways and UP, because that is where kicked dust goes, and none of it
|
|
; downwards because there is ground in the way.
|
|
DustV:
|
|
0x03 0x00 0x00 0x00
|
|
0xFD 0xFF 0x00 0x00
|
|
0x02 0x00 0xFF 0xFF
|
|
0xFE 0xFF 0xFF 0xFF
|
|
0x01 0x00 0xFE 0xFF
|
|
0xFF 0xFF 0xFE 0xFF
|
|
|
|
; Gas out of a docking port: small, even, and in every direction, because nothing is in the way.
|
|
GasV:
|
|
0x02 0x00 0x00 0x00
|
|
0xFE 0xFF 0x00 0x00
|
|
0x01 0x00 0x01 0x00
|
|
0xFF 0xFF 0x01 0x00
|
|
0x01 0x00 0xFF 0xFF
|
|
0xFF 0xFF 0xFF 0xFF
|
|
|
|
SparkAt:
|
|
0x00
|
|
SparkSlot:
|
|
0x00
|
|
SparkCount:
|
|
0x00
|
|
BurstLeft:
|
|
0x00
|
|
BurstColour:
|
|
0x00
|
|
BurstOXLow:
|
|
0x00
|
|
BurstOXHigh:
|
|
0x00
|
|
BurstOYLow:
|
|
0x00
|
|
BurstOYHigh:
|
|
0x00
|
|
CopyLeft:
|
|
0x00
|
|
HideSlot:
|
|
0x00
|
|
PatchLeft:
|
|
0x00
|
|
|
|
; What a flame is being asked to be, before it is written out.
|
|
FlameSlot:
|
|
0x00
|
|
FlameTile:
|
|
0x00
|
|
FlameXLow:
|
|
0x00
|
|
FlameXHigh:
|
|
0x00
|
|
FlameYLow:
|
|
0x00
|
|
FlameYHigh:
|
|
0x00
|
|
FlameFlags:
|
|
0x00
|
|
FlameSize:
|
|
0x00
|
|
FlameOffLow:
|
|
0x00
|
|
FlameOffHigh:
|
|
0x00
|
|
FlameAway:
|
|
0x08 0x00 ; A tile past the lander, which is where a plume comes out.
|
|
FlameBack:
|
|
0xF8 0xFF ; And a tile the other way, for the engine pointing the other way.
|
|
|
|
; Holding onto the station, which is the same kind of rest as sitting on the ground.
|
|
Docked:
|
|
0x00
|
|
; Whether it has been clear of the station since it last let go. It starts clear, having never
|
|
; been attached to anything.
|
|
DockClear:
|
|
0x01
|
|
; Where the lander is sliding to, and how far it has to go to get there.
|
|
DockTargetLow:
|
|
0x00
|
|
DockTargetHigh:
|
|
0x00
|
|
EaseLow:
|
|
0x00
|
|
EaseHigh:
|
|
0x00
|
|
EaseForward:
|
|
0x08 0x00 ; Half a pixel a frame, which settles the worst gap in about half
|
|
EaseBackward:
|
|
0xF8 0xFF ; a second - slow enough to see and fast enough not to wait for.
|
|
DockGapLow:
|
|
0x00
|
|
DockGapHigh:
|
|
0x00
|
|
DockRiseLow:
|
|
0x00
|
|
DockRiseHigh:
|
|
0x00
|
|
DockVxLow:
|
|
0x00
|
|
DockVxHigh:
|
|
0x00
|
|
|
|
; Whether the lander is pinned against the ceiling, so the warning is said once rather than
|
|
; sixty times a second and taken away when it comes back down.
|
|
Adrift:
|
|
0x00
|
|
|
|
; ---- Forty cells is the row, so forty characters is the limit ----
|
|
LandedText:
|
|
"Nowhere in particular. Nothing here."
|
|
LoadedText:
|
|
"Loaded. Take it across the moon."
|
|
DeliveredText:
|
|
"Delivered. Eighty units of fuel."
|
|
WrongText:
|
|
"Not the base this cargo is for."
|
|
AdriftText:
|
|
"Leaving the moon. Nothing up here."
|
|
EmptyText:
|
|
0x00
|
|
CrashedText:
|
|
"Crashed."
|
|
DockedText:
|
|
"Docked. Eighty units of fuel."
|
|
StruckText:
|
|
"Wrecked against the station."
|
|
|
|
; ---- The station, which is a body and not a special case ----
|
|
;
|
|
; A circular orbit is exactly what a thing at orbital speed DOES under the rules already here:
|
|
; the pull and the swing cancel at 64 sixteenths, at any height, because gravity never falls
|
|
; off and the moon wraps. So the station needs no physics of its own - a position, a constant,
|
|
; and the same wrap the lander uses.
|
|
;
|
|
; Four rows above the world's origin, which is off the top of a forty column screen and
|
|
; comfortably inside a wide one. That is deliberate: it makes the station a reason to use the
|
|
; zoom rather than something that can be flown to without it.
|
|
;
|
|
; It starts half a moon away, so it has to be found. At four pixels a frame a lap is 256
|
|
; frames, a little over four seconds, so it will not stay lost for long.
|
|
StationLow:
|
|
0xA0
|
|
StationHigh:
|
|
0x34
|
|
StationDownLow:
|
|
0x00
|
|
StationDownHigh:
|
|
0xFA ; ---- Minus 1,536 sixteenths, which is twelve rows up ----
|
|
;
|
|
; About two thirds of the way from the ground to the ceiling. It
|
|
; was four rows up when four rows was most of the sky there was,
|
|
; which put it exactly where anything climbing away from the
|
|
; surface had to pass - and being run down there is not a hazard,
|
|
; it is a toll. Twelve rows up is clear of work near the ground and
|
|
; still a dozen rows under the ceiling.
|
|
StationSpeed:
|
|
0x40 0x00 ; Orbital speed, and the whole of its behaviour.
|
|
|
|
; How far round the moon the station is from the lander, and where that puts it on the screen.
|
|
GapLow:
|
|
0x00
|
|
GapHigh:
|
|
0x00
|
|
StationAt:
|
|
0x00 0x00
|
|
StationTop:
|
|
0x00 0x00
|
|
|
|
StationArt:
|
|
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
|
|
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
|
|
0x01 0x01 0x00 0x01 0x01 0x00 0x01 0x01
|
|
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
|
|
0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01
|
|
0x01 0x01 0x00 0x01 0x01 0x00 0x01 0x01
|
|
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
|
|
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
|
|
|
|
StationArtAt:
|
|
#Reserve 0d2
|
|
|
|
; ---- The plume, which hangs off whichever side the engine is pushing from ----
|
|
;
|
|
; Widest where it meets the lander and tapering away, so it reads as coming OUT rather than
|
|
; sitting there. One tile for up and down, since a vertical flip turns one into the other, and
|
|
; a second for the sides, because a flip cannot rotate a tile a quarter turn.
|
|
;
|
|
; Twenty four pixels each, on purpose: it makes a count of them mean something.
|
|
FlameDownArt:
|
|
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
|
|
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
|
|
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
|
|
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
|
|
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
|
|
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
|
|
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
|
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
|
FlameDownArtAt:
|
|
#Reserve 0d2
|
|
|
|
FlameSideArt:
|
|
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
|
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00
|
|
0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x00
|
|
0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x00
|
|
0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x00
|
|
0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x00
|
|
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00
|
|
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
|
FlameSideArtAt:
|
|
#Reserve 0d2
|
|
|
|
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
|
|
|
|
#Include math.asm
|
|
|
|
#Include boom.asm
|
|
|
|
#Include alarm.asm
|