The console says WHICH KEY WENT DOWN, which 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. Lunar Porter's thrust is a burn per press for exactly that reason. So a pad is its own device on ports 0x60 to 0x6F, reporting a LEVEL. One read gives every button at once, holding is the natural thing to express, two directions together cost nothing, and reading does not consume it - a game may ask twice in a frame and be told the same thing both times. Four of them, because a party is four. They cost a port each and nothing at all when unused. The directions are the low nibble so "which way" is an AND with 0x0F; the buttons are the high nibble for the same reason. 0x64 says which are really there, so a game can ask for a controller rather than sitting silent while somebody presses things at it. They never interrupt: a game polls once a frame because that is when it draws. KEY-UP ON THE CONSOLE WAS THE OTHER WAY TO DO THIS AND 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. Voyager drives pad nought from the keyboard as well as from any real controller, OR-ed rather than chosen between, so a game written for a pad is playable on a machine with none and unplugging one mid-game does not leave somebody holding nothing. And a recorded path, which is what makes any of it testable: --pad names a file of one byte a frame, and the manifest has an eighth column for it. A BYTE A FRAME AND NOT A BYTE A READ - a level asked twice in one frame has to answer the same both times, and a file that advanced per read would depend on how the program happened to be written. Voyager's own tests run headless with nobody holding anything, so without this the device would be exercised only by somebody playing: the state the console's line editing was in when it broke twice in two days. 0x50 is the timer, not free. The block this went in was chosen after looking rather than before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
80 lines
3.3 KiB
C
80 lines
3.3 KiB
C
// pad.h
|
|
// Game controllers for the Voyager.
|
|
// Written by Anachronaut
|
|
|
|
#ifndef PAD_H
|
|
#define PAD_H
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
// ---- 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
|