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.
This commit is contained in:
Anachronaut
2026-09-03 21:48:32 -04:00
parent 85029d3b85
commit a02d701efe
13 changed files with 329 additions and 56 deletions
+253 -45
View File
@@ -48,8 +48,11 @@ start:
; floor the system gives every program does not.
SWI osTakeScreen
CALL setView ; The block every gauge below reads its screen numbers out of.
SETD.0 Wide
LDA.0
OUTA 0x31 ; Forty columns to start in. B swaps to eighty and back.
RSTA
OUTA 0x31 ; Forty columns. A moon 320 pixels across reads better than 640.
; ---- And the view put back to the top of the map ----
;
@@ -92,6 +95,7 @@ everyFrame:
CALL waitFrame
CALL readPad
CALL readControls
CALL zoomButton
CALL fall
CALL move
CALL follow
@@ -418,7 +422,8 @@ drawNextCell:
LDA.1
INCA
STA.1
INIB 0d25
SETD.2 MoonRows
LDB.2
CCF
SUB
BNQ drawRow
@@ -440,10 +445,12 @@ putLander:
OUTA 0xE9 ; Tile 201.
INIA 0x03
OUTA 0xE9 ; Attribute three, so it is yellow against a grey moon.
INIA 0d156
SETD.0 ShipX
LDA.0
OUTA 0xE9
RSTA
OUTA 0xE9 ; X: the middle of a 320 pixel screen, less half a tile.
INCD.0
LDA.0
OUTA 0xE9 ; X: the middle of the screen, less half a tile. Never moves.
OUTA 0xE9
OUTA 0xE9 ; Y, which every frame overwrites.
INIA 0x11
@@ -1269,20 +1276,35 @@ driftSized2:
SETD.1 DriftSize
STA.1
; Where it starts: the middle for a rightward drift, and that much back for a leftward one.
INIA 0d160
SETD.0 DriftLeftward
LDB.0
BRB driftAt ; Nought is rightward, so the bar starts at the middle.
SETD.0 DriftLow
LDB.0
CCF
SUB
MVQA
driftAt:
; ---- Where it starts, in sixteen bits ----
;
; The middle for a rightward drift and that much back for a leftward one, and it is a WORD
; now because the middle of an eighty column screen is 320 and will not go in a byte.
SETD.0 MiddleX
LDA.0
SETD.1 DriftAt
STA.1
INCD.0
LDA.0
INCD.1
STA.1
SETD.0 DriftLeftward
LDA.0
BRA driftAt ; Nought is rightward, so the bar starts at the middle.
SETD.0 DriftAt
LDA.0
SETD.1 DriftLow
LDB.1
CCF
SUB
SETD.1 DriftAt
STQ.1
INCD.0
LDA.0
RSTB
SUB ; And the borrow, which is the other half of the step back.
STQ.0
driftAt:
INIA 0d4
OUTA 0xE3
@@ -1298,11 +1320,14 @@ driftAt:
SETD.0 DriftAt
LDA.0
OUTA 0xE9
RSTA
INCD.0
LDA.0
OUTA 0xE9 ; X.
INIA 0d188
SETD.0 BarY
LDA.0
OUTA 0xE9
RSTA
INCD.0
LDA.0
OUTA 0xE9 ; Y, near the bottom and clear of the ground most places.
SETD.0 DriftSize
LDA.0
@@ -1585,17 +1610,24 @@ showHeightReady:
;
; A sprite is placed by its TOP, so a bar that grows up is one whose top moves as its height
; changes. Both come out of the same number and the foot stays put.
INIA 0d180
SETD.0 HeightBar
LDB.0
SETD.0 HeightFoot
LDA.0
LDB.1
CCF
SUB
SETD.1 HeightAt
STQ.1
INCD.0
LDA.0
RSTB
SUB ; And the borrow, which is the other half of the foot.
INCD.1
STQ.1
; Nought is the off switch, and it is the SIZE that switches it: a target height of nought
; is the natural height, which would be a tile sitting on the ground pretending to be sky.
LDA.0 ; DP0 still names the bar, from working the top out above.
SETD.0 HeightBar
LDA.0
BRA showHeightEmpty
INIA 0x11
BRI showHeightSized
@@ -1631,7 +1663,8 @@ showHeightSized:
SETD.0 HeightAt
LDA.0
OUTA 0xE9
RSTA
INCD.0
LDA.0
OUTA 0xE9 ; Y.
SETD.0 HeightSize
LDA.0
@@ -1650,6 +1683,85 @@ showHeightSized:
OUTA 0xE9 ; And no depth, A being nought already.
RET
; ---- Two zoom levels, which the device turns out to have already had ----
;
; Forty columns and eighty are the same map, the same 8x8 cells and the same engine; only how
; many of them fit on the screen differs. The map is 128 by 128 either way and the moon is
; exactly 128 columns of it, so forty shows under a third of the moon and eighty shows nearly
; two thirds - and the front end scales whatever it is handed up to the same window, so 320 by
; 200 at four times and 640 by 400 at twice fill the same glass.
;
; THAT IS A ZOOM, and nothing in the video device had to change to get it. What has to change
; is every screen coordinate in this program, because the middle of the screen is 160 on one
; and 320 on the other. They all live in one block.
setView:
SETD.0 ViewNarrow
SETD.1 Wide
LDA.1
BRA setViewFrom
SETD.0 ViewWide
setViewFrom:
SETD.1 View
INIA 0d15
SETD.2 ViewLeft
STA.2
setViewByte:
LDA.0
STA.1
INCD.0
INCD.1
SETD.2 ViewLeft
LDA.2
DECA
STA.2
BNA setViewByte
RET
; ---- Out for the orbit, in for the landing ----
;
; The moon is redrawn because it is filled as many rows deep as the mode shows, and a moon
; drawn 25 deep on a screen showing 50 is a moon floating over nothing. The fixed sprites go
; again because their places moved with the middle.
toggleView:
SETD.0 Wide
LDA.0
INIB 0x01
XOR
STQ.0
CALL setView
SETD.0 Wide
LDA.0
OUTA 0x31
CALL drawMoon
CALL putLander
CALL putGauge
CALL putOrbital
RET
; ---- One press, one swap ----
;
; A pad is LEVEL and not an event: it says what is held NOW, sixty times a second. A view that
; swapped whenever B was down would swap sixty times a second, which is not a zoom, it is a
; strobe. So the swap happens on the frame the button goes down, and the frame it comes up is
; remembered for the comparison rather than acted on.
zoomButton:
SETD.0 Held
LDA.0
INIB 0x20 ; B.
AND
MVQA
SETD.1 ZoomWas
LDB.1
STA.1 ; Now becomes what "last frame" means next time round.
CCF
SUB
BRQ zoomDone ; The same as last frame, so nothing has been pressed or let go.
LDA.1 ; DP1 still names it, and it now holds this frame's answer.
BRA zoomDone ; It changed to NOT held, which is the release and not the press.
CALL toggleView
zoomDone:
RET
; ---- Where orbital speed is, marked on the drift bar ----
;
; The bar says how fast sideways and the physics says 64 sixteenths is the speed at which the
@@ -1667,28 +1779,54 @@ showHeightSized:
;
; Written once. They never move, and a sprite table is a memory rather than a display list.
putOrbital:
; The leftward mark: sixty four back from the middle, and two more for its own width, so the
; bar's leading edge meets it rather than the bar covering it up.
SETD.0 MiddleX
LDA.0
INIB 0d66
CCF
SUB
SETD.1 MarkAt
STQ.1
INCD.0
LDA.0
RSTB
SUB ; And the borrow, because the middle can be 320.
INCD.1
STQ.1
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x40
OUTA 0xE5 ; Sprite four begins sixty four bytes in.
INIA 0d94
CALL putMark
; And the rightward one, sixty four on from it.
SETD.0 MiddleX
LDA.0
INIB 0d64
CCF
ADD
SETD.1 MarkAt
STQ.1
INCD.0
LDA.0
RSTB
ADD
INCD.1
STQ.1
INIA 0d4
OUTA 0xE3
INIA 0xC0
OUTA 0xE4
INIA 0x50
OUTA 0xE5 ; And sprite five, sixteen bytes after it.
INIA 0d224
CALL putMark
RET
; One mark, at the screen column in A, straddling the drift bar so the bar meets it end on.
; One mark, at the column in MarkAt, straddling the drift bar so the bar meets it end on.
putMark:
SETD.1 MarkAt
STA.1
INIA 0xC8
OUTA 0xE9 ; The solid block, which is what every bar here is made of.
INIA 0x05
@@ -1696,14 +1834,23 @@ putMark:
; White was the first choice and white is what TEXT is, so the
; ceiling check - which counts white to find its warning - started
; counting these instead and read a warning that was never up.
LDA.1 ; DP1 still names it, from being handed it above.
SETD.1 MarkAt
LDA.1
OUTA 0xE9
RSTA
INCD.1
LDA.1
OUTA 0xE9 ; X.
INIA 0d184
OUTA 0xE9
RSTA
OUTA 0xE9 ; Y, four pixels above the bar it brackets.
SETD.0 BarY
LDA.0
INIB 0d4
CCF
SUB
OUTQ 0xE9
INCD.0
LDA.0
RSTB
SUB
OUTQ 0xE9 ; Y, four pixels above the bar it brackets.
INIA 0x11
OUTA 0xE9 ; One tile by one, however small it ends up drawn.
RSTA
@@ -1786,7 +1933,8 @@ sayNext:
LDA.1
INCA
STA.1
INIB 0d40
SETD.2 RowCells
LDB.2
CCF
SUB
BNQ sayCell
@@ -1920,7 +2068,8 @@ fallBarSized:
STA.1
; Where it starts: the middle going down, and that much back going up.
INIA 0d100
SETD.0 MiddleY
LDA.0
SETD.0 DriftLeftward
LDB.0
BRB fallBarAt ; Nought is downward, so it starts at the middle.
@@ -1944,10 +2093,12 @@ fallBarAt:
SETD.0 FallColour
LDA.0
OUTA 0xE9
INIA 0x2C
SETD.0 FallX
LDA.0
OUTA 0xE9
INIA 0x01
OUTA 0xE9 ; X is 300, which is two bytes: the screen is wider than one.
INCD.0
LDA.0
OUTA 0xE9 ; X, in two bytes, because the screen is wider than one.
SETD.0 FallAt
LDA.0
OUTA 0xE9
@@ -2139,7 +2290,7 @@ DriftHigh:
DriftLeftward:
0x00
DriftAt:
0x00
0x00 0x00
DriftSize:
0x00
DriftColour:
@@ -2175,8 +2326,65 @@ GentleDown:
0d12
GentleAcross:
0d8
; ---- The numbers that change when the screen does ----
;
; Copied over from whichever table below matches the mode, so a gauge reads a variable and
; never has to know which screen it is drawing on. The order here and in both tables is the
; same order and has to stay that way: setView copies the block straight across.
View:
HalfScreen:
0xA0 0x00 ; 160 pixels, which is half of a forty column screen.
0xA0 0x00
ShipX:
0x9C 0x00
MiddleX:
0xA0 0x00
BarY:
0xBC 0x00
FallX:
0x2C 0x01
MiddleY:
0d100
HeightFoot:
0xB4 0x00
RowCells:
0d40
MoonRows:
0d25
ViewLeft:
0x00 ; How much of the block is still to copy.
; Forty columns: 320 by 200, and the moon 25 rows deep because that is all there is to see.
ViewNarrow:
0xA0 0x00 ; Half a screen, which is where the lander is held.
0x9C 0x00 ; And the lander itself, half a tile back from it.
0xA0 0x00 ; The middle, where both bars start.
0xBC 0x00 ; The drift bar's row, twelve pixels off the bottom.
0x2C 0x01 ; The fall bar's column, twenty pixels in from the right.
0d100 ; Half the height, which is where the fall bar starts.
0xB4 0x00 ; The height bar's foot.
0d40 ; Cells to a window row.
0d25 ; Rows of moon to draw.
; Eighty columns: 640 by 400, the same cells at half the size, so twice as much moon either
; way. Every screen number doubles except the ones measured from an edge, which are the edge
; less the same margin - a panel should sit the same distance in whichever screen it is on.
ViewWide:
0x40 0x01 ; 320.
0x3C 0x01 ; 316.
0x40 0x01 ; 320.
0x84 0x01 ; 388, which is twelve off the bottom of four hundred.
0x6C 0x02 ; 620, twenty in from the right of six hundred and forty.
0d200
0x7C 0x01 ; 380.
0d80
0d50
; Which of the two is up. Nought is forty columns, which is what the program starts in.
Wide:
0x00
; What B was doing last frame, because a pad says what is held and not what was pressed.
ZoomWas:
0x00
PixelLow:
0x00
@@ -2252,11 +2460,11 @@ HeightHigh:
HeightBar:
0x00
HeightAt:
0x00
0x00 0x00
HeightSize:
0x00
MarkAt:
0x00
0x00 0x00
Outward:
0x00
Lift:
+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. |
| 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. |
| 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. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. And going sideways lifts the moon off you: at four pixels a frame the surface falls away as fast as the lander falls towards it, and gravity stops arriving. There is a ceiling sixty four pixels above the screen, which pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and gravity brings it back. What kills you out here is running dry a long way from the ground. |
| 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. Each pad is a base, told apart by the colour it is drawn in, and landing at one either loads cargo for the base across the moon or delivers what is aboard and pays eighty units of fuel. A landing is not an ending: the lander rests where it is until the throttle opens again, and A or Start says "read it" as readily as a key does. What a base says goes in the window rather than out of the console: the console draws into the map, so a message printed while flying is one the lander then flies over, and printing scrolls the whole world up a row. Opening the throttle wipes the line. Two bars read the speeds and are green while a landing would survive and red while it would not, so "can I put down" is a glance rather than a sum. A third runs up the left edge and is how much sky is under the lander, half a pixel of bar to a pixel of it; it reads nought the moment the lander is down, and it exists because the orbit takes the lander off the top of the screen and a panel that only works while the ground is in sight is no use above it. And gravity here is the pull MINUS the swing outwards, which is what makes an orbit rather than a one way trip: under four pixels a frame sideways the pull wins and the lander falls, over it the swing wins and the lander climbs, and at it they cancel and it circles. Falling buys sideways speed and climbing spends it - at a rate set by the product of the two, so the trade stops by itself as the sideways speed runs out - and that is the cycle: a fall carries the lander past orbital speed and becomes a climb, the climb pays it back and becomes a fall, periapse and apoapse, round and round. Orbital speed is marked on the drift bar, sixty four pixels either side of the middle, because a number nothing points at is folklore. Gravity never falls off and the moon wraps, so a circular orbit is the same length at any height and one orbital speed serves everywhere. There is a ceiling sixty four pixels above the screen, which pins rather than ends: leaving upward is recoverable, so the lander is held there and told so, and one sideways is spent every tick it tries to climb - without that the pin wiped the climb, the trade saw neither fall nor climb, and the speed pushing it up never changed. What kills you out here is running dry a long way from the ground. B swaps the screen between forty columns and eighty, which is the same map, the same cells and the same engine at half the size: nearly two thirds of the moon at once for the orbit, and twice as big for the landing. |
| 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. |
| Edit | A line editor. |