Lander opens on a splash, with music

The first customer for the player outside the program it was pulled out
of, and the argument for doing it: a splash needs no resident player at
all. Nothing else is happening while the logo is up, so Lander owns the
timer and all four channels exactly as Play does. Music UNDER a running
game is still deferred, and still the harder problem.

It polls the timer rather than being interrupted by it. Lander has no
vector segment and waits for the screen by reading port 0x30, so it waits
for a beat by reading port 0x50 - the same shape, and it brings no
handler that would have to be taken away before the game starts.

The tune is read off the disk. A missing one means the logo and silence
and the game starts anyway, the way a missing /lander.state means the
defaults stand.

FOUR THINGS THIS COST, each found by running it:

A subroutine cannot answer in A. CALL saves and restores it, so splashSkip
handed its caller back the A it already had - the channel number of the
last stepVoice - and the splash ended on its first pass whatever anybody
pressed. Q is what survives a RET, which nextRandom says twenty lines
away and I did not read.

Port 0x3D is how many window rows are SHOWN, and it is two once putGauge
runs and nothing before. A line written to row eleven went somewhere real
and was displayed nowhere.

A nought is not a keypress. It is what a recorded keyboard file holds
while nobody is typing, and a splash that took it for a key is one no
test could ever watch.

And skipping has to be free. Asked after blanking the window and reading
the file, a skipped splash still cost a fifth of a second - enough to
push the thruster test's early capture past the frame it looks at. Asked
first, it costs a pad read.

player.asm no longer asks a caller for Order0 to Order3: the voice records
name NoOrder instead, so a program whose tune comes from a file does not
have to define four order lists it never uses. That was the file case
finding a wart in the contract.

Every other Lander test now skips the splash with a space - a key the
game itself ignores, since it answers to q, z and the arrows - so they go
on testing what they tested. The one in sound.sh presses nothing, which
is what makes it the one that hears the music.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-06 10:06:20 -04:00
co-authored by Claude Opus 5
parent 6bb1565dea
commit 00b31b88af
17 changed files with 392 additions and 52 deletions
+194 -6
View File
@@ -88,6 +88,13 @@ start:
CALL mulReady ; The quarter square table, once. The orbit below needs a product.
CALL putTiles
; Key mode before the splash, because "press anything" needs the keystroke as it happens
; rather than a line at the end of one.
INIA 0x01
OUTA 0x02
CALL splash
CALL makeMoon
CALL carvePads
CALL drawMoon
@@ -95,9 +102,6 @@ start:
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
@@ -3541,6 +3545,168 @@ groundLevel:
STA.0
RET
; ---- The studio, and its theme ----
;
; What a splash screen is: whose game this is, over whatever music they want it said to. The
; game's own title screen is a different thing and comes later.
;
; IT POLLS THE TIMER RATHER THAN BEING INTERRUPTED BY IT. This program has no vector segment
; and waits for the screen by reading port 0x30, so waiting for a beat by reading port 0x50 is
; the same shape - and it means the splash brings no handler that would have to be taken away
; before the game starts. Reading the status is what answers a tick nobody was interrupted by.
;
; A tune that is not there is not a failure. The logo still shows, for a moment measured in
; frames instead of ticks, and the game starts. That is what /lander.state does too.
splash:
; ---- Somebody already leaning on something means no splash at all ----
;
; Asked before anything is drawn or read, so that skipping costs nothing: the alternative
; blanks twenty five rows of window and reads a file off the disk before it thinks to look,
; which is a fifth of a second spent showing a picture nobody wanted.
CALL splashSkip
BNQ splashNone
; ---- The whole screen is window while the splash is up ----
;
; Port 0x3D is how many window rows are SHOWN, and putGauge sets it to two later for the
; gauge. Two is not enough to put a line in the middle of the screen, and until putGauge
; runs it is not even that - so the splash asks for all of them, which has the second use of
; hiding the map, where the shell's prompt is still sitting.
INIA 0d25
OUTA 0x3D
RSTA
OUTA 0x3E ; From the top.
; Blanked a row at a time, because what is in the window is whatever was there before. A
; CALL hands back A and B, so the row and the count both survive it.
INIB 0d25 ; A is already nought, from setting the window's top above.
splashBlank:
SETD.0 Nothing
CALL sayAtRow
INCA
DECB
BNB splashBlank
SETD.0 StudioLine
INIA 0d11
CALL sayAtRow
SETD.0 SplashTune
SETD.1 SplashBuffer
SWI osFileRead
BNQ splashSilent
SETD.0 SplashBuffer
CALL useTune ; Which sets the tick out of the tune's own header.
BNQ splashSilent
INIA 0x03
OUTA 0x51 ; Run and repeat. NO interrupt: this waits by looking.
splashPlay:
SETD.1 Voice0
RSTA
CALL stepVoice
SETD.1 Voice1
INIA 0d1
CALL stepVoice
SETD.1 Voice2
INIA 0d2
CALL stepVoice
SETD.1 Voice3
INIA 0d3
CALL stepVoice
SETD.1 Playing
LDA.1
BRA splashDone ; Every voice has run out of order list.
CALL splashSkip
BNQ splashDone
CALL waitTick
BRI splashPlay
splashSilent:
; No tune on the disk, so the logo is held for about a second and a half of frames.
INIB 0d90
splashHold:
CALL waitFrame
CALL splashSkip
BNQ splashDone
DECB
BNB splashHold
splashDone:
RSTA
OUTA 0x51 ; The timer stopped, whether or not it was ever started.
CALL hushVoices
; And the window given back, because putGauge is about to ask for the two rows it wants.
RSTA
OUTA 0x3D
splashNone:
RET
; ---- Anything pressed, on a pad or a key ----
;
; THE ANSWER COMES BACK IN Q, because Q is what survives a RET. A CALL saves and restores A,
; so a routine that answered there would hand its caller back the A it already had - which
; here was the channel number the last stepVoice was given, and the splash ended on its first
; pass every time whatever anybody pressed. nextRandom above says the same thing about the
; same register, which is where it should have been read.
splashSkip:
CALL readPad
SETD.0 Held
LDA.0
INIB 0x50 ; A, or Start - the same two waitKey asks about, and for the same
AND ; reason: a direction held on a stick is not somebody asking to
BNQ splashSkipYes ; begin, and ANY bit counted one as such.
INA 0x01
INIB 0x01 ; READY
AND
BRQ splashSkipNo
INA 0x00 ; Taken, so it is not still waiting at the prompt afterwards.
BRA splashSkipNo ; A NOUGHT IS NOT A KEYPRESS. It is what a recorded keyboard file
; holds while nobody is typing, and what readControls has always
; ignored - a splash that took it for a key would be a splash
; nothing could ever watch.
splashSkipYes:
INIA 0d1
RSTB
OR ; Q is one: somebody asked for the game.
RET
splashSkipNo:
RSTA
RSTB
OR ; And Q is nought: nobody did.
RET
; A beat, waited for by looking - the same shape as waitFrame, one device along. Reading the
; status is what brings the tick down.
waitTick:
INA 0x50
INIB 0x01
AND
BRQ waitTick
RET
; Every voice let go of. The splash owns all four while it plays and the game wants them back
; silent - and every sound in this game loads its own patch before its note, so nothing else
; has to be put back.
hushVoices:
RSTA
hushOne:
OUTA 0x41
PSHA
RSTA
OUTA 0x45
POPA
INCA
INIB 0d4
CCF
SUB
BNQ hushOne
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
@@ -3554,12 +3720,21 @@ groundLevel:
; The letters are ordinary tiles: the character generator starts at the space, so glyph n is
; character n less thirty two.
sayInWindow:
INIA 0d1 ; Row one, which is where a base's message goes.
sayAtRow:
; A is which window row. Kept in memory across the bank select below, which needs A itself.
SETD.1 SayRow
STA.1
INIA 0d5
OUTA 0xE3
INIA 0xC1
OUTA 0xE3 ; Which needs A, so the row came out of it a moment ago.
LDA.1 ; DP1 still holds SayRow.
INIB 0xC0
CCF
ADD ; Window row n begins at 0xC000 plus n times 256.
MVQA
OUTA 0xE4
RSTA
OUTA 0xE5 ; Window row one begins at 0xC100.
OUTA 0xE5
SETD.1 SayAt
STA.1
@@ -4072,6 +4247,17 @@ Wide:
;
; 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.
SayRow:
0d1
StudioLine:
" ANACHRONAUT"
Nothing:
0x00
SplashTune:
"/splash.tune"
SplashBuffer:
#Reserve 0d1024
StateName:
"/lander.state"
StateBuffer:
@@ -4480,6 +4666,8 @@ LanderArtAt:
#Include math.asm
#Include player.asm
#Include boom.asm
#Include alarm.asm