diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index c5b6d0a..5da0f8f 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -3591,7 +3591,27 @@ splashBlank: INIA 0d11 CALL sayAtRow - SETD.0 SplashTune + ; ---- Where the tune is, asked rather than assumed ---- + ; + ; This used to name /splash.tune, which was true for exactly as long as the game and its + ; things both sat at the root of the disk that started the machine. The game lives in a + ; directory of its own now and what somebody types is a launcher in /Apps, so the only + ; thing that says where the tune is, is where the game itself came from. + ; + ; And it has to be asked rather than worked out from the working directory, because that + ; is where the PERSON is standing. A player in their own directory is not standing in + ; Lander's, and should not have to be. + SETD.0 MyPath + INIB 0d192 + SWI osWhereAmI + SETD.0 MyPath + SETD.1 SplashTune + SETD.2 TunePath + INIB 0d192 + CALL pathBeside + BNQ splashSilent ; A path that did not fit cannot name a tune. + + SETD.0 TunePath SETD.1 SplashBuffer SWI osFileRead BNQ splashSilent @@ -4253,8 +4273,13 @@ StudioLine: " ANACHRONAUT" Nothing: 0x00 +; Beside the game, wherever the game turns out to be. See splash, which asks. SplashTune: -"/splash.tune" +"splash.tune" +MyPath: + #Reserve 0d192 +TunePath: + #Reserve 0d192 SplashBuffer: #Reserve 0d1024 @@ -4666,6 +4691,10 @@ LanderArtAt: #Include math.asm +; Joining a name to the place another thing is in, which is how the tune is found now that +; the game does not know where it lives until it asks. +#Include path.asm + #Include player.asm #Include boom.asm diff --git a/Programs/CosmOS/Launchers/Lander b/Programs/CosmOS/Launchers/Lander new file mode 100644 index 0000000..51450f8 --- /dev/null +++ b/Programs/CosmOS/Launchers/Lander @@ -0,0 +1,16 @@ +#! script +; The launcher for Lunar Porter, which is what typing "Lander" actually starts. +; +; An application with things of its own gets a DIRECTORY of its own, and a typed word names +; a file rather than a directory - so what sits in /Apps is this, and the game sits with its +; tune in /Packages/app.Lander. The shell decides what to run by reading it, so this file +; beginning "#!" and Say.sbx beginning "SBEX" go down the same road and neither the shell nor +; the person has to know which kind of thing they started. +; +; That indirection is the point of it. Where the game physically lives stops being part of +; its name, so an installer may put it on another disk and change only this line. +; +; #quiet, because a launcher is machinery and not narration. Without it, starting a game +; prints the path of the thing it is about to start. +#quiet +0:/Packages/app.Lander/Lander.sbx $args diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index ead634f..275be4d 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -758,6 +758,7 @@ laid out in three directories: | -- | -- | | `/Apps` | What you run. The second place the shell looks for a word it does not recognise, so anything here starts by name from anywhere on the disk - programs, and scripts too. | | `/Source` | The things you name to the assembler: CosmOS itself, the assembler itself, and small programs to read. | +| `/Packages` | What applications own. One directory per packaged application, named `app.`, holding the program and its assets so they can be found together and removed together. | | `/Lib` | The things those include. Everything here is named by an `#Include` somewhere and by nothing else, which is what makes it a library rather than a source. `path.asm` is the newest of them: joining a name to a place, which is arithmetic on two strings and so wants no help from the system. | The split is by role rather than by which directory the host keeps a file in, and it only @@ -800,7 +801,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 opens on a splash: the studio, and whatever /splash.tune holds, played over it before the world is built. Lander includes Libraries/player.asm and owns the timer and all four channels while it plays, because nothing else is happening yet - and it POLLS the timer rather than being interrupted by it, the same way it waits for the screen, so it brings no handler that would have to be taken away before the game starts. Any key or A or Start skips it, and one already held when it begins means no splash at all rather than a picture nobody wanted; a missing tune means the logo and silence, the way a missing /lander.state means the defaults stand. A flight begins where a flight begins: run does not reload, so the numbers the assembler wrote are load-time values and an init routine puts back the ones a new flight needs - including the terrain seed, which is a written number, so the moon is the same moon every time rather than whatever the last run's random walk left behind. What is not on that list keeps what the last run left it, which is a decision rather than an oversight. 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 EVERY thruster that catches bangs - a pilot already burning upwards who then adds a sideways one has lit an engine, so what is watched is which of them are lit rather than whether any is. The rumble is struck once and runs until every one is out, since restriking it when a second joins would start its attack over, which is a stutter rather than an engine, 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. No instrument is set up once at startup: each sound loads its patch immediately before its note, because the bang and the latch share a channel and a channel used by two sounds has to be told which of them it is about to be. That rule arrived as a workaround for something worse - an LFO used to belong to the DEVICE and not to a channel, so whichever patch was loaded last owned both of them for every voice at once, which is what made the warning's trill vanish after the first landing of a run when the landing's patch carried an LFO switched off. An intermittent fault of the worst kind, right until something unrelated plays and right again next time the machine starts. Loading at the note fixed each sound as it STARTED and could not fix two of them overlapping, so the device was changed instead and an LFO now belongs to the channel it was written to. 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. | +| 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 opens on a splash: the studio, and whatever splash.tune holds, played over it before the world is built - found beside the game by asking the system where the game came from, rather than at a fixed place on the disk, since the game lives in /Packages/app.Lander now and what you type is a launcher in /Apps. Lander includes Libraries/player.asm and owns the timer and all four channels while it plays, because nothing else is happening yet - and it POLLS the timer rather than being interrupted by it, the same way it waits for the screen, so it brings no handler that would have to be taken away before the game starts. Any key or A or Start skips it, and one already held when it begins means no splash at all rather than a picture nobody wanted; a missing tune means the logo and silence, the way a missing /lander.state means the defaults stand. A flight begins where a flight begins: run does not reload, so the numbers the assembler wrote are load-time values and an init routine puts back the ones a new flight needs - including the terrain seed, which is a written number, so the moon is the same moon every time rather than whatever the last run's random walk left behind. What is not on that list keeps what the last run left it, which is a decision rather than an oversight. 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 EVERY thruster that catches bangs - a pilot already burning upwards who then adds a sideways one has lit an engine, so what is watched is which of them are lit rather than whether any is. The rumble is struck once and runs until every one is out, since restriking it when a second joins would start its attack over, which is a stutter rather than an engine, 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. No instrument is set up once at startup: each sound loads its patch immediately before its note, because the bang and the latch share a channel and a channel used by two sounds has to be told which of them it is about to be. That rule arrived as a workaround for something worse - an LFO used to belong to the DEVICE and not to a channel, so whichever patch was loaded last owned both of them for every voice at once, which is what made the warning's trill vanish after the first landing of a run when the landing's patch carried an LFO switched off. An intermittent fault of the worst kind, right until something unrelated plays and right again next time the machine starts. Loading at the note fixed each sound as it STARTED and could not fix two of them overlapping, so the device was changed instead and an LFO now belongs to the channel it was written to. 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. | @@ -981,8 +982,9 @@ which assembles its own now. reads the disk for everything else. ``` -/ Apps Source Lib System +/ Apps Packages Source Lib System /Apps what you run, and the second place the shell looks for a word +/Packages what applications own, one app. directory each /Source what you assemble, including stage1.asm and stage2.asm /Lib what those include /System/Boot cosmos.bin, and the slots the loader lives in @@ -1488,6 +1490,46 @@ directory stays the person's; this is how a program finds its own things. `Programs/CosmOS/Apps/Where.asm` is the whole of it in one program, and exists to be run rather than read. +## An Application With Things Of Its Own: + +A simple program is one file and lives in `/Apps`. One with **assets** gets a directory of +its own and a **launcher** in `/Apps` under the bare name somebody types. + +```text +/Apps/Say.sbx a simple program, unchanged +/Apps/Lander a launcher +/Packages/app.Lander/Lander.sbx the program +/Packages/app.Lander/splash.tune its tune +``` + +The launcher is a script, and starting it needs no new rule at all: the shell runs what it +reads, so `Say.sbx` begins `SBEX` and gets loaded while `/Apps/Lander` begins `#!` and gets +read as lines, and neither the shell nor the person has to know which kind of thing they +started. It says `#quiet`, because a launcher is machinery rather than narration. + +**The indirection is the point.** Where the game physically lives stops being part of its +name, so an installer may put it on another disk and change one line. That is also why the +launcher names the drive: found in the system's place on drive 0 by somebody standing on a +disk of their own, its lines run on *their* disk, and a plain `/Packages/...` would look +there. Naming `0:` makes the game and its things be looked for where the game is. + +**Not in `/Apps`, for two reasons.** A launcher and a directory of the same name cannot both +be there - SBFS refuses the second outright. And `/Apps` is the directory the shell walks for +every word it does not know and Tab walks for every first word, so doubling what is in it is +a cost on the path that runs most. + +**The `app.` goes in front rather than behind**, and that is not decoration. Tab matches the +*start* of a name, so a prefix is a namespace and a suffix is a collision: with `Copy.app` +beside `Copy.sbx`, typing `Copy` and pressing Tab completes to the shared `Copy.` and hands +you a broken word. In front, `C` and Tab does not list them at all and `app.` and Tab is how +you ask for them. + +**Assets and documents are told apart by where they are looked for.** Lander's tune lives +beside Lander and is found by asking where Lander is; `/lander.state`, a saved position, +stays at the root and is found where the *person* is standing. One belongs to the program and +the other to whoever ran it, which is the whole distinction and the reason the working +directory could not serve both. + ## The Filesystem Library: The disk knows blocks and nothing else, so a filesystem is software. Programs/CosmOS/Source/sbfs.asm is one. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 6ae7f0d..fc08a1c 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -26,13 +26,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -51,7 +50,7 @@ args.script 64 pass.script 20 holds.script 87 crossed.txt 560 -38 files, 1 directory +36 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 53c84a0..b626db8 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -16,13 +16,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -40,7 +39,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > drive 1 > dir other.txt 28 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 33cf4f4..61df993 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -33,13 +33,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -57,7 +56,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 7d4e805..dfc4432 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -23,13 +23,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -47,7 +46,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index e96e626..6a5fdd3 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -23,13 +23,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -47,7 +46,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index f672baa..cac772c 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -109,13 +109,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -133,7 +132,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 6ea896a..786da27 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -28,13 +28,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -52,7 +51,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index b0ee8cf..f84b9ad 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -16,13 +16,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -40,7 +39,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > load load what? > load nosuch.sbx diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index cb21626..e43deec 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -14,13 +14,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -38,7 +37,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > load Say.sbx loaded, starting at 5000 > run the disk took its time diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index baa747a..9882fe8 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -23,13 +23,12 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 11260 +Packages Pad.sbx 264 Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -47,7 +46,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -37 files, 1 directory +35 files, 2 directories > exit halted Execution halted. diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index cc62f6c..7967f83 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -153,12 +153,22 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/ "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Depth.asm" -o "$WORK/Depth.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Depth.sbx" >/dev/null -# Lander.sbx is Lunar Porter, and the biggest program on the disk. It takes the screen, the -# mode and the map, so it is also the one that says a program can have all three back. +# ---- Lunar Porter, which is the first application with a directory of its own ---- +# +# Lander.sbx is the biggest program on the disk. It takes the screen, the mode and the map, +# so it is also the one that says a program can have all three back. +# +# It does NOT go in /Apps any more. A program with assets gets /Packages/app. and a +# LAUNCHER in /Apps under the bare name that is typed - so what "Lander" names is a script, +# which the shell runs because of what is inside it rather than what it is called. Every +# recorded Lander test still types just "Lander" and knows nothing about any of this, which +# is exactly the claim being made. "$ROOT/Assembler" -I "$ROOT/Programs/Sounds" -I "$ROOT/Programs/Libraries" \ -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Lander.asm" -o "$WORK/Lander.sbx" >/dev/null -"$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" >/dev/null +"$TOOL" mkdir "$DISKS/cosmos.img" /Packages >/dev/null +"$TOOL" mkdir "$DISKS/cosmos.img" /Packages/app.Lander >/dev/null +"$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" /Packages/app.Lander/Lander.sbx >/dev/null # Pad.sbx says what the controllers are doing, which is the only way to tell a pad nobody # noticed from one that is mapped to nothing. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ @@ -188,9 +198,14 @@ printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset # Written in the tune language and compiled here, so the music can be changed without # touching the game. Lander reads it off the disk at startup and starts anyway if it is not # there, the way it treats /lander.state. +# +# BESIDE THE GAME rather than at the root, which is what the game now asks for: it is told +# where it was loaded from and joins the tune's name to that. /lander.state stays at the root +# on purpose - a saved position is the PERSON's, so it is found where the person is standing, +# and the two of them together are the whole distinction between an asset and a document. "$ROOT/SoundPatch" --blob "$ROOT/Programs/Sounds/Oboe.json" Oboe "$WORK/oboe.patch" >/dev/null "$ROOT/TuneC" -I "$WORK" "$ROOT/Programs/Tunes/splash.score" "$WORK/splash.tune" >/dev/null -"$TOOL" put "$DISKS/cosmos.img" "$WORK/splash.tune" splash.tune >/dev/null +"$TOOL" put "$DISKS/cosmos.img" "$WORK/splash.tune" /Packages/app.Lander/splash.tune >/dev/null # tune.sbx, which used to be a boot image and is now a program like any other. It brings a # vector of its own - the timer's, so that it has a beat to play to - which makes it the @@ -237,6 +252,9 @@ python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20) "$TOOL" put "$DISKS/cosmos.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Where.sbx" /Apps/Where.sbx >/dev/null +# And the launcher for Lunar Porter, under the bare name somebody types. Put here rather than +# beside the game above, because /Apps is not made until now. +"$TOOL" put "$DISKS/cosmos.img" "$ROOT/Programs/CosmOS/Launchers/Lander" /Apps/Lander >/dev/null # A SCRIPT in the system's place, which is the one thing on this disk that is fetched from # drive 0 a block at a time WHILE it runs. Its own name carries the drive in front of it, # and resolving a name like that moves the machine to that drive - so a script started from diff --git a/Tests/video.sh b/Tests/video.sh index 50a81d7..8d59782 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -1623,7 +1623,7 @@ for i in range(400): keys += b'\x80' + b'\x00' * 47 open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 8000) " -timeout 30 "$EMU" --fast --cycles 900000 --keyboard "$BUILD/lander.keys" \ +timeout 30 "$EMU" --fast --cycles 1100000 --keyboard "$BUILD/lander.keys" \ --screen "$BUILD/lander.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \ --ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/lander.out" 2>&1 || true read -r MOON SKY SHIP SHIPLEFT SHIPRIGHT </dev/null || echo "-1 -1") EOT [ "$EARLY" -gt 0 ] && [ "$LATE" -gt 0 ] && [ "$LATE" -lt "$EARLY" ] \ @@ -1827,7 +1827,7 @@ open('$BUILD/fly.keys','wb').write(b'Lander\n' + SKIP + b'\x00' * 8000)" python3 -c "open('$BUILD/none.pad','wb').write(b'\x00' * 60000)" cp "$ROOT/Tests/build/disks/cosmos.img" "$BUILD/orbit.img" "$ROOT/SplitDisk" put "$BUILD/orbit.img" "$BUILD/orbit.state" /lander.state > /dev/null -for when in 3000000 13000000 25000000 37000000; do +for when in 3200000 13200000 25200000 37200000; do timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ --pad "$BUILD/none.pad" --screen "$BUILD/orbit$when.ppm" \ --disk "$BUILD/orbit.img" --ram-disk 2048 \ @@ -1842,8 +1842,8 @@ def height(path): ship = [i // width for i in range(len(px) // 3) if px[i * 3:i * 3 + 3] == bytes.fromhex('d8c048')] return min(ship) if ship else -1 -print(height('$BUILD/orbit3000000.ppm'), height('$BUILD/orbit13000000.ppm'), - height('$BUILD/orbit25000000.ppm'), height('$BUILD/orbit37000000.ppm')) +print(height('$BUILD/orbit3200000.ppm'), height('$BUILD/orbit13200000.ppm'), + height('$BUILD/orbit25200000.ppm'), height('$BUILD/orbit37200000.ppm')) " 2>/dev/null || echo "-1 -1 -1 -1") EOT # A smaller row is higher up. Fifty pixels of change either way, against turning points that @@ -1976,11 +1976,17 @@ EOT # is the drift: it grows right from the middle of the screen for a rightward one and left for # a leftward one, and NOUGHT DRAWS NOTHING, so "stopped" is the state with no bar at all. # -# The recording holds Right for eighty frames and then nothing. Checked in two places: with a -# drift, where the bar starts at the middle and runs right; and with none, where a sprite of -# no width is a sprite that is not drawn. -python3 -c "open('$BUILD/drift.pad','wb').write(b'\x00' * 20 + b'\x01' * 80 + b'\x00' * 400)" -timeout 30 "$EMU" --fast --cycles 1600000 --keyboard "$BUILD/lander.keys" \ +# The recording holds Right for a hundred and twenty frames and then nothing. Checked in two +# places: with a drift, where the bar starts at the middle and runs right; and with none, +# where a sprite of no width is a sprite that is not drawn. +# +# IT WAS EIGHTY, AND THE PAD IS COUNTED FROM THE MACHINE STARTING rather than from the game +# starting. A launcher put twelve frames between those two, so the game saw twelve fewer +# frames of the burn and drew a narrower bar - which is a real reading of a real drift, and +# nothing to do with what this check is about. Longer, so that what is being asked about is +# whether a drift draws a bar rather than how long the pilot held the button. +python3 -c "open('$BUILD/drift.pad','wb').write(b'\x00' * 20 + b'\x01' * 120 + b'\x00' * 400)" +timeout 30 "$EMU" --fast --cycles 1800000 --keyboard "$BUILD/lander.keys" \ --pad "$BUILD/drift.pad" --screen "$BUILD/drift.ppm" \ --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ "$BUILD/cosmos.bin" > "$BUILD/drift.out" 2>&1 || true @@ -2343,13 +2349,13 @@ python3 -c " pad = bytearray(b'\x08' * 3000) pad[100:108] = b'\x28' * 8 # Up and B together, so the climb is not interrupted. open('$BUILD/hoverwide.pad','wb').write(bytes(pad) + b'\x00' * 20000)" -for when in 2000000 3000000 5000000 7000000; do +for when in 2200000 3200000 5200000 7200000; do timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ --pad "$BUILD/hoverwide.pad" --screen "$BUILD/station$when.ppm" \ --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ "$BUILD/cosmos.bin" > "$BUILD/station.out" 2>&1 || true done -timeout 60 "$EMU" --fast --cycles 3000000 --keyboard "$BUILD/fly.keys" \ +timeout 60 "$EMU" --fast --cycles 3200000 --keyboard "$BUILD/fly.keys" \ --pad "$BUILD/hovernarrow.pad" --screen "$BUILD/stationnarrow.ppm" \ --disk "$ROOT/Tests/build/disks/cosmos.img" --ram-disk 2048 \ "$BUILD/cosmos.bin" > "$BUILD/stationnarrow.out" 2>&1 || true @@ -2366,11 +2372,11 @@ def station(path, band): if px[i * 3:i * 3 + 3] == blue and i // width < band] return len(at), (min(x for x, y in at) if at else -1) narrow = station('$BUILD/stationnarrow.ppm', 70) -seen, x3 = station('$BUILD/station3000000.ppm', 200) +seen, x3 = station('$BUILD/station3200000.ppm', 200) print(narrow[0], seen, - station('$BUILD/station2000000.ppm', 200)[1], x3, - station('$BUILD/station5000000.ppm', 200)[1], - station('$BUILD/station7000000.ppm', 200)[1]) + station('$BUILD/station2200000.ppm', 200)[1], x3, + station('$BUILD/station5200000.ppm', 200)[1], + station('$BUILD/station7200000.ppm', 200)[1]) " 2>/dev/null || echo "0 0 -1 -1 -1 -1") EOT # Forty pixels is the whole of the art, so none of it is clipped and it is the tile meant. @@ -2640,7 +2646,7 @@ open('$BUILD/crash.state','wb').write(struct.pack(' /dev/null -for when in 1200000 1600000 2200000; do +for when in 1400000 1800000 2400000; do timeout 60 "$EMU" --fast --cycles $when --keyboard "$BUILD/fly.keys" \ --pad "$BUILD/none.pad" --screen "$BUILD/burst$when.ppm" \ --disk "$BUILD/crash.img" --ram-disk 2048 \ @@ -2658,8 +2664,8 @@ def bits(path): return 0, 0 xs = [x for x, y in at] return len(at), max(xs) - min(xs) -e = bits('$BUILD/burst1200000.ppm'); l = bits('$BUILD/burst1600000.ppm') -print(e[0], e[1], l[0], l[1], bits('$BUILD/burst2200000.ppm')[0]) +e = bits('$BUILD/burst1400000.ppm'); l = bits('$BUILD/burst1800000.ppm') +print(e[0], e[1], l[0], l[1], bits('$BUILD/burst2400000.ppm')[0]) " 2>/dev/null || echo "0 0 0 0 -1") EOT # Forty pixels in one eight wide block is the lander; anything else in its colour is what is @@ -2671,7 +2677,7 @@ EOT && result ok "and they carry on outwards" "$EARLYWIDE pixels apart, then $LATEWIDE" \ || result no "and they carry on outwards" "$EARLYWIDE pixels apart, then $LATEWIDE" # And they are gone by the time the verdict is up, rather than hanging over the words. -[ "$AFTERBITS" = "0" ] && grep -q "Crashed" "$BUILD/burst2200000.out" \ +[ "$AFTERBITS" = "0" ] && grep -q "Crashed" "$BUILD/burst2400000.out" \ && 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" @@ -2933,7 +2939,7 @@ done # And a plain fall onto the pad it starts over, which lands rather than crashes. # 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 +for when in 3100000 3250000 3500000; 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 \ @@ -2953,8 +2959,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/dust2900000.ppm')[0], look('$BUILD/dust3050000.ppm')[0], - look('$BUILD/dust3300000.ppm')[0], +print(look('$BUILD/dust3100000.ppm')[0], look('$BUILD/dust3250000.ppm')[0], + look('$BUILD/dust3500000.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]) diff --git a/makefile b/makefile index af73302..6638a31 100644 --- a/makefile +++ b/makefile @@ -186,6 +186,21 @@ PROG_DEPS = $(BINARIES:.bin=.d) COSMOS = $(PROG_BUILD)/CosmOS/Source/cosmos.bin APPS = $(patsubst $(PROG_DIR)/CosmOS/Apps/%.asm,$(PROG_BUILD)/CosmOS/Apps/%.sbx,\ $(wildcard $(PROG_DIR)/CosmOS/Apps/*.asm)) + +# ---- Applications that have things of their own ---- +# +# A simple program is one file and lives in /Apps. One with ASSETS gets a directory of its +# own - /Packages/app. - and a launcher in /Apps under its bare name, which is what +# somebody actually types. The launcher is indirection: where the thing lives stops being +# part of its name, so an installer can put it elsewhere and change one line. +# +# Named here rather than found, because being packaged is a decision somebody makes and not +# something a directory listing can say. Both halves of the placing below are driven from +# this one list, so a name added here cannot end up half moved. +PACKAGED = Lander +# And what each of them owns beyond its own program. A tune named here is put in its +# package instead of at the root, where the tunes that belong to nobody in particular go. +LANDER_TUNES = splash COSMOS_DISK = $(PROG_BUILD)/cosmos.img # ---- A disk that is yours ---- @@ -585,7 +600,7 @@ $(COSMOS_DISK): makefile # # Found rather than wildcarded, because the tree the mirror walks is a tree and $(wildcard) # does not recurse. build is left out for the reason the mirror leaves it out. -MIRRORED := $(shell find $(PROG_DIR) \( -name '*.asm' -o -name '*.score' \) -not -path '$(PROG_BUILD)/*' 2>/dev/null) +MIRRORED := $(shell find $(PROG_DIR) \( -name '*.asm' -o -name '*.score' -o -path '*/Launchers/*' \) -not -path '$(PROG_BUILD)/*' 2>/dev/null) $(COSMOS_DISK): $(MIRRORED) $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \ $(PROG_DIR)/testPrograms/stringKeyword.asm \ @@ -624,6 +639,22 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \ $(DISKTOOL) mkdir $@ /Lib $(DISKTOOL) mkdir $@ /System $(DISKTOOL) mkdir $@ /System/Boot + @# ---- And what applications own ---- + @# + @# One directory per packaged application, so its files can be found together and + @# removed together - which is the question install and remove are both made of. NOT in + @# /Apps, for two reasons: a launcher and a directory of the same name cannot both be + @# there, and /Apps is the directory the shell walks for every word it does not know and + @# Tab walks for every first word, so doubling what is in it is a cost on the path that + @# runs most. + @# + @# The "app." in front rather than behind is what keeps them out of the way. Tab matches + @# the START of a name, so a prefix is a namespace and a suffix is a collision: with + @# Copy.app beside Copy.sbx, typing Copy and pressing Tab completes to the shared "Copy." + @# and hands you a broken word. + $(DISKTOOL) mkdir $@ /Packages + @for app in $(PACKAGED); do \ + $(DISKTOOL) mkdir $@ /Packages/app.$$app >/dev/null || exit 1; done @# The system itself, as a file, which is the whole of what boot.cfg chooses between. @# No boot.cfg is written: stage two falls back to this name when there is none, and a @# clean install having nothing to configure is the right default. @@ -631,8 +662,17 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \ @# What you run. /Apps is the second place the shell looks when a word it does not know @# turns out to be a program, so anything in here starts by name from anywhere. @for app in $(APPS); do \ - $(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done + name=`basename $$app .sbx`; \ + case " $(PACKAGED) " in \ + *" $$name "*) where=/Packages/app.$$name/$$name.sbx ;; \ + *) where=/Apps/$$name.sbx ;; \ + esac; \ + $(DISKTOOL) put $@ $$app $$where >/dev/null || exit 1; done $(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx + @# The launcher goes where the program used to be, under the bare name that is typed. + @for app in $(PACKAGED); do \ + $(DISKTOOL) put $@ $(PROG_DIR)/CosmOS/Launchers/$$app /Apps/$$app >/dev/null \ + || exit 1; done @# ---- And the music, compiled ---- @# @# A .score is written and a .tune is what the machine reads, the same way a .asm is @@ -656,7 +696,11 @@ $(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) \ name=`basename $$score .score`; \ $(TUNE_RUN) -I $(PROG_BUILD)/tunes $$score $(PROG_BUILD)/tunes/$$name.tune \ >/dev/null || exit 1; \ - $(DISKTOOL) put $@ $(PROG_BUILD)/tunes/$$name.tune $$name.tune >/dev/null \ + case " $(LANDER_TUNES) " in \ + *" $$name "*) where=/Packages/app.Lander/$$name.tune ;; \ + *) where=$$name.tune ;; \ + esac; \ + $(DISKTOOL) put $@ $(PROG_BUILD)/tunes/$$name.tune $$where >/dev/null \ || exit 1; done @# ---- What you assemble: all of it ---- @#