From 85029d3b8515d0b4f80cb85cb7b524b482ae6abf Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Thu, 3 Sep 2026 20:57:16 -0400 Subject: [PATCH] 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. --- Programs/CosmOS/Apps/Lander.asm | 241 +++++++++++++++++++++++++++- Tests/expected/cosmosCrossDisk.out | 2 +- Tests/expected/cosmosDrives.out | 2 +- Tests/expected/cosmosFault.out | 2 +- Tests/expected/cosmosFlip.out | 2 +- Tests/expected/cosmosGrid.out | 2 +- Tests/expected/cosmosMonitor.out | 2 +- Tests/expected/cosmosMonitorRun.out | 2 +- Tests/expected/cosmosRun.out | 2 +- Tests/expected/cosmosSlowDisk.out | 2 +- Tests/expected/cosmosSprite.out | 2 +- Tests/video.sh | 66 ++++++++ 12 files changed, 315 insertions(+), 12 deletions(-) diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index cab9f5f..de23f25 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -73,6 +73,7 @@ start: CALL drawMoon CALL putLander CALL putGauge + CALL putOrbital INIA 0x01 OUTA 0x02 ; Key mode. @@ -99,6 +100,7 @@ everyFrame: CALL showDrift CALL showFall CALL showFuel + CALL showHeight SETD.0 Flying LDA.0 BNA everyFrame @@ -1505,6 +1507,223 @@ waitKeyDone: ; in the low half, and the answer needs both halves because a screen is taller than 255 ; sixteenths. 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 LDA.0 INIB 0d8 @@ -1516,13 +1735,13 @@ restOnSurface: SHL SHL SHL ; Four turns, which is times sixteen. - SETD.0 DownHigh + SETD.0 GroundHigh STA.0 RSTA CCF ADD MVQA - SETD.0 DownLow + SETD.0 GroundLow STA.0 RET @@ -2020,6 +2239,24 @@ OrbitAt: ; which is the only thing here that gravity did not already need. OrbitStep: 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: 0x00 Lift: diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 4681189..e5652a8 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -25,7 +25,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 3373cd7..9996f44 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 876807d..4934619 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -32,7 +32,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index c84ae4d..7a2bee7 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index 3c811ad..a96f531 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 758e7da..40b85be 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -108,7 +108,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 1779c0f..1e6fbf7 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -27,7 +27,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index b467867..99ecc07 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index c5c9cde..78d19c2 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -13,7 +13,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index 4b86208..e046646 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 4651 +Lander.sbx 4968 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/video.sh b/Tests/video.sh index a1187d0..82f501c 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -1966,6 +1966,72 @@ EOT && 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" +# ---- 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 </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 ---- # # A screen with nothing on it and a cursor half way down it is not a cleared screen. This