The two LFOs lived in the Synth, so four channels shared them and whichever patch loaded last owned them for every voice at once. A sound with its LFO switched off silenced the trill under a sound that was still playing - which is what made Lunar Porter's low fuel warning intermittent: the first landing, docking or crash of a run took its trill away, and it was right again next time the machine started. The engine fix went upstream to soundThing and has come back. synth.c and synth.h are re-vendored at 71e3cb2, character for character bar the ASCII transliteration, and now carry two changes: the LFOs moved into the Voice, and synthSyncVoices carries a free LFO's cycle down alongside its rate. That second hunk does nothing here - it only matters to a caller that syncs voices, and this device never does, because syncing would flatten four channels into one instrument. It is taken so the vendored file stays identical in both trees, and it is commented as such. Upstream also found a bug in the original patch, in patchLoad, which is soundThing's own file and does not travel. Downstream the LFO parameter groups 0x60 and 0x70 now read the selected channel like every parameter beside them, so an LFO written to one channel is inaudible on the other three. Everything else about the device is unchanged. Lunar Porter keeps loading each patch immediately before its note, but for the smaller reason that now applies: the bang and the latch share channel three, and a channel used by two sounds has to be told which of them it is about to be. The comment that said otherwise, and the manual's warning about sharing, are rewritten as history rather than as a caveat. Tests/sound.sh's shared-LFO check is inverted to assert the fixed behaviour, with a third leg added: after proving another channel's patch leaves this one alone, it switches this channel's OWN LFO off and requires the pitch to move. Without that, both checks would pass on a device where writing an LFO did nothing at all. Routing either group back to voice 0 is caught. Cost, measured: four channels sounding continuously for 400 seconds of audio takes 5.0 s of wall clock against 4.59 s before, about 9% of total emulator time. Half of that is wasted on voices that cannot sound, since VOICE_COUNT is 8 and there are four channels; recovering it would mean diverging the vendored file, which is not worth it at this price.
4395 lines
120 KiB
NASM
4395 lines
120 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
|
|
|
|
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 thrustSound ; Which of the thrusters just lit, or just went out.
|
|
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.
|
|
CALL pendTick ; And whatever note is waiting to follow another.
|
|
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
|
|
|
|
INIA 0d36
|
|
SETD.0 LatchFirst
|
|
STA.0
|
|
INIA 0d48
|
|
SETD.0 LatchSecond
|
|
STA.0
|
|
CALL latch ; The same shape two octaves lower, which is the ground.
|
|
CALL runPend
|
|
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
|
|
INIA 0d60
|
|
SETD.0 LatchFirst
|
|
STA.0
|
|
INIA 0d72
|
|
SETD.0 LatchSecond
|
|
STA.0
|
|
CALL latch ; Middle C then the C above it: taking hold.
|
|
CALL runPend
|
|
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.
|
|
INIA 0d72
|
|
SETD.0 LatchFirst
|
|
STA.0
|
|
INIA 0d60
|
|
SETD.0 LatchSecond
|
|
STA.0
|
|
CALL latch ; The C above middle then middle: letting go.
|
|
CALL undockGas ; Both 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.
|
|
; ---- Channel three has two patches, and that is not a shortage ----
|
|
;
|
|
; The bang and the latch are the two OUTCOMES OF THE SAME EVENT: a lander arriving somewhere
|
|
; either arrives or does not, and a crash ends the run outright. They can never sound at once,
|
|
; so they share, and whichever is wanted loads itself at the moment. Fifty writes is nothing
|
|
; against a thing that happens once.
|
|
;
|
|
; That leaves nought and one for the thruster, which needs two of its own: a bang at ignition
|
|
; is a one-shot and the rumble under it is held, and a held note struck on the same channel
|
|
; would cut the bang off at the very moment it was meant to be heard.
|
|
boom:
|
|
INIA 0x03
|
|
OUTA 0x41
|
|
SETD.0 BoomPatch
|
|
CALL playPatch
|
|
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
|
|
|
|
; ---- Two notes, quickly, which is what a latch sounds like ----
|
|
;
|
|
; Up for arriving and down for leaving, and two octaves lower for the ground - the same shape
|
|
; said in a different register, because a lander setting down and a lander taking hold of a
|
|
; station are the same kind of event.
|
|
;
|
|
; The second note is PENDING rather than played: at an undocking the pilot is mid-burn and
|
|
; stopping the world for an eighth of a second to fit a note in would be felt as the controls
|
|
; sticking. Where the game is stopping anyway - a landing, a docking - runPend below simply
|
|
; pumps it until it has come out.
|
|
latch:
|
|
INIA 0x03
|
|
OUTA 0x41
|
|
SETD.0 LatchPatch
|
|
CALL playPatch
|
|
SETD.0 LatchFirst
|
|
LDA.0
|
|
OUTA 0x44
|
|
SETD.0 LatchSecond
|
|
LDA.0
|
|
SETD.1 PendNote
|
|
STA.1
|
|
INIA 0d8
|
|
SETD.1 PendIn
|
|
STA.1 ; An eighth of a second, which is quick succession and not a chord.
|
|
RET
|
|
|
|
; Whatever note is waiting, one frame nearer. Nothing at all when none is.
|
|
pendTick:
|
|
SETD.0 PendIn
|
|
LDA.0
|
|
BRA pendTickDone
|
|
DECA
|
|
STA.0
|
|
BNA pendTickDone
|
|
INIA 0x03
|
|
OUTA 0x41
|
|
SETD.0 PendNote
|
|
LDA.0
|
|
OUTA 0x44
|
|
pendTickDone:
|
|
RET
|
|
|
|
; For the two that can afford to wait, the same shape runBurst has.
|
|
runPend:
|
|
CALL waitFrame
|
|
CALL pendTick
|
|
SETD.0 PendIn
|
|
LDA.0
|
|
BNA runPend
|
|
RET
|
|
|
|
; ---- Why every sound loads its patch at the moment it plays ----
|
|
;
|
|
; CHANNEL THREE IS TWO INSTRUMENTS. The bang and the latch share it, and a patch is the whole
|
|
; voice, so the channel has to be told which of them it is about to be. Loading at the moment
|
|
; of the note costs fifty writes at a moment already doing more than fifty, and means a sound
|
|
; is what its patch says whatever played before it.
|
|
;
|
|
; The other three channels have one sound each and could be set up once at startup. They are
|
|
; not, because a single rule is worth more here than the writes it saves: every sound loads
|
|
; its own patch, so there is no question of which sounds are safe to set up early and which
|
|
; are not.
|
|
;
|
|
; ---- What this used to be working around ----
|
|
;
|
|
; The LFOs belonged to the DEVICE and not to a channel: two of them against four voices, with
|
|
; a patch carrying LFO settings the way it carries everything else, so whichever patch loaded
|
|
; last owned them for every channel at once.
|
|
;
|
|
; That is what made the low fuel warning sound right sometimes and not others. Its trill is a
|
|
; saw LFO on the pitch, and the patches on channel three carry an LFO that is switched OFF, so
|
|
; the first landing, docking or crash of a run took the warning's trill away and left a plain
|
|
; tone. Before any of them it was right, which is exactly the shape an intermittent fault has.
|
|
;
|
|
; Loading at the moment of the note made each sound correct AS IT STARTED, which was as far as
|
|
; a program could get on its own. It could not fix two sounds OVERLAPPING - a warning going off
|
|
; mid-burn still re-tuned the rumble's LFO for as long as it lasted.
|
|
;
|
|
; The device was fixed instead: an LFO now belongs to the channel it was written to, so four
|
|
; channels are four independent instruments in this respect as in every other. Overlapping
|
|
; sounds no longer interfere, and the paragraph above is history rather than a caveat.
|
|
|
|
; ---- Lit or not, and only the CHANGES matter ----
|
|
;
|
|
; The same condition the flames are drawn on, for the same reason: a held button with a dry
|
|
; tank is a pilot doing nothing. What is watched is the edge - a rumble restruck every frame
|
|
; would never get past its own attack, and a bang struck every frame is a buzz.
|
|
thrustSound:
|
|
; ---- WHICH thrusters are lit, not merely whether any is ----
|
|
;
|
|
; Kept as the set of them, because the two sounds want different questions asked of it: the
|
|
; pop is about each engine and the rumble is about all of them together.
|
|
SETD.0 Fuel
|
|
LDA.0
|
|
BRA thrustNoneLit
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x0F
|
|
AND
|
|
MVQA
|
|
BRI thrustHaveSet
|
|
thrustNoneLit:
|
|
RSTA
|
|
thrustHaveSet:
|
|
SETD.0 ThrustNow
|
|
STA.0
|
|
|
|
; ---- A pop for every thruster that has just caught ----
|
|
;
|
|
; Not for the first one only. A pilot already burning upwards who then adds a sideways
|
|
; thruster has lit an engine, and that is what an engine lighting sounds like - so what is
|
|
; wanted is the bits that are on NOW and were not before, rather than "was anything on".
|
|
;
|
|
; What changed is an exclusive or, and of those the ones lit now is an and, which is two
|
|
; instructions and no comparison at all.
|
|
SETD.1 ThrustWas
|
|
LDB.1
|
|
XOR
|
|
MVQB
|
|
AND
|
|
BRQ thrustNoPop ; Nothing newly lit, whatever else is still burning.
|
|
RSTA
|
|
OUTA 0x41
|
|
SETD.0 ThrustOnPatch
|
|
CALL playPatch
|
|
INIA 0d36
|
|
OUTA 0x44 ; The bang of it catching.
|
|
thrustNoPop:
|
|
|
|
; ---- And the rumble, which is about all of them at once ----
|
|
;
|
|
; Struck when the first lights and not again until every one has gone out. Restriking it
|
|
; when a second thruster joins would start its attack over, which is a stutter rather than
|
|
; an engine.
|
|
SETD.0 ThrustNow
|
|
LDA.0
|
|
BRA thrustAllOut
|
|
SETD.0 Rumbling
|
|
LDA.0
|
|
BNA thrustRemember ; Already running, and it should simply keep running.
|
|
INIA 0x01
|
|
STA.0
|
|
OUTA 0x41
|
|
SETD.0 ThrustHoldPatch
|
|
CALL playPatch ; After the pop's, so the held sound owns the LFO while it runs.
|
|
INIA 0d60
|
|
OUTA 0x44 ; Held, until the gate comes up.
|
|
BRI thrustRemember
|
|
thrustAllOut:
|
|
SETD.0 Rumbling
|
|
LDA.0
|
|
BRA thrustRemember ; Already quiet.
|
|
CALL hushRumble
|
|
thrustRemember:
|
|
SETD.0 ThrustNow
|
|
LDA.0
|
|
SETD.1 ThrustWas
|
|
STA.1 ; Now becomes what "last frame" means next time round.
|
|
RET
|
|
|
|
; ---- The rumble stopped, wherever the stopping is happening ----
|
|
;
|
|
; A HELD note outlives the loop that was holding it. Landing while the thruster is still down
|
|
; ends the flying and then waits to be told the message has been read - and the frame that
|
|
; would have noticed the button coming up never runs, so the engine roared under the verdict
|
|
; and went on roaring. Anything that stops to wait calls this.
|
|
;
|
|
; Forgetting last frame with it, so that a thruster still held when the waiting ends counts as
|
|
; lighting again rather than as never having stopped.
|
|
hushThrust:
|
|
CALL hushRumble
|
|
RSTA
|
|
SETD.0 ThrustWas
|
|
STA.0 ; And nothing was lit, so anything still held counts as new.
|
|
RET
|
|
|
|
; The held note stopped, wherever the stopping is happening.
|
|
hushRumble:
|
|
INIA 0x01
|
|
OUTA 0x41
|
|
RSTA
|
|
OUTA 0x45 ; The gate up is the only thing that ends a held note.
|
|
SETD.0 Rumbling
|
|
STA.0
|
|
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
|
|
|
|
; ---- 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.
|
|
alarm:
|
|
INIA 0x02
|
|
OUTA 0x41
|
|
SETD.0 AlarmPatch
|
|
CALL playPatch ; Its own LFO back, whatever the last sound left there.
|
|
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 hushThrust ; Nothing waits with an engine still running.
|
|
waitKeyPoll:
|
|
CALL readPad
|
|
SETD.0 Held
|
|
LDA.0
|
|
INIB 0x50 ; A, or Start.
|
|
AND
|
|
BNQ waitKeyDone
|
|
INA 0x01
|
|
INIB 0x01 ; READY
|
|
AND
|
|
BRQ waitKeyPoll
|
|
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
|
|
; The two notes of a latch, and how long until the second of them.
|
|
LatchFirst:
|
|
0x00
|
|
LatchSecond:
|
|
0x00
|
|
PendNote:
|
|
0x00
|
|
PendIn:
|
|
0x00
|
|
; Which thrusters were lit last frame and which are now, since only the changes are worth a
|
|
; sound - and which ones they are, not merely how many.
|
|
ThrustWas:
|
|
0x00
|
|
ThrustNow:
|
|
0x00
|
|
; Whether the held rumble is running, which is a different question from any of the above.
|
|
Rumbling:
|
|
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
|
|
|
|
#Include thrustOn.asm
|
|
#Include thrustHold.asm
|
|
#Include latch.asm
|