A thruster you can hear, and a latch on arriving and leaving

Three patches, and the last of the sounds that were asked for.

The thruster bangs when it lights and rumbles while it burns. The rumble
is this game's first HELD note: its gate goes down when a thruster lights
and does not come up until every one is out. Only the edges matter - a
rumble restruck every frame would never get past its own attack, and a
bang struck every frame is a buzz - and the condition is the flames':
a held button with a dry tank is a pilot doing nothing.

Arriving latches two notes quickly. Middle C then the C above for taking
hold of the station, the same pair reversed for letting go, and two
octaves lower for the ground - the same shape in a different register,
because setting down and taking hold are the same kind of event. The
second note is PENDING rather than played: at an undocking the pilot is
mid-burn, and stopping the world for an eighth of a second to fit a note
in would be felt as the controls sticking. Where the game is stopping
anyway, runPend simply pumps it out.

Four channels for five sounds. The bang and the latch share one, because
a lander arriving either arrives or does not, and a crash ends the run.
That leaves the thruster its two, which it needs: a held note struck on
the same channel as the ignition bang would cut the bang off at the
moment it was meant to be heard.

---- A held note outlives the loop that was holding it ----

Landing while the thruster was still down ended the flying and then
waited to be told the message had been read, so the frame that would have
noticed the button coming up never ran. The engine roared under the
verdict and went on roaring until the machine stopped. Anything that
stops to wait hushes it now, and forgets last frame with it, so a thruster
still held when the waiting ends counts as lighting again.

---- Three checks that had to be rebuilt around the new noise ----

A landing is not silent any more, so the crash is measured against the
second BEFORE it rather than against a quiet landing. The dust samples
moved eight frames later, because the latch plays first. And the warning
check lost its measure twice: counting bursts could not tell a beep from
a nag, and measuring total length stopped working the day the thruster
got a rumble. It burns, stops, and listens AFTER - warned once there is
nothing left, nagging the last beep is still fading. Nought against 5,679.
This commit is contained in:
Anachronaut
2026-09-04 17:34:16 -04:00
parent 6fe898b5fc
commit 388faafd04
19 changed files with 645 additions and 51 deletions
+188 -14
View File
@@ -80,7 +80,7 @@ start:
CALL putLander CALL putLander
CALL putGauge CALL putGauge
CALL putOrbital CALL putOrbital
CALL putBoom ; The instruments, once, the way the tiles are. CALL putThrust ; The instruments, once, the way the tiles are.
CALL putAlarm CALL putAlarm
INIA 0x01 INIA 0x01
@@ -101,6 +101,7 @@ everyFrame:
CALL readPad CALL readPad
CALL readControls CALL readControls
CALL zoomButton CALL zoomButton
CALL thrustSound ; Which of the thrusters just lit, or just went out.
CALL fall CALL fall
CALL move CALL move
CALL moveStation CALL moveStation
@@ -115,6 +116,7 @@ everyFrame:
CALL showFuel CALL showFuel
CALL showHeight CALL showHeight
CALL burstTick ; Whatever is in the air, one frame on. CALL burstTick ; Whatever is in the air, one frame on.
CALL pendTick ; And whatever note is waiting to follow another.
SETD.0 Flying SETD.0 Flying
LDA.0 LDA.0
BNA everyFrame BNA everyFrame
@@ -1819,6 +1821,14 @@ touchdown:
SUB SUB
BNC touchdownCrash BNC touchdownCrash
INIA 0d36
SETD.0 LatchFirst
STA.0
INIA 0d48
SETD.0 LatchSecond
STA.0
CALL latch ; The same shape two octaves lower, which is the ground.
CALL runPend
CALL landingDust ; Something to see for an arrival, as a crash has always had. CALL landingDust ; Something to see for an arrival, as a crash has always had.
; ---- Down safely, which is not the end of anything ---- ; ---- Down safely, which is not the end of anything ----
@@ -2001,6 +2011,14 @@ dock:
INIA 0x01 INIA 0x01
SETD.0 Docked SETD.0 Docked
STA.0 STA.0
INIA 0d60
SETD.0 LatchFirst
STA.0
INIA 0d72
SETD.0 LatchSecond
STA.0
CALL latch ; Middle C then the C above it: taking hold.
CALL runPend
CALL payFuel CALL payFuel
SETD.0 DockedText SETD.0 DockedText
CALL sayInWindow CALL sayInWindow
@@ -2081,7 +2099,14 @@ dockLetGo:
STA.0 STA.0
SETD.0 DockClear SETD.0 DockClear
STA.0 ; A is still nought: not clear until it has actually got away. STA.0 ; A is still nought: not clear until it has actually got away.
CALL undockGas ; Started and left running: the pilot is mid-burn. INIA 0d72
SETD.0 LatchFirst
STA.0
INIA 0d60
SETD.0 LatchSecond
STA.0
CALL latch ; The C above middle then middle: letting go.
CALL undockGas ; Both started and left running: the pilot is mid-burn.
RET RET
; ---- Half a pixel of the way there, or the whole of it if that is nearer ---- ; ---- Half a pixel of the way there, or the whole of it if that is nearer ----
@@ -2699,11 +2724,151 @@ hideSprite:
; Effects take the top channel and work downwards, so music, if it ever arrives, can take ; Effects take the top channel and work downwards, so music, if it ever arrives, can take
; nought and count up and the two never have to negotiate. There are four, and this is the ; nought and count up and the two never have to negotiate. There are four, and this is the
; first program to want any of them while also doing something else. ; first program to want any of them while also doing something else.
putBoom: ; ---- Channel three has two patches, and that is not a shortage ----
;
; The bang and the latch are the two OUTCOMES OF THE SAME EVENT: a lander arriving somewhere
; either arrives or does not, and a crash ends the run outright. They can never sound at once,
; so they share, and whichever is wanted loads itself at the moment. Fifty writes is nothing
; against a thing that happens once.
;
; That leaves nought and one for the thruster, which needs two of its own: a bang at ignition
; is a one-shot and the rumble under it is held, and a held note struck on the same channel
; would cut the bang off at the very moment it was meant to be heard.
boom:
INIA 0x03 INIA 0x03
OUTA 0x41 ; Channel three. Every parameter write below lands on it. OUTA 0x41
SETD.0 BoomPatch SETD.0 BoomPatch
CALL playPatch CALL playPatch
INIA 0d60 ; Middle C. The noise is not really pitched, but the filter is
; tuned off the note and this is where it was found by ear.
OUTA 0x44 ; Writing the note is what starts it.
RET
; ---- Two notes, quickly, which is what a latch sounds like ----
;
; Up for arriving and down for leaving, and two octaves lower for the ground - the same shape
; said in a different register, because a lander setting down and a lander taking hold of a
; station are the same kind of event.
;
; The second note is PENDING rather than played: at an undocking the pilot is mid-burn and
; stopping the world for an eighth of a second to fit a note in would be felt as the controls
; sticking. Where the game is stopping anyway - a landing, a docking - runPend below simply
; pumps it until it has come out.
latch:
INIA 0x03
OUTA 0x41
SETD.0 LatchPatch
CALL playPatch
SETD.0 LatchFirst
LDA.0
OUTA 0x44
SETD.0 LatchSecond
LDA.0
SETD.1 PendNote
STA.1
INIA 0d8
SETD.1 PendIn
STA.1 ; An eighth of a second, which is quick succession and not a chord.
RET
; Whatever note is waiting, one frame nearer. Nothing at all when none is.
pendTick:
SETD.0 PendIn
LDA.0
BRA pendTickDone
DECA
STA.0
BNA pendTickDone
INIA 0x03
OUTA 0x41
SETD.0 PendNote
LDA.0
OUTA 0x44
pendTickDone:
RET
; For the two that can afford to wait, the same shape runBurst has.
runPend:
CALL waitFrame
CALL pendTick
SETD.0 PendIn
LDA.0
BNA runPend
RET
; ---- The thruster, which is a bang and then a rumble ----
;
; Both on their own channels because one is struck and the other is HELD: the rumble's note
; goes on when a thruster lights and its gate does not come up until every thruster is out.
putThrust:
RSTA
OUTA 0x41
SETD.0 ThrustOnPatch
CALL playPatch
INIA 0x01
OUTA 0x41
SETD.0 ThrustHoldPatch
CALL playPatch
RET
; ---- Lit or not, and only the CHANGES matter ----
;
; The same condition the flames are drawn on, for the same reason: a held button with a dry
; tank is a pilot doing nothing. What is watched is the edge - a rumble restruck every frame
; would never get past its own attack, and a bang struck every frame is a buzz.
thrustSound:
SETD.0 Fuel
LDA.0
BRA thrustOut
SETD.0 Held
LDA.0
INIB 0x0F
AND
BRQ thrustOut
INIA 0x01
BRI thrustNow
thrustOut:
RSTA
thrustNow:
SETD.1 ThrustWas
LDB.1
STA.1 ; Now becomes what "last frame" means next time round.
CCF
SUB
BRQ thrustDone ; The same as last frame, so nothing has lit or gone out.
LDA.1
BRA thrustStopped
RSTA
OUTA 0x41
INIA 0d48
OUTA 0x44 ; The bang of it catching.
INIA 0x01
OUTA 0x41
INIA 0d36
OUTA 0x44 ; And the rumble under it, which is held.
RET
thrustStopped:
CALL hushThrust
thrustDone:
RET
; ---- The rumble stopped, wherever the stopping is happening ----
;
; A HELD note outlives the loop that was holding it. Landing while the thruster is still down
; ends the flying and then waits to be told the message has been read - and the frame that
; would have noticed the button coming up never runs, so the engine roared under the verdict
; and went on roaring. Anything that stops to wait calls this.
;
; Forgetting last frame with it, so that a thruster still held when the waiting ends counts as
; lighting again rather than as never having stopped.
hushThrust:
INIA 0x01
OUTA 0x41
RSTA
OUTA 0x45 ; The gate up is the only thing that ends a held note.
SETD.0 ThrustWas
STA.0
RET RET
; ---- A whole patch, out of a table ---- ; ---- A whole patch, out of a table ----
@@ -2729,15 +2894,6 @@ playPatchOne:
BNA playPatchOne BNA playPatchOne
RET RET
; Two writes, which is what a note is meant to cost.
boom:
INIA 0x03
OUTA 0x41
INIA 0d60 ; Middle C. The noise is not really pitched, but the filter is
; tuned off the note and this is where it was found by ear.
OUTA 0x44 ; Writing the note is what starts it.
RET
; ---- A quarter of a tank, said once ---- ; ---- A quarter of a tank, said once ----
; ;
; ONCE, and that is the whole design of it. A lander is at its most careful in the last few ; ONCE, and that is the whole design of it. A lander is at its most careful in the last few
@@ -2789,6 +2945,8 @@ alarm:
; read that". A or Start does it, and so does any key - asked for rather than waited on, so ; read that". A or Start does it, and so does any key - asked for rather than waited on, so
; one already pressed counts. ; one already pressed counts.
waitKey: waitKey:
CALL hushThrust ; Nothing waits with an engine still running.
waitKeyPoll:
CALL readPad CALL readPad
SETD.0 Held SETD.0 Held
LDA.0 LDA.0
@@ -2798,7 +2956,7 @@ waitKey:
INA 0x01 INA 0x01
INIB 0x01 ; READY INIB 0x01 ; READY
AND AND
BRQ waitKey BRQ waitKeyPoll
INA 0x00 INA 0x00
waitKeyDone: waitKeyDone:
RET RET
@@ -3674,6 +3832,18 @@ Fuel:
0xFF 0xFF
GaugeColour: GaugeColour:
0x00 0x00
; The two notes of a latch, and how long until the second of them.
LatchFirst:
0x00
LatchSecond:
0x00
PendNote:
0x00
PendIn:
0x00
; Whether a thruster was lit last frame, since only the changes are worth a sound.
ThrustWas:
0x00
; Whether the quarter tank warning has been given since the last time it was filled. ; Whether the quarter tank warning has been given since the last time it was filled.
AlarmSaid: AlarmSaid:
0x00 0x00
@@ -4159,3 +4329,7 @@ LanderArtAt:
#Include boom.asm #Include boom.asm
#Include alarm.asm #Include alarm.asm
#Include thrustOn.asm
#Include thrustHold.asm
#Include latch.asm
File diff suppressed because one or more lines are too long
+54
View File
@@ -0,0 +1,54 @@
{
"osc0_waveform": 5,
"osc0_dutyCycle": 0.500000,
"osc0_detune": 0.000000,
"osc0_gain": 4.000000,
"osc0_active": 1,
"osc0_octave": 2,
"osc0_modRouting0": 0,
"osc0_modRouting1": 2,
"osc0_modRouting2": 0,
"osc0_modDepth0": 0.000000,
"osc0_modDepth1": 180.000000,
"osc0_modDepth2": 4.000000,
"osc1_waveform": 5,
"osc1_dutyCycle": 0.545000,
"osc1_detune": -15.000000,
"osc1_gain": 4.000000,
"osc1_active": 1,
"osc1_octave": 1,
"osc1_modRouting0": 0,
"osc1_modRouting1": 2,
"osc1_modRouting2": 0,
"osc1_modDepth0": -0.750000,
"osc1_modDepth1": 1104.000000,
"osc1_modDepth2": 4.000000,
"ampEnv_attack": 0.001000,
"ampEnv_decay": 0.100000,
"ampEnv_sustain": 0.000000,
"ampEnv_release": 0.001000,
"modEnv_attack": 0.001000,
"modEnv_decay": 0.288601,
"modEnv_sustain": 0.000000,
"modEnv_release": 2.000000,
"lfo0_rate": 8.796101,
"lfo0_waveform": 0,
"lfo0_active": 0,
"lfo0_mode": 0,
"lfo1_rate": 1.000000,
"lfo1_waveform": 0,
"lfo1_active": 0,
"lfo1_mode": 0,
"filter_cutoff": 389.089539,
"filter_resonance": 0.164624,
"filter_type": 0,
"filter_active": 1,
"filter_modRouting": 0,
"filter_modDepth": 11600.000000,
"filter_resModRouting": 0,
"filter_resModDepth": 0.000000,
"voice_levelSource": 2,
"voice_gate": 1,
"master_volume": 1.000000,
"master_pitchBendRange": 2.000000
}
+54
View File
@@ -0,0 +1,54 @@
{
"osc0_waveform": 5,
"osc0_dutyCycle": 0.500000,
"osc0_detune": 0.000000,
"osc0_gain": 4.000000,
"osc0_active": 1,
"osc0_octave": 0,
"osc0_modRouting0": 0,
"osc0_modRouting1": 0,
"osc0_modRouting2": 0,
"osc0_modDepth0": 0.000000,
"osc0_modDepth1": 0.000000,
"osc0_modDepth2": 0.000000,
"osc1_waveform": 0,
"osc1_dutyCycle": 0.500000,
"osc1_detune": -29.360291,
"osc1_gain": 2.868294,
"osc1_active": 1,
"osc1_octave": -1,
"osc1_modRouting0": 0,
"osc1_modRouting1": 3,
"osc1_modRouting2": 0,
"osc1_modDepth0": 0.000000,
"osc1_modDepth1": 307.482422,
"osc1_modDepth2": 0.000000,
"ampEnv_attack": 0.076245,
"ampEnv_decay": 0.182086,
"ampEnv_sustain": 1.000000,
"ampEnv_release": 0.001000,
"modEnv_attack": 0.001000,
"modEnv_decay": 0.213054,
"modEnv_sustain": 0.000000,
"modEnv_release": 0.001000,
"lfo0_rate": 19.253021,
"lfo0_waveform": 0,
"lfo0_active": 1,
"lfo0_mode": 1,
"lfo1_rate": 1.000000,
"lfo1_waveform": 0,
"lfo1_active": 0,
"lfo1_mode": 0,
"filter_cutoff": 2081.465332,
"filter_resonance": 0.000000,
"filter_type": 0,
"filter_active": 1,
"filter_modRouting": 2,
"filter_modDepth": -12846.765625,
"filter_resModRouting": 0,
"filter_resModDepth": 0.000000,
"voice_levelSource": 1,
"voice_gate": 0,
"master_volume": 1.000000,
"master_pitchBendRange": 2.000000
}
+54
View File
@@ -0,0 +1,54 @@
{
"osc0_waveform": 5,
"osc0_dutyCycle": 0.500000,
"osc0_detune": 0.000000,
"osc0_gain": 4.000000,
"osc0_active": 1,
"osc0_octave": 2,
"osc0_modRouting0": 0,
"osc0_modRouting1": 2,
"osc0_modRouting2": 0,
"osc0_modDepth0": 0.000000,
"osc0_modDepth1": 180.000000,
"osc0_modDepth2": 4.000000,
"osc1_waveform": 5,
"osc1_dutyCycle": 0.545000,
"osc1_detune": -15.000000,
"osc1_gain": 4.000000,
"osc1_active": 1,
"osc1_octave": 1,
"osc1_modRouting0": 0,
"osc1_modRouting1": 2,
"osc1_modRouting2": 0,
"osc1_modDepth0": -0.750000,
"osc1_modDepth1": 1104.000000,
"osc1_modDepth2": 4.000000,
"ampEnv_attack": 2.000000,
"ampEnv_decay": 0.100000,
"ampEnv_sustain": 0.000000,
"ampEnv_release": 0.001000,
"modEnv_attack": 0.001000,
"modEnv_decay": 0.247558,
"modEnv_sustain": 0.000000,
"modEnv_release": 2.000000,
"lfo0_rate": 8.796101,
"lfo0_waveform": 0,
"lfo0_active": 0,
"lfo0_mode": 0,
"lfo1_rate": 1.000000,
"lfo1_waveform": 0,
"lfo1_active": 0,
"lfo1_mode": 0,
"filter_cutoff": 1242.353027,
"filter_resonance": 0.164624,
"filter_type": 0,
"filter_active": 1,
"filter_modRouting": 0,
"filter_modDepth": 11600.000000,
"filter_resModRouting": 0,
"filter_resModDepth": 0.000000,
"voice_levelSource": 2,
"voice_gate": 1,
"master_volume": 1.000000,
"master_pitchBendRange": 2.000000
}
+58
View File
@@ -0,0 +1,58 @@
; LatchPatch, converted from Programs/Sounds/DockLatch.json by SoundPatch.
; Designed in soundThing, where it can be heard. Do not edit the numbers
; here: change the patch and convert it again.
#Data
LatchPatch:
0d50 ; how many pairs follow
0x00 0d5 ; oscillator 0, waveform
0x01 0d255 ; gain
0x02 0d128 ; duty
0x03 0d128 ; detune, in cents
0x04 0d130 ; octave
0x05 0d1 ; on
0x06 0d0 ; what moves its width
0x07 0d128 ; and how far
0x08 0d2 ; what moves its pitch
0x09 0d147 ; and how far
0x0A 0d0 ; what moves its gain
0x0B 0d255 ; and how far
0x10 0d5 ; oscillator 1, waveform
0x11 0d255 ; gain
0x12 0d140 ; duty
0x13 0d126 ; detune, in cents
0x14 0d129 ; octave
0x15 0d1 ; on
0x16 0d0 ; what moves its width
0x17 0d0 ; and how far
0x18 0d2 ; what moves its pitch
0x19 0d246 ; and how far
0x1A 0d0 ; what moves its gain
0x1B 0d255 ; and how far
0x20 0d4 ; amplitude envelope, attack
0x21 0d40 ; decay
0x22 0d0 ; sustain
0x23 0d4 ; release
0x30 0d4 ; modulation envelope, attack
0x31 0d68 ; decay
0x32 0d0 ; sustain
0x33 0d180 ; release
0x40 0d1 ; filter, on
0x41 0d0 ; type: 0 low, 1 high, 2 band
0x42 0d110 ; cutoff, in hertz
0x43 0d42 ; resonance
0x44 0d0 ; what moves the cutoff
0x45 0d255 ; and how far, in hertz
0x46 0d0 ; what moves the resonance
0x47 0d128 ; and how far
0x60 0d0 ; LFO 0, on
0x61 0d0 ; waveform
0x62 0d220 ; rate, in hertz
0x63 0d0 ; 0 free, 1 starts with a voice
0x70 0d0 ; LFO 1, on
0x71 0d0 ; waveform
0x72 0d127 ; rate, in hertz
0x73 0d0 ; 0 free, 1 starts with a voice
0x50 0d2 ; what shapes the level
0x51 0d1 ; 0 gated, 1 triggered
+58
View File
@@ -0,0 +1,58 @@
; ThrustHoldPatch, converted from Programs/Sounds/ThrustHold.json by SoundPatch.
; Designed in soundThing, where it can be heard. Do not edit the numbers
; here: change the patch and convert it again.
#Data
ThrustHoldPatch:
0d50 ; how many pairs follow
0x00 0d5 ; oscillator 0, waveform
0x01 0d255 ; gain
0x02 0d128 ; duty
0x03 0d128 ; detune, in cents
0x04 0d128 ; octave
0x05 0d1 ; on
0x06 0d0 ; what moves its width
0x07 0d128 ; and how far
0x08 0d0 ; what moves its pitch
0x09 0d128 ; and how far
0x0A 0d0 ; what moves its gain
0x0B 0d128 ; and how far
0x10 0d0 ; oscillator 1, waveform
0x11 0d183 ; gain
0x12 0d128 ; duty
0x13 0d125 ; detune, in cents
0x14 0d127 ; octave
0x15 0d1 ; on
0x16 0d0 ; what moves its width
0x17 0d128 ; and how far
0x18 0d3 ; what moves its pitch
0x19 0d161 ; and how far
0x1A 0d0 ; what moves its gain
0x1B 0d128 ; and how far
0x20 0d35 ; amplitude envelope, attack
0x21 0d54 ; decay
0x22 0d255 ; sustain
0x23 0d4 ; release
0x30 0d4 ; modulation envelope, attack
0x31 0d59 ; decay
0x32 0d0 ; sustain
0x33 0d4 ; release
0x40 0d1 ; filter, on
0x41 0d0 ; type: 0 low, 1 high, 2 band
0x42 0d171 ; cutoff, in hertz
0x43 0d0 ; resonance
0x44 0d2 ; what moves the cutoff
0x45 0d0 ; and how far, in hertz
0x46 0d0 ; what moves the resonance
0x47 0d128 ; and how far
0x60 0d1 ; LFO 0, on
0x61 0d0 ; waveform
0x62 0d253 ; rate, in hertz
0x63 0d1 ; 0 free, 1 starts with a voice
0x70 0d0 ; LFO 1, on
0x71 0d0 ; waveform
0x72 0d127 ; rate, in hertz
0x73 0d0 ; 0 free, 1 starts with a voice
0x50 0d1 ; what shapes the level
0x51 0d0 ; 0 gated, 1 triggered
+58
View File
@@ -0,0 +1,58 @@
; ThrustOnPatch, converted from Programs/Sounds/ThrustOn.json by SoundPatch.
; Designed in soundThing, where it can be heard. Do not edit the numbers
; here: change the patch and convert it again.
#Data
ThrustOnPatch:
0d50 ; how many pairs follow
0x00 0d5 ; oscillator 0, waveform
0x01 0d255 ; gain
0x02 0d128 ; duty
0x03 0d128 ; detune, in cents
0x04 0d130 ; octave
0x05 0d1 ; on
0x06 0d0 ; what moves its width
0x07 0d128 ; and how far
0x08 0d2 ; what moves its pitch
0x09 0d147 ; and how far
0x0A 0d0 ; what moves its gain
0x0B 0d255 ; and how far
0x10 0d5 ; oscillator 1, waveform
0x11 0d255 ; gain
0x12 0d140 ; duty
0x13 0d126 ; detune, in cents
0x14 0d129 ; octave
0x15 0d1 ; on
0x16 0d0 ; what moves its width
0x17 0d0 ; and how far
0x18 0d2 ; what moves its pitch
0x19 0d246 ; and how far
0x1A 0d0 ; what moves its gain
0x1B 0d255 ; and how far
0x20 0d180 ; amplitude envelope, attack
0x21 0d40 ; decay
0x22 0d0 ; sustain
0x23 0d4 ; release
0x30 0d4 ; modulation envelope, attack
0x31 0d63 ; decay
0x32 0d0 ; sustain
0x33 0d180 ; release
0x40 0d1 ; filter, on
0x41 0d0 ; type: 0 low, 1 high, 2 band
0x42 0d152 ; cutoff, in hertz
0x43 0d42 ; resonance
0x44 0d0 ; what moves the cutoff
0x45 0d255 ; and how far, in hertz
0x46 0d0 ; what moves the resonance
0x47 0d128 ; and how far
0x60 0d0 ; LFO 0, on
0x61 0d0 ; waveform
0x62 0d220 ; rate, in hertz
0x63 0d0 ; 0 free, 1 starts with a voice
0x70 0d0 ; LFO 1, on
0x71 0d0 ; waveform
0x72 0d127 ; rate, in hertz
0x73 0d0 ; 0 free, 1 starts with a voice
0x50 0d2 ; what shapes the level
0x51 0d1 ; 0 gated, 1 triggered
+1 -1
View File
@@ -25,7 +25,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -32,7 +32,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -108,7 +108,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -27,7 +27,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -13,7 +13,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 8608 Lander.sbx 9168
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+110 -26
View File
@@ -2672,16 +2672,13 @@ EOT
# says "this channel, this note". Channel THREE, effects counting down from the top, so music # says "this channel, this note". Channel THREE, effects counting down from the top, so music
# can one day take nought and count up without the two having to negotiate. # can one day take nought and count up without the two having to negotiate.
# #
# Checked against silence, which is the useful half: a landing in the same conditions makes no # Checked against silence BEFORE it rather than against a quiet landing, because a landing is
# sound at all, so this is measuring the crash and not the machine humming. # not quiet any more - it latches two notes of its own. The silence that still means something
# is the second before the crash: the machine does not hum, so anything there is the event.
timeout 60 "$EMU" --fast --cycles 3000000 --keyboard "$BUILD/fly.keys" \ timeout 60 "$EMU" --fast --cycles 3000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/none.pad" --sound "$BUILD/bang.raw" \ --pad "$BUILD/none.pad" --sound "$BUILD/bang.raw" \
--disk "$BUILD/crash.img" --ram-disk 2048 \ --disk "$BUILD/crash.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/bang.out" 2>&1 || true "$BUILD/cosmos.bin" > "$BUILD/bang.out" 2>&1 || true
timeout 60 "$EMU" --fast --cycles 3000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/none.pad" --sound "$BUILD/hush.raw" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/hush.out" 2>&1 || true
read -r BANGPEAK BANGLOUD BANGFADE HUSHPEAK <<EOT read -r BANGPEAK BANGLOUD BANGFADE HUSHPEAK <<EOT
$(python3 -c " $(python3 -c "
import struct import struct
@@ -2698,12 +2695,17 @@ def look(path):
back = max((abs(v) for v in s[half:]), default=0) back = max((abs(v) for v in s[half:]), default=0)
return peak, loud, (1 if front > back else 0) return peak, loud, (1 if front > back else 0)
b = look('$BUILD/bang.raw') b = look('$BUILD/bang.raw')
print(b[0], b[1], b[2], look('$BUILD/hush.raw')[0]) # The second before it, which is a lander falling and making no noise at all.
import struct
raw = open('$BUILD/bang.raw', 'rb').read()
s = struct.unpack('<%dh' % (len(raw) // 2), raw)
before = max((abs(v) for v in s[:48000]), default=0)
print(b[0], b[1], b[2], before)
" 2>/dev/null || echo "0 0 0 -1") " 2>/dev/null || echo "0 0 0 -1")
EOT EOT
[ "$BANGPEAK" -gt 2000 ] && [ "$BANGLOUD" -gt 5000 ] && [ "$HUSHPEAK" = "0" ] \ [ "$BANGPEAK" -gt 2000 ] && [ "$BANGLOUD" -gt 5000 ] && [ "$HUSHPEAK" = "0" ] \
&& result ok "a crash makes a noise, and a landing does not" "peak $BANGPEAK against silence" \ && result ok "a crash makes a noise out of silence" "peak $BANGPEAK, and nothing at all before it" \
|| result no "a crash makes a noise, and a landing does not" "crash peak $BANGPEAK, landing peak $HUSHPEAK" || result no "a crash makes a noise out of silence" "crash peak $BANGPEAK, $HUSHPEAK beforehand"
# ---- And it fades ---- # ---- And it fades ----
# #
# A byte of envelope is not seconds: it is squared and scaled to four of them, so the first # A byte of envelope is not seconds: it is squared and scaled to four of them, so the first
@@ -2714,6 +2716,82 @@ EOT
&& result ok "and it is loudest at the front" "which is what makes it a bang" \ && 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" || result no "and it is loudest at the front" "louder in the second half than the first"
# ---- The thruster: a bang at ignition and a rumble under it ----
#
# The rumble is the first HELD note this game plays, and a held note outlives whatever was
# holding it. Its gate goes down when a thruster lights and does not come up until every one
# of them is out - so what is checked is that it goes QUIET again, which is the half that can
# fail silently in the other direction.
#
# Placed high with a full tank so the burn can be let go of with plenty of air left: three
# clear seconds between the thruster going out and the ground arriving.
python3 -c "
import struct
open('$BUILD/high.state','wb').write(struct.pack('<hhhhhhBBBB',
0x14A0, -2000, 0, 0, 0x14A0, 2600, 255, 0, 0, 0))
"
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/high.img"
"$ROOT/SplitDisk" put "$BUILD/high.img" "$BUILD/high.state" /lander.state > /dev/null
python3 -c "open('$BUILD/hold.pad','wb').write(b'\x00' * 130 + b'\x08' * 60 + b'\x00' * 40000)"
timeout 60 "$EMU" --fast --cycles 12000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/hold.pad" --sound "$BUILD/hold.raw" \
--disk "$BUILD/high.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/hold.out" 2>&1 || true
# ---- And landing with the thruster STILL DOWN ----
#
# The case the release above cannot reach. Braking into the ground ends the flying and then
# waits to be told the message has been read, so the frame that would have noticed the button
# coming up never runs - and a held note outlives the loop that was holding it. It roared under
# the verdict and went on roaring until the machine stopped.
python3 -c "open('$BUILD/brakeland.pad','wb').write(b'\x00' * 150 + b'\x08' * 90 + b'\x00' * 8000)"
timeout 60 "$EMU" --fast --cycles 8000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/brakeland.pad" --sound "$BUILD/braked.raw" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/braked.out" 2>&1 || true
# And a docking, where the latch sounds on its own with nothing else going on.
timeout 60 "$EMU" --fast --cycles 8000000 --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/none.pad" --sound "$BUILD/latch.raw" \
--disk "$BUILD/dock.img" --ram-disk 2048 \
"$BUILD/cosmos.bin" > "$BUILD/latch.out" 2>&1 || true
read -r BURNING AFTERWARDS LATCHLONG UNDERVERDICT <<EOT
$(python3 -c "
import struct
def samples(path):
raw = open(path, 'rb').read()
return struct.unpack('<%dh' % (len(raw) // 2), raw)
def peak(s, a, b):
w = s[a:b]
return max((abs(v) for v in w), default=0)
h = samples('$BUILD/hold.raw')
b = samples('$BUILD/braked.raw')
l = samples('$BUILD/latch.raw')
loud = [i for i, x in enumerate(l) if abs(x) > 300]
span = (loud[-1] - loud[0]) if loud else 0
# During the burn, and a second and a half after it, which is still long before the ground.
print(peak(h, 115200, 124800), peak(h, 192000, 288000), span, peak(b, 240000, 336000))
" 2>/dev/null || echo "0 -1 0 -1")
EOT
[ "$BURNING" -gt 1000 ] \
&& result ok "a held thruster rumbles" "$BURNING while it burns" \
|| result no "a held thruster rumbles" "$BURNING while it burns"
# ---- And stops ----
#
# A gated note ends when its gate comes up and at no other time. Landing while the thruster was
# still down used to leave it roaring under the verdict for ever, because the frame that would
# have noticed the button coming up never ran - so anything that stops to wait now hushes it.
[ "$AFTERWARDS" = "0" ] \
&& result ok "and goes quiet when it stops" "silence a second and a half later" \
|| result no "and goes quiet when it stops" "still $AFTERWARDS long after the thruster went out"
# Two notes an eighth of a second apart, so the whole thing outlasts either of them. One note
# is about a fifth of a second; this is four tenths.
[ "$LATCHLONG" -gt 14400 ] \
&& result ok "and a dock latches on two notes" "$LATCHLONG samples, which is longer than one" \
|| result no "and a dock latches on two notes" "$LATCHLONG samples, which is about one note"
# Two seconds of the verdict being up, with nothing under it.
[ "$UNDERVERDICT" = "0" ] \
&& result ok "and nothing roars under the verdict" "landed on the thruster, and it stopped anyway" \
|| result no "and nothing roars under the verdict" "$UNDERVERDICT still sounding after landing"
# ---- A quarter of a tank, said once ---- # ---- 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 # ONCE is the whole design of it. A lander is at its most careful in the last few seconds
@@ -2731,7 +2809,9 @@ open('$BUILD/lowfuel.state','wb').write(struct.pack('<hhhhhhBBBB',
" "
cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/lowfuel.img" cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/lowfuel.img"
"$ROOT/SplitDisk" put "$BUILD/lowfuel.img" "$BUILD/lowfuel.state" /lander.state > /dev/null "$ROOT/SplitDisk" put "$BUILD/lowfuel.img" "$BUILD/lowfuel.state" /lander.state > /dev/null
python3 -c "open('$BUILD/burn.pad','wb').write(b'\x08' * 40000)" # Three hundred frames of thruster and then nothing. Long enough to drain past a quarter
# several times over, and it STOPS - which is what makes the measurement below possible.
python3 -c "open('$BUILD/burn.pad','wb').write(b'\x08' * 300 + b'\x00' * 40000)"
for when in 1700000 2400000; do for when in 1700000 2400000; do
timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/burn.pad" --screen "$BUILD/fuel$when.ppm" \ --pad "$BUILD/burn.pad" --screen "$BUILD/fuel$when.ppm" \
@@ -2757,26 +2837,28 @@ def gauge(path):
import struct import struct
raw = open('$BUILD/warn.raw', 'rb').read() raw = open('$BUILD/warn.raw', 'rb').read()
s = struct.unpack('<%dh' % (len(raw) // 2), raw) s = struct.unpack('<%dh' % (len(raw) // 2), raw)
# ---- HOW LONG it sounds for, not how many times it starts ---- # ---- WHAT IS STILL SOUNDING once the thruster stops ----
# #
# Counting bursts cannot tell one beep from a nag. Without the latch the warning fires on # Two measurements failed before this one. Counting bursts cannot tell a beep from a nag: the
# every tick the tank goes down, which is once every ten frames - far faster than the sound # warning would fire every tick the tank drops, far faster than the sound decays, so the beeps
# decays, so the beeps run into each other and a burst counter sees ONE long burst either way. # run together and a burst counter sees one long burst either way. Measuring the total length
# The length is what differs: six tenths of a second against ten seconds of it. # stopped working the day the thruster got a rumble of its own, which sounds for the whole burn
loud = [i for i, x in enumerate(s) if abs(x) > 300] # whatever the warning does.
span = (loud[-1] - loud[0]) if loud else 0 #
print(gauge('$BUILD/fuel1700000.ppm'), gauge('$BUILD/fuel2400000.ppm'), span) # So: burn, stop, and listen AFTER. Warned once, the beep went long ago and the rumble releases
# at once, so there is nothing. Nagging, the last beep was on the burn's final tick and is
# still fading. Measured, that is nought against five thousand six hundred.
after = max((abs(x) for x in s[250000:275000]), default=0)
print(gauge('$BUILD/fuel1700000.ppm'), gauge('$BUILD/fuel2400000.ppm'), after)
" 2>/dev/null || echo "none none -1") " 2>/dev/null || echo "none none -1")
EOT EOT
# Green cells then red cells, and none of the other colour either time. # Green cells then red cells, and none of the other colour either time.
[ "$ABOVE" = "8,0" ] && [ "$BELOW" = "0,7" ] \ [ "$ABOVE" = "8,0" ] && [ "$BELOW" = "0,7" ] \
&& result ok "the fuel gauge goes red at a quarter" "eight green became seven red" \ && 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" || 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. [ "$WARNINGS" -lt 500 ] \
# The warning is under a second of that, or it is a nag rather than a warning. && result ok "and the warning sounds once, not once a tick" "nothing left over when the burn ends" \
[ "$WARNINGS" -gt 0 ] && [ "$WARNINGS" -lt 48000 ] \ || result no "and the warning sounds once, not once a tick" "$WARNINGS still sounding after the burn"
&& 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 ---- # ---- Dust on landing, and gas on letting go ----
# #
@@ -2808,7 +2890,9 @@ for when in 9900000 10150000 10800000; do
"$BUILD/cosmos.bin" > "$BUILD/gas.out" 2>&1 || true "$BUILD/cosmos.bin" > "$BUILD/gas.out" 2>&1 || true
done done
# And a plain fall onto the pad it starts over, which lands rather than crashes. # And a plain fall onto the pad it starts over, which lands rather than crashes.
for when in 2700000 2820000 3100000; do # Eight frames later than they used to be: the landing latch plays its two notes before the
# dust goes up, and runPend waits for the second of them.
for when in 2900000 3050000 3300000; do
timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \
--pad "$BUILD/none.pad" --screen "$BUILD/dust$when.ppm" \ --pad "$BUILD/none.pad" --screen "$BUILD/dust$when.ppm" \
--disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \
@@ -2828,8 +2912,8 @@ def look(path):
puff = len([1 for x, y in where('f0f0f0') if y > 20]) puff = len([1 for x, y in where('f0f0f0') if y > 20])
ship = where('d8c048') ship = where('d8c048')
return puff, (min(y for x, y in ship) if ship else -1) return puff, (min(y for x, y in ship) if ship else -1)
print(look('$BUILD/dust2700000.ppm')[0], look('$BUILD/dust2820000.ppm')[0], print(look('$BUILD/dust2900000.ppm')[0], look('$BUILD/dust3050000.ppm')[0],
look('$BUILD/dust3100000.ppm')[0], look('$BUILD/dust3300000.ppm')[0],
look('$BUILD/gas9900000.ppm')[0], look('$BUILD/gas10150000.ppm')[0], look('$BUILD/gas9900000.ppm')[0], look('$BUILD/gas10150000.ppm')[0],
look('$BUILD/gas10800000.ppm')[0], look('$BUILD/gas10800000.ppm')[0],
look('$BUILD/gas10150000.ppm')[1], look('$BUILD/gas10800000.ppm')[1]) look('$BUILD/gas10150000.ppm')[1], look('$BUILD/gas10800000.ppm')[1])