Files
SplitBit-Emulator/Tests/makedisks.sh
T
AnachronautandClaude Opus 5 a916103a7f Sprites: things that move without the screen moving
Everything drawn on this machine was in a cell. Something between two
cells meant rewriting both; something moving a pixel at a time meant
rewriting them sixty times a second, which is affordable for one thing
and not for twenty. A sprite is put at a pixel and the device draws it
over whatever is behind, so moving it costs two bytes.

MADE OF TILES, which is the decision the rest follows from: m by n taken
in reading order from one index, so there is no second pixel format, no
second kind of memory, and nothing a sprite can show that the map cannot.
A 16 by 16 character is four tiles and the background can name the same
four.

256 entries of 8 bytes at 0xC000 in the atlas - eight so the entry
address is a shift, the same no-multiply argument as the palette's four.
Position is signed and sixteen bits, because 640 by 400 does not fit in a
byte and a sprite has to be able to sit half off the left rather than
appearing whole at the edge.

A PIXEL OF ZERO IS NOT DRAWN, or every sprite is a rectangle. Tested
before the attribute is added, so a hole belongs to the art and not to
the colour scheme. The same rule the other way round is what "behind"
means: drawn only where the background pixel was zero, so a thing walks
behind a pillar and in front of the floor in one frame.

All of them draw, every frame, so they cannot flicker. Real machines
dropped them per scanline because they had a fixed number of shift
registers; this has a loop. The limit is the size of the table, which is
a constant rather than a property of what is on screen.

And the system takes them down at exit. The sprite table sits in the gap
the screen save walks around - to the end of the map, then the palette -
and that is right, because nothing the shell draws is a sprite: there is
nothing to give back, only something to take away. Otherwise a program
that put a ball up and left would leave it over the prompt, in front of
everything, with nothing able to type it away. Sprite.asm deliberately
leaves its own, because a program that faulted could not have cleared it.

Every check here was re-broken and failed: transparency, reading order,
draw order, priority, and size. Size needed breaking twice - the first
attempt did not compile, and a silent build failure had left the old
binary passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-02 11:42:11 -04:00

722 lines
42 KiB
Bash
Executable File

#!/usr/bin/env bash
# Builds the disk images the tests read from.
#
# These are made with SplitDisk, which is the other implementation of the same format.
# That is the point of them: a SplitBit program reading one of these is being checked
# against something written by different code from a written specification, rather than
# against itself.
#
# Written by Anachronaut
set -eu
# Made absolute before anything else. This script cds into its own working directory part
# of the way down, so a relative build path would be measured from the wrong place from
# there on - and the way that failed was not an error but a disk quietly missing some of
# the files it was supposed to have, which is a much worse thing to debug.
mkdir -p "$1"
BUILD="$(cd "$1" && pwd)"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TOOL="$ROOT/SplitDisk"
DISKS="$BUILD/disks"
WORK="$BUILD/.diskwork"
[ -x "$TOOL" ] || { echo "SplitDisk is not built."; exit 1; }
mkdir -p "$DISKS" "$WORK"
# Two directory blocks, so that a file can be put beyond the first one and the walk from
# block to block gets exercised rather than assumed.
"$TOOL" format "$DISKS/sbfs.img" 64 2 >/dev/null
cd "$WORK"
printf 'hello from a file' > greeting.txt
# Eight files fill the first directory block exactly, so everything after this lands in
# the second one.
for i in 1 2 3 4 5 6 7 8; do printf 'filler %d' "$i" > "filler$i.txt"; done
# Longer than a block, so reading it has to cross from one to the next. The pattern
# repeats every twenty six bytes, which makes a misplaced block obvious to read.
awk 'BEGIN { for (i = 0; i < 700; i++) printf "%c", 65 + (i % 26) }' > across.txt
: > empty.txt
printf 'exactly twenty two!!!!' > longname.txt
"$TOOL" put "$DISKS/sbfs.img" greeting.txt >/dev/null
for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/null; done
"$TOOL" put "$DISKS/sbfs.img" across.txt >/dev/null
"$TOOL" put "$DISKS/sbfs.img" empty.txt >/dev/null
"$TOOL" put "$DISKS/sbfs.img" longname.txt aName22CharactersLong! >/dev/null
# A disk with a loadable program on it. The program is assembled here rather than kept as
# bytes, so that what gets loaded is always built from the source beside it.
"$TOOL" format "$DISKS/load.img" 64 2 >/dev/null
# The assembler writes a loadable program itself, because the source says where it goes.
"$ROOT/Assembler" "$ROOT/Programs/Loader/loadable.asm" -o "$WORK/hello.sbx" >/dev/null
"$TOOL" put "$DISKS/load.img" "$WORK/hello.sbx" >/dev/null
# A disk for CosmOS. greet.sbx asks the system for everything it does rather than talking
# to the hardware itself, so loading and running it exercises the whole path: the loader,
# the vector table, the service handlers, and giving the machine back at the end.
#
# hello.sbx is the opposite case, and that is why it is here: it is the original
# hello.asm, written before any of this existed, and it still writes straight to port
# 0x00 rather than calling osPrintString. A program is allowed to reach past the system
# to the hardware, so something has to check that one still gives the machine back.
#
# notes.txt is there so that loading something that is not a program can be tried too.
# 512K, and four directory blocks for 32 files. It was 64K and 16, which was ample for a
# disk that held programs and nothing else - but the native assembler reads SOURCE from
# here, and the CosmOS sources alone are 104,142 bytes against the 65,536 a 256 block disk
# holds. A machine that is going to assemble itself has to be able to hold its own source.
"$TOOL" format "$DISKS/cosmos.img" 2048 4 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
# Assembled to a different working name so it cannot tread on load.img's hello.sbx above,
# then put under the name the shell asks for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/appHello.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/appHello.sbx" hello.sbx >/dev/null
# Life.sbx is the one that had to be taught to stop. It runs to a still life and returns
# on its own, so the test needs no cycle limit: whether it ends is the thing being checked
# and a limit would hide the answer by supplying one.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Life.asm" -o "$WORK/Life.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Life.sbx" >/dev/null
# Snake.sbx is the one that is played rather than watched. It reads the console a key at a
# time without ever waiting for one, so a script of moves drives it a move to a frame, and
# what is recorded is a whole game: turning, eating, growing, and running into a wall.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Snake.asm" -o "$WORK/Snake.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Snake.sbx" >/dev/null
# Keys.sbx brings a vector of its own, which is what the version two format exists for. It
# is the only program here the system has to install anything for, so it is what says the
# whole path works: written into the file, installed at run, taken back out at exit.
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Keys.asm" -o "$WORK/Keys.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Keys.sbx" >/dev/null
# Say.sbx is the first program that can be told anything. Everything before it did the same
# thing however it was started.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null
# Break.sbx stops itself twice and shows what the registers were each time. It needs no disk
# of its own: it only prints.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Break.asm" -o "$WORK/Break.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Break.sbx" >/dev/null
# Grid.sbx is the first program that uses the screen as a screen rather than as somewhere to
# put letters: it redefines a tile, fills a map bigger than the display, and scrolls it by
# moving the origin. Tests/video.sh boots this disk and looks at the pixels.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Grid.asm" -o "$WORK/Grid.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Grid.sbx" >/dev/null
# Press.sbx says what the console handed it, which is how the keys that are not characters
# are checked at all. It reads a line first and then keys, because line mode drops them and
# key mode delivers them and both halves of that rule want testing.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Press.asm" -o "$WORK/Press.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Press.sbx" >/dev/null
# Mode.sbx has NO DATA SEGMENT, which is the whole reason it is here. Nothing else on this
# disk is that shape, and the loader used to stop the machine dead on it: a segment of no
# bytes asks the memory controller for the whole 64K, which does not fit, which it refuses.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Mode.asm" -o "$WORK/Mode.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Mode.sbx" >/dev/null
# Flip.sbx draws a whole screen into the bank that is not being shown, waits, and then shows
# it. It is the only program here that leaves the machine displaying the OTHER screen, which
# is what makes it the thing that checks the system puts that back.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Flip.asm" -o "$WORK/Flip.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Flip.sbx" >/dev/null
# Sprite.sbx moves a ball across the shell's own text without writing a byte of the map, and
# leaves the sprite in the table on the way out - so it is what says the system clears them.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Sprite.asm" -o "$WORK/Sprite.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Sprite.sbx" >/dev/null
# Crash.sbx breaks in each of the four ways the system now catches, which is the only way to
# reach a fault screen from a recorded test.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Crash.asm" -o "$WORK/Crash.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Crash.sbx" >/dev/null
# A script that names something nothing was ever set to. Stop on failure is what says the
# expansion really did fail rather than merely complaining: the line after it must not run.
printf '#! script\necho before\necho $nosuchname\necho after\n' > vars.script
# A script with blocks in it, because that is where they are actually for. It nests, it
# takes the second branch of an outer if, and the branch nobody takes mentions a name that
# was never set - which must not be an error, because nothing there is being run.
printf '#! script\nset colour red\nif same $colour red\n echo it is red\n if same $colour blue\n echo and blue\n else\n echo but not blue\n end\nelse\n echo $neverSetAnywhere\nend\nif same $colour blue\n echo wrong\nelse\n echo right\nend\nif same $colour blue\n if same $colour red\n echo deep wrong\n else\n echo deep also wrong\n end\nend\necho done\n' > blocks.script
"$TOOL" put "$DISKS/cosmos.img" vars.script >/dev/null
"$TOOL" put "$DISKS/cosmos.img" blocks.script >/dev/null
# Loops, which are what a block is for once there is more than one way out of it. Nested, and
# with an if inside one and a loop inside a branch nobody takes - which must run no times at
# all rather than once.
printf '#! script\nfor a in 1 2\n for b in x y\n echo $a$b\n end\nend\nset n go\nwhile same $n go\n echo round\n set n stop\nend\nfor c in p q\n if same $c p\n echo only p\n end\nend\nif same 1 2\n for d in never\n echo not this\n end\nend\nfor e in\n echo no words\nend\necho finished\n' > loops.script
"$TOOL" put "$DISKS/cosmos.img" loops.script >/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 screen's, so that it has a beat to play to - which makes it the
# second thing here that the version two format exists for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/Examples/tune.asm" -o "$WORK/tune.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/tune.sbx" >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
# ---- A second disk, for the second drive ----
#
# Deliberately nothing like the first: its own file and its own directory, so that a test
# looking at one cannot be looking at the other by accident. Small, because what is on it
# matters and how much does not.
"$TOOL" format "$DISKS/other.img" 512 4 >/dev/null
printf 'this lives on the other disk' > other.txt
"$TOOL" put "$DISKS/other.img" other.txt >/dev/null
"$TOOL" mkdir "$DISKS/other.img" /notes >/dev/null
# A name that begins with a digit, because a drive prefix is a digit and a colon and the
# colon is the whole of what tells them apart. Without it this would be drive 2.
"$TOOL" mkdir "$DISKS/other.img" /2things >/dev/null
# A file of more than one block, because a cross-drive copy of a single block never looks
# the path up twice and so never meets the cache that used to hand back the right blocks of
# the wrong disk.
python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20)"
"$TOOL" put "$DISKS/other.img" twoblocks.txt >/dev/null
# ---- And a /Apps on drive 0, because that is where a system keeps its programs ----
#
# The apps above sit at the root of this image, which is where they were put before there
# were directories and is fine for finding a program on the disk you are standing on. The
# THIRD place the shell looks is /Apps on drive 0, and nothing could exercise it while this
# disk had no such place.
"$TOOL" mkdir "$DISKS/cosmos.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null
"$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
# ---- Scripts, including the ones that are meant to go wrong ----
#
# Built here rather than committed, because two of them are about BYTE POSITIONS and a
# committed file is one an editor can helpfully repair. cross.script puts a command across
# the 256 byte boundary between two blocks, and nonl.script ends without a newline; both
# were real faults before they were checks, and both are invisible in a text editor.
printf '#! script\n; A comment, which never reaches the shell.\necho saying what it is doing\nSay from a script\n\necho\nSay and again\n' > hi.script
"$TOOL" put "$DISKS/cosmos.img" hi.script >/dev/null
# Stops at the bad line, so "after" must not appear.
printf '#! script\nSay before\nnosuchcommand\nSay after\n' > bad.script
"$TOOL" put "$DISKS/cosmos.img" bad.script >/dev/null
# No #! on the front, so it is a text file and not a script.
printf 'Say this has no shebang\n' > plain.script
"$TOOL" put "$DISKS/cosmos.img" plain.script >/dev/null
# A command that starts in the first block and finishes in the second.
python3 -c "
head = '#! script\n'
pad = ''
while len(head) + len(pad) < 246:
pad += '; pad\n'
open('cross.script','w').write(head + pad + 'Say across the block boundary\n')
"
"$TOOL" put "$DISKS/cosmos.img" cross.script >/dev/null
# The last line has no newline after it and still has to run.
printf '#! script\nSay with no newline after me' > nonl.script
"$TOOL" put "$DISKS/cosmos.img" nonl.script >/dev/null
# ---- One script inside another ----
#
# outer.script resumes in its SECOND block, which is the case the whole design turns on: the
# inner script reads its own block into the one buffer there is, so coming back means reading
# the outer one's block again and landing on the byte it left off at. Generated, because
# where the do line falls is the entire point and no editor should be able to move it.
python3 -c "
head = '#! script\n'
pad = ''
while len(head) + len(pad) < 300:
pad += '; pad\n'
open('outer.script','w').write(head + pad +
'echo outer in block one\ndo inner.script\necho outer resumed in block one\n')
"
"$TOOL" put "$DISKS/cosmos.img" outer.script >/dev/null
printf '#! script\necho inner one\necho inner two\n' > inner.script
"$TOOL" put "$DISKS/cosmos.img" inner.script >/dev/null
# A script that runs itself, which is what the depth limit is for.
printf '#! script\necho self\ndo loop.script\n' > loop.script
"$TOOL" put "$DISKS/cosmos.img" loop.script >/dev/null
# ---- A disk that starts itself ----
#
# Its own image, and that is the point rather than an inconvenience: a startup script runs on
# every boot, so putting one on the disk the other twenty tests use would prepend it to all of
# them. This is that disk with /System/Boot/startup.sh added.
cp "$DISKS/cosmos.img" "$DISKS/startup.img"
"$TOOL" mkdir "$DISKS/startup.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/startup.img" /System/Boot >/dev/null
# It ENDS QUIET on purpose. Nothing puts the flag back when the outermost script finishes -
# there is no caller to restore it from - so a script ending quiet is the case that catches a
# shell left with no prompt for whoever types next. Ending #loud tests nothing.
printf '#! script\n#quiet\necho Segan Voyager\necho CosmOS ready.\n#loud\necho and loud again\n#quiet\necho and quiet again at the end\n' > startup.sh
"$TOOL" put "$DISKS/startup.img" startup.sh /System/Boot/startup.sh >/dev/null
# The same disk with something at that name that is not a script, which must say so rather
# than run it or ignore it.
cp "$DISKS/cosmos.img" "$DISKS/startupbad.img"
"$TOOL" mkdir "$DISKS/startupbad.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/startupbad.img" /System/Boot >/dev/null
printf 'echo I have no shebang\n' > startupbad.sh
"$TOOL" put "$DISKS/startupbad.img" startupbad.sh /System/Boot/startup.sh >/dev/null
# A disk of its own for the writing test, with one file already on it so that what it
# writes has to be placed somewhere that does not tread on what is there.
"$TOOL" format "$DISKS/write.img" 32 1 >/dev/null
printf 'already here' > here.txt
"$TOOL" put "$DISKS/write.img" here.txt >/dev/null
# A disk of its own for the editing test, which deletes and renames things and would
# otherwise leave the writing test's disk looking nothing like the writing test expects.
# It starts with one file on it so that a document written here has to be placed around
# something, and so that the disk can be compared afterwards against one that never had
# any of it: the metadata should come back exactly as it was.
"$TOOL" format "$DISKS/edit.img" 32 1 >/dev/null
"$TOOL" put "$DISKS/edit.img" here.txt >/dev/null
# A disk for the shell's delete and rename, which is its own because those change what is
# on it. A fixture whose name has a directory in it is used as it stands rather than being
# made fresh per test, so a test that writes to a shared one would quietly change what
# every test after it sees.
"$TOOL" format "$DISKS/files.img" 32 1 >/dev/null
printf 'the first one' > one.txt
printf 'the second one' > two.txt
"$TOOL" put "$DISKS/files.img" one.txt >/dev/null
"$TOOL" put "$DISKS/files.img" two.txt >/dev/null
# A disk for the editor, holding nothing but the editor. Its own, because the whole point
# of it is that it writes: a document made on a shared fixture would turn up in the file
# listing of every test that came after it.
"$TOOL" format "$DISKS/editor.img" 256 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Edit.asm" -o "$WORK/Edit.sbx" >/dev/null
"$TOOL" put "$DISKS/editor.img" "$WORK/Edit.sbx" >/dev/null
# A disk that has directories on it AND gets written to, which nothing else covers: every
# other version two fixture is read only, and every fixture that is written to is flat.
# Making a file runs the allocator over a directory entry, and a directory has no blocks,
# so it must be in nobody's way while a run of free ones is looked for. Its own disk, for
# the same reason the editor has one: what it writes would otherwise turn up in the
# listing of every test that came after it.
"$TOOL" format "$DISKS/treewrite.img" 256 2 >/dev/null
"$TOOL" mkdir "$DISKS/treewrite.img" /Folder >/dev/null
"$TOOL" mkdir "$DISKS/treewrite.img" /Folder/Inner >/dev/null
"$TOOL" put "$DISKS/treewrite.img" "$WORK/Edit.sbx" >/dev/null
# A disk for the file services, holding nothing but the program that exercises them. Its
# own, because that program writes: it tidies up after itself, but a run that stopped part
# way would leave a document behind on a fixture every later test reads.
"$TOOL" format "$DISKS/services.img" 256 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null
"$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null
# A disk for streaming, and the only one here that has to be big: the file on it is bigger
# than the machine's Data Memory, which is the entire point of the services it checks.
#
# THE FILE IS GENERATED RATHER THAN TAKEN FROM THE REPOSITORY. Concatenating the CosmOS
# sources would be a truer picture of what streaming is for, and would change the recorded
# checksum every time a line of CosmOS was edited - so a real difference would arrive in a
# crowd of meaningless ones, which is the same trap the cycle counts used to set. This is
# 84000 bytes, which is 328 whole blocks and 32 bytes over, so the short block at the end
# is exercised rather than assumed.
"$TOOL" format "$DISKS/stream.img" 1024 2 >/dev/null
awk 'BEGIN { for (i = 0; i < 4000; i++) printf "streaming line %05d\n", i }' > big.txt
awk 'BEGIN { for (i = 0; i < 50; i++) printf "small %05d\n", i }' > small.txt
"$TOOL" put "$DISKS/stream.img" big.txt >/dev/null
"$TOOL" put "$DISKS/stream.img" small.txt >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Stream.asm" -o "$WORK/Stream.sbx" >/dev/null
"$TOOL" put "$DISKS/stream.img" "$WORK/Stream.sbx" >/dev/null
# A disk for Type. The first file crosses several blocks and deliberately has no zero byte
# for the application to mistake for an end marker. The second proves that zero blocks is
# a valid empty file rather than an error.
"$TOOL" format "$DISKS/type.img" 64 2 >/dev/null
printf 'first line\nsecond line\n' > readable.txt
awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", i }' >> readable.txt
: > empty.txt
"$TOOL" put "$DISKS/type.img" readable.txt >/dev/null
"$TOOL" put "$DISKS/type.img" empty.txt >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/Type.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null
# A disk for Copy and Compare. The files mark all three shapes a streamed tool has to
# distinguish: no blocks, an exact whole block, and a part block. The large pair says the
# programs do not quietly depend on Data Memory being able to hold either input, and they
# differ at one byte while keeping the same length so Compare has to inspect content rather
# than stopping at the descriptor.
"$TOOL" format "$DISKS/copycompare.img" 1024 4 >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Input >/dev/null
"$TOOL" mkdir "$DISKS/copycompare.img" /Output >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Compare.asm" -o "$WORK/Compare.sbx" >/dev/null
"$TOOL" put "$DISKS/copycompare.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null
"$TOOL" put "$DISKS/copycompare.img" "$WORK/Compare.sbx" /Apps/Compare.sbx >/dev/null
: > copy-empty.dat
awk 'BEGIN { for (i = 0; i < 256; i++) printf "%c", 65 + (i % 26) }' > copy-exact.dat
awk 'BEGIN { for (i = 0; i < 257; i++) printf "%c", 65 + (i % 26) }' > copy-tail.dat
awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", 65 + (i % 26) }' > copy-large.dat
awk 'BEGIN { for (i = 0; i < 84000; i++) printf "%c", (i == 65535 ? 33 : 65 + (i % 26)) }' \
> copy-different.dat
"$TOOL" put "$DISKS/copycompare.img" copy-empty.dat /Input/empty.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-exact.dat /Input/exact.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-tail.dat /Input/tail.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-large.dat /Input/large.dat >/dev/null
"$TOOL" put "$DISKS/copycompare.img" copy-different.dat /Input/different.dat >/dev/null
# A disk with directories on it, which is the whole of what version two adds. Built by the
# host tool, because at this rung the machine can read a tree and not yet make one - and
# that split is the point: the two implementations are checked against each other rather
# than each against itself.
#
# ONE NAME IS PUT DOWN TWICE, in two directories, because a name meaning something
# different in each place is what directories are for and what a flat filesystem cannot do.
#
# The two are DIFFERENT PROGRAMS on purpose. Say echoes whatever it is given and hello
# prints one fixed line, so which of them ran is written in the output. Two copies of one
# program would have been the easier fixture and a useless one: resolving to the wrong
# Say.sbx would have printed exactly what resolving to the right one prints, and the test
# would have passed while the path walk was ignoring directories entirely.
"$TOOL" format "$DISKS/tree.img" 256 4 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/hello.asm" -o "$WORK/treeHello.sbx" >/dev/null
"$TOOL" mkdir "$DISKS/tree.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/tree.img" /Apps/Deep >/dev/null
"$TOOL" put "$DISKS/tree.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
"$TOOL" put "$DISKS/tree.img" "$WORK/treeHello.sbx" /Apps/Deep/Say.sbx >/dev/null
printf 'this is not a program' > rooted.txt
"$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null
# A disk for the program that tries to commit a file bigger than it reserved. Its own,
# because what it leaves behind is a file whose size is the thing being checked.
"$TOOL" format "$DISKS/claim.img" 64 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Claim.asm" -o "$WORK/Claim.sbx" >/dev/null
"$TOOL" put "$DISKS/claim.img" "$WORK/Claim.sbx" >/dev/null
# A blank disk for the machine to build a tree on itself. It starts as a version ONE disk
# with nothing at all on it, because half of what this checks is that making the first
# directory raises the version - the number says what is on a disk rather than what made
# it, so a disk with no directories is flat whoever formatted it.
"$TOOL" format "$DISKS/build.img" 128 2 >/dev/null
# A disk for moving about on. Two directories hold a file of THE SAME NAME with different
# text in it, which is the fixture the working directory needs: "notes.txt" has to mean a
# different file from each of them, and the only way to see that it does is for the two to
# say different things.
#
# It is also what checks the remembered file is dropped when the machine moves. That cache
# is keyed on the path as somebody typed it, so "notes.txt" is the same key in both places
# and nothing about the entry it remembers looks wrong - it is the kind of stale that is
# believed rather than noticed.
#
# Type and Say go in /Apps, so that a program can be started from anywhere; hello goes in
# /A under the name Say.sbx, so that where you are is visibly tried before /Apps.
"$TOOL" format "$DISKS/cwd.img" 256 4 >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /Apps >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /A >/dev/null
"$TOOL" mkdir "$DISKS/cwd.img" /B >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Type.sbx" /Apps/Type.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/treeHello.sbx" /A/Say.sbx >/dev/null
printf 'these are the notes in A\n' > notesA.txt
printf 'and these are the very different notes in B\n' > notesB.txt
"$TOOL" put "$DISKS/cwd.img" notesA.txt /A/notes.txt >/dev/null
"$TOOL" put "$DISKS/cwd.img" notesB.txt /B/notes.txt >/dev/null
# Wander is the only thing that can move the machine from inside a program, which makes it
# the only thing that can check the shell puts the working directory back afterwards.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Wander.asm" -o "$WORK/Wander.sbx" >/dev/null
"$TOOL" put "$DISKS/cwd.img" "$WORK/Wander.sbx" /Apps/Wander.sbx >/dev/null
# A disk for invoking a program by typing its name. Four files, each there to say one
# thing about how a typed word turns into a file name.
#
# dir.sbx is a working program under the name of a built-in command, which is the only way
# to check that the built-ins really are tried first. If the search ever moved ahead of the
# dispatch chain, this disk would start answering "dir" with a program.
#
# notes.sbx is text under a program's name, so that a file that IS found and IS NOT a
# program can be told apart from a word that names nothing. notes.txt is the same text
# under its own name, which no typed word can reach: "notes.txt" looks for notes.txt.sbx.
"$TOOL" format "$DISKS/invoke.img" 64 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" dir.sbx >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/invoke.img" notes.txt >/dev/null
"$TOOL" put "$DISKS/invoke.img" notes.txt notes.sbx >/dev/null
# A disk for the assembler that runs on the machine. It holds a source file and the two
# programs that check the front end - the reader on its own and the tokenizer on its own -
# because a fault in either of those would otherwise turn up much later as a mysterious
# wrong byte in an output file.
#
# hello.asm is here rather than something written for the occasion because it is the
# oldest program in the repository: the first thing this machine ever ran is the first
# thing it assembles for itself.
"$TOOL" format "$DISKS/asm.img" 2048 4 >/dev/null
cp "$ROOT/Programs/Examples/hello.asm" hello.asm
"$TOOL" put "$DISKS/asm.img" hello.asm >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
"$ROOT/Programs/CosmOS/Assembler/readTest.asm" -o "$WORK/readTest.sbx" >/dev/null
"$TOOL" put "$DISKS/asm.img" "$WORK/readTest.sbx" >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
"$ROOT/Programs/CosmOS/Assembler/tokenTest.asm" -o "$WORK/tokenTest.sbx" >/dev/null
"$TOOL" put "$DISKS/asm.img" "$WORK/tokenTest.sbx" >/dev/null
# A tree deeper than the prompt can print, for the buffer that used to walk off the front
# of itself. Eight directories of twenty two characters is 184 characters of path, and the
# prompt is built backwards into 127 bytes - so before this was bounded, the walk wrote
# down past the start of the buffer and over the shell's own command names. The word
# "exit" was the first casualty, five bytes below.
#
# MADE HERE RATHER THAN BY THE TEST, because a path this long cannot be given to mkdir in
# one piece: naming it is capped well below what walking into it a level at a time can
# reach, which is exactly why the depth is unbounded in the first place.
"$TOOL" format "$DISKS/deep.img" 512 8 >/dev/null
DEEPPATH=""
for i in 1 2 3 4 5 6 7 8; do
DEEPPATH="$DEEPPATH/abcdefghijklmnopqrst0$i"
"$TOOL" mkdir "$DISKS/deep.img" "$DEEPPATH" >/dev/null
done
# A disk whose superblock claims a directory bigger than the parent field can name. Entry
# 65535 has no parent number - index plus one wraps to zero, which is the root - so
# anything created inside it lands in the root instead while the tool reports success.
#
# THE CLAIM IS ALL THAT IS NEEDED, so this is a small disk that lies rather than a sixteen
# megabyte one that tells the truth. Mounting reads block 0 and gets as far as the
# geometry, which is where it is refused. SplitDisk will not make one of these any more,
# so the superblock is written by hand.
"$TOOL" format "$DISKS/bigdir.img" 64 2 >/dev/null
printf '\x20\x00' | dd of="$DISKS/bigdir.img" bs=1 seek=10 conv=notrunc status=none
# A disk that can be started: stage one reads the live boot slot into Program Memory and
# jumps into it, and each slot holds a different payload so that choosing between them is
# visible rather than assumed.
#
# The payload is written RAW. A loadable program carries a sixteen byte header saying where
# its pieces go, and stage one does not read headers - it reads blocks, which is the whole
# point of keeping it small enough to put in a ROM.
"$TOOL" format "$DISKS/chain.img" 256 4 4 >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/slotTest.asm" -o "$WORK/slotTest.sbx" >/dev/null
tail -c +17 "$WORK/slotTest.sbx" > "$WORK/slotTest.raw"
"$TOOL" boot "$DISKS/chain.img" "$WORK/slotTest.raw" 0 >/dev/null
# The other slot says something else, so a test that reads "booted" is reading slot zero
# rather than reading whatever happens to be in Program Memory.
sed 's/INIA 0x62 ; "b"/INIA 0x6F ; "o"/; s/INIA 0x74 ; "t"/INIA 0x68 ; "h"/' \
"$ROOT/Programs/Boot/slotTest.asm" > "$WORK/otherSlot.asm"
"$ROOT/Assembler" "$WORK/otherSlot.asm" -o "$WORK/otherSlot.sbx" >/dev/null
tail -c +17 "$WORK/otherSlot.sbx" > "$WORK/otherSlot.raw"
"$TOOL" boot "$DISKS/chain.img" "$WORK/otherSlot.raw" 1 >/dev/null
# And the same disk with the other slot chosen, so both are exercised.
cp "$DISKS/chain.img" "$DISKS/chainAlt.img"
"$TOOL" bootslot "$DISKS/chainAlt.img" 1 >/dev/null
# And a slot whose payload has a Data Segment of its own. Stage one places Program Memory
# and nothing else, so the payload copies its data down from just past its own code - which
# is the mechanism the real second stage will need, since sbfs.asm has variables and a
# string it compares against.
"$TOOL" format "$DISKS/chainData.img" 256 4 4 >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/slotData.asm" -o "$WORK/slotData.sbx" >/dev/null
tail -c +17 "$WORK/slotData.sbx" > "$WORK/slotData.raw"
"$TOOL" boot "$DISKS/chainData.img" "$WORK/slotData.raw" 0 >/dev/null
# ---- A disk the machine can start itself from ----
#
# Stage one in the emulator's hands, stage two in a boot slot, and the system as an
# ordinary file. Nothing here is a boot-specific format: /System/cosmos.bin is the same
# SPBT image the emulator has always been handed directly, which is what makes a program
# that wants no operating system startable the same way.
# ---- Forty blocks a slot, the same as a disk that ships ----
#
# It was thirty-two, which was ten thousand bytes of headroom when stage two was four
# thousand bytes and none at all when it reached 8,234. A fixture tighter than the thing it
# stands in for is a fixture that fails on a change the real disk would have taken, and the
# failure says "the boot slot is too small" rather than what actually grew.
"$TOOL" format "$DISKS/selfboot.img" 512 4 40 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/Boot/stage2.asm" -o "$WORK/stage2.sbx" >/dev/null
tail -c +17 "$WORK/stage2.sbx" > "$WORK/stage2.raw"
"$TOOL" boot "$DISKS/selfboot.img" "$WORK/stage2.raw" 0 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Source/cosmos.asm" -o "$WORK/bootCosmos.bin" >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/Boot/bare.asm" -o "$WORK/bare.bin" >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /System >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /System/Boot >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootCosmos.bin" /System/Boot/cosmos.bin >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bare.bin" /System/Boot/bare.bin >/dev/null
"$TOOL" mkdir "$DISKS/selfboot.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/bootFiles.sbx" >/dev/null
"$TOOL" put "$DISKS/selfboot.img" "$WORK/bootFiles.sbx" /Apps/Files.sbx >/dev/null
# And one with no system on it, so that a second stage which cannot find what to start
# says so rather than jumping somewhere.
"$TOOL" format "$DISKS/nosystem.img" 512 4 40 >/dev/null
"$TOOL" boot "$DISKS/nosystem.img" "$WORK/stage2.raw" 0 >/dev/null
# ---- Configuration choosing what starts ----
#
# The same disk three ways, differing only in /System/Boot/boot.cfg. Nothing else about
# any of them changes, which is what makes these tests of the FILE rather than of the
# machinery under it.
for variant in bare broken fallback; do
cp "$DISKS/selfboot.img" "$DISKS/cfg$variant.img"
done
# A bare metal image, which is the whole point of loading an ordinary boot image: a program
# wanting no operating system is a file like any other.
printf 'system /System/Boot/bare.bin\n' > "$WORK/bare.cfg"
"$TOOL" put "$DISKS/cfgbare.img" "$WORK/bare.cfg" /System/Boot/boot.cfg >/dev/null
# Every kind of mistake at once, and a good setting underneath them. The machine still
# starts, and says what it could not use on the way.
{
printf '; a file with things wrong in it\n'
printf 'fallbcak /System/Boot/cosmos.bin\n'
printf 'system\n'
printf 'system /System/Boot/bare.bin\n'
} > "$WORK/broken.cfg"
"$TOOL" put "$DISKS/cfgbroken.img" "$WORK/broken.cfg" /System/Boot/boot.cfg >/dev/null
# A system that is not there, and something to fall back to.
{
printf 'system /System/Boot/missing.bin\n'
printf 'fallback /System/Boot/cosmos.bin\n'
} > "$WORK/fallback.cfg"
"$TOOL" put "$DISKS/cfgfallback.img" "$WORK/fallback.cfg" /System/Boot/boot.cfg >/dev/null
# ---- A system that never reaches a prompt ----
#
# The case the boot state exists for. Pointing the configuration at something that dies
# before the shell is, without a mark on the disk, a machine that can never be told
# anything again - so the loader marks it before handing over and the system clears the
# mark on arrival, which makes "did not arrive" a thing the next start can see.
"$ROOT/Assembler" "$ROOT/Programs/Boot/wedged.asm" -o "$WORK/wedged.bin" >/dev/null
for stage in first second third; do
cp "$DISKS/selfboot.img" "$DISKS/wedge$stage.img"
"$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null
{
printf 'system /System/Boot/wedged.bin\n'
printf 'fallback /System/Boot/cosmos.bin\n'
} > "$WORK/wedge.cfg"
"$TOOL" put "$DISKS/wedge$stage.img" "$WORK/wedge.cfg" /System/Boot/boot.cfg >/dev/null
done
# Each disk begins where the one before it ended, so the three tests read as three
# consecutive starts of one machine without any of them depending on the others running.
"$TOOL" bootstate "$DISKS/wedgesecond.img" 1 >/dev/null
"$TOOL" bootstate "$DISKS/wedgethird.img" 2 >/dev/null
# ---- Settling from the machine itself ----
#
# The loop the boot state opens has to be closeable from inside: a machine that says
# "settle it to try again" and gives you no way to do so has told you about a problem it
# will not let you fix. Settle is a PROGRAM rather than a shell word, which is the shape
# this system is growing into - one job each, reached through SWI, replaceable.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Settle.asm" -o "$WORK/Settle.sbx" >/dev/null
cp "$DISKS/wedgethird.img" "$DISKS/settle.img"
"$TOOL" put "$DISKS/settle.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null
# And one already settled, so that saying so is checked as well as doing it.
cp "$DISKS/selfboot.img" "$DISKS/settled.img"
"$TOOL" put "$DISKS/settled.img" "$WORK/Settle.sbx" /Apps/Settle.sbx >/dev/null
# A configuration naming something broken with NOTHING to fall back to. The mark would
# otherwise be the only thing between the machine and its own configuration, and refusing
# on the strength of it turns "the last start failed" into "no start is permitted".
cp "$DISKS/selfboot.img" "$DISKS/nofallback.img"
"$TOOL" put "$DISKS/nofallback.img" "$WORK/wedged.bin" /System/Boot/wedged.bin >/dev/null
printf 'system /System/Boot/cosmos.bin\n' > "$WORK/lonely.cfg"
"$TOOL" put "$DISKS/nofallback.img" "$WORK/lonely.cfg" /System/Boot/boot.cfg >/dev/null
"$TOOL" bootstate "$DISKS/nofallback.img" 1 >/dev/null
# ---- What a program made of it ----
#
# A status is only worth having if it survives the program that set it, so this runs three
# in a row and asks after each: one that works, one that is asked wrongly, and one that
# fails outright. Status is a PROGRAM and reaches the number through a service, which is
# what will let something other than a person read it.
"$TOOL" format "$DISKS/status.img" 512 4 >/dev/null
"$TOOL" mkdir "$DISKS/status.img" /Apps >/dev/null
for app in Status Say Type Settle; do
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null
"$TOOL" put "$DISKS/status.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null
done
# ---- A file with lines longer than the editor's buffer ----
#
# Edit copied a file into an 81 byte line buffer with NO BOUND, and the buffer is followed
# in memory by the head of the document and the pointer its allocator hands out. A source
# file with 94 character lines wrote characters over both: a 31 line file opened as 3, and
# opening it a second time walked a list that led back into itself for ever.
#
# Typing was always safe - osReadLine is told how much room there is - so a new document
# behaved and a source file did not, which is exactly how it was found.
"$TOOL" format "$DISKS/editlong.img" 512 4 >/dev/null
"$TOOL" mkdir "$DISKS/editlong.img" /Apps >/dev/null
for app in Edit Status; do
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/$app.asm" -o "$WORK/$app.sbx" >/dev/null
"$TOOL" put "$DISKS/editlong.img" "$WORK/$app.sbx" /Apps/$app.sbx >/dev/null
done
"$TOOL" put "$DISKS/editlong.img" "$ROOT/Programs/CosmOS/Apps/hello.asm" /hello.asm >/dev/null
# And one past what it can hold, which must be refused rather than shortened: a line this
# cut would be written back cut, and the file damaged by having been looked at.
{ printf 'short line\n'; printf 'x%.0s' $(seq 1 200); printf '\nanother short one\n'; } \
> "$WORK/toolong.txt"
"$TOOL" put "$DISKS/editlong.img" "$WORK/toolong.txt" /long.txt >/dev/null
# ---- Starting something else just this once ----
#
# A program that owns the whole machine has nowhere to run: it cannot be started from the
# shell, because starting it means there is no shell, and pointing boot.cfg at it means a
# machine that keeps starting it. Once writes a request the loader reads before boot.cfg
# and deletes before it jumps.
#
# Three disks, each one start further along, so the three tests read as three consecutive
# starts of one machine without any of them depending on another having run.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Once.asm" -o "$WORK/Once.sbx" >/dev/null
cp "$DISKS/selfboot.img" "$DISKS/onceasked.img"
"$TOOL" put "$DISKS/onceasked.img" "$WORK/Once.sbx" /Apps/Once.sbx >/dev/null
# The request, written by hand so that the second and third disks do not depend on the
# first test having run to make it.
printf 'system /System/Boot/bare.bin\n' > "$WORK/once.cfg"
cp "$DISKS/onceasked.img" "$DISKS/oncedue.img"
"$TOOL" put "$DISKS/oncedue.img" "$WORK/once.cfg" /System/Boot/once.cfg >/dev/null
# And after it has been taken: the request is gone, and nothing else changed.
cp "$DISKS/onceasked.img" "$DISKS/onceafter.img"
# ---- Starting again without leaving the machine ----
#
# The whole loop in one session: ask for a one shot, restart, watch it own the machine.
# Before Reboot the only way to restart was to stop the emulator and run it again, which
# meant the one thing the machine could not do was the thing Once was written for.
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Reboot.asm" -o "$WORK/Reboot.sbx" >/dev/null
cp "$DISKS/onceasked.img" "$DISKS/reboot.img"
"$TOOL" put "$DISKS/reboot.img" "$WORK/Reboot.sbx" /Apps/Reboot.sbx >/dev/null