diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index 2a3a43e..07bf8d4 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -80,7 +80,7 @@ start: CALL putLander CALL putGauge CALL putOrbital - CALL putBoom ; The instruments, once, the way the tiles are. + CALL putThrust ; The instruments, once, the way the tiles are. CALL putAlarm INIA 0x01 @@ -101,6 +101,7 @@ everyFrame: CALL readPad CALL readControls CALL zoomButton + CALL thrustSound ; Which of the thrusters just lit, or just went out. CALL fall CALL move CALL moveStation @@ -115,6 +116,7 @@ everyFrame: CALL showFuel CALL showHeight CALL burstTick ; Whatever is in the air, one frame on. + CALL pendTick ; And whatever note is waiting to follow another. SETD.0 Flying LDA.0 BNA everyFrame @@ -1819,6 +1821,14 @@ touchdown: SUB 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. ; ---- Down safely, which is not the end of anything ---- @@ -2001,6 +2011,14 @@ dock: INIA 0x01 SETD.0 Docked 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 SETD.0 DockedText CALL sayInWindow @@ -2081,7 +2099,14 @@ dockLetGo: STA.0 SETD.0 DockClear 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 ; ---- 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 ; 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. -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 - OUTA 0x41 ; Channel three. Every parameter write below lands on it. + OUTA 0x41 SETD.0 BoomPatch 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 ; ---- A whole patch, out of a table ---- @@ -2729,15 +2894,6 @@ playPatchOne: BNA playPatchOne 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 ---- ; ; 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 ; one already pressed counts. waitKey: + CALL hushThrust ; Nothing waits with an engine still running. +waitKeyPoll: CALL readPad SETD.0 Held LDA.0 @@ -2798,7 +2956,7 @@ waitKey: INA 0x01 INIB 0x01 ; READY AND - BRQ waitKey + BRQ waitKeyPoll INA 0x00 waitKeyDone: RET @@ -3674,6 +3832,18 @@ Fuel: 0xFF GaugeColour: 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. AlarmSaid: 0x00 @@ -4159,3 +4329,7 @@ LanderArtAt: #Include boom.asm #Include alarm.asm + +#Include thrustOn.asm +#Include thrustHold.asm +#Include latch.asm diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 5626344..3ccb8d1 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -727,7 +727,7 @@ from every assembly file in it. Several are old programs written for the bare ma | Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. | | Sprite | Moves a ball across the shell's own text, writing not one byte of the map to do it. It leaves the sprite in the table on the way out, because clearing them is the system's job - see below. | | Pad | Says what the controllers are doing, printing a line whenever one changes. It tells apart the three things that look identical from inside a game that is not responding: a pad nobody noticed, a pad mapped to nothing, and a mapping that is wrong. | -| Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. It turns from green to red at a quarter of a tank, and a warning sounds ONCE on the way down through it - once, because a lander is at its most careful in the last seconds before it touches and something repeating in its ear through that is a distraction rather than a warning. Filling up at a base allows it again. The sound is the moment it happened and the colour is how things stand, and both come off the same number so they cannot disagree. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. Four landing pads are carved into the moon after it is generated, levelled to whatever height their first column happened to have, and marked in cyan by an attribute rather than a tile of their own. The lander starts above one, because that is where a porter's day begins. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. A third runs up the left edge and is how much sky is under the lander, half a pixel of bar to a pixel of it; it reads nought the moment the lander is down, and it exists because the orbit takes the lander off the top of the screen and a panel that only works while the ground is in sight is no use above it. And gravity here is the pull MINUS the swing outwards, which is what makes an orbit rather than a one way trip: under four pixels a frame sideways the pull wins and the lander falls, over it the swing wins and the lander climbs, and at it they cancel and it circles. Falling buys sideways speed and climbing spends it - at a rate set by the product of the two, so the trade stops by itself as the sideways speed runs out - and that is the cycle: a fall carries the lander past orbital speed and becomes a climb, the climb pays it back and becomes a fall, periapse and apoapse, round and round. Orbital speed is marked on the drift bar, sixty four pixels either side of the middle, because a number nothing points at is folklore. Gravity never falls off and the moon wraps, so a circular orbit is the same length at any height and one orbital speed serves everywhere. There is a ceiling 192 pixels above the world's origin, which is the top of the wide view - it was sixty four while sixty four was all the sky a forty column screen had over the origin, which was a fact about the view rather than about the world. It pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and one sideways is spent every tick it tries to climb - without that the pin wiped the climb, the trade saw neither fall nor climb, and the speed pushing it up never changed. What kills you out here is running dry a long way from the ground. B, or z on the keyboard, swaps the screen between forty columns and eighty, which is the same map, the same cells and the same engine at half the size: nearly two thirds of the moon at once for the orbit, and twice as big for the landing. The twenty extra rows that buys go to the SKY and not to the moon - the wide view starts twenty four rows above the world's origin rather than at it, so the ground sits near the bottom and the whole flyable band is on the screen. Drawn down from the origin instead it was 71 per cent rock, which is a zoom that shows you more of the thing you cannot fly through. A lander at the ceiling is off the top of a forty column screen, which is where the orbit lives and why the altitude bar had to exist; zoomed out it is in the picture. And there is a station up there, twelve rows above the world's origin - about two thirds of the way from the ground to the ceiling, clear of work near the surface - going round at orbital speed - which needs no physics of its own, because a body at 64 sixteenths is exactly what a circular orbit IS under these rules, at any height, since gravity never falls off and the moon wraps. It is off the top of a forty column screen too, so it is somewhere to go that the zoom is needed to see. Docking it is landing on something that is moving: close enough, and slow enough RELATIVE TO THE STATION, or it is a wreck. Its speed is orbital speed, which is the number the marks on the drift bar point at, so the instrument for it was on the screen before there was anything to dock with. A dock pays eighty units of fuel and holds the lander a tile under the station until a thruster lets go, sliding it into line at half a pixel a frame rather than snapping - a dock is allowed eight pixels out either way, so putting the lander exactly in place the instant it took hold moved it a whole tile in one frame, right at the moment the player was being told they had been careful. It settles squarely under the port, which it did not used to: the lander is drawn from half a screen LESS HALF A TILE, because that is what centres it, and the station was drawn from half a screen exactly, so a perfectly flown dock still looked four pixels out. Getting there is a two burn manoeuvre and not a straight line: climbing SPENDS sideways speed, so a lander cannot rise while matched - it arrives slower than orbital every time, and has to raise the far side of its orbit and then circularise at the top. Which is also why it reads /lander.state if the disk has one: sixteen bytes of position, velocity, station, fuel and screen, laid straight over the numbers it would otherwise start with, so a rendezvous can be examined without flying one first. A disk without the file is the game as it always was. The buffer is a whole block wide because osFileRead lands a file in blocks of 256, and sixteen bytes of room for it wrote over everything that followed. A landing kicks up dust, sideways and up off the lander's feet, because that is where kicked dust goes and there is ground in the way of the rest of it. Letting go of the station vents gas instead, evenly in every direction, since nothing is in the way of a docking port - and that one does NOT stop the world the way the others do, because it goes off on the frame a thruster is pressed and freezing then would be felt as the controls sticking. There is none on docking: a dock is a catch and not a touchdown, and there is nothing under it to kick. A lander also has to get two tiles clear of the station before it can take hold of it again, because a docked one sits exactly one tile under it - so letting go upwards docked it straight back on, took the fuel again, and sat waiting to be told the message had been read. A crash also makes a noise, which is the first sound this game has, and it is a TRIGGERED voice - struck once and then playing its own length, so nothing has to come back and end it. That the program says so rather than the patch is deliberate: the patch decides what the bang sounds like and the program decides that it is a bang. It is noise through a low pass that the modulation envelope shuts as the level falls, so the bright part is only at the front and it is a boom rather than a hiss. The instrument is built at startup the way the tiles are, because a patch is twenty odd writes and a note is two - which is what the selector and value registers are for. It plays on channel THREE: effects count down from the top so that music, if it ever arrives, can take nought and count up and the two never have to negotiate. A crash takes the lander apart: it goes, and six pieces of it leave in a rough hexagon at its own colour for about a second before the verdict is said. It used to be a line of text and a lander still sitting there in one piece, so a watcher had to read the words to know what had happened. The world is not running while that plays - it is a loop of its own, so nothing else has to know how to be half destroyed. 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 where the gas leaves. Every other reading here is a number drawn as a bar and all of them say what is happening TO the lander, so without this a watcher has to read gauges to work out that a thruster is even lit. It is drawn while the BUTTON IS HELD rather than while the engine fires: the engine fires one frame in ten, because that is the tick gravity is applied on, and a flame that honest is a fault lamp rather than a rocket. An empty tank draws none, and neither does the retro thruster on the ground, because in both cases the button really is doing nothing. Down is a retro thruster at half the strength of up - one sixteenth a tick against two, which is exactly gravity's own step, so it can stop a climb and hurry a descent and can never turn a landing approach into a crash faster than letting go would. Arresting a rise otherwise meant a sideways burn and a wait for the orbit to come round, which is how a rendezvous really is flown and a lot to ask of somebody who has not flown one. It does nothing at all to a lander on the ground, which is not politeness: touchdown has already had its say and will not speak again, so without that guard a landed lander holding it goes straight through the moon. | +| Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. It turns from green to red at a quarter of a tank, and a warning sounds ONCE on the way down through it - once, because a lander is at its most careful in the last seconds before it touches and something repeating in its ear through that is a distraction rather than a warning. Filling up at a base allows it again. The sound is the moment it happened and the colour is how things stand, and both come off the same number so they cannot disagree. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. Four landing pads are carved into the moon after it is generated, levelled to whatever height their first column happened to have, and marked in cyan by an attribute rather than a tile of their own. The lander starts above one, because that is where a porter's day begins. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. A third runs up the left edge and is how much sky is under the lander, half a pixel of bar to a pixel of it; it reads nought the moment the lander is down, and it exists because the orbit takes the lander off the top of the screen and a panel that only works while the ground is in sight is no use above it. And gravity here is the pull MINUS the swing outwards, which is what makes an orbit rather than a one way trip: under four pixels a frame sideways the pull wins and the lander falls, over it the swing wins and the lander climbs, and at it they cancel and it circles. Falling buys sideways speed and climbing spends it - at a rate set by the product of the two, so the trade stops by itself as the sideways speed runs out - and that is the cycle: a fall carries the lander past orbital speed and becomes a climb, the climb pays it back and becomes a fall, periapse and apoapse, round and round. Orbital speed is marked on the drift bar, sixty four pixels either side of the middle, because a number nothing points at is folklore. Gravity never falls off and the moon wraps, so a circular orbit is the same length at any height and one orbital speed serves everywhere. There is a ceiling 192 pixels above the world's origin, which is the top of the wide view - it was sixty four while sixty four was all the sky a forty column screen had over the origin, which was a fact about the view rather than about the world. It pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and one sideways is spent every tick it tries to climb - without that the pin wiped the climb, the trade saw neither fall nor climb, and the speed pushing it up never changed. What kills you out here is running dry a long way from the ground. B, or z on the keyboard, swaps the screen between forty columns and eighty, which is the same map, the same cells and the same engine at half the size: nearly two thirds of the moon at once for the orbit, and twice as big for the landing. The twenty extra rows that buys go to the SKY and not to the moon - the wide view starts twenty four rows above the world's origin rather than at it, so the ground sits near the bottom and the whole flyable band is on the screen. Drawn down from the origin instead it was 71 per cent rock, which is a zoom that shows you more of the thing you cannot fly through. A lander at the ceiling is off the top of a forty column screen, which is where the orbit lives and why the altitude bar had to exist; zoomed out it is in the picture. And there is a station up there, twelve rows above the world's origin - about two thirds of the way from the ground to the ceiling, clear of work near the surface - going round at orbital speed - which needs no physics of its own, because a body at 64 sixteenths is exactly what a circular orbit IS under these rules, at any height, since gravity never falls off and the moon wraps. It is off the top of a forty column screen too, so it is somewhere to go that the zoom is needed to see. Docking it is landing on something that is moving: close enough, and slow enough RELATIVE TO THE STATION, or it is a wreck. Its speed is orbital speed, which is the number the marks on the drift bar point at, so the instrument for it was on the screen before there was anything to dock with. A dock pays eighty units of fuel and holds the lander a tile under the station until a thruster lets go, sliding it into line at half a pixel a frame rather than snapping - a dock is allowed eight pixels out either way, so putting the lander exactly in place the instant it took hold moved it a whole tile in one frame, right at the moment the player was being told they had been careful. It settles squarely under the port, which it did not used to: the lander is drawn from half a screen LESS HALF A TILE, because that is what centres it, and the station was drawn from half a screen exactly, so a perfectly flown dock still looked four pixels out. Getting there is a two burn manoeuvre and not a straight line: climbing SPENDS sideways speed, so a lander cannot rise while matched - it arrives slower than orbital every time, and has to raise the far side of its orbit and then circularise at the top. Which is also why it reads /lander.state if the disk has one: sixteen bytes of position, velocity, station, fuel and screen, laid straight over the numbers it would otherwise start with, so a rendezvous can be examined without flying one first. A disk without the file is the game as it always was. The buffer is a whole block wide because osFileRead lands a file in blocks of 256, and sixteen bytes of room for it wrote over everything that followed. A landing kicks up dust, sideways and up off the lander's feet, because that is where kicked dust goes and there is ground in the way of the rest of it. Letting go of the station vents gas instead, evenly in every direction, since nothing is in the way of a docking port - and that one does NOT stop the world the way the others do, because it goes off on the frame a thruster is pressed and freezing then would be felt as the controls sticking. There is none on docking: a dock is a catch and not a touchdown, and there is nothing under it to kick. A lander also has to get two tiles clear of the station before it can take hold of it again, because a docked one sits exactly one tile under it - so letting go upwards docked it straight back on, took the fuel again, and sat waiting to be told the message had been read. A thruster bangs when it lights and rumbles while it burns, and the rumble is a HELD note: its gate goes down when one lights and does not come up until every one is out - or until anything stops to wait, because a held note outlives the loop that was holding it and landing on the thruster used to leave it roaring under the verdict. Arriving somewhere latches two notes quickly, middle C then the C above for taking hold of the station and the same pair reversed for letting go, two octaves lower for the ground. The second note is pending rather than played, since at an undocking the pilot is mid-burn and stopping the world for an eighth of a second would be felt as the controls sticking. 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 outright. A crash also makes a noise, which was the first sound this game had, and it is a TRIGGERED voice - struck once and then playing its own length, so nothing has to come back and end it. That the program says so rather than the patch is deliberate: the patch decides what the bang sounds like and the program decides that it is a bang. It is noise through a low pass that the modulation envelope shuts as the level falls, so the bright part is only at the front and it is a boom rather than a hiss. The instrument is built at startup the way the tiles are, because a patch is twenty odd writes and a note is two - which is what the selector and value registers are for. It plays on channel THREE: effects count down from the top so that music, if it ever arrives, can take nought and count up and the two never have to negotiate. A crash takes the lander apart: it goes, and six pieces of it leave in a rough hexagon at its own colour for about a second before the verdict is said. It used to be a line of text and a lander still sitting there in one piece, so a watcher had to read the words to know what had happened. The world is not running while that plays - it is a loop of its own, so nothing else has to know how to be half destroyed. 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 where the gas leaves. Every other reading here is a number drawn as a bar and all of them say what is happening TO the lander, so without this a watcher has to read gauges to work out that a thruster is even lit. It is drawn while the BUTTON IS HELD rather than while the engine fires: the engine fires one frame in ten, because that is the tick gravity is applied on, and a flame that honest is a fault lamp rather than a rocket. An empty tank draws none, and neither does the retro thruster on the ground, because in both cases the button really is doing nothing. Down is a retro thruster at half the strength of up - one sixteenth a tick against two, which is exactly gravity's own step, so it can stop a climb and hurry a descent and can never turn a landing approach into a crash faster than letting go would. Arresting a rise otherwise meant a sideways burn and a wait for the orbit to come round, which is how a rendezvous really is flown and a lot to ask of somebody who has not flown one. It does nothing at all to a lander on the ground, which is not politeness: touchdown has already had its say and will not speak again, so without that guard a landed lander holding it goes straight through the moon. | | Depth | Four pillars at four distances and a ball walking past them, behind the near ones and in front of the far ones. The ball is sprite nought and every pillar is numbered after it, so table order puts it in front of all four - what actually decides is the depth buffer, asked a column at a time. | | Flip | Draws a whole screen into the bank that is not being shown, waits, and then shows it in one byte out of one port. It writes nothing else at all - not a tile, not a colour - so it does not ask for the screen to be saved, and the line it printed is still there when it comes back. It deliberately does not put the displayed screen back either, because that is the system's to restore: a program that faulted while flipped could not have. | | Edit | A line editor. | diff --git a/Programs/Sounds/DockLatch.json b/Programs/Sounds/DockLatch.json new file mode 100644 index 0000000..5fa77c3 --- /dev/null +++ b/Programs/Sounds/DockLatch.json @@ -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 +} diff --git a/Programs/Sounds/ThrustHold.json b/Programs/Sounds/ThrustHold.json new file mode 100644 index 0000000..9d73959 --- /dev/null +++ b/Programs/Sounds/ThrustHold.json @@ -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 +} diff --git a/Programs/Sounds/ThrustOn.json b/Programs/Sounds/ThrustOn.json new file mode 100644 index 0000000..1824f98 --- /dev/null +++ b/Programs/Sounds/ThrustOn.json @@ -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 +} diff --git a/Programs/Sounds/latch.asm b/Programs/Sounds/latch.asm new file mode 100644 index 0000000..fa91925 --- /dev/null +++ b/Programs/Sounds/latch.asm @@ -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 diff --git a/Programs/Sounds/thrustHold.asm b/Programs/Sounds/thrustHold.asm new file mode 100644 index 0000000..a13ae31 --- /dev/null +++ b/Programs/Sounds/thrustHold.asm @@ -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 diff --git a/Programs/Sounds/thrustOn.asm b/Programs/Sounds/thrustOn.asm new file mode 100644 index 0000000..6f8bdfb --- /dev/null +++ b/Programs/Sounds/thrustOn.asm @@ -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 diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 42adce2..a37e263 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -25,7 +25,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 54bf7f2..c443ddb 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 884fd10..83c8525 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -32,7 +32,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 77d1b5a..8d49060 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index b81d74e..018685a 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 1040880..9c6b5fc 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -108,7 +108,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 4fe773b..369240b 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -27,7 +27,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 938fc3e..fb798c4 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index c5124e2..bbf0a86 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -13,7 +13,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index e2841e6..4ea87ad 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 8608 +Lander.sbx 9168 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/video.sh b/Tests/video.sh index 53162a2..beb4f75 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -2672,16 +2672,13 @@ EOT # 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. # -# Checked against silence, which is the useful half: a landing in the same conditions makes no -# sound at all, so this is measuring the crash and not the machine humming. +# Checked against silence BEFORE it rather than against a quiet landing, because a landing is +# 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" \ --pad "$BUILD/none.pad" --sound "$BUILD/bang.raw" \ --disk "$BUILD/crash.img" --ram-disk 2048 \ "$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 < back else 0) 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") EOT [ "$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 no "a crash makes a noise, and a landing does not" "crash peak $BANGPEAK, landing peak $HUSHPEAK" + && 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 out of silence" "crash peak $BANGPEAK, $HUSHPEAK beforehand" # ---- And it fades ---- # # 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 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(' /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 < 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 ---- # # 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(' /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 timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ --pad "$BUILD/burn.pad" --screen "$BUILD/fuel$when.ppm" \ @@ -2757,26 +2837,28 @@ def gauge(path): 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 ---- +# ---- WHAT IS STILL SOUNDING once the thruster stops ---- # -# 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) +# Two measurements failed before this one. Counting bursts cannot tell a beep from a nag: the +# warning would fire every tick the tank drops, far faster than the sound decays, so the beeps +# run together and a burst counter sees one long burst either way. Measuring the total length +# stopped working the day the thruster got a rumble of its own, which sounds for the whole burn +# whatever the warning does. +# +# 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") 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" +[ "$WARNINGS" -lt 500 ] \ + && result ok "and the warning sounds once, not once a tick" "nothing left over when the burn ends" \ + || result no "and the warning sounds once, not once a tick" "$WARNINGS still sounding after the burn" # ---- 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 done # 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" \ --pad "$BUILD/none.pad" --screen "$BUILD/dust$when.ppm" \ --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]) ship = where('d8c048') return puff, (min(y for x, y in ship) if ship else -1) -print(look('$BUILD/dust2700000.ppm')[0], look('$BUILD/dust2820000.ppm')[0], - look('$BUILD/dust3100000.ppm')[0], +print(look('$BUILD/dust2900000.ppm')[0], look('$BUILD/dust3050000.ppm')[0], + look('$BUILD/dust3300000.ppm')[0], look('$BUILD/gas9900000.ppm')[0], look('$BUILD/gas10150000.ppm')[0], look('$BUILD/gas10800000.ppm')[0], look('$BUILD/gas10150000.ppm')[1], look('$BUILD/gas10800000.ppm')[1])