From 0264b19a3dc6f0efcdd7d0dcb7e3fbad785a4ffc Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Fri, 4 Sep 2026 12:23:31 -0400 Subject: [PATCH] Dust on landing, gas on letting go, and a lander that stays let go One particle system, three uses now: a lander coming apart, dust kicked up by a landing, and gas out of a docking port on release. Where they start, how fast they go, what colour they are and how long they last are arguments; everything else is shared. Dust goes 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. Gas goes evenly in every direction, because nothing is in the way of a docking port. Neither happens on docking - a dock is a catch and not a touchdown, and there is nothing under it to kick. ---- Ticked from the frame loop, not run in place ---- The explosion can afford to stop the world; there is nothing left to fly. The undocking puff cannot, because it goes off on the frame a thruster is pressed, and freezing a quarter of a second exactly then is felt as the controls sticking. So a burst advances one frame at a time from the main loop, and the two that can afford to wait just pump that same tick until the air is clear. ---- And letting go did not let go ---- Which the puff is what found. A docked lander sits EXACTLY one tile under the station, so releasing upwards moved it towards the station and it docked again on the very next frame: took the fuel again, said so again, and waited to be told the message had been read - which reads as the controls locking up the instant they are used. It has to get clear now before it can take hold again, and the two distances have to differ: docking wants one tile, re-arming wants two. A single distance re-armed on the frame it let go, because a tile is exactly where it was sitting. Three checks, each seen to fail on its own break. The last of them measures HOW FAR the lander has got and not merely that it moved: a sixteen frame pause on release still leaves it climbing, four rows short of a free run, so "it moved" would pass for a stall that has been slept through. --- Programs/CosmOS/Apps/Lander.asm | 264 +++++++++++++++++++++++----- Programs/CosmOS/README.md | 2 +- Tests/expected/cosmosCrossDisk.out | 2 +- Tests/expected/cosmosDrives.out | 2 +- Tests/expected/cosmosFault.out | 2 +- Tests/expected/cosmosFlip.out | 2 +- Tests/expected/cosmosGrid.out | 2 +- Tests/expected/cosmosMonitor.out | 2 +- Tests/expected/cosmosMonitorRun.out | 2 +- Tests/expected/cosmosRun.out | 2 +- Tests/expected/cosmosSlowDisk.out | 2 +- Tests/expected/cosmosSprite.out | 2 +- Tests/video.sh | 77 ++++++++ 13 files changed, 310 insertions(+), 53 deletions(-) diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index f32c704..78cd9af 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -112,6 +112,7 @@ everyFrame: CALL showFall CALL showFuel CALL showHeight + CALL burstTick ; Whatever is in the air, one frame on. SETD.0 Flying LDA.0 BNA everyFrame @@ -1795,6 +1796,8 @@ touchdown: SUB BNC touchdownCrash + CALL landingDust ; Something to see for an arrival, as a crash has always had. + ; ---- Down safely, which is not the end of anything ---- ; ; The view settles, the arrival is dealt with, and the lander sits where it is until @@ -1877,13 +1880,13 @@ dock: CALL magnitude SETD.0 DriftHigh LDA.0 - BNA dockDone + BNA dockAway ; 256 sixteenths or more, which is properly out of the way. SETD.0 DriftLow LDA.0 INIB 0d128 CCF SUB - BNC dockDone ; A whole tile away or more, so there is nothing to touch. + BNC dockDone ; A tile away or more: too far to dock, not far enough to re-arm. ; ---- And close enough, up and down ---- SETD.0 StationDownLow @@ -1905,7 +1908,7 @@ dock: CALL magnitude SETD.0 DriftHigh LDA.0 - BNA dockDone + BNA dockAway SETD.0 DriftLow LDA.0 INIB 0d128 @@ -1913,6 +1916,19 @@ dock: SUB BNC dockDone + ; ---- Still in the doorway it just left by ---- + ; + ; Letting go UPWARDS moves the lander towards the station, so without this it docked again on + ; the very next frame - took the fuel again, said so again, and waited to be told the message + ; had been read, which reads as the controls locking up the instant they are used. + ; + ; TWO DISTANCES, and they have to be different. Docking wants a tile; re-arming wants two, + ; because a lander riding the station sits EXACTLY a tile under it - so a single distance + ; re-armed on the very frame it let go, and the frame after that it was back on. + SETD.0 DockClear + LDA.0 + BRA dockDone + ; ---- Touching. Now, how fast, and RELATIVE TO WHAT ---- ; ; The station is going at orbital speed, so a lander going at orbital speed is standing still @@ -1966,6 +1982,12 @@ dock: SETD.0 DockedText CALL sayInWindow CALL waitKey + RET + +dockAway: + INIA 0x01 + SETD.0 DockClear + STA.0 ; Two tiles off, so the next arrival counts as an arrival. dockDone: RET @@ -2034,6 +2056,9 @@ dockLetGo: RSTA SETD.0 Docked 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. RET ; ---- Half a pixel of the way there, or the whole of it if that is nearer ---- @@ -2322,41 +2347,53 @@ putFlame: OUTA 0xE9 ; And no depth. RET -; ---- Coming apart, which is the only thing on this screen that is not a number ---- +; ---- Six pieces of something, thrown out from somewhere ---- ; -; A crash used to be a line of text and a lander still sitting there in one piece. The verdict -; was the whole of it, and somebody watching a recording had to read the words to know what had -; happened - the same problem the flames were for. +; One system, three uses: a lander coming apart, dust kicked up by a landing, and gas out of a +; docking port on release. What differs is where they start, how fast they go, what colour +; they are and how long they last, so all four of those are arguments and the rest is shared. ; -; The lander goes and six pieces of it leave in a rough hexagon, at its own colour, for half a -; second. The world is not running while this happens: it is a loop of its own, so nothing has -; to know how to be half destroyed. -explode: - RSTA - CALL hideSprite ; The lander itself. - INIA 0x70 - CALL hideSprite - INIA 0x80 - CALL hideSprite ; And both flames, which would otherwise hang in the air. +; ---- Ticked from the frame loop rather than run in place ---- +; +; The explosion can afford to stop the world - there is nothing left to fly. The undocking puff +; CANNOT: it goes off on the frame a thruster is pressed, and freezing for a quarter of a +; second exactly then would be felt as the controls sticking. So the burst advances one frame +; at a time from the main loop, and the two that can afford to wait simply pump that same tick +; until it runs out. +startBurst: + ; DP0 names six pairs of speeds; the origin, colour and length are already set. + SETD.1 BurstV + INIA 0d24 + SETD.2 CopyLeft + STA.2 +startBurstCopy: + LDA.0 + STA.1 + INCD.0 + INCD.1 + SETD.2 CopyLeft + LDA.2 + DECA + STA.2 + BNA startBurstCopy - ; Every piece starts where the lander was. RSTA SETD.0 SparkAt STA.0 INIA 0d6 SETD.0 SparkCount STA.0 -explodeStart: - ; DP0 is what copyWord reads and DP1 what it writes, so the lander's place is DP0. Written - ; the other way round first, which copied the empty sparks OVER ShipX - and since that is - ; the view block, the lander's own column with it. - SETD.0 ShipX +startBurstPlace: + ; DP0 is what copyWord reads and DP1 what it writes, so the origin is DP0. Written the other + ; way round first, which copied the empty pieces OVER ShipX - and since that is in the view + ; block, the lander's own column went with them. + SETD.0 BurstOXLow SETD.1 Spark SETD.2 SparkAt LDA.2 DPUA.1 CALL copyWord - SETD.0 ShipTopLow + SETD.0 BurstOYLow SETD.1 Spark SETD.2 SparkAt LDA.2 @@ -2374,13 +2411,14 @@ explodeStart: LDA.0 DECA STA.0 - BNA explodeStart + BNA startBurstPlace + RET - INIA 0d48 - SETD.0 SparkLeft - STA.0 ; Getting on for a second, which is long enough to watch. -explodeFrame: - CALL waitFrame +; One frame of whatever is in the air. Nothing at all when nothing is. +burstTick: + SETD.0 BurstLeft + LDA.0 + BRA burstTickDone RSTA SETD.0 SparkAt STA.0 @@ -2390,12 +2428,12 @@ explodeFrame: INIA 0d6 SETD.0 SparkCount STA.0 -explodeOne: +burstOne: SETD.0 Spark SETD.2 SparkAt LDA.2 DPUA.0 - SETD.1 SparkV + SETD.1 BurstV DPUA.1 CALL addWord ; Across, by however fast this piece is going. SETD.0 Spark @@ -2404,7 +2442,7 @@ explodeOne: INCA INCA DPUA.0 - SETD.1 SparkV + SETD.1 BurstV DPUA.1 CALL addWord ; And down. CALL putSpark @@ -2425,22 +2463,26 @@ explodeOne: LDA.0 DECA STA.0 - BNA explodeOne + BNA burstOne - SETD.0 SparkLeft + SETD.0 BurstLeft LDA.0 DECA STA.0 - BNA explodeFrame + BNA burstTickDone + CALL hideBurst ; The last frame of it, so they go rather than hang there. +burstTickDone: + RET - ; And away, or they would sit there under the verdict. +; Every piece off the screen, which is a size of nought six times. +hideBurst: INIA 0x90 SETD.0 SparkSlot STA.0 INIA 0d6 SETD.0 SparkCount STA.0 -explodeClear: +hideBurstOne: SETD.0 SparkSlot LDA.0 CALL hideSprite @@ -2454,7 +2496,103 @@ explodeClear: LDA.0 DECA STA.0 - BNA explodeClear + BNA hideBurstOne + RET + +; For the two that can afford to wait: pump the tick until there is nothing left in the air. +runBurst: + CALL waitFrame + CALL burstTick + SETD.0 BurstLeft + LDA.0 + BNA runBurst + RET + +; ---- Coming apart, which is the only thing on this screen that is not a number ---- +; +; A crash used to be a line of text and a lander still sitting there in one piece. The verdict +; was the whole of it, and somebody watching a recording had to read the words to know what had +; happened - the same problem the flames were for. +explode: + RSTA + CALL hideSprite ; The lander itself. + INIA 0x70 + CALL hideSprite + INIA 0x80 + CALL hideSprite ; And both flames, which would otherwise hang in the air. + SETD.0 ShipX + SETD.1 BurstOXLow + CALL copyWord + SETD.0 ShipTopLow + SETD.1 BurstOYLow + CALL copyWord + INIA 0x03 + SETD.0 BurstColour + STA.0 ; Yellow: it is the lander that is coming apart. + INIA 0d48 + SETD.0 BurstLeft + STA.0 ; Getting on for a second, which is long enough to watch. + SETD.0 SparkV + CALL startBurst + CALL runBurst + RET + +; ---- Dust, for a landing that went right ---- +; +; A crash has had something to watch since the pieces arrived, and an arrival did not. The +; puff comes off the LANDER'S FEET rather than its middle, which is where the ground is, and +; it goes sideways and up because that is where kicked dust goes. +; +; Not on docking. A dock is a catch and not a touchdown - there is nothing under it to kick. +landingDust: + SETD.0 ShipX + SETD.1 BurstOXLow + CALL copyWord + SETD.0 ShipTopLow + LDA.0 + INIB 0d8 + CCF + ADD + SETD.1 BurstOYLow + STQ.1 + SETD.0 ShipTopHigh + LDA.0 + RSTB + ADD ; A tile down from its top, which is where its feet are. + SETD.1 BurstOYHigh + STQ.1 + INIA 0x07 + SETD.0 BurstColour + STA.0 ; White, which is the one thing on this moon that is not grey. + INIA 0d16 + SETD.0 BurstLeft + STA.0 + SETD.0 DustV + CALL startBurst + CALL runBurst + RET + +; ---- And gas, for letting go of a station ---- +; +; Something has to come out of a docking port when it lets go, and this is a small symmetric +; puff of it. STARTED AND LEFT RUNNING, unlike the other two: it happens on the frame a +; thruster is pressed, and stopping the world exactly then would be felt as the controls +; sticking rather than as an effect. +undockGas: + SETD.0 ShipX + SETD.1 BurstOXLow + CALL copyWord + SETD.0 ShipTopLow + SETD.1 BurstOYLow + CALL copyWord + INIA 0x07 + SETD.0 BurstColour + STA.0 + INIA 0d16 + SETD.0 BurstLeft + STA.0 + SETD.0 GasV + CALL startBurst RET ; One piece, where the pointers say it now is. @@ -2468,8 +2606,9 @@ putSpark: OUTA 0xE5 INIA 0xC8 OUTA 0xE9 ; The solid block, at two pixels by two. - INIA 0x03 - OUTA 0xE9 ; Yellow: it is the lander that is coming apart. + SETD.0 BurstColour + LDA.0 + OUTA 0xE9 SETD.0 Spark SETD.2 SparkAt LDA.2 @@ -3639,6 +3778,11 @@ ShipTopHigh: ; beside them are a rough hexagon, so it comes apart evenly rather than in a line. Spark: #Reserve 0d24 +; The speeds in use this burst, copied over from one of the three below. +BurstV: + #Reserve 0d24 + +; A lander coming apart: a rough hexagon at two pixels a frame, so it goes evenly. SparkV: 0x02 0x00 0x00 0x00 0x01 0x00 0x02 0x00 @@ -3646,13 +3790,45 @@ SparkV: 0xFE 0xFF 0x00 0x00 0xFF 0xFF 0xFE 0xFF 0x01 0x00 0xFE 0xFF + +; Dust off a landing: sideways and UP, because that is where kicked dust goes, and none of it +; downwards because there is ground in the way. +DustV: + 0x03 0x00 0x00 0x00 + 0xFD 0xFF 0x00 0x00 + 0x02 0x00 0xFF 0xFF + 0xFE 0xFF 0xFF 0xFF + 0x01 0x00 0xFE 0xFF + 0xFF 0xFF 0xFE 0xFF + +; Gas out of a docking port: small, even, and in every direction, because nothing is in the way. +GasV: + 0x02 0x00 0x00 0x00 + 0xFE 0xFF 0x00 0x00 + 0x01 0x00 0x01 0x00 + 0xFF 0xFF 0x01 0x00 + 0x01 0x00 0xFF 0xFF + 0xFF 0xFF 0xFF 0xFF + SparkAt: 0x00 SparkSlot: 0x00 SparkCount: 0x00 -SparkLeft: +BurstLeft: + 0x00 +BurstColour: + 0x00 +BurstOXLow: + 0x00 +BurstOXHigh: + 0x00 +BurstOYLow: + 0x00 +BurstOYHigh: + 0x00 +CopyLeft: 0x00 HideSlot: 0x00 @@ -3686,6 +3862,10 @@ FlameBack: ; Holding onto the station, which is the same kind of rest as sitting on the ground. Docked: 0x00 +; Whether it has been clear of the station since it last let go. It starts clear, having never +; been attached to anything. +DockClear: + 0x01 ; Where the lander is sliding to, and how far it has to go to get there. DockTargetLow: 0x00 diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index b9ed088..4f06415 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. 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 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. 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 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/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 980d09d..276f738 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 9876c81..0fd6abc 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 45422eb..298732d 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 663ab45..c4264b5 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index a723055..d8e5d0b 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 777df55..b750427 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 1b7f228..ce614e2 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index f56b5e5..1c87c55 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index e2ea9a7..c2715d6 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index 06fee6a..c1eb696 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 7913 +Lander.sbx 8243 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/video.sh b/Tests/video.sh index ea4f7a2..33ecfd7 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -2661,6 +2661,83 @@ EOT && result ok "and are gone before the verdict" "nothing left over the words" \ || result no "and are gone before the verdict" "$AFTERBITS pixels still there" +# ---- Dust on landing, and gas on letting go ---- +# +# The same six pieces as the explosion, thrown differently: dust goes 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; gas goes evenly in every direction, because nothing is in the way of a docking +# port. Neither happens on DOCKING - a dock is a catch, not a touchdown, and there is nothing +# under it to kick. +# +# ---- The gas is the one that must not stop the world ---- +# +# It goes off on the frame a thruster is pressed. The explosion can afford to hold everything +# still, because there is nothing left to fly; freezing a quarter of a second exactly as the +# controls are used would be felt as them sticking. So the check watches the LANDER MOVE while +# the gas has gone, and how FAR it has got by then. +python3 -c " +import struct +open('$BUILD/undock.state','wb').write(struct.pack(' /dev/null +# Acknowledge the dock at frame 400, then hold up from 600 to let go of it. +python3 -c "open('$BUILD/undock.pad','wb').write(b'\x00' * 400 + b'\x10' * 8 + b'\x00' * 192 + b'\x08' * 4000 + b'\x00' * 20000)" +for when in 9900000 10150000 10800000; do + timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ + --pad "$BUILD/undock.pad" --screen "$BUILD/gas$when.ppm" \ + --disk "$BUILD/undock.img" --ram-disk 2048 \ + "$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 + 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 \ + "$BUILD/cosmos.bin" > "$BUILD/dust.out" 2>&1 || true +done +read -r DUSTBEFORE DUSTDURING DUSTAFTER GASBEFORE GASDURING GASAFTER SHIPHELD SHIPFREE < 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], + 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]) +" 2>/dev/null || echo "-1 -1 -1 -1 -1 -1 -1 -1") +EOT +[ "$DUSTBEFORE" = "0" ] && [ "$DUSTDURING" -gt 0 ] && [ "$DUSTAFTER" = "0" ] \ + && result ok "a landing kicks up dust" "none, then $DUSTDURING pixels of it, then none" \ + || result no "a landing kicks up dust" "$DUSTBEFORE before, $DUSTDURING during, $DUSTAFTER after" +[ "$GASBEFORE" = "0" ] && [ "$GASDURING" -gt 0 ] && [ "$GASAFTER" = "0" ] \ + && result ok "and letting go of the station vents gas" "none, then $GASDURING pixels, then none" \ + || result no "and letting go of the station vents gas" "$GASBEFORE before, $GASDURING during, $GASAFTER after" +# ---- Which is the half that matters ---- +# +# The lander has to be somewhere ELSE by the time the gas has gone. If letting go froze the +# world it would still be at the row it undocked from - and so would it if the lander re-docked +# on the next frame, which is what happened before a lander had to get clear of the station +# before it could take hold of it again: it took the fuel again, said so again, and sat waiting +# to be told the message had been read. +# HOW FAR it has got, not merely that it moved. Twelve rows is what a free run manages by +# then; a sixteen frame pause on letting go still leaves it climbing, just four rows short - so +# "it moved" passes for a stall that has been slept through and this does not. +[ "$SHIPFREE" -gt 0 ] && [ "$(( SHIPHELD - SHIPFREE ))" -ge 8 ] \ + && result ok "and the lander is well away, not merely moving" "row $SHIPHELD became row $SHIPFREE" \ + || result no "and the lander is well away, not merely moving" "row $SHIPHELD, then row $SHIPFREE" + # ---- Clearing puts the cursor back at the top ---- # # A screen with nothing on it and a cursor half way down it is not a cleared screen. This