A quarter-tank warning that says it once, and a gauge that keeps saying it

Two halves of the same number. The warning is the moment it happened and
the colour is how things stand: it fires on the way down through a
quarter of a tank and then holds its peace, and the gauge stays red until
a base fills the lander up, which also allows the warning again.

Once, because a lander is at its most careful in the last few seconds
before it touches, and something repeating in its ear through that is not
a warning, it is a distraction.

Checked where the fuel actually moves rather than once a frame - the tank
only changes in takeFuel and payFuel, so there is nowhere else it can
cross a threshold.

Channel two, with the crash on three. Separate channels rather than one
reused, because a crash while the warning is still sounding should not cut
it off, and on a machine with four voices there is no reason to be clever.

Half and a tenth are the obvious next thresholds and are deliberately not
here: one is enough to find out whether being told at all is welcome.

---- And the crash sound is the one that was designed for it ----

Crash.json, which carries voice_gate itself, so the program's own trigger
write is gone - it would have masked a deliberate choice rather than
backing one up. The note moved from 24 to 60 by ear, and the comment
saying it was low went with it.

---- A check that could not tell a beep from a nag ----

Counting bursts of sound cannot: without the latch the warning fires
every tick the tank drops, far faster than the sound decays, so the beeps
run into each other and a burst counter sees one long burst either way.
It measures the LENGTH now - 26,944 samples against 490,348 - which is
the difference between six tenths of a second and ten seconds of it.
This commit is contained in:
Anachronaut
2026-09-04 16:45:26 -04:00
parent de765267c5
commit 6fe898b5fc
16 changed files with 225 additions and 31 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ Mode.sbx 48
Flip.sbx 173
Sprite.sbx 442
Depth.sbx 672
Lander.sbx 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
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 8409
Lander.sbx 8608
Pad.sbx 264
Crash.sbx 632
vars.script 50
+64
View File
@@ -2714,6 +2714,70 @@ EOT
&& result ok "and it is loudest at the front" "which is what makes it a bang" \
|| result no "and it is loudest at the front" "louder in the second half than the first"
# ---- A quarter of a tank, said once ----
#
# ONCE is the whole design of it. A lander is at its most careful in the last few seconds
# before it touches, and something repeating in its ear through that is not a warning, it is a
# distraction. So it fires on the way DOWN through a quarter tank and then holds its peace,
# and filling up at a base allows it again.
#
# The gauge says the same thing the other way round: the sound is the moment it happened, the
# colour is how things stand. Both off the same number, so they cannot disagree.
python3 -c "
import struct
# Seventy of two hundred and fifty five, which is a few ticks of thruster above the quarter.
open('$BUILD/lowfuel.state','wb').write(struct.pack('<hhhhhhBBBB',
0x14A0, 0, 0, 0, 0x14A0, 2600, 70, 0, 0, 0))
"
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/lowfuel.img"
"$ROOT/SplitDisk" put "$BUILD/lowfuel.img" "$BUILD/lowfuel.state" /lander.state > /dev/null
python3 -c "open('$BUILD/burn.pad','wb').write(b'\x08' * 40000)"
for when in 1700000 2400000; do
timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/burn.pad" --screen "$BUILD/fuel$when.ppm" \
--disk "$BUILD/lowfuel.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/fuel.out" 2>&1 || true
done
timeout 60 "$EMU" --fast --cycles 12000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/burn.pad" --sound "$BUILD/warn.raw" \
--disk "$BUILD/lowfuel.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/warn.out" 2>&1 || true
read -r ABOVE BELOW WARNINGS <<EOT
$(python3 -c "
def gauge(path):
d = open(path, 'rb').read()
width = int(d[:40].split()[1])
px = d[d.index(b'255\n') + 4:]
def cells(colour):
c = bytes.fromhex(colour)
# The top row of the window, which is where the gauge is drawn.
return sum(1 for i in range(len(px) // 3)
if px[i * 3:i * 3 + 3] == c and i // width < 8) // 64
return '%d,%d' % (cells('50c050'), cells('d04038'))
import struct
raw = open('$BUILD/warn.raw', 'rb').read()
s = struct.unpack('<%dh' % (len(raw) // 2), raw)
# ---- HOW LONG it sounds for, not how many times it starts ----
#
# Counting bursts cannot tell one beep from a nag. Without the latch the warning fires on
# every tick the tank goes down, which is once every ten frames - far faster than the sound
# decays, so the beeps run into each other and a burst counter sees ONE long burst either way.
# The length is what differs: six tenths of a second against ten seconds of it.
loud = [i for i, x in enumerate(s) if abs(x) > 300]
span = (loud[-1] - loud[0]) if loud else 0
print(gauge('$BUILD/fuel1700000.ppm'), gauge('$BUILD/fuel2400000.ppm'), span)
" 2>/dev/null || echo "none none -1")
EOT
# Green cells then red cells, and none of the other colour either time.
[ "$ABOVE" = "8,0" ] && [ "$BELOW" = "0,7" ] \
&& result ok "the fuel gauge goes red at a quarter" "eight green became seven red" \
|| result no "the fuel gauge goes red at a quarter" "$ABOVE above, $BELOW below"
# Twelve seconds of holding the thruster, which empties the tank entirely and keeps it empty.
# The warning is under a second of that, or it is a nag rather than a warning.
[ "$WARNINGS" -gt 0 ] && [ "$WARNINGS" -lt 48000 ] \
&& result ok "and the warning sounds once, not once a tick" "$WARNINGS samples of it in twelve seconds of running dry" \
|| result no "and the warning sounds once, not once a tick" "$WARNINGS samples of warning"
# ---- Dust on landing, and gas on letting go ----
#
# The same six pieces as the explosion, thrown differently: dust goes sideways and UP off the