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, the same accident that made Lunar Porter read all four in the first place - and a flight flown for the purpose was lost to it. So every pad is or-ed into the byte. 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 plays back on pad nought, where --pad puts the first file given, and any program that reads more than one pad reads them or-ed anyway for exactly the same reason. --record-pad takes one file now rather than filling pads in turn, because there is nothing left for the second one to mean. The check for it plays a recording on pad ONE with nought holding nothing and requires the bytes back. That is the case that was missing: the round trip was tested and passed, on pad nought, which is the only pad it could not have gone wrong on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
108 lines
4.9 KiB
C
108 lines
4.9 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.
|
|
//
|
|
// CONNECTED IS SAID SEPARATELY FROM HELD, and it has to be: a front end calls this every
|
|
// frame for every pad, so "held nothing" is the commonest thing it says and cannot also mean
|
|
// "there is no pad here". Getting that wrong made 0x64 answer nought with a controller
|
|
// plugged in, and a game that asked whether there was one was told no while the device
|
|
// underneath was reporting its buttons perfectly.
|
|
void padSet(int which, int connected, uint8_t held);
|
|
|
|
// ---- Writing one down ----
|
|
//
|
|
// A demo recorder. What it writes is EXACTLY WHAT --pad READS, one byte a frame, so playing a
|
|
// recording back needs no conversion and no second format - and the round trip is a property
|
|
// 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 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.
|
|
//
|
|
// 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);
|
|
|
|
#endif // PAD_H
|