Put a reset button on the case, and stop Escape closing the window

ESCAPE WAS A BUG I LEFT. This machine sends Escape to the console like any other key, and
Raylib closes a window on Escape unless it is told not to - so a program reading keys could
be ended by one of them, taking whatever was in memory with it. SetExitKey(KEY_NULL), and it
is a byte again.

F12 is the reset button. A button on the case rather than a key the machine can see: nothing
sends a function key to the console, so nothing can be surprised by one. It does what writing
MACHINE_RESET does, which is that the machine starts the way it started - the boot chain runs
again and finds whatever the disk now says to run.

Which is what makes a bare metal program escapable. Once puts a demo in front of the next
start and deletes the request before jumping, so a demo that has taken the whole machine is
one keypress from the system coming back, instead of closing the window and opening it again.

IT HAD TO REACH A MACHINE THAT IS WAITING, and that took two more things. A reset is acted on
between instructions, and a machine blocked on a key is part way through one - so the button
would have set a flag that nothing ever came along to notice, in exactly the situation a
reset button is for. The wait ends now: the console is told its input is over, which it is
for a machine about to stop existing.

And the reset puts the console's input back - nothing pushed back, no line half gathered, and
not at the end of input. That was already wrong before the button existed: a reset after the
input ran out left a console that had run out afterwards, so a machine could be restarted
once and then never typed at again.

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-29 11:05:16 -04:00
co-authored by Claude Opus 5
parent 13b20c8834
commit f7657081be
5 changed files with 86 additions and 0 deletions
+45
View File
@@ -126,6 +126,28 @@ static void drainKeyboard(void) {
}
}
// ---- The reset button ----
//
// F12, because it is a button on the case rather than a key the machine can see: nothing
// sends a function key to the console, so nothing can be surprised by one. What it does is
// exactly what writing MACHINE_RESET does - the machine starts the way it started, which
// means the boot chain runs again and finds whatever the disk now says to run.
//
// Which is what makes a bare metal program escapable. Once puts a demo in front of the next
// start and deletes the request before jumping, so a demo that has taken the whole machine
// is one keypress away from the system coming back - and nobody has to close the window and
// open it again to get there.
static int resetPending = 0;
static void checkResetButton(void) {
if (IsKeyPressed(KEY_F12)) {
requestReset();
// Remembered as well as asked for, so that a machine waiting on a key can be woken
// to go and notice it. See voyagerKey.
resetPending = 1;
}
}
static void presentFrame(void) {
// The device turns video memory into pixels; this puts them on the glass. Everything
// that decides what the screen looks like is in the machine, where the suite can
@@ -170,6 +192,7 @@ static void presentFrame(void) {
EndDrawing();
// EndDrawing has just polled, which is the one moment Raylib's queues hold anything.
drainKeyboard();
checkResetButton();
}
// What the console asks while it is waiting. Presenting from in here is what keeps the
@@ -179,6 +202,19 @@ static int voyagerKey(int mayWait) {
if (!windowOpen) {
return CONSOLE_GONE;
}
// ---- The button has to reach a machine that is waiting ----
//
// A reset is acted on between instructions, and a machine blocked on a key is part way
// through one - so pressing the button while a program sits waiting would set the flag
// and nothing would ever come along to notice it. Which is precisely the moment a reset
// button earns its keep: a program that is stuck is the one you want to get out of.
//
// So the wait ends. The console treats that as the end of input, which it is for the
// machine that is about to stop existing, and the reset puts the console's input back.
if (resetPending) {
resetPending = 0;
return CONSOLE_GONE;
}
// Whatever is already waiting, however long ago it was typed. This is the answer to
// both questions, and asking it first is what makes a program that polls rarely see
// every key rather than one in six.
@@ -202,6 +238,8 @@ static int voyagerKey(int mayWait) {
return keyTake();
}
int main(int argc, char *argv[]) {
int headless = takeHeadless(&argc, argv);
@@ -252,6 +290,13 @@ int main(int argc, char *argv[]) {
InitWindow(VIDEO_MAX_WIDTH * SCREEN_SCALE, VIDEO_MAX_HEIGHT * SCREEN_SCALE,
"Segan Voyager");
SetTargetFPS(60);
// ---- Escape is a byte, not a way out ----
//
// Raylib closes a window on Escape unless it is told not to, and this machine sends
// Escape to the console like any other key. So a program reading keys could be
// ended by one of them, taking whatever was in memory with it - which is a poor way
// to find out that a default was left as it was found.
SetExitKey(KEY_NULL);
// One texture, updated in place. Making a new one every frame would be a new
// allocation sixty times a second for a picture that is the same size every time.
Image blank = GenImageColor(VIDEO_MAX_WIDTH, VIDEO_MAX_HEIGHT, BLACK);