Lander starts a flight where a flight starts

run does not reload, and that is right: run's job is to run the program
that is loaded, which is why it is a separate word from load and why
typing a program's name does both. What follows is that the numbers the
assembler wrote into a Data Segment are LOAD-time values, true once, and
a program that wants them true at every start has to say so itself.

Lander did not. Flying is 0x01 in the data and is only ever cleared, so a
second run began with the loop already over: the program started and
handed the machine straight back. Reproduced by running it, quitting, and
running again - the leftover game keys land at the shell prompt, which is
what a program that never read them looks like.

AND THE TEST FOUND A SECOND ONE, quieter and worse. The terrain seed is a
written number, so a fresh load always walks out the same moon - but
nextRandom moves it, and a second run generated a DIFFERENT moon. Nobody
decided that. A test comparing the whole screen found it without anybody
having had to think of it in advance, which is the argument for comparing
the picture rather than the variables somebody remembered to check.

Only what a flight needs to begin is put back. Anything not on the list
keeps what the last run left it, which is deliberate - state surviving a
run is sometimes exactly what is wanted, and the way to have that is for
the list to be a decision rather than a sweep. The things that are simply
nought are a table of NAMES that the assembler turns into addresses, so
adding a variable that must start empty means adding it there and nowhere
else.

video.sh now runs Lander, quits, runs it again, and requires the same
picture a hundred frames in - the same moon, the same lander in the same
place, the same gauges. break.sh confirms it by taking the seed line back
out: 37,995 bytes of the picture differ.

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 09:02:50 -04:00
co-authored by Claude Opus 5
parent b160c66110
commit 87467a7d3a
13 changed files with 144 additions and 11 deletions
+95
View File
@@ -48,6 +48,20 @@ start:
; floor the system gives every program does not. ; floor the system gives every program does not.
SWI osTakeScreen SWI osTakeScreen
; ---- A flight starts where a flight starts, and not where the last one stopped ----
;
; RUN DOES NOT RELOAD, and that is right: run's job is to run the program that is loaded,
; which is why it is a separate word from load and why typing a program's name does both.
; What follows from it is that the numbers the assembler wrote into the Data Segment are
; LOAD-time values, true once, and a program that wants them true at every start has to say
; so itself. This one did not, so a second run began with Flying already nought - the loop
; ended before it started and the program handed the machine straight back.
;
; Only what a flight needs to begin. Anything not named here keeps what the last run left
; it, which is deliberate: state surviving a run is sometimes exactly what is wanted, and
; the way to have that is for this list to be a decision rather than a sweep.
CALL initialise
CALL loadState ; Before the view, because a state may say which view to be in. 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. CALL setView ; The block every gauge below reads its screen numbers out of.
SETD.0 Wide SETD.0 Wide
@@ -3195,6 +3209,76 @@ showHeightSized:
; BINARY, because that is what a file is on this machine. A tool to build one from readable ; 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 ; text is a small job for another day; until then the tests write the bytes, which is plain
; enough while the fields are named. ; enough while the fields are named.
; ---- What a new flight is ----
;
; Called before loadState, so a saved state still wins: this puts the world back to its
; beginning and that puts a remembered one over the top of it.
initialise:
; Everything that is nought when a flight begins, from a table of WHERE rather than a line
; each. A label written in the Data Segment comes out as its address, so the list below is
; names and the assembler turns them into pointers - and 0xFFFF ends it, the same way it
; ends the music player's order lists, because no useful address can begin up there.
SETD.0 ZeroAtStart
zeroNext:
LDA.0
INIB 0xFF
XOR
BRQ zeroDone
LDD.1.0 ; DP1 is the place this entry names.
INCD.0
INCD.0
RSTA
STA.1
BRI zeroNext
zeroDone:
; The sideways speed is two bytes and the table above deals in one, so it says so here.
RSTA
SETD.1 SpeedAcross
STA.1
INCD.1
STA.1
; And the numbers that are not nought: where the lander is, where the station is, a full
; tank, the gravity tick, and the flag that says the loop should run at all.
INIA 0xA0
SETD.1 AcrossLow
STA.1
INIA 0x14
SETD.1 AcrossHigh
STA.1
INIA 0x02
SETD.1 DownHigh
STA.1
INIA 0xA0
SETD.1 StationLow
STA.1
INIA 0x34
SETD.1 StationHigh
STA.1
INIA 0xFA
SETD.1 StationDownHigh
STA.1
INIA 0xFF
SETD.1 Fuel
STA.1
INIA 0d1
SETD.1 FallTick
STA.1
SETD.1 Flying
STA.1 ; A is still one, and this is the one that matters most.
; ---- And the moon, which is the same moon every time ----
;
; The seed is a written number rather than anything taken from the world, so a fresh load
; always walks out the same terrain. nextRandom moves it, though, so a second run started
; from wherever the first one left it and generated a different moon - which was not a
; decision anybody made. Put back here, the moon is the moon.
INIA 0x5D
SETD.1 Seed
STA.1
RET
loadState: loadState:
SETD.0 StateName SETD.0 StateName
SETD.1 StateBuffer SETD.1 StateBuffer
@@ -4041,6 +4125,17 @@ KeyHeld:
Flying: Flying:
0x01 0x01
; ---- Everything a new flight puts back to nought ----
;
; Names, which the assembler writes out as addresses. Adding a variable that has to start
; empty means adding it here and nowhere else.
ZeroAtStart:
DownLow SpeedDown SpeedDownHigh StationDownLow
Held HasPad KeyHeld
PendNote PendIn ThrustWas ThrustNow Rumbling
AlarmSaid Wide AtBase Carrying Landed BurstLeft Docked Adrift
0xFF 0xFF
Terrain: Terrain:
#Reserve 0d128 #Reserve 0d128
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -25,7 +25,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -32,7 +32,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -108,7 +108,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -27,7 +27,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -13,7 +13,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 9201 Lander.sbx 9361
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+38
View File
@@ -3249,6 +3249,44 @@ PY2
)" )"
[ "$LETTERS" = "16" ] && result ok "and does not rub out what it moves over" "all sixteen letters are still there" || result no "and does not rub out what it moves over" "$LETTERS of sixteen cells still have ink" [ "$LETTERS" = "16" ] && result ok "and does not rub out what it moves over" "all sixteen letters are still there" || result no "and does not rub out what it moves over" "$LETTERS of sixteen cells still have ink"
# ---- Running a program twice is the same as running it once ----
#
# RUN DOES NOT RELOAD, and that is right: run's job is to run the program that is loaded,
# which is why it is a separate word from load and why typing a program's name does both. It
# follows that the numbers the assembler wrote into a Data Segment are LOAD-time values, true
# once - and a program that wants them true at every start has to say so itself.
#
# Lander did not. A second run began with Flying already nought, so the loop ended before it
# started and the program handed the machine straight back. Worse and quieter: the terrain
# seed is a written number, so a fresh load always walks out the same moon - but nextRandom
# moves it, and a second run generated a different moon, which was nobody's decision.
#
# THE CHECK IS THE WHOLE SCREEN, because that is the only thing that knows about all of it at
# once. A hundred frames in, run once and run twice have to be the same picture: the same
# 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)
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" \
--pad "$BUILD/still.pad" --screen "$BUILD/run$WHEN.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/run$WHEN.out" 2>&1 || true
done
if [ ! -f "$BUILD/runonce.ppm" ] || [ ! -f "$BUILD/runtwice.ppm" ]; then
result no "a program run twice starts where it started" "no picture came out"
elif cmp -s "$BUILD/runonce.ppm" "$BUILD/runtwice.ppm"; then
result ok "a program run twice starts where it started" "the same screen a hundred frames in"
else
DIFFERS="$(python3 -c "
a = open('$BUILD/runonce.ppm','rb').read()
b = open('$BUILD/runtwice.ppm','rb').read()
print(sum(1 for x, y in zip(a, b) if x != y))")"
result no "a program run twice starts where it started" "$DIFFERS bytes of the picture differ"
fi
echo echo
if [ "$FAIL" -eq 0 ]; then if [ "$FAIL" -eq 0 ]; then
echo "All $PASS video checks passed." echo "All $PASS video checks passed."