Thruster flames, so a watcher can see what the pilot is doing

Every reading on this screen is a number drawn as a bar - how fast
sideways, how fast down, how much sky, how much tank - and all of it says
what is happening TO the lander. None of it says what the pilot is doing
about it, so somebody watching over a shoulder has to read gauges to work
out that a thruster is even lit.

A plume hangs off whichever side the engine is pushing from: under the
lander to lift, over it to retro, and on the far side from the way it is
being pushed sideways, since that is the side the gas leaves. One tile
does up and down, because a vertical flip turns one into the other, and a
second does the sides, because a flip cannot rotate a tile a quarter
turn. Twenty four pixels each, on purpose, so a count of them means
something.

HELD, NOT FIRED. The engine fires one frame in ten, because that is the
tick gravity is applied on, and a flame that honest would be one frame of
light six times a second - a fault lamp, not a rocket. What is drawn is
the button being down, which is the truthful answer to "is the pilot
burning": the tick is how the sum gets done, not what is happening.

An empty tank draws nothing, and nor does the retro thruster on the
ground, because in both cases the button really is doing nothing. The
second of those was a lie the first version told.

Red, and that is by elimination again: white is the ceiling warning, cyan
the landing pads, magenta the instruments, blue the station, yellow the
lander itself - and the lander is counted as exactly forty pixels of
yellow, so a yellow flame would have broken it.

Three checks, each seen to fail on its own break. Two things the fixtures
taught: a lander placed at the world's origin sits BEHIND the two row
window, which reads exactly like a flame that is not drawn; and red has
to be looked for in a box round the lander rather than a column, because
the speed bars go red and one of the four pads is red too.
This commit is contained in:
Anachronaut
2026-09-04 11:49:38 -04:00
parent 418631a221
commit c408fc6cf6
13 changed files with 404 additions and 12 deletions
+305 -1
View File
@@ -106,6 +106,7 @@ everyFrame:
CALL touchdown
CALL dock
CALL showLander
CALL showFlames
CALL showStation
CALL showDrift
CALL showFall
@@ -184,6 +185,49 @@ putTiles:
OUTA 0xE7
INIA 0x01
OUTA 0xE8 ; Blit.
OUTA 0xE0
SETD.1 FlameDownArtAt
SETD.0 FlameDownArt
STD.0.1
LDA.1
OUTA 0xE1
INCD.1
LDA.1
OUTA 0xE2
INIA 0d4
OUTA 0xE3
INIA 0x32
OUTA 0xE4
INIA 0xC0
OUTA 0xE5 ; Tile 203, the plume that hangs under a lifting lander.
RSTA
OUTA 0xE6
INIA 0x40
OUTA 0xE7
INIA 0x01
OUTA 0xE8
OUTA 0xE0
SETD.1 FlameSideArtAt
SETD.0 FlameSideArt
STD.0.1
LDA.1
OUTA 0xE1
INCD.1
LDA.1
OUTA 0xE2
INIA 0d4
OUTA 0xE3
INIA 0x33
OUTA 0xE4
RSTA
OUTA 0xE5 ; Tile 204, the same thing lying on its side.
OUTA 0xE6
INIA 0x40
OUTA 0xE7
INIA 0x01
OUTA 0xE8
RET
; ---- A moon, one column at a time ----
@@ -1448,13 +1492,18 @@ showLander:
LDB.1
CCF
ADD
SETD.1 ShipTopLow
STQ.1
OUTQ 0xE9
SETD.0 PixelHigh
LDA.0
SETD.1 ShipYAdd
INCD.1
LDB.1
ADD ; And the carry, which is the other half of the shift.
OUTQ 0xE9
SETD.1 ShipTopHigh
STQ.1
OUTQ 0xE9 ; Kept as well as sent: the flames hang off this row.
RET
; ---- Green if it would survive, red if it would not ----
@@ -2080,6 +2129,198 @@ easeAcrossWrap:
STQ.0 ; Round the moon, the way every other step along it is.
RET
; ---- Flames, so a watcher can see what the pilot is doing ----
;
; Every reading on this screen is a NUMBER drawn as a bar: how fast sideways, how fast down,
; how much sky, how much tank. All of it says what is happening TO the lander and none of it
; says what the pilot is doing about it, so somebody watching over a shoulder has to read
; gauges to work out that a thruster is even lit.
;
; ---- Shown while the button is held, not while the engine fires ----
;
; The engine fires one frame in ten, because that is the tick gravity is applied on. A flame
; that honest would be one frame of light six times a second, which is a fault lamp and not a
; rocket. What is drawn is the BUTTON BEING DOWN, and that is the truthful answer to "is the
; pilot burning" - the tick is how the sum gets done, not what is happening.
;
; An empty tank draws nothing, because then the button really is doing nothing.
showFlames:
INIA 0x70
SETD.0 FlameSlot
STA.0
INIA 0xCB
SETD.0 FlameTile
STA.0
SETD.0 ShipX
SETD.1 FlameXLow
CALL copyWord ; Straight under the lander, whichever way it is pushing.
SETD.0 Fuel
LDA.0
BRA flameNoLift
SETD.0 Held
LDA.0
INIB 0x08 ; Up, so the plume is UNDER the lander, pushing it away.
AND
BRQ flameTryDown
SETD.0 FlameAway
SETD.1 FlameOffLow
CALL copyWord
CALL flameBelow
RSTA
BRI flameLiftFlagged
flameTryDown:
SETD.0 Landed
LDA.0
BNA flameNoLift ; The retro thruster does nothing on the ground, so it shows nothing.
SETD.0 Held
LDA.0
INIB 0x04 ; Down, so it is over the lander and turned upside down.
AND
BRQ flameNoLift
SETD.0 FlameBack
SETD.1 FlameOffLow
CALL copyWord
CALL flameBelow
INIA 0x02 ; VFLIP.
flameLiftFlagged:
SETD.0 FlameFlags
STA.0
INIA 0x11
BRI flameLiftSized
flameNoLift:
RSTA
flameLiftSized:
SETD.0 FlameSize
STA.0
CALL putFlame
INIA 0x80
SETD.0 FlameSlot
STA.0
INIA 0xCC
SETD.0 FlameTile
STA.0
SETD.0 ShipTopLow
SETD.1 FlameYLow
CALL copyWord
SETD.0 Fuel
LDA.0
BRA flameNoSide
SETD.0 Held
LDA.0
INIB 0x02 ; Left, so the plume is on the RIGHT, pushing that way.
AND
BRQ flameTryRight
SETD.0 FlameAway
SETD.1 FlameOffLow
CALL copyWord
CALL flameBeside
RSTA
BRI flameSideFlagged
flameTryRight:
SETD.0 Held
LDA.0
INIB 0x01 ; Right, so it is on the left and turned about.
AND
BRQ flameNoSide
SETD.0 FlameBack
SETD.1 FlameOffLow
CALL copyWord
CALL flameBeside
INIA 0x01 ; HFLIP.
flameSideFlagged:
SETD.0 FlameFlags
STA.0
INIA 0x11
BRI flameSideSized
flameNoSide:
RSTA
flameSideSized:
SETD.0 FlameSize
STA.0
CALL putFlame
RET
; The lander's row, plus the offset - a whole word, so its sign comes along with it.
flameBelow:
SETD.0 ShipTopLow
LDA.0
SETD.1 FlameOffLow
LDB.1
CCF
ADD
SETD.1 FlameYLow
STQ.1
SETD.0 ShipTopHigh
LDA.0
SETD.1 FlameOffHigh
LDB.1
ADD ; And the carry, which is the other half of it.
SETD.1 FlameYHigh
STQ.1
RET
; And the lander's column, the same way.
flameBeside:
SETD.0 ShipX
LDA.0
SETD.1 FlameOffLow
LDB.1
CCF
ADD
SETD.1 FlameXLow
STQ.1
INCD.0
LDA.0
SETD.1 FlameOffHigh
LDB.1
ADD
SETD.1 FlameXHigh
STQ.1
RET
; One flame, out of the block above.
putFlame:
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
SETD.0 FlameSlot
LDA.0
OUTA 0xE5
SETD.0 FlameTile
LDA.0
OUTA 0xE9
INIA 0x01
OUTA 0xE9 ; Scheme one, red, which is what a rocket looks like.
SETD.0 FlameXLow
LDA.0
OUTA 0xE9
SETD.0 FlameXHigh
LDA.0
OUTA 0xE9
SETD.0 FlameYLow
LDA.0
OUTA 0xE9
SETD.0 FlameYHigh
LDA.0
OUTA 0xE9
SETD.0 FlameSize
LDA.0
OUTA 0xE9 ; Nought when nothing is held, which is the off switch.
SETD.0 FlameFlags
LDA.0
OUTA 0xE9
RSTA
OUTA 0xE9
OUTA 0xE9
OUTA 0xE9
OUTA 0xE9 ; Its natural size.
OUTA 0xE9 ; And no depth.
RET
; ---- A key, or a button ----
;
; Somebody flying on a controller should not have to reach for the keyboard to say "yes, I
@@ -3190,6 +3431,38 @@ Carrying:
0x00
Landed:
0x00
; Where showLander put the lander this frame, which is where the flames have to meet it.
ShipTopLow:
0x00
ShipTopHigh:
0x00
; What a flame is being asked to be, before it is written out.
FlameSlot:
0x00
FlameTile:
0x00
FlameXLow:
0x00
FlameXHigh:
0x00
FlameYLow:
0x00
FlameYHigh:
0x00
FlameFlags:
0x00
FlameSize:
0x00
FlameOffLow:
0x00
FlameOffHigh:
0x00
FlameAway:
0x08 0x00 ; A tile past the lander, which is where a plume comes out.
FlameBack:
0xF8 0xFF ; And a tile the other way, for the engine pointing the other way.
; Holding onto the station, which is the same kind of rest as sitting on the ground.
Docked:
0x00
@@ -3298,6 +3571,37 @@ StationArt:
StationArtAt:
#Reserve 0d2
; ---- The plume, which hangs off whichever side the engine is pushing from ----
;
; Widest where it meets the lander and tapering away, so it reads as coming OUT rather than
; sitting there. One tile for up and down, since a vertical flip turns one into the other, and
; a second for the sides, because a flip cannot rotate a tile a quarter turn.
;
; Twenty four pixels each, on purpose: it makes a count of them mean something.
FlameDownArt:
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
0x00 0x00 0x00 0x01 0x01 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
FlameDownArtAt:
#Reserve 0d2
FlameSideArt:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00
0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x00
0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x00
0x01 0x01 0x01 0x01 0x01 0x01 0x00 0x00
0x01 0x01 0x01 0x01 0x00 0x00 0x00 0x00
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
FlameSideArtAt:
#Reserve 0d2
LanderArt:
0x00 0x00 0x01 0x01 0x01 0x01 0x00 0x00
0x00 0x01 0x01 0x01 0x01 0x01 0x01 0x00
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
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 6814
Lander.sbx 7477
Pad.sbx 264
Crash.sbx 632
vars.script 50
+88
View File
@@ -2515,6 +2515,94 @@ grep -q "Wrecked against the station" "$BUILD/wreck.out" \
&& result ok "and the same approach unmatched is a wreck" "64 sixteenths, eight times the limit" \
|| result no "and the same approach unmatched is a wreck" "no wreck in the transcript"
# ---- Thruster flames ----
#
# Every other reading here is a number drawn as a bar: how fast sideways, how fast down, how
# much sky, how much tank. All of it says what is happening TO the lander and none of it says
# what the PILOT is doing, so somebody watching over a shoulder has to read gauges to work out
# that a thruster is even lit.
#
# A plume hangs off whichever side the engine is pushing from - under the lander to go up,
# over it to go down, and on the far side from the way it is being pushed sideways. Twenty
# four pixels of it, which is the whole tile, so the count says it is not clipped.
#
# Held rather than fired. The engine fires one frame in ten, because that is the tick gravity
# is applied on, and a flame that honest would be one frame of light six times a second - a
# fault lamp, not a rocket. The button being down is the thing being shown.
python3 -c "
import struct
# The same place every time, with an empty tank for the last of them. Down 512 sixteenths and
# not nought: the world's origin is the top of the screen, and the two row window is drawn OVER
# the map there, so a lander placed at the origin is behind the fuel gauge and its flame with
# it - which reads exactly like a flame that is not being drawn.
for name, fuel in (('flamefull', 255), ('flamedry', 0)):
open('$BUILD/%s.state' % name, 'wb').write(struct.pack('<hhhhhhBBBB',
0x14A0, 512, 0, 0, 0x14A0, 2600, fuel, 0, 0, 0))
"
for which in flamefull flamedry; do
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/$which.img"
"$ROOT/SplitDisk" put "$BUILD/$which.img" "$BUILD/$which.state" /lander.state > /dev/null
done
for held in none up down right left dry; do
case "$held" in
none) byte='\x00'; disk=flamefull ;;
up) byte='\x08'; disk=flamefull ;;
down) byte='\x04'; disk=flamefull ;;
right) byte='\x01'; disk=flamefull ;;
left) byte='\x02'; disk=flamefull ;;
dry) byte='\x08'; disk=flamedry ;;
esac
python3 -c "open('$BUILD/flame$held.pad','wb').write(b'$byte' * 40000)"
timeout 60 "$EMU" --fast --cycles 1500000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/flame$held.pad" --screen "$BUILD/flame$held.ppm" \
--disk "$BUILD/$disk.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/flame$held.out" 2>&1 || true
done
read -r FNONE FUP FDOWN FRIGHT FLEFT FDRY <<EOT
$(python3 -c "
def flame(path):
# Red, and IN A BOX ROUND THE LANDER. The speed bars go red when they say a landing would
# not be survived, and one of the four landing pads is red as well - both of them turned
# up inside a filter that only looked at the column.
d = open(path, 'rb').read()
width = int(d[:40].split()[1])
px = d[d.index(b'255\n') + 4:]
def where(colour):
c = bytes.fromhex(colour)
return [(i % width, i // width) for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == c]
ship = where('d8c048')
if not ship:
return 'noship'
sx, sy = min(x for x, y in ship), min(y for x, y in ship)
lit = [(x, y) for x, y in where('d04038')
if sx - 12 <= x <= sx + 20 and sy - 12 <= y <= sy + 20]
if not lit:
return '0'
# Which side of the lander it came out on, which is the half that matters.
fx, fy = min(x for x, y in lit), min(y for x, y in lit)
side = 'below' if fy > sy else 'above'
if fx < sx: side = 'left'
if fx > sx + 4: side = 'right'
return '%d%s' % (len(lit), side)
print(flame('$BUILD/flamenone.ppm'), flame('$BUILD/flameup.ppm'),
flame('$BUILD/flamedown.ppm'), flame('$BUILD/flameright.ppm'),
flame('$BUILD/flameleft.ppm'), flame('$BUILD/flamedry.ppm'))
" 2>/dev/null || echo "x x x x x x")
EOT
[ "$FNONE" = "0" ] && [ "$FUP" = "24below" ] && [ "$FDOWN" = "24above" ] \
&& result ok "a lifting thruster shows a flame under the lander" "none held draws none, up draws 24 below, down 24 above" \
|| result no "a lifting thruster shows a flame under the lander" "none $FNONE, up $FUP, down $FDOWN"
# Sideways is the one that is easy to get backwards: pushing RIGHT means the plume comes out
# of the LEFT of the lander, because that is the side the gas leaves from.
[ "$FRIGHT" = "24left" ] && [ "$FLEFT" = "24right" ] \
&& result ok "and comes out the far side going sideways" "right draws left, left draws right" \
|| result no "and comes out the far side going sideways" "right $FRIGHT, left $FLEFT"
# An empty tank draws nothing, because then the button really is doing nothing.
[ "$FDRY" = "0" ] \
&& result ok "and an empty tank draws none at all" "the button is doing nothing, and shows it" \
|| result no "and an empty tank draws none at all" "$FDRY with a dry tank"
# ---- 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