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:
co-authored by
Claude Opus 5
parent
13b20c8834
commit
f7657081be
@@ -47,6 +47,21 @@ The suite holds the two to being the same machine rather than taking it on trust
|
||||
the entire manifest through Voyager as well, with `--headless`, and requires it to satisfy
|
||||
every recorded result byte for byte.
|
||||
|
||||
**F12 is the reset button**, and 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. It
|
||||
does exactly what writing to the machine port does - the machine starts the way it started,
|
||||
which means the boot chain runs again and finds whatever the disk now says to run.
|
||||
|
||||
That is what makes a bare metal program escapable. `Once` puts one 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, without closing the window and opening it again.
|
||||
It works on a machine that is stuck waiting for a key, too, which is when a reset button
|
||||
earns its keep.
|
||||
|
||||
**Escape reaches the machine.** 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.
|
||||
|
||||
**The screen belongs to the machine, not to the window.** The video device is a tile engine
|
||||
on ports 0x30 to 0x3F that brings its own bank of video memory, and it renders into a buffer
|
||||
that is a pure function of that memory - so the same program draws the same picture whether
|
||||
|
||||
@@ -631,6 +631,18 @@ static uint8_t pendingInterrupts[INTERRUPT_LINE_BYTES];
|
||||
// Whether somebody has asked the machine to start over, and taking that request away.
|
||||
static int resetWanted = 0;
|
||||
|
||||
void requestReset(void) {
|
||||
resetWanted = 1;
|
||||
}
|
||||
|
||||
void consoleResetInput(void) {
|
||||
consolePushback = -1;
|
||||
consoleEnded = 0;
|
||||
consoleLineLength = 0;
|
||||
consoleLineAt = 0;
|
||||
clearInterrupt(PORT_CONSOLE);
|
||||
}
|
||||
|
||||
int takeResetRequest(void) {
|
||||
int wanted = resetWanted;
|
||||
resetWanted = 0;
|
||||
|
||||
@@ -306,6 +306,19 @@ void serviceDevices(void);
|
||||
// the instruction that asked - the CPU is mid-step and its state is not yet consistent.
|
||||
int takeResetRequest(void);
|
||||
|
||||
// ---- The button on the front of the case ----
|
||||
//
|
||||
// A machine has one, and a window is the case. Writing MACHINE_RESET is how a PROGRAM asks;
|
||||
// this is how a person does, without needing a program that is willing to listen - which is
|
||||
// the whole point of a reset button and the reason the port exists at all.
|
||||
void requestReset(void);
|
||||
|
||||
// Puts the console's input back to how a machine starts: nothing pushed back, no line half
|
||||
// gathered, and NOT at the end of input. Called when the machine starts over, because a
|
||||
// console that had run out of input would still have run out afterwards - and a reset that
|
||||
// left the keyboard dead would be a reset nobody could use twice.
|
||||
void consoleResetInput(void);
|
||||
|
||||
void raiseInterrupt(uint8_t port);
|
||||
|
||||
void clearInterrupt(uint8_t port);
|
||||
|
||||
@@ -236,6 +236,7 @@ void machineRunSlice(Machine *m) {
|
||||
}
|
||||
videoReset();
|
||||
consoleHome();
|
||||
consoleResetInput();
|
||||
initializeCPU(&m->cpu, Program, Data);
|
||||
break; // Out of this batch; the loop above carries on with a new CPU.
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user