From 88ecb208f4b495bda816d23388c67c044d7dd794 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Wed, 2 Sep 2026 21:13:09 -0400 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- Programs/CosmOS/Apps/Lander.asm | 48 ++++++++++++++++++++++++----- Tests/expected/cosmosCrossDisk.out | 2 +- Tests/expected/cosmosDrives.out | 2 +- Tests/expected/cosmosFault.out | 2 +- Tests/expected/cosmosFlip.out | 2 +- Tests/expected/cosmosGrid.out | 2 +- Tests/expected/cosmosMonitor.out | 2 +- Tests/expected/cosmosMonitorRun.out | 2 +- Tests/expected/cosmosRun.out | 2 +- Tests/expected/cosmosSlowDisk.out | 2 +- Tests/expected/cosmosSprite.out | 2 +- Tests/video.sh | 20 +++++++----- 12 files changed, 64 insertions(+), 24 deletions(-) diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index 1018afb..3caceb8 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -371,11 +371,30 @@ burnRight: CALL addWord RET -; Gravity, which is the same small number every frame and the reason this is a game. +; ---- Gravity, which is the reason this is a game ---- +; +; NOT EVERY FRAME. One sixteenth of a pixel per frame per frame is the smallest step this +; arithmetic can take and it is still far too much - it crossed the screen in a second and +; flew like a gas giant. So it is applied one frame in FallEvery, which divides the pull by +; that much and costs a byte and a compare. +; +; The alternative was a finer unit for velocity than for position, which means a shift every +; time one is added to the other, twice a frame, for ever. A counter is cheaper and it is one +; byte to change while the feel is being found. fall: + SETD.1 FallTick + LDA.1 + DECA + STA.1 + BNA fallDone + SETD.1 FallEvery + LDA.1 + SETD.1 FallTick + STA.1 SETD.0 SpeedDown SETD.1 Gravity CALL addWord +fallDone: RET ; ---- Where it is now ---- @@ -425,7 +444,7 @@ follow: SETD.0 PixelLow LDB.0 CALL eighth - OUTA 0x36 ; The column origin. + OUTQ 0x36 ; The column origin. IN Q, because RET puts A back. RET ; ---- The lander, put where it now is ---- @@ -514,7 +533,16 @@ toPixels: ; Three turns right is a divide by eight, and a moon 1024 pixels round is 128 columns, so the ; whole answer is in the low half and the high half is the bits that rotated out of it. ; -; B cannot be read. Adding nothing to it puts it in Q, which can be moved. +; ---- And the answer comes back in Q ---- +; +; It came back in A first, which is a lie a subroutine cannot tell: RET puts A back the way it +; found it, so the caller wrote out the high byte of the position it had passed in. The fine +; register was computed inline and was right, the coarse register was not, and the picture +; scrolled smoothly within a cell and never advanced one. Q is the only register that crosses +; a RET, which is why every answer in this program comes back in it. +; +; B cannot be read at all. Adding nothing to it puts it in Q, which is where this wanted to +; be anyway. eighth: SHR SHR @@ -522,7 +550,6 @@ eighth: RSTA CCF ADD - MVQA RET waitFrame: @@ -550,14 +577,21 @@ SpeedAcross: SpeedDown: 0x00 0x00 +; ---- The four numbers the feel lives in ---- +; +; Adjacent on purpose, because tuning them is what the first rung is for. Gravity: 0x01 0x00 +FallEvery: + 0d6 ; Gravity one frame in six. Every frame was Jupiter. +FallTick: + 0d1 ThrustUp: - 0xEC 0xFF ; Twenty sixteenths upwards, which is minus twenty. + 0xF8 0xFF ; Eight sixteenths upwards, which is minus eight. ThrustLeft: - 0xF8 0xFF ; Eight sixteenths to the left. + 0xFC 0xFF ; Four to the left. ThrustRight: - 0x08 0x00 + 0x04 0x00 HalfScreen: 0xA0 0x00 ; 160 pixels, which is half of a forty column screen. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index 25d337b..3ba506e 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -25,7 +25,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 290c7fd..70605e4 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 865706a..c9ae451 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -32,7 +32,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 1ef4ce8..d39e2ef 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index d2e08f5..76487b9 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 9cb4e64..e977938 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -108,7 +108,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index e4e7410..2dfb143 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -27,7 +27,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 6f7af5c..8278c93 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -15,7 +15,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index 3ef56f5..39633d7 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -13,7 +13,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index c580f52..ce25fac 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -22,7 +22,7 @@ Mode.sbx 48 Flip.sbx 173 Sprite.sbx 442 Depth.sbx 672 -Lander.sbx 984 +Lander.sbx 1009 Crash.sbx 632 vars.script 50 blocks.script 343 diff --git a/Tests/video.sh b/Tests/video.sh index 977e904..fe471e3 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -1525,17 +1525,23 @@ EOT # takes the screen, changes the mode, redefines tiles, fills all 3,200 cells of the map from a # terrain it generated, and moves a sprite over it every frame. # -# The keys are a burn of the lifting thruster every sixteenth frame, which is more than the -# gravity and so keeps the lander up long enough to be looked at. The cycle count is tuned to -# catch it before it climbs off the top; if CosmOS's size shifts the boot far enough, the -# lander check fails saying it is not on screen, which is a request to re-tune. +# A burn of the lifting thruster every forty eighth frame, which is exactly the gravity: one +# sixteenth of a pixel every sixth frame against eight every forty eighth. Exactly cancelling +# the ACCELERATION still leaves an average velocity, so it drifts upward slowly and this is +# caught early rather than left to settle. +# +# EARLY, and that is the point of 1.5 million cycles. This program starts almost at once - the +# first guess at where to look was ten million cycles in, on the assumption that taking a +# screen was expensive, and by then the lander had flown 700 frames and left the picture. A +# capture near the start is worth far more than a tuned one: there is much less between it and +# the beginning that can move. python3 -c " keys = b'Lander\n' for i in range(400): - keys += b'\x80' + b'\x00' * 15 -open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 4000) + keys += b'\x80' + b'\x00' * 47 +open('$BUILD/lander.keys','wb').write(keys + b'\x00' * 8000) " -timeout 30 "$EMU" --fast --cycles 12000000 --keyboard "$BUILD/lander.keys" \ +timeout 30 "$EMU" --fast --cycles 1500000 --keyboard "$BUILD/lander.keys" \ --screen "$BUILD/lander.ppm" --disk "$ROOT/Tests/build/disks/cosmos.img" \ --ram-disk 2048 "$BUILD/cosmos.bin" > "$BUILD/lander.out" 2>&1 || true read -r MOON SKY SHIP SHIPLEFT SHIPRIGHT <