The pymdl book
38. Era artifacts
Everything in this book that is the era's rather than pymdl's came off a tape. This chapter is about the tapes: what the archive is, what pymdl took from it and where that lives, the formats those files are in and how they are read, and the discipline that keeps a copy honest about where it came from and whether it still matches.
38.1 The archive
MDL files, 1971-1987 is a collection published by MIT's Department of
Distinctive Collections from the Tapes of Tech Square (ToTS, MC-0741): the
MDL-specific files from 64 tape images, extracted with itstar (for ITS
DUMP tapes) or the TOPS-20 equivalent, laid out as
mdl/<tape>/<directory>/<name>.<version>, with the tape's own file
timestamps preserved in tree.txt and a catalogue in mdl-files.csv. It
holds the interpreter's sources (mudsys), three generations of compiler
(compil, ncompi, xcompi), the assembler and the small programs
(mprog), the debugging tools (mudbug), the library (libmud), the pure
code databases (mudsav), saved images, and forty-one of the compiler's own
.record files. Its preferred citation is in Appendix G; its licence is
MIT's.
The tapes this book cites most, by their archive numbers:
| tape | reel date | what pymdl takes from it |
|---|---|---|
7005458 |
1981-08-03 | TS MUDDLE release 55; the xcompi compiler sources pymdl's compil/ descends from; the debugging tools; MUDSAV;SQUOZE TAB55, the release symbol table |
9006255 |
1983-08-04 | the ncompi compiler binaries (38 FBINs), SAV FILE and FIXUP FILE, the library, MPROG sources including the SQUOZE package and TIMFCN; the ITS root the era sessions run on |
9006231 |
1983-05-17 | the assembler source (mprog/assem.undr88) |
9006260 |
1983-03-02 | CLR; helper binaries (atosq.nbin, mudhak) and the BTB; opcode tables |
7005401 |
1983-08-02 | an earlier compil tree: atosq.ucr005, the era's own assembly source; ops.vec |
2100099 |
1981-09-03 | the April 1981 compiler sources, the coherent late set closest to the ncompi binaries |
100332 |
1982-12-29 | connect-dir.mud; a 106-series compiler save |
9005183, 9000679 |
1980, 1982 | interpreter and compiler helpers the tests load |
A second archive, ~/muddle, is the TOPS-20 side: <mdl.comp> with the
compiler's load file pcomp.load.13, and the 1977 manuals. Both are read
and never written: the rule for every file in them is that pymdl copies, it
does not edit, and where a copy had to change the change is recorded (38.4).
38.2 What pymdl carries, and where
MDL source, in the package. src/pymdl/mdl/ holds 116 files of the
era's MDL -- the compiler (60), the debugging tools (37), the library system
(7), the small programs (7), the two symbol tables and three loose programs
-- each a copy of an archived original except where MANIFEST.tsv says
otherwise. This is what pymdl runs interpreted: chapters 29, 30, 34 and 35.
The library directory mdllib/ beside them is a build output, the
installed copies tools/build_mdllib.py makes by running the era's own
librarian over those sources (chapter 30), and nothing in it is tracked.
Era binaries, for the tests. tests/data/era/ mirrors the archive's
layout: 109 files, 2.7 megabytes, every one either a copy -- an FBIN,
NBIN, SAVE or symbol table, byte for byte, which nothing can regenerate
because MIT's compiler emitted it -- or an extract: the pure-code database
MUDSAV;SAV FILE is 11 megabytes and its FIXUP FILE three, and the
compiler's FBINs reference forty-four blocks between them, so the vendored
database holds just those blocks. tools/vendor_era_data.py --build
refreshes what is vendored from the archive and writes the manifest;
--verify asks the archive whether every copy still matches, and skips when
there is no archive. Vendoring is what let the suite stop skipping on a
machine without the tapes -- before it, the suite reported green while
testing a fraction of what it claimed.
Oracle corpora. tests/data/oracle/ holds 710 self-contained MDL
expressions written to be asked of MDL 55 and of pymdl, and a golden file of
pymdl's own answers. That golden file compares pymdl against itself: it
cannot say pymdl is right, only that it has not changed silently across 710
forms; the era side is re-derived by the oracle test of chapter 37 when it
runs.
One saved world. tests/data/madadv-1977.save is MADMAN;MADADV SAVE
from the last MIT-DDC dump, December 1977: a saved interpreter with a game
in it, read as an object world by mdlsave.py (38.3).
38.3 The formats
36-bit words in 8-bit bytes. Every binary came through Bawden's
evacuated format (36.6): five 7-bit bytes for a text word, a five-byte escape
otherwise. its36.py decodes and encodes it and is the reader under every
other format here.
Object files. NBIN, FBIN and GBIN are chapter 33's: MDL text with
a binary portion for the RSUBRs (33.11, 33.12).
The pure-code database. SAV FILE holds the purified code blocks a
whole GROUP-GLUEd package shares, FIXUP FILE their chapter 33 fixup
records, and an FBIN keeps no code of its own: its RSUBR's first slot
is a read-time %<PCODE "name" offset> into a block (33.13). The record
used to say those binaries were unreadable, that "the machine words live in
the pure database and do not resolve in a bare image"; tools/era_tape_code.py
is the proof that they resolve, recovering MIT's own compiled words per
function straight off the tape.
Found along the way. A pure block is the output of the compiler generation before the one it contains --
ncompi's binaries were compiled by the compiler beforencompi-- so a byte difference between our compiler's words and the tape's can be version drift rather than a defect, which is why the oracle of 34.8 compiles our source on MDL 55 instead of diffing against the tape.tools/era_tape_diff.pydoes diff against the tape, and has to account for four kinds of difference the same source compiled the same way must show: anM-relative offset (package base there, function base here), an index into a shared reference vector, a call to a sibling that is a directPUSHJwhen glued and anMCALLwhen standalone, and pooled constants. Only a word outside those four is unexplained, and an unexplained word is either a real divergence or a source-revision difference, which is the whole question.
Saved images. SAVE (chapter 14) wrote the interpreter's core, and
mdlsave.py reads one back, format from mudsys/save.mcr159: word 0 the
version string (54 , 55 ), word 3 the top of garbage-collected space,
words 5 onward memory at their own addresses, then the purified pages. The
interpreter's high segment -- its code, and every built-in atom that lived
there -- is not in the file; it was shared from the TS MUDDLE executable,
so pointers above the pure region resolve to nothing. What is there is
walked with the in-core layouts of muddle.mcr324 and atomhk: a value is
two words, [type,,flags][value]; the type vector is pairs indexed by type
code (the census of 35's 7.1.7); an atom is a value cell, a link word and an
ASCIZ name.
Measured (
tests/test_mdlsave.py). The December 1977MADADVsave is release 54, its purified region starts at502000, it holds sixteen oblists (the package system's) and more than 1,800 atoms, among themLIVING-ROOM,DIAGNOSE,CAROUSEL-OUT. The four surviving saved games order themselves by atom count: about 960, then 1,220 (July 1977), then this one at 1,608 -- theROOM-type rework, the volcano, the balloon.
GC-DUMP files. GC-DUMP and GC-READ (chapter 14, the manual's 11.5)
promise to reproduce an object "exactly, including sharing". The PDP-10's
format was a relocated copy of the garbage-collector graph; pymdl's is its
own byte stream, one entry per backing store, so REST-sharing, shared
tails and cycles round-trip, and it survives a PRINTB channel. The
restrictions are the manual's and were measured: MDL 55 refuses a FRAME, a
LOCD and a TUPLE with STRUCTURE-CONTAINS-UNDUMPABLE-TYPE, naming the
type. pymdl does not read the era's GC-DUMP files; none survive that
anything here needs.
Symbol tables. MUDSAV;SQUOZE TAB55, the release's export of its DDT
symbols, is carried as text in octal (mdl/mudsav/squoze.tab55.txt),
verified word-identical to the tape's binary, and ddt.syms55 is the fuller
table -- globals and local labels -- extracted from MIT's own dumped image.
The assembler, the emulator and the debugger all answer from them (35, 36).
The BTB; opcode tables ops.vec and ops.ovec are transcribed the same
way.
Records. The compiler's .record files (34, 5.1.1) survive on the
tapes, forty-one of them, and are what a compilation's messages looked like;
pymdl's FILE-COMPILE writes the same kind of file.
38.4 Provenance
A copy can drift, be quietly edited, or be a stand-in that only looks like
the original, so provenance here is derived, never asserted.
tools/mdl_manifest.py matches every file under src/pymdl/mdl/ by content
against both archives, whitespace normalised and evacuated files decoded
first, and writes MANIFEST.tsv: the archived original, its era date, a hash
and a note. A file that matches nothing is pymdl's own -- a stand-in, an
extract or a conversion -- and keeps the note it had. --check fails when
the manifest is stale; it is run when material is vendored, not on every test
run, because it needs the archives. tests/data/era/MANIFEST.tsv does the
same for the binaries, with --verify against the tape.
Found along the way. Two things a search by filename cannot catch, and a content match did. An ITS first name is six characters, so
bitsgenisbitsge.ucr002on the tape andcompdeciscompde.ucr030, and both were once recorded as having no ITS source at all. And a file can live inside another:POPWR2'sDEFINEwas found insidecargen.mud, and the era'sSQUOZEpackage vendored today matches 99.4% of9006231/mprog/squoze.undr04-- the remainder being five control characters and the lettersCOUafter<ENDPACKAGE>, a tape artifact the era's own librarian refuses (it interns an atom onINITIAL), so the copy stops at the end of the package and the manifest says so. The match has a floor: a 27-byte stand-in once "matched" inside a binary, a false provenance, so a substring shorter than 200 characters means nothing.
Provenance is also sticky: once a file has been matched to one original, a later run does not silently move it to another tape that happens to hold the same text, because the archive holds many identical copies and which one is named should not depend on directory order.
For the compiler the record goes further, because its source was changed
(34.10). compil/CHANGES.md carries every diff against the archived
original and the measurement that justified it, and four instruments watch
the source's standing against the archive: era_source_audit.py checks our
functions' names and #DECLs against the ~780 signatures carried by MIT's
own FBINs; era_source_outliers.py reports the functions where every
archived revision agrees and ours alone differs, which is the shape the
CSTORED-SLOT defect had; era_source_ab.py swaps one module's source for
another archived revision and re-measures, the one arm of a tape difference
that can be manipulated; and era_tape_code.py with era_tape_diff.py
compares words. Their standing finding is worth stating plainly: pymdl's
compil/ descends from the xcompi tree, which is the worst match to
MIT's shipped binaries among the revisions the archive holds, and the
restorations in CHANGES.md are what closed the gap.
Found along the way. Copies rot on the tape too. Nine of the ten surviving copies of
prcod.nbindescend from one damaged master with the low bit of every word zeroed:PUSH TP,1(AB),ASH A,1andMOVSI A,1collapse to their zero-address forms, the type-vector name in the fixup record stops resolving, and a fixup location slides off itsJRSTonto the instruction before it. The7005401copy is the intact original -- its words match the assembler sourceprimhk.1and its fixup locations land on the reference sites -- so that is the one staged and vendored, and the nine "byte-identical" copies agree only with each other. And100332/compil/atosq.nbin.2is an older binary format the loader does not read, which is why theATOSQreconstruction of chapter 35 was checked against the four newer binaries and not five.
38.5 What is not here, and why
- The interpreter.
mudsys, MIT's MIDAS source for MDL itself, is not vendored and never runs here: pymdl is the interpreter, and the sources are read as evidence (eval.mcr111,readch.mcr187,save.mcr159are cited by line throughout the code) rather than executed. - The games. Zork, in all its dated trees, is a program pymdl runs and
lives in its own repository; the game corpus that once sat under
src/was removed on 2026-08-28 and a synthetic program stands in for it in the tests. - The simulator. apsim, the KL10 and KA10 builds, and the ITS build that hosts MDL 55 are separate projects (chapter 37); pymdl points at them and skips without them.
- Most of the tapes. Of the archive's thousands of files pymdl carries
116 sources and 109 binaries. Everything else is reached, when a
measurement needs it, through
PYMDL_ERA_CORPUS, read-only.