From 922511c8a75a329c3b76aabaf138f3c52c202bdf Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Thu, 3 Sep 2026 23:02:19 -0400 Subject: [PATCH] A retro thruster, at half the strength of the one that lifts Arresting a rise meant a sideways burn and a wait for the orbit to come back round. That is how a rendezvous really is flown and it is a lot to ask of somebody who has not flown one before, so down now makes the correction directly. HALF THE STRENGTH, deliberately: one sixteenth a tick against two, which is exactly gravity's own step. So it can stop a climb and it can hurry a descent, and it can never turn a landing approach into a crash faster than simply letting go would - the cheap way out of a mistake stays the expensive one. Four against eight on a keyboard, which is the same ratio. It does NOTHING to a lander on the ground, and that guard is load bearing rather than tidy. touchdown has already had its say and returns early once a lander is down, so there is nothing underneath to stop it and no crash to say it happened: measured without the guard, holding it drives the lander clean off the picture and spends a fifth of the tank doing it. ---- And the fixture that hid all of that ---- The first version of the landed check passed just as happily with the guard deleted, and the reason is worth writing down. A lander that has landed WAITS to be told its message has been read, and the keyboard fixture pads with NULs, which are not keys. So the program sat in that loop for ever and the picture was frozen at the moment of touchdown - every thruster held afterwards did nothing, which reads exactly like a working guard. The pad now presses A to get past the wait before the check tests anything, and both the row AND the fuel are read, because an engine that fired and moved nothing would keep the row and one that moved the lander for free would keep the fuel. The altitude bar check upstream leans on that same freeze for its stable end, and its comment said "stays landed" when what it means is "is not running any more". Corrected, because that is the sort of comment that sends the next person looking in the wrong place. --- Programs/CosmOS/Apps/Lander.asm | 44 ++++++++++++++++ Programs/CosmOS/README.md | 2 +- 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 | 79 +++++++++++++++++++++++++++-- 13 files changed, 130 insertions(+), 15 deletions(-) diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index 81e244e..f2df4f6 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -681,6 +681,11 @@ readControls: SUB BRQ burnUp LDA.1 + INIB 0x81 ; Down + CCF + SUB + BRQ burnDown + LDA.1 INIB 0x82 ; Left CCF SUB @@ -704,6 +709,16 @@ burnUp: SETD.1 ThrustUp CALL addWord RET +burnDown: + ; The same rule the pad's retro thruster has: nothing to push away from on the ground. + SETD.0 Landed + LDA.0 + BNA burnDownDone + SETD.0 SpeedDown + SETD.1 ThrustDown + CALL addWord +burnDownDone: + RET burnLeft: SETD.0 SpeedAcross SETD.1 ThrustLeft @@ -875,6 +890,31 @@ fallAlreadyUp: SETD.1 PadUp CALL addWord fallNotUp: + ; ---- And down, at half the strength of up ---- + ; + ; Arresting a rise took a sideways burn and a wait for the orbit to come back round, which is + ; how a rendezvous is really flown and is a lot to ask of somebody who has not flown one + ; before. A retro thruster makes the same correction directly. + ; + ; HALF THE STRENGTH, deliberately. Up is two sixteenths a tick and this is one, which is the + ; same as gravity - so it can stop a climb and it can hurry a descent, and it can never turn + ; a landing approach into a crash faster than simply letting go would. The cheap way out of + ; a mistake stays the expensive one. + SETD.0 Landed + LDA.0 + BNA fallNotDown ; Sitting on the ground, where there is no down to go. + SETD.0 Held + LDA.0 + INIB 0x04 ; Down + AND + BRQ fallNotDown + CALL takeFuel + BRQ fallNotDown ; Nothing in the tank, so nothing out of the engine. + SETD.0 SpeedDown + SETD.1 PadDown + CALL addWord +fallNotDown: + SETD.0 Held LDA.0 INIB 0x02 ; Left @@ -2490,6 +2530,8 @@ FallTick: ; A press on a machine with no pad, which has to be a whole burn because it happens once. ThrustUp: 0xF8 0xFF ; Eight sixteenths upwards, which is minus eight. +ThrustDown: + 0x04 0x00 ; Four down, which is half of the eight that lifts. ThrustLeft: 0xFC 0xFF ; Four to the left. ThrustRight: @@ -2508,6 +2550,8 @@ PadUp: ; ONE, not two. There is no air on a moon, so nothing slows a drift but the opposite thruster ; and stopping means cancelling the velocity exactly. At two a tick the smallest correction ; was twice as big as it needed to be and overshooting was the normal outcome. +PadDown: + 0x01 0x00 ; Plus one: half of up, and exactly gravity's own step. PadLeft: 0xFF 0xFF ; Minus one. PadRight: diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 29de53b..bf990e5 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -727,7 +727,7 @@ from every assembly file in it. Several are old programs written for the bare ma | Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. | | Sprite | Moves a ball across the shell's own text, writing not one byte of the map to do it. It leaves the sprite in the table on the way out, because clearing them is the system's job - see below. | | Pad | Says what the controllers are doing, printing a line whenever one changes. It tells apart the three things that look identical from inside a game that is not responding: a pad nobody noticed, a pad mapped to nothing, and a mapping that is wrong. | -| Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. Four landing pads are carved into the moon after it is generated, levelled to whatever height their first column happened to have, and marked in cyan by an attribute rather than a tile of their own. The lander starts above one, because that is where a porter's day begins. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. A third runs up the left edge and is how much sky is under the lander, half a pixel of bar to a pixel of it; it reads nought the moment the lander is down, and it exists because the orbit takes the lander off the top of the screen and a panel that only works while the ground is in sight is no use above it. And gravity here is the pull MINUS the swing outwards, which is what makes an orbit rather than a one way trip: under four pixels a frame sideways the pull wins and the lander falls, over it the swing wins and the lander climbs, and at it they cancel and it circles. Falling buys sideways speed and climbing spends it - at a rate set by the product of the two, so the trade stops by itself as the sideways speed runs out - and that is the cycle: a fall carries the lander past orbital speed and becomes a climb, the climb pays it back and becomes a fall, periapse and apoapse, round and round. Orbital speed is marked on the drift bar, sixty four pixels either side of the middle, because a number nothing points at is folklore. Gravity never falls off and the moon wraps, so a circular orbit is the same length at any height and one orbital speed serves everywhere. There is a ceiling sixty four pixels above the screen, which pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and one sideways is spent every tick it tries to climb - without that the pin wiped the climb, the trade saw neither fall nor climb, and the speed pushing it up never changed. What kills you out here is running dry a long way from the ground. B, or z on the keyboard, swaps the screen between forty columns and eighty, which is the same map, the same cells and the same engine at half the size: nearly two thirds of the moon at once for the orbit, and twice as big for the landing. The twenty extra rows that buys go to the SKY and not to the moon - the wide view starts twenty four rows above the world's origin rather than at it, so the ground sits near the bottom and the whole flyable band is on the screen. Drawn down from the origin instead it was 71 per cent rock, which is a zoom that shows you more of the thing you cannot fly through. A lander at the ceiling is off the top of a forty column screen, which is where the orbit lives and why the altitude bar had to exist; zoomed out it is in the picture. And there is a station up there, four rows above the world's origin, going round at orbital speed - which needs no physics of its own, because a body at 64 sixteenths is exactly what a circular orbit IS under these rules, at any height, since gravity never falls off and the moon wraps. It is off the top of a forty column screen too, so it is somewhere to go that the zoom is needed to see. It cannot be docked with yet: it goes round, and that is all. | +| Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. Four landing pads are carved into the moon after it is generated, levelled to whatever height their first column happened to have, and marked in cyan by an attribute rather than a tile of their own. The lander starts above one, because that is where a porter's day begins. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. A third runs up the left edge and is how much sky is under the lander, half a pixel of bar to a pixel of it; it reads nought the moment the lander is down, and it exists because the orbit takes the lander off the top of the screen and a panel that only works while the ground is in sight is no use above it. And gravity here is the pull MINUS the swing outwards, which is what makes an orbit rather than a one way trip: under four pixels a frame sideways the pull wins and the lander falls, over it the swing wins and the lander climbs, and at it they cancel and it circles. Falling buys sideways speed and climbing spends it - at a rate set by the product of the two, so the trade stops by itself as the sideways speed runs out - and that is the cycle: a fall carries the lander past orbital speed and becomes a climb, the climb pays it back and becomes a fall, periapse and apoapse, round and round. Orbital speed is marked on the drift bar, sixty four pixels either side of the middle, because a number nothing points at is folklore. Gravity never falls off and the moon wraps, so a circular orbit is the same length at any height and one orbital speed serves everywhere. There is a ceiling sixty four pixels above the screen, which pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and one sideways is spent every tick it tries to climb - without that the pin wiped the climb, the trade saw neither fall nor climb, and the speed pushing it up never changed. What kills you out here is running dry a long way from the ground. B, or z on the keyboard, swaps the screen between forty columns and eighty, which is the same map, the same cells and the same engine at half the size: nearly two thirds of the moon at once for the orbit, and twice as big for the landing. The twenty extra rows that buys go to the SKY and not to the moon - the wide view starts twenty four rows above the world's origin rather than at it, so the ground sits near the bottom and the whole flyable band is on the screen. Drawn down from the origin instead it was 71 per cent rock, which is a zoom that shows you more of the thing you cannot fly through. A lander at the ceiling is off the top of a forty column screen, which is where the orbit lives and why the altitude bar had to exist; zoomed out it is in the picture. And there is a station up there, four rows above the world's origin, going round at orbital speed - which needs no physics of its own, because a body at 64 sixteenths is exactly what a circular orbit IS under these rules, at any height, since gravity never falls off and the moon wraps. It is off the top of a forty column screen too, so it is somewhere to go that the zoom is needed to see. It cannot be docked with yet: it goes round, and that is all. Down is a retro thruster at half the strength of up - one sixteenth a tick against two, which is exactly gravity's own step, so it can stop a climb and hurry a descent and can never turn a landing approach into a crash faster than letting go would. Arresting a rise otherwise meant a sideways burn and a wait for the orbit to come round, which is how a rendezvous really is flown and a lot to ask of somebody who has not flown one. It does nothing at all to a lander on the ground, which is not politeness: touchdown has already had its say and will not speak again, so without that guard a landed lander holding it goes straight through the moon. | | Depth | Four pillars at four distances and a ball walking past them, behind the near ones and in front of the far ones. The ball is sprite nought and every pillar is numbered after it, so table order puts it in front of all four - what actually decides is the depth buffer, asked a column at a time. | | Flip | Draws a whole screen into the bank that is not being shown, waits, and then shows it in one byte out of one port. It writes nothing else at all - not a tile, not a colour - so it does not ask for the screen to be saved, and the line it printed is still there when it comes back. It deliberately does not put the displayed screen back either, because that is the system's to restore: a program that faulted while flipped could not have. | | Edit | A line editor. | diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 700d665..b0a2b01 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index d99d0a6..a4fa732 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 6631a9d..481dee6 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 5742966..77b32cd 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index 1bcc887..2f5fe53 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index ee4dcb8..02759ae 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index e70339d..5fa6870 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 776a259..8247aba 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index 2a76baf..5f194db 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index d6887f0..c8395b9 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 5751 +Lander.sbx 5823 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/video.sh b/Tests/video.sh index 5e5923c..25b4c37 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -1978,10 +1978,12 @@ EOT # 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. +# hundred pixels and is nowhere near the edge of the claim; the descent lands, and a landed +# lander then WAITS to be told its message has been read, so the picture is frozen at the +# moment of touchdown and that end cannot drift with the boot time the way the deleted orbit +# check did. (Frozen, not merely still - the retro thruster check below had to press A to get +# past that wait before it could test anything at all.) One flight that did both spent four +# seconds airborne and read thirty pixels, 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 @@ -2212,6 +2214,75 @@ EOT && result ok "and a lander at the ceiling is only on screen zoomed out" "off the top, then at row $OUTSHIP" \ || result no "and a lander at the ceiling is only on screen zoomed out" "row $INSHIP zoomed in, row $OUTSHIP out" +# ---- The retro thruster ---- +# +# Arresting a rise used to take a sideways burn and a wait for the orbit to come round, which +# is how a rendezvous really is flown and is a lot to ask of somebody who has not flown one +# before. Down makes the correction directly, at HALF the strength of up - one sixteenth a +# tick against two, which is exactly gravity's own step, so it can hurry a descent and can +# never turn a landing approach into a crash faster than simply letting go would. +# +# The same four hundred frames of climb in both, so the two pictures are identical up to the +# moment the retro fires and the only difference measured is the thing being tested. Read off +# the ALTITUDE BAR rather than the lander, because at this point in the flight the lander is +# off the top of the screen - which is what that bar is for. +python3 -c "open('$BUILD/coast.pad','wb').write(b'\x08' * 400 + b'\x00' * 20000)" +python3 -c "open('$BUILD/retro.pad','wb').write(b'\x08' * 400 + b'\x04' * 600 + b'\x00' * 20000)" +# ---- The A press matters, and it took a while to find out why ---- +# +# A lander that has just landed WAITS to be told the message has been read - waitKey, which +# spins until A or Start or a key. The keyboard fixture pads with NULs and a NUL is not a key, +# so without the press here the program sits in that loop for ever and the picture is frozen +# at the moment of landing. Every thruster held afterwards did nothing, which reads exactly +# like a working guard and is nothing of the sort: the first version of this check passed just +# as happily with the guard deleted. +python3 -c "open('$BUILD/sit.pad','wb').write(b'\x00' * 150 + b'\x08' * 90 + b'\x00' * 100 + b'\x10' * 8 + b'\x00' * 8000)" +python3 -c "open('$BUILD/sitdown.pad','wb').write(b'\x00' * 150 + b'\x08' * 90 + b'\x00' * 100 + b'\x10' * 8 + b'\x04' * 2000 + b'\x00' * 8000)" +for view in coast retro; do + timeout 60 "$EMU" --fast --cycles 8600000 --keyboard "$BUILD/fly.keys" \ + --pad "$BUILD/$view.pad" --screen "$BUILD/$view.ppm" \ + --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ + "$BUILD/cosmos.bin" > "$BUILD/$view.out" 2>&1 || true +done +for view in sit sitdown; do + timeout 60 "$EMU" --fast --cycles 14000000 --keyboard "$BUILD/fly.keys" \ + --pad "$BUILD/$view.pad" --screen "$BUILD/$view.ppm" \ + --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ + "$BUILD/cosmos.bin" > "$BUILD/$view.out" 2>&1 || true +done +read -r COASTALT RETROALT SITROW DOWNROW </dev/null || echo "0 0 -1 -2") +EOT +[ "$RETROALT" -lt "$COASTALT" ] \ + && result ok "the retro thruster hurries a descent" "$COASTALT of sky coasting, $RETROALT under thrust" \ + || result no "the retro thruster hurries a descent" "$COASTALT coasting, $RETROALT under thrust" +# And it does NOTHING on the ground. Without that guard a landed lander holding it sinks +# straight through the moon - touchdown has already had its say and will not speak again, so +# there is nothing underneath to stop it and no crash to say it happened. +# Both the row AND the fuel, because either alone is soft: an engine that fired and moved +# nothing would keep the row, and one that moved the lander without charging for it would keep +# the fuel. With the guard deleted this reads "off screen" and a fifth of the tank gone - it +# goes straight through the moon, because touchdown has already had its say and will not speak +# again, so there is nothing underneath to stop it and no crash to say it happened. +[ "$DOWNROW" = "$SITROW" ] && [ "$SITROW" != "-1,0" ] \ + && result ok "and does nothing to a lander on the ground" "$SITROW unchanged after two thousand frames of it" \ + || result no "and does nothing to a lander on the ground" "$SITROW sitting, $DOWNROW holding it down" + # ---- The orbiting station ---- # # A body at orbital speed, which under the rules already here is all a circular orbit IS: the