diff --git a/Source/Emulator/pad.c b/Source/Emulator/pad.c index e709404..2baf08f 100644 --- a/Source/Emulator/pad.c +++ b/Source/Emulator/pad.c @@ -10,6 +10,24 @@ static uint8_t held[PAD_COUNT]; static FILE *recorded[PAD_COUNT]; static uint8_t live[PAD_COUNT]; static int connected[PAD_COUNT]; + +// ---- What the machine is told, which changes only on a frame ---- +// +// The live state is written by whatever is watching real hardware, on ITS clock - a window +// polls its keyboard once a host frame, and a host frame is not a machine frame. Read +// straight through, that made a pad whose value could change in the middle of a machine +// frame, which breaks the one promise the device makes: that asking twice in a frame gives +// the same answer both times. +// +// It also made recordings that were not of the flight. The recorder samples on a frame +// boundary and the program reads whenever it reads, so the two saw different bytes - and a +// replay of that is a DIFFERENT FLIGHT, faithfully reproduced. It flew a lander off the top +// of the screen that had never gone there. +// +// So the live state is latched here once a frame, and what the machine reads and what the +// recorder writes are the same thing by construction. Real hardware does this too, and for +// the same reason: a controller is sampled once a frame, not continuously. +static uint8_t reported[PAD_COUNT]; static FILE *recording; // ---- The frame the recordings advance on ---- @@ -28,6 +46,7 @@ void padReset(void) { for (int n = 0; n < PAD_COUNT; n++) { held[n] = 0; live[n] = 0; + reported[n] = 0; connected[n] = 0; // The files are NOT closed or forgotten. They were named on the command line and // outlive a reset, the same as a disk image does: a machine that restarted itself @@ -40,7 +59,7 @@ void padReset(void) { // What the device would report for this pad, which is what a recording has to hold: a // recording of a playback that wrote the LIVE state would be a file of noughts. static uint8_t effective(int which) { - return (recorded[which] != NULL) ? held[which] : live[which]; + return (recorded[which] != NULL) ? held[which] : reported[which]; } void padRecordTo(FILE *file) { @@ -72,6 +91,9 @@ void padTick(unsigned long now) { while (now - lastFrame >= VIDEO_FRAME_CYCLES) { lastFrame += VIDEO_FRAME_CYCLES; for (int n = 0; n < PAD_COUNT; n++) { + // The live state is taken as it stands at the frame boundary and held there + // until the next one, so nothing the machine reads was never recorded. + reported[n] = live[n]; if (recorded[n] == NULL) { continue; } diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index fce014d..2fcb78b 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -1219,6 +1219,8 @@ The console says **which key went down**. That is the right shape for typing and A pad reports a **level** rather than an event. One read gives every button at once, holding is the natural thing to express, and two directions together cost nothing. **Reading does not consume it** - a game may ask twice in a frame and be told the same thing both times, which an event queue cannot promise. +**A pad is sampled once a frame**, and what it reports does not change in between. That is not an implementation detail: what is watching a real controller runs on its own clock - a window polls its keyboard once a *host* frame, which is not a machine frame - and read straight through, a pad's value could change in the middle of a frame and a program asking twice would get two answers. Real hardware latches a controller once a frame for the same reason. + Key-up on the console would have been the other way to do it, and it was rejected: a terminal hands over characters and can never report a key coming up however it is asked, so it would have been a thing that worked behind a window and silently did not down a wire. A separate device can honestly say it is not there. **They never interrupt.** A game polls once a frame because that is when it draws, and an interrupt for every button would be exactly the event model a pad exists to avoid. diff --git a/demo.pad b/demo.pad index bba2c30..0142fcb 100644 Binary files a/demo.pad and b/demo.pad differ