Commit Graph
14 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 2808688fa1 TuneC: a written tune becomes the bytes the player reads
The compiler, and the last thing the ladder was waiting for. A tune names
its instruments, writes sequences of notes and durations, and gives each
voice an order list of sequence names - which is where repetition comes
from, since a phrase played four times is written once and named four
times.

  #Tick 0d125000
  #Patch Oboe oboe.patch
  #Voice 0d0 Oboe
  #Sequence Verse
    0d64 0d4  0d67 0d4  0d72 0d8
  #Order 0d0
    Verse Verse Ending

"#" is a directive and ";" is a comment, exactly as in SplitBit assembly
and in the shell's scripts, and numbers are written the way the assembler
writes them. One rule across the machine rather than a third dialect -
and the rule earned itself immediately: the first tune I wrote said
"#Voice 0" and was refused, correctly, for a bare number.

WHAT IT REFUSES IS EVERYTHING THE PLAYER CANNOT NOTICE. The machine has
no names, so it cannot say a sequence does not exist. It has no lengths,
so it cannot say the voices will come apart four bars after the mistake.
A duration of nought is counted down to 255 and held, which sounds like a
hang rather than an error. And by the time a tune is loaded, "no starting
instrument" and "instrument nought" are the same byte - so the user's
ruling, that a voice with a part and no instrument is an error, can only
be kept here.

SoundPatch gains --blob, writing the same table as raw bytes. It stays
the only thing that reads soundThing's JSON: a second program parsing
that format is a second opinion about what a patch means, and the seam
between two opinions is where the LFO bug lived for a fortnight. Patches
are found beside the tune and then on a -I path, the way an include is.

THE TEST IS THAT TWO IMPLEMENTATIONS AGREE. maketune.py lays the fixture
out by hand and TuneC compiles a written source, and the suite checks
they match byte for byte - the discipline SplitDisk and sbfs.asm are held
to, for the same reason: either alone is only self-consistent. The
fixture predates the compiler, so this is also TuneC checked against
something written before it existed. Four more checks cover the four
refusals.

Also: the SoundPatch binary was tracked, alone among the six tools, and
.gitignore lists every other one. Untracked, and TuneC added beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-05 21:11:36 -04:00
AnachronautandClaude Opus 5 3e48e9690d Put a disk of your own in drive 1
make run-voyager and make run-cosmos now attach Disks/personal.img as the
second drive. It is made the first time it is wanted and then left alone:
a rule with no prerequisites, so make never looks at it again.

NOT UNDER build/, and that is the whole point. Everything else in this
repository is made from source and can be thrown away without losing
anything, so 'clean' empties build/ without a thought - and a disk of your
own that a clean deletes is not a disk of your own. It is the one place
here where something MADE ON THE MACHINE can live, which starts to matter
the moment there are tools on it that make things.

Disks/ is in .gitignore for the same reason: what you make on the machine
is yours and not the repository's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-31 16:45:17 -04:00
AnachronautandClaude Opus 5 e3ef25e3b3 Split the machine from its front end, and add Voyager
The Segan Voyager is the same SplitBit with a screen and a speaker instead of a terminal,
and this is the rung that makes there be two of them at all. Everything that is actually
the machine - the CPU, the controller, the devices, the run loop, the reporting - moves to
machine.c, and each front end brings one file of its own. emulator.c is now sixty lines of
argument handling and a three line loop.

The machine runs in SLICES rather than to completion, because that is the cut a window
needs: run a slice, present a frame, run another. A terminal runs slices until the machine
stops. Both loops are three lines, which is why the cut is there rather than anywhere else.

At this stage Voyager's window is empty. There is no video device yet and inventing a
temporary way to draw would mean building something to throw away.

PLAIN MAKE STILL WORKS WITH NO GRAPHICS LIBRARY. Raylib is probed by compiling and linking
against it rather than by looking for a file, because a header with no library behind it
passes a file check and then fails at link time. Where it is missing, make says so once and
builds everything else - the machine, the assembler, the disk tool, the linter and the whole
suite. A project about a small understandable CPU should not need OpenGL to run its tests.
That nearly broke here: make strict globs Source/Emulator/*.c, so it would have tried to
compile voyager.c and failed on precisely the machines the split exists to support, and this
machine has Raylib so nothing would have caught it.

Tests/voyager.sh runs the WHOLE MANIFEST through Voyager and holds it to the recorded
results SplitBit is held to. Not that the two look alike: that one satisfies every recording
the other does, byte for byte, exit status included. It reuses run.sh, which now takes the
machine from SPLITBIT_EMULATOR, rather than keeping a second copy of the runner that would
drift. Voyager not being built is not a failure - it says so and passes.

Verified both ways. Made Voyager print one extra line, and 114 of 165 failed: exactly the
tests that run the emulator, with the 51 assemble-only and xfail cases correctly untouched.
Removed the binary, and the script skipped. Built with HAVE_RAYLIB=no, and everything else
still built and checked clean.

--headless is taken out of the arguments in voyager.c rather than in the shared parser,
which should not learn about a window only one binary has. It exists so the suite can run
this binary at all: a front end that could only be exercised by a person looking at it would
be a front end nothing checks.

loadFile takes a const char * now, which it always should have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-28 21:57:09 -04:00
AnachronautandClaude Opus 5 999451e935 Ignore External, which is other people's repositories
soundThing and Hatchet-GPU are kept in the working tree to be read, not built: one is
where the synthesizer comes from, the other is where the 8x8 font and the scroll register
idea come from. Neither belongs to this repository. They carry their own .git directories,
their own build outputs and three compiled binaries between them, and a plain
'git add -A' would have taken all of it.

What this repository takes from them will be vendored deliberately, with a note in the
file saying where it came from and what changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-28 17:49:38 -04:00
Anachronaut 54ff7196c9 Stage 0: the emulator carries the ROM, so a disk is enough
./SplitBit --disk system.img
  stage two
  CosmOS
  >

No boot image named. The emulator shadows its built in stage one into
Program Memory - boot vector included - and the CPU then does exactly what
it has always done: reads the boot vector and starts where it points.
NOTHING ABOUT THE CPU CHANGED to make a machine that starts itself, which
is what picking shadowing over a mapped ROM bought.

The ROM is generated from Programs/Boot/stage1.asm by the makefile rather
than committed beside it, because a copy of a program kept next to the
program is a copy that goes stale. That makes the assembler a real
dependency of the emulator, which it always sort of was and now says so.
od and awk rather than xxd, which is not everywhere, or python, which the
README does not ask anybody to install in order to build this.

loadROM is loadFile given bytes instead of a path: both go through one
reader over an fmemopen stream, because a ROM is a boot image and there is
no reason for the machine to have two ways of understanding one.

Naming an image still works and is what every other test here does. That
path is not a shortcut to apologise for - placing memory from outside is a
real thing real machines allow, and it is a debugger. The help says so now.
No image and no disk is the one case with nothing to run, and it says that
rather than printing a usage message about a missing file.

run.sh gained a "rom" mode which hands the emulator a disk and nothing
else. The source column still names stage1.asm, because that is what is IN
the ROM: assembling it there says the thing the emulator carries is a thing
that still assembles.
2026-08-27 14:41:33 -04:00
Anachronaut 2b079324ae Add SplitLint, and let it be told when something is deliberate
SplitLint reports valid assembly that has a shorter direct expression:
zero loads that could be RSTA or RSTB, Q moved through the stack where MVQA
would do, self-cancelling push and pop pairs, assignments overwritten
before use, unreachable fallthrough, one-byte pointer moves that could be
INCD or DECD, a branch to the label directly below it, a SETD reloading an
address the pointer already holds, and branches whose carry is known.

Its model is deliberately local and conservative: every label and every
directive forgets all known state, so a claim only ever lives inside a
straight-line region. It knows the calling convention - CALL forgets DP3
and keeps the rest, RCAL and SWI forget everything - and it shares
assembly.o with the assembler, so an added opcode cannot leave it holding a
private copy of the instruction table.

260 warnings across the corpus, of which three were wrong in the way that
matters: branchTest.asm and interruptFlagTest.asm exist to check that a
branch whose carry is known behaves correctly, so a diagnostic saying the
outcome is known is exactly right and exactly unwanted.

A line whose comment says "splitlint: <reason>" is now not reported on. THE
REASON IS REQUIRED and a bare marker is refused, because a suppression
nobody explained outlives whatever made it necessary. Suppressed warnings
are not counted, so --fatal-warnings does not fail on one, and the number
of them is printed at the end so the claim is visible rather than silent.

Tests/lint.sh checked a TOTAL: twenty three warnings expected, twenty three
found. That number stays right while the thing behind it goes wrong - a
rule that stopped firing while another fired twice would pass, and so would
a rule reporting at the wrong line. It now checks which warning came out
and at which line, that nothing else came out, and that the four lines
meant to stay quiet did. Confirmed by breaking one rule's message and
watching it name that rule: the old assertion passed the same sabotage,
because the warning still fired and the count never moved.

Written with the user while I was away; my part is the suppression
mechanism, the harness rewrite, and the three marks in the test programs.
2026-08-26 17:23:17 -04:00
AnachronautandClaude Opus 5 fa3982dbd9 Move the CosmOS third of the Programming Manual to CosmOS
386 of the manual's 1,116 lines documented an operating system rather than
a machine. The split inside that file was never tutorial against reference;
it was the machine against the software that happens to run on it.

  What A Program May Ask The System For   129  -> CosmOS README
  Programs That Come With The System       111  -> CosmOS README
  Reading And Writing The Filesystem        64  -> CosmOS README
  Loading A Program From A Disk             52  -> Assembler Manual
  The Console Library                       25  -> CosmOS README

The services are the clearest case: a hundred and thirty lines describing
what CosmOS offers a program, in the manual for a CPU that has no operating
system of its own. A different system on the same machine would offer
different services and that section would be wrong for it.

The loadable program format goes to the Assembler Manual instead, because
SBEX is a thing the assembler WRITES. Nothing in the CPU knows what it is.

The Programming Manual is 716 lines and fourteen sections now, all of them
about the machine.

TWO DUPLICATE DESCRIPTIONS COLLAPSED INTO ONE EACH. The application list
existed in both documents in different words, and the CosmOS copy had gone
stale - no Break, no Stream, no assembler - because only the manual's copy
was checked. Moving the checked one in and deleting the other leaves one
list, and docs.sh follows it.

The second was made by this commit and caught while reading the seams: the
CosmOS README already had a service table, so the move briefly produced two.
That section now says what services are for and points at the one table.

Renaming a section as it moved: "Reading And Writing The Filesystem" is
"The Filesystem Library", which says what it is and reads beside "The
Console Library".

docs.sh follows all five, and each was verified by renaming the heading in
its new home and reading the complaint. The README and the CosmOS README
both described what the other manuals cover, and both were wrong the moment
this landed; they say the division out loud now, since it is the point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-21 14:35:18 -04:00
AnachronautandClaude Opus 5 6dbb38b209 Stop committing assembled programs, nine of which had gone stale
Thirteen build artifacts were tracked: ten .bin files, a disk image, and the
text file that had been put on it. Every one is produced by 'make' in
Programs/ from a source file that is also tracked.

NINE OF THE TEN BINARIES DISAGREED WITH THEIR OWN SOURCE, including
Programs/CosmOS/Bin/cosmos.bin - the operating system. Only hello.bin still
matched, and that by accident.

That is worse than shipping no binary at all. A repository holding both a
source file and a stale build of it has two answers to what a program does,
and the wrong one is the one that runs. Somebody reads the source, runs the
binary, and sees behaviour that is not in the code with nothing to tell them
why.

Nothing referenced any of them - not the makefiles, not the tests, not the
manuals. Checked before removing, and checked after: the suite passes, and a
clean clone with all of them gone builds both tools and assembles every
program from source alone.

.gitignore now covers *.bin, *.sbx and *.img, unanchored, because a build
output may appear anywhere a source file lives.

Programs/CosmOS/Bin/ held cosmos.bin, disk.img and a 17 byte hello.txt that
existed only to be put on that image. It was a snapshot of a demo from
before Tests/makedisks.sh built its own fixtures, and it goes with them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-21 13:29:41 -04:00
AnachronautandClaude Opus 5 51601545ae Anchor the gitignore patterns to the repository root
Without a leading slash a pattern matches at any depth, so the rules for
the three built binaries also matched directories: "Assembler" ignored
Source/Assembler. The files already in it stayed tracked, which is why
nothing had gone wrong yet, but a new source file added there would have
been silently untracked and left out of a commit.

Found by git refusing to stage a change to Source/Assembler/firstPass.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:16:06 -04:00
Anachronaut 91c9d49d1b CosmOS pre-alpha and launchable application versions of old programs. 2026-08-17 15:31:49 -04:00
Anachronaut eff6902bcf Block device peripheral and SBFS file system implemented. 2026-08-16 14:03:29 -04:00
Anachronaut 6d1966d500 Interrupt system implemented, some new programs. 2026-08-15 00:44:13 -04:00
Anachronaut 94b3a7af28 New SplitBit programs. 2026-08-13 23:42:55 -04:00
Anachronaut c2440ae5fa Various bug fixes to assembler, added more data pointers. 2026-08-13 23:41:22 -04:00