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
File diff suppressed because one or more lines are too long
+16 -6
View File
@@ -16,9 +16,11 @@
; PatchTable one two-byte address per patch
; SequenceTable one two-byte address per sequence
; VoiceStart four bytes, the patch index each voice starts on
; Order0..Order3 one-byte sequence indices each, ending in 0xFF
;
; and must call loadStartPatches once, then stepVoice once a tick for each voice, with A
; and point each voice's order cursor at a list of one-byte sequence indices ending in 0xFF.
; A tune read from a file needs none of that: useTune does all of it out of the header.
;
; It must call loadStartPatches once, then stepVoice once a tick for each voice, with A
; holding the channel and DP1 the voice's record. Playing counts down as voices run out, and
; reaching nought is the piece being over.
;
@@ -424,13 +426,13 @@ loadPatchPair:
; tick runs the count out, finds the end, and goes to the order list for the real first
; sequence - so the beginning of a piece needs no special case anywhere.
Voice0:
Order0 Empty 0d1 0d1
NoOrder Empty 0d1 0d1
Voice1:
Order1 Empty 0d1 0d1
NoOrder Empty 0d1 0d1
Voice2:
Order2 Empty 0d1 0d1
NoOrder Empty 0d1 0d1
Voice3:
Order3 Empty 0d1 0d1
NoOrder Empty 0d1 0d1
Playing:
0d4
@@ -455,6 +457,14 @@ TuneSequences:
TuneMagic:
0x53 0x42 0x54 0x55 0x01 ; "SBTU" and version one.
; An order list of nothing, which every voice starts pointed at. WHOEVER SUPPLIES THE TUNE
; SETS THE REAL ONE - useTune out of a file's header, or a program with a tune of its own in
; eight lines - so the records below name nothing that a caller has to have defined. They used
; to name Order0 to Order3, which made four order lists part of the contract even for a caller
; whose tune comes from a file and never has any.
NoOrder:
0xFF
; The sequence a voice starts on, so that its first tick goes through the order list like
; every other bar does.
Empty:
+67
View File
@@ -0,0 +1,67 @@
; Four bars, and the bass is the same one under two of them.
#Tick 0d125000 ; cycles a tick: a sixteenth note at 120 beats a minute
#Patch Oboe oboe.patch
#Voice 0d0 Oboe ; which instrument each voice starts on
#Voice 0d1 Oboe
#Voice 0d2 Oboe
#Voice 0d3 Oboe
#Sequence MotifA
0x30 0d2
0x00 0d2
0x3C 0d2
0x00 0d4
0x48 0d4
0x00 0d4
0x47 0d4
0x00 0d6
; 28 beats
#Sequence MotifB
0x00 0d2
0x35 0d2
0x00 0d2
0x40 0d4
0x00 0d4
0x45 0d4
0x00 0d4
0x43 0d6
#Sequence IntroQuiet
0x00 0d28
#Sequence ChordRoot
0x2C 0x4
0x2E 0x4
0x30 0x4
#Sequence ChordFifth
0x33 0x4
0x35 0x4
0x37 0x4
#Sequence ChordThird
0x3C 0x4
0x3E 0x4
0x40 0x4
#Sequence ChordQuiet
0x00 0d12
#Sequence Bass
0x24 0x8
#Order 0d0
MotifA ChordRoot
#Order 0d1
MotifB ChordFifth
#Order 0d2
IntroQuiet ChordThird
#Order 0d3
IntroQuiet ChordQuiet Bass
+4 -3
View File
@@ -25,14 +25,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -44,7 +45,7 @@ outer.script 376
inner.script 44
loop.script 35
crossed.txt 560
31 files, 1 directory
32 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -15,14 +15,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -33,7 +34,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> drive 1
> dir
other.txt 28
+4 -3
View File
@@ -32,14 +32,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -50,7 +51,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -22,14 +22,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -40,7 +41,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -22,14 +22,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -40,7 +41,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -108,14 +108,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -126,7 +127,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -27,14 +27,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -45,7 +46,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+4 -3
View File
@@ -15,14 +15,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -33,7 +34,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> load
load what?
> load nosuch.sbx
+4 -3
View File
@@ -13,14 +13,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -31,7 +32,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> load Say.sbx
loaded, starting at 5000
> run the disk took its time
+4 -3
View File
@@ -22,14 +22,15 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 9361
Lander.sbx 11260
Pad.sbx 264
Crash.sbx 632
vars.script 50
blocks.script 343
loops.script 272
splash.tune 218
tune.sbx 318
Play.sbx 2525
Play.sbx 2526
notes.txt 21
Apps <dir>
hi.script 121
@@ -40,7 +41,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
30 files, 1 directory
31 files, 1 directory
> exit
halted
Execution halted.
+9
View File
@@ -174,6 +174,15 @@ printf '#! script\nset colour red\nif same $colour red\n echo it is red\n if s
# all rather than once.
printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset n go\nwhile same $n go\n echo round\n set n stop\nend\nfor c in p q\n if same $c p\n echo only p\n end\nend\nif same 1 2\n for d in never\n echo not this\n end\nend\nfor e in\n echo no words\nend\necho finished\n' > loops.script
"$TOOL" put "$DISKS/cosmos.img" loops.script >/dev/null
# ---- The splash Lander plays over its studio line ----
#
# Written in the tune language and compiled here, so the music can be changed without
# touching the game. Lander reads it off the disk at startup and starts anyway if it is not
# there, the way it treats /lander.state.
"$ROOT/SoundPatch" --blob "$ROOT/Programs/Sounds/Oboe.json" Oboe "$WORK/oboe.patch" >/dev/null
"$ROOT/TuneC" -I "$WORK" "$ROOT/Programs/Tunes/splash.tune.txt" "$WORK/splash.tune" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/splash.tune" splash.tune >/dev/null
# tune.sbx, which used to be a boot image and is now a program like any other. It brings a
# vector of its own - the timer's, so that it has a beat to play to - which makes it the
# second thing here that the version two format exists for.
+48
View File
@@ -916,6 +916,54 @@ PY2
|| result no "and a file that is not a tune is refused" "$(tail -2 "$BUILD/notune.out" | tr '\n' ' ')"
fi
# ---- A game plays its own music ----
#
# The first customer for the player outside the program it was extracted from. Lander includes
# Libraries/player.asm, reads /splash.tune off the disk, and plays it over a studio line before
# the world is built - owning the timer and all four channels while it does, because nothing
# else is happening yet.
#
# 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 - and
# brings no handler that would have to be taken away before the game starts.
#
# No key is pressed here, which is the point: every other Lander test skips the splash with a
# space, so this is the only one that hears it.
if [ ! -f "$ROOT/Tests/build/disks/cosmos.img" ]; then
result no "a game plays its own music" "cosmos.img is missing"
else
python3 -c "
open('$BUILD/splash.keys','wb').write(b'Lander\n' + b'\x00'*900)
open('$BUILD/still.pad','wb').write(b'\x00' * 20000)"
timeout 90 "$EMU" --fast --cycles 40000000 --keyboard "$BUILD/splash.keys" \
--pad "$BUILD/still.pad" --sound "$BUILD/splash.raw" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/splash.out" 2>&1 || true
# The tune is six seconds of ticks. What is checked is that music is still playing well
# after a splash that failed to load would have given up: the silent path holds the logo
# for ninety frames, a second and a half, and then the game starts.
read -r EARLY LATE <<EOT
$(python3 - "$BUILD/splash.raw" <<'PY2'
import struct, sys
try:
d = open(sys.argv[1], "rb").read()
except OSError:
print("0 0"); raise SystemExit
n = len(d) // 2
v = struct.unpack("<%dh" % n, d)
def rms(a, b):
w = v[a:b]
return int((sum(x*x for x in w) / len(w)) ** 0.5) if w else 0
print("%d %d" % (rms(48000, 96000), rms(192000, 240000)))
PY2
)
EOT
[ "${EARLY:-0}" -gt 500 ] && [ "${LATE:-0}" -gt 500 ] \
&& result ok "a game plays its own music" "still sounding at four seconds, so it is the tune and not the silent hold" \
|| result no "a game plays its own music" "RMS was $EARLY at one second and $LATE at four"
fi
echo
if [ "$FAIL" -eq 0 ]; then
echo "All $PASS sound checks passed."
+17 -9
View File
@@ -1617,7 +1617,8 @@ EOT
# capture near the start is worth far more than a tuned one: there is much less between it and
# the beginning that can move.
python3 -c "
keys = b'Lander\n'
SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
keys = b'Lander\n' + SKIP
for i in range(400):
keys += b'\x80' + b'\x00' * 47
open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 8000)
@@ -1663,7 +1664,8 @@ EOT
# not the one the program is about to ask for. The moon has to come out exactly as it does from a fresh
# prompt: the rows a program WRITES and the rows the screen READS are two different things,
# and only one of them is under the program's control.
python3 -c "open('$BUILD/scrolled.keys','wb').write(b'\n' * 80 + b'Lander\n' + b'\x00' * 3000)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/scrolled.keys','wb').write(b'\n' * 80 + b'Lander\n' + SKIP + b'\x00' * 3000)"
python3 -c "open('$BUILD/scrolled.pad','wb').write(b'\\x00' * 40 + b'\\x08' * 400)"
timeout 30 "$EMU" --fast --cycles 1500000 --keyboard "$BUILD/scrolled.keys" \
--pad "$BUILD/scrolled.pad" --screen "$BUILD/scrolled.ppm" \
@@ -1813,7 +1815,8 @@ open('$BUILD/orbit.state','wb').write(struct.pack('<hhhhhhBBBB',
"
# Its own keyboard file and a silent pad. $BUILD/lander.keys holds a key down every forty
# eight bytes for the held-thruster check, which would fly this orbit as well as measure it.
python3 -c "open('$BUILD/fly.keys','wb').write(b'Lander\n' + b'\x00' * 8000)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/fly.keys','wb').write(b'Lander\n' + SKIP + b'\x00' * 8000)"
python3 -c "open('$BUILD/none.pad','wb').write(b'\x00' * 60000)"
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/orbit.img"
"$ROOT/SplitDisk" put "$BUILD/orbit.img" "$BUILD/orbit.state" /lander.state > /dev/null
@@ -1938,7 +1941,8 @@ EOT
# up the same row reads F, U, E, L across cells nought to three. So cells one and three being
# empty is the whole difference, and it is 29 and 22 pixels of it rather than a threshold
# somebody has to believe.
python3 -c "open('$BUILD/quit.keys','wb').write(b'Lander\n' + b'\x00' * 400 + b'q' + b'\x00' * 200 + b'clear\n' + b'\x00' * 200)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/quit.keys','wb').write(b'Lander\n' + SKIP + b'\x00' * 400 + b'q' + b'\x00' * 200 + b'clear\n' + b'\x00' * 200)"
timeout 30 "$EMU" --fast --cycles 14000000 --keyboard "$BUILD/quit.keys" \
--pad "$BUILD/burn.pad" --screen "$BUILD/quit.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
@@ -2029,7 +2033,8 @@ EOT
# lifting. Borrowing it here meant the lander was being flown from the keyboard and the
# controller at once, and the gentle descent below arrived as a crash - which reads exactly
# like a broken altimeter and is nothing of the sort.
python3 -c "open('$BUILD/fly.keys','wb').write(b'Lander\n' + b'\x00' * 8000)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/fly.keys','wb').write(b'Lander\n' + SKIP + b'\x00' * 8000)"
python3 -c "open('$BUILD/climb.pad','wb').write(b'\x00' * 20 + b'\x09' * 300 + b'\x01' * 200 + b'\x00' * 6000)"
python3 -c "open('$BUILD/settle.pad','wb').write(b'\x00' * 150 + b'\x08' * 90 + b'\x00' * 8000)"
timeout 60 "$EMU" --fast --cycles 6000000 --keyboard "$BUILD/fly.keys" \
@@ -2140,7 +2145,8 @@ EOT
# Forty zeroes before the z, because the console hands over ONE KEY A FRAME - a z two hundred
# bytes in is a z two hundred frames away, which is past the end of this capture and reads
# exactly like a key that does nothing.
python3 -c "open('$BUILD/zoom.keys','wb').write(b'Lander\n' + b'\x00' * 40 + b'z' + b'\x00' * 8000)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/zoom.keys','wb').write(b'Lander\n' + SKIP + b'\x00' * 40 + b'z' + b'\x00' * 8000)"
timeout 60 "$EMU" --fast --cycles 2200000 --keyboard "$BUILD/zoom.keys" \
--screen "$BUILD/zoomkey.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/zoomkey.out" 2>&1 || true
@@ -2176,7 +2182,8 @@ print(int(d[:40].split()[1]))
# alone looks exactly like clearing them. A hundred and eighteen returns first, which puts the
# prompt in that band, and then the difference appears: 136 pixels of somebody else's text
# hanging in the sky.
python3 -c "open('$BUILD/deep.keys','wb').write(b'\n' * 118 + b'Lander\n' + b'\x00' * 8000)"
python3 -c "SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/deep.keys','wb').write(b'\n' * 118 + b'Lander\n' + SKIP + b'\x00' * 8000)"
timeout 60 "$EMU" --fast --cycles 8000000 --keyboard "$BUILD/deep.keys" \
--pad "$BUILD/tap.pad" --screen "$BUILD/deep.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
@@ -3266,8 +3273,9 @@ PY2
# moon, the same lander in the same place, the same gauges. Any state that survives a run and
# should not shows up here without anybody having to have thought of it in advance.
python3 -c "
open('$BUILD/once.keys','wb').write(b'Lander\n' + b'\x00'*100)
open('$BUILD/twice.keys','wb').write(b'Lander\n' + b'\x00'*100 + b'q' + b'\x00'*20 + b'run\n' + b'\x00'*100)
SKIP = b'\x20' # a key the game ignores, so it skips the splash and nothing else
open('$BUILD/once.keys','wb').write(b'Lander\n' + SKIP + b'\x00'*100)
open('$BUILD/twice.keys','wb').write(b'Lander\n' + SKIP + b'\x00'*100 + b'q' + b'\x00'*20 + b'run\n' + SKIP + b'\x00'*100)
open('$BUILD/still.pad','wb').write(b'\x00' * 20000)"
for WHEN in once twice; do
timeout 60 "$EMU" --fast --cycles 24000000 --keyboard "$BUILD/$WHEN.keys" \