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