diff --git a/Source/Emulator/machine.c b/Source/Emulator/machine.c index e5e266a..dd81d68 100644 --- a/Source/Emulator/machine.c +++ b/Source/Emulator/machine.c @@ -238,13 +238,13 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro padFromFile(n, pad); } - for (int n = 0; n < options->padRecordCount; n++) { - FILE *pad = fopen(options->padRecord[n], "wb"); + if (options->padRecord != NULL) { + FILE *pad = fopen(options->padRecord, "wb"); if (pad == NULL) { - fprintf(stderr, "Error: Couldn't write pad file: %s\n", options->padRecord[n]); + fprintf(stderr, "Error: Couldn't write pad file: %s\n", options->padRecord); return 0; } - padRecordTo(n, pad); + padRecordTo(pad); } if (options->keyboard != NULL) { diff --git a/Source/Emulator/pad.c b/Source/Emulator/pad.c index 283c415..e709404 100644 --- a/Source/Emulator/pad.c +++ b/Source/Emulator/pad.c @@ -10,7 +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]; +static FILE *recording; // ---- The frame the recordings advance on ---- // @@ -43,11 +43,8 @@ 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 padRecordTo(FILE *file) { + recording = file; } void padFromFile(int which, FILE *file) { @@ -96,11 +93,13 @@ void padTick(unsigned long now) { // 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]); + if (recording != NULL) { + uint8_t all = 0; + for (int n = 0; n < PAD_COUNT; n++) { + all |= effective(n); } + fputc(all, recording); + fflush(recording); } } } diff --git a/Source/Emulator/pad.h b/Source/Emulator/pad.h index 8a45620..c5c8a8f 100644 --- a/Source/Emulator/pad.h +++ b/Source/Emulator/pad.h @@ -86,10 +86,20 @@ void padSet(int which, int connected, uint8_t held); // worth testing: play a file while recording it and the same bytes come out. // // 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 four cells +// base to another is a few hundred frames of steering that has to arrive somewhere eight cells // wide, and hand-authoring one is a piloting exercise rather than a test. Playing it once and // keeping what happened is the whole answer. -void padRecordTo(int which, FILE *file); +// +// EVERY PAD AT ONCE, or-ed into one byte, and not one pad chosen by number. Which pad a +// controller lands on is an accident of the host - the first recording made with this came +// back 1,766 frames of nothing, because it recorded pad nought and the controller was +// somewhere else. A demo is a record of what somebody DID, and on a machine one person is +// playing the number it arrived on is not part of that. +// +// A recording therefore plays back on pad nought, which is where --pad puts the first file +// given. Any program that reads more than one pad reads them or-ed anyway, for exactly the +// same reason. +void padRecordTo(FILE *file); void padTick(unsigned long now); uint8_t padRead(uint8_t port); diff --git a/Source/Emulator/utility.c b/Source/Emulator/utility.c index 5b5f90d..2b55656 100644 --- a/Source/Emulator/utility.c +++ b/Source/Emulator/utility.c @@ -39,9 +39,10 @@ void printHelp(const char *programName) { printf(" 1 right, 2 left, 4 down, 8 up, 16 A, 32 B, 64 start,\n"); printf(" 128 select.\n"); printf(" -Y, --record-pad FILE\n"); - printf(" Write what a controller held, one byte a frame, in the\n"); - printf(" format --pad reads. Given again for the next pad. Play a\n"); - printf(" thing once and keep what happened.\n"); + printf(" Write what the controllers held, one byte a frame, in the\n"); + printf(" format --pad reads. Every pad at once, because which one a\n"); + printf(" controller lands on is the host's business. Play a thing\n"); + printf(" once and keep what happened.\n"); printf(" -N, --sound FILE Save every sample the machine made, as raw signed 16 bit\n"); printf(" at 48kHz. What --screen is for a picture: the only way to\n"); printf(" check a sound on a machine with no speaker.\n"); @@ -142,9 +143,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) { options->sound = optarg; break; case 'Y': - if (options->padRecordCount < PAD_DRIVE_COUNT) { - options->padRecord[options->padRecordCount++] = optarg; - } + options->padRecord = optarg; break; case 'P': // Fills the pads in turn, the same way --disk fills the drives, so the first diff --git a/Source/Emulator/utility.h b/Source/Emulator/utility.h index 52e86ea..0972130 100644 --- a/Source/Emulator/utility.h +++ b/Source/Emulator/utility.h @@ -47,9 +47,10 @@ typedef struct { // line editing was in when it broke twice in two days. const char *pads[PAD_DRIVE_COUNT]; int padCount; // --pad given more than once fills them in turn, like --disk. - // Where to write what each pad held, one byte a frame, in the format --pad reads. - const char *padRecord[PAD_DRIVE_COUNT]; - int padRecordCount; + // Where to write what the controllers held, one byte a frame, in the format --pad reads. + // Every pad or-ed together, because which one a controller lands on is the host's business + // and a demo is a record of what somebody did. + const char *padRecord; } EmulatorOptions; uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options); diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 7659a9e..44c2c57 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -76,6 +76,13 @@ A recording is written one byte a frame and is only worth having if playing it b was recorded, so the strongest form of that claim is checked: a recording is made **of a playback**, and the bytes coming out have to be the bytes that went in. +**Every pad is recorded at once**, or-ed into one byte, rather than one pad chosen by number. +Which pad a controller lands on is an accident of the host: the first recording ever made with +this came back 1,766 frames of nothing, because it recorded pad 0 and the controller was +somewhere else, and the flight had to be flown again for nothing. A demo is a record of what +somebody *did*, and on a machine one person is playing the number it arrived on is not part of +that. + 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 by hand got within two columns and no closer. That diff --git a/Tests/replay.sh b/Tests/replay.sh index a90e7da..3759124 100755 --- a/Tests/replay.sh +++ b/Tests/replay.sh @@ -65,6 +65,26 @@ print('yes' if again[:len(first)] == first else 'no') && result ok "a recording of a playback is the playback" "byte for byte, in and out" \ || result no "a recording of a playback is the playback" "the bytes differ" +# ---- And it does not matter which controller it was ---- +# +# The first recording ever made with this came back 1,766 frames of nothing. It recorded pad +# NOUGHT and the controller was somewhere else - which pad one lands on is an accident of the +# host, and the flight had to be flown again for nothing. +# +# So every pad is or-ed into the byte. Here the same recording is played on pad ONE, with +# nought holding nothing, and what comes out has to be what went in. +python3 -c "open('$BUILD/idle.pad','wb').write(b'\x00' * 64)" +"$EMU" --fast --pad "$BUILD/idle.pad" --pad "$ROOT/Tests/input/padTest.pad" \ + --record-pad "$BUILD/padone.pad" "$BUILD/padTest.bin" > "$BUILD/padone.out" 2>&1 +ELSEWHERE="$(python3 -c " +first = open('$ROOT/Tests/input/padTest.pad', 'rb').read() +again = open('$BUILD/padone.pad', 'rb').read() +print('yes' if again[:len(first)] == first else 'no') +" 2>/dev/null || echo error)" +[ "$ELSEWHERE" = "yes" ] \ + && result ok "a controller on any pad is recorded" "flown on pad one, written all the same" \ + || result no "a controller on any pad is recorded" "the bytes differ" + # ---- One byte a frame, and the frame is the machine's ---- # # A recording is a TIMELINE. If it were a byte a read, a program that polled twice in one diff --git a/demo.pad b/demo.pad index 5da401c..bba2c30 100644 Binary files a/demo.pad and b/demo.pad differ diff --git a/start.keys b/start.keys new file mode 100644 index 0000000..3c2ab22 Binary files /dev/null and b/start.keys differ