df50c2f0f8e21d39ccde9ed5b706f980c8350bd1
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
df50c2f0f8 |
The pad was working; the game was told there was not one
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 |
||
|
|
8eb4e4d67e |
Lunar Porter, rung two: it lands, or it does not
The terrain is an array in Data Memory rather than something read back out of the map, and that is the whole reason this is cheap: the ground under the lander is one index into 128 bytes, where asking the screen would be a transfer through the controller every frame. The column is the world position over eight, masked to the moon's 128. The surface is that column's row times eight - three turns left of the shift register, since a row is at most 24 and 192 fits in the low half. The feet are the lander's top plus its eight pixels. WHAT DECIDES IS THE SPEED AT THE MOMENT IT ARRIVES. Both of them, and both have to be gentle: three quarters of a pixel a frame downwards and half of one sideways. Sideways is the tighter on purpose, because a landing that was soft downwards and sliding is a lander on its side - which is the interesting half of the difficulty, and the half the drift bar was blind about until it existed. Two fixtures say it works, and they differ only in what was held: one holds nothing and falls the whole way, the other pulses the thruster six frames in sixteen and survives. Same terrain, same seed, same keys. Also: the gamepad did nothing, and the reason is that the four direction buttons are the D-PAD. A lot of controllers made this century have one nobody uses - the thumb goes on the stick, which reports as an axis rather than a button - so a pad that was plugged in and working correctly did nothing at all. The stick counts as held past halfway now. Untested here, because there is no controller in this environment and the suite runs headless; Voyager also says at startup which controllers it can see, so a pad that still does nothing can be told apart from one nothing noticed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
db0c26e13f |
A bar for the drift, and a lighter touch sideways
A moon has no air, so a sideways drift never stops by itself and stopping one means cancelling the velocity EXACTLY. That is not hard to do; it is hard to do blind, which is what it was - a number nothing on the screen said anything about. So sprite one is a bar whose width is the drift. It runs right from the middle of the screen for a rightward one and left for a leftward one, so which way is as plain as how fast, and stopped is the one state with nothing drawn at all. The whole of it is a target width written once a frame; the device stretches one tile into it and the program draws nothing. Sideways thrust is one a tick rather than two. At two, the smallest correction available was twice the size it needed to be and overshooting was the normal outcome. WHICH ZERO MEANS NOTHING TURNED OUT TO MATTER. A target width of nought is the NATURAL width, not an empty sprite - so a bar with no drift in it came out eight pixels wide, sitting at the middle of the screen, saying "stopped" in the same shape it says "drifting slightly". What draws nothing is a SIZE of nought, which is the other zero in the other byte. Both meanings are deliberate and documented and it still caught me out inside a week of writing them down. The check for it earned its place by failing on that before it was found, which is the best evidence a check can offer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
b1dde7908c |
Lunar Porter flies on a controller
A held thruster burns every tick it is held for, which is the whole reason the pad exists: the console can only say a key went down, so a thruster driven by it could be pumped and never leaned on. The burn happens on the same tick gravity does, and for the same reason - a sixteenth of a pixel is the smallest step this arithmetic takes, and applied sixty times a second it is an enormous acceleration. On the tick, thrust and gravity are two numbers whose RATIO is the whole feel of the thing. Position still moves every frame; only the acceleration is stepped, and nothing can see that. Two against gravity's one, so climbing and falling are the same speed. Three was the first try and it left the moon after about a second of holding. If there is a pad the console's arrows are ignored, because under a window the same keypress reaches both - the pad as a level, the console as a byte - and a thruster that fired twice for one press would be a mystery to anybody tuning it. q still quits, since a pad has no letter for it. With no pad the arrows still burn once a press, which is the most that can be done down a wire. And break.sh now rebuilds the disk images as well as the binaries. Half the things worth breaking here are SplitBit assembly rather than C, and those live on the fixture disks - so an edit to a .asm file changed nothing the suite could see, and the tool reported that nothing caught the break. That is the exact lie it was written to prevent, turning up in a new place. With the disks rebuilt it catches this one: the lander falls between the two captures instead of climbing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
88ecb208f4 |
The coarse scroll register was being sent the wrong register
CALL eighth OUTA 0x36 RET puts A back the way it found it, so the column origin was written the high byte of the position that had been passed in, and the answer the subroutine had worked out went nowhere. The fine register was computed inline with OUTQ and was correct, which is exactly what it looked like from the outside: smooth scrolling within a cell that never advanced one. Q is the only register that crosses a RET. Every other answer in this program already came back in it; this one had been written as if A would do, and A very nearly does, which is what makes it worth a comment rather than a fix. Gravity was Jupiter's. A sixteenth of a pixel per frame per frame is the smallest step this arithmetic can take and it crossed the screen in a second, so it is applied one frame in six instead - which divides the pull by six and costs a byte and a compare. The alternative was a finer unit for velocity than for position, and that means a shift every time one is added to the other, twice a frame, for ever. And the check that catches all this now looks 1.5 million cycles in rather than twelve. The first number came from assuming a program that saves a whole screen takes a long time to start; it does not, and by twelve million the lander had flown seven hundred frames and left the picture. A capture near the beginning is worth more than a tuned one - there is less between it and the start that can move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |
||
|
|
d6c81fa32c |
Lunar Porter, rung one: it flies
A lander over a moon that wraps. Landing, crashing, fuel, cargo and bases are not here - this rung exists to answer whether it FEELS right, because everything after it is bookkeeping and none of it is worth building on a lander that is no fun to fly. The moon comes for free. The map's column origin is a ring in hardware, so 128 cells is 1024 pixels of surface with no edge and no seam to cross. Position and velocity are sixteen bit in SIXTEENTHS OF A PIXEL, and the unit is the design: gravity is a small number added to a velocity and a velocity is a number added to a position, with no multiply or divide anywhere. 1024 pixels is 16,384 sixteenths, which is 2^14 - so going all the way round is an AND with 0x3FFF rather than a comparison, and it is never wrong at the seam. The lander never moves sideways. The world scrolls under it and it sits at the middle of the screen, which is a byte a frame instead of two and is also what makes the wrap invisible: there is no moment where it jumps. One key is one burn. The console says which key went down and there is no such thing as a key coming up, so a thruster cannot be held - a press adds to the velocity once. That is a property of the machine rather than a choice this program made, and it reads as pumping the engine. Four bugs found by running it, all worth keeping written down: B CANNOT BE A LOOP COUNTER here. Every comparison is an INIB, so the count was overwritten by whichever bound was last tested and the loop reset itself for ever. Counters that outlive arithmetic live in memory. A subroutine answers in Q, and the AND after it read A. The moon came out flat because it was testing the height against 1 instead of the random number. Row minus height, not height minus row: they are equal at the surface, equal does not borrow, and the surface row has to be ground. And the shift register has A as its HIGH half. Written the other way, the view scrolled by 256 cells for every one it should have. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW |