Lunar Porter, rung one: it flies
A lander over a moon that wraps. Landing, crashing, fuel, cargo and bases are not here - this rung exists to answer whether it FEELS right, because everything after it is bookkeeping and none of it is worth building on a lander that is no fun to fly. The moon comes for free. The map's column origin is a ring in hardware, so 128 cells is 1024 pixels of surface with no edge and no seam to cross. Position and velocity are sixteen bit in SIXTEENTHS OF A PIXEL, and the unit is the design: gravity is a small number added to a velocity and a velocity is a number added to a position, with no multiply or divide anywhere. 1024 pixels is 16,384 sixteenths, which is 2^14 - so going all the way round is an AND with 0x3FFF rather than a comparison, and it is never wrong at the seam. The lander never moves sideways. The world scrolls under it and it sits at the middle of the screen, which is a byte a frame instead of two and is also what makes the wrap invisible: there is no moment where it jumps. One key is one burn. The console says which key went down and there is no such thing as a key coming up, so a thruster cannot be held - a press adds to the velocity once. That is a property of the machine rather than a choice this program made, and it reads as pumping the engine. Four bugs found by running it, all worth keeping written down: B CANNOT BE A LOOP COUNTER here. Every comparison is an INIB, so the count was overwritten by whichever bound was last tested and the loop reset itself for ever. Counters that outlive arithmetic live in memory. A subroutine answers in Q, and the AND after it read A. The moon came out flat because it was testing the height against 1 instead of the random number. Row minus height, not height minus row: they are equal at the surface, equal does not borrow, and the surface row has to be ground. And the shift register has A as its HIGH half. Written the other way, the view scrolled by 256 cells for every one it should have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
5222c85100
commit
d6c81fa32c
@@ -1519,6 +1519,49 @@ EOT
|
||||
&& result ok "and the near pillar hides the middle" "sprite nought, behind sprite one" \
|
||||
|| result no "and the near pillar hides the middle" "$BALLOVER ball pixels across a pillar of $PILLAR"
|
||||
|
||||
# ---- Lunar Porter, flying ----
|
||||
#
|
||||
# The biggest program on the disk, and the one that uses the most of the machine at once: it
|
||||
# takes the screen, changes the mode, redefines tiles, fills all 3,200 cells of the map from a
|
||||
# terrain it generated, and moves a sprite over it every frame.
|
||||
#
|
||||
# The keys are a burn of the lifting thruster every sixteenth frame, which is more than the
|
||||
# gravity and so keeps the lander up long enough to be looked at. The cycle count is tuned to
|
||||
# catch it before it climbs off the top; if CosmOS's size shifts the boot far enough, the
|
||||
# lander check fails saying it is not on screen, which is a request to re-tune.
|
||||
python3 -c "
|
||||
keys = b'Lander\n'
|
||||
for i in range(400):
|
||||
keys += b'\x80' + b'\x00' * 15
|
||||
open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 4000)
|
||||
"
|
||||
timeout 30 "$EMU" --fast --cycles 12000000 --keyboard "$BUILD/lander.keys" \
|
||||
--screen "$BUILD/lander.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \
|
||||
--ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/lander.out" 2>&1 || true
|
||||
read -r MOON SKY SHIP SHIPLEFT SHIPRIGHT <<EOT
|
||||
$(python3 -c "
|
||||
d = open('$BUILD/lander.ppm', 'rb').read()
|
||||
px = d[d.index(b'255\n') + 4:]
|
||||
w = 320
|
||||
moon, ship = bytes.fromhex('d8d8d8'), bytes.fromhex('d8c048')
|
||||
at = [px[o:o + 3] for o in range(0, len(px), 3)]
|
||||
shipAt = [i % w for i, c in enumerate(at) if c == ship]
|
||||
print(sum(1 for c in at if c == moon), sum(1 for c in at if c == bytes(3)), len(shipAt),
|
||||
min(shipAt) if shipAt else -1, max(shipAt) if shipAt else -1)
|
||||
" 2>/dev/null || echo "0 0 0 -1 -1")
|
||||
EOT
|
||||
# A moon that filled the screen or left it empty would be a terrain generator that had run
|
||||
# off one end of its clamp, which is the way that kind of loop usually fails.
|
||||
[ "$MOON" -gt 10000 ] && [ "$SKY" -gt 10000 ] \
|
||||
&& result ok "a moon with a sky over it" "$MOON pixels of ground and $SKY of sky" \
|
||||
|| result no "a moon with a sky over it" "$MOON ground, $SKY sky"
|
||||
# It never moves sideways: the world scrolls under it, so it is at the middle of a 320 pixel
|
||||
# screen every frame of its life. Forty pixels is the whole of the shape, so none of it has
|
||||
# been clipped or drawn twice.
|
||||
[ "$SHIP" = "40" ] && [ "$SHIPLEFT" = "156" ] && [ "$SHIPRIGHT" = "163" ] \
|
||||
&& result ok "and a lander in the middle of it" "all forty pixels, at the screen's centre" \
|
||||
|| result no "and a lander in the middle of it" "$SHIP pixels, x $SHIPLEFT to $SHIPRIGHT"
|
||||
|
||||
# ---- 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
|
||||
|
||||
Reference in New Issue
Block a user