0x64 counted only the RECORDED pads. So a controller plugged into Voyager reported its buttons perfectly, and every game asking whether there was a controller was told no - which is exactly what Lunar Porter asked, once, at startup, before falling back to the console for the rest of the run. The cause is worth naming: a front end calls padSet every frame for every pad, so "held nothing" is the commonest thing it says and cannot also mean "there is no pad here". Connected is said separately now. Pad nought is always there behind a window, because the keyboard is behind it - which is the useful answer rather than the literal one. And Pad.asm, which is what should have existed before any of that guessing began. It prints a line whenever a pad changes, and tells apart the three states that look identical from inside a game that will not respond: one nobody noticed, one mapped to nothing, and a mapping that is wrong. WHY A PROGRAM AND NOT A PRINT IN THE FRONT END: because the question is what the MACHINE can see. A front end reporting what it thinks it is sending answers a different question, and the gap between those two is the whole of this bug. It also found that osPrintNumber takes A as the HIGH half - the same way round as the shift register and every other pair here, and not what a byte in A wants. Every value came out 256 times too big. Gravity is one frame in ten rather than six. The ratio between thrust and gravity is the feel; how often the tick comes round is how fast that feel arrives, and one in six was still touchy. Same lander, more time to think. And the verdict waits for a key. It printed and left immediately, taking the screen with it - so the one thing worth seeing, the lander sitting on the ground it had just reached, was gone before it could be looked at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
86 lines
3.7 KiB
C
86 lines
3.7 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);
|
|
|
|
void padTick(unsigned long now);
|
|
uint8_t padRead(uint8_t port);
|
|
|
|
#endif // PAD_H
|