Commit Graph
6 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 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
2026-09-02 22:14:36 -04:00
AnachronautandClaude Opus 5 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
2026-09-02 21:58:31 -04:00
AnachronautandClaude Opus 5 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
2026-09-02 21:13:09 -04:00
AnachronautandClaude Opus 5 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
2026-09-02 16:52:01 -04:00
AnachronautandClaude Opus 5 fba1b553d2 Depth: a ball behind the near pillars and in front of the far ones
The demo for what V5 added. Four pillars at four distances, each ONE 8 by
8 tile stretched to its own width and height, and a ball walking past all
of them at a distance between two.

What it shows is the thing an ordering cannot. The ball is sprite NOUGHT
and every pillar is numbered after it, so table order puts the ball in
front of all four - and it is still hidden behind two of them, because the
depth buffer is asked per column. Caught mid-straddle in the checks: the
ball is 48 wide and the pillar 32, so it shows on both sides and nowhere
across the middle.

Writing it found the conceptual trap in the feature, which is now written
down where somebody will hit it. The pillars first carried their own
distance in their entries AND wrote that same distance into their columns,
so each was asked whether it was in front of itself - and 20 is not nearer
than 20, so all four vanished. THE BUFFER IS WHAT HAS BEEN DRAWN AND A
SPRITE'S DEPTH IS A QUESTION ASKED OF IT. Scenery writes it; it does not
ask.

Also found that a scheme only gives a colour to index one. The default
palette sets each scheme's paper and ink and nothing between them, so
art drawn in index two comes out black until a program writes a palette.

And the fixture disk's root directory was full: four blocks, 32 entries,
all taken, so adding an app failed the whole disk build. Loudly, which is
the right way round - but it is a wall that moves for free, so it is eight
blocks and 64 entries now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-02 15:58:34 -04:00
AnachronautandClaude Opus 5 a916103a7f Sprites: things that move without the screen moving
Everything drawn on this machine was in a cell. Something between two
cells meant rewriting both; something moving a pixel at a time meant
rewriting them sixty times a second, which is affordable for one thing
and not for twenty. A sprite is put at a pixel and the device draws it
over whatever is behind, so moving it costs two bytes.

MADE OF TILES, which is the decision the rest follows from: m by n taken
in reading order from one index, so there is no second pixel format, no
second kind of memory, and nothing a sprite can show that the map cannot.
A 16 by 16 character is four tiles and the background can name the same
four.

256 entries of 8 bytes at 0xC000 in the atlas - eight so the entry
address is a shift, the same no-multiply argument as the palette's four.
Position is signed and sixteen bits, because 640 by 400 does not fit in a
byte and a sprite has to be able to sit half off the left rather than
appearing whole at the edge.

A PIXEL OF ZERO IS NOT DRAWN, or every sprite is a rectangle. Tested
before the attribute is added, so a hole belongs to the art and not to
the colour scheme. The same rule the other way round is what "behind"
means: drawn only where the background pixel was zero, so a thing walks
behind a pillar and in front of the floor in one frame.

All of them draw, every frame, so they cannot flicker. Real machines
dropped them per scanline because they had a fixed number of shift
registers; this has a loop. The limit is the size of the table, which is
a constant rather than a property of what is on screen.

And the system takes them down at exit. The sprite table sits in the gap
the screen save walks around - to the end of the map, then the palette -
and that is right, because nothing the shell draws is a sprite: there is
nothing to give back, only something to take away. Otherwise a program
that put a ball up and left would leave it over the prompt, in front of
everything, with nothing able to type it away. Sprite.asm deliberately
leaves its own, because a program that faulted could not have cleared it.

Every check here was re-broken and failed: transparency, reading order,
draw order, priority, and size. Size needed breaking twice - the first
attempt did not compile, and a silent build failure had left the old
binary passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-02 11:42:11 -04:00