Go to the Index

Inside Spacewar!
Part 8: Hyperspace!

Prev. Next

(Introduction · Semantics of Interaction · Hyperspace As-We-Know-It: Spacewar! 3.1 · Spacewar! 2b and the "Minskytron" Hyperspace · The Auto-Restart Patch for Spacewar! 2b)

Spacewar! is a sporty game for young ladies and gentlemen who happen to have access to a DEC PDP-1 with a scope attached to it (favorably some kind of Type 30 CRT). Like in every noble, sporty game, there is some kind of provision to equalize a missmatch of mastership in order to provide a fair and interesting game: Golf has a handicap, chess has extra time or even extra moves and odds, and Spacewar! has hyperspace. This is also the subject of today's episode of our walk through the internals of Spacewar!, the earliest known digital video game.

People playing Spacewar! at the PDP-1 console, ca 1964

Young ladies and gentlemen enjoying a game of Spacewar!
at the AFIPS Fall Joint Computer Conference, ca. 1964.
Image courtesy of the Computer History Museum (CHM), Catalog No 102652460.

(Notably, we are not suggesting here that any of the distinguished players
depicted on this photograph would have required to resort to hyperspace.)


Update: According to an updated image description at the CHM site
the image shows Barbera Stephenson, the first female engineer at DEC.

BTW: You may play Spacewar! here, too (original code running in an online emulation).

Semantics of Interaction

For anyone who has encountered the term "HYPERSPACE" for the first time as a button-label at an Atari™ arcade machine this is maybe one of the most artificial words of all and indispensably linked to electronic gaming. It might be surprising that this term was actually a no-brainer, when it was officially introduced to the world of games in 1962:

Each ship has one other means of getting from one place to another, namely "hyperspace," which allows him to get out of the way quickly.
(J. M. Graetz, "Spacewar! Real-Time Capability of the PDP-1", DECUS Proceedings 1962: Papers and Presentations of the Digital Equipment Computer Users Society; Maynard, Massachusetts, 1962. p. 37-39)

And going a bit more into detail:

HYPERSPACE
This is an emergency device. It frequently happens that a ship cannot accelerate fast enough to get out of the way of an approaching torpedo. The player may send the ship into hyperspace then. The ship then will disappear and very shortly will reappear somewhere else on the scope. Since this is a way of getting from one place to another without traveling the distance between, the method used must be hyperspace! (J. M. Graetz, ibidem.)

Apparently there is no need to introduce the term at all. — How comes?

When it comes to the cultural background of Spacewar!, the references to E. E. "Doc" Smith and especially the Lenseman series are ubiquitous — a notion that has been fed by Spacewar's authors themselves in a number of articles and interviews. But, if we investigate this a bit more thoroughly, we might have to broaden our view on the subject.

Spacewar! features a quite distinctive vocabulary, both in the source comments and in the various accounts on it and recollections. On the other hand, the Lenseman series exhibits a distinctive wording of its own, and there isn't that much of an intersection here. It's true that torpedoes are not an uncommon thing in the Lenseman series, and there's even a rare notion of hyper-spatial tubes. But more often, we will encounter projectors, rays, and beams. Especially, E. E. Smith seams to have been fond of the word "ether", as in ether-waves, sub-ether, ether-wall, ether-defense, and even sub-ether-drives. Had Spacewar! really been staged in a Doc-Smith-universe exclusively, then there would have been some shields of sorts (ahem: "ether-walls"), there would have been no need for "mumbling" about "photon bombs" (for the lack of gravity), since these would have clearly been projetor beams, and, if there wouldn't have been sub-ether-drives or enigmatic Nth space, it would have been "hyper-space" at least — notably with a hyphen in it.

Now, I'm not an expert on the subject of SF literature, but Wikipedia credits John Campbell for using the term "hyperspace" as early as 1931 (Islands of Space) and describes a frequent use of the term ever thereafter. With a well established Science Fiction Society at MIT, copies of various specimens of the genre were probably circulating frequently and were not difficult to find. While Martin Graetz's DECUS paper makes an implicit reference to written SF literature and the origin of this term as an artifice of story telling by classifying hyperspace as one of the means of transportation in Spacewar!, there is probably more to it. Books were still a major reference at the mid-century, but there's more to consider in terms of media references, especially, when it comes to the low and mid brow field of rather ephimeral culture.

While Steve Russell once described Spacewar's spaceship as "crude cartoons" and the outlines of the Wedge as "curvy like a Buck Rogers 1930s spaceship" (quoted in: Kent, Steven L, The Ultimate History of Video Games. Roseville, CA: Prima Publishing, 2001), Martin Graetz recollects the bunch of future Spacewar authors frequently visiting "one of Boston's seedier cinemas to view the latest trash from Toho" in "breaks between books" (J. M. Graetz, "The Origin of Spacewar"; in: Creative Computing, Aug. 1981). Probably we'll have to consider visual media as well: We might well imagine a scene in a now forgotten, nameless Japanese SF movie, where a ship would magically disappear — Whizz – Swoosh – Blip — in an amazingly crude effect amidst the smokey clouds of exploding space granades — and probably there was as much smoke in front of the screen as in the flickering images projected onto it. At least, this seams to be a more credible source when it comes to the role of hyperspace in the game mechanics as an "emergency device" (J. M. Graetz, 1962, ibidem), or, as Wikipedia puts it, "Hyperdrive may also allow for dramatic escapes as the pilot 'jumps' to hyperspace in the midst of battle to avoid destruction." (Wikipedia, "Hyperspace (science fiction)", as of Sep. 2014).

But hyperspace was more than just a non-Newtonian, non-Euclidian element in the simulation, more than another feature of the game mechanics. With computer games being altogether turn-based before, Spacewar! not only introduced game mechanics all by itself, it also introduced an entirely new situation for the players in front of the screen by its taxing, ever demanding realtime interaction*). Hyperspace had also a social side, as a stress relief: "You were gone for a discernible period of time, which gave your opponent a bit of a breather (…)" (J. M. Graetz, 1981, ibidem). By this, hyperspace allowed the players to resurface for a glimpse of time from the hypnotic, virtual realm of the Type 30 display, before submering again and resuming the epic battle of the Needle and the Wedge. On the other hand, this would not only provide a break, it would be also breaking the magic bond to the simulation and the addictive scene exhibited on the PDP's scope, presumingly another reason for its use being rather frowned at, aside from the prey thus escaping its due fate. — In deed, Spacewar! was a sporty game.

—————————
*) "They were absolutely out of their bodies, like they were in another world. Once you experienced this, nothing else would do. This was beyond psychedelics. It impressed the heck out of me." (Stewart Brand on Spacewar! gaming as observed at Stanford's computer center in the early 1960's, quoted in: Markoff, John, "A Long Time Ago, in a Lab Far Away . . ."; New York Times, February 28, 2002). Compare also the following conjuncture in the same NYT-story: "Spacewar was the original 'twitch' game, requiring lightning reflexes. (…) Before long a 'hyperspace' option was added so that a player could make his ship vanish and reappear at a random place on the screen, avoiding certain death." (John Markoff, ibidem).

Hyperspace As-We-Know-It: Spacewar! 3.1

While we're commonly referring here to J. M. Graetz's writings as an authorized source on the background of Spacewar!, we'll have to consider in this context that the passages on hyperspace (both in the DECUS paper and in the Cerative Computing story) are describing the hyperspace patch for Spacewar! 2b. Was this driven by the "Mark One Hyperfield Generators" (J. M. Graetz, 1981, ibidem), the spaceships in Spacewar! 3.1 feature a revised hyperdrive that could be called "Mark I, Series B":

It's a little controversial. Some people deplore it, and it's fairly common to play games without it.... It was of course vital to put in problems with hyperspace. You know, when you come back into normal space from hyperspace, there is initially a small energy-well which looks amazingly like a star; if a torpedo is shot into that energy well, lo and behold the ship blows up. There is also a certain probability of blowing up as you finally break out of hyperspace. Our explanation was that these were the Mark One hyperfield generators and they hadn't done really a thorough job of testing them — they had rushed them into the fleet. And unfortunately the energies that were being dissipated in the generators at breakout were juuust barely what they could handle. So the probability of the generator flying apart and completely killing the spaceship was noticeable on the first couple of uses, and after four uses it was only an even chance of surviving hyperspace. So it was something that you could use but it wasn't something that you wanted to use.
Steve Russell on hyperspace (quoted in: Brand, Stewart, ""SPACEWAR — Fanatic Life and Symbolic Death Among the Computer Bums"; Rolling Stone magazine, Dec. 1972.)

While the tubes and coils would still require some time to cool and relaod, once the hyperfield generators were ready, they would be reponsive on the spot, sending the ship reliably to the voids of "Nth space" ether each time the trigger was deployed. The tricky part became the re-entry: There was an increasing chance of the ship breaking, with a pixel dust explosion being a certainty on the 8th attempt. Besides of not only displacing the ship to a random location (as in the original hyperspace patch), the ships were now reset to a random direction and velocity, as well. And there was also another little extra, namely the addition of a re-entry or break-out phase, in which the ship's position would be heralded by a bright, star-like spot on the screen.

We may note some important differences to the original implementation (which we will investigate a little later): First, the cluncky, unreliable feeling of the interaction with the original hyperspace device was gone, like the breaking animation as well. Second, the additional changes applied to the ship's parameters were not only adding some randomness, but were also adding some more information to be quickly processed and reacted to, making the re-entry an even more encompassing phase of the game. And finally, the introduction of the "energy well" would not only add another risk for the player who made the jump — as the ship would be sitting like a lame duck on the screen, ready to be shot by an alert opponent —, it would also make an end to the breaking of the flow by making this rather a phase of increased and intensified concentration. — Had hyperspace been an outstanding feature in Spacewar! 2b and the original patch, it had now become an integrated part of the game, not only in terms of code, but even more so in terms of gameplay.

Also, we may observe, once more, the notion of hyperspace being "for noobs" and the game commonly played without it. What's interesting here, is that this was left as a convention to the players and there was no provision to disable the feature. (This could have been easily assigned to sense switch 6, which was disabling the heavy star and by this gravity. This would have been a nice feature for Spacewar! as a demonstration object — see, this is how Newton's laws govern flight in the voids of space; see, this is, what it's like, if we add a star and gravity —, but as soon as Spacewar! had become a game, nobody would have probably done without it.) Thus, the various sense switches and parameters were only configuring the simulation, but where not intended to set up any rules of the games. You had to trust the word of your opponent, with the steady risk of your prey escaping all the same.

Note: A somewhat similar risk and dependancy on human conduct was added by a later modification to the program that would send the PDP-1 into debug mode by bringing up DDT on the press of a key of the console typewriter: "After the drum was delivered one of its 32 tracks was dedicated to Spacewar, and the program changed to respond to any keypress on the typewriter by invoking DDT ... the most common purpose of which was to change one of the low-memory constants." (Joe Morris, alt.folklore.computers/T7Oduo-ljgI, 4 Nov. 2001.) As a result, anyone near the Flexowriter was watched with eagle's eyes, since there was always the risk of someone imposing an arbitrary stop to the game, either by accident or as a hoax. (There's a source on this, too, but I'm momentarily not able to find it.)

Anyway, time to investigate the code and insert the standard credits:

Spacewar! was conceived in 1961 by Martin Graetz, Stephen Russell, and Wayne Wiitanen. It was first realized on the PDP-1 in 1962 by Stephen Russell, Peter Samson, Dan Edwards, and Martin Graetz, together with Alan Kotok, Steve Piner, and Robert A Saunders. Spacewar! is in the public domain, but this credit paragraph must accompany all distributed versions of the program.

The Code

First, there are some constants/parameters related to hyperspace at the top of the source:

spacewar 3.1  24 sep 62  pt. 1
...

/ interesting and often changed constants
...
mhs, 22,	law i 10	/ number of hyperspace shots
hd1, 23,	law i 40	/ time in hyperspace before breakout
hd2, 24,	law i 100	/ time in hyperspace breakout
hd3, 25,	law i 200	/ time to recharge hyperfield generators
hr1, 26,	scl 9s		/ scale on hyperspatial displacement
hr2, 27,	scl 4s		/ scale on hyperspatially induced velocity
hur, 30,	40000		/ hyperspatial uncertancy

Second, there is the trigger for hyperspace in the spaceship routine at label sr5:

sr5,	count i \mh3, st3	/ hyperbutton active?
	dzm i \mh3
	lac i \mh2
	sza i
	jmp st3
	lac \scw
	cma
	ior i mco
	and (600000
	sza
	jmp st3
	lac i ml1
	dac i \mh1
	lac (hp1 400000
	dac i ml1
	xct hd1
	dac i ma1
	law 3
	dac i mb1

And finally, there are the hyperspace routines themselves:

	/ hyperspace routines 

/ this routine handles a non-colliding ship invisibly
/ in hyperspace

hp1,	dap hp2
	count i ma1, hp2
	law hp3		/ next step
	dac i ml1
	law 7
	dac i mb1
	random 
	scr 9s
	sir 9s
	xct hr1
	add i mx1
	dac i mx1
	swap
	add i my1
	dac i my1
	random
	scr 9s
	sir 9s
	xct hr2
	dac i \mdy
	dio i \mdx
	setup \hpt,3
	lac ran
	dac i mth
hp4,	lac i mth
	sma
	sub (311040
	spa
	add (311040
	dac i mth
	count \hpt,hp4
	xct hd2
	dac i ma1
hp2,	jmp .

/ this routine handles a ship breaking out of
/ hyperspace.

hp3,	dap hp5
	count i ma1,hp6
	lac i \mh1
	dac i ml1
	law 2000
	dac i mb1
	count i \mh2,hp7
	dzm i \mh2

hp7,	xct hd3
	dac i \mh3
	lac i \mh4
	add hur
	dac i \mh4
	random
	ior (400000
	add i \mh4
	spa
	jmp hp5
	lac (mex 400000
	dac i ml1
	law i 10
	dac i ma1
	law 2000
	dac i mb1
hp6,	lac i mx1
	dispt i, i my1, 2
hp5,	jmp .

Further, the code makes use of the following macro definitions:

define setup A,B
	law i B
	dac A
	term

define count A,B
	isp A
	jmp B
	term

	define
dispt A,Y,B
	repeat 6, B=B+B
	lio Y
	dpy-A+B
	term

	define
random
	lac ran
	rar 1s
	xor (355670
	add (355670
	dac ran
	term

(Code as in version 3.1. For the basics of PDP-1 instructions and Macro assembler code, please refer to Part 1 or bring up the instruction list available at the tab at the bottom of the page.)

The Trigger

We've already investigated how the hyperspace trigger was parsed and the hyperspace routine was replaced for a spaceship's standard object routine in Part 5, but it might be suitable to recapitulate this here in context:

sr5,	count i \mh3, st3	// count up cooling, jump to return, if still counting
	dzm i \mh3		// reset cooling counter (prevent count around)
	lac i \mh2		// load number of remaining jumps
	sza i			// is it not zero?
	jmp st3			// no (zero), jump to return (st3)
	lac \scw		// load current control word
	cma			// complement it
	ior i mco		// inclusive or with old control word
	and (600000		// apply mask for bits 0 and 1
	sza			// is it zero?
	jmp st3			// no, jump to return
	...			// (hyperspace setup here)
st3,				// end of spaceship routine
srt,	jmp .			// return

(As usual, comments starting with a double slash are mine; N.L.)

Before we start, we may recall how the objects are stored and referred to: The objects are stored in arrays of their properties right at the end of the code and the addresses of the current object's properties are set up in various pointers by the main loop, both in labeled locations and variables. By including the defer bit or i-bit, as in "lac i \mh2", we are rather refering to the property itself than to the pointer in which's address part the location of this property is stored for a deferred memory lookup.

We start with a combined count up and check of the hyperspace cooling in pointer \mh3 (pointing to the the third of four hyperspace properties of a spaceship). If the value would be negative after the increment and we would still be counting, we skip the rest and are jumping to the return at label st3 (see the macro count). Otherwise, we reset the counter to not eventually end up at a value of 400000 (the maximum negative number), thus accidentially starting a very long cooling phase.

The next three instructions are checking the second of the four hyperspace properties in pointer \mh2. If zero (please mind the negating i-bit in the sza or skip on zero accumulator instruction), we take the jump to the return address at st3 again and skip the rest. The property in \mh2 has been initialized during the setup phase of the game, when the spaceships acquire their initial properties like start positions, by executing the instruction in constant msh. Since this contains the instruction "law i 10" (load decimal -8), and since it will be incremented inside the hyperspace routine for each jump, it does what's indicated by the comment to msh, namely limitting the "number of hyperspace shots". Therefor, any attempts of jumping to hyperspace will be blocked after the eighth one, if we weren't exploding on re-entry earlier.

If we've taken the hurdles so far, we're finally parsing the control input: The control word is stored in variable \scw, which is now loaded into the accumulator by the instruction "load \scw". Next, this value is complemented ("cma") and an inclusive OR is applied with the last control word stored in \mco. (At least a comment in an other part of the source told us it would be the old control word.) Then, a bit-mask for the two highest bits is applied by an AND, as in "and (600000". If the resulting value would be zero, we're skipping the jump to the return and are finally setting up for hyperspace.

What is happening here? As we've seen in the Part 5, the highest bit in \scw is for counter-clockwise rotation and the second one is for clockwise rotation. So the mask in the constant 600000 is isolating the bits for left and right turn. But what might this complementing and or-ing be all about?
Let's ignore \mco for a moment and consider both the bits for left and right being set: The complement will flip the bits to zero and the skip on zero will successfully skip the jump to return and continue with the setup-part.
So, what could the OR with \mco be adding to this?
If any of the two bits would have been set in the previous frame, the bits won't be zero anymore, thus inhibiting hyperspace. Since we would have taken the jump to hyperspace in the previous frame, if both of them would have been set, we've just to consider the case of only one of them being set: Apparently, this was intended to prevent an accidential jump to hyperspace, when an overlap of turn commands would occur while maneuvering with the test word controls. (With hyperspace being wired up to a distinctive lever position in the control boxes, this would not have been an issue with them.) But this is dead code, because, as we've already seen in Part 5, the variable \mco is never set in Spacewar! 3.1. — And this is actually a good thing, because this would have made the use of hyperspace nearly inhumanly difficult, since you would have to activate the two test word switches exactly at the same time, during the very same frame. It is probably because of this, why this piece of code was eventually discarded in Spacewar! 4 in favor for a much simpler method for testing bits 0 and 1.

But, we may ask, why was the control input encoded in a 4-lines signal at all, if this would impose such problems? Considering the wide use of 5-bit encodings (like Baudot code), a 5-lines signal would have been the more natural option to go with from the beginning. So, why introduce an encoding scheme at all? I would guess, this was related both to human factors and to the secrecy and stealth of operations. Fiddling with the tiny test word switches was probably complex enough with four of them to handle, but by adding another one, you would have to move and reposition your hand(s) in order to reach it. Moreover, there was put some importance to the secrecy of the players, like by carefully choosing such types of levers and switches for the control boxes that would provide a silent operation, in order to not to disclose any void attempts of operation. ("They were switchboard type switches, so they didn't click; […] one of the problems with clicking switches, which is what people tend to start with, is you could tell when your opponent is out of torpedoes or rockets because you hear the switch clicking and nothing is happening on the screen." Oral History of Steve Russell, interviewed by Al Kossow, rec. 9 Aug 2008, CHM catalog no. 102746453, p. 16) Analogously, having to reposition or move a hand wouldn't only add some discomfort to the players, it would also disclose their intentions to the opponent.

Eventually, in case the trigger was successfully parsed, we arrive at the setup of the hyperspace-routine:

	lac i ml1		// load address of spaceship routine
	dac i \mh1		// and store it in \mh1
	lac (hp1 400000		// address of hyperspace routine, non-collidable
	dac i ml1		// store it as new object routine
	xct hd1			// execute "law i 40" (load -40)
	dac i ma1		// store it in ma1 (duration in frames)
	law 3			// load 3 and store it as the instruction count
	dac i mb1

Compared to the tests before, this is rather straight forward: First, we load the address of the current spaceship routine (the code exected just now) from pointer ml1 and store it in \mh1, the pointer to the first of the four hyperspace properties. Then we set up a new control handler, by loading a constant expression into the accumulator which is assembled from the address of the hyperspace routine and the value 400000 added to it. The value 400000 is representing a set sign-bit, thus any collision detection will be disabled for a ship in hyperspace. (See Part 3 for details.) The instruction "dac i ml1" installs this as the new handler code and the spaceship will from now on be handled by the hyperspace routine at hp1. The instruction "xct hd1" executes the instruction stored at label hd1. Since this happens to be "law i 40", the value -40 will be loaded into the accumulator and then deposited in the pointer ma1, as the duration in frames, readily prepared for a count-up. Finally, we load the value 3 and store it in the property mb1, representing the estimated instruction count of the routine. (As we've seen in Part 3, this reduces the number of extra cycles spent in an empty counting loop at the end of the main loop in order to stabilize the frame rate.) The next instrucion will be the jump to the return address at label srt / sr3, which is also marking the end of the entire spaceship routine.

So far, we've seen the following properties and pointers involved:

\mh1 .... backup of the address of the spaceship routine
\mh2 .... counter for remaining jumps
\mh3 .... hyperspace cooling
\mh4 .... remaining probability of breaking (not seen yet)

 ml1 .... current object routine (sign-bit set: non-collidable)
 mb1 .... instruction count
 ma1 .... general purpose counter, here: duration in frames

The Thrills of Hyperspace

How difficult would it be to navigate in Nth dimensional hyperspace? As we may have already guessed by the instruction count in mb1 of only 3, this is surprisingly unspectular, because — let's face it — there isn't much to do at all:

/ this routine handles a non-colliding ship invisibly
/ in hyperspace

hp1,	dap hp2
	count i ma1, hp2
	...				// (initialize next step)
hp2,	jmp .

We just deposit the return address in the address part of hp2 and then increment property ma1 in a count loop. As long as the contents of the counter is still negative (this will be so for decimal 31 frames), we jump to the return at label hp2. — That's all folks!

Eventually the counter will overflow to zero and we'll continue at the code following immediately to the insertion of the macro count:

	law hp3		/ next step
	dac i ml1	// store address of hp3 as new object routine
	law 7
	dac i mb1	// instruction count: 7
	random 		// get a random number (in AC)
	scr 9s		// split it: highest 9 bits in AC, lower 9 bits in IO
	sir 9s		// both registers aligned to the right
	xct hr1		// scale it (scl 9s)
	add i mx1	// add it to pos x, store new position
	dac i mx1
	swap		// swap register
	add i my1	// add it to pos y, store new position
	dac i my1
	random		// get a new random number
	scr 9s		// split it
	sir 9s
	xct hr2		// scale it (scl 4s)
	dac i \mdy	// store as new velocity in dx, dy
	dio i \mdx

First, this is setting up a new start address for the object routine to be used during the breakout phase: "law hp3" loads the address in the accumulator and "dac i ml1" stores it as the new object routine. Since there is no sign-bit, the ship will be collidable during the breakout. The instrcuction count will be 7, so we're loading the number by "law 7" and store it in property-pointer mb1.

Second, we're setting up the new positional properties. We'll start with the breakout position, which we'll have to prepare here in order to display the heralding spot. The macro random (we've seen this one a couple of times before) loads a fresh random number into the accumulator. This value is now splitted in twos by the next to instructions: "scr 9s" performs a right-shift by 9 bit positions on the combined AC and IO registers. The lower half of the value will be now in the higher part of IO and the formerly 9 highest bits in the lower half of the accumulator. The instruction "sir 9s" applies another shift to the right by 9 bits, but this time to IO only. (We may note that any previous contents of IO would be shifted out by now.)

We may recall, how positions are stored in Spacewar!: These are screen-coordinates, ready to be applied by the dpy instruction, with the screen location in the 10 highest bits. (We may think of this as a fractional number with a sign-bit, 9 integer digits and a binary point to the right of bit 9.) Since our splitted random number is now aligned to the right, this would hardly add anything to the display position, if we would think of this as an offset vector. The instruction "xct hr1" now applies the appropriate scaling, by executing the instruction in the constant hr1 (in the constants table right at the beginning of the source). As this is a "scl 9s", we are moving the random vector by a combined shift by 9 bits to the left for a full-scale effect, anything from the same spot to half a screen width in any direction. Then we're adding the x-value of this offset vector to x-position in mx1 and store the updated value. After a swap, exchanging the contents of the AC and IO registers by two consecutive "rcl 9s" instructions, we're updating the y-position.

Another insertion of macro random get's us a new random number in AC, which ist then splitted into the AC and IO registers, just as we've seen it above. The instruction "xct hr2" applies another scaling, this time a "scl 4s", a combined shift to the left by just 4 bits. Since the next two instructions are storing this vector as the new delta x and delta y (variables \mdx and \mdy respectively), the scaling was just setting up the maximum velocity of the ship after the breakout.

With the positional and movement vectors set up, we've another property left, namely the rotation angle in mth:

	setup \hpt,3		// 2 iterations
	lac ran			// get last random number
	dac i mth		// store it
hp4,	lac i mth		// now adjust it to 0..2π
	sma			// is it negative?
	sub (311040		// no, subtract 2π
	spa			// is it positive?
	add (311040		// no, add 2π
	dac i mth		// store it
	count \hpt,hp4		// repeat it
	xct hd2			// duration of breakout phase (law i 100)
	dac i ma1		// store it
hp2,	jmp .			// return

The insertion of the macro setup loads the number -3 into the variable \hp3 to be used as a counter. Then we load the last random number (stored in location ran) and deposit it as the new rotation angle. Now, this random number is anything from 0 to 777777 (decimal -131071 to +131071) and the angle is in the format of the sine-cosine routine, namely a signed fractional number with three integer bits in the range of -2π .. 2π. To adjust the angle to the required format, we load its value at label hp4 again (this is also the start of the adjusting loop). Would the value be positive, we subtract from it (as in the constant expession "(311040"). Would it be now negative, we add to it. Then we store it again. Since 2π is fitting about 2 times into 777777 (decimal 262143/102944), we'll have to repeat this at least once, as provided by the next insertion of the macro count. We may note that there is some sort of relation between the velocity vector and the rotary angle, since they are both based on the same random number. But this will go quite unnoticed, since the wrap around for adjusting the angle to the required range won't scale that well, thus obfuscating this dependency to the eyes of the users.

The last instructions are loading the "time in hyperspace breakout" by executing the instruction at label hd2 in the constants table. This is "law i 100" and we store it in ma1 (or to be precise, into the location in the address part of pointer ma1), thus initilizing a count up for (octal) 100 frames.

Note: We may notice that this was a bit lengthy operation, at least a bit longer than 7 instructions, the number stored as the current instruction count. For this, there will be a small unnoticeable hick-up in the frame rate.

Breakout

The basic breakout part is nearly as easy as the in-hyperspace part, we've seen before:

hp3,	dap hp5			// store return address
	count i ma1,hp6		// count up on pointer ma1 (100 frames)
	... 			// (re-entry code here)
hp6,	lac i mx1
	dispt i, i my1, 2	// display a dot at x/y in brightness 2
hp5,	jmp .			// return

As we can see, we'll jump to the display code at label hp6 as long as the counter in ma1 is negative. At hp5, we're loading the x-position, we've assembled above, into the accumulator as the screen x-coordinate, suppling the y-coordinate as the second argument to the macro dispt. The first parameter is a value to be subtracted from the opcode of the dpy display command, making this a dpy-i instruction, basically a shoot and forget without negotiating for a completion pulse. The third parameter is the brightness, here 2 for the second brightest intensity available. (Since this is displayed in every frame, it will be visually at least as bright as the brightest of the background stars.) Thus, we're just displaying the heralding spot, a sitting target, since we didn't include a sign-bit in the link to this as the object routine.

After octal 77 iterations we'll finally reach the breakout and setup the ship to normal cruise mode in the instructions immediately below the insertion of the macro count:

	lac i \mh1		// restore object routine from backup
	dac i ml1
	law 2000		// restore instruction count (2000)
	dac i mb1
	count i \mh2,hp7	// count up number of jumps remaining
	dzm i \mh2		// prevent count around

We've seen this pattern twice before: First we're setting up the object routine for the spaceship, this time restoring it from its backup in property mh1, and restore the default instruction count of 2000 in mb1. We discussed the count up on property mh2 already in the context of parsing the trigger: The property mh2 will be counted up from octal -10, blocking any further attempts after it would have reached zero at the 8th jump. The instruction "dzm i \mh2" is the usual protection against a count around, but here of more decorative nature, since the code won't be visted again, after mh2 became zero. Notably, both branches, the jump to label hp7, if still counting, and the fall through, in case we would have reached zero, are meeting at the next instruction labeled hp7, directly below.

Now it's time for the critical re-entry. Since there wasn't a chance to blow up until now, this our last chance to have the thing explode, because some of the tubes of the hyperfield generators would blow under the burders of re-entry. Let's see, how it was done:

hp7,	xct hd3			// (law i 200) load -200
	dac i \mh3		// store as hyperspace cooling time
	lac i \mh4		// load remaining probability of a clean return
	add hur			// add hyperspatial uncertancy (40000)
	dac i \mh4		// store it
	random			// get a random number
	ior (400000		// set sign-bit
	add i \mh4		// add updated uncertancy
	spa			// is it positive (abs random number ≤ uncertancy)?
	jmp hp5			// no, jump to return
	lac (mex 400000		// oops, explode (at mex, non-colliding)
	dac i ml1		// store explosion routine as object routine 
	law i 10		// setup explosion size
	dac i ma1
	law 2000		// setup instruction count
	dac i mb1
hp5,	jmp .			// return

First, we execute another setup from the contsants table, this time from label hd3 ("time to recharge hyperfield generators") loading the number 200 by the instruction "law i 200", and store it as the cooling time in the property mh3 (the one counted up and checked right at beginning of the trigger-parsing).

So, will we blow up in the end?

We begin with loading the probability-property in \mh4 into the acummulator and by adding the constant hur to it. The constant hur is, once again, defined in the constants table and happens to be the number 40000. After we've stored the updated probablity, we get us a fresh random number from the macro random and make sure that it is a negative value by applying an inclusive OR by 400000 (the equivalent of a set sign-bit) to it. The result in the accumulator is now the difference of the negative random number and the value in pointer \mh4. If negative, the absolute random number was greater than our propability treshold, if positive, it didn't reach the threshold and we will skip the following jump to the return to see an explosion.

This is set up in the usual pattern: The instruction "lac (mex 400000" sets up the explosion routine at mex as the new object routine, notably with a set sign-bit and therefor not collidable. This is stored in pointer \ml1 as the spaceship's new object routine. Finally, we store the number -10 in pointer ma1 as the the size of the explosion (the same size as when a spaceship is colliding with the central star) and reset the instruction count in mb1 to the standard value of 2000.

Let's go back to the probability check: The property in pointer mh4 was initially set up to zero and will be octal 40000 at the first jump and it will be eventually incremented to 400000 (40000×8, or a shift by a full octal digit) at the eighth and last jump. The random number is forced to be a negative value by setting the sign-bit by an inclusive OR (we don't care about the other ones, since they are a bunch of random bits anyway). Then we add the threshold, and check for a positive result. In other words, if the threshold in pointer \mh4 exceeds or equals the absolute value of the random number, we will get a positive result (random + \mh4 ≥ 0) and the ship will break at re-entry. Since our threshold in \mh4 increases, the likeliness of the result being a negative number decreases steadily — and by this the risk of breaking increases from jump to jump. Eventually, at the eighth jump, the value in \mh4 will be 400000 after the increment, the equivalent of a set sign-bit and the maximum negative number to be represented in the PDP-1. Thus, the addition will overflow to a positive number, regardless of the state of the other bits in the accumulator, and the ship will break with certainty.

Two things are interesting here: First, how this is rather working on a bunch of bits, than treating the values as numbers in an orthodox way. We could come up with some ease with an equivalent of this which would exhibit a more mathematical view, like we've seen it in other parts of the code, by using rather a combination of skips and complements (which would also perform in exactly the same cycle time).

The other one being the fact that this renders the limit on the "number of hyperspace shots" rather useless, since \mh2 will never block the hyperspace trigger by reaching zero after the 8th jump, since the ship will break with certainty on this last shot and we will never return to see that check again.

Anyway, these are constants to be modified ad libidum, so it's nice to have both of these mechanisms and not only one combining them in a single, rather obfuscated check. On the other hand, we could muse that the check for the maximum number of jumps would be some kind of legacy from a previous implementation which would have worked on an absolute limit only, like the hyperspace patch for Spacewar! 2b did it. If so, what we have just looked at, would have been rather "hyperfield generators MK I, Series C" — "C" at least.

*****

By this, believe it or not, we're actually through with Spacewar! 3.1, having inspected literally every single line of code! (Ok, we didn't inspect every single one of the starfield co-ordinates, but we had at least a look at a map of them.) — But there's still a little extra to be explored, and you wouldn't want to miss this one, for sure …

Spacewar! 2b and the "Minskytron" Hyperspace

Time to have a closer look at the first real-life implementation of hyperspace in human history.

Before Spacewar! 3.1, there was Spacewar! 2b and the hyperspace patch by Martin Graetz. This is also the setup described in nearly 20 years later "The Origin of Spacewar" (Creative Computing, Aug. 1981), by hyperspace's original author, Martin Graetz:

Hyperspace
While all this was going on, I was in my secret hideaway (then known as the Electronic Systems Lab) working on the ultimate panic button: hyperspace. The idea was that when everything else failed you could jump into the fourth dimension and disappear. As this would introduce an element of something very like magic into an otherwise rational universe, the use of hyperspace had to be hedged in some way. Our ultimate goal was a feature that, while useful, was not entirely reliable. The machinery, we said, would be the "Mark One Hyperfield Generators ... hadn't done a thorough job of testing ... rushed them to the fleet" and so on. They'd be good for one or two shots, but would deteriorate rapidly after that. They might not work at all ("It's not my fault, Chewie!") or if they did, your chances of coming back out intact were rather less than even. Slug [Steve Russell; Ed.]: "It was something you could use, but not something you wanted to use."

The original hyperspace was not that elegant. "MK I unreliability" boiled down to this: you had exactly three jumps. In each jump your ship's co-ordinates were scrambled so that you never knew where you would reappear — it could be in the middle of the sun. You were gone for a discernible period of time, which gave your opponent a bit of a breather, but you came back with your original velocity and direction intact. To jump, you pushed the blast lever forward.

Hyperspace had one cute feature (well, I thought it was cute). Do you remember the Minskytron? One of its displays looked very much like a classical Bohr atom, which in those days was an overworked metaphor for anything to do with space and science-fiction. Reasoning that a ship entering hyperspace would cause a local distortion of space-time resulting in a warp-induced photonicstress emission (see how easy this is?), I made the disappearing ship leave behind a short Minskytron signature (Figure 4).
The Minskytron Hyperspace (The Origin of Spacewar, Figure 4)

The Origin of Spacewar! — Figure 4.
"Warp-induced photonic stress emission." The Hyperspace Minskytron signature.
(Please mind that the image is upside down, slightly rotated, and off-center.)
Credits: J.M. Graetz 1962/1981

And in his DECUS paper Martin Graetz gives another detail that we haven't mentioned already (see the top of the page for the preceding passage), as it applies to the hyperspace patch only:

Each player has exactly three hyperspace jumps.
(J. M. Graetz, "Spacewar! Real-Time Capability of the PDP-1", DECUS Proceedings 1962: Papers and Presentations of the Digital Equipment Computer Users Society; Maynard, Massachusetts, 1962. p. 37-39. p. 38)

Only a few months ago this would have been essentially all, we could report on the first hyperspace in gaming history. But, meantime, we managed to not only identify the version of Spacewar described (Spacewar! 2b) and the missing hyperspace routine (hyperspace85.bin), we also identified the source code of both Spacewar! 2b and the hyperspace85-patch in a batch of listings that were donated to the iMusée, the Musée de l'informatique du Quebec. (For more on this, see Intermission: Digging up the Minskytron Hyperspace in this series.) This puts us not only in the rather privileged position to offer an in-depth view on this for the very first time, we may also experience it on our own (in emulation), like this:

The Minskytron Hyperspace (closeup, emulation)

Jumping to hyperspace near the heavy star leaving the "Minkytron" hyperspace signature behind.
(Emulation, closeup; image: N. Landsteiner 2014)

Play Spacewar! 2b! and the "Minskytron" hyperspace patch (original code running in an online emulation).

The setup we're describing here is namely Spacewar! 2b (2 Apr 62), the patch "hyperspace85.bin" (which isn't exactly the original name, as we'll see soon), and the patch "spaceWarRstrt.bin" (also named "spacewAutoRestartPatch.bin") applied to it. (A game of Spacewar! 2b originally ended with one or both of the ships blowing up, something that was fixed by the auto-restart patch in order to provide a continous gameplay.)
Also, there was a scorer patch ("A scoring facility was added so that finite matches could be played, making it easier to limit the time any one person spent at the controls." — J.M. Graetz in "Origin of Spacewar" on the last prepatory steps.), which seems to be lost, but was probably much like the scoring in version 3.1.
This is also the setup described in "Origin of Spacewar" and probably the setup shown at the annual Science Open House Day at MIT in May 1962, the very event, Spacewar's development had been scheduled to. — We may note that by then loading and setting up a game of Spacewar! had become a rather lengthy procedure, with the core program to load, probably the starfield data from another tape ("stars.bin"), the hyperspace patch, the auto-restart patch, to be applied to the hyperspace patch, and the scorer patch, probably to be applied to the auto-restart patch. Moreover, we may assume that applying any changes to any of the sources would have become rather difficult, because you would have to preserve the locations of any key addresses for the other patches — we'll see some of this in the hyperspace patch — and therefore the game was essentially frozen until the code was consolidated and merged into a single strain in fall 1962, the piece of code we know as version 3.

So, let's have a look at it — and we're up to some surprises:

The Code

HYPERSPACE 85

/A new tape prepared by JMG, 26 Oct 1985
/from the listing of Hyperspace VIci, 2 May 1962

/Macros privy to Hyperspace

define setx S,K
	law K		dac S
terminate

define split
	scr 9s	sal 9s
terminate

define pack X,Y,S
	lac X		lio Y		sar 9s		scl 9s		dac S
termin

define marvin X1,Y1,X2,Y2,S,SS
	lac X1		add X2		S		add Y1 		dac Y1
	sub Y2		SS		cma		add X1		dac X1
termi

define dismin X,Y,XC,YC
	lac Y		add YC		swap		lac X		add XC		dpn
term

define reset S,K
	lac (K		dac S
term

define random N
	lac N		rar 1s		xor (335671			add (335671
	dac N
term

dpn=dpy-i

/Symbols privy to SPACEWAR

mh1=2665		mh2=2666		mh3=2667		mx1=1567
my1=1577		mq1=1642		ml1=1540		mb1=1637
srt=2343		ran=2654		nnn=4500		not=3573
scw=2673		oc=337			mco=2254		ot1=2365
ot2=2402		nb1=3243

///

dimension th1(2),th2(2),th3(2),th4(2),th5(2),th6(2)

/Patch location table

hyp=5240		a3x=1525		a4=5510
mlx=1456		hml=5530		shx=2341
srh=5545		mqx=1642		hq1=5570


/Patches directly into SPACEWAR

a3x/	jda a4-1

/Hypertable initialization, part 1

a4/	dap a4x
	lac .-2
	dac nb1 1		/finish main program business
	clear th1,th5 1
	law 4 i
	dac th1
	dac th1 1		/counts tries
	lac (opr
	dac th4
	dac th4 1		/recovery switch
a4x,	jmp .

mlx/	jmp hml

/Hypertable initialization, part 2

hml/	init hp1,th1
	init hp2,th2
	init hp3,th3
	init hp4,th4
	init hp5,th5
	init hp6,th6
	jmp ml1

///

shx/	jmp srh

/Control word inspection sequence

srh/

hp4,	xct .			/th4 table (opr to start)
	lac scw
	cma
	and mco i
	sma
	jmp sh1
	ral 1
	spa
	jsp hyp			/go for it
sh1,	lio scw
	jmp srt-1

mqx/	jsp hq1

/Main loop table indexing

hq1/	dap hqx
	idx mx1
	idx hp1
	idx hp2
	idx hp3
	idx hp4
	idx hp5
	idx hp6
hqx,	jmp .

///

/Hyperspace calculation and Minskytron display

hyp/	dap hxt
hp1,	isp .			/(th1)OK to jum?
	jmp hp2-1		/yes
	dzm hp1 i
	jmp hxt
	lio mx1 i
hp2,	dio .			/(th2)
	lio my1 i
hp3,	dio .			/(th3)
	lac ml1 i
hp6,	dap .			/(th6)
	random ran
	add mx1 i		/diddle ship coordinates
	dac mx1 i
	add my1 i
	dac my1 i

	setx mb1 i,364
	law 20 i
hp5,	dac .			/(th5) loop counters
	move xy1,mh1 i
	move xy2,mh2 i
	move xy3,mh3 i
	init ml1 i,he1
	jmp he1 3

he1,	dap hxt
	setx mb1 i,325
	setx \hcl,4 i		/inner loop count
	lac mh1 i		/unpack point coordinates
	split
	dac \hx1
	dio \hy1
	lac mh2 i
	split
	dac \hx2
	dio \hy2
	lac mh3 i
	split
	dac \hx3
	dio \hy3


/Here it is...
h3,	marvin \hx1,\hy1,\hx2,\hy2,sar 5s,sar 5s
	marvin \hx2,\hy2,\hx3,\hy3,sar 8s,sar 4s
	dismin \hx2,\hy2,hp2 i,hp3 i
	marvin \hx3,\hy3,\hx1,\hy1,sar 1s,sar 6s
	dismin \hx3,\hy3,hp2 i,hp3 i

///

h3a,	count \hcl,h3
	pack \hx1,\hy1,mh1 i	/repack coordinates
	pack \hx2,\hy2,mh2 i
	pack \hx3,\hy3,mh3 i
h3b,	count hp5 i,hxt
	setx hp5 i,100 i	/delay timer
	init ml1 i,he2		/delay entry
	setx mb1 i,12

hxt,	jmp .			/BACK TO THE FRAY...

/Delay and recovery time count loops

he2,	dap hxt
	count hp5 i,hxt
	lac hp6 i
	dap ml1 i
	setx mb1 i,2000
	reset hp4 i,jmp hyj	/recovery gate
	setx hp5 i,500 i
	jmp hxt

hyj,	count hp5 i,sh1
	reset hp4 i,nop
	jmp sh1

xy1,	6
xy2,	10765
xy3,	767765


variables
constants

start

Further, the code makes use of some of the macros from the "macro fio-dec system", most of which we've seen before, as well:

define initialize A,B
	law B
	dap A
	term

define swap
	rcl 9s
	rcl 9s
	term

define count A,B
	isp A
	jmp B
	term

define move A,B
	lio A
	dio B
	term

define clear A,B
	init .+2, A
	dzm
	index .-1, (dzm B+1, .-1
	term

The first suprise is the name of the patch: If you were, like me, a bit suspecious about the name "hyperspace85.bin", you were exactly right. "hyperspace85.bin" is really for hyperspace 1985 and it's a retyped tape from 26 Oct 1985 from an original source of "Hyperspace VIci" from 2 May 1962. We may note the rather advanced and fine granular version code, hinting at a development phase quite furious.

The second surprise is the style of this source: It is nothing like the rest of the Spacewar! code and illustrates quite perfectly, how remote Martin Graetz's "secret hideaway (then known as the Electronic Systems Lab)" really was. Just to note some of the numerous idiosyncrasies of this source:

If we dared to engage in a dissasembly of the binary object code before the source was available, near to nothing would have prepared us for the sight of the real thing. But at least some of these oddities are not that odd at all and may even shed some light on other aspects of the Spacewar!-code:

For instance, on the tab delimited instructions of the macro definitions: We've actually seen this before in the definition of the macro "mult" for the sine-cosine routine, but we didn't make much fuzz about it, then. It's quite logical to assume that Martin Graetz must have had some precedent or model for this coding style, and we already have found an example for this. We may even assume that this might have been fairly common with PDP-1 code at the time.

Then there is this rather strange use of predefined constants for some labels and starting locations of various routines (inspite of just letting the assembler do its job and assign the locations for them), and we may even come up with an explanation for this: We've already mentioned the quite late date (2 May 1962) and the quite advanced version code (VIci) of the source. We've also seen, how the final program would have been loaded in incremental steps with some patches to be applied and even some dependencies there. Namely, there's a dependency on a location in the hyperspace patch for the auto-restart patch, which is meant to be applied to the first one. In order to not break the system and not to have to reassemble all the other patches, making the labels constants would have been a logical thing to do. Just note the key addresses of any previous version as listed in the symbol table of the assembler output and define these as constants for the new version. Provided, the code snippet or routine in question would be either shorter in length in the new version or it would be split to separate parts that would fit into the space of the existing patch. This is also what we see here, and there are in deed some empty memomry registers (or "wholes" in the resulting object code) left over from this process.

But we're dwelling on preliminaries and side-aspects again, let's start with the code:

Setup

The patch starts with some macor definitions that we'll cover as we encounter their use in the code. Then there are blocks of assignements which are mostly setting up locations in the main program in some labels that are providing the usual names to the patch. (A partial copy of the symbol table of the main program.) A few others are defining start addresses for individual patches of code, for reasons we've mentioned before.

/Symbols privy to SPACEWAR

mh1=2665		mh2=2666		mh3=2667		mx1=1567
my1=1577		mq1=1642		ml1=1540		mb1=1637
srt=2343		ran=2654		nnn=4500		not=3573
scw=2673		oc=337			mco=2254		ot1=2365
ot2=2402		nb1=3243

dimension th1(2),th2(2),th3(2),th4(2),th5(2),th6(2)

/Patch location table

hyp=5240		a3x=1525		a4=5510
mlx=1456		hml=5530		shx=2341
srh=5545		mqx=1642		hq1=5570

The first notable instruction we have to address here, isn't exactly an instruction, but a pseudo instruction of the Macro assembler:

dimension th1(2),th2(2),th3(2),th4(2),th5(2),th6(2)

We haven't seen the keyword dimension before. This one is reserving the number of addresses provided in the paranthesis and assigns the address of the first of these locations to the label provided in front of it. The locations will be reserved where the variables keyword is found in the source. Notably, when looking at the resulting object code, this isn't discernible from the table setup for the spaceships properties, we've seen in the main program.

Speeding ahead a bit, we'll provide the semantics of this table besides its definition, a series of pairs of properties, one for each of the ships:

th1   ....  number of hyperspace shots, spaceship 1
th1 1 ....  same for spaceship 2
th2   ....  pos x
th2 1
th3   ....  pos y
th3 1
th4   ....  internal jump vector (see hp4), initially "opr"
th4 1
th5   ....  loop counter
th5 1
th6   ....  backup: address of spaceship routine 
th6 1

While the main program is also managing 3 properties per ship dedicated to hyperspace (mh1 .. mh3), these were obviously not enough and the patch extends the objects by this table, the properties of which it will have to manage on its own.

What's following now, isn't just a single patch, but a series of patches to be applied to main program:

The First Patch

The smallest constant for a pseudo-label to be found in the source is the definition "a4=5510" — and a4 is also the first real piece of code that we encounter, when scanning the source top-down. Apart that it isn't. There's the patch for the main source, and this one is a jda to the location immediately before a4.

/Patches directly into SPACEWAR

a3x/	jda a4-1	// a3x=1525

The patched location is address 1525 in the main program, and this is, what it looks like:

//Spacewar 2b:

	law 2000
	dac nb1
	dac nb1 1	// location 1525: patched to "jda a4-1"
	jmp ml0

This is just at the end of the code setting up the spaceships at the start of a game, with the jump to ml0 being the jump to the start of the very first frame. We may note that this isn't just patching the final "jmp ml0" (which would have been easy), but is rather avoiding this important location carefully (maybe it was already patched elsewhere). By replacing the instruction "dac nb1 1" by "jda a4-1", the program is thus rerouted to the patch, making the address a4-1 or 5507 the effectively narrowest address of the patch.

/Hypertable initialization, part 1

// 5510

a4/	dap a4x
	lac .-2			// store 2000 (put by jda into a4-1) in nb1+1
	dac nb1 1		/finish main program business
	clear th1,th5 1		// clear table from th1 to th5+1
	law 4 i			// load -4 (3 jumps)
	dac th1			// store it in th1 and tha+1
	dac th1 1		/counts tries
	lac (opr		// load (opr, store it in th4 and th4+1
	dac th4
	dac th4 1		/recovery switch
a4x,	jmp .			// return

Since the patched instruction was setting up the instruction count for spaceship 2 to the value 2000, the contents of the accumulator, and because this is now replaced by the return address put there by the jda instruction which we inserted there in place, we have a little problem. But since we used the jda instruction, which first places the current contents of the accumulator in the memory location specified in the address part of the instruction and then performs a usual jump-to-subroutine, we've saved this in address a4-1. So, in order to clean up mess we left behind, we first place the return address in the address part of our jump to return at a4x as usual, and restore the contents of the accumulator by "lac .-2". (Since we advanced for a single address, ".-2" is pointing to "a4-1".) Now we can fix the lost instruction from the main program by performing the instruction "dac nb1 1" in the patch, storing the value 2000 as the instruction count for spaceship 2.

By then we're ready to perform the task, we've come here for at all, namely, we're initializing the properties of the spaceships that are private to the patch. The macro instruction "clear th1,th5 1" resets the table entries th1 to th5+1, therby clearing the properties of the two ships (with the only exception of the contents of th6, to be overwritten with the address of the spaceship routine). Next, we're setting up the number of possible jumps for each spaceship: "law 4 i" loads the number -4 into the accumulator — and since this will be used in a usual count-loop, there will be 3 jumps to hyperspace. This is now stored in the hyperspace-properties private to the patch, in locations th1 and th1+1, as in instructions "dac th1" and "dac th1 1" repectively.

Finally, we load the opcode of the instruction "opr" as a constant and store it in the properties th4 and th4+1 for each of the ships. "opr" is here used as a synonym for "nop" (no operation) and we'll see later, what this "recovery switch" is good for. By then, we've finished the private setup phase and are handing back the command to the main program by the jump to the return address at label a4x.

The Second Patch

Despite of the comment "Hypertable initialization, part 2", the next patch isn't exactly commencing on the previous task. It patches the final jump to location ml1 at the end of the instructions that are both defining the objects table, but are also, on the operative side, initializing the properties at the beginning of each frame:

// 1456

mlx/	jmp hml
// Spacewar 2b

...
nh3=nh2 nob
nnn=nh3 nob
	jmp ml1		// location 1456: patched by "jmp hml"

With the instruction "jmp ml1" replaced, we'll take the detour to label hml:

/Hypertable initialization, part 2

// 5530

hml/	init hp1,th1	// set hp1 to address of th1, etc
	init hp2,th2
	init hp3,th3
	init hp4,th4
	init hp5,th5
	init hp6,th6
	jmp ml1

The code at hml is setting up the addresses of the table entries th1 .. th6 in the address parts of the corresponding addresses ph1 .. ph6 for a dual use, both as an operative instruction and as a pointer, just as we've seen it in the main program. Therefor the address parts of ph1 .. ph6 will be pointing to the properties of the first spaceship. (This is also the last time, we've seen the labels thn in the code.) As the patched jump instruction "jmp ml1" is free of context, we can execute it here without further ado, picking up the flow of the main program, just were we made the detour.

"init" is once again an abbreviation for the macro initialize, the same we've encountered repeatedly in the main code:

define initialize A,B
	law B
	dap A
	term

With both the setup for a fresh game and new frame covered, we're ready to actually patch the spaceship routine:

The Trigger

// 2341

shx/	jmp srh

The location patched here, is actually interesting by itself, since it's storing the old control word in mco, an instruction that is missing in Spacewar 3.1:

sr5,  move \scw, i mco

The macro move is copying the contents of one location to another using the IO register, bypassing the accumulator:

define move A,B
	lio A
	dio B
	term

Thus, the patched location will be in the expanded code of the inserted macro, as in:

	lio \scw	// location 2341: patched by "jmp srh"
	dio i mco

And by patching this, we will end up jumping to the following patch:

/Control word inspection sequence

// 5545

srh/

hp4,	xct .		/th4 table (opr to start)
	lac scw		// load control word (redefined here to scw)
	cma		// complement it
	and mco i	// and mco (last control word)
	sma		// is it minus?
	jmp sh1		// no, fix up and return
	ral 1		// next bit
	spa		// is it positive?
	jsp hyp		/go for it	// no (bit 0 and bit 1 set), hyperspace...
sh1,	lio scw		// fix up: scw in IO
	jmp srt-1

The first instruction at label hp4 will execute, what's in property th4 (or th4+1 for spaceship 2). For now, let's have it with that. Let's just recall that this will be executing the void instruction opr, when we would come here at the beginning of a game. So this will be without further effect(s). (A curious reader, scanning ahead, may find that this may be eventually replaced by a jump instruction, thus it's an internal switch to an alternate branch of code, quite like the method replacement in the main code. We may note some object-like notion expressed in this code, as well.)

Let's scan the trigger: Once again, we see a comparison of the control word and the previous control word, quite like in Spacewar! 3.1, but it's a bit of a different theme here: We load the control word by "lac scw" (scw has been redefined here as a simple label, pointing to the actual address of the variable as assigned by the assembler when the main program was compiled), complement it and perform a logical and with the old control word (once again redefined here for the patch as a simple label pointing to the actual location, hence the i-bit for indirect addressing). Since the highest bit is also the sign-bit, we may test it by the sma instruction (skip on minus AC). If the bit is not set, we'll execute the next jump to sh1, where we'll fix the patched instruction and jump back to the main program.

If the sign-bit would be set, we're still in business and rotate the second highest bit into the sign position by the instruction "ral 1" (rotate AC left by 1 bit). This time, we're skipping the jump, if the bit would be not set and the contents of the accumulator would be positive. If negative, we execute the jump-to-subroutine to label hyp, where we'll initilize for the jump to hyperspace.

If we make the skip, we'll execute the instruction "lio scw" at label sh1, fixing the instruction that was replaced by the patch. The final instruction "jmp srt-1" takes us to where we've left the main program, since srt is defined as 2343 in the patch and 2342 is the same as the instruction immediately following to the patched location in the main program.

But, what did we actually check here? Since the control word is complemented at the beginning, it will trigger, if both of the two highest bits are not set. But, since there is also the masking operation with the previous control word, the jump to hyperspace will trigger only, if both of these bits were set in the previous frame. In other words, a ship will not jump to hyperspace, when the trigger is deployed, but, instead, when it is released! This is quite remarkable, and might be addressing the issue with overlapping control input for left and right turn in normal flight. Since an occasional overlap will result in still one of the bits being set, the code will not parse this as a jump. On the other hand, the jump is a bit difficult to handle with the console switches, because the two switches have to be released in the very same frame. Thus, the hyperfield generators might be said to be "not entirely reliable" and that they "might not work at all", even without a dedicated a random factor for this the trigger, at least with the test word controls.

Note: We can see now that the quite complicated code in Spacewar! 3.1 is just inverting this operation in order to send a ship to hyperspace, as soon as the trigger is deployed. It might be that this would have been just the same level of difficulty for the player-operators to handle, but it might also be that letting the switches flip back to their off positions at once — as in the hyperspace patch — would have been a tick easier to handle.

And Yet Another Patch

// 1642

mqx/	jsp hq1

/Main loop table indexing

// 5570

hq1/	dap hqx		// store return address
	idx mx1		// index hyperspace properties
	idx hp1
	idx hp2
	idx hp3
	idx hp4
	idx hp5
	idx hp6
hqx,	jmp .		// return

Here we patch the code that is iterating the pointer in order to have them point to the next object (here, spaceship 2), right at the end of the main loop of Spacewar! 2Bs:

mq1,  idx mx1    / end of comparison and display loop (location 1642)

We'll have to fix the replaced instruction "idx mx1", which we're doing right at the beginning after storing the return address. Then the various pointers are incremented to point to the next address in the internal properties table, just like it is done for the properties of the main program.

We may observe, that this piece of code will be visted for each of the up to (decimal) 24 objects, with the first two of them being the spaceships and any other ones being torpedoes. But we've only reserved some properties for the two spaceships — will the code break, if the pointers are incremented for a torpedo? Not at all, since the code for the trigger is actually visited by spaceships only (as it is patched into the spaceship routine). With the start of a new frame, the property pointers will be reset near label hml to point to the properties of the first spaceship and any previous increments will be ignored. Hence, the only case of interest here is, when the pointers are incremented to point to the properties of spaceship 2, as in the increment of ph1 to be advanced from th1 to th1+1.

Into The Voids of Hyperspace

/Hyperspace calculation and Minskytron display

// 5240

hyp/	dap hxt
hp1,	isp .			/(th1)OK to jum[p]? // th1 was initialized to -4
	jmp hp2-1		/yes
	dzm hp1 i		// reset th1 (prevent count around) and return
	jmp hxt
	lio mx1 i		// store pos x in th2
hp2,	dio .			/(th2)
	lio my1 i		// store pos y in th3
hp3,	dio .			/(th3)
	lac ml1 i		// backup main routine in th6
hp6,	dap .			/(th6)

Here we go for hyperspace: First, we deposite the return address, since this is a subroutine. Then, we check, if there would be still a jump left by executing an index and skip on positive instruction on hp1, the pointer to the number of hyperspace jumps. If it is positive after the increment, we have either arrived at zero, or its value would have been zero before. Eitherway, we'll reset the counter to zero to prevent a count around to a negative value and jump to the return address in the next instruction. If the value is still negative after the increment, we'll skip these two instructions by executing the instruction "jmp hp2-1", which takes us to the instruction "lio mx1 i", where we load the value of the x-coordinate of the spaceship into the IO register to store it for further use in the property th2. (The address of th2 is setup in location hp2, where we stored it in the address part before. Thus, we may also use the label ph2 as a pointer to this address.) Just the same, we store the y-coordinate in property th3, aliased by pointer ph3. Finally, we back up the location of the current spaceship routine in th6 (aliased in the address part of hp6).

With all the old properties saved, we're assigning some new values to the spaceship's properties, to be used after re-entry from hyperspace:

	random ran		// now set up random coors for breakout
	add mx1 i		/diddle ship coordinates
	dac mx1 i
	add my1 i
	dac my1 i

The expression "random ran" stores a fresh random number in location ran and leaves it also in the accumulator. (The macro random is quite the same as in Spacewar! 3.1, but uses an arbitrary location to store the random number, rather than using a dedicated location for this. The location of the random number is to be provided as the single parameter to the macro and is always the same all over the program, here defined in the label ran.) The current x-coordinate of the ship is now updated by adding the random number (left in the accumulator by random) and stored. The y-coordinate is then updated by adding the x-coordinate to it and stored again. The ship will be positioned anywhere on the screen, when the positional vector in mx1/my1 is used again by the standard spaceship routine after the re-entry.

Having taken care of the continuity with the standard routine, it's time to setup the internal hyperflight routine as the spaceship's object method:

	setx mb1 i,364		// store 364 as instruction count
	law 20 i		// load -20, display frames to go
hp5,	dac .			/(th5) loop counters
	move xy1,mh1 i		// mh1 = 6      (  0,   6)
	move xy2,mh2 i		// mh2 = 10765	( 10, 765)
	move xy3,mh3 i		// mh3 = 767765 (767, 765)
	init ml1 i,he1		// ml1 = he1 (operational object routine)
	jmp he1 3

The first of these instructions stes the instruction count of the hyperspace routine in the property mb1 (in the main program) to the number 364 by inserting the following macro:

define setx S,K
	law K
	dac S
terminate

Then we store the number of frames to go in the counter th5, aliased in the address part of hp5. The insertions of the macro move are setting up some values in the hyperspace properties mh1, mh2, and mh3. While we, won't bother with the semantics here, we may note that these are the hyperspace properties already defined and managed inside the main program. (Also, we may note that these are of an alltogether different use and meaning here, as compared to Spacewar! 3.1.) Finally, we're linking the hyperspace routine at he1 as the current object routine of the spaceship in ml1 (also a property of the main program).

Thus prepared, we're ready to go to hyperspace, as in "jmp he1 3":

he1,	dap hxt			// deposit return address
	setx mb1 i,325		// set instruction count
	setx \hcl,4 i		/inner loop count // \hcl = -4, do it 4 times
	lac mh1 i		/unpack point coordinates
	split			// mh1 to \hx1, \hy1
	dac \hx1
	dio \hy1
	lac mh2 i
	split			// mh2 to \hx2, \hy2
	dac \hx2
	dio \hy2
	lac mh3 i
	split			// mh2 to \hx2, \hy2
	dac \hx3
	dio \hy3

The first two instructions are storing the return address and are setting the instruction count, when the routine is called in a conssecutive frame. During the very first frame, we skip these and are entering at he1+3. The instruction "setx \hcl,4 i" initilizes another loop counter to the number -4. This is an inner loop, to performed for each frame, and since the count up will be executed after the action — quite like in a do-while construct —, there will be 4 iterations of this inner loop.

The next instructions are "unpacking [the] point coordinates" via the macro split. The coordinates are stored in the properties mh1 .. mh3 that we've initialized just before. Now we may see, what they are about.

define split		// split AC to hi AC, hi IO (unpack)
	scr 9s
	sal 9s
terminate

By shifting the accumulator (AC) and the IO register as a combined 36-bit register by half a word-length to the right, the lower part of AC is shifted into the higher part of IO. The next "sal 9s" shifts the remainder in AC back into the higher part. Hence, the value in the accumulator will be split into a lower and a higher part, and the formerly lower part will be now in the highest 9 bits of IO.

This is then stored in the variables \hx1 (from AC) and \hy1 (from IO), repeated for the pairs \hx2,\hy2 (unpacked from mh2) and \hx3,\hy3 (unpacked from mh3).

Therefor, the start values of these variables will be:

sx1:      0,   sy1:   6000   (mh1:      6)
sx2:  10000,   sy2: 765000   (mh2:  10765)
sx3: 767777,   sy3: 765000   (mh3: 767765)

By this, we arrived at the heart of the patch, the famous "Minskytron"-like warp-induced photonic stress emission!

The "Minskytron" Signature

/Here it is...
h3,	marvin \hx1,\hy1,\hx2,\hy2,sar 5s,sar 5s
	marvin \hx2,\hy2,\hx3,\hy3,sar 8s,sar 4s
	dismin \hx2,\hy2,hp2 i,hp3 i
	marvin \hx3,\hy3,\hx1,\hy1,sar 1s,sar 6s
	dismin \hx3,\hy3,hp2 i,hp3 i

First of all, marvin is a macro, and by its tongue-in-cheek title it seems to suggest the following entry to the Oxford Dictionary:

marvin | ’märvin |

verb   (marvins, marvined)
To draw an arc or ellipse like Marvin Minsky: The program marvins a circle onto the screen.

ORIGIN 1960s, found in a program listing by J. M. Graetz; after Marvin Minsky, then head of the Artificial Intelligence Laboratory at MIT and author of the "Tri-Pos: Three-Position Display" for the TX-0 computer.

A naive guess made on the impression of the images above might suggest that an instance of marvin would be advancing over a circle in incremental steps, like in:

function circle_step() {
    x = x - (y >> 4);
    y = y + (x >> 4);
    display(x, y);
}

(This algorithm had been discovered by Marvin Minsky by accident: "The circle algorithm was invented by mistake when I tried to save one register in a display hack! Ben Gurley had an amazing display hack using only about six or seven instructions, and it was a great wonder. But it was basically line-oriented. It occurred to me that it would be exciting to have curves, and I was trying to get a curve display hack with minimal instructions." Marvin Minsky in HAKMEM "Item 149 (Minsky): Circle Algorithm" — As a historical footnote we may add that the same algorithm was used for determining the sine and cosine by the Sinclair Scientific pocket calculator in 1974, compare: "Reversing Sinclair's amazing 1974 calculator hackl" by Ken Shirriff.)

But the "Minskytron", Marvin Minsky's "Tri-Pos: Three-Position Display", is quite a different beast, as it is not only using parameters on each of these values, it interconnects them to form a visual synthesizer by having three of these generators driving and modulating each other, quite like the frequency modulators of a Moog synthesizer.

The 'Miskytron' port to the PDP-1, at the Computer History Museum

Marvin Minsky's "Three Position Display" (Minskytron) on the PDP-1 at the CHM.
Photo by Joi Ito, 2007 (CreativeCommons)

BTW: You may explore the Minskytron here (online emulation).

This is, what the macro marvin actually looks like:

define marvin X1,Y1,X2,Y2,S,SS
	lac X1
	add X2
	S			// apply a right-shift scaling
	add Y1 
	dac Y1			// Y1 = Y1 + S(X1 + X2)
	sub Y2
	SS			// apply a right-shift scaling
	cma
	add X1
	dac X1			// X1 = X1 - SS(Y1 - Y2)
termi

Or translated to a C-like pseudo-code (please mind 18-bit word limits):

function marvin(*x1, *y1, x2, y2, sx, sy) {
    y1 = y1 + ((x1 + x2) >> sx);
    x1 = x1 - ((y1 - y2) >> sy);
}

Note: With a bit of abstraction we might regard this as some kind of wavelet transformation, with the expression "(x1+x2) >> sx" as a low-pass band working on the average and the expression "(y1-y2) >> sy" as some kind of high-pass band based on the difference, with weighted coefficients applied by parameters sx and sy, adding distortion in the x- and y-domains.

Since we would want to put some output on the screen too, there is another macro for this, named dismin, displaying a dot relatively to a central anchor point provided in (XC|YC) with an offset added by the vector (X|Y):

define dismin X,Y,XC,YC		// display a dot at X+XC, Y+YC
	lac Y
	add YC
	swap
	lac X
	add XC
	dpn			// dpn=dpy-i
term

Let's have another look at the instances of marvin:

/Here it is...
h3,   marvin \hx1,\hy1,\hx2,\hy2,sar 5s,sar 5s    // hy1 += (hx1+hx2)/32
                                                  // hx1 -= (hy1-hy2)/32
      marvin \hx2,\hy2,\hx3,\hy3,sar 8s,sar 4s    // hy2 += (hx2+hx3)/256
                                                  // hx2 -= (hy2-hy3)/16
      dismin \hx2,\hy2,hp2 i,hp3 i                // display @(x+hx2, y+hy2)
      marvin \hx3,\hy3,\hx1,\hy1,sar 1s,sar 6s    // hy3 += (hx3+hx1)/2
                                                  // hx3 -= (hy3-hy1)/64
      dismin \hx3,\hy3,hp2 i,hp3 i                // display @(x+hx3, y+hy3)

As we may observe, there are 3 generators, but only two of them, namely the one for (\hx2|\hy2) and the one for (\hx3|\hy3) are connected to a visual output via the macro dismin. Moreover, the generators are visited in the order (\hx1|\hy1), (\hx2|\hy2), (\hx3|\hy3) in a round-robin fashion, but are feeding each other in the opposite direction, using the state of the previous generator as a modifier. Notably, this modifier will be in the state as at the end of the previous step.

Thus we get a digital synthesizer with three interleaved generators (G1, G2, G3) and two of them connected to a visual output, this being some kind of a generator in its own by adding the input vector as an offset to a fixed positional vector:

Schematics of the  'Minskytron' hyperspace signature

Schematics of the "Minskytron" hyperspace signature.

As we can see here, the only fixed values are in the scaling vectors, the bit-shifts to be applied to the partial sums. Since they are only symmetrical for the very first generator, the one without any connection to the visual output, and because there is a notable disparity in the scaling of the other ones, we may expect some kind of distortion of the resulting circles. Also, there might be some skew or rotation involved, as the oscillation of one generator is added to the oscillation of the previous one. (Compare the screenshot above.)

This will be visited exactly four times in each frame, drawing a series of consecutive dots in two arcs loosly aligned to the vertical and horizontal axis respectively (mind the scaling factors for the second and third generators):

h3a,	count \hcl,h3		// repeat 4 times
	pack \hx1,\hy1,mh1 i	/repack coordinates
	pack \hx2,\hy2,mh2 i
	pack \hx3,\hy3,mh3 i

If the count-up at h3a yields a negative result, we continue for another round-trip over the marvinous oscillators at label h3. After the 4th iteration, we'll fall through and pack the point coordinates back into the properties mh1 to mh3, to preserve them separately for each of the ships:

define pack X,Y,S	// pack X, Y to hi, lo AC, deposit in S
	lac X
	lio Y
	sar 9s
	scl 9s
	dac S
termin

This is exactly the reverse operation of split, but with the addition of storing the packed value in the location provided in parameter S. Notably, there is a small side effect of this packing and unpacking: Since a display coordinate has a precision of 10 bits and our packed ccordinates will only preserve the highest 9 bits, only the 4 consecutive dots drawn in the same frame will line up smoothly, but we may expect some jumpy discontinuities between frames (which may be actually observed, compare the screenshot), due to the loss of the least significant bits.

Follows the count up between frames and the set up of the code for the rentry phase, in case we would be finished and fall through the count-loop:

h3b,	count hp5 i,hxt
	setx hp5 i,100 i	/delay timer	// 100 delay frames (time in hyperspace)
	init ml1 i,he2		/delay entry	// setup object routine
	setx mb1 i,12				// instruction count 12

hxt,	jmp .			/BACK TO THE FRAY...

At h3b the counter hp5 will be incremented. Since it was initilized to octal -20 and we're incrementing after the feat, we're drawing decimal 14 groups of 4 dots for each of the two arcs.

All in all, as we iterate over the individual steps of the code, we'll get the scientifically refined version of the comics-style Whizz – Swoosh effect, we were imagining so vividly in the introduction: A carefully adjusted 3D-like, slightly distorted drawing of a two-thirds Bohr atom, fading slowly away at the very screen position, where the ship had been just before.

Should we fall through, we're finished with the warp-induced photonic stress emission and are setting up for the actual hyperspace phase: "setx hp5 i,100 i" resets the counter hp5 to the value -100, now to be repurposed to count the number of frames, for which the ship will be invisible in hyperspace (dec. 77 frames). "init ml1 i,he2" links the object routine for this phase and "setx mb1 i,12" sets the instruction count to octal 12.

In either case (still stress-emitting or already up for hyperspace), we finish at label hxt and are performing the return to the main program (or, in case this was the very first frame, the jump to the return to back to main).

 
And here is what it looks like in action, the warp-induced photonic stress emission signature as left behind by a ship making the jump to hyperspace:

The Spacewar! 2b hyperspace-patch in action

The Spacewar! 2b hyperspace patch in action (screen recording of the emulation).
Click to play the animation.

The Lonesomeness of Hyperspace

/Delay and recovery time count loops

he2,	dap hxt
	count hp5 i,hxt		// count up
	lac hp6 i		// restore spaceship routine from  backup
	dap ml1 i
	setx mb1 i,2000		// reset instruction count to 2000
	reset hp4 i,jmp hyj	/recovery gate	// "lac (jmp hyj   dac hp4 i"
	setx hp5 i,500 i	// set hp5 to -500 (cooling frames)
	jmp hxt			// return

As in Spacewar! 3.1, there isn't exactly much to do in hyperspace: We store the return address and are counting up on hp5, if still negative after the increment, we're finshed and jump to the return at hxt. If we fall through, it's time for re-entry: First, we restore the standard spaceship routine from the backup in pointer hp6 (property th6). Then, we reset the instruction count to the standard value of 2000 and are up for a final piece of wizardry: "reset hp4 i,jmp hyj" sets the "recovery gate" in hp4 (property th4) to the constant "jmp hyj", an instruction that will be executed by the xct instruction at the very entrance of the normal in-flight patch. This will send the code to the location at label hyj (see below) instead of executing the usual trigger-parsing. Then, we repurpose the counter hp5 once again, this time for the hyperspace cooling of octal 500 frames (decimal 320). The jump at hxt takes us finally back to the main program.

hyj,	count hp5 i,sh1		// count up cooling
	reset hp4 i,nop		// over reset recovery gate (hp4 = nop)
	jmp sh1			// jump to return

xy1,	6
xy2,	10765
xy3,	767765

These are actually the very last instructions of the hyperspace patch: "count hp5 i,sh1" performs the count up on the hyperspace cooling, blocking any further attempt to jump to hyperspace. If the count up finished, we reset the "recovery gate" in hp4 to the void operation nop (synonymous to the initial opr), thus clearing the detour to this piece of code. "jmp sh1" is once again a jump to the return address, the last one in the entire patch.

Follow the constants for the initialization of the marvin-generators — and, with this being the end of the hyperspace patch as provided by the final keyword start after the pseudo-instructions variables and constants for reserving the locations for the various assembler entities in place, we've reached the end of the listing:

variables
constants

start

*****

Pfew! — This has become nearly a double issue, but we were also discussing two versions of hyperspace at once. But, eventually, we've arrived at the finish line, having seen all there is to see. — But, as usual, there's a little extra that should be presented in this context: This time it's a short one, the auto-restart patch, patching into the hyperspace patch and by this illustrating pretty well, how the dependencies of these code snippets would have built up.

The Auto-Restart Patch

This is the source code reconstructed from disassembly:

spacewar 2b auto-restart patch
/ requires the hyperspace 85 patch

/ locations in spacewar 2b

a=1462		/ setup for test word controls
ml1=1540	/ property linking to an object's handling routine
mtb=3103	/ start address of the objects table
ntr=3543	/ property for the number of torpsedos remaining

/ locations in hyperspace85 (Hyperspace VIci, 2 May 1962)

hml=5530
hmx=hml+14	/ (5544)

/ patches into hyperspace85

hmx/

jmp rs0			/ replaces "jmp ml1"

/ start of patch

5610/

rs0,	lac mtb		/ control word of first spaceship
	sza i		/ is it not empty?
	jmp rs1		/ empty, restart
	lac mtb 1	/ check second spaceship
	sza i
	jmp rs1
	lac ntr		/ add number of remaining torpedoes
	add ntr 1	/ sum them up
	sza i		/ any left?
	jmp rs1		/ no, restart
	scr 2s		/ operand is 440, 2 high bits
	dac rsc		/ reset rsc to torps remaining / 4
	jmp ml1		/ return to main

rs1,	isp rsc		/ count up on rsc
	jmp ml1		/ jump to ml1, if still counting
	jmp a		/ finished, start a new game

rsc,	0

	start 0

Let's have a quick look at this: The patched location in the "hyperspace85" patch is the final jump back to label ml1, to be found right after the part where the pointers are setup to point to the properties of the first spaceship. At the beginning of each frame we'll take a detour to location 5610, following immediately after the hyperspace patch in the address space of the PDP-1.

So, when may a game be over? A nice guess would be, when any of the ships would be no more, or if both of the ships would be out of torpedoes. This is also what the code at label rs0 is checking at the start of each frame.

The first instruction "lac mtb" loads the address of the object routine into the accumulator. Should this be not zero ("sza i"), indicating that the ship is still in any state of flight, hyperspace, or explosion, we skip the next jump instruction. Should the property have been reset to zero, this ship is out of order and we'll take the jump to label rs1 for a restart. The same is then repeated for spaceship 2 by inspecting the property "mtb+1".

The next check is on the torpedoes, but this time it's a check on both of the ships at once. For this, we load the number of remaining torpedoes from ntr (spaceship 1) and ntr+1 (spaceship 2) and add them. Should this sum be zero, we miss the probing skip ("sza i") and jump to the restart at rsc, because both of the ships are out of torpedoes.

If there are still both of the ships and any torpedoes left, the game is still commencing. What's left to be done? Let's have a look at actual restart procedure at label rs1 before, which happens to be an incarnation of our well known macro count, followed by the location reserved for the counter:

rs1,	count rsc, ml1		// count up restart delay
	jmp a			// finished, start a new game

rsc, 0

So, this is counting up rsc to provide a delay before the restart. Thus we may expect the contents of location rsc to be a negative number. This was assigned in the last few instructions of the previous piece of code:

	scr 2s		/ operand is 440, 2 high bits
	dac rsc		/ set up rsc to torps remaining / 4
	jmp ml1		/ return to main

The first instruction "scr 2s" is quite peculiar. First, because it's actually "scr 440", where 440 is a value with two high bits, the functional equivalent of the assembler constant "2s" (or value 3). Second, it's a combined shift on the accumulator and the IO register, but we're interested in the accumulator only, because the contents of this is what is deposited into rsc in the next instruction, just before the jump to the main program. Odd, but this is, how it is. But, what's actually in the accumulator?

As we've seen before, the contents of the accumulator is the sum of the remaining torpedoes of the two ships, a sum of two negative numbers and hence a negative value, too. It must be some negative value, because, if it would have been zero, we would have made the jump to the delayed restart at label rs1 before. Obviously, the restart delay is a function of the remaining torpedoes, to be precisice, the number of any torpedoes remaining in the game divided by 4 (since we executed a right-shift by two bits).

Since there are (decimal) 32 torpedoes for each ship, the sum will be anything from -64 to zero. Dividing this by 4 we get a restart delay of up to (decimal) 16 frames, a mere second.

But there's still something remarkable to be observed, namely the very location, where the game is restarted. Label "a" in the main program happens to be the code where the game is setup for reading the control boxes for input. In other words, if we were using the test word controls, we would be up for a surprise: The game would be out of order, because the program would be scanning the ports for the control boxes exclusively by executing the instruction "iot 11" and wouldn't listen to the test word switches anymore. Folks using the test word controls would want to change this to the instruction "jmp a1", where location a1 is the (octal) address 1457.

That's all!

*****

Yet another remark or observation, namely on the memory available at the PDP-1: In the hyperspace patch we've finally met an occasion, where some properties would have been packed and unpacked to and from a compressed store. We may note that this wasn't done, because there wouldn't have been any spare locations left in the PDP-1, but apparently because of the patch already bordering to the auto-restart patch in locations 56105630. Even then, there wouldn't have been any need to do so and this is actually a waste of memory locations, since the various intructions for packing and unpacking the properties are using more space than the 2 × 6 locations required for storing the plain values all along. Moreover, there's still a huge block of unused memory registers, namely to be used for the DDT online-debugger, which would have still fit into the PDP-1's memory in parallel to Spacewar! — even, when various patches had been applied to the program.

*****

Is this also the end of our journey through Spacewar-code? Not at all, since there are still some sights to be visited that are related to versions 4 of Spacewar!, namely the on-screen scorer patch for Spacewar! 4.8. Moreover, we'll want to engage in some sort of interpretation and higher level analysis that may be a bit more educated, since we've acquired by now some sound knowledge of the code and its workings. So there is still more to come.

Stay tuned for the next episode …

 

Norbert Landsteiner
Vienna, September/October 2014
www.masswerk.at

 

In case you would have found any errors or inconsistencies, or would have additional information,
please contact me.

*****

Previous:   Part 7: Shootout at El Cassiopeia
Next:   Part 9: Like a WW II Pilot’s Tally — The 4.8 Scorer Patch and Other Spacewar! 4 Oddities

Back to the index.