Episode 5: Yet Another Progress Update
We had high hopes of putting some more on the screen today (think saucers), but we're still tinkering with the rocket ship.
Unidentified PDP-1-esque babble.
(Image: Still from Mars Attacks!, Warner Bros. 1996)
Last episode, we proudly presented a hack to combine a constant velocity for the spaceship with a cheap way to manage viscous acceleration. Maybe, a bit too much of a hack. Also, on the Type 30 CRT Display and its iconic trails glowing in the long sustain, the very path of any maneuvering object poses not only an algorithmic challenge, but an esthetical one, as well. And it's with the latter, we weren't exactly happy with. Moreover, we're looking for a nicely conigurable solution that would be still viable when we will fine-tune the timing and pace of the game. So we return to the problem of maneuvering a spaceship with grace.
But not all was terrible with our hack. Especially the idea of using averages for damping was quite nice. We'll keep this, while we get rid of the hack regarding the acceleration. As one of the problem of simulating the behavior of Computer Space on the PDP-1 originates in the much faster response of the DEC machine, we decide to opt for a configureable sensitivity, in stead.
This is our plan:
- Manage "viscosity" by a combination of sensibility and damping by averages.
- Have a fixed, but configurable velocity for the rocket.
- Manage the acceleration response by a sensibility implemented by a configurable number of frame skips.
- Add a configurable number of averaging steps for damping (i.e., viscosity of directional changes in movement).
- Balance the overall experience by a) the basic velocity (also the max. velocity), b) the sensibility in response to input, and c) the number of averaging staps.
- There are actually a number of possible sweet spots in this approach and we'll decide on the exact balance later.
As a side effect, we change the name of a few symbols and see some grow of the parameters table at the top of the code. Other, we decide to implement a general frame counter to manage some common skipping and alternation conditions, like for the background display or the exhaust flame animation of the rocket ship.
In a still v.0.05 still looks much like v.0.04.
(Section of screenshot, www.masswerk.at/icss/)
▶ Experience the code live: www.masswerk.at/icss/.
PDP-1 Instruction of the Day
In our ongoing endeavor of familiarizing our readers with the PDP-1 and empowering them to benefit from the source code provided below, we present another nifty instruction of the PDP-1, namely "xct", as in execute.
Instruction "xct" performs the remote execution of a single instruction located in the memory register specified in its address part. In other words, "xct" remotes the control flow for a single step. That is, as long as no skip occurs: In this special case the skip will occur locally and the control flow will be changed permanently.
This nifty indirect execution comes just at the price of a single additional instruction cycle and is especially handy for configuring parameters.
Macro Assembler Trick of the Day
Speaking of configurable parameters, we find a quite interesting construct in the parameters table of Spacewar!, our gold standard in PDP-1 code, which also illustrates nicely the use of remote instructions to be called by "xct":
(Spacewar! 3.1) / interesting and often changed constants /symb loc usual value (all instructions are executed, / and may be replaced by jda or jsp) tno, 6, law i 41 / number of torps + 1 tvl, 7, sar 4s / torpedo velocity rlt, 10, law i 20 / torpedo reload time tlf, 11, law i 140 / torpedo life foo, 12, -20000 / fuel supply ....
Now, what are these numbers following the labels and sperated by commata from the instructions? Incidentally, the code shown here starts at address 6 — are these numbers somehow magically asserting the memory locations (while nothing alike is specified anywhere in the syntax of the Macro assembler)?
Short answer: No. These are actually labels, which may, by the way, consist of numbers only. Since these "interesting and often changed constants" are meant to be changed on the fly in memory, the source code also serves as a reference to these parameters. It's actually a nice idea to mark the memory location in a clear and easily discernable way by putting them in front of the respective values or instructions by the means of an otherwise unused numeric label.
An observant reader, risking a glimpse at the source code below, may catch us copying this neat trick in our own program.
And here's the code, so far:
ironic computer space 0.05 nl 2016-10-18
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
define load A,B
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
add (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, 2400 / rocket angular acceleration
rvl, 7, sar 7s / scaling rocket velocity
ras, 10, law i 4 / rocket acceleration sensitivity (skip)
rad, 11, law i 3 / rocket acceleration damping (averaging)
ran, 12, 0 / random number
/routine to flush sequence breaks, if they occur.
sbf, tyi
lio 2
lac 0
lsm
jmp i 1
/sine-cosine subroutine - Adams associates
/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
add cos
dac sin
jmp .+4
sin, 0
dap csx
lac sin
spa
si1, add (311040
sub (62210
sma
jmp si2
add (62210
si3, ral 2s
mul (242763
dac sin
mul sin
dac cos
mul (756103
add (121312
mul cos
add (532511
mul cos
add (144417
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
add (62210
sma
jmp si3
add (62210
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
lac \frc / check frame conter
and (1
sza i /skip every second frame
bgx, jmp . / return
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)
add bgv / add vertical offset
swap / swap contents of ac and io
add bgh / add horizontal offset
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
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
dzm \rac /acceleration skip counter
/main loop
fr0, load \ict, -4500 / initial instruction budget (delay)
idx \frc / increment frame counter
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
lac \rth /load angle
spi /sign cleared in io?
add raa /no, add angular acceleration
ril 1s /next bit
spi
sub raa
sma /normalize 0 >= angle >= 2Pi (311040)
sub (311040
spa
add (311040
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 \frc /load frame counter
isp \rac /sensitivity, frame skip
jmp rp1
xct ras /reset counter
dac \rac
lac \sn /dx
cma
xct rvl /apply scaling for acceleration
swap /swap result into io
xct rad /damping, get loop count
dac \rdc
rx0, swap /get intermediate value from io
add \rdx /average with old dx
sar 1s
swap /swap into io
isp \rdc /increment loop
jmp rx0
dio \rdx /store updated dx
lac \cs /same for dy
xct rvl
swap
xct rad
dac \rdc
ry0, swap
add \rdy
sar 1s
swap
isp \rdc
jmp ry0
dio \rdy
jmp rp1
rp0, dzm \rac
rp1, scale \sn, 4s, \sn1 /get position of rocket tip
sar 4s /offset x = (sin >> 4) - (sin >> 8)
cma
add \sn1
dac \sn1
scale \cs, 4s, \cn1 /offset y = (cos >> 4) - (cos >> 8)
sar 4s
cma
add \cn1
dac \cn1
lac \rpx /update pos x (add dx)
add \rdx
dac \rpx
sub \sn1 /subtract offset for tip, store it in \px
dac \px
lac \rpy /same for y
add \rdy
dac \rpy
add \cn1
dac \py
scale \sn, 7s, \sn4 /scaled sine 4 steps
sar 1s
add \sn4
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
add \cn4
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
add (1000
dac \ict
rkx, jmp .
/control word get routines
/read control boxes
rcb, dap rcx
cli
iot 11
rcx, jmp .
/read testword
rtw, dap rtx
lat
swap
rtx, jmp .
/rocket display
/ step rearwards - x add \sn1, y sub \cn1
/ step outwards - x add \cm1, y add \sm1
/ 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
add \sm2
add \sm1
swap
add \sn2
add \sn1
add \cm2
add \cm1
disp
swap /y +4, x +2
sub \cn4
add \sm2
swap
add \sn4
add \cm2
disp
swap /y +4, x +1
sub \cn4
add \sm1
swap
add \sn4
add \cm1
disp
swap /y +4, x -1
sub \cn4
sub \sm1
swap
add \sn4
sub \cm1
disp
swap /y +5, x +4
sub \cn4
sub \cn1
add \sm4
swap
add \sn4
add \sn1
add \cm4
disp
swap /y +1, x -6
sub \cn1
sub \sm4
sub \sm2
swap
add \sn1
sub \cm4
sub \cm2
disp
swap /y +4, x +3
sub \cn4
add \sm2
add \sm1
swap
add \sn4
add \cm2
add \cm1
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
lac \px /load first pos
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
lac \frc /load frame counter
and (2
sza /is it zero? (state switch)
jmp ro2
ro1, lac \py /state 1, display at y-1
add \cn1
swap
lac \px
sub \sn1
disp
jmp rox
ro2, lac \py /state 2, display at y+4
sub \cn4
swap
lac \px
add \sn4
disp
jmp rox /jump to return
constants
variables
start 4
That's all for this post.
— stay tuned! —
▶ Next: Episode 6: Saucers!
◀ Previous: Episode 4: “A Rocket Ship That You Control”
▲ Back to the index.
2016-10-18, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2016/10. —