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.
This commit is contained in:
Anachronaut
2026-09-03 23:02:19 -04:00
parent 800f555ffb
commit 922511c8a7
13 changed files with 130 additions and 15 deletions
+44
View File
@@ -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:
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
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 5751
Lander.sbx 5823
Pad.sbx 264
Crash.sbx 632
vars.script 50
+75 -4
View File
@@ -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 <<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:]
bar = sum(1 for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == bytes.fromhex('c860c0') and i % width < 25)
ship = [i // width for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == bytes.fromhex('d8c048')]
# The fuel gauge, which says whether the engine fired at all.
fuel = sum(1 for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == bytes.fromhex('50c050') and i // width < 16)
return bar // 3, (min(ship) if ship else -1), fuel
print(look('$BUILD/coast.ppm')[0], look('$BUILD/retro.ppm')[0],
'%d,%d' % look('$BUILD/sit.ppm')[1:], '%d,%d' % look('$BUILD/sitdown.ppm')[1:])
" 2>/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