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.
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.
▶ 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.
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 "
(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? Incidentially, 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. —