A state can be placed, which four things were queued behind

Lander reads sixteen bytes from /lander.state if the disk has one and
starts from those: position, velocity, where the station is, fuel, and
which screen to be in. A disk without the file is the game as it always
was, which is every other flight in the suite.

It exists because some states 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. The
orbit check had already been deleted for the same reason, and the strike
check was flown on a nineteen frame window, which is the sort of fixture
that ends up measuring the boot time rather than the physics.

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 with the fields named, which reads plainly enough.

---- The buffer is a whole block, and that is not caution ----

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 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.

Four checks, each seen to fail on its own break: a state file places the
lander (row 192 and twelve cells of gauge, against thirty one with no
file), a matched approach docks and is paid once and not once a frame, it
then rides the station a tile under it, and the same approach unmatched
is a wreck. The flown strike fixture and its narrow window are gone.
This commit is contained in:
Anachronaut
2026-09-04 10:51:01 -04:00
parent 804dd3040e
commit 1928275f87
13 changed files with 198 additions and 53 deletions
+107
View File
@@ -48,6 +48,7 @@ start:
; 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
@@ -2172,6 +2173,75 @@ showHeightSized:
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
@@ -2882,6 +2952,43 @@ ViewWide:
; 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
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
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -32,7 +32,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -108,7 +108,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -27,7 +27,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -13,7 +13,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 6290
Lander.sbx 6692
Pad.sbx 264
Crash.sbx 632
vars.script 50
+80 -42
View File
@@ -2347,52 +2347,90 @@ MOVED=$(( X3 - X2 ))
&& result ok "and round the back of the moon and out again" "gone, then back at $X7" \
|| result no "and round the back of the moon and out again" "$X5 at five million, $X7 at seven"
# ---- Docking, and the half of it that can be flown to ----
# ---- Docking, from a state placed rather than flown to ----
#
# The same shape as touchdown: 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, which is doing
# orbital speed - the number the marks on the drift bar already point at.
# The same shape as touchdown: close enough, and slow enough RELATIVE TO THE STATION, or it is
# a wreck. Its speed is orbital speed, which is what the marks on the drift bar point at.
#
# ---- A NARROW FIXTURE, and it is written down rather than hidden ----
# ---- Placed, because this cannot be flown to ----
#
# The climb here has to arrive at the station's altitude just as the station arrives at the
# lander, and the window measured 496 to 514 frames - about nineteen. 505 sits in the middle
# of it. That is thinner than this suite likes and it is the same shape as the orbit check
# that had to be deleted for measuring the boot time, so: IF THIS EVER FAILS, re-measure the
# window before believing the code is broken.
# Ninety six pad files were tried and every one either hit the moon or was run down on the way
# up, which is not the search's fault: climbing SPENDS sideways speed, so a lander cannot rise
# while matched and arrives slower than orbital every time. Reaching the station wants two
# burns and a phase.
#
# It was not always narrow. The station used to orbit four rows up, where anything climbing
# away from the surface had to pass, and a strike could be had with any climb between 135 and
# 300 frames - eleven of this suite's other flights were being run down as collateral. Moving
# it to twelve rows and the ceiling to twenty four fixed all eleven at once, and made the
# station properly hard to blunder into. The narrow window is the price of that, and it is the
# right way round.
python3 -c "open('$BUILD/struck.pad','wb').write(b'\x08' * 505 + b'\x00' * 60000)"
timeout 60 "$EMU" --fast --cycles 40000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/struck.pad" --disk "$ROOT/Tests/build/disks/cosmos.img" \
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/struck.out" 2>&1 || true
grep -q "Wrecked against the station" "$BUILD/struck.out" \
&& result ok "the station runs down a lander in its lane" "at 64 sixteenths of relative speed" \
|| result no "the station runs down a lander in its lane" "no wreck in the transcript"
# ---- And the half that is not checked here ----
#
# THE SUCCESSFUL DOCK IS NOT FLOWN. It was measured working, from a build that PLACED the
# lander alongside the station and matched: the tank went from 100 to 180, once and not once a
# frame, the message went up, and the pair then held together - lander at 316,168 and station
# at 320,160, one tile apart and unmoving for two hundred and forty frames, which is what
# riding looks like. A thrust in any direction let go of it.
#
# What could not be found was a pad file that flies there. Ninety six were tried and every one
# either hit the moon or was run down on the way UP, which turns out not to be the search's
# fault: climbing SPENDS sideways speed, by the exchange that makes the orbit work at all, so
# a lander cannot rise while matched - it arrives slower than orbital every time. Docking needs
# two burns, one to raise the far side of the orbit and one to circularise at the top, and no
# straight line of button presses stumbles into that.
#
# So it wants either a hand flown recording or, better, a way to PLACE the lander at a chosen
# position and velocity before the loop starts - which is now wanted by this, by the orbit
# above, by the ride, and by the narrow window three paragraphs up.
# So Lander reads sixteen bytes from /lander.state if the disk has one, and starts from those
# instead. A disk without one is the game as it always was, which is every other flight in
# this file. The strike below used to be flown, on a climb whose window was nineteen frames
# wide - the sort of fixture that ends up measuring the boot time. It is placed now and exact.
python3 -c "
import struct
def state(across, down, vx, vy, stationX, stationDown, fuel, wide, landed):
return struct.pack('<hhhhhhBBBB', across, down, vx, vy, stationX, stationDown,
fuel, wide, landed, 0)
# Hanging at the world's origin with a part tank, to show a state is read at all.
open('$BUILD/place.state','wb').write(state(0x14A0, 0, 0, 0, 0x34A0, -1536, 100, 1, 1))
# Four pixels behind the station, at its height, and matched to its speed exactly.
open('$BUILD/dock.state','wb').write(state(0x34A0 - 64, -1536 + 64, 64, 0, 0x34A0, -1536, 100, 1, 0))
# In the same place and NOT matched: 64 sixteenths of relative speed, eight times the limit.
open('$BUILD/wreck.state','wb').write(state(0x34A0, -1536, 0, 0, 0x34A0, -1536, 100, 1, 0))
"
for which in place dock wreck; do
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/$which.img"
"$ROOT/SplitDisk" put "$BUILD/$which.img" "$BUILD/$which.state" /lander.state > /dev/null
done
python3 -c "open('$BUILD/ackonly.pad','wb').write(b'\x00' * 200 + b'\x10' * 8 + b'\x00' * 60000)"
for run in place:3000000 dock:6000000 dock2:9000000 wreck:3000000; do
which="${run%%:*}"; when="${run##*:}"
timeout 60 "$EMU" --fast --cycles "$when" --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/ackonly.pad" --screen "$BUILD/$which.ppm" \
--disk "$BUILD/${which%2}.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/$which.out" 2>&1 || true
done
# And the same moment with no state file at all, which is the contrast that says the file did it.
timeout 60 "$EMU" --fast --cycles 3000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/ackonly.pad" --screen "$BUILD/nostate.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/nostate.out" 2>&1 || true
read -r PLACEDY PLACEDFUEL PLAINFUEL DOCKFUEL DOCK2FUEL RIDESHIP RIDESTATION <<EOT
$(python3 -c "
def look(path):
d = open(path, 'rb').read()
width = int(d[:40].split()[1])
px = d[d.index(b'255\n') + 4:]
def where(colour):
c = bytes.fromhex(colour)
return [(i % width, i // width) for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == c]
ship = where('d8c048')
station = [p for p in where('5880e0') if p[1] < 250]
# The gauge is the top row of the window; the speed bars are green too, further down.
fuel = sum(1 for x, y in where('50c050') if y < 8) // 64
return (min(y for x, y in ship) if ship else -1, fuel,
'%d,%d' % (min(x for x, y in ship), min(y for x, y in ship)) if ship else 'none',
'%d,%d' % (min(x for x, y in station), min(y for x, y in station)) if station else 'none')
p = look('$BUILD/place.ppm'); d = look('$BUILD/dock.ppm'); d2 = look('$BUILD/dock2.ppm')
print(p[0], p[1], look('$BUILD/nostate.ppm')[1], d[1], d2[1], d2[2], d2[3])
" 2>/dev/null || echo "-1 -1 -1 -1 -1 none none")
EOT
# Placed at the world's origin with a hundred units: row 192 is the origin in the wide view,
# and twelve cells of gauge is a hundred. The plain disk at the same moment has a full tank,
# which is what says the FILE moved these and not the flight.
[ "$PLACEDY" = "192" ] && [ "$PLACEDFUEL" = "12" ] && [ "$PLAINFUEL" != "12" ] \
&& result ok "a state file places the lander" "row 192 and $PLACEDFUEL cells, against $PLAINFUEL with no file" \
|| result no "a state file places the lander" "row $PLACEDY, $PLACEDFUEL cells, plain disk $PLAINFUEL"
# A hundred plus eighty is a hundred and eighty, which is twenty two cells - and it is still
# twenty two three thousand cycles later, so it was paid ONCE and not once a frame.
[ "$DOCKFUEL" = "22" ] && [ "$DOCK2FUEL" = "22" ] \
&& result ok "a matched approach docks and is paid" "a hundred became a hundred and eighty, once" \
|| result no "a matched approach docks and is paid" "$DOCKFUEL cells, then $DOCK2FUEL"
# Held to it, one tile under, rather than merely paid by it.
[ "$RIDESHIP" = "316,104" ] && [ "$RIDESTATION" = "320,96" ] \
&& result ok "and rides it, a tile under" "lander $RIDESHIP, station $RIDESTATION" \
|| result no "and rides it, a tile under" "lander $RIDESHIP, station $RIDESTATION"
grep -q "Wrecked against the station" "$BUILD/wreck.out" \
&& result ok "and the same approach unmatched is a wreck" "64 sixteenths, eight times the limit" \
|| result no "and the same approach unmatched is a wreck" "no wreck in the transcript"
# ---- Clearing puts the cursor back at the top ----
#