From adefce975bb2d4896b59049c3259c8058fd0177e Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sat, 29 Aug 2026 14:48:31 -0400 Subject: [PATCH] Let the reset button reach a machine that has stopped The gesture rebooted CosmOS and could not reboot picture.bin, which is the case it was added for. picture.asm ends in HALT, and a halted machine runs no instructions - so nothing ever reached the code that notices a reset, because a reset is noticed BETWEEN INSTRUCTIONS and there are none. It only ever worked because CosmOS was still going. Which is backwards: a machine that is not going anywhere is exactly the one worth restarting, and it is the one that cannot hear a request by itself. The restart is lifted out of the run loop into machineTakeReset, and the window asks every frame whether the machine is running or not. NAMED AS EMULATOR MAGIC, because it is. There is no reset line on this machine and no keyboard controller to assert one; the window reaches in and sets the same flag the machine port sets. When those are designed, a keyboard controller will have to see the gesture and pull reset regardless of what the CPU is doing - which is the property that matters and the one a port write can never have, since a port write needs a program willing and able to make it. The shape of that is already visible here: asking every frame rather than leaving it to the machine to notice is what a line does. A restart now clears the cycle limit as well, since a machine stopped for reaching one is another thing somebody would press the button over. The three existing reset tests still pass, and they are the ones that matter: they exercise the same restart through the machine port. What no test reaches is the gesture itself, which exists only when there is a window. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- README.md | 13 +++++++- Source/Emulator/machine.c | 62 ++++++++++++++++++++++++++------------- Source/Emulator/machine.h | 11 +++++++ Source/Emulator/voyager.c | 14 +++++++++ 4 files changed, 79 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0fc99c9..8ac1405 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,18 @@ imitate a login screen. On Windows an application cannot see it without a kernel on Linux the desktop takes it. It is unavailable for the same reason it seemed right. It does exactly what writing to the machine port does: the machine starts the way it started, -so the boot chain runs again and finds whatever the disk now says to run. +so the boot chain runs again and finds whatever the disk now says to run. **And it works on a +machine that has stopped** - one that halted, or faulted, or is a bare metal demo that ended. +That is the whole point of a button: a machine which is not going anywhere is exactly the one +worth restarting, and it is the one that cannot notice a request by itself, because a reset is +otherwise seen between instructions and a halted machine runs none. + +This part is **emulator magic and known to be**. There is no reset line on this machine yet +and no keyboard controller to assert one; the window reaches in and sets the same flag the +machine port sets. When those are designed, a keyboard controller will have to see the gesture +and pull reset regardless of what the CPU is doing - which is the property that matters, and +the one a port write can never have, since a port write needs a program willing and able to +make it. 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 diff --git a/Source/Emulator/machine.c b/Source/Emulator/machine.c index 9b5d26c..cf238e0 100644 --- a/Source/Emulator/machine.c +++ b/Source/Emulator/machine.c @@ -108,6 +108,47 @@ static int keyboardHook(int mayWait) { return byte & 0xFF; } +// ---- Starting over ---- +// +// WHAT A RESET REPEATS IS HOW THIS MACHINE STARTED. Named an image, it is placed again; named +// none, the ROM is shadowed again and reads the disk for the rest. Anything else would mean a +// reset changed what the machine is, which is the one thing a reset must not do. +// +// The disk is not unplugged and its image keeps everything written to it. That is what warm +// means: the machine starts again, the world it starts into does not. +// +// The vector table goes, and that is a deliberate departure from leaving memory alone. A +// vector points into whatever installed it, and after this that program is not running - so a +// handler left behind would aim an interrupt at an address belonging to something gone. It is +// the argument CosmOS already makes when it takes a program's vectors back at exit. +static int machineRestart(Machine *m) { + memset(Program + SOFTWARE_VECTOR_BASE, 0, + (size_t)(0x10000 - SOFTWARE_VECTOR_BASE)); + uint8_t failed = (m->programFile != NULL) + ? loadFile(m->programFile, Program, Data) + : loadROM(bootROM, bootROMBytes, Program, Data); + if (failed) { + fprintf(stderr, "Error: The machine could not be started again.\n"); + m->restartFailed = 1; + return 0; + } + videoReset(); + consoleHome(); + consoleResetInput(); + initializeCPU(&m->cpu, Program, Data); + // A machine that had stopped is running again, which is the entire point of asking from + // outside: the interesting time to restart something is when it is not going anywhere. + m->limitReached = 0; + return 1; +} + +int machineTakeReset(Machine *m) { + if (!takeResetRequest()) { + return 0; + } + return machineRestart(m); +} + uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *programFile) { m->options = *options; m->programFile = programFile; @@ -218,26 +259,7 @@ void machineRunSlice(Machine *m) { // The disk is not unplugged and its image keeps everything written to it. That // is what warm means: the machine starts again, the world it starts into does // not. - if (takeResetRequest()) { - // The vector table goes, and that is a deliberate departure from leaving - // memory alone. A vector points into whatever installed it, and after this - // that program is not running - so a handler left behind would aim an - // interrupt at an address belonging to something gone. It is the argument - // CosmOS already makes when it takes a program's vectors back at exit. - memset(Program + SOFTWARE_VECTOR_BASE, 0, - (size_t)(0x10000 - SOFTWARE_VECTOR_BASE)); - uint8_t failed = (m->programFile != NULL) - ? loadFile(m->programFile, Program, Data) - : loadROM(bootROM, bootROMBytes, Program, Data); - if (failed) { - fprintf(stderr, "Error: The machine could not be started again.\n"); - m->restartFailed = 1; - return; - } - videoReset(); - consoleHome(); - consoleResetInput(); - initializeCPU(&m->cpu, Program, Data); + if (machineTakeReset(m)) { break; // Out of this batch; the loop above carries on with a new CPU. } if (m->cpu.Status & STATUS_HALT) { diff --git a/Source/Emulator/machine.h b/Source/Emulator/machine.h index 4cbdd2f..3f1a556 100644 --- a/Source/Emulator/machine.h +++ b/Source/Emulator/machine.h @@ -65,6 +65,17 @@ int machineRunning(const Machine *m); // is the point of cutting it here rather than anywhere else. void machineRunSlice(Machine *m); +// ---- Asking from outside, when nothing inside is asking ---- +// +// A reset is normally noticed between instructions, which works when there are instructions. +// A HALTED MACHINE RUNS NONE, so a program that has finished - or faulted, or is a bare metal +// demo that ended with HALT - could ask to be restarted for ever and nothing would come along +// to hear it. That is exactly the machine somebody wants to restart. +// +// So a front end calls this every time round its own loop, whether the machine is running or +// not. Returns 1 if it started over. +int machineTakeReset(Machine *m); + void machineStop(Machine *m); // Says how the run went and returns what the process should exit with. diff --git a/Source/Emulator/voyager.c b/Source/Emulator/voyager.c index 6433123..44f4779 100644 --- a/Source/Emulator/voyager.c +++ b/Source/Emulator/voyager.c @@ -128,6 +128,17 @@ static void drainKeyboard(void) { // ---- The reset button ---- // +// EMULATOR MAGIC, AND KNOWN TO BE. There is no reset line on this machine yet and no keyboard +// controller to assert one: the window reaches in and pokes the same flag a program pokes +// through the machine port. When those are designed, a keyboard controller will have to see +// this gesture and pull reset REGARDLESS OF WHAT THE CPU IS DOING - which is the property +// that matters and the one a port write cannot have, since a port write needs a program +// willing and able to make it. +// +// The shape of that is already visible here. A reset is normally noticed between +// instructions, and a halted machine runs none - so the window asks every frame rather than +// leaving it to the machine to notice, which is what real hardware would do with a line. +// // ON REAL HARDWARE THIS IS NOT A KEY AT ALL. A Voyager has a button on the case, and what a // window has instead of a case is a gesture. So the gesture wants two properties a single // key does not have. @@ -344,6 +355,9 @@ int main(int argc, char *argv[]) { // it drew is still there to look at - a program that ends should not take its output // off the screen with it. while (windowOpen && !WindowShouldClose()) { + // Before the running check, not after it: a machine that has stopped is the one + // worth restarting, and it is the one that cannot notice a reset by itself. + machineTakeReset(&machine); if (machineRunning(&machine)) { machineRunSlice(&machine); }