BUGS

BUGS IN THE ORIGINAL COLOSSAL CAVE ADVENTURE

Direct analysis of advent.for and advent.dat - adv350-pdp10


This page documents three bugs present in the original PDP-10 FORTRAN source of Colossal Cave Adventure - specifically the adv350-pdp10 release, the definitive 350-point version authored by Will Crowther and Don Woods in 1977. All analysis is based directly on advent.for and advent.dat from that release. No derivative port or reimplementation has been consulted.

Bug 1 was identified during the construction of this port, and first published on 28 April, 2026. The specific mechanism - an undeclared variable causing a silent miscount in a specific reachable game state - has not been identified or documented before. O'Dwyer confirmed on review that it is a genuine bug and worthy of note.

All three bugs are faithfully replicated here: correcting them would make the game behave differently from the original.


BUG 1 - AN UNDECLARED VARIABLE FOOLS THE SOFTLOCK DETECTOR

The variable SPICES is used in a critical conditional without ever being assigned a value. The result is that one specific path to softlocking the game - collapsing the troll bridge before seeing the rare spices - is silently mishandled. The softlock detector fails to account for the lost treasure, and as a consequence the cave never closes for that game instance.

MECHANISM

The game tracks unseen treasures via two counters. TALLY counts how many treasures have not yet been seen. TALLY2 counts how many can never be seen - permanently lost treasures. When TALLY equals TALLY2 and both are non-zero, the lamp limit is cut to 35 turns as a signal that the game cannot be won. When TALLY reaches zero - all treasures seen at least once - the cave closing countdown CLOCK1 begins:

        IF(TALLY.EQ.0.AND.LOC.GE.15.AND.LOC.NE.33)CLOCK1=CLOCK1-1

RARE SPICES is object 63, placed at room 127 (Chamber of Boulders) on the far side of the troll chasm. When the player crosses the bridge carrying the bear, the bridge collapses and the chasm becomes permanently impassable. The code at label 30310 is supposed to account for any treasures now permanently out of reach by incrementing TALLY2:

30310   ...
        IF(PROP(SPICES).LT.0)TALLY2=TALLY2+1

The intent is correct: if SPICES has not yet been seen (PROP still -1), it can never be seen now, so TALLY2 should go up. But SPICES itself is never assigned. In the initialisation block where every named object receives its object number via a VOCAB lookup, SPICES is absent:

        KEYS=VOCAB(0+'KEYS',1)
        LAMP=VOCAB(0+'LAMP',1)
        ...
        BEAR=VOCAB(0+'BEAR',1)
        CHAIN=VOCAB(0+'CHAIN',1)
C       SPICES never appears here

SPICES is implicitly typed as INTEGER by the IMPLICIT INTEGER(A-Z) directive at line 21 of advent.for, but it is never given a value. Its value at the point of the conditional is undefined - whatever happens to be in that memory location. On the PDP-10, that value is not -1. The condition evaluates false. TALLY2 is not incremented.

The consequence: if the player collapses the troll bridge before visiting room 127, PROP(63) stays at -1 permanently. TALLY cannot reach zero. CLOCK1 never ticks. The cave never closes for that game instance.

Don Woods was aware that the softlock detector had limitations and removed it entirely in Adventure 2.5. His comment in that version notes that there were simply too many ways to reach an unwinnable state for the checks to be reliable - killing the bird before the snake, leaving the bottle on the far side of the troll bridge, and so on. The SPICES variable being undefined is one more instance of the same class of problem.

TRIGGERING CONDITIONS

Cross the troll bridge (rooms 117 to 122), proceed directly to the bear in room 130, free it with food, and return across the bridge while toting the bear - without having first visited room 127 via rooms 124 and 125. Room 127 and room 130 are on separate branches from the fork at room 124, so this is a natural route.


BUG 2 - THE CARRY COUNTER GOES NEGATIVE

In the final cave closing sequence, the HOLDNG counter - which tracks how many objects the player is carrying - is decremented once for the liquid in a carried bottle, despite never having been incremented for it. HOLDNG goes to -1, giving the player an effective carry limit of eight items rather than seven in the endgame repository.

This bug was identified by O'Dwyer. His account is at quuxplusone.github.io.

MECHANISM

HOLDNG is managed by the CARRY and DROP subroutines. CARRY increments HOLDNG and sets PLACE to -1. DROP decrements HOLDNG when PLACE is -1, then sets PLACE to the destination. Liquids are never passed through CARRY directly - when the player picks up a bottle containing liquid, the bottle goes through CARRY (HOLDNG+1) but the liquid is assigned PLACE=-1 directly:

        CALL CARRY(OBJ,LOC)
        K=LIQ(0)
        IF(OBJ.EQ.BOTTLE.AND.K.NE.0)PLACE(K)=-1

HOLDNG is never incremented for the liquid. This is intentional - the liquid is inside the bottle. The death handler accounts for this correctly by zeroing both liquids before its drop loop:

        PLACE(WATER)=0
        PLACE(OIL)=0
        ...
        DO 98 J=1,100
        ...
        CALL DROP(I,K)
98      CONTINUE

The final closing sequence at label 11000 has no such guard. If the player enters the closing with a full bottle, PUT(BOTTLE,115,1) drops the bottle (HOLDNG-1) but leaves PLACE(WATER)=-1. The destroy loop then reaches WATER, TOTING returns true, DSTROY calls DROP(WATER,0), which decrements HOLDNG for an increment that never happened:

        PROP(BOTTLE)=PUT(BOTTLE,115,1)
        ...
        DO 11010 I=1,100
        IDONDX=I
11010   IF(TOTING(IDONDX))CALL DSTROY(IDONDX)

HOLDNG ends at -1. The carry limit check compares against -1, so the player can pick up all eight repository objects without hitting the limit.

TRIGGERING CONDITIONS

Enter the final closing sequence while carrying a bottle that contains water or oil.


BUG 3 - WALKING ON THE CEILING

In the Hall of the Mountain King (room 19), the compass directions NORTH and SOUTH are paired with the wrong relative directions. NORTH is a synonym for LEFT and SOUTH for RIGHT, when they should be the reverse. The adventurer must be walking on the ceiling.

This bug was identified by O'Dwyer in 2012 and confirmed directly by Don Woods, who recalled finding it himself at some point and noted it was corrected between his version 1 and version 2.5. O'Dwyer's account is at quuxplusone.github.io.

MECHANISM

The bug is in the travel table in advent.dat rather than in the FORTRAN source. Lines 448-450 for room 19 read:

19    311028  45  36   # destination 28, cond 311, dirs: 45=N  36=LEFT
19    311029  46  37   # destination 29, cond 311, dirs: 46=S  37=RIGHT
19    311030  44   7   # destination 30, cond 311, dirs: 44=W   7=FORWA

NORTH and LEFT lead to the same destination, as do SOUTH and RIGHT. For WEST and FORWARD to be synonyms is reasonable given that the Hall of Mists lies to the east and the player is facing west. But NORTH being synonymous with LEFT, and SOUTH with RIGHT, only makes sense if the player is oriented facing the ceiling. The correct pairing would be NORTH with RIGHT and SOUTH with LEFT.

Don Woods corrected this in Adventure 2.5:

19    311028  45  37   # 45=N 37=RIGHT  (corrected)
19    311029  46  36   # 46=S 36=LEFT   (corrected)
19    311030  44   7   # 44=W  7=FORWA

The original pairing is preserved without comment in KNUT0350, GIBI0375, LUPI0440, PLAT0550, LONG0751, and SMIT0370. It is faithfully replicated in this port, which uses the original advent.dat verbatim.

TRIGGERING CONDITIONS

Type LEFT or RIGHT in the Hall of the Mountain King. LEFT takes you north (to the west side passage) when it should go south, and RIGHT takes you south when it should go north.


NOTES

All three bugs are present in the adv350-pdp10 source and are faithfully replicated in this port. Bug 1 was identified during construction of this port. Bugs 2 and 3 are credited to O'Dwyer.

Bug 3 is a data bug rather than a logic bug - it exists in the travel table in advent.dat, not in the FORTRAN source. This distinction matters for port authors: a port that reads the original dat file verbatim replicates the bug automatically, while a port that reconstructs the travel table from scratch might accidentally correct it.