A pad is sampled once a frame, and the recorder writes what was sampled
The live state is written by whatever watches real hardware, on ITS clock: a window polls its keyboard once a HOST frame, which is not a machine frame. Read straight through, that made a pad whose value could change in the middle of a machine frame - breaking the one promise the device makes, that asking twice in a frame gives the same answer both times. The manual said it could not happen and the code allowed it. It also made recordings that were not of the flight. The recorder sampled on a frame boundary and the program read whenever it read, so the two saw different bytes. 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, and every check said the replay was deterministic and re-recorded as itself, because it was. Both were true and neither was the point. So the live state is latched once a frame. What the machine reads and what the recorder writes are now the same thing by construction rather than by two clocks happening to agree. Real hardware latches a controller once a frame for the same reason. WHAT IS STILL NOT COVERED: the latch itself. Every recorded pad already changes only on a frame, so the tests cannot tell a latched live pad from an unlatched one - the case that went wrong is the one with a real hand on a real controller, which is the case a headless suite has none of. Said here rather than left to look tested. 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
de1857f5f7
commit
ee77d79780
+23
-1
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user