diff --git a/README.md b/README.md index c5d2683..7f16a2b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 08b0cda..679b8ab 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -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; diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index ebcfd4d..68047ee 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -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); diff --git a/Source/Emulator/machine.c b/Source/Emulator/machine.c index 08575c4..9b5d26c 100644 --- a/Source/Emulator/machine.c +++ b/Source/Emulator/machine.c @@ -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. } diff --git a/Source/Emulator/voyager.c b/Source/Emulator/voyager.c index ec55093..5eedbbd 100644 --- a/Source/Emulator/voyager.c +++ b/Source/Emulator/voyager.c @@ -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);