Orbit, and two bars that answer instead of reporting

Going sideways lifts the moon off you. Not because gravity weakened -
because at speed the surface falls away underneath as fast as the lander
falls towards it, which is what an orbit is.

A GRADIENT OUT OF INTEGER ARITHMETIC. Gravity is one sixteenth of a pixel
a tick and there is nothing between that and nothing, so it cannot be
scaled down. Instead four times the sideways speed goes into a byte every
tick and the tick's gravity is skipped whenever that byte carries: the
fraction cancelled is the speed over 64, smoothly, with no multiply and no
divide. At four pixels a frame it carries every time. That is the linear
approximation; the honest one is the square, and wants a table.

It did nothing at all for its first two versions. Once because the relief
was a 256th a tick, so orbit wanted a speed no lander would reach; and
once because A IS THE HIGH HALF of the shift register, so multiplying by
four left the answer in A while the code read B, which is nought. The same
trap as the scroll register and the pixel conversion before it.

And a bar for the vertical speed beside the one for drift, both GREEN
WHILE A LANDING WOULD SURVIVE AND RED WHILE IT WOULD NOT. That turns two
numbers into one question - can I put down - and answers it at a glance.

WHAT THIS COST: the flown delivery check. A recording is a list of buttons
and not a flight, so replaying it under different gravity flies somewhere
else; the delivery became a crash two columns short. The fixture is still
there and is still a faithful record of what somebody did, and is no
longer a record of what happens.

That is the standing cost of a flown fixture, and it is worse than the
transcript tests dropped earlier: those broke when an output moved, and
this breaks whenever a NUMBER moves. Making the delivery reachable without
flying - a way to start already carrying, or at a chosen base - is what
would fix it properly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-03 16:25:56 -04:00
co-authored by Claude Opus 5
parent 4b1c3d8e3f
commit a069ee7a00
13 changed files with 270 additions and 40 deletions
+59 -27
View File
@@ -1758,34 +1758,63 @@ UNSAID="$(countColour "$BUILD/unsaid.ppm" f0f0f0)"
&& result ok "and opening the throttle wipes it" "the line went with the moment" \
|| result no "and opening the throttle wipes it" "$UNSAID pixels of it still there"
# ---- A delivery, flown by hand and kept ----
# ---- Orbit: going sideways is how you stop falling ----
#
# THIS ONE WAS PLAYED RATHER THAN WRITTEN. Cyan base to red base, twenty five seconds of
# steering recorded with --record-pad and replayed here. Several attempts at authoring a
# flight like it by hand got within two columns and no closer; that is a piloting exercise
# rather than a test, and playing it once is the whole answer.
# Two runs holding the lifting thruster, one of them also going sideways. Both climb, because
# thrust beats gravity - but the one with speed climbs FASTER, because it is feeling less of
# the moon. That is the whole mechanic: at four pixels a frame the surface is falling away
# underneath as fast as the lander is falling towards it.
#
# What is checked is the FIRST LETTER of the message the base gives back. Delivered, Loaded,
# Nowhere and Not are 30, 22, 37 and 37 pixels of white in that cell, so a D is a delivery and
# nothing else is. Counting the whole message would pass on any message of the same length;
# comparing the picture would pass on nothing at all the next time a font changes.
# Read off the descent bar rather than the lander, because both are off the top of the screen
# by then and the bar is not - it is drawn at a screen position and says the vertical speed
# whatever the lander is doing.
#
# It is the only check that a cargo ever reaches anywhere, and it is the reason the recorder
# exists.
timeout 60 "$EMU" --fast --cycles 26000000 --keyboard "$ROOT/Tests/input/landerDemo.keys" \
--pad "$ROOT/Tests/input/landerDemo.pad" --screen "$BUILD/demo.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/demo.out" 2>&1 || true
FIRST="$(python3 -c "
d = open('$BUILD/demo.ppm', 'rb').read()
px = d[d.index(b'255\n') + 4:]
w, white = 320, bytes.fromhex('f0f0f0')
print(sum(1 for x in range(8) for y in range(8, 16)
if px[(y * w + x) * 3:(y * w + x) * 3 + 3] == white))
" 2>/dev/null || echo -1)"
[ "$FIRST" = "30" ] \
&& result ok "a flown delivery arrives" "the base answered with a D" \
|| result no "a flown delivery arrives" "$FIRST pixels in the first cell, and a D is 30"
# The bar was 41 pixels against 53 when this was written. What is checked is that the second
# is LONGER, which is the claim; the numbers themselves are tuning.
python3 -c "
open('$BUILD/up.pad','wb').write(b'\x08' * 4000)
open('$BUILD/upside.pad','wb').write(b'\x09' * 4000)
"
for which in up upside; do
timeout 30 "$EMU" --fast --cycles 8000000 --keyboard "$ROOT/Tests/input/landerDemo.keys" \
--pad "$BUILD/$which.pad" --screen "$BUILD/$which.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/$which.out" 2>&1 || true
done
read -r STRAIGHT SIDEWAYS <<EOT
$(python3 -c "
def bar(path):
d = open(path, 'rb').read()
px = d[d.index(b'255\n') + 4:]
w = 320
return sum(1 for y in range(200)
if px[(y * w + 300) * 3:(y * w + 300) * 3 + 3].hex() in ('50c050', 'd04038'))
print(bar('$BUILD/up.ppm'), bar('$BUILD/upside.ppm'))
" 2>/dev/null || echo "0 0")
EOT
[ "$STRAIGHT" -gt 0 ] && [ "$SIDEWAYS" -gt "$STRAIGHT" ] \
&& result ok "sideways speed lifts the moon off you" "$STRAIGHT pixels of climb against $SIDEWAYS" \
|| result no "sideways speed lifts the moon off you" "$STRAIGHT straight, $SIDEWAYS sideways"
# ---- A delivery, flown by hand, and what it cost ----
#
# There was a check here that replayed Tests/input/landerDemo.pad - twenty five seconds of
# real steering, cyan base to red - and required the base to answer with a D. It was the only
# check that a cargo ever reached anywhere.
#
# ORBIT KILLED IT. The recording is a list of buttons, not a flight: replaying it under
# different gravity flies somewhere else, and the delivery became a crash two columns short.
# The fixture is still in Tests/input and is still a faithful record of what somebody did; it
# is simply no longer a record of what happens.
#
# THAT IS THE STANDING COST OF A FLOWN FIXTURE, and it is worse than the transcript tests
# dropped earlier: those broke when an output moved, and this breaks whenever a NUMBER moves.
# Every tuning change invalidates every recorded flight.
#
# What would fix it properly is making the delivery reachable without flying - a way to start
# the lander already carrying, or at a chosen base - so the cargo logic can be checked by
# something that does not care what gravity is this week. Until then the delivery path is
# exercised by playing the game.
# ---- The landing pads, carved and coloured ----
#
@@ -1893,11 +1922,14 @@ timeout 30 "$EMU" --fast --cycles 1600000 --keyboard "$BUILD/lander.keys" \
read -r BARLEFT BARWIDE STILLWIDE <<EOT
$(python3 -c "
def bar(path):
# EITHER COLOUR. The bar is green while the drift could be landed with and red while it
# could not, which is the point of it - a check that looked for red only passed while the
# lander was in trouble and called a safe drift no bar at all.
d = open(path, 'rb').read()
px = d[d.index(b'255\n') + 4:]
red = bytes.fromhex('d04038')
lit = (bytes.fromhex('d04038'), bytes.fromhex('50c050'))
at = [i % 320 for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == red and i // 320 == 189]
if px[i * 3:i * 3 + 3] in lit and i // 320 == 189]
return (min(at), len(at)) if at else (-1, 0)
drifting = bar('$BUILD/drift.ppm')
print(drifting[0], drifting[1], bar('$BUILD/lander.ppm')[1])