A demo recorder: --record-pad writes what --pad reads
One byte a frame, in exactly the format the player takes, so a recording needs no conversion and there is no second format to keep in step. That symmetry is the feature, and it makes the strongest form of the claim testable: a recording is made OF a playback, and the bytes coming out have to be the bytes that went in. It exists because some inputs cannot sensibly be written by hand. Flying a lander from one base to another is a few hundred frames of steering that has to arrive somewhere eight cells wide, and several attempts at authoring one got within two columns and no closer. That is a piloting exercise rather than a test. Playing it once and keeping what happened is the answer. A BYTE FOR EVERY FRAME, written inside the loop that advances the recordings rather than after it, so a machine that jumped several frames at once still writes one for each. A recording is a timeline: one that skipped the frames nobody looked at would play back faster than it was flown. What is recorded is what the DEVICE WOULD REPORT, not the live state - a recording of a playback that wrote the live state would be a file of noughts. And it is flushed as it goes, because a recording is usually stopped by whoever is playing rather than by the program ending, and a demo lost to a buffer is a demo flown twice. Tests/replay.sh is where this and whatever follows it are checked. Twelve scripts now. 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
caf5e1f99d
commit
a163c670d0
+31
-1
@@ -10,6 +10,7 @@ static uint8_t held[PAD_COUNT];
|
||||
static FILE *recorded[PAD_COUNT];
|
||||
static uint8_t live[PAD_COUNT];
|
||||
static int connected[PAD_COUNT];
|
||||
static FILE *recording[PAD_COUNT];
|
||||
|
||||
// ---- The frame the recordings advance on ----
|
||||
//
|
||||
@@ -36,6 +37,19 @@ void padReset(void) {
|
||||
started = 0;
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
void padRecordTo(int which, FILE *file) {
|
||||
if (which < 0 || which >= PAD_COUNT) {
|
||||
return;
|
||||
}
|
||||
recording[which] = file;
|
||||
}
|
||||
|
||||
void padFromFile(int which, FILE *file) {
|
||||
if (which < 0 || which >= PAD_COUNT) {
|
||||
return;
|
||||
@@ -72,6 +86,22 @@ void padTick(unsigned long now) {
|
||||
// off the edge of the world long after the test meant to stop.
|
||||
held[n] = (byte == EOF) ? 0 : (uint8_t)byte;
|
||||
}
|
||||
// ---- And a byte written for every frame that went by ----
|
||||
//
|
||||
// Inside the loop rather than after it, so a machine that jumped several frames at
|
||||
// once still writes one byte for each of them. A recording is a TIMELINE, and one
|
||||
// that skipped the frames nobody was looking at would play back faster than it was
|
||||
// flown.
|
||||
//
|
||||
// Flushed as it goes, because a recording is usually stopped by whoever is playing
|
||||
// rather than by the program ending, and a demo lost to a buffer would be a demo
|
||||
// flown twice.
|
||||
for (int n = 0; n < PAD_COUNT; n++) {
|
||||
if (recording[n] != NULL) {
|
||||
fputc(effective(n), recording[n]);
|
||||
fflush(recording[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +126,5 @@ uint8_t padRead(uint8_t port) {
|
||||
}
|
||||
// A recording wins over a live pad, so a test is not at the mercy of whatever somebody
|
||||
// is leaning on while it runs.
|
||||
return (recorded[which] != NULL) ? held[which] : live[which];
|
||||
return effective(which);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user