# Episode 4: “A Rocket Ship That You Control”

In the last episode we put a rocketship on the screen, now it's time to add controls to its. Because, of course, this is one of the most compelling promises made by Computer Space. Also, some refinements…

For a beginning, we tweak the outline of the rocket ship, we already have, just a little bit. Essentially, we move up the last three rows of dots up by a grid unit to make it a bit bulkier. Also, we adjust the scaling (sadly, it's not just a fraction of a power of 2 of the unit vector and we've to add some components). Same applies for the offset of the tip, providing the perceptual pivoting point of our ship. Finally, we increase the brightness level of the rocket display, mostly for a larger dot size.

With this in place, we turn to the graphics of the exhaust flames of our litlle spaceship.

### Holy Smoke

The thrust animation in Computer Space consists of just two pixels drawn alternately at the stern of the ship. Should be easy to implement, shouldn't it? — Not so, as may be guessed by this introduction: At a closer inspection of footage of the game, we're thrown back into the Heisenberg uncertainty of Computer Space graphics, once again, as shapes vary with rotation. (We may call this the Bushnell-Dabney Uncertainty of electronically generated visuals.)

As may be observed, the exhausts are much more accentuated at horizontal or diagonal rotational angles than in a close to vertical position of the ship, where they appear somewhat compressed and also closer to the vertical center of the rocket.

We settle for a compromise — and, of course, it's more on the prominent side, for effects. By this we arrive at the final form of our spaceship:

### Trusting Thrust

With the thrusting animation in place, we're ready to apply any player contolled dynamics to our little ship. As Computer Space has it's spaceship at a constant velocity, this is more or less about updating the values of delta x and delta y to a scaled amount of the unit vector (sine and cosine of the rotational angle) each time the player engages the thrust button. More or less — as there is more to this: Computer Space not only features a computer-like electronic device, but also a viscous space, where any newly applied dynamics just gradually come into effect.

We're facing two problems here: Constraining the velocity to a certain amount and implementing an accumulating acceleration. And, most important, we would like to avoid complex calculations, like square roots (as would be required for scaling a vector), for runtime's sake.

In order to do so, we come up with the following hack   [ not by K.T. ;-) ]:

```procedures:

1) scale the unit vector (sin for dx, cos for dy) to max velocity, store it.
2) scale this again for acceleration to be applied.
4) restrain the resulting components (for dx, dy) to the range of max velocity vector.
(use the same method we've already used for the rotational angle.)```

By this, we've arrived at a new value for `dx` (or `dy`, respectively) that we can trust to not to exceed the maximum velocity. And this by a few shifts and additions/subtractions only! What's still missing, is a proper viscosity:

```(cont.)

5) average with old delta (dx, dy) for viscosity.
6) store results (to be added as constant delta to position later).```

With this in place, we are ready to actually burn some fuel and maneuver the ship accross the screen. Thanks to the method descibed above we arrive at a quite pleasing experience that isn't far from the original behavior.

As a final improvement, we put the scalings for max velocity and for the rocket's acceleration in a parameters table on top of our code for the ease of any adjustments, following Steve Russell's best practice (who did similar in Spacewar! 3.1).

Experience the code live:   www.masswerk.at/icss/.

### The Dry Stuff — Implementation Details

Since the code has by now become a bit more complex, we wont go into verbose, descriptive details anymore, but rather refer any interested audiences to the comments in the code (see below).

However, it may be appropriate to introduce some more PDP-1 instructions for the benefit of the curious reader:

We're using a program flag (flag 5) to store, whether the ship is thrusting or not. (There are 6 program flags, 1–6, implemented in hardware. Flag 4 is also used for the console typewriter and we will avoid this one.) Instructions for setting and clearing flags are part of the operate group of instructions:

```stf n  ... set flag n (1...6)
clf n  ... clear flag n (1...6)```

The operate group includes instruction to change the internal state of the CPU and it's registers, like:

```cla  ...... clear AC
cma  ...... complement AC
cli  ...... clear IO
lat  ...... load AC from testword switches
hlt  ...... halt
nop  ...... no operation
opr  ...... basic instruction code of operate group (syn. to nop)```

Instructions of a group may be combined by microprogramming and will be executed in a single cycle. For this, we simply add the instruction codes (and substract the vale of the group, `opr`, since only the 12 lower bits are used for microprogramming. The micro-sequence in which the instructions occur are hardwired into the CPU. Some of these microprogrammed instructions also enjoy a well known mnemonic:

```clc = cma+cla-opr  / clear AC and complement it (777777)

cla stf 5 .... clear AC and set flag 5 at once```

Another group of microprogrammable instructions is the skip group, used to branch on conditions:

```sma  ...... skip on minus AC (sign-bit set)
spa  ...... skip on plus AC
sza  ...... skip on zero AC
spi  ...... skip on plus IO
szf n  .... skip on flag n zero
szs n0 .... skip sense switch n zero (n: 1...6)
e.g., "zss 10" to skip on sense switch 1 zero```

Setting the `i-bit` inverts the condition:

```sza i  .... skip on AC NOT zero
szf i n ... skip on flag n set (not zero)```

Again, skip conditions may be combined by microprogramming to form a union. Here, instruction "`szf`" represents the basic instruction code of the group:

`szm = sza sma-szf  / skip on zero or minus AC`

Having thus covered pretty much of the basic PDP-1 instructions, there are, we'll close our little PDP-1 101 for today.

And here's the code, so far:

```ironic computer space  0.04  nl 2016-10-17

mul=mus
div=dis

define initialize A,B
law B
dap A
term

define index A,B,C
idx A
sas B
jmp C
term

define swap
rcl 9s
rcl 9s
term

lio (B
dio A
term

define setup A,B
law i B
dac A
term

define count A,B
isp A
jmp B
term

/macros specific to the program

define scale A,B,C
lac A
sar B
dac C
term

define random
lac ran
rar 1s
xor (355671
dac ran
term

/ Computer Space
/ Original arcade game by Nolan Bushnell and Ted Dabney,
/ Syzygy Engineering / Nutting Associates, 1971.
/"A simulated space battle that pits
/ computer-guided saucers against
/ a rocket ship that you control."

3/	jmp sbf			/ ignore seq. break
jmp a0			/ start addr, use control boxes
jmp a1			/ alt. start addr, use testword controls

/game parameters

raa,  6,	2700		/ rocket angular acceleration
rvm,  7,	sar 1s		/ scaling rocket max velocity
rva, 10,	sar 4s		/ scaling rocket acceleration
ran, 11,	0		/ random number

/routine to flush sequence breaks, if they occur.

sbf,	tyi
lio 2
lac 0
lsm
jmp i 1

/calling sequence= number in AC, jda sin or jdacos.
/argument is between +/- +2 pi, with binary point to right of bit 3.
/answer has binary point to right of bit 0.  Time = 2.35-? ms.
/changed for auto-multiply , ddp 1/19/63

cos,	0
dap csx
lac (62210
dac sin
jmp .+4

sin,	0
dap csx
lac sin
spa
sub (62210
sma
jmp si2

si3,	ral 2s
mul (242763
dac sin
mul sin
dac cos
mul (756103
mul cos
mul cos
mul sin
scl 3s
dac cos
xor sin
sma
jmp csx-1
lac (377777
lio sin
spi
cma
jmp csx

lac cos
csx,	jmp .

si2,	cma
sma
jmp si3
spa
jmp .+3
sub (62210
jmp si3

sub (62210
jmp si1

/subroutines for background display

nos=77 	/number of stars

/table of stars coors (nos words, 9 bits x and 9 bits y)
bst,	. nos/

/setup (nos random coors starting at bst)

bsi,	dap bsx				/ deposit return address
init bsc, bst			/ deposit first addr of bst in bsc
bsl,	random				/ get a new random number
bsc,	dac .				/ store it in current loc of st
index bsc, (dac bst+nos, bsl	/ increment bsl, repeat at bgl for nos times
bsx,	jmp .				/ return

/display background stars (displays every 2nd frame only)

bg,	dap bgx				/ deposit return address
isp bgc				/ increment bgc, skip on positive
bgx,	jmp .				/ return
law i 2				/ load -2 into ac
dac bgc				/ deposit in bgc to reset frame counter
init bgl, bst			/ init bgl to first addr of stars table
bgl,	lac .				/ get coors of next star (x/y)
cli				/ clear io
scr 9s				/ shift low 9 bits in high 9 bits of io (x)
sal 9s				/ move remaining 9 bits in ac in high part (y)
swap				/ swap contents of ac and io
dpy-i				/ display a dot at coors in ac (x) and io (y)
index bgl, (lac bst+nos, bgl	/ repeat the loop at bgl nos times
jmp bgx				/ return

bgc,	0
bgh,	0
bgv,	0

/here from start

a0,	law rcb			/configure to read control boxes (sa 4)
dap rcw
jmp a2

a1,	law rtw			/configure to read testword (sa 5)
dap rcw

/start a new run

a2,	jsp bsi			/ initial setup of bg-stars
dzm bgh			/ reset offsets of stars to zero
dzm bgv

/rocket reset

ar,	dzm \rth		/ rotational angle
lac (-70000
dac \rpx		/ pos x
cla
dac \rpy		/ pos y
dzm \rdx
dzm \rdy

/main loop

fr0,	load \ict, -4500	/ initial instruction budget (delay)

jsp bg			/ display background
jsp rkt			/ rocket routine

count \ict, .		/ use up rest of time of main loop
jmp fr0			/ next frame

/player rocket routine

rkt,	dap rkx
rcw,	jsp .			/read control word (ccw, cw, trust, fire)
cla clf 5 -opr
dio \cw			/merge (or) spacewar player inputs
rcr 4s
ior \cw
dac \cw

lio \cw			/parse and process rotation
spi			/sign cleared in io?
ril 1s			/next bit
spi
sub raa
sma			/normalize 0 >= angle >= 2Pi (311040)
sub (311040
spa
dac \rth		/update angle

ril 1s			/parse thrust input
spi
stf 5			/set flag 5 for thrust
jda sin			/get sin, store it in \sn
dac \sn
lac \rth
jda cos			/get cos, store it in \cs
dac \cs

szf i 5			/flag 5 set? update dx / dy
jmp rp0
lac \sn			/dx
cma
sar 7s
xct rvm			/apply scaling for max velocity
dac \t1
xct rva			/apply scaling for acceleration
spa			/is it positive?
jmp . 6			/no, skip next 5 instr.
sub \t1			/constrain positive vel.
sma
cla
jmp . 5
sub \t1			/constrain negative vel.
spa
cla
add \rdx		/average with old dx for viscosity
sar 1s
dac \rdx		/store updated dx
lac \cs			/same for dy
sar 7s
xct rvm
dac \t1
xct rva
spa
jmp . 6
sub \t1
sma
cla
jmp . 5
sub \t1
spa
cla
sar 1s

rp0,	scale \sn, 4s, \sn1	/get position of rocket tip
sar 4s			/offset x = (sin >> 4) - (sin >> 8)
cma
dac \sn1
scale \cs, 4s, \cn1	/offset y = (cos >> 4) - (cos >> 8)
sar 4s
cma
dac \cn1
lac \rpx		/update pos x (add dx)
dac \rpx
sub \sn1		/subtract offset for tip, store it in \px
dac \px
lac \rpy		/same for y
dac \rpy
dac \py

scale \sn, 7s, \sn4	/scaled sine 4 steps
sar 1s
dac \sn4		/sn4 = (sin >> 7) + (sin >> 8)
dac \sm4		/sm4 for lateral offsets (will be complemented)
sar 1s
dac \sn2		/scaled sine 2 steps
dac \sm2
sar 1s			/scaled sine single step
dac \sn1
dac \sm1
scale \cs, 7s, \cn4	/same for cosine
sar 1s
dac \cn4
dac \cm4
sar 1s
dac \cn2
dac \cm2
sar 1s
dac \cn1
dac \cm1

jsp rod			/display it

lac \ict		/update instruction count
dac \ict

rkx,	jmp .

/control word get routines

rcb,	dap rcx
cli
iot 11
rcx,	jmp .

rtw,	dap rtx
lat
swap
rtx,	jmp .

/rocket display
/ step rearwards -  x add \sn1, y sub \cn1
/ step inwards   -  x sub \cm1, y sub \sm1

define disp
dpy-i 100		/display a dot at brightness level +1
term

rod, 	dap rox
stf 6			/set flag 6
lac \px
lio \py
disp

rop,	swap			/y +3, x +3
sub \cn2
sub \cn1
swap
disp

swap			/y +4, x +2
sub \cn4
swap
disp

swap			/y +4, x +1
sub \cn4
swap
disp

swap			/y +4, x -1
sub \cn4
sub \sm1
swap
sub \cm1
disp

swap			/y +5, x +4
sub \cn4
sub \cn1
swap
disp

swap			/y +1, x -6
sub \cn1
sub \sm4
sub \sm2
swap
sub \cm4
sub \cm2
disp

swap			/y +4, x +3
sub \cn4
swap
disp

szf i 6			/flag 6 set? (skip on not zero)
jmp rot
clf 6			/clear flag 6
lac \cm1		/invert unit vector components
cma
dac \cm1
lac \sm1
cma
dac \sm1
lac \cm2
cma
dac \cm2
lac \sm2
cma
dac \sm2
lac \cm4
cma
dac \cm4
lac \sm4
cma
dac \sm4
lio \py
jmp rop			/second pass for other side

rot,	szf i 5			/no, flag 5 set?
rox,	jmp .			/no, return
swap			/draw exhaust flame
sub \sm4		/x -6
sub \sm2
swap
sub \cm4
sub \cm2
dac \px			/store position
dio \py
idx \rtc		/increment a counter and check second least bit
and (2
sza			/is it zero? (state switch)
jmp ro2
ro1,	lac \py			/state 1, display at y-1
swap
lac \px
sub \sn1
disp
jmp rox
ro2,	lac \py			/state 2, display at y+4
sub \cn4
swap
lac \px
disp

constants
variables

start 4```

That's all for this post.

Next:   Episode 5: Yet Another Progress Update

Previous:   Episode 3: Rocketry — Or, Fly Me to the Moon Stars

Back to the index.

— This series is part of Retrochallenge 2016/10. —