Commit Graph
4 Commits
Author SHA1 Message Date
Anachronaut c312853f8e The machine starts itself
stage two
  CosmOS
  > saved it
  read it back, 22 bytes:

Stage one hands over to stage two out of a boot slot; stage two mounts the
filesystem, finds /System/cosmos.bin, takes the image apart and places its
code, its data and its vector table, and jumps to the entry point the
vectors named. Nothing placed memory for it.

What it loads is an ORDINARY BOOT IMAGE, the same SPBT file the emulator
has always been handed. That was the user's call and it is the whole trick:
a second stage that loads the machine's normal image format is not a
boot-specific mechanism, so bare metal SplitBit stops being a special case.
A program wanting no operating system under it is just an image, written
under CosmOS like any other, and startable because it is a file.

Three things in it worth knowing:

- THE ENTRY POINT IS CAUGHT ON ITS WAY PAST. Program Memory cannot be read
  back, so the boot vector cannot be looked up after being installed; the
  vector loop notices the one addressed at 0xFC00 and keeps it.
- A missing "VEC" is not a fault. An image written before vectors existed
  simply ends after its data, and then the entry point is zero, which is
  what every such image has always relied on.
- Feature flags that are set mean an image asking for a machine this may
  not be, and the honest answer to a request that cannot be understood is
  to refuse rather than to run it anyway.

The test records that the system WORKS afterwards rather than that it
started. A loaded program running is what says the vector table arrived,
because a program reaches the system through SWI and nothing else; the file
written and the directory entered say the filesystem and the console came
up with it. A second disk has a boot slot and nothing to start, and says so
rather than jumping somewhere.
2026-08-27 14:13:23 -04:00
Anachronaut 82adeeb193 A boot payload can arrange its own Data Segment
Stage one places Program Memory and nothing else, because knowing where a
payload's data ends and its code begins would mean knowing a format, and
knowing formats is what ROM must do as little of as possible. But the real
second stage needs a Data Segment: sbfs.asm has variables and a string it
compares against.

The answer needs nothing new. A loadable image is written into the slot as
code followed by data, so the data image is already in Program Memory just
past the code - and the payload's first instructions blit it down to where
it was assembled for. Proved by slotData.asm, which prints from a string it
placed itself.

The padding is the part worth recording. The blit needs a length and the
assembler will not work out the difference between two labels, so the
segment is padded to a round number and that number is what gets copied.
The first draft padded to 257 and copied 256, and the byte that did not
arrive was padding, so it worked by luck. It is exact now and says why.

This is the shape the user asked for and it goes further than the
mechanism: the second stage becomes a loader for the machine's ORDINARY
image format rather than for anything boot-specific, so bare metal SplitBit
stops being a special case. A program that wants no operating system is
just an image, developed under CosmOS like any other, and selectable at
boot because it is a file.
2026-08-27 13:45:11 -04:00
Anachronaut d07b23f90b Rung 2: the machine starts itself off a disk
Stage one exists and works. It is 330 bytes of program and everything it
knows is a thing that will be true forever: which port the disk is on, that
a SplitBit disk begins with its own name, and where two numbers sit in that
first block. Not what a file is, not what a directory is, not that SBFS has
versions. It reads the live boot slot into Program Memory, jumps to the
first byte, and prints one character and halts if there is nothing there.

It is an ordinary boot image for now, so the whole chain runs on machinery
that already exists and the emulator has not been touched. Nothing about it
changes when it moves into ROM except who puts it in memory.

SplitDisk gained "boot" to write a slot and "bootslot" to choose one, kept
apart on purpose: writing a slot and starting from it are different
decisions, and joining them would make every write a commitment. A slot is
always written WHOLE, because one still holding the tail of what was there
before is one whose contents depend on its history, and stage one reads all
of it without knowing where the file stopped.

Three recorded tests, and the pair is the point: two disks differing only
in which slot the superblock names, with payloads that say different
things. One prints "booted" and the other does not, so this is a test of
CHOOSING a slot rather than a test that some bytes were read. The third
boots a disk with no boot area and gets the one character a ROM has room
for. Eight more host checks, including that a slot is padded whole.

Two things worth recording. The first draft used #Align to put the scratch
buffer at 0x8000 and produced a 33K file - thirty two kilobytes of zeroes
in something meant to be a ROM. It is an address, not storage, which is
exactly what the assembler's own scratch map exists to say.

And SplitLint caught the second in code written an hour after the baseline
that catches it. In the blit set-up, RSTA writes a source address of zero
and then RSTA writes a bank number of zero - two unrelated quantities that
are equal by accident, in the most safety critical file in the repository.
It is marked with a reason rather than removed.
2026-08-26 23:31:04 -04:00
Anachronaut 0a2965bc63 Name the rules, say when there is nothing to say, and hold a baseline
Four things SplitLint wanted, and they build on each other.

EVERY WARNING NAMES ITS RULE, in brackets at the end the way a compiler
names the flag that produced it. Twelve rules, listed by --help. That makes
the other three possible: suppressions can name one rule and leave the line
honest about the others, the harness can assert on a rule's identity rather
than on the wording of its message, and --machine can print one tab
separated line per warning - file, line, rule, message, help - so nothing
downstream reads prose. This file's own output was parsed with regular
expressions three times in one day before it had a shape to rely on.

A CLEAN RUN SAYS SO:

  No style warnings: 121 files checked against 12 rules.

It used to exit in silence, which does not tell you it found nothing - it
tells you nothing at all, and from outside the two are identical.

A MARKER THAT SILENCES NOTHING IS ITSELF REPORTED, as dead-suppression. An
exception that outlived whatever made it necessary is the thing the
required reason exists to prevent, and naming the wrong rule now gets you
both the warning you meant to silence and a note that your suppression is
doing nothing.

AND THE CORPUS IS HELD TO A BASELINE. Sixty one warnings are left in it
deliberately and nothing stopped a sixty second. Tests/lint-baseline.txt
records how many of each rule each file should produce, so a new one fails
make test while the sixty one stay quiet; confirmed by adding an INIA 0d0
to Say.asm and watching it name the file, the rule and the count. It counts
per file and rule rather than recording line numbers, because line numbers
would churn the whole baseline whenever anything was inserted above a
warning - the same reason cycle counts are stripped from recorded output
here. ./Tests/lint.sh --bless records it again.

One thing to know for next time: the rule name was inserted before the line
number at all twenty one call sites, and the signature was changed to match
rather than the twenty one call sites being fixed. (path, rule, line) reads
no worse than (path, line, rule) and one edit has fewer ways to go wrong
than twenty one.
2026-08-26 21:00:21 -04:00