Files
AnachronautandClaude Opus 5 de1857f5f7 Record every pad, not the one that happened to be first
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
2026-09-03 14:27:58 -04:00

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