An altitude bar, and orbital speed marked on the drift bar

The orbit takes the lander off the top of the screen, and the panel only
worked while the ground was in sight. Both other bars are rates: they say
how fast, and neither says where.

The altitude bar is height above the surface underneath, up the left edge,
half a pixel of bar to a pixel of sky - the flyable band is about 256
pixels and the screen is 200 tall, so pixel for pixel would run off the
top of the very screen it describes. Above the surface rather than above
some fixed line, so it reads NOUGHT the moment the lander is down.

The marks say where 64 sixteenths is. Without one that number is folklore:
a pilot can feel that somewhere around here the falling stops and has no
way to see where. The bar is a pixel a sixteenth from the middle at 160,
so the marks sit at 224 and at the two pixels before 96, adjacent rather
than overlapping - a bar at orbital speed would otherwise hide the thing
it is being measured against.

Both in magenta. Red and green are taken and they MEAN something here,
how fast and whether it can be landed with, and an altitude is neither.
White was the first choice and the ceiling check counts white to find its
warning, so it read a warning that was never up - the suite caught that.
Cyan was the second and it is the colour of a landing pad, which is
checked as whole cells of it.

groundLevel is factored out of restOnSurface, which had the same sum.

Three checks, each seen to fail on its own break. Two flights, because no
one flight holds both ends of the bar well: the climb reads 219 pixels and
the descent lands and then stays landed. They fly on their own keyboard
file - the shared one holds a key down every forty eight bytes for the
held-thruster check, so borrowing it flew the lander from the keyboard and
the controller at once and turned the gentle descent into a crash.
This commit is contained in:
Anachronaut
2026-09-03 20:57:16 -04:00
parent c514d5328e
commit 85029d3b85
12 changed files with 315 additions and 12 deletions
+239 -2
View File
@@ -73,6 +73,7 @@ start:
CALL drawMoon CALL drawMoon
CALL putLander CALL putLander
CALL putGauge CALL putGauge
CALL putOrbital
INIA 0x01 INIA 0x01
OUTA 0x02 ; Key mode. OUTA 0x02 ; Key mode.
@@ -99,6 +100,7 @@ everyFrame:
CALL showDrift CALL showDrift
CALL showFall CALL showFall
CALL showFuel CALL showFuel
CALL showHeight
SETD.0 Flying SETD.0 Flying
LDA.0 LDA.0
BNA everyFrame BNA everyFrame
@@ -1505,6 +1507,223 @@ waitKeyDone:
; in the low half, and the answer needs both halves because a screen is taller than 255 ; in the low half, and the answer needs both halves because a screen is taller than 255
; sixteenths. ; sixteenths.
restOnSurface: restOnSurface:
CALL groundLevel
SETD.0 GroundLow
LDA.0
SETD.1 DownLow
STA.1
SETD.0 GroundHigh
LDA.0
SETD.1 DownHigh
STA.1
RET
; ---- How far above the ground, as a bar up the left edge ----
;
; The orbit put the lander OFF THE TOP OF THE SCREEN, and an instrument panel that only works
; while the ground is in sight is no use to a game whose best flying happens above it. Both
; other bars are rates: they say how fast, and neither of them says where.
;
; Height above the surface underneath rather than above some fixed line, so it reads NOUGHT
; the moment the lander is down - a bar that still showed a third of itself while sitting on a
; pad would be an altimeter nobody could trust. It moves as the moon does underneath, which is
; not noise: passing over a mountain really does leave less room below.
;
; Half a pixel of bar to a pixel of sky. The flyable band is about 256 pixels from the ceiling
; to the lowest ground and the screen is 200 tall, so a bar drawn pixel for pixel would run
; off the top of the very screen it is trying to describe.
showHeight:
CALL groundLevel
SETD.0 GroundLow
LDA.0
SETD.1 DownLow
LDB.1
CCF
SUB
SETD.0 HeightLow
STQ.0
SETD.0 GroundHigh
LDA.0
SETD.1 DownHigh
LDB.1
SUB ; And the borrow the half below left, which is the other half.
SETD.0 HeightHigh
STQ.0
LDA.0 ; DP0 is still HeightHigh, from the store above.
INIB 0x80
AND
BNQ showHeightNone ; Negative: the feet are under the surface, so there is no sky.
LDA.0 ; The AND left A alone, so it still holds the high half.
INIB 0d16
CCF
SUB
BNC showHeightFull ; 4,096 sixteenths or more, which is the whole bar and then some.
LDA.0 ; DP0 still names the high half, and the compare did not touch A.
SETD.0 HeightLow
LDB.0
SHL
SHL
SHL ; ---- Over thirty two, which comes out in A ----
;
; A is the HIGH half of the shift register, so three turns LEFT of
; the whole sixteen bits leaves bits five to twelve in A - which is
; the height over thirty two, in one instruction each and no
; division anywhere.
BRI showHeightReady
showHeightFull:
INIA 0d127
BRI showHeightReady
showHeightNone:
RSTA
showHeightReady:
SETD.1 HeightBar
STA.1
; ---- Grown upwards, from a fixed foot ----
;
; A sprite is placed by its TOP, so a bar that grows up is one whose top moves as its height
; changes. Both come out of the same number and the foot stays put.
INIA 0d180
SETD.0 HeightBar
LDB.0
CCF
SUB
SETD.1 HeightAt
STQ.1
; Nought is the off switch, and it is the SIZE that switches it: a target height of nought
; is the natural height, which would be a tile sitting on the ground pretending to be sky.
LDA.0 ; DP0 still names the bar, from working the top out above.
BRA showHeightEmpty
INIA 0x11
BRI showHeightSized
showHeightEmpty:
RSTA
showHeightSized:
SETD.1 HeightSize
STA.1
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x30
OUTA 0xE5 ; Sprite three begins forty eight bytes in.
INIA 0xC8
OUTA 0xE9 ; The solid block.
INIA 0x05
OUTA 0xE9 ; ---- Scheme five, magenta ----
;
; Red and green are taken and they MEAN something here: how fast,
; and whether it can be landed with. Neither is a thing an altitude
; is, so magenta is the panel's other job - where the lander is
; rather than how it is going - and the marks below share it.
;
; Cyan was the first choice and it is the colour of a landing pad.
; The pads are checked as whole cells of it, so a three pixel bar
; in the same colour would have made that check count the panel.
INIA 0d8
OUTA 0xE9
RSTA
OUTA 0xE9 ; X, hard against the left edge and clear of the moon's middle.
SETD.0 HeightAt
LDA.0
OUTA 0xE9
RSTA
OUTA 0xE9 ; Y.
SETD.0 HeightSize
LDA.0
OUTA 0xE9
RSTA
OUTA 0xE9 ; No flags.
INIA 0d3
OUTA 0xE9
RSTA
OUTA 0xE9 ; Three pixels wide.
SETD.0 HeightBar
LDA.0
OUTA 0xE9
RSTA
OUTA 0xE9 ; The height, which is the altitude. NOUGHT DRAWS NOTHING.
OUTA 0xE9 ; And no depth, A being nought already.
RET
; ---- Where orbital speed is, marked on the drift bar ----
;
; The bar says how fast sideways and the physics says 64 sixteenths is the speed at which the
; swing outwards cancels the pull. Without a mark on it that number is FOLKLORE: a pilot can
; feel that somewhere around here the falling stops, and has no way to see where. With one,
; the bar reaching the mark IS circular orbit, under it is falling and past it is climbing,
; and the whole mechanic becomes something read at a glance instead of guessed at.
;
; One each way, because a moon that wraps can be gone round in either direction.
;
; Placed so the BAR'S EDGE MEETS THE MARK at exactly orbital speed. The rightward bar starts
; at the middle and runs 64 pixels to 223, so its mark begins at 224; the leftward one ends at
; the middle and starts at 96, so its mark is the two pixels before that. Adjacent rather than
; overlapping, or a bar at orbital speed would hide the thing it is being measured against.
;
; Written once. They never move, and a sprite table is a memory rather than a display list.
putOrbital:
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x40
OUTA 0xE5 ; Sprite four begins sixty four bytes in.
INIA 0d94
CALL putMark
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x50
OUTA 0xE5 ; And sprite five, sixteen bytes after it.
INIA 0d224
CALL putMark
RET
; One mark, at the screen column in A, straddling the drift bar so the bar meets it end on.
putMark:
SETD.1 MarkAt
STA.1
INIA 0xC8
OUTA 0xE9 ; The solid block, which is what every bar here is made of.
INIA 0x05
OUTA 0xE9 ; Magenta, the same as the height bar: both say where, not how fast.
; White was the first choice and white is what TEXT is, so the
; ceiling check - which counts white to find its warning - started
; counting these instead and read a warning that was never up.
LDA.1 ; DP1 still names it, from being handed it above.
OUTA 0xE9
RSTA
OUTA 0xE9 ; X.
INIA 0d184
OUTA 0xE9
RSTA
OUTA 0xE9 ; Y, four pixels above the bar it brackets.
INIA 0x11
OUTA 0xE9 ; One tile by one, however small it ends up drawn.
RSTA
OUTA 0xE9 ; No flags.
INIA 0d2
OUTA 0xE9
RSTA
OUTA 0xE9 ; Two pixels wide.
INIA 0d11
OUTA 0xE9
RSTA
OUTA 0xE9 ; And eleven tall, so it shows above and below a three pixel bar.
OUTA 0xE9 ; No depth, A being nought already.
RET
; ---- Where a lander standing on the ground below would have its top ----
;
; The same sum, wanted in two places: putting one down on the surface, and measuring how far
; above the surface one is. In sixteenths, because that is what a height is measured in.
groundLevel:
SETD.0 SurfaceAt SETD.0 SurfaceAt
LDA.0 LDA.0
INIB 0d8 INIB 0d8
@@ -1516,13 +1735,13 @@ restOnSurface:
SHL SHL
SHL SHL
SHL ; Four turns, which is times sixteen. SHL ; Four turns, which is times sixteen.
SETD.0 DownHigh SETD.0 GroundHigh
STA.0 STA.0
RSTA RSTA
CCF CCF
ADD ADD
MVQA MVQA
SETD.0 DownLow SETD.0 GroundLow
STA.0 STA.0
RET RET
@@ -2020,6 +2239,24 @@ OrbitAt:
; which is the only thing here that gravity did not already need. ; which is the only thing here that gravity did not already need.
OrbitStep: OrbitStep:
0x00 0x00
; Ground level under the lander, the height above it, and what the bar makes of that.
GroundLow:
0x00
GroundHigh:
0x00
HeightLow:
0x00
HeightHigh:
0x00
HeightBar:
0x00
HeightAt:
0x00
HeightSize:
0x00
MarkAt:
0x00
Outward: Outward:
0x00 0x00
Lift: Lift:
+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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
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 4651 Lander.sbx 4968
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+66
View File
@@ -1966,6 +1966,72 @@ EOT
&& result ok "and no drift draws none" "a width of nought is a sprite that is not there" \ && result ok "and no drift draws none" "a width of nought is a sprite that is not there" \
|| result no "and no drift draws none" "$STILLWIDE pixels of bar with nothing moving" || result no "and no drift draws none" "$STILLWIDE pixels of bar with nothing moving"
# ---- The altitude bar, and the mark that says where orbital speed is ----
#
# The orbit takes the lander OFF THE TOP OF THE SCREEN, which left an instrument panel that
# only worked while the ground was in sight. Two additions answer that: a bar up the left edge
# for how much sky is underneath, and a pair of marks on the drift bar showing where the
# sideways speed stops being a fall and starts being an orbit.
#
# BOTH HALVES OF THE BAR, because either alone is vacuous. A bar that was always full would
# pass "there is one while flying" and a bar that was never drawn would pass "there is none
# while landed" - it is the two together that say the thing is measuring something.
#
# Two flights, because no one flight holds both ends well. The climb reaches a bar of two
# hundred pixels and is nowhere near the edge of the claim; the descent lands and then STAYS
# landed, so its end of it cannot drift with the boot time the way the deleted orbit check
# did. One flight that did both spent four seconds airborne and read thirty pixels, which is a
# margin thin enough to be luck.
#
# The marks are checked BY POSITION rather than by counting, because position is the whole
# claim: 64 sixteenths is orbital speed, the bar is a pixel a sixteenth from the middle at
# 160, so the marks belong at 224 and at the two pixels before 96. A count would pass with
# them anywhere on the screen. Read off the landed frame, where nothing is drifting and the
# drift bar cannot be lying across them.
# ---- Its own keyboard file, and that is not fussiness ----
#
# $BUILD/lander.keys is not just the command that starts the program: it holds a key down
# every forty eight bytes, because the test that made it is checking that a HELD KEY keeps
# 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 "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" \
--pad "$BUILD/climb.pad" --screen "$BUILD/climb.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/climb.out" 2>&1 || true
timeout 60 "$EMU" --fast --cycles 9000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/settle.pad" --screen "$BUILD/settle.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/settle.out" 2>&1 || true
read -r FLYING RESTING MARKS <<EOT
$(python3 -c "
def magenta(path, low, high):
d = open(path, 'rb').read()
px = d[d.index(b'255\n') + 4:]
ink = bytes.fromhex('c860c0')
return [i % 320 for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == ink and low <= i % 320 <= high]
# The bar is at the left edge; the marks are out where the drift bar is.
print(len(magenta('$BUILD/climb.ppm', 0, 20)),
len(magenta('$BUILD/settle.ppm', 0, 20)),
','.join(str(x) for x in sorted(set(magenta('$BUILD/settle.ppm', 21, 319)))) or 'none')
" 2>/dev/null || echo "0 0 none")
EOT
[ "$FLYING" -gt 100 ] \
&& result ok "a lander above the ground draws an altitude bar" "$FLYING pixels of it" \
|| result no "a lander above the ground draws an altitude bar" "$FLYING pixels of it"
# Sitting on a pad is nought sky underneath, and NOUGHT DRAWS NOTHING. An altimeter that still
# showed a third of itself on the ground is one nobody could read the rest of.
[ "$RESTING" = "0" ] \
&& result ok "and one sitting on the ground draws none" "a bar of nought is no bar at all" \
|| result no "and one sitting on the ground draws none" "$RESTING pixels still up"
[ "$MARKS" = "94,95,224,225" ] \
&& result ok "orbital speed is marked on the drift bar" "64 pixels either side of the middle" \
|| result no "orbital speed is marked on the drift bar" "marks at $MARKS"
# ---- Clearing puts the cursor back at the top ---- # ---- Clearing puts the cursor back at the top ----
# #
# A screen with nothing on it and a cursor half way down it is not a cleared screen. This # A screen with nothing on it and a cursor half way down it is not a cleared screen. This