Commit Graph
29 Commits
Author SHA1 Message Date
Anachronaut 9ae59bfccb A bang for the crash, which is this game's first sound
Noise through a low pass that the modulation envelope shuts as the level
falls, so the bright part is only at the front of it: a boom rather than
a hiss. Noise because every other waveform here has a pitch, and a
pitched bang is a note.

The instrument is built at startup the way the tiles are, and a crash
only says "this channel, this note". That is what the selector and value
registers are for - a patch is twenty odd writes and a note is two - and
it is the shape every sound after this one should take.

CHANNEL THREE. There are four, and effects count down from the top so
that music, if it ever arrives, can take nought and count up and the two
never have to negotiate. This is also the first program to drive the
sound device while also doing something else; the only other customer is
the patch editor, whose whole job is the device.

---- A byte of envelope is not seconds ----

It is squared and scaled to four of them, so the decay first written here
was 200 - which is two and a half seconds. Over the eight tenths of a
second the pieces are in the air that is not a bang fading, it is the
FRONT THIRD of one, and it both sounded and measured as a flat wash of
noise. Ninety is about half a second and it fades to silence with time to
spare.

Two checks, each seen to fail on its own break. The first is against
SILENCE - a landing in the same conditions makes no sound at all, so it
is measuring the crash and not the machine humming - and the second is
that it is louder in its first half than its second, which is the
difference the decay was getting wrong.
2026-09-04 12:35:29 -04:00
Anachronaut 0264b19a3d Dust on landing, gas on letting go, and a lander that stays let go
One particle system, three uses now: a lander coming apart, dust kicked
up by a landing, and gas out of a docking port on release. Where they
start, how fast they go, what colour they are and how long they last are
arguments; everything else is shared.

Dust goes sideways and UP off the lander's feet, because that is where
kicked dust goes and there is ground in the way of the rest of it. Gas
goes evenly in every direction, because nothing is in the way of a
docking port. Neither happens on docking - a dock is a catch and not a
touchdown, and there is nothing under it to kick.

---- Ticked from the frame loop, not run in place ----

The explosion can afford to stop the world; there is nothing left to fly.
The undocking puff cannot, because it goes off on the frame a thruster is
pressed, and freezing a quarter of a second exactly then is felt as the
controls sticking. So a burst advances one frame at a time from the main
loop, and the two that can afford to wait just pump that same tick until
the air is clear.

---- And letting go did not let go ----

Which the puff is what found. A docked lander sits EXACTLY one tile under
the station, so releasing upwards moved it towards the station and it
docked again on the very next frame: took the fuel again, said so again,
and waited to be told the message had been read - which reads as the
controls locking up the instant they are used.

It has to get clear now before it can take hold again, and the two
distances have to differ: docking wants one tile, re-arming wants two.
A single distance re-armed on the frame it let go, because a tile is
exactly where it was sitting.

Three checks, each seen to fail on its own break. The last of them
measures HOW FAR the lander has got and not merely that it moved: a
sixteen frame pause on release still leaves it climbing, four rows short
of a free run, so "it moved" would pass for a stall that has been slept
through.
2026-09-04 12:23:31 -04:00
Anachronaut 115efa1fa1 A crash takes the lander apart, instead of just saying so
The verdict used to be the whole of it: a line of text and a lander still
sitting there in one piece, so somebody watching a recording had to read
the words to know what had happened. That is the same problem the flames
were for.

The lander goes, and both flames with it, and six pieces of it leave in a
rough hexagon at its own colour for about a second. Then they go too,
rather than hanging over the words. The world is not running while it
plays: it is a loop of its own, so nothing else in the program has to
know how to be half destroyed.

Yellow, which is the lander's own colour, because it IS the lander - and
it is only free to use because the lander itself is hidden by then. The
check that counts exactly forty pixels of yellow looks at a flying frame.

---- Two mistakes worth keeping ----

The block went in between touchdownCrash and touchdownStop, so a crash
fell into the explosion and returned from there - no verdict, no end of
run, and the lander sitting there being crashed into the ground again
every frame. The linter caught it as a subroutine nothing called walking
into, which is exactly what it was.

And copyWord goes DP0 to DP1, so setting the pieces off from the lander's
position had the pointers the wrong way round: it copied the empty pieces
OVER ShipX. Since that is in the view block, it took the lander's own
column with it, and the one piece that could be seen drifted out of the
top left corner of the screen.

Three checks, each seen to fail on its own break. They measure the SPREAD
and not the count: two of the six leave the top of the screen on the way,
so the count drops from twenty four to sixteen, which is correct and
would make an exact count a check that breaks the day a lander crashes
somewhere else.
2026-09-04 12:06:00 -04:00
Anachronaut c408fc6cf6 Thruster flames, so a watcher can see what the pilot is doing
Every reading on this screen is a number drawn as a bar - how fast
sideways, how fast down, how much sky, how much tank - and all of it says
what is happening TO the lander. None of it says what the pilot is doing
about it, so somebody watching over a shoulder has to read gauges to work
out that a thruster is even lit.

A plume hangs off whichever side the engine is pushing from: under the
lander to lift, over it to retro, and on the far side from the way it is
being pushed sideways, since that is the side the gas leaves. One tile
does up and down, because a vertical flip turns one into the other, and a
second does the sides, because a flip cannot rotate a tile a quarter
turn. Twenty four pixels each, on purpose, so a count of them means
something.

HELD, NOT FIRED. The engine fires one frame in ten, because that is the
tick gravity is applied on, and a flame that honest would be one frame of
light six times a second - a fault lamp, not a rocket. What is drawn is
the button being down, which is the truthful answer to "is the pilot
burning": the tick is how the sum gets done, not what is happening.

An empty tank draws nothing, and nor does the retro thruster on the
ground, because in both cases the button really is doing nothing. The
second of those was a lie the first version told.

Red, and that is by elimination again: white is the ceiling warning, cyan
the landing pads, magenta the instruments, blue the station, yellow the
lander itself - and the lander is counted as exactly forty pixels of
yellow, so a yellow flame would have broken it.

Three checks, each seen to fail on its own break. Two things the fixtures
taught: a lander placed at the world's origin sits BEHIND the two row
window, which reads exactly like a flame that is not drawn; and red has
to be looked for in a box round the lander rather than a column, because
the speed bars go red and one of the four pads is red too.
2026-09-04 11:49:38 -04:00
Anachronaut 7e82b64d47 A dock that slides into line, and settles squarely on the port
Two things, and the second is the one that was actually wrong.

The lander is slid into place at half a pixel a frame rather than put
there. A dock is allowed eight pixels out in either direction, so
snapping moved it a whole tile in a single frame - a jump, at the very
moment the player was being told they had been careful. The worst gap
closes in about half a second, which reads as the two of them settling
together.

And it settles SQUARELY now. It used to come to rest four pixels out
however carefully it was flown, because the lander is drawn from half a
screen less half a tile - which is what centres an eight pixel lander on
the middle - while the station was drawn from half a screen exactly, so
its left edge sat where the lander's centre was. Both come off the same
origin now, and a gap of nothing puts one exactly above the other.

stationGap is factored out along the way. Three callers wanted it: the
one that draws the station, the one that decides whether it can be docked
with, and now the one that slides the lander in under it. The sideways
ease goes through that wrapped gap rather than the raw positions, because
a dock made either side of the moon's seam has a raw difference of most
of a moon.

---- And the fixtures moved to frame 400 ----

A dock waits to be told its message has been read, and reading a state
file costs a disk read, so a placed run starts a good deal later than a
plain one. The acknowledgement was at frame 100 and stopped working the
day the state file arrived: the program had not reached the dock yet and
the press went by unheard, which shows up as every sprite missing and
reads like a drawing bug. Everything after it is sampled well clear of
both ends.

Two checks, each seen to fail on its own break - the alignment settles at
4,-8 without the shared origin, and the slide reads 0,-8 the whole way
without the easing. Which of the two axes each one actually watches is
written down beside them, because it is not the one you would guess.
2026-09-04 11:23:54 -04:00
Anachronaut 1928275f87 A state can be placed, which four things were queued behind
Lander reads sixteen bytes from /lander.state if the disk has one and
starts from those: position, velocity, where the station is, fuel, and
which screen to be in. A disk without the file is the game as it always
was, which is every other flight in the suite.

It exists because some states cannot be flown to. A successful dock needs
the lander alongside the station and matched, and NINETY SIX pad files
failed to get there - not for want of trying, but because climbing spends
sideways speed, so a lander cannot rise while matched and arrives slower
than orbital every time. Reaching it wants two burns and a phase. The
orbit check had already been deleted for the same reason, and the strike
check was flown on a nineteen frame window, which is the sort of fixture
that ends up measuring the boot time rather than the physics.

Binary, because that is what a file is on this machine. A tool to build
one from readable text is a small job for another day; until then the
tests write the bytes with the fields named, which reads plainly enough.

---- The buffer is a whole block, and that is not caution ----

osFileRead lands a file in Data Memory and a file is stored in blocks of
256, so reading sixteen bytes into sixteen bytes of room writes over
whatever follows. It did: the first version put the buffer in front of
the view tables, and the program read its state, wiped the numbers every
gauge draws from, and left immediately - "finished" and back to the
prompt, with nothing on the screen to say why.

Four checks, each seen to fail on its own break: a state file places the
lander (row 192 and twelve cells of gauge, against thirty one with no
file), a matched approach docks and is paid once and not once a frame, it
then rides the station a tile under it, and the same approach unmatched
is a wreck. The flown strike fixture and its narrow window are gone.
2026-09-04 10:51:01 -04:00
Anachronaut 804dd3040e Docking, and a ceiling that is no longer the old screen's edge
The station can be docked with: close enough, and slow enough RELATIVE TO
IT, or it is a wreck. Its speed is orbital speed, which is what the marks
on the drift bar point at, so the instrument for this was on the screen
before there was anything to dock with. A dock pays eighty units of fuel,
once and not once a frame, and holds the lander a tile under the station
until a thruster lets go - checked before the speeds are put back, or a
burn would be wiped on the frame it was made and the lander could never
leave.

---- And the ceiling went up, which is what made it work ----

Sixty four pixels was the whole of the sky a forty column screen had over
the world's origin. It was never a fact about the world, it was a fact
about the view, and the wide one starts twenty four rows higher: a lander
stopped at the old line was stopped a long way short of the top of its own
picture for no reason it could see. It is 192 pixels now, the top of the
wide view.

The station went from four rows up to twelve - about two thirds of the way
from the ground to the ceiling. At four it sat exactly where anything
climbing away from the surface had to pass, and being run down there is
not a hazard, it is a toll: ELEVEN of this suite's flights were being run
down as collateral, including the ceiling check, which has to climb past
it to reach the ceiling at all. Moving both fixed all eleven at once.

The height bar divides by sixty four rather than thirty two, because the
band it describes is 368 pixels now and half a pixel of bar to a pixel of
sky would stand 184 tall and run off the top of the forty column screen it
is drawn beside.

---- What is checked, and what is written down instead ----

The wreck is flown. The fixture is narrow - the window measured 496 to 514
frames of climb and it sits at 505 - and that is said in place, along with
the instruction to re-measure before believing the code is broken. It was
not always narrow: at the old altitude any climb from 135 to 300 frames
struck. Narrow is the right way round, because it means the station is
hard to blunder into.

The successful dock is NOT flown, and ninety six pad files failed to find
it. That is not the search's fault: climbing spends sideways speed, so a
lander cannot rise while matched. It is measured working instead - tank
100 to 180, message up, and the pair holding together one tile apart for
two hundred and forty frames.
2026-09-04 10:37:52 -04:00
Anachronaut 922511c8a7 A retro thruster, at half the strength of the one that lifts
Arresting a rise meant a sideways burn and a wait for the orbit to come
back round. That is how a rendezvous really is flown and it is a lot to
ask of somebody who has not flown one before, so down now makes the
correction directly.

HALF THE STRENGTH, deliberately: one sixteenth a tick against two, which
is exactly gravity's own step. So it can stop a climb and it can hurry a
descent, and it can never turn a landing approach into a crash faster
than simply letting go would - the cheap way out of a mistake stays the
expensive one. Four against eight on a keyboard, which is the same ratio.

It does NOTHING to a lander on the ground, and that guard is load
bearing rather than tidy. touchdown has already had its say and returns
early once a lander is down, so there is nothing underneath to stop it
and no crash to say it happened: measured without the guard, holding it
drives the lander clean off the picture and spends a fifth of the tank
doing it.

---- And the fixture that hid all of that ----

The first version of the landed check passed just as happily with the
guard deleted, and the reason is worth writing down. A lander that has
landed WAITS to be told its message has been read, and the keyboard
fixture pads with NULs, which are not keys. So the program sat in that
loop for ever and the picture was frozen at the moment of touchdown -
every thruster held afterwards did nothing, which reads exactly like a
working guard. The pad now presses A to get past the wait before the
check tests anything, and both the row AND the fuel are read, because an
engine that fired and moved nothing would keep the row and one that moved
the lander for free would keep the fuel.

The altitude bar check upstream leans on that same freeze for its stable
end, and its comment said "stays landed" when what it means is "is not
running any more". Corrected, because that is the sort of comment that
sends the next person looking in the wrong place.
2026-09-03 23:02:19 -04:00
Anachronaut 800f555ffb A station in orbit, going round and not yet dockable
S1 of the station: it exists, it orbits, it wraps, it is drawn. Docking
is deliberately not here - the point of stopping at this rung is to fly
up and find out whether matching a four pixel a frame target feels good
before any rules are written about what happens when you reach it.

IT NEEDS NO PHYSICS OF ITS OWN. A body at 64 sixteenths is exactly what
a circular orbit is under the rules already here: the pull and the swing
cancel at that speed at ANY height, because gravity never falls off and
the moon is a cylinder that does not rotate. So the station is a
position, a constant, and the same fourteen bit wrap the lander uses.
That also means there is no prograde or retrograde to choose - nothing
privileges a direction, which is what makes a second station going the
other way a thing that can exist later.

Four rows above the world's origin: off the top of a forty column screen
and comfortably inside a wide one, so it is somewhere to go that the zoom
is needed to see. It starts half a moon away and a lap is 256 frames, a
little over four seconds, so it has to be found but will not stay lost.

Its column is the middle of the screen plus how far round it is from the
lander, wrapped to fourteen bits - which measures the long way round
whenever it is behind, so anything past the half way point becomes a
negative offset instead. Off the edge needs no test at all: a sprite's X
is signed and sixteen bits, so a station three hundred pixels to the left
is asked for at minus a hundred and forty and the device declines.

Blue, and that is not taste. White is counted to find the ceiling
warning, cyan to find the landing pads, magenta is the instruments and
yellow is the lander. Blue is the one ink no check measures, and picking
a measured one has broken a test twice already.

toPixelsSigned is factored out of showLander, since the station wants the
same sign-extended conversion.

Four checks, each seen to fail on its own break. Measured going round at
244 pixels in sixty frames, off the far side of the moon, and back on the
other edge.
2026-09-03 22:41:28 -04:00
Anachronaut 32559ce872 The room a zoom buys goes to the sky, not to the moon
Zooming out drew fifty rows DOWN from the world's origin, which put
exactly the same sky on the screen as before with twice as much moon
under it. Measured: 47 per cent rock zoomed in and 71 per cent zoomed
out. A zoom that shows you more of the thing you cannot fly through is
not worth a button.

The eighty column screen is fifty rows and the flyable band is thirty -
the ceiling is eight rows above the origin and the deepest valley is
twenty two below it - so the twenty rows a zoom buys have to go
somewhere. They go above. The wide view starts twenty four rows over the
origin, the ground sits near the bottom, and the whole band is on the
screen: 23 per cent rock instead of 71.

Which needed three things. The moon is drawn from row minus twenty four
rather than from nought, because rows above the origin are sky by
definition and because whatever the shell left in them is otherwise still
there. The row origin comes out of the view block like every other screen
number. And the lander's own Y moves with the view, which meant making
toPixels' answer SIGNED at last: it masks to twelve bits, so a lander
above the origin came back as a large positive number rather than a small
negative one - harmless while such a lander was off the picture either
way, and wrong the moment the view moved up to include it.

That is the point of the button. A lander at the ceiling is off the top
of a forty column screen, which is where the orbit lives and why the
altitude bar had to exist; zoomed out it is at row 149 and you can watch
the whole orbit.

Four checks, and the proportion is deliberately not on its own: measured
alone it passes for a moon floating over a void, because pointing the
view back at the origin leaves the rows under the terrain simply never
drawn, and black counts as sky. Both breaks that matter went straight
through it. What catches them is that the ground has to reach the bottom
of the screen and the sky has to be empty - the latter only on a shell
scrolled a hundred and eighteen lines deep, since that is what it takes
to get anything into the rows the wide view moves into.

The tap fixture also moved to frame 100. Fifty rows of moon take longer
to draw than twenty five, and a six frame tap at frame thirty now lands
before the program is reading a controller at all, which reads exactly
like a button that has stopped working.
2026-09-03 22:14:02 -04:00
Anachronaut 437ddf8ebe z zooms too, so a keyboard is not shut out of it
B was the only way to swap the view, and the game is meant to be flyable
without a controller - the arrows fly it when there is no pad. Somebody
without one had no way to zoom at all.

Tested ABOVE the pad test rather than beside the arrows, and that is the
distinction: which way the lander is flown is a question a controller
answers better, so the arrows stand aside for one. How much of the moon
is on the screen is not that kind of question, and a player with a pad
may still have a keyboard in front of them.

No edge to remember here either. The console delivers a key ONCE, which
is the whole difference between a key and a held button.

The check puts the z forty bytes into the keyboard file, because the
console hands over one key a frame: a z two hundred bytes in is a z two
hundred frames away, which is past the end of the capture and reads
exactly like a key that does nothing. It cost a wrong answer first time.
2026-09-03 21:54:12 -04:00
Anachronaut a02d701efe Two zoom levels on a button, which the device already had
Forty columns and eighty are the same map, the same 8x8 cells and the
same engine; only how many of them fit differs. The map is 128 by 128
either way and the moon is exactly 128 columns of it. And the front end
scales whatever it is handed by the largest whole number that fits, so
320 by 200 at four times and 640 by 400 at twice fill the same glass.

So the two modes ARE two zoom levels and nothing in the video device had
to change to get them: forty columns shows under a third of the moon at
twice the size, eighty shows nearly two thirds. Out for the orbit, in for
the landing, and B says which.

What did have to change is every screen coordinate in Lander, because the
middle of the screen is 160 on one and 320 on the other. They now live in
one block that setView copies over from whichever of two tables matches
the mode, so a gauge reads a variable and never has to know which screen
it is on. Several coordinates became sixteen bit on the way, since 620
will not go in a byte. follow already read HalfScreen and showLander
already writes the world position straight through, so the lander itself
needed nothing but its resting column.

The moon is redrawn on a swap because it is filled as many rows deep as
the mode shows, and a moon drawn 25 deep on a screen showing 50 floats
over nothing.

The swap is edge triggered. A pad is LEVEL and not an event, so a view
that swapped while B was down would swap sixty times a second - which is
not a zoom, it is a strobe, and it redraws the whole moon each time. The
check for that holds the button for three hundred frames and requires the
lander to land where a six frame tap left it; with the edge dropped it
ends seventeen pixels adrift, which is the strobe costing it frames.

Four checks, each seen to fail on its own break. The README's Lander
entry also still described the relief orbit that the previous commit
replaced, and now describes the one that is there.
2026-09-03 21:48:32 -04:00
Anachronaut 85029d3b85 An altitude bar, and orbital speed marked on the drift bar
The orbit takes the lander off the top of the screen, and the panel only
worked while the ground was in sight. Both other bars are rates: they say
how fast, and neither says where.

The altitude bar is height above the surface underneath, up the left edge,
half a pixel of bar to a pixel of sky - the flyable band is about 256
pixels and the screen is 200 tall, so pixel for pixel would run off the
top of the very screen it describes. Above the surface rather than above
some fixed line, so it reads NOUGHT the moment the lander is down.

The marks say where 64 sixteenths is. Without one that number is folklore:
a pilot can feel that somewhere around here the falling stops and has no
way to see where. The bar is a pixel a sixteenth from the middle at 160,
so the marks sit at 224 and at the two pixels before 96, adjacent rather
than overlapping - a bar at orbital speed would otherwise hide the thing
it is being measured against.

Both in magenta. Red and green are taken and they MEAN something here,
how fast and whether it can be landed with, and an altitude is neither.
White was the first choice and the ceiling check counts white to find its
warning, so it read a warning that was never up - the suite caught that.
Cyan was the second and it is the colour of a landing pad, which is
checked as whole cells of it.

groundLevel is factored out of restOnSurface, which had the same sum.

Three checks, each seen to fail on its own break. Two flights, because no
one flight holds both ends of the bar well: the climb reads 219 pixels and
the descent lands and then stays landed. They fly on their own keyboard
file - the shared one holds a key down every forty eight bytes for the
held-thruster check, so borrowing it flew the lander from the keyboard and
the controller at once and turned the gentle descent into a crash.
2026-09-03 20:57:16 -04:00
Anachronaut c514d5328e An orbit that comes back round, out of gravity minus the swing
The relief version could never make one. It only ever SUBTRACTED from
gravity, so a lander a little too slow sank for ever and one a little
too fast rose for ever - nothing in it could turn a fall around, because
nothing in it ever pushed up. There was no periapse to have.

Gravity minus the swing outwards has a sign change in it, and that is
the whole mechanic. Below orbital speed the pull wins and the lander
falls; above it the swing wins and the lander climbs; at 64 sixteenths
they cancel and it circles. Falling buys sideways speed and climbing
spends it, so a fall carries the lander past orbital and turns into a
climb, and the climb pays it back and turns into a fall.

The trade is the quarter square multiply, because the rate has to be
the PRODUCT of the two speeds. Set by the vertical speed alone it drained
a climb to nothing, and any minimum to stop that became a trap the climb
spent its way into - measured freezing at 23 with a gate of 24 and at 3
with a gate of 4. With the product in it there is no gate: as the
sideways speed goes to nothing the trade stops by itself.

Half the product rather than a quarter or the high half. The high half
alone is nought below a product of 256, which is a dead patch exactly
where the turn begins, and a quarter still ran the lander into the roof
before it came round - the whole sky is about 145 pixels.

Measured, placed at 80 sideways and left alone: apoapse at -1024 with 59
sideways, periapse at -124 with 71, and round again at 165, 241, 299 and
369 ticks with no sign of decay. A period of about 22 seconds.

The ceiling also spends one sideways when it wipes a climb. Without that
it was a trap with no way out: the pin wipes the climb, so the trade sees
neither fall nor climb and never touches the speed that is pushing the
lander up. Measured pinned at the top with 117 sideways, unmoving, for
the whole of a four minute flight.

Tests/makedisks.sh needed the library path, since Lander now includes
math.asm, and the app went from 2,924 bytes to 4,651 - mostly the 1,022
byte table - which is what the listing expectations move for.
2026-09-03 19:15:50 -04:00
AnachronautandClaude Opus 5 f5642f52b5 A ceiling that pins rather than ends
Climbing made a sixteen bit height count down past nought and round to
65535, so a lander that kept going came back through the bottom and hit
the ground FROM ABOVE. Two thousand pixels of climb, which a full tank
reaches easily.

Pinned instead, and told so in the window. Leaving upward is RECOVERABLE -
gravity is always there and a lander with fuel can always come back - so
ending the run would punish a state the player can fly out of. What kills
you out here is running dry a long way from the ground, which is a death
somebody flew into rather than one a boundary handed them.

TWO DIFFERENT LINES, and both were got wrong before they were got right.
The warning covers being AT the ceiling or above it: compared against the
ceiling itself, a pinned lander read as back inside the world the next
frame and the warning was written and wiped sixty times a second, so it
never appeared at all. The pin covers being STRICTLY above it: including
the ceiling dragged the height back every frame and the lander could never
descend, which is a lid nobody can leave and worse than the wrap.

And only the climb is spent, never the fall. Zeroing the speed outright
pinned it there for ever - gravity adds once a tick and a clamp running
every frame wiped the pull nine times out of ten.

The orbit check is gone, and the reason is in video.sh. Every window where
the difference showed turned out to be a few frames wide: hold the
thruster and both landers are pinned with their climbs spent, ease off and
both land and freeze. A version of it passed against one disk and failed
against another, which is a check measuring the boot time rather than the
physics. Orbit is verified by measurement and said to be so, rather than
left looking tested.

cosmosLanderDry wants seventy million cycles now instead of forty: it
spends the whole tank at the ceiling before it falls the length of the
world.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 18:42:34 -04:00
AnachronautandClaude Opus 5 a069ee7a00 Orbit, and two bars that answer instead of reporting
Going sideways lifts the moon off you. Not because gravity weakened -
because at speed the surface falls away underneath as fast as the lander
falls towards it, which is what an orbit is.

A GRADIENT OUT OF INTEGER ARITHMETIC. Gravity is one sixteenth of a pixel
a tick and there is nothing between that and nothing, so it cannot be
scaled down. Instead four times the sideways speed goes into a byte every
tick and the tick's gravity is skipped whenever that byte carries: the
fraction cancelled is the speed over 64, smoothly, with no multiply and no
divide. At four pixels a frame it carries every time. That is the linear
approximation; the honest one is the square, and wants a table.

It did nothing at all for its first two versions. Once because the relief
was a 256th a tick, so orbit wanted a speed no lander would reach; and
once because A IS THE HIGH HALF of the shift register, so multiplying by
four left the answer in A while the code read B, which is nought. The same
trap as the scroll register and the pixel conversion before it.

And a bar for the vertical speed beside the one for drift, both GREEN
WHILE A LANDING WOULD SURVIVE AND RED WHILE IT WOULD NOT. That turns two
numbers into one question - can I put down - and answers it at a glance.

WHAT THIS COST: the flown delivery check. A recording is a list of buttons
and not a flight, so replaying it under different gravity flies somewhere
else; the delivery became a crash two columns short. The fixture is still
there and is still a faithful record of what somebody did, and is no
longer a record of what happens.

That is the standing cost of a flown fixture, and it is worse than the
transcript tests dropped earlier: those broke when an output moved, and
this breaks whenever a NUMBER moves. Making the delivery reachable without
flying - a way to start already carrying, or at a chosen base - is what
would fix it properly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 16:25:56 -04:00
AnachronautandClaude Opus 5 caf5e1f99d A base speaks in the window, not into the world
Two bugs with one cause. The console draws into the map, so a message
printed while flying was a message the lander then flew over - and
printing scrolls, so every one of them moved the whole world up a row. The
window is at a screen position and forty cells wide, and neither is true
of it.

So the window is two rows now: the gauge, and whatever there is to say.
The letters are ordinary tiles, because the character generator starts at
the space and glyph n is character n less thirty two. The rest of the row
is blanked after every message, or a short one would leave the tail of a
long one behind it.

Opening the throttle wipes the line, because a message that outlived the
moment would be read as describing this one.

The crash still goes to the console, deliberately: it is the last thing
the program says and it should survive the program.

A or Start continues from a message as readily as a key does. Somebody
flying on a controller should not have to reach for the keyboard to say
they have read something.

AND TWO TESTS WENT WITH IT, which is the interesting part. cosmosLanderSoft
and cosmosLanderPadOne asserted on lines in a transcript, and the lines
moved off the console - so both went on passing while checking nothing at
all. A test that asserts a side effect rather than the thing itself is
always one refactor from being decorative. What they were for is now
checked in the picture, where the message actually is.

The lander check moved earlier too. The window grew to two rows, so by 1.5
million cycles the lander had climbed behind the status bar - the window
doing exactly what it should, and leaving the check counting six pixels of
a forty pixel lander.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 12:23:12 -04:00
AnachronautandClaude Opus 5 bfb515e23b Cargo: bases with names, and a landing that is not an ending
Four bases, told apart by the scheme their pad is drawn in, so "the cyan
one" is a thing a person can say and a thing the machine already knows.
Yellow is missing on purpose: it is the lander, and a base the same colour
as the thing landing on it would be a poor joke.

Land empty at a base and it loads cargo for the base ACROSS THE MOON, two
along - so the pairs are cyan with red and green with blue, and the
wrapping surface is a route rather than scenery. Land carrying at the
right one and it takes the cargo and pays eighty units of fuel. Land at
the wrong one and nothing happens, which is why the destination will want
to be on the screen.

A LANDING NO LONGER ENDS THE RUN. The lander rests where it is, exactly on
the surface with both speeds zeroed, until the throttle opens again -
which is the only way to stop being landed. Gravity does not pull on
something already sitting down, and a base does not hand out cargo sixty
times a second to a lander parked on it.

The pad array holds the base's number plus one rather than a flag. Nought
still means no pad, so it is still one lookup, and a flag would have to be
followed by "and which of the four" - the same walk done twice for an
answer already in hand.

Pads are eight columns rather than four. Four was 32 pixels in a moon 1024
round, which is a target somebody flying by feel misses over and over.

WHAT IS NOT COVERED, and why: the delivery and wrong-base paths need a
lander flown from one base to another, and hand-authoring a recorded pad
input that hits an eight column pad across a 128 column moon is a piloting
exercise rather than a correctness one. Several attempts got within two
columns. Loading, crashing, landing off a pad and running dry are all
covered; delivery is built and flown by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 12:04:27 -04:00
AnachronautandClaude Opus 5 7257ad369c Landing pads, carved rather than looked for
A random walk does not leave flat ground and a lander wants some. Four
pads are cut into the moon after it is generated, each four columns
levelled to whatever height its first column happened to have - so they
sit in the landscape rather than on a shelf above it. The moon decides
where they are; this only decides that they are flat.

Searching for flat spots was the alternative and it can fail, which means
a fallback that carves anyway - the carving, plus a search nobody needed.

They are marked by an ATTRIBUTE and not a tile of their own, which costs
no art at all: a nibble is added to every index in a tile, so one solid
block is grey moon or a cyan pad depending on the byte beside it.

Which columns are pads is an array, because asking has to be one lookup.
Four comparisons per column per row is 12,800 of them for one screen, and
the landing verdict asks the same question again.

THE LANDER STARTS ABOVE ONE, because that is where a porter's day begins.
Starting in the middle of nowhere meant a straight descent landed in the
middle of nowhere, which is a fine thing to be able to do and a poor thing
to have to.

That change cost the crash test its teeth, and the way it did is worth
keeping. It held nothing at all and let the lander fall - and a short drop
onto the high ground of the base you started above is survivable, which is
correct, and left the test saying nothing. It holds Right now: lateral
speed has no limit and nothing slows it, so a slide always ends badly
however the rest is tuned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 11:45:30 -04:00
AnachronautandClaude Opus 5 2274be4b68 Lunar Porter, rung three and a half: fuel
Every thruster costs a unit every tick it fires, so holding two at once
costs two - the honest price, and it makes a drift you corrected expensive
in a way a drift you avoided is not.

AN EMPTY TANK IS NOT AN ENDING. There is no message and nothing stops: a
lander with no fuel is still flying, it just cannot do anything about
where. What happens next is gravity, and gravity is patient. The test for
it holds the thruster from the first frame to the last and crashes anyway,
which is what says the fuel is real - a lander that could hold Up for ever
would land every time, and the economy this is the first half of would
have nothing to buy.

The gauge is in the window, which is what the window was built for two
commits ago: a bar at a SCREEN position, so the moon turning underneath
does not carry it off. Thirty five cells after a label, redrawn whole
every frame because seventy bytes out of one port is cheaper than working
out which of them changed.

A byte of fuel, and a byte is enough. Over eight it is a bar of up to
thirty one cells - a shift, because there is no divide - and at a unit a
thruster a tick it is about forty seconds of holding the engine open.
Sixteen bits would be more arithmetic for a number nobody reads to the
unit.

Cargo and the bases are the other half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 00:35:34 -04:00
AnachronautandClaude Opus 5 e3eccd17a8 Settle the view before saying how the landing went
The console draws into the map and the map is what is being scrolled, so a
message printed while the view was three pixels into a cell came out three
pixels off the top, with as much of its first row missing as the cell above
it had lost.

The flying is over by then, so the fractional part of the view has no more
work to do. Putting it back is what makes the whole message visible.

This is not the general problem. A status bar that has to stay readable
WHILE the map moves is a different thing entirely, and nothing here solves
it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 00:02:59 -04:00
AnachronautandClaude Opus 5 a074a831f0 The shell scrolls, and the moon was drawn where nobody was looking
Lunar Porter never put the row origin back. The map is a ring 128 rows
tall that the screen shows 25 of, and the shell leaves that origin
wherever its last command finished - so a moon drawn into rows nought to
24 while the screen is reading from row forty is a moon nobody can see. It
came out as terrain missing, or half there, depending on how far down the
prompt had got. Running Pad first was enough; so was holding Return.

Nothing here is tidiness. It is the difference between the rows a program
WRITES and the rows the screen READS, and only one of those is under the
program's control. Grid has always known this; Lander did not.

The check for it needed writing twice. Forty returns caught nothing,
because the shell runs an eighty column screen which is FIFTY rows tall -
forty returns fill it and never scroll it, so the origin was still nought
and the test passed against a build with the fix taken out. The screenful
that matters is the one the shell is using, not the one the program is
about to ask for. At eighty it is 28,608 pixels of moon with the fix and
none at all without it.

break.sh is what said so. The first version of this check looked exactly
like a passing test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-02 23:51:48 -04:00
AnachronautandClaude Opus 5 f7be843ed9 Lunar Porter takes any controller, not the first one
A controller does not always arrive on pad nought. The front end hands out
the numbers the host gave it, so a game that reads only the first one
works on the machine it was written on and silently does nothing on the
next - which is the shape of "the pad is detected, Pad shows it, and the
game ignores it".

Four reads and three ORs. One person flies this and which socket they
plugged into is not a thing they should have to know. Presence is any of
the four bits rather than the low one, for the same reason.

The manifest's pad column takes several fixtures now, comma separated, and
they fill the pads in turn. So cosmosLanderPadOne holds nothing on pad
nought and flies the whole landing on pad one - a test that fails on the
version of this program that shipped an hour ago.

Also confirmed while looking: raylib 6 does refresh which gamepads are
ready every frame in PollInputEvents, so a hot-plugged pad should be seen.
Whatever is stopping that is above us and worth a separate look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-02 23:36:05 -04:00
AnachronautandClaude Opus 5 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
2026-09-02 22:48:24 -04:00
AnachronautandClaude Opus 5 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
2026-09-02 22:33:32 -04:00
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