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
+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" \