From c514d5328efb333bd9a2ef98c1cc649ce67c1f40 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Thu, 3 Sep 2026 19:15:50 -0400 Subject: [PATCH] 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. --- Programs/CosmOS/Apps/Lander.asm | 295 ++++++++++++++++++++++++---- 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/makedisks.sh | 2 +- Tests/video.sh | 38 ++-- 13 files changed, 295 insertions(+), 60 deletions(-) diff --git a/Programs/CosmOS/Apps/Lander.asm b/Programs/CosmOS/Apps/Lander.asm index aa40c9e..cab9f5f 100644 --- a/Programs/CosmOS/Apps/Lander.asm +++ b/Programs/CosmOS/Apps/Lander.asm @@ -66,6 +66,7 @@ start: OUTA 0x34 OUTA 0x38 ; No fraction of a cell downwards either. + CALL mulReady ; The quarter square table, once. The orbit below needs a product. CALL putTiles CALL makeMoon CALL carvePads @@ -662,64 +663,112 @@ fall: LDA.0 BNA fallResting - ; ---- Orbit ---- + CALL exchange + + ; ---- Orbit, which is gravity MINUS the swing outwards ---- ; - ; The faster it is going sideways, the less of the moon it feels. Not because gravity got - ; weaker - because at speed the surface is falling away underneath as fast as the lander is - ; falling towards it, which is what an orbit IS. + ; The first version of this only ever SUBTRACTED from gravity, and that is why it never + ; behaved like an orbit. Cancelling some of the pull can stop a fall from getting worse; it + ; can never turn one round, because nothing in it ever pushes up. A lander a little too slow + ; sank for ever and a lander a little too fast rose for ever, and both of them were right. ; - ; 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 simply be scaled down. Instead the - ; sideways speed is added into a byte every tick and the tick's gravity is skipped whenever - ; that byte carries - so the fraction of ticks cancelled is the speed over 256, smoothly, - ; with no multiply and no divide. At 256 sixteenths a frame it carries every time and the - ; lander is in orbit. + ; What actually happens is a difference between two things. Gravity pulls in and going round + ; throws out, and which of them wins depends on the sideways speed: ; - ; That is the linear approximation. The honest one is the square of the speed, which wants a - ; multiply this machine has not got; a table would give the curve if the feel ever asks for - ; it. What is here is one add and one branch. + ; below orbital speed the pull wins and the lander falls + ; at orbital speed they cancel and the lander circles + ; above orbital speed the swing wins and the lander climbs + ; + ; That sign change is the whole mechanic. Falling buys sideways speed (see the exchange + ; above), so a fall carries the lander past orbital speed and the swing turns it into a + ; climb: a periapse. Climbing spends that speed back, drops it below orbital, and the pull + ; turns the climb into a fall: an apoapse. Round and round, which is what an orbit is. + ; + ; ---- A gradient out of integer arithmetic ---- + ; + ; The step is one sixteenth of a pixel a tick and there is nothing between that and nothing, + ; so the strength cannot be scaled down directly. Instead the distance from orbital speed is + ; added into a byte every tick and the step is taken only when that byte carries - so the + ; fraction of ticks that act is the distance over 256, smoothly, with no divide. + ; + ; Times four, because the speed alone put orbit at a lateral speed no lander would ever + ; reach. At four it lands on 64 sixteenths - four pixels a frame, which crosses the moon in + ; about three seconds and takes ten seconds of holding a thruster to build. Something worked + ; up to rather than stumbled into. SETD.0 SpeedAcross CALL magnitude SETD.0 DriftHigh LDA.0 - BNA fallResting ; A whole high byte of it is far past orbital speed. - ; ---- Four times the speed, and orbit at sixty four ---- - ; - ; The speed alone made the relief a 256th a tick, so orbit wanted a lateral speed no lander - ; would ever reach and the whole thing was invisible. Times four puts it at 64 sixteenths - - ; four pixels a frame, which crosses the moon in about three seconds and takes ten seconds - ; of holding a thruster to build. Something worked up to rather than stumbled into. + BNA fallOutFull ; A whole high byte of it: far past orbital, and thrown outwards. SETD.0 DriftLow LDA.0 INIB 0d64 CCF SUB - BNC fallOrbiting ; No borrow, so it is at orbital speed and gravity is gone. - LDA.0 ; DP0 is still DriftLow, from the comparison above. + BRC fallInward ; Borrowed, so it is short of orbital speed and the pull wins. + + ; ---- Above orbital speed, so the swing wins ---- + MVQA ; Q is how far past, from the comparison just above. + CCF + SUB ; B is still 64, from that same comparison. + BNC fallOutFull RSTB SHL - SHL ; ---- Times four, and it comes out in A ---- - ; - ; A is the HIGH half of the shift register, so a value in A with - ; nothing in B is already that value times 256 - and rotating left - ; twice makes it times 1024, whose top byte is the value times - ; four. Written the other way first, reading B out of it, which is - ; nought every time: the relief was always none and the whole - ; mechanic did nothing at all. + SHL ; Times four; A is the high half, so the answer comes out in A. + BRI fallOutStep +fallOutFull: + INIA 0xFF +fallOutStep: + SETD.1 OrbitStep + STA.1 + INIA 0x01 + SETD.1 Outward + STA.1 + BRI fallSwing + + ; ---- Below orbital speed, so the pull wins ---- +fallInward: + MVQA ; Q is the shortfall, less its sign. + NOTA + MVQA + INCA + INIB 0d64 + CCF + SUB + BNC fallInFull + RSTB + SHL + SHL + BRI fallInStep +fallInFull: + INIA 0xFF ; Standing still, which is the whole of gravity and no swing. +fallInStep: + SETD.1 OrbitStep + STA.1 + RSTA + SETD.1 Outward + STA.1 + +fallSwing: + SETD.0 OrbitStep + LDA.0 SETD.1 OrbitAt LDB.1 CCF ADD STQ.1 - BRC fallResting ; Carried, so this tick's pull is what the speed cancelled. - BRI fallPulled -fallOrbiting: - BRI fallResting - -fallPulled: + BNC fallResting ; No carry, so this tick is one of the ones that does nothing. + SETD.0 Outward + LDA.0 + BNA fallPushed SETD.0 SpeedDown SETD.1 Gravity CALL addWord + BRI fallResting +fallPushed: + SETD.0 SpeedDown + SETD.1 Lift + CALL addWord fallResting: ; ---- And whatever is being leaned on, on the same tick ---- @@ -773,6 +822,142 @@ fallNotLeft: fallDone: RET +; ---- Falling buys sideways speed; climbing spends it ---- +; +; What the first orbit was missing. Cancelling gravity by speed lets a fast lander stop +; falling and gives it no way back: a little too slow and it sank for ever, faster and faster, +; because ALTITUDE DID NOTHING. Nothing turned height into speed or speed into height. +; +; A real orbit is that exchange. Falling towards periapse trades height for speed, which buys +; more relief, which turns the fall into a climb; climbing towards apoapse pays it back and +; the climb becomes a fall. That cycle is the whole character of an orbit. +; +; ---- And the rate is the PRODUCT, which is why this needed a multiply ---- +; +; The first attempt traded at a rate set by the vertical speed alone, and it drained a climb +; all the way to nought: a lander that climbed long enough had no sideways speed left to buy +; relief with, and sank. Gating it above a minimum made it worse - the climb spent its way +; down to the gate and locked itself out, frozen there for ever. +; +; The honest rate is proportional to BOTH speeds, and that is what fixes it: as the sideways +; speed falls towards nought the trade stops by itself. The product is its own gate, and no +; threshold is needed or wanted. +exchange: + SETD.0 SpeedAcross + CALL magnitude + SETD.0 DriftHigh + LDA.0 + BNA exchangeQuick ; A whole high byte of sideways speed: take the largest step. + SETD.0 DriftLow + LDA.0 + SETD.1 MulA + STA.1 + SETD.0 DriftLeftward + LDA.0 + SETD.1 AcrossWasLeft + STA.1 ; Which way it is going, before the next magnitude overwrites it. + + SETD.0 SpeedDown + CALL magnitude + SETD.0 DriftHigh + LDA.0 + BNA exchangeQuick + SETD.0 DriftLow + LDA.0 + SETD.1 MulB + STA.1 + + CALL mul8 + + ; ---- Half the product ---- + ; + ; The product reaches 65,025 and the accumulator is a byte, so what is wanted is a window + ; into the middle of it. The high half alone was tried first and it was both too weak and + ; too coarse: a lander at 80 sideways falling at 2 has a product of 160, whose high half is + ; nought, so the trade did not happen at all until the fall was already fast. A dead patch + ; right where the turn is supposed to begin. + ; + ; Half keeps the low bits, so the trade starts as soon as there is any fall at all, and it + ; is eight times stronger besides. That is what closes the loop inside the height of the + ; screen rather than somewhere off the top of it: the whole sky here is about 145 pixels + ; between the ground and the ceiling, and a quarter - tried in between - still ran the + ; lander into the roof before the turn came round. + SETD.0 MulHigh + LDA.0 + INIB 0d02 + CCF + SUB + BNC exchangeQuick ; 512 or more, which saturates the step whatever the low half. + RSTB + SHL + SHL + SHL + SHL + SHL + SHL + SHL ; Times a hundred and twenty eight, which comes out in A. + SETD.1 ExchangeStep + STA.1 + SETD.0 MulLow + LDA.0 + RSTB + SHR ; And the low half halved, which is the rest of the window. + LDB.1 ; DP1 still names the step, from the store just above. + CCF + ADD ; At most 128 plus 127, so this cannot carry. + MVQA + BRI exchangeAdd +exchangeQuick: + INIA 0xFF + +exchangeAdd: + SETD.1 ExchangeAt + LDB.1 + CCF + ADD + STQ.1 + BNC exchangeDone ; No carry, so there is nothing to trade this tick. + + ; ---- Which way the step goes ---- + ; + ; Falling grows the sideways speed and climbing shrinks it, which is a step AWAY from nought + ; or TOWARDS it depending on which way the lander is already going. Both signs are the top + ; bit a magnitude left behind, so the exclusive or of them is the whole decision: the same + ; means grow, different means shrink. + SETD.0 DriftLeftward + LDA.0 + SETD.1 AcrossWasLeft + LDB.1 + XOR + BNQ exchangeSlower + SETD.0 SpeedAcross + SETD.1 StepUp + CALL addWord + RET +exchangeSlower: + SETD.0 SpeedAcross + SETD.1 StepDown + CALL addWord +exchangeDone: + RET + +; One sideways, towards standing still, whichever way it is going. +spendAcross: + SETD.0 SpeedAcross + CALL magnitude + SETD.0 DriftLeftward + LDA.0 + BNA spendAcrossLeft + SETD.0 SpeedAcross + SETD.1 StepDown + CALL addWord + RET +spendAcrossLeft: + SETD.0 SpeedAcross + SETD.1 StepUp + CALL addWord + RET + ; ---- One unit, if there is one ---- ; ; Q comes back nought when the tank is empty, and the thruster that asked does not fire. There @@ -888,6 +1073,20 @@ moveAdrift: SETD.0 SpeedDownHigh STA.0 + ; ---- And the sideways speed that bought the climb ---- + ; + ; Without this the ceiling was a trap with no way out. A lander held here has its climb + ; wiped every time, so the exchange below sees no fall and no climb, trades nothing, and the + ; sideways speed that is pushing it up NEVER CHANGES: above orbital speed the swing pushes + ; up, the pin wipes it, and round again for ever. Measured: pinned at the top with 117 + ; sideways, unmoving, for the whole of a four minute flight. + ; + ; So the pin spends one sideways as well, which is only reached on the ticks the lander is + ; actually trying to climb - about one frame in ten. Six seconds of scraping along the roof + ; brings it back under orbital speed and it falls away on its own. Pushing against the top + ; of the sky costs the speed that got you there. + CALL spendAcross + moveAdriftSaid: ; Said once rather than sixty times a second. SETD.0 Adrift @@ -1816,6 +2015,28 @@ AtBase: ; of it is a tick of gravity that never happened. OrbitAt: 0x00 + +; How hard the swing is pulling or pushing this tick, and which way. And one step upwards, +; which is the only thing here that gravity did not already need. +OrbitStep: + 0x00 +Outward: + 0x00 +Lift: + 0xFF 0xFF + +; Where the height-for-speed trade has got to, and which way the lander was going when the +; product was worked out. One step of sideways speed, either way. +ExchangeAt: + 0x00 +ExchangeStep: + 0x00 +AcrossWasLeft: + 0x00 +StepUp: + 0x01 0x00 +StepDown: + 0xFF 0xFF SayAt: 0x00 @@ -1862,3 +2083,5 @@ LanderArt: LanderArtAt: #Reserve 0d2 + +#Include math.asm diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index dab7309..4681189 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index f40e2f3..3373cd7 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index e2a052f..876807d 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 9f95800..c84ae4d 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index e3987a9..3c811ad 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index bd931c5..758e7da 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 958610f..1779c0f 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 7044c38..b467867 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index 35f96d9..c5c9cde 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index 94f7fdb..4b86208 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 2924 +Lander.sbx 4651 Pad.sbx 264 Crash.sbx 632 vars.script 50 diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 82cc75b..881e2a5 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -146,7 +146,7 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/ "$TOOL" put "$DISKS/cosmos.img" "$WORK/Depth.sbx" >/dev/null # Lander.sbx is Lunar Porter, and the biggest program on the disk. It takes the screen, the # mode and the map, so it is also the one that says a program can have all three back. -"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ +"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Lander.asm" -o "$WORK/Lander.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Lander.sbx" >/dev/null # Pad.sbx says what the controllers are doing, which is the only way to tell a pad nobody diff --git a/Tests/video.sh b/Tests/video.sh index 0be518f..a1187d0 100755 --- a/Tests/video.sh +++ b/Tests/video.sh @@ -1790,21 +1790,33 @@ HOME="$(countColour "$BUILD/ceiling22000000.ppm" f0f0f0)" # ---- Orbit, which is measured and not checked here ---- # -# Going sideways lifts the moon off you: four times the lateral speed goes into a byte every -# tick and the tick's gravity is skipped when it carries. It works, and it was measured -# working - climbing at 13, 26, 39 sixteenths without lateral speed and 14, 31, 50 with it, -# the gap widening as the speed builds. +# Gravity minus the swing outwards. Below orbital speed the pull wins and the lander falls; +# above it the swing wins and the lander climbs; and falling buys sideways speed while +# climbing spends it. That is what makes a closed orbit rather than a one way trip, and it +# was measured doing it. Placed at 80 sideways and left alone, turning points at: # -# THERE IS NO CHECK FOR IT, and that is deliberate rather than forgotten. Every window in -# which the difference is visible turned out to be a few frames wide: hold the thruster and -# both landers are pinned against the ceiling with their climbs spent; ease off and both land -# and freeze; and either way the comparison becomes two nothings. A version of this check did -# exist and passed against one disk and failed against another, which is a check that is -# measuring the boot time rather than the physics. +# tick 44 height -1024 sideways 59 apoapse, and it falls away +# tick 123 height -124 sideways 71 periapse, and it climbs again +# tick 165 height -1024 sideways 59 +# tick 241 height -124 sideways 70 +# tick 299 height -1024 sideways 59 +# tick 369 height -114 sideways 70 # -# What would make it checkable is a way to put the lander somewhere with a velocity, without -# flying it there - the same thing the delivery below wants. Until then this is verified by -# somebody measuring it, and said so here rather than left looking tested. +# Four hundred and eighty ticks with no sign of the amplitude decaying, a period of about a +# hundred and thirty ticks, which is twenty two seconds of play. +# +# THERE IS STILL NO CHECK FOR IT, and it is still deliberate. The measurement above was taken +# with a build that PLACED the lander at eighty sideways, because reaching orbital speed +# through the controls takes a sustained burn while holding height, and the phase of that burn +# against the gravity tick - one frame in ten - decides whether the thruster is seen at all. +# Two pad files a frame apart fly differently. That is a fine thing for a game and a poor one +# for a check. +# +# What would make it checkable is still the same thing: a way to put the lander somewhere with +# a velocity, without flying it there, which is now wanted by four separate untested paths. +# The other candidate is a hand flown recording of an orbit, the way the delivery below is a +# hand flown delivery. Until one of those exists this is verified by somebody measuring it, +# and said so here rather than left looking tested. # ---- A delivery, flown by hand, and what it cost ---- #