Start CosmOS with a script, and let a script hold its tongue

Three things scripts wanted, and they are one thing: a machine that can
have a face.

/System/Boot/startup.sh runs before anybody can type. Every way of reaching
the prompt for the first time goes through it, including the one where
there is no disk - in which case there is nothing to find and nothing is
said. A MISSING one is ordinary and silent, because a clean install has
none and a machine that complained every boot about a file nobody wrote
would be teaching its owner to ignore it. One that is THERE and does not
begin with #! is the other case entirely: somebody meant that to run.

#quiet stops each line being echoed, #loud puts it back. The prompt and the
echo go together, because together they are what makes a script look like
typing, so a quiet script gets neither and what it prints is all that
appears. A nested script inherits quiet - a build that asked for it meant
its helpers too - and gets its own setting back when the helper returns.
Anything else beginning with # is handed to the shell, which does not know
it and stops the script, because a script that asked for something this
shell cannot do should not carry on as though it had been given it.

clear empties the screen, which the console has been able to do since
before there was a screen to do it on.

THE PROMPT IS NOW SAID BY WHOEVER SUPPLIES THE LINE. It used to be said at
the top of the loop, which is a decision made before the line is read and
an answer not known until after - and it was wrong at both ends. #quiet is
itself a line, so its prompt went out before anything knew to stay silent;
and the line after a quiet script's last one comes from the console, having
already been denied one. Off by exactly one line in opposite directions. A
first attempt at this remembered whether the prompt had been skipped, which
worked and was a flag standing in for a structure. The monitor's assembler
prints a prompt of its own, so it reads through shellReadRaw, which is the
same source without one.

One admission. Handing the console its prompt back when a quiet script
ended was a real fix when I wrote it and stopped being one an hour later,
because the restructure above means the console's own path prompts whatever
the flag holds. The comment claimed it fixed something. Breaking it on
purpose changed nothing, which is how that was found, and it is now a
comment saying so instead of a line pretending to work.

The startup fixture ends QUIET on purpose: nothing puts the flag back when
the outermost script finishes, so a script ending #loud would have tested
the easy half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-30 16:59:23 -04:00
co-authored by Claude Opus 5
parent c28826df77
commit 553882d28d
13 changed files with 297 additions and 40 deletions
+77 -6
View File
@@ -72,7 +72,7 @@ scriptSlotAt:
DECA
BRA scriptSlotDone
scriptSlotStep:
DPUP.3 0d70
DPUP.3 0d71
DECA
BNA scriptSlotStep
scriptSlotDone:
@@ -80,7 +80,7 @@ scriptSlotDone:
; Seventy bytes, DP0 to DP1.
scriptCopyState:
INIB 0d70
INIB 0d71
scriptCopyByte:
LDA.0
STA.1
@@ -136,8 +136,18 @@ scriptOpen:
; the time the answer is known the caller's place is already gone.
;
; A still holds the depth from the check above: SUB writes Q and leaves it alone.
BRA scriptOpenFirst
BRA scriptOpenOutermost
CALL scriptPush
BRI scriptOpenFirst
scriptOpenOutermost:
; ---- The one place a stale quiet would matter ----
;
; A script started from the prompt begins loud whatever the last one left behind, which is
; what makes this the only place the flag has to be put back. Nested scripts INHERIT
; instead: a build that asked for quiet meant its helpers too.
RSTA
SETD.1 ScriptQuiet
STA.1
scriptOpenFirst:
SETD.1 ScriptName
@@ -288,6 +298,45 @@ scriptLineDone:
SUB
BRQ scriptLineAgain
; ---- A directive, which is about the file rather than for the shell ----
;
; '#' the way the assembler means it. #quiet stops each line being echoed as it runs, for a
; script whose own output is the point and which the prompts get in the way of; #loud puts
; it back.
;
; ANYTHING ELSE BEGINNING WITH # IS HANDED TO THE SHELL, which does not know it and says
; so and stops the script. That is deliberate and it is free: a script that asked for
; something this shell cannot do should not carry on as though it had been given it, and
; the machinery for saying so already exists.
INIB 0d35 ; '#'
CCF
SUB
BNQ scriptLineGive
SETD.1 QuietWord
CALL textSame
BRQ scriptLineQuiet
SETD.0 ScriptInto
LDD.0.0
SETD.1 LoudWord
CALL textSame
BRQ scriptLineLoud
SETD.0 ScriptInto
LDD.0.0
BRI scriptLineGive
scriptLineQuiet:
INIA 0x01
SETD.1 ScriptQuiet
STA.1
BRI scriptLineAgain
scriptLineLoud:
RSTA
SETD.1 ScriptQuiet
STA.1
BRI scriptLineAgain
scriptLineGive:
RSTA ; Q = 0: there is a line.
RSTB
CCF
@@ -446,7 +495,17 @@ scriptClose:
STA.1
BRA scriptCloseNone
CALL scriptPop
RET
scriptCloseNone:
; ---- Nothing to put back here ----
;
; The flag is only ever READ while a script is running: the console's own path says its
; prompt whatever this holds, and the first script started from the prompt sets it to loud
; on the way in. So a stale quiet cannot be observed, and clearing it here would be a line
; no test could tell the difference about - which is how it was written the first time, with
; a comment claiming it fixed something. It fixed something that a later change had already
; made impossible.
RET
; Every script ending at once, which is what a line that did not work means. A build whose
@@ -459,13 +518,19 @@ scriptAbandon:
#Data
QuietWord:
"#quiet"
LoudWord:
"#loud"
ScriptDepth:
0x00
; ---- Seventy bytes, and they are next to each other on purpose ----
;
; Name, blocks left, next block, where in the block: the whole of where a script has got to.
; Saving it is one copy because of this order, and nothing else may be put between them.
; Name, blocks left, next block, where in the block, and whether it is echoing: the whole of
; where a script has got to. Saving it is one copy because of this order, and nothing else
; may be put between them.
ScriptName:
#Reserve 0d64
ScriptBlocks:
@@ -474,11 +539,17 @@ ScriptIndex:
0x00 0x00
ScriptAt:
0x00 0x00
; Saved with the rest, so that a quiet script calling a loud one gets its quiet back when
; the loud one finishes. A new script INHERITS it rather than resetting, because a build
; that asked for quiet meant its helpers too; only the first script started from the prompt
; begins loud.
ScriptQuiet:
0x00
; Three would do - a save happens on the second script and not the first - but four costs
; seventy bytes and removes an off-by-one from the only place it could hide.
ScriptSaved:
#Reserve 0d280
#Reserve 0d284
ScriptInto:
0x00 0x00
ScriptRoom: