Inside Spacewar!
Part 11: The Spacewar 2B Preservation Project
(Porting the oldest digital video game to "modern" hardware.)
(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
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.
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
- Spacewar 2B
- the starfield data ("stars by prs for s/w 2b / stars 1 - 3/13/62, prs.")
- the hyperspace patch ("Hyperspace 85 by JMG" / "Hyperspace VIci, 2 May 1962") — patches Spacewar 2B
- the auto-restart patch — patches the hyperspace patch
- the scoring/matchplay patch (lost/unknown)
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 https://www.masswerk.at/
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 https://www.masswerk.at/
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:
- Single game operation (restart required for a new game).
- No table of "interesting and often changed constants", parameters are rather inlined and values for torpedo speed, life, and reload time differ, contibuting to the single major difference in gameplay.
- Exhaust flames of spaceships are drawn as a short, continuous line (half the size of later versions).
- Single shots only, no salvoes.
- The Expensive Planetarium modulates brightnesses by refresh rates only (as opposed to using the intensity of the "
dpy
" instruction) and features variable speeds. Moreover, the start position is right at the margin of the starfield rather than in the lateral center (as observed in other versions of Spacewar). - Explosions are drawn in a well perceptible box (the so-called "crock explosions", compare "The Origin of Spacewar" and source code comments). Moreover, there is no flag indicating a non-collidible state.
- Thus, an alledgly winning ship may still collide with the debris of an exploding opponent, resulting in a tie.
- The object table reserves the same number of properties for all collidible objects, while later versions reserve some for the two spaceships only (thus, put in modern terms, subclassing the spaceships).
- There is a property for the spaceships' fuel supply (same as used in later versions), but the supply is never allocated, nor consumed. Thus, the ships are of infinite fuel supply.
- Finally, a subtle difference in the implementation of the random number generator.
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 data 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 https://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:
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 https://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/
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
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
- displayed scores of the individual spaceships on the console lights, just as Spacewar 3.1
- included a matchplay facility, just like Spacewar 3.1
- resolved ties, just as Spacewar 3.1
- was written by Steve Russell, just as the code we find in Spacewar 3.1.
Thus, the scorer patch must have been functionally identical or very similar to the scoring facility 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 flag (as it was done in Spacewar 3.1) to resolve this issue or we may even attempt to modify the entire collision detection. But none of these approaches do 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 (the major difference being that exploded ships will hold zero in their respective table entry):
(...) 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 lac mtb // spaceship 1 alive? sza i clf 1 sza idx \1sc lac 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/63define mult Zjda mpylac Zterm... mult(242763 ... multsin ... mult(756103 ... multcos ... multcos ... multsin ...
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 // change dac sin mul sin // change dac cos mul (756103 // change add (121312 mul cos // change add (532511 mul cos // change add (144417 mul sin // change 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:
- Source code: https://wwww.masswerk.at/spacewar/sources/spacewar_2b_m_2016.txt
- Binary object tape: https://wwww.masswerk.at/spacewar/sources/spacewar_2b_m_2016.rim
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 —
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.