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 ---- #