Go to the Index

Inside Spacewar!
Part 11: The Spacewar 2B Preservation Project
(Porting the oldest digital video game to "modern" hardware.)

Prev. Next

(Preliminary Remarks · About the Project · Resources / Ingredients · The Port — Inlining the Patches · Fixing the Restart Condition · Adding a Scoring and Matchplay Device · Modifying the Hyperspace Trigger · The Port, Part 2 — Automatic Multiply/Divide · The Resulting Program)

Preliminary Remarks

This is about the eraliest known version of a digital video game, Spacewar! 2B, which also happens to be the first digital video game publicly shown in a presentation and also the first one to be cited in literature, and it's about providing a carefully revised version that would actually run on the only real hardware preserved, i.e., a PDP-1 with the automatic multiply/divide option. Notably, this project is not affiliated to the Computer History Museum (which homes the only runnable DEC PDP-1) in any way and there are probably just minimal chances to see this program on the real machine. — If nothing else, this project may prove the term "modern hardware" to be somewhat relative.

About this Project

One of the major driving forces for investigating the source code of Spacewar! was, right from the beginning, my ambition to identify the very version of Spacewar! as it was presented at the MIT Science Open House in May 1962 and as described by Martin Graetz in "The Origin of Spacewar" (Creative Computing, August issue 1981; Volume 7, Number 8) and in his DECUS conference paper "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).

Spacewar 2B and the Hyperspace Minskytron signature

The Origin of Spacewar, Figure 4.
"Warp-induced photonic stress emission." The Hyperspace Minskytron signature.

And, in deed, we managed to identify the source code of Spacewar! 2B along with its essential patches, including the first hyperspace implementation featuring the "Warp-induced photonic stress emission", also known as the Minskytron Hyperspace. (Compare "Intermission: Digging up the Minskytron Hyperspace" and "Part 8: Hyperspace!".)

Sadly, the configuration is currently running in emulation only, since Spacewar 2B is coded for a PDP-1 without the automatic hardware multiply/divide option, whereas the only existing PDP-1 is a later PDP-1 with the automatic multiply/divide option installed. The problem being that the hardware multiply/divide option reuses the instruction codes of the rudimentary multiply- and divide-steps of the earlier PDP-1s, so you may not have both of them at once. It's true that there is switch hidden inside the cabinets of the PDP-1 to set up the machine to rather use the legacy instruction decoding for "mus" and "dis", but this is bit impractical and out of the world. So we may want to produce an updated version of Spacewar 2B for use with the automatic multiply/divide option, which seems quite legit, too, as it has been already attempted in genuine context before. (Compare the "Intermission: Yet Another Patch, Spacewar! 2b SA 5 and the Secrets of PDP-1 Loaders".)

Moreover, there is still a patch missing, namely a patch adding matchplay and scoring facillities. (See below.) And, last but not least, there's another technical reason for Spacewar 2B not to be seen on the Computer History Museum's restored PDP-1, the very implementation of the hyperspace trigger: Spacewar 2B's hyperspace triggers rather on the release of both clockwise and counter-clockwise turn (left + right) than on them being deployed at once, like we know it from later versions of Spacewar. The later implementation poses a problem for any installation using rather buttons for turns than a lever or joystick of sorts, since hyperspace may be deployed easily by accident while players are navigating their ships without any intention of actually escaping to hyperspace. Thus, the CHM's control boxes include some ciruitry in order to inhibit those signals, when raised by the navigational buttons and features a dedicated hyperspace button instead. But it's also this circuitry that prohibits the hyperspace trigger from working, when operated rather on the release of the combined signals, as we encounter it in Spacewar 2B. So we want to fix this, too, by inverting the functionality of the trigger to something alike the implementation of Spacewar 3.1.

Finally, Spacewar 2B originally just runs a single game and has to be restarted for any new one, something that was fixed by an auto-restart patch. While the logic of this patch is flawless, there is an edge-case, where Spacewar 2B is stuck in an infinite explosion of two colliding ships, still requiring a manual restart. Therefor, as we're going to provide an updated version, we may want to address this, as well, but in a way as non-intrusive as possible to the original code and appearance.

There are numerous reasons to attempt a preservation of Spacewar 2B along with its patches: First, it's the earliest full implementation of the first know digital video game. Then, it's also the first digital video game described in literature and we may want to preserve a working functional equivalent of the game as described. Moreover, — apart from minor cosmetical differences to later versions — it doesn't only feature Martin Graetz's Minskytron Hyperspace, but also the original implementation of Peter Samson's Expensive Planetarium, featuring a background starfield moving at variable speeds and also implementing the varying intensities of the star's magnitudes by a different technical approach (compare "Part 1: The 'Expensive Planetarium'").

This episode is about how this updated version of Spacewar 2B is implemented, thus also providing a detailed documentation of the steps and modifications involved.

Spacewar 2B on the PDP-1 (fictional montage)

Spacewar 2B as imagined on the CHM's PDP-1.
Montage, N. Landsteiner, 2016; Photo of PDP-1 by Tom Bennett (creative commons).

As for naming things, we'll christen our revised version "Spacewar! 2B-m", where "m" is for "multiply" or "modified".

The source code of the resulting program is available here, the game can be experienced along with the original here:

See Spacewar! 2B-m running in an in-browser emulation.

Resources / Ingredients

The full configuration of Spacewar 2B as shown at the MIT Science Open House in May 1962 consists of

Let's have a quick glance at the individual parts.

Spacewar 2B

Spacewar 2B survived both as an authentic listing and in binary code on paper tape. The listing is part of some collected printouts and notes that were given by Martin Graetz to Brian Silverman in the 1990s (the printout of Spacewar 3.1 was used for the seminal Spacewar emulation in Java by Barry Silverman, Brian Silverman, and Vadim Gerasimov) and later donated by Brian Silverman to the iMusée, the Musée de l'informatique du Quebec. This listing is titled "spacewar 2b 25 mar 62", a transcript of the printout is available at http://www.masswerk.at/spacewar/sources/spacewar_2b_25mar62.txt. The printout also shows some hand-written annotations documenting an earlier attempt to port the game to a PDP-1 with the auto multiply/divide option.

The binary tapes are "spacewar2B_2apr62.bin" [1] and "spaceWar_SA-5.bin" [2]. The code loaded from these paper tapes is identical, but while "spacewar2B_2apr62.bin" loads only the core program, "spaceWar_SA-5.bin" is an all-in-one tape comprises also the starfield data, along with a separate file representing an early attempt at patching the game for a PDP-1 with the automatic multiply/divide option installed.

[1] http://textfiles.com/bitsavers/bits/DEC/pdp1/papertapeImages/20050823/spacewar2B_2apr62.bin
[2] http://textfiles.com/bitsavers/bits/DEC/pdp1/papertapeImages/20031202/InsidePDP1_80s_box3/spaceWar_SA-5.bin

A disassembly of the two tapes shows identical code to the listing of "spacewar 2b 25 mar 62", with the only exception of the sense switch options checked for reverse polarity. (Where "spacewar 2b 25 mar 62" is checking for a switch set to off, the binary versions check the respective on states, just like the later versions of Spacewar.) Thus we may derive a nearly authentic listing of "spacewar 2b 2 apr 62", by adjusting the few instructions used for these checks according to the disassembly. A listing of this revised source code is available at http://www.masswerk.at/spacewar/sources/spacewar_2b_2apr62.txt. This is also the very source we will rely on for this project.

These later versions ("spacewar2B_2apr62.bin" and "spaceWar_SA-5.bin") provide sense switch settings in the following fashion, also documented by the Computer History Museum's Catalog Number 102631130:

SSW 1 UP    INERTIAL ROTATION
      DOWN  BERGENHOLM ROTATION
SSW 2 UP    LIGHT GRAVITY
      DOWN  HEAVY GRAVITY
SSW 3,4
      00  MOVING STARS, NORMAL
      01  MOVING STARS, FAST
      10  STATIONARY STARS
      11  NO STARS
SSW 5 UP    HEAVY START TRAP
      DOWN  HEAVY STAR TO ANTIPOINT
SSW 6 UP    NO HEAVY STAR
      DOWN  HEAVY STAR ON

Note: "up" = on, "down" = off

As can be seen, there are 4 settings for the Expensive Planetarium, where later versions just feature on and off. Also, there's no option for continuous fire (salvoes). We may break down the most important differences to later versions of Spacewar as follows:

In all other respects, the code is very similar to Spacewar 3.1, which may be said to represent a consolidated an augmented version of Spacewar 2B.

The Starfield Data

This is the same dat as used by all version of Spacewar (with the exception of Spacewar 4.0TS [ddp, 5/4/63], which only uses a reduced subset). Notably, it was set up by Peter Samson for this very version of Spacewar, 2B, as indicated by it's title, "stars by prs for s/w 2b". The date, "3/13/62", which is the same as the date annotated to the background display code, provides a hint on the timeframe we may attribute to the development of the main program.

Hyperspace 85 / Hyperspace VIci, 2 May 1962

The original hyperspace patch survived both in binary code [3] and as a listing included in the collected printouts donnated to the iMusée [4]. The dissassembly of the binary code on paper tape and the listing provide identical code.

[3] http://textfiles.com/bitsavers/bits/DEC/pdp1/papertapeImages/20031202/InsidePDP1_80s_box3/hyperspace85.bin
[4] A transcript of the listing is available at http://www.masswerk.at/spacewar/sources/hyperspace85.txt.

The printout reads the following title:

 Hyperspace 85
/
 /A new tape prepared by JMG, 3[corr.] 26 Oct 1985
 /from the listing of Hyperspace VIci, 2 May 1962

Here with all the hand-written corrections and annotations:

Heading comments of the original listing of the hyperspace85 patch for Spacewar! 2b.

The hyperspace patch is applied directly to Spacewar 2B by 4 installing hooks in total (at the entrance of the main loop, inside the code for setting up a game, in the iteration of objects in the main loop, and in the code for decoding any player input). We have already discussed the code in detail previously, see Part 8, Spacewar! 2b and the "Minskytron" Hyperspace.

We may assume that the repunch was prepared for use with the PDP-1 of the Computer History Museum, when still the Boston Computer Museum, as part of an attempt to provide a running version for the PDP-1 with the automatic multiply/divide option installed — as documented by the hand-written annotations found in the listing of "spacewar 2b 25 mar 62". (Both the listings of "Hyperspace 85" and "spacewar 2b 25 mar 62" are showing the same hand writing.) In essence, we're here accomplishing what was attempted previously in the Boston context, presumably by Martin Graetz.

The Auto-Restart Patch

The auto-restart patch is preserved in digital images of binary paper tapes only, namely in the tape-files "spaceWarRstrt.bin" [5] and "spacewAutoRestartPatch.bin" [6]. While the information on the two tape images differs on the binary level, both are loading identical code. The source code reconstructed from the disassembly is available at http://www.masswerk.at/spacewar/sources/spacewAutoRestartPatch.txt.

[5] http://textfiles.com/bitsavers/bits/DEC/pdp1/papertapeImages/20030408/spaceWarRstrt.bin
[6] http://textfiles.com/bitsavers/bits/DEC/pdp1/papertapeImages/20031202/InsidePDP1_80s_box3/spacewAutoRestartPatch.bin

Notably, the auto-restart patch hooks into the hyperspace patch, thus requiring the hyperspace patch already in memory. We've discussed the code in detail previously, see Part 8, The Auto-Restart Patch.

The Scorer Patch

There's no known source code or binary tape of the scorer patch available. But we know it to have existed:

J.M. Graetz, "The Origin of Spacewar" (Creative Computing, August 1981):

The game was essentially complete by the end of April, 1962. The only further immediate work was to make Spacewar! presentable for MIT's annual Science Open House in May. 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. To provide for the crowds that we (accurately) anticipated, a large screen laboratory CRT was attached to the computer to function as a slave display. Perched on top of a high cabinet, it allowed a roomful of people to watch in relative comfort.

J.M. Graetz, "Spacewar! Real-Time Capability of the PDP-1" (DECUS Proceedings 1962, p. 37-39):

All collidable objects explode on coming into critical range. The current rules require that a game is won only if the remaining ship (after the opponent has exploded) can successfully avoid being blown up by any torpedos which may be left over. A tie is declared: when both ships collide (and explode); when an apparent victor is destroyed by a loose torpedo; or when both ships run out of torpedos.

Steven Levy, Hackers (O'Reilly; Sebastopol, CA, 2010; p. 54):

Russell eventually wrote a subroutine that would keep score, displaying in octal (everyone could sight-read that base-eight number system by then) the total of games won.

So, we may conclude that this scorer patch

Thus, the scorer patch must have been functinally identical or very similar to the scoring facillity in Spacewar 3.1 and was written by the same author. Also, we already observed that Spacewar 3.1 was rather a consolidated version of Spacewar 2B and its various patches. Thus, we may attempt to provide a suitable reconstruction by referring to the source code of Spacewar 3.1.

As scoring and matchplay requires some kind of auto-restart functionality, the scorer patch either required the auto-restart patch (probably by installing a hook at its exit) or provided some restart code of its own. Since the restart conditions are a bit different for Spacewar 2B and Spacewar 3.1 (version 3.1 implements the object handler with an is-collidible flag stored in the sign-bit, while Spacewar 2B does not), we will assume the former, keeping as much of the original code as possible.

Modifications

Apart from the port to the automatic multiply/divide, there's something that we may want to fix for the purpose of a version of Spacewar 2B that is suitable for display. Namely, it's an edge case not caught by the auto-restart patch: As mentioned above, there is no is-collidible flag encoded in the object properties of Spacewar 2B. Therefor, any objects already exploding in a collision keep colliding (thus resetting the explosion each frame) as long as they are still in the same epsilon environment. Since the restart patch is checking for the object handlers being set to zero, as done at the end of any explosion, this results in infinitely exploding objects, when both objects of a collision are of zero or very minimal velocity. (This may occur as simply as by the two spaceships colliding at the antipode, when warped through the gravitational star with minimal delay.)

Finally, we want to fix the problem with the hyperspace trigger and the control boxes found at the CHM. This may be obtained simply by reversing the logic of the trigger to something alike the approach we see in Spacewar 3.1, thus still closely related to the original code.

The Port — Inlining the Patches

Since all the patches include hard-coded addresses, we may want to start with inlining the patches. We will do this by inlining most of the code that is directly hooking into the main program and putting the core logic of the patch at the end, thus referring to the inclusion of the Spacewar 4.8 scorer patch into Spacewar 4.1/4.2 by Peter Samson as best practice.

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.

Inlining the Hyperspace Patch

The hyperspace patch uses the same symbols as Spacewar 2B, but all of them are assigned directly by the patch, while some of them are rather defined as assembler varibles in the main program. Since the PDP-1 Macro assembler determines the type of a symbol on its first occurence, the difference in notation (an assmbler variable is denoted by an upper stroke in one of the characters, or in the modern C-implementation by a leading back-slash) doesn't matter and we may keep the original code essentially unchanged.

The first part to be inlined (labeled "hml") hooks directly into the end of the setup of the object properties, patching the jump to the initialization of the spaceships (executed at the start of a game) at label "ml1":

/main control routine for spaceships

nob=30					/total number of colliding objects

ml0,	load \mtc, -4000		/delay for loop
	init ml1, mtb			/loc of calc routines
	add (nob
	dap mx1				/ x
nx1=mtb nob
	add (nob
	dap my1				/ y
ny1=nx1 nob

	(...)

nh2=nh1 nob
	add (nob
	dac \mh3
nh3=nh2 nob
nnn=nh3 nob
// patched by hyperspace patch
//	jmp ml1

// inlined hyperspace patch (hml), Hypertable initialization, part 2
	init hp1,th1
	init hp2,th2
	init hp3,th3
	init hp4,th4
	init hp5,th5
	init hp6,th6
	jmp ml1
// end of inlined patch

(As usual, any comments starting with double slashes are by me, N.L., and not part of the original source code.)

Note: The symbols "th1" .. "th6" for additional object properties related to the hyperspace patch are defined by a "dimension" pseudo-instruction of the Macro assembler located at the beginning of the hyperspace patch. As the modern C-version (macro1.c) doesn't implement this statement, we're providing a compatible definition as in:

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

// macro1.c doesn't implement the pseudo-instruction "dimension",
// have a similar definition inlined here

th1,	0	0
th2,	0	0
th3,	0	0
th4,	0	0
th5,	0	0
th6,	0	0

The second or rather "first" part of the hyperspace initialization (labeled "a4") hooks into the end of the initial setup of the spaceship properties:

	(...)
	law 2000
	dac nb1
	dac nb1 1		// patch location for hyperspace (a3x)
// inlined hyperspace patch (a4), Hypertable initialization, part 1
	clear th1,th5 1
	law 4 i
	dac th1
	dac th1 1		/counts tries
	lac (opr
	dac th4
	dac th4 1		/recovery switch
// end of inlined patch
	jmp ml0

The next part of inlined code (labeled "hq1") concerns the iteration over these properties at the end of the comparison loop. Originally, this patches location "mq1", but we may want to append this code rather after the increment of the hyperspace-related properties already defined and handled in Spacewar 2B:

mq1,	idx mx1				/ end of comparison and display loop
	idx my1
	(...)
	idx \mh1
	idx \mh2
	idx \mh3
// additional hyperspace properties (inlined patch, hq1)
	idx hp1
	idx hp2
	idx hp3
	idx hp4
	idx hp5
	idx hp6
// end of inlined patch

The last part to be inlined is the hook for the trigger, patched into the end of the code checking for any torpedoes to be fired. Here, we just inline the hook, since the trigger code is closely related to the rest of the hyperspace code and we may want to preserve the context:

sr4,	dac .
	law i 40
	dac i ma1			/ permit torp tubes to cool
trf,	law i 300				/ life of torpedo
sr6,	dac .
	law 20
sr7,	dap .				/ length of torp calc.
// patched for hyperspace
//sr5,	move \scw, i mco	/ store as old control word
// inlined patch vector (shx), expanding macro "move" for inlining
sr5,	jmp srh
	dio i mco
// end of inlined patch
srt,	jmp .

The rest of the hyperspace patch goes at the end of the main program, just before the "constants" and "variables" pesudo-instructions:

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

dpn=dpy-i

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

// macro1.c doesn't implement the pseudo-instruction "dimension",
// have a similar definition inlined here

th1,	0	0
th2,	0	0
th3,	0	0
th4,	0	0
th5,	0	0
th6,	0	0

/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

/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


// end of Hyperspace 85 (Hyperspace VIci)

Inlining the Auto-Restart Patch

The auto-restart patch hooks directly into the part of the hyperspace patch that we appended to the initizialization of the object table. By inlining the patch we get the following:

nob=30					/total number of colliding objects

ml0,	load \mtc, -4000		/delay for loop
	init ml1, mtb			/loc of calc routines
	add (nob
	dap mx1				/ x
nx1=mtb nob
	(...)
nnn=nh3 nob
// patched by hyperspace patch
//	jmp ml1

// inlined hyperspace patch (hml), Hypertable initialization, part 2
	init hp1,th1
	init hp2,th2
	init hp3,th3
	init hp4,th4
	init hp5,th5
	init hp6,th6
// patched by auto-restart
//	jmp ml1
// end of inlined patch

// auto-restart patch (from disassembly)
rs0,	lac mtb			// control word of first spaceship
	sza i			// is it not zero?
	jmp rs1			// zero, restart
	lac mtb 1		// check second spaceship
	sza i
	jmp rs1
	lac ntr			// get sum of remaining torpedoes
	add ntr 1
	sza i			// any left?
	jmp rs1			// no, restart
	scr 2s			// (scr 440) operand 440 has 2 high bits
	dac \rsc		// reset rsc to torps remaining / 4
	jmp ml1			// return to main
rs1,	count \rsc, ml1		/ count up on rsc
	jmp a3			// finished, start a new game
// end of auto-restart patch

Fixing the Restart Condition

As pointed out previously, the check for the spaceships' handler addresses being reset to zero at the end of any explosions misses an edge-case, where two motionless objects (the two spaceships) keep colliding infinitely.

Now, we could either install an is-collidible falg (as it was done in Spacewar 3.1) to resolve this issue or attempt even to modify the entire collision detection. But any of these approaches do not recommend themselves for the preservation of the original program as they would also come with a few side effects on the overal behavior of the game.

Here, we opt for the least intrusive fix by including some kind of emergency break: We keep a count of consecutive frames involving explosions of both of the two spaceships (in the newly introduced variable "\mec"), and, if this is significantly beyond of what we may expect to see in normal operations, we'll reset both the spaceships handler properties to zero, thus providing a sane condition for the restart patch to trigger. With these modification applied the restart patch thus becomes:

// auto-restart patch (from disassembly)
rs0,	lac mtb			// control word of first spaceship
	sza i			// is it not zero?
	jmp rs1			// zero, restart
	lac mtb 1		// check second spaceship
	sza i
	jmp rs1
	lac ntr			// get sum of remaining torpedoes
	add ntr 1
	sza i			// any left?
	jmp rs1			// no, restart
	scr 2s			// (scr 440) operand 440 has 2 high bits
	dac \rsc		// reset rsc to torps remaining / 4
//	jmp ml1			// return to main

// patch to limit number of consec. explosion frames (nl, 3/3/2016)
	law mex			// load address of label mex
	sas mtb			// is control word of first spaceship the same?
	jmp rs3			// no
	sas mtb 1		// is control word of second spaceship the same?
	jmp rs3			// no
	count \mec, ml1		// count up explosion frames
rs2,	dzm mtb			// force spaceships to zero state
	dzm mtb 1
rs3,	law i 70		// reset max frames of exploding ships
	dac \mec
	jmp ml1
// end of infinite explosions fix

rs1,	count \rsc, ml1		/ count up on rsc
	jmp a3			// finished, start a new game
// end of auto-restart patch

Adding a Scoring and Matchplay Device

As already pointed out, we may refer to the code of Spacewar 3.1 in order to reconstruct the lost scorer patch for Spacewar 2B. This will hook into the very end of the auto-restart patch, where we find the jump to a new game, and will go with just a few minor changes to the code of Spacewar 3.1:

	(...)
rs1,	count \rsc, ml1		/ count up on rsc
// disabled for scoring
//	jmp a3			// finished, start a new game
// end of auto-restart patch

// scoring (like spacewar 3.1)
	stf 1				// flag 1 - spaceship 1 survived
	stf 2				// flag 2 - spaceship 2 survived
	law mtb				// spaceship 1 alive?
	sza i
	clf 1
	sza
	idx \1sc
	law mtb 1			// spaceship 2 alive?
	sza i
	clf 2
	sza
	idx \2sc

	lac \gct			// check match count, is it the last game?
	sma
	jmp sa5
	count \gct, sa5
	lac \1sc			// is it a draw?
	sas \2sc
	jmp sa4
	law i 1				// do another game (tie-break)
	dac \gct
sa5,	lat				// is tw and 40 set? skip display else
	and (40
	sza i
	jmp a3				// new game
sa4,	lac \1sc			// display scores and halt
	lio \2sc
	hlt
	lat				// clear scores on tw and 40 zero
	and (40
	sza
	jmp a3
	dzm \1sc
	dzm \2sc
sa6,	lat				// read match setup (number of games)
	rar 6s
	and (37
	sza
	cma
	dac \gct
	jmp a3
// end of scoring


a1,	law mg2				/ test word control
	dac \cwg
//	jmp a3
	jmp sa6				// patched (nl, 3/3/2016)

a,	law mg1				/ iot 11 console control
	dac \cwg
	jmp sa6				// patched (nl, 3/3/2016)

a3,	clear mtb, nnn-1		/ clear out all tables
	law ss1				/ set up spaceships
	dac mtb
	law ss2
	dac mtb 1
	(...)

The only difference here to Spacewar 3.1 is in referring rather to "mtb" and "mtb 1" than to "ss1" and "ss2" in order to keep the context of the auto-restart patch. Also, just like the auto-restart patch, we're just checking them for being set to zero (compare the code iof the restart patch). Moreover, we strip an erroneous clearing of flag 2 and are prefixing all the labels by "sa" in order to prohibit any collisions with existing labels.

Modifying the Hyperspace Trigger

The part of the hyperspace trigger responsible for registering the release of the clockwise and counter-clockwise turn signals is found near label "hp4" (or synonymously label "srh"). In order to provide compatibility to later versions and the CHM installation, we substitute it by code found in Spacewar 3.1:

srh,
hp4,	xct .			/th4 table (opr to start)
	lac scw
	cma
// adaption for CHM-style control boxes
// rather trigger on deploy than release, like spacewar 3.1 (nl, 3/3/2016)
//	and mco i
//	sma
//	jmp sh1
//	ral 1
//	spa
	ior i mco
	and (600000
	sza i
// end of trigger code adaption
	jsp hyp			/go for it

The Port, Part 2 — Automatic Multiply/Divide

Now, that we've a fully functional version of Spacewar 2B that can be proven to behave identical to the program loaded from the original tapes (apart from the recoded hyperspace trigger and the modifications applied to auto-restart patch), we may address an upgrade to the automatic hardware multiply/divide option.

As we'll see, this turns out to be much simpler than what we may expect based on previous investigations of earlier attempts to accomplish this.

Multiplication

The automatic multiplication provided by the instruction "mul" is functionally identical to the software solution provided by the BBN subroutine "mpy". (Compare Part 6, Excursus – De Computatione Mechanica.) This is also illustrated by Spacewar 4.0, which provides a modified version of the sine-cosine routine by just substituting the instruction "mul" for any inclusion of the macro "mult", as in:

/sine-cosine subroutine; Adams associates
/calling sequence= number in AC, jda sin or jda cos.
/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

define	mult Z
	jda mpy
	lac Z
	term

	...
	mult (242763
	...
	mult sin
	...
	mult (756103
	...
	mult cos
	...
	mult cos
	...
	mult sin
	...

We may do similar for our purpose here:

/sine-cosine subroutine. Adams associates
/calling sequence= number in AC, jda sin or jda cos.
/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, nl 3/3/2016
//same as spacewar 4.0, 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

The subroutine "mpy" is also called once inside the gravity calculations and we'll substitue this again by a simple "mul" instruction. (For the updated gravity in context see below.)

While the subroutine "mpy" and the instruction "mul" both perform a multiplication on two fractional values (1 > n >= 0), the BBN-subroutine also provides another entrance for an integer multiplication returning 17 bits only:

/BBN multiply subroutine
/Call. lac one factor, jda mpy or imp, lac other factor.

imp,	0				/returns low 17 bits and sign in ac
	dap im1
im1,	xct
	jda mpy
	lac imp
	idx im1
	rir 1s
	rcr 9s
	rcr 9s
	jmp i im1

mp2,	0

mpy,	0				/returns 34 bits and 2 signs
	dap mp1
	lac mpy
	(...)
mp3,	idx mp1
	lac mp2
	jmp i mp1

With "mpy" being a functional equivilent of instruction "mul", this reduces to:

//Implements the BBN multiply subroutine, low 17 bits version
//Call. lac one factor, imp, lac other factor.
//adapted for auto-multiply, nl 3/3/2016

imp,	0				/returns low 17 bits and sign in ac
	dap im1
im1,	xct
	mul imp
	idx im1
	rir 1s
	rcr 9s
	rcr 9s
	jmp i im1

As "imp" is only called twice (and also, because the integer division is a bit more complex to be called as a subroutine), we rather opt for a macro providing the same code just in place:

	define
imul A
	mul A
	rir 1s
	rcr 9s
	rcr 9s
	term

Divison

Again, instruction "div" provides the equivalent of what is accomplished by the BBN-subroutine "dvd". Unlike with multiplication, there is an overflow condition for a division, handled by an preemptive abortion of the algorithm. Here, there is a subtle difference, as the hardware solution restores the original values provided in AC and IO registers, while the BBN-subroutine leaves them just as is. For our purpose we may ignore this difference, as Spacewar never meets this error condition and also doesn't provide any code for handling it. Moreover, the high-precision fractional method "dvd" isn't called once in the entire code, just the integer version "idv":

/BBN Divide subroutine

/calling sequence. lac hi-dividend, lio lo-dividend, jda dvd, lac divisor
/returns quot in ac, rem in io.

idv,	0				/integer divide, dividend in ac.
	dap dv1
	lac idv
	scr 9s
	scr 8s
	dac dvd
	jmp dv1

dvd,	0				// essentially eqivalent to instr. "div"
	dap dv1
dv1,	xct
	(...)
	jmp i dv1

Again, we substitute the integer subroutine by a macro definition (here along with the definition for the integer multiplication, we've already seen above):

mul=mus
div=dis

// additional macros for integer multiply and divide as
// substitutes for BBN subroutines ipl and idv. (nl, 3/3/2016)

	define
imul A
	mul A
	rir 1s
	rcr 9s
	rcr 9s
	term

	define
idiv A
	scr 9s
	scr 8s
	div A
	term

Substituting the Subroutine Calls

Now we may finally put our newly defined macros to use by modifying the gravity calculations accordingly:

	lac i mx1
	sar 9s
	sar 2s
	dac \t1
//adapted for auto-multiply, nl 3/3/2016
//	jda imp
//	lac \t1
	imul \t1
	dac \t2
	lac i my1
	sar 9s
	sar 2s
	dac \t1
//adapted for auto-multiply, nl 3/3/2016
//	jda imp
//	lac \t1
	imul \t1
	add \t2
	sub (1
	sma i sza-skp
	jmp pof
	add (1
	dac \t1
	jda sqt
	sar 9s
//adapted for auto-multiply, nl 3/3/2016
//	jda mpy
//	lac \t1
	mul \t1
	scr 2s
	szs i 20				/ switch 2 for light star
	scr 2s
	sza
	jmp bsg
	dio \t1
	lac i mx1
	cma
//adapted for auto-divide, nl 3/3/2016
//	jda idv
//	lac \t1
	idiv \t1
	opr
	dac \bx
	lac i my1
	cma
//adapted for auto-divide, nl 3/3/2016
//	jda idv
//	lac \t1
	idiv \t1
	opr
	dac \by

Notably, we accounted for all calls to the BBN-subroutines and may strip them from the code, just like it was done in Spacewar 4.0.

Adjusting the Timing

The whole point of hardware multiplication and division is a significant gain in speed — but for our very purpose this comes quite unwhished for, since we want to preserve the original experience. There are various ways to cope with that: Spacewar has a count-up loop to spend any excess runtime, and we could just increase this cycle count as it has been done in Spacewar 4. But the only part affected by the upgrade is the spaceship routine, so this will result in side effects on frames where just a single spaceship is drawn (like the hyperspace animation) or none at all (like with explosions of colliding ships). Or we may want to increase the runtime count of the spaceship routine that is subtracted each frame from the total count for the purpose of this count-up. But this cycle count is also used to determine the size and timing of explosions and we would have to change other parts of the code as well.

Putting all this in consideration, it may be the best and least intrusive way to adjust for the runtime differences by just including another count-up loop, just in place where the differences do occur, namely at the end of the gravity calculations. For this, we simply repurpose the variable "\t1", which isn't used anymore in the current run of the spaceship routine:

	(...)
	idiv \t1
	opr
	dac \by
//compensate for faster auto-multiply/divide, nl 3/3/2016
	law i 400
	dac \t1
	count \t1, .
//end of compensation loop

By this we've successfully finished our port of Spacewar 2B.

(Note: A similar approach as discussed here — subsituting macros for the subroutine calls and adding a loop to adjust timing — could be used to port Spacewar! 3.1 for operations with the automatic multiply/divide option.)

A Last Fix

There's just another small adjustment to the code left to be mentioned, regarding the C-implementation of the Macro-assembler. While macro1.c is generally a great program, there are still subtle differences to the original implementation — and one of them is responsible for our final fix: Spacewar 2B implements the macro for the random number generator a bit differently than later versions of Spacewar by taking the address of the memory location of the random number as an argument. Apparently, this parameter must be used in the main code at least once before it is provided as an argument to the macro in order to work with macro1.c (else the assembler throws an error). To cope with that, we have to include a dummy statement, and, again, we do this in the least obstrusive way, right at the beginning of the code:

// dummy statement for macro1.c to make \ran known
3/
	lac \ran		// overwritten by next statement


// start of spacewar 2b  2 apr 62

3/
	jmp sbf			/ ignore seq. break
	(...)

The Resulting Program

The source code and a digital image of the resulting object code are available at the following locations:

Compare the original game and the revised version side-by-side (PDP-1 emulation in HTML5/JavaScript):

Run Spacewar! 2B-m in the browser.

 

— finis —

 

Norbert Landsteiner
Vienna, March 2016
www.masswerk.at

 

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

*****

Previous:   Part 10: Spacewar 4.4 — A Twofold Stand; World's First Attempt at a (Planar) Multiplayer FPS Game
Next time: TLTR Edition (A factual description of Spacewar!)

Back to the index.