5 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 563bc20a75 Tab lists its matches in columns, and says what it can in colour
Tab's listing was six instructions: print the name, print two spaces. It
was the crown of this shell for a while and looks plain next to ls.

Columns cost NOTHING EXTRA. Tab already walks its candidates twice - once
to find the answer, once to show the matches, and tabRunSources exists
because those are the same walk asked two questions. The longest match is
counted on the first, which already visits every one of them, so the
listing needs no walk of its own to know how wide a column should be.
Padding goes before a name rather than after, so a row ends on a name.

Directories are blue and the shell's own commands are green. Both are
free: Tab appends the separator itself, and a built-in is not a file at
all - which is also the only way anybody could know it will run.

A FILE IS NOT COLOURED, and that is a decision. Whether a file will run
is a read of its first block, which is what ls does and what makes ls
cost twice what it otherwise would. Here it would be worse than slow:
candidates are offered from inside a directory walk, and looking a file
up would overwrite the very fields holding the walk's own position. Doing
it safely means holding every candidate name in memory, which is a buffer
the shell would carry whether anybody pressed Tab or not - and ls is one
keystroke away. A Tab press already costs about 200,000 cycles, so the
read was not the objection.

---- And the machine could no longer build itself ----

Found by make test, not by reading. The native assembler ran out of room
for label names on cosmos.asm: 16,758 bytes against 16,384. The index was
1,341 of 1,536 in the same breath.

Which is scratch.asm's own warning happening a second time - "two
ceilings a hundred bytes apart look like one ceiling until the first is
lifted" - so both were raised, out of the seventeen kilobyte page that
file deliberately left unclaimed against exactly this. Names to 26,624
and the index to 2,048, both left about a third clear, with 1,792 bytes
still unclaimed for the same reason.

A name is thirteen bytes on average and an index entry is four, so the
arena will always be the one that speaks first. That is now written down
where the two numbers are.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-06 20:18:56 -04:00
AnachronautandClaude Opus 5 afda6ca83b Finishing says nothing, which is what finishing looks like
The shell printed "finished" every time a program gave the machine back.
That made sense when starting a program and getting the machine back was
most of what the machine did and the prompt was the only other thing on
the screen. Under a listing, between two commands, it is noise.

And it was printed whatever the program made of it, so a program that had
just explained what went wrong was answered with the word "finished" -
"there is no such directory: nowhere" and then, immediately, "finished".

The prompt coming back is what says a program is over. It is the same
argument osLastStatus is made of: a program that failed has already said
so in words, and anything the shell adds beside that is the shell talking
over it. A program that FAULTED still says so, because the fault screen
printed in red above and a program that is gone should not look like one
that ended.

A NEWLINE ONLY IF ONE IS WANTED. A program that stopped part way along a
line would leave the prompt sitting in the middle of its last output,
which the word used to prevent by accident. The console knows which
column the cursor is in, so the shell asks - where a blank line printed
every time would be right about half of the time, and an empty directory
listed with ls would be followed by a blank line for no reason.

Fifty nine recorded sessions lose the word. Two of them move a cursor up
a row with it, which is the same fact seen from the screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-06 19:49:43 -04:00
AnachronautandClaude Opus 5 323d7a0330 A listing says what is left of the disk, and what will fit
dir said what was there and nothing about what was left. SplitDisk has
printed the free figure since it was written, so the machine's own
listing was the poorer of the two implementations at describing the same
disk.

  43 files, 3 directories
  46 of 64 entries, 1893 blocks free

Entries first, because they are the ceiling nobody notices until they hit
it: a disk of small files runs out of directory slots long before it runs
out of blocks.

COUNTED RATHER THAN ASKED. The superblock keeps a free count and this
file calls it "a note rather than the truth" in three places. sbfsSpace
reads the whole directory table instead, which costs a read per directory
block and is the answer rather than a guess. SplitDisk goes on reading
the note and saying when it is stale, which is the right place for that
check - the host tool is what you audit a disk with.

---- And it is a fact about the disk, not about where you are ----

The first version added the blocks up as the LISTING walked past them,
which cost no extra read and was wrong: that walk stops only on entries
in the working directory, so the same disk came out as 1,996 blocks free
from the root and 2,025 from /Apps. Comparing against SplitDisk is what
said so, which is what having two implementations is for.

---- The longest run, which is what decides whether a file fits ----

  4 of 16 entries, 37 blocks free
  the longest run is 25

Files are laid down contiguously, so the free total does not say whether
a file will fit. Both implementations learn it, from one specification.

Said only when it differs from the free total. Deleting is what fragments
a contiguous store, and a disk that has only been appended to has one gap
at the end - so on a healthy disk this is silent, and a line that appears
only when something is wrong is a line somebody reads.

There is no sort on this machine and the entries are in no order, so a
candidate walks the disk: each pass finds the used extent nearest at or
after it, and anything the candidate stands inside pushes it to the far
end and starts the pass again. The same trick allocating uses. So it
costs a pass per gap rather than per file - nearly nothing on a disk with
one gap, more the more fragmented the disk is, which is the right way
round.

holes.img is six files with the second and fourth deleted, because no
other disk here can show any of this: none of them has ever had anything
deleted from it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-06 18:19:04 -04:00
AnachronautandClaude Opus 5 736037462e The shell edits the line it is given
Three different things used to do this job, and which one you got depended on
where the machine was running. On a terminal the host held the line and did the
echoing and the backspacing; behind a window the console's own gatherer did it;
from a file nothing did it at all. One job, three implementations, none of them
in the system - which is why there was no way to move about in a line and
nowhere for a history to live.

So editLine does it. Key mode while a line is being read and line mode straight
after, so nothing else in the system and no program calling osReadLine notices
anything changed. Left and Right, Home and End, Backspace for the character
before the cursor and Delete for the one under it, and anything typed goes in
where the cursor is with the rest of the line moving along.

Ctrl-D means the end of input again, on an empty line, because that was a thing
the terminal did while it was holding the line and it is not holding it now.
Same trade as the echoing.

MOST KEYSTROKES DRAW NOTHING BUT THEMSELVES. A character typed at the end of a
line needs no cursor moved: printing it is the whole change, and a backspace
there is three ordinary bytes. That matters beyond speed - moving the cursor by
hand is what a terminal is TOLD about, in an escape sequence, so redrawing on
every keypress would fill every recorded transcript in this suite with them.
The line is only reprinted when something happened in the middle of it.

Where the line STARTS is worked out backwards from where printing ended, rather
than trusted from what was remembered. That is what makes it survive the screen
scrolling: a line printed on the bottom row moves everything up by one, and a
remembered row would be one too low from then on.

The command line holds 127 characters, up from 63. The limit started to be felt
the moment a line could be moved about in.

58 recordings changed, and every one of them by the echo. THE PROOF IS NOT A
HEURISTIC: a CosmOS built with the echo silenced reproduces 187 of the 188
recordings byte for byte. The one exception is cosmosTyped, the backspace test,
where the rub-out marks now come from the shell instead of from the console's
gatherer - same marks, different author.

cosmosEditKeys is the new test, and every line in it is typed wrong and then
corrected with a different key. Its last line is eighty six characters at a
prompt in column two on an eighty column screen, so the line runs onto the row
below and the shell has to find the start of something it can no longer see;
breaking either half of that arithmetic fails it.

Also: agree.sh looked for "> the same", anchored to a prompt that no longer
precedes what a command prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-31 23:01:55 -04:00
Anachronaut 546f336823 Configuration files, and boot.cfg as the first of them
One setting to a line: a key, a space, the rest of the line is the value.
A semicolon starts a comment. The format was noticed rather than designed -
textSplit already cuts the first word off a line and leaves the rest, and
textSame already insists two strings end together, so reading a setting is
those two routines and a loop. It is also what the shell reads, which makes
a configuration line a command line the machine reads instead of a person
typing one.

The format was chosen by asking what the BOOT LOADER could manage, because
it is the worst case in every direction: a few kilobytes, no operating
system to report to, and if it fails the machine does not start. Two
formats would be worse than one and the loader cannot have the richer one.

CONFIGURATION IS ADVICE. A missing file, a missing key, an unusable value,
a line too long to read: all of them mean use the default and none is a
failure. BUT QUIET IS NOT SILENT - a setting somebody meant, which did not
take effect, says so. That was the user's addition and it is the better
rule: the default alone leaves the only symptom being that the machine did
not do what somebody asked.

So two routines. cfgGet reads and says nothing, because reading three
settings should not report one bad line three times. cfgCheck reads the
file once and reports, and is handed the caller's list of keys - whether a
key means anything is the only part of this a shared reader cannot judge.

/System/Boot/ holds the boot files, and stage two reads boot.cfg for what
to start, with a fallback to try if it does not work and a name compiled in
for when the file says nothing.

THE TEST FOUND A REAL BUG, and it is the interaction I would not have
thought to look for. First-match-wins met an empty value: a file with

  system
  system /System/Boot/bare.bin

matched the first line, handed back nothing, and the machine tried to start
a file with no name while a good setting sat underneath. An unusable value
is an absent one - which is what "configuration is advice" says, and this
is where it earns its keep.

cfgBare starts an image with no operating system in it at all, which is
what loading an ordinary boot image buys: a program wanting the whole
machine is a file like any other, chosen the same way the system is. Three
disks differing ONLY in boot.cfg, so each is a test of the file rather than
of the machinery under it.
2026-08-27 16:04:21 -04:00