diff --git a/Programs/testPrograms/padTest.asm b/Programs/testPrograms/padTest.asm new file mode 100644 index 0000000..888b678 --- /dev/null +++ b/Programs/testPrograms/padTest.asm @@ -0,0 +1,51 @@ +; Asks the machine what its controllers are doing. +; +; A pad reports a LEVEL and not an event: one read gives every button at once, holding is the +; natural thing to say, and reading does not consume anything - so asking twice in a frame +; gives the same answer twice, which this checks by doing exactly that. +; +; The recording behind it is one byte a frame. It presses up, holds it a second frame, adds +; right, lets go of up, lets go of everything, then presses A and B together - which is the +; case a console's key-at-a-time stream cannot express at all. +; +; Written by Anachronaut + +#Program + +start: + INA 0x64 + OUTA 0x00 ; Which pads are there: one bit each, so pad nought alone is 1. + RSTA + SETD.0 Count + STA.0 + +everyFrame: + INA 0x30 + INIB 0x01 + AND + BRQ everyFrame ; The frame the recording steps on. + + INA 0x60 + OUTA 0x00 + ; And again, without a frame in between. A level does not go away when it is looked at. + INA 0x60 + OUTA 0x00 + + SETD.0 Count + LDA.0 + INCA + STA.0 + INIB 0d6 + CCF + SUB + BNQ everyFrame + + ; A pad that is not there reads as nothing held, which is honest rather than an error. + INA 0x63 + OUTA 0x00 + HALT + +#Data + +Count: + 0x00 diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 52e5554..ba1ee2b 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -7,6 +7,7 @@ #include "../Assembler/assembly.h" // For the fault vector numbers. #include "controller.h" #include "video.h" +#include "pad.h" #include "sound.h" #include "font.h" #include @@ -1168,6 +1169,9 @@ void deviceTick(unsigned long now) { soundTick(now); // And the timer, which is the only beat a program can choose for itself. timerTick(now); + // And the pads, whose recordings step on a frame so that a level read twice in one is + // the same level both times. + padTick(now); if (diskPending && now >= diskReadyAt) { diskSettle(); } @@ -1341,6 +1345,7 @@ static const DeviceRecord deviceTable[] = { { PORT_VIDEO, DEVICE_VIDEO, DEVICE_FLAG_HAS_MEMORY }, { PORT_SOUND, DEVICE_SOUND, 0 }, { PORT_TIMER, DEVICE_TIMER, 0 }, + { PORT_PAD, DEVICE_PAD, 0 }, { PORT_REGISTRY, DEVICE_REGISTRY, 0 }, }; static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0])); @@ -1393,6 +1398,11 @@ static const DeviceRecord *deviceOnPort(uint8_t port) { if (port > PORT_TIMER && port <= PORT_TIMER_TOP) { return deviceOnPort(PORT_TIMER); } + // Sixteen ports, one device: four pads, a port saying which are there, and eleven kept + // for the analogue axes that are not built. + if (port > PORT_PAD && port <= PORT_PAD_TOP) { + return deviceOnPort(PORT_PAD); + } for (int i = 0; i < deviceCount; i++) { if (deviceTable[i].port == port) { return &deviceTable[i]; @@ -1570,6 +1580,9 @@ uint8_t InputHandler(uint8_t Address) { if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) { return soundRead(Address); } + if (Address >= PORT_PAD && Address <= PORT_PAD_TOP) { + return padRead(Address); + } if (Address >= PORT_TIMER && Address <= PORT_TIMER_TOP) { return timerRead(Address); } diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index 7012030..fa10e79 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -314,6 +314,9 @@ void consoleSetInputHook(int (*hook)(int mayWait)); #define DEVICE_VIDEO 0x14 #define DEVICE_SOUND 0x15 #define DEVICE_TIMER 0x16 +// Game controllers. Polled, never interrupting: a game asks once a frame because that is when +// it draws, and an interrupt for every button would be the event model a pad exists to avoid. +#define DEVICE_PAD 0x17 // What a device brings besides itself. This means memory that somebody has to register // with the controller, so the controller's own bank 2 does not count: it is already there. diff --git a/Source/Emulator/machine.c b/Source/Emulator/machine.c index 6f6f833..c5aa7e7 100644 --- a/Source/Emulator/machine.c +++ b/Source/Emulator/machine.c @@ -8,6 +8,7 @@ #include "cpu.h" #include "controller.h" #include "io.h" +#include "pad.h" #include "video.h" #include "sound.h" #include "utility.h" @@ -134,6 +135,7 @@ static int machineRestart(Machine *m) { return 0; } videoReset(); + padReset(); soundReset(); timerReset(); consoleHome(); @@ -207,6 +209,7 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro // the device's, and a reset that left last program's screen up would be a reset that // did not happen. videoReset(); + padReset(); soundReset(); timerReset(); if (options->sound != NULL) { @@ -220,6 +223,21 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro if (m->options.debug) { printRegisters(&m->cpu, Program, Data); } + // ---- The pads, from files ---- + // + // Opened here beside the keyboard because they are the same kind of thing: a recording + // standing in for a person, so that what a person would exercise is reachable from a + // suite. They are never closed, for the same reason the keyboard is not - the machine + // outlives the call and the host reclaims them when it stops. + for (int n = 0; n < options->padCount; n++) { + FILE *pad = fopen(options->pads[n], "rb"); + if (pad == NULL) { + fprintf(stderr, "Error: Couldn't open pad file: %s\n", options->pads[n]); + return 0; + } + padFromFile(n, pad); + } + if (options->keyboard != NULL) { keyboardFile = fopen(options->keyboard, "rb"); if (keyboardFile == NULL) { diff --git a/Source/Emulator/pad.c b/Source/Emulator/pad.c new file mode 100644 index 0000000..b424f61 --- /dev/null +++ b/Source/Emulator/pad.c @@ -0,0 +1,94 @@ +// pad.c +// Game controllers for the Voyager. +// Written by Anachronaut + +#include "pad.h" +#include "video.h" + +// What each pad is holding, and where each one gets it from. +static uint8_t held[PAD_COUNT]; +static FILE *recorded[PAD_COUNT]; +static uint8_t live[PAD_COUNT]; + +// ---- The frame the recordings advance on ---- +// +// The screen's frame, and it is the same one on purpose: a game reads its pad once a frame +// because that is when it draws, so a byte a frame is a byte a poll for anything written the +// ordinary way - without making it a byte a READ, which would answer a game that asked twice +// differently from one that asked once. +// +// On the machine's clock, so a recording plays back the same over the same cycles however +// fast the host ran. +static unsigned long lastFrame; +static int started; + +void padReset(void) { + for (int n = 0; n < PAD_COUNT; n++) { + held[n] = 0; + live[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 + // and lost its controllers would be a strange thing to debug. + } + lastFrame = 0; + started = 0; +} + +void padFromFile(int which, FILE *file) { + if (which < 0 || which >= PAD_COUNT) { + return; + } + recorded[which] = file; +} + +void padSet(int which, uint8_t heldNow) { + if (which < 0 || which >= PAD_COUNT) { + return; + } + live[which] = heldNow; +} + +void padTick(unsigned long now) { + // The first tick sets the clock rather than counting a frame from nought, or a machine + // that started late would take a run of bytes all at once. + if (!started) { + lastFrame = now; + started = 1; + } + while (now - lastFrame >= VIDEO_FRAME_CYCLES) { + lastFrame += VIDEO_FRAME_CYCLES; + for (int n = 0; n < PAD_COUNT; n++) { + if (recorded[n] == NULL) { + continue; + } + const int byte = fgetc(recorded[n]); + // ---- The end of a recording is nothing held ---- + // + // Not a pad that vanishes and not the last frame repeating for ever. A recording + // that ran out and left a direction pressed would send whatever it was driving + // off the edge of the world long after the test meant to stop. + held[n] = (byte == EOF) ? 0 : (uint8_t)byte; + } + } +} + +uint8_t padRead(uint8_t port) { + if (port == PAD_PRESENT) { + uint8_t there = 0; + for (int n = 0; n < PAD_COUNT; n++) { + if (recorded[n] != NULL) { + there |= (uint8_t)(1u << n); + } + } + return there; + } + const int which = port - PORT_PAD; + if (which < 0 || which >= PAD_COUNT) { + // Everything else in the block is reserved and reads as nothing, which is what a + // port block being kept for later should do. + return 0; + } + // 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]; +} diff --git a/Source/Emulator/pad.h b/Source/Emulator/pad.h new file mode 100644 index 0000000..03dfcd0 --- /dev/null +++ b/Source/Emulator/pad.h @@ -0,0 +1,79 @@ +// pad.h +// Game controllers for the Voyager. +// Written by Anachronaut + +#ifndef PAD_H +#define PAD_H + +#include +#include + +// ---- What a pad is, and why it is not the console ---- +// +// The console says WHICH KEY WENT DOWN. That is the right shape for typing and the wrong one +// for playing: a game wants to know what is being held, this frame, possibly several things +// at once, and a stream of presses cannot say that. A key that is down and staying down sends +// nothing at all. +// +// So a pad reports a LEVEL rather than an event. One read gives the state of every button at +// once, holding is the natural thing to express, and two directions at the same time costs +// nothing. Reading it does not consume it: a game may ask twice in a frame and get the same +// answer both times, which an event queue cannot promise. +// +// IT IS NOT AN EXTENSION OF THE CONSOLE, and that is deliberate. A terminal hands over +// characters and cannot report a key coming up however it is asked, so key-up on the console +// would have been a thing that worked behind a window and silently did not down a wire - and +// "the same program behaves the same everywhere" is worth more than the convenience. A +// separate device can honestly say it is not there. + +#define PORT_PAD 0x60 +#define PORT_PAD_TOP 0x6F + +// Four, because a party is four. They cost a port each and nothing at all when unused. +#define PAD_COUNT 4 + +// ---- The buttons, in one byte ---- +// +// The four directions in the low nibble, so "which way" is an AND with 0x0F and needs no +// shifting. The four buttons in the high nibble for the same reason. +#define PAD_RIGHT 0x01 +#define PAD_LEFT 0x02 +#define PAD_DOWN 0x04 +#define PAD_UP 0x08 +#define PAD_A 0x10 +#define PAD_B 0x20 +#define PAD_START 0x40 +#define PAD_SELECT 0x80 + +// ---- Which of them are there ---- +// +// One bit a pad, so a game can say "this wants a controller" rather than sitting silent while +// somebody presses things at it. A pad that is not there reads as nothing held, which is the +// same as a pad nobody is touching - the difference matters only to whoever wants to explain +// it, and that is exactly who this port is for. +#define PAD_PRESENT 0x64 + +// Ports 0x65 to 0x6F are reserved. Analogue axes are the thing they are being kept for: a +// paddle is two more bytes a pad and an argument about deadzones, and nothing wants one yet. + +void padReset(void); + +// ---- Recorded input, which is what makes this testable ---- +// +// Voyager reads a real pad, and its tests run headless with no window and no hands. Without a +// recorded path this whole device would be exercised only by somebody playing, which is the +// state the console's line editing was in when it broke twice in two days. +// +// A file is one byte a FRAME, not one byte a read. A pad is a level: a game that asks twice +// in one frame has to be told the same thing both times, and a file that advanced per read +// would answer differently depending on how the game was written. +void padFromFile(int which, FILE *file); + +// A live pad, set by whatever is watching real hardware. Ignored for a pad that has a file, +// so a recording always wins over whatever somebody happens to be holding. +void padSet(int which, uint8_t held); + +void padTick(unsigned long now); +uint8_t padRead(uint8_t port); + +#endif // PAD_H diff --git a/Source/Emulator/utility.c b/Source/Emulator/utility.c index 7ddbb1e..fe248cf 100644 --- a/Source/Emulator/utility.c +++ b/Source/Emulator/utility.c @@ -34,6 +34,10 @@ void printHelp(const char *programName) { printf(" keyboard rather than a terminal. Which means the console does\n"); printf(" its own line editing, the way it must when a window is open\n"); printf(" and there is no terminal behind it to do it.\n"); + printf(" -P, --pad FILE Hold a controller from a file, one byte a frame. Given\n"); + printf(" again for the next pad. A byte is the buttons held:\n"); + printf(" 1 right, 2 left, 4 down, 8 up, 16 A, 32 B, 64 start,\n"); + printf(" 128 select.\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"); @@ -52,6 +56,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) { {"screen", required_argument, 0, 'S'}, {"keyboard", required_argument, 0, 'K'}, {"sound", required_argument, 0, 'N'}, + {"pad", required_argument, 0, 'P'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0 } }; @@ -71,7 +76,7 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) { *options = (EmulatorOptions){0}; // Parse options - while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:R:", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:R:P:", long_options, &option_index)) != -1) { switch (opt) { case 'd': options->debug = 1; @@ -131,6 +136,13 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) { case 'N': options->sound = optarg; break; + case 'P': + // Fills the pads in turn, the same way --disk fills the drives, so the first + // one named is pad nought and the machine has as many as were asked for. + if (options->padCount < PAD_DRIVE_COUNT) { + options->pads[options->padCount++] = optarg; + } + break; case 'h': printHelp(argv[0]); return OPTIONS_HELP; diff --git a/Source/Emulator/utility.h b/Source/Emulator/utility.h index eb809ce..16971c1 100644 --- a/Source/Emulator/utility.h +++ b/Source/Emulator/utility.h @@ -16,6 +16,9 @@ #define OPTIONS_HELP 1 // The user asked for help, so stop, but not because of an error. #define OPTIONS_ERROR 2 // The command line was no good, stop and complain. +// As many as the machine has pads. +#define PAD_DRIVE_COUNT 4 + typedef struct { uint8_t debug; // Step one instruction at a time, printing the registers. uint8_t fast; // Ignore the cycle rate and run as fast as the host allows. @@ -36,6 +39,14 @@ typedef struct { const char *screen; // Where to save a picture of the screen when the machine stops. const char *keyboard; // Feed the console from this file as a keyboard, not a terminal. const char *sound; // Where to save the samples the machine made, as raw 16 bit. + // ---- A controller made of a file ---- + // + // One byte a frame, each byte the buttons held during it. Voyager reads a real pad, and + // its own tests run headless with nobody holding anything - so without this the device + // would be exercised only by somebody playing, which is exactly the state the console's + // 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. } EmulatorOptions; uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options); diff --git a/Source/Emulator/voyager.c b/Source/Emulator/voyager.c index 4237fbf..2f93a19 100644 --- a/Source/Emulator/voyager.c +++ b/Source/Emulator/voyager.c @@ -22,6 +22,7 @@ #include "machine.h" #include "video.h" +#include "pad.h" #include "sound.h" #include "io.h" #include "utility.h" @@ -106,6 +107,48 @@ static int keyTake(void) { } // Everything Raylib has, taken before it can throw any of it away. +// ---- The pads, read as levels ---- +// +// Once a frame, from whatever is actually there. THIS IS THE ONE THING THE CONSOLE CANNOT DO: +// a window knows which keys are down, a terminal only ever learns which one was pressed, and +// asking the console to report a key coming up would have been a promise it could keep behind +// a window and nowhere else. +// +// The keyboard drives pad nought as well as any real controller, so a game written for a pad +// is playable on a machine with none - and so is a game written for four, badly. What a game +// reads is the pad; it never learns which one of them somebody used. +static void readPads(void) { + for (int n = 0; n < PAD_COUNT; n++) { + uint8_t held = 0; + if (IsGamepadAvailable(n)) { + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_LEFT_FACE_RIGHT)) { held |= PAD_RIGHT; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_LEFT_FACE_LEFT)) { held |= PAD_LEFT; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_LEFT_FACE_DOWN)) { held |= PAD_DOWN; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_LEFT_FACE_UP)) { held |= PAD_UP; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) { held |= PAD_A; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT)){ held |= PAD_B; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_MIDDLE_RIGHT)) { held |= PAD_START; } + if (IsGamepadButtonDown(n, GAMEPAD_BUTTON_MIDDLE_LEFT)) { held |= PAD_SELECT; } + } + if (n == 0) { + // ---- And the keyboard, on top of it ---- + // + // Arrows or WASD for the direction, Z and X for the buttons. OR-ed with whatever + // a real pad is doing rather than chosen between, so unplugging one mid-game does + // not leave somebody holding nothing. + if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) { held |= PAD_RIGHT; } + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) { held |= PAD_LEFT; } + if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) { held |= PAD_DOWN; } + if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) { held |= PAD_UP; } + if (IsKeyDown(KEY_Z)) { held |= PAD_A; } + if (IsKeyDown(KEY_X)) { held |= PAD_B; } + if (IsKeyDown(KEY_ENTER)) { held |= PAD_START; } + if (IsKeyDown(KEY_TAB)) { held |= PAD_SELECT; } + } + padSet(n, held); + } +} + static void drainKeyboard(void) { int character; while ((character = GetCharPressed()) > 0) { @@ -276,6 +319,7 @@ static void presentFrame(void) { EndDrawing(); // EndDrawing has just polled, which is the one moment Raylib's queues hold anything. drainKeyboard(); + readPads(); checkResetButton(); } diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index 22c7f26..7b7be15 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -602,6 +602,7 @@ If nothing is installed for the vector a device refused with, the machine stops | 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 | | 0x40 - 0x4F | The sound device. See Making A Noise. Four channels, played by writing to ports; it brings no memory. | 0x15 | | 0x50 - 0x54 | The timer. See Keeping Time. Counts the machine's cycles and says when a period has gone by. | 0x16 | +| 0x60 - 0x6F | The game controllers. See Controllers. Four pads, polled, reporting what is held. | 0x17 | | 0xE0 - 0xEF | The memory controller. See The Memory Controller. | 0x03 | | 0xFF | The bus registry. See Asking What Is There. | 0x01 | @@ -1167,8 +1168,48 @@ One thing to be careful of: the registry remembers which port it was asked about | 0x14 | Screen. | | 0x15 | Sound. | | 0x16 | Timer. | +| 0x17 | Game controllers. | | 0x17 - 0xFF | Peripherals. | +## Controllers: + +Four pads on ports 0x60 to 0x6F. Each one is **one byte, read, saying what is held right now**. + +| Port | Holds | +| --- | --- | +| 0x60 - 0x63 | Pads 0 to 3. | +| 0x64 | Which pads are there, one bit each. | +| 0x65 - 0x6F | Reserved. | + +| Bit | Button | +| --- | --- | +| 0x01 | Right | +| 0x02 | Left | +| 0x04 | Down | +| 0x08 | Up | +| 0x10 | A | +| 0x20 | B | +| 0x40 | Start | +| 0x80 | Select | + +The four directions are the low nibble, so *which way* is an `AND` with `0x0F` and needs no shifting. The four buttons are the high nibble for the same reason. + +### Why This Is Not The Console: + +The console says **which key went down**. That is the right shape for typing and the wrong one for playing: a game wants to know what is being held, this frame, possibly several things at once, and a stream of presses cannot say that. A key that is down and staying down sends nothing at all. + +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. + +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. + +### When There Is No Pad: + +A pad that is not there reads as nothing held, which is the same as a pad nobody is touching. The difference matters only to whoever wants to explain it, so 0x64 says which are really there and a game can ask for a controller rather than sitting silent while somebody presses things at it. + +`Programs/testPrograms/padTest.asm` reads one twice a frame to show that looking does not take it away. + ## Keeping Time: A period, in cycles, and a bit that says when one has gone by. diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 1b08b6e..a3e9d7f 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -101,7 +101,7 @@ from `make`, not from here. ### 1. Recorded output `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares -everything it printed against a file in `Tests/expected`. 206 tests, of which 144 run, 35 +everything it printed against a file in `Tests/expected`. 207 tests, of which 145 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. @@ -404,6 +404,21 @@ A keyboard file installs the same hook a window does, so the same path runs. It test the window: Voyager's own key queue is still out of reach, and so is anything about presenting frames. It tests the console, which is where the logic is. +**pad** names a file in `Tests/input` to be held on a controller, one byte a frame, each +byte the buttons held during it. It exists for the same reason as **keys** and matters more: +a pad reports what is *held*, and a suite has no hands. + +**A byte a frame, not a byte a read.** A pad is a level, so a game that asks twice in one +frame has to be told the same thing both times, and a file that advanced per read would +answer differently depending on how the program happened to be written. The frame is the +machine's own, so a recording plays back the same over the same cycles however fast the host +ran. + +It is also the only way this device is reachable at all. Voyager reads a real controller and +its own tests run `--headless`, with no window and nobody holding anything - so without a +recorded path a pad would be exercised only by somebody playing, which is exactly the state +the console's line editing was in when it broke twice in two days. + ## Fixture Disks: `Tests/makedisks.sh` builds 27 images with SplitDisk before anything runs, into diff --git a/Tests/expected/padTest.out b/Tests/expected/padTest.out new file mode 100644 index 0000000..1ebd81d Binary files /dev/null and b/Tests/expected/padTest.out differ diff --git a/Tests/input/padTest.pad b/Tests/input/padTest.pad new file mode 100644 index 0000000..0401319 Binary files /dev/null and b/Tests/input/padTest.pad differ diff --git a/Tests/manifest b/Tests/manifest index 5b0844a..6662bb4 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -4,7 +4,7 @@ # One test per line, fields separated by '|'. Blank lines and lines starting # with '#' are ignored. # -# name | source | mode | stdin | limit | disk | keys +# name | source | mode | stdin | limit | disk | keys | pad # # source is relative to Programs/. Everything assembles from there with # Libraries/ on the include path, and the binary is written into Tests/build. @@ -1000,6 +1000,18 @@ cosmosFlip | CosmOS/Source/cosmos.asm | run | cosmosFli # the ball never moved at all - the whole 150 frames of it went past in the shell, reading a # line made of nulls. Through a keyboard a null is silence, which is what makes it a wait. cosmosSprite | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | cosmosSprite.keys +# ---- A controller, which is a level and not an event ---- +# +# The eighth column is a pad fixture: one byte a frame, each byte the buttons held during it. +# It exists for the same reason the keyboard fixture does - a device only a person can work is +# a device nothing checks - and it matters more here, because a pad is the ONE THING THE +# CONSOLE CANNOT DO. A terminal reports which key was pressed and can never report one coming +# up, so holding a direction, or holding two buttons at once, has no expression there at all. +# +# The recording presses up, holds it, adds right, lets go of up, lets go of everything, then +# presses A and B together. The program reads each frame twice to show that looking does not +# take it away. +padTest | testPrograms/padTest.asm | run | - | - | - | - | padTest.pad # Which disk the registers mean. Several disks are one controller with a drive register # rather than several devices, because a port is an immediate byte inside the instruction # that names it and a program cannot compute one. Run with a single disk, so drive 1 is a diff --git a/Tests/run.sh b/Tests/run.sh index 1afd53f..45beb61 100755 --- a/Tests/run.sh +++ b/Tests/run.sh @@ -171,7 +171,7 @@ uncolour() { sed -i -E 's/\x1b\[[0-9;]*m//g' "$1" } -while IFS='|' read -r name src mode stdin limit disk keys; do +while IFS='|' read -r name src mode stdin limit disk keys pad; do name="$(trim "$name")" [ -z "$name" ] && continue case "$name" in \#*) continue ;; esac @@ -179,8 +179,10 @@ while IFS='|' read -r name src mode stdin limit disk keys; do mode="$(trim "$mode")"; stdin="$(trim "$stdin")" limit="$(trim "$limit")"; disk="$(trim "$disk")" keys="$(trim "${keys:-}")" + pad="$(trim "${pad:-}")" [ -z "$disk" ] && disk="-" [ -z "$keys" ] && keys="-" + [ -z "$pad" ] && pad="-" wanted "$name" || continue @@ -247,6 +249,19 @@ while IFS='|' read -r name src mode stdin limit disk keys; do fi EMUARGS+=(--keyboard "$INPUT/$keys") fi + # ---- And a controller made of a file ---- + # + # A pad reports what is HELD, and a suite has no hands. One byte a frame, which is + # what makes a device that only a person could otherwise exercise into one the + # recordings cover - the same argument as the keyboard fixture above. + if [ "$pad" != "-" ]; then + if [ ! -f "$INPUT/$pad" ]; then + FAIL=$((FAIL + 1)); FAILED_NAMES+=("$name") + report "FAIL" "$name" "missing pad fixture $pad" + continue + fi + EMUARGS+=(--pad "$INPUT/$pad") + fi [ "$limit" != "-" ] && EMUARGS+=(--cycles "$limit") # A disk starts fresh for every test, so a test cannot pass because of what a # previous one left lying on it. How that is arranged differs between a scratch diff --git a/makefile b/makefile index 112e57c..a193183 100644 --- a/makefile +++ b/makefile @@ -57,7 +57,7 @@ OBJ_DIR = Object # SplitBit from Voyager is one file each: a terminal or a window. Anything that drifts out # of the shared list and into one of those is behaviour the other does not have, which is # the thing this split exists to prevent. -MACHINE_SRCS = machine.c io.c controller.c video.c font.c sound.c synth.c utility.c cpu.c bootstrap.c assembly.c rom.c +MACHINE_SRCS = machine.c io.c controller.c video.c pad.c font.c sound.c synth.c utility.c cpu.c bootstrap.c assembly.c rom.c EMU_SRCS = emulator.c $(MACHINE_SRCS) VOY_SRCS = voyager.c $(MACHINE_SRCS) ASM_SRCS = Assembler.c assembly.c firstPass.c Assm-util.c secondPass.c