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
This commit is contained in:
Anachronaut
2026-09-03 11:45:30 -04:00
co-authored by Claude Opus 5
parent 6073086584
commit 7257ad369c
18 changed files with 171 additions and 22 deletions
+131 -3
View File
@@ -68,6 +68,7 @@ start:
CALL putTiles CALL putTiles
CALL makeMoon CALL makeMoon
CALL carvePads
CALL drawMoon CALL drawMoon
CALL putLander CALL putLander
CALL putGauge CALL putGauge
@@ -223,6 +224,73 @@ makeNext:
BNQ makeColumn BNQ makeColumn
RET RET
; ---- Somewhere to put it down ----
;
; A random walk does not leave flat ground, and a lander wants some. So four pads are CARVED
; after the moon is made rather than looked for in it: searching can fail, and a fallback that
; carves anyway is the carving plus a search nobody needed.
;
; Each is four columns levelled to the height the first of them happened to have, so the pads
; sit in the landscape rather than on a shelf above it - the moon decides where they are, and
; this only decides that they are flat.
;
; The columns are marked in an array of their own. Asking "is this a pad" while drawing has to
; be one lookup: four comparisons per column per row is 12,800 of them for a screen.
carvePads:
SETD.3 PadTable
INIA 0d4
SETD.1 PadsLeft
STA.1
carveOne:
; The height this pad is levelled to, which is whatever its first column already was.
LDA.3
SETD.1 Terrain
DPUA.1
LDA.1
SETD.1 PadHeight
STA.1
RSTA
SETD.1 PadWide
STA.1
carveColumn:
; This column of the pad: levelled, and marked as somewhere to land.
LDA.3
SETD.1 PadWide
LDB.1
CCF
ADD
MVQA ; The pad's first column plus how far along this is.
PSHA
SETD.1 Terrain
DPUA.1
SETD.0 PadHeight
LDA.0
STA.1
POPA
SETD.1 IsPad
DPUA.1
INIA 0x01
STA.1
SETD.1 PadWide
LDA.1
INCA
STA.1
INIB 0d4
CCF
SUB
BNQ carveColumn
INCD.3
SETD.1 PadsLeft
LDA.1
DECA
STA.1
BNA carveOne
RET
; The shift register. Q comes back the new value, because Q is what survives a RET. ; The shift register. Q comes back the new value, because Q is what survives a RET.
nextRandom: nextRandom:
SETD.1 Seed SETD.1 Seed
@@ -284,11 +352,31 @@ drawCell:
CCF CCF
SUB SUB
BRC drawSky ; Borrowed: this row is above the surface, so it is sky. BRC drawSky ; Borrowed: this row is above the surface, so it is sky.
BRQ drawSurface ; Equal: this row IS the surface, which may be somewhere to land.
drawGround:
INIA 0xC8 INIA 0xC8
OUTA 0xE9 ; Tile 200, the ground block. OUTA 0xE9 ; Tile 200, the ground block.
RSTA RSTA
OUTA 0xE9 ; Attribute nought: grey on black, which is a fine moon. OUTA 0xE9 ; Attribute nought: grey on black, which is a fine moon.
BRI drawNextCell BRI drawNextCell
; ---- The top of a pad, in a colour that says so ----
;
; The same solid tile in a different scheme, which costs no art at all: an attribute is a
; nibble added to every index in the tile, so one block is a grey moon and a cyan landing pad
; depending on the byte beside it.
drawSurface:
SETD.1 IsPad
SETD.0 Column
LDA.0
DPUA.1
LDA.1
BRA drawGround ; Not a pad, so it is ordinary moon.
INIA 0xC8
OUTA 0xE9
INIA 0x06
OUTA 0xE9 ; Scheme six, which is cyan against everything else out there.
BRI drawNextCell
drawSky: drawSky:
RSTA ; A is still this column's height, from the comparison above. RSTA ; A is still this column's height, from the comparison above.
OUTA 0xE9 OUTA 0xE9
@@ -850,6 +938,8 @@ touchdown:
INIB 0x7F INIB 0x7F
AND ; The moon is 128 columns, so seven bits of it. AND ; The moon is 128 columns, so seven bits of it.
MVQA MVQA
SETD.1 LandColumn
STA.1 ; Kept, because the verdict below wants it too.
SETD.1 Terrain SETD.1 Terrain
DPUA.1 ; Terrain plus the column, which is the row its surface is on. DPUA.1 ; Terrain plus the column, which is the row its surface is on.
LDA.1 LDA.1
@@ -920,6 +1010,20 @@ touchdown:
SUB SUB
BNC touchdownCrash BNC touchdownCrash
; ---- Down safely, and whether it was anywhere useful ----
;
; A pad is where a base is, and the rest of the moon is somewhere a lander can survive
; arriving at and do nothing about. The difference is one lookup, because the carving wrote
; an array rather than leaving four comparisons to be done again here.
SETD.1 IsPad
SETD.0 LandColumn
LDA.0
DPUA.1
LDA.1
BRA touchdownNowhere
SETD.0 AtBaseText
BRI touchdownStop
touchdownNowhere:
SETD.0 LandedText SETD.0 LandedText
BRI touchdownStop BRI touchdownStop
touchdownCrash: touchdownCrash:
@@ -1059,10 +1163,15 @@ waitFrame:
#Base 0x3000 #Base 0x3000
; Sixteenths of a pixel, low half first, because that is the order the sums above walk in. ; Sixteenths of a pixel, low half first, because that is the order the sums above walk in.
; ---- Which is over a base, because that is where a day starts ----
;
; 0x14A0 sixteenths is 330 pixels, which is column 41 - inside the pad carved at 40. 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.
AcrossLow: AcrossLow:
0x00 0xA0
AcrossHigh: AcrossHigh:
0x0F ; Somewhere near the middle of the moon to start. 0x14
DownLow: DownLow:
0x00 0x00
DownHigh: DownHigh:
@@ -1173,8 +1282,27 @@ Flying:
Terrain: Terrain:
#Reserve 0d128 #Reserve 0d128
; One byte a column: whether a lander touching down there is at a base. Written by the carving
; and read while drawing and while landing, which is the whole reason it is an array rather
; than four comparisons done again every time somebody asks.
IsPad:
#Reserve 0d128
PadTable:
0d8 0d40 0d72 0d104 ; Four bases, evenly round a moon 128 columns about.
PadsLeft:
0x00
PadWide:
0x00
PadHeight:
0x00
LandColumn:
0x00
LandedText: LandedText:
"Down safely." "Down safely, in the middle of nowhere."
AtBaseText:
"Down safely at a base."
CrashedText: CrashedText:
"Crashed." "Crashed."
+1 -1
View File
@@ -727,7 +727,7 @@ from every assembly file in it. Several are old programs written for the bare ma
| Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. | | Grid | The first program to use the screen as a screen. Redefines a tile above the font, fills all 128 map rows, and scrolls it diagonally a pixel at a time. |
| Sprite | Moves a ball across the shell's own text, writing not one byte of the map to do it. It leaves the sprite in the table on the way out, because clearing them is the system's job - see below. | | Sprite | Moves a ball across the shell's own text, writing not one byte of the map to do it. It leaves the sprite in the table on the way out, because clearing them is the system's job - see below. |
| Pad | Says what the controllers are doing, printing a line whenever one changes. It tells apart the three things that look identical from inside a game that is not responding: a pad nobody noticed, a pad mapped to nothing, and a mapping that is wrong. | | Pad | Says what the controllers are doing, printing a line whenever one changes. It tells apart the three things that look identical from inside a game that is not responding: a pad nobody noticed, a pad mapped to nothing, and a mapping that is wrong. |
| Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. | | Lander | Lunar Porter, rung one: a lander over a moon that wraps. Flown with a controller if there is one - a held thruster burns every tick it is held for - and with the arrow keys if there is not, where one press is one burn and that is the most the console can say. A bar at the bottom is the sideways drift, drawn as a sprite stretched to the speed - a moon has no air, so a drift never stops by itself and stopping one means cancelling it exactly, which is hard to do blind. It lands or crashes on arrival, and what decides is the speed at the moment it touches: gentler than three quarters of a pixel a frame downwards and half of one sideways, or it is a lander on its side. A fuel gauge sits in the window layer, where the moon turning underneath cannot scroll it away, and every thruster costs a unit of fuel every tick it fires. An empty tank is not an ending: it is a lander still flying that can no longer do anything about where. Four landing pads are carved into the moon after it is generated, levelled to whatever height their first column happened to have, and marked in cyan by an attribute rather than a tile of their own. The lander starts above one, because that is where a porter's day begins. |
| Depth | Four pillars at four distances and a ball walking past them, behind the near ones and in front of the far ones. The ball is sprite nought and every pillar is numbered after it, so table order puts it in front of all four - what actually decides is the depth buffer, asked a column at a time. | | Depth | Four pillars at four distances and a ball walking past them, behind the near ones and in front of the far ones. The ball is sprite nought and every pillar is numbered after it, so table order puts it in front of all four - what actually decides is the depth buffer, asked a column at a time. |
| Flip | Draws a whole screen into the bank that is not being shown, waits, and then shows it in one byte out of one port. It writes nothing else at all - not a tile, not a colour - so it does not ask for the screen to be saved, and the line it printed is still there when it comes back. It deliberately does not put the displayed screen back either, because that is the system's to restore: a program that faulted while flipped could not have. | | Flip | Draws a whole screen into the bank that is not being shown, waits, and then shows it in one byte out of one port. It writes nothing else at all - not a tile, not a colour - so it does not ask for the screen to be saved, and the line it printed is still there when it comes back. It deliberately does not put the displayed screen back either, because that is the system's to restore: a program that faulted while flipped could not have. |
| Edit | A line editor. | | Edit | A line editor. |
+1 -1
View File
@@ -25,7 +25,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -32,7 +32,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
> Lander > Lander
Down safely. Down safely at a base.
finished finished
> exit > exit
halted halted
+1 -1
View File
@@ -1,6 +1,6 @@
CosmOS CosmOS
> Lander > Lander
Down safely. Down safely at a base.
finished finished
> exit > exit
halted halted
+1 -1
View File
@@ -108,7 +108,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -27,7 +27,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -15,7 +15,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -13,7 +13,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
+1 -1
View File
@@ -22,7 +22,7 @@ Mode.sbx 48
Flip.sbx 173 Flip.sbx 173
Sprite.sbx 442 Sprite.sbx 442
Depth.sbx 672 Depth.sbx 672
Lander.sbx 1756 Lander.sbx 2108
Pad.sbx 264 Pad.sbx 264
Crash.sbx 632 Crash.sbx 632
vars.script 50 vars.script 50
Binary file not shown.
+1
View File
@@ -0,0 +1 @@

+11 -6
View File
@@ -1014,14 +1014,19 @@ cosmosSprite | CosmOS/Source/cosmos.asm | run | -
padTest | testPrograms/padTest.asm | run | - | - | - | - | padTest.pad padTest | testPrograms/padTest.asm | run | - | - | - | - | padTest.pad
# ---- Lunar Porter, arriving ---- # ---- Lunar Porter, arriving ----
# #
# The same program, the same keys, and two different controllers. One holds nothing at all and # The same program, the same keys, and two different controllers. One holds Right and never
# the lander falls the whole way; the other pulses the thruster six frames in sixteen and # stops, so it arrives sliding; the other pulses the thruster six frames in sixteen and comes
# brings it down slowly enough to survive. # down slowly enough to survive.
# #
# WHAT SAYS WHICH IS THE SPEED AT THE MOMENT IT ARRIVES, and nothing else - the terrain is the # WHAT SAYS WHICH IS THE SPEED AT THE MOMENT IT ARRIVES, and nothing else - the terrain is the
# same terrain, from the same fixed seed, so the two runs differ only in what was held. That # same terrain, from the same fixed seed, so the two runs differ only in what was held.
# is the whole of the game's difficulty in two fixtures. #
cosmosLanderCrash | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | lander.keys | landerFall.pad # SLIDING RATHER THAN FALLING, and the difference matters. The crash fixture used to hold
# nothing at all, and stopped crashing the day the lander began its day above a base: a short
# drop onto high ground is survivable, which is correct and made the test say nothing. Lateral
# speed has no limit and nothing slows it, so a slide always ends badly however the rest is
# tuned.
cosmosLanderCrash | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | lander.keys | landerSlide.pad
cosmosLanderSoft | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | lander.keys | landerSoft.pad cosmosLanderSoft | CosmOS/Source/cosmos.asm | run | - | 60000000 | disks/cosmos.img | lander.keys | landerSoft.pad
# ---- And the same landing, flown from the second controller ---- # ---- And the same landing, flown from the second controller ----
# #
+15
View File
@@ -1707,6 +1707,21 @@ EOT
&& result ok "a held thruster keeps lifting" "row $EARLY early, row $LATE later" \ && result ok "a held thruster keeps lifting" "row $EARLY early, row $LATE later" \
|| result no "a held thruster keeps lifting" "row $EARLY early, row $LATE later" || result no "a held thruster keeps lifting" "row $EARLY early, row $LATE later"
# ---- The landing pads, carved and coloured ----
#
# A random walk does not leave flat ground and a lander wants some, so four pads are carved
# after the moon is made. They are marked in the picture by an ATTRIBUTE rather than a tile of
# their own: the same solid block in scheme six, which costs no art at all, because a nibble
# is added to every index in the tile and one block is grey moon or cyan pad depending on the
# byte beside it.
#
# Checked as a multiple of thirty two pixels, because a pad is four cells wide and one row
# tall - so whatever is visible of them, it comes in whole cells.
PADS="$(countColour "$BUILD/lander.ppm" 50c0c8)"
[ "$PADS" -gt 0 ] && [ $(( PADS % 8 )) = 0 ] \
&& result ok "the moon has pads carved into it" "$PADS pixels of them, in whole cells" \
|| result no "the moon has pads carved into it" "$PADS pixels, which is not whole cells of pad"
# ---- The fuel gauge, in the window ---- # ---- The fuel gauge, in the window ----
# #
# A bar in the window layer, which is at a SCREEN position - so the moon turning underneath it # A bar in the window layer, which is at a SCREEN position - so the moon turning underneath it