Spectrum pages
Home -> Spectrum -> Mindshadow

_____________________________________________________________________

Mindshadow (Interplay / Activision)

Mindshadow is a text adventure game, ported to the Spectrum among many other platforms. I'm not sure what was the original (possibly the Apple II?) but given the various gameplay videos on YouTube, the Spectrum version doesn't look like the best of the ports.

The Apple II version, on startup, says 'ADVENT VERSION 1.0', suggesting that on that platform, there may be a clear distinction between the interpreter ('ADVENT') and the adventure database. This isn't the case in the Spectrum port; several of the special cases are implemented in Z80 code.

Here's some detail on the methods the game uses to store its data:

Words

At 8CCAh (part 1) / 96B2h (part 2) is a sorted table of words. The table is formed:

	DB	first_char
	DB	characters	;for word 0
	DB	first_char
	DB	characters	;for word 1
	...			;etc.

first_char is 00h-1Eh. 1Fh marks the end of the table.

At startup, the program scans the entire list of words, and builds a lookup table at 5B00h with 4 bytes per entry:

	DW	word_number	;0FFFFh for an unused entry
	DW	address

When printing a word, the game engine searches this table backward from the end until it finds an entry with word_number less than or equal to the required ID. Starting at that point, it then expands each word in turn until the required one is reached.

For example, the game engine wants to print word 72. It scans the table at 5B00h until it reaches 5B2Ch, which says that word 69 is found at 8E15h. It then expands words 69, 70, 71 and 72 into the buffer:

Word 69: 0, 'ENTER'    buffer contains     'ENTER'
Word 70: 5, '.'        buffer now contains 'ENTER.'
Word 71: 1, 'nglish'   buffer now contains 'English'
Word 72: 2, 'tering'   buffer now contains 'Entering'

So the buffer now contains 'entering', as required.

Messages

Messages start at 7601h (part 1) / 7AA4h (part 2). Messages consist of a sequence of 2-byte word IDs, in big-endian byte order, terminated by 0FFh. The second message in part 1, for example, is 00 B8 00 ED 03 62 02 FF 05 52 03 95 FF:

00 B8:	Word 0B8h, 'You'
00 ED:	Word 0EDh, 'are'
03 62:	Word 362h, 'lost'
02 FF:	Word 2FFh, 'in'
05 52:	Word 552h, 'the'
03 95:	Word 395h, 'mountains.'
FF	End of message

Vocab

The nouns table is at 0A4C0h (part 1) / 0B23Eh (part 2). The verbs table is at 0A71Ch (part 1) / 0B49Ah (part 2).

Each entry is a big-endian word. If the top bit is set, then this is the last word in this group of synonyms. The other bits give the word number.

For example, the part 1 noun table begins:

00 D0:	Word 0D0h, 'all'.        Noun group 0.
82 39:	Word 239h, 'everything'. Noun group 0. Top bit set, end of group.
05 16:	Word 516h, 'stones'.     Noun group 1.
04 70:	Word 470h, 'rubble'.     Noun group 1.
03 F2:	Word 3F2h, 'pebbles'.    Noun group 1.
04 65:	Word 465h, 'rocks'.      Noun group 1.
84 65:	Word 465h, 'rocks'.      Noun group 1. Top bit set, end of group.
01 F2:  Word 1F2h, 'diagram'.    Noun group 2.

Word 0FFFh appears to be used as a placeholder for unknown words.

Maps

Mindshadow is divided into five zones, each of up to 36 rooms in a 6×6 grid. Part 1 consists of two zones, part 2 of three:

Zone 0: Island
Zone 1: Pirate ship
Zone 2: London docks
Zone 3: Luxembourg
Zone 4: The second floor of the Luxembourg Hotel

The zone table is at 7589h (part 1) / 7A2Ch (part 2). Each entry in it is 40 bytes. The first 36 give the room connections and description number:

Bit 7: Cannot go West
Bit 6: Cannot go North
Bit 5: Cannot go East
Bit 4: Cannot go South
Bits 3-0: Room description number 0-15	

The byte at offset 36 is the start of room description messages for the zone. This will be added to the room description number to produce a message number.

At offset 37 is the number of the start room for the zone.

Bytecode

At 6136h (both versions) is the bytecode defining the game logic.

As a rule, the bytecode interpreter, when called, will execute only one instruction. To execute more than one, they need to be wrapped in a BEGIN ... END block. Compare the way that in C, an if() can be followed either by a single statement, or a block surrounded by curly brackets.

In general, an instruction is 2 bytes long:

	DB	tttaaaaa, bbbbbbbb	;T is instruction type, 
					;A is parameter 1,
					;B is parameter 2

However, if the type bits are 000 or 011, the instruction is a single byte.

The meanings of the type bits are:

000: BEGIN a block. The interpreter will be called repeatedly until the byte
          it encounters is 01h (END).
001: Match word. Bits 1-0 of the first instruction byte give the word type:
		00 => Verb 2. If the main verb is a movement word, this
				will give the direction.
		01 => Verb 1. The main verb in the player's input.
		10 => Noun 1. The main noun in the player's input.
		11 => Noun 2. The second noun (for inputs like 'KILL ORC WITH SWORD')
     The second instruction byte gives the verb / noun number to compare to.
     If there is a match, the bytecode interpreter will be called recursively
     on the following instruction. If not, it will be skipped. If the following
     byte is then 60h (ELSE) then the instruction following it will be 
     executed if there was no match, skipped otherwise.

010: Check condition. As with the 'match word' check above, the following 
     instruction will be executed if the condition is true, skipped if false;
     and if there is an ELSE following, the instruction after that will be
     executed if the condition is false, skipped if it is true.
     Bits 4-0 of the first instruction byte give a parameter, 0-31. 
     The second instruction byte describes the test or operation to perform:
		00h => Add the parameter to the current room number 
			  (parameter is sign-extended to twos complement, so
                          0-15 are positive, 16-31 become -16 to -1). Then 
                          move the player to that room.
		01h => Try to move the player via an exit. Parameter is
		          0 for North, 1 for East, 2 for South, 3 for West.
		02h => Go to the start room of a zone. The parameter 
                          specifies the zone.
		03h => The parameter specifies a zone. Condition is true
                          if the player is in that zone, false if not.
		04h => The parameter specifies a room number. Condition is
                          true if the player is in that room, false if not.
		05h => Describe the location.
		06h => Restart the game (not used).
		07h => Set a boolean flag. The parameter gives the number
		          of the flag, 0-31.
		08h => Same as 07h.
		09h => Clear a flag.
		0Ah => Condition is true if the specified flag is set.
		0Bh => List inventory.
		0Ch => The parameter is the number of an object. That 
			  object will be selected for future operations.
		0Dh => The parameter is the number of an object, less 16. 
			  Add 16 and use that object for future operations.
		0Eh => Condition is true if the current object is available
			  (carried or in the current room).
		0Fh => Condition is true if the current object is carried.
		10h => Attempt to take the current object.
		11h => Attempt to drop the current object.
		12h => Bring the current object into play (reset its 
			  'destroyed' flag).
		13h => Remove the current object from play (set its 
			  'destroyed' flag).
		14h => Condition is true if the current object does not 
			  have its 'destroyed' flag set.
		15h => Condition is true if the number of objects carried
			  ls less than the parameter.
		16h => Moves object to room 15.
		17h => Print a stock message. The parameter is:
			  0 => "Object: Dropped."
			  1 => "Object: Taken."
			  2 => "What do you want to do with the object?"
			  3 => "I don't know the word word."
			  4 => "I don't know how to word."
			  Other values => "You can't command."	
		18h => Implements TAKE ALL.	
		19h => Implements DROP ALL.	
		1Ah => Reparse input?
		1Bh => Paint graphic number param + 70.
		1Ch => Save/load operations. The parameter is:
			  0 => RAM load
			  1 => RAM save
			  2 => Cassette load
			  3 => Cassette save
			  Other values => Cassette save, then reboot.
		1Dh => Clear graphics and display text screen.
		1Eh => Lock up the computer (not used). Possibly for
			  debugging?

100:	The parameter and the following byte are combined to produce a 13-bit
	message number. This message is displayed.

101:	Not used. This combines its parameter and the following byte in the
	same way, but then reboots the computer.

110:	As 010, but there is no conditional execution.

Other values: The bytecode interpreter returns, leaving the instruction 
pointer pointing at the offending byte. This is how it responds on finding
byte 01h (END block) or 60h (ELSE).

For example, here's (some of) the code to handle giving rum to the Captain:

21 03	IF verb is 3, 'bribe'
22 06	  IF noun is 6, 'rum'
00	    BEGIN
23 1F         IF noun2 is 31, 'captain'
C0 1A           Reparse input
40 0F         IF current object is carried
48 04           IF currently in room 8 (beach)
44 0A             IF flag 4 is set (pirate ship is present)
00                  BEGIN
C7 0C                 Reference object 7, an empty bottle
40 14                 IF that object exists
00                      BEGIN
80 E8                     Message 250, 'the captain, disgusted, sails off'
C4 09                     Clear flag 4 (pirate ship leaves)
C0 05                     Redescribe room
01                      END
60                    ELSE  (object 7 does not exist)
00                      BEGIN
C6 0C                     Reference object 6, a bottle of rum
C0 13                     Destroy that object
80 75                     Message 135, 'the captain takes you aboard his ship'
C1 02                     Move the player to zone 1, the pirate ship
01                      END
01                    END 
60                  ELSE ; Pirate ship not present
C2 08                 Set flag 2
60                ELSE ; Not currently in room 8
C2 08               Set flag 2
60              ELSE ; Current object not carried
C2 08             Set flag 2
01          END 

Graphics

There is a lookup table of graphics at 0ACDCh (part 1) / 0BA5Bh (part 2). Each entry is a word, pointing to a 0-terminated string of polygons.

The first byte of a polygon specifies the brush to use:

For brushes 0,1,2: Bits 7 and 6 are the brush number. Bits 5-0 then
give the attribute to use.
For brushes 3-10:  Bits 7 and 6 are both set. Bits 5-3 give the brush number
less 3. Bits 2-0 give the paper colour (ink colour is always 0).

The brushes are:

Number   Pattern   Write mask
00h      055h      0FFh
01h	 000h      000h
02h      0FFh      000h
03h      0FFh      0FFh
04h      0AAh      000h
05h      0AAh      0AAh
06h      0AAh      0AAh
07h      088h      0FFh
08h      0CCh      000h
09h      088h      088h
0Ah      0EEh      033h

Following the brush are pairs of coordinates:

	DB	x1,y1
	DB	x2,y2
	etc.

until a coordinate pair is encountered with bit 7 of Y set. These coordinates then form a polygon to be filled with the specified brush.

Y-coordinates work down from the top of the screen.

=====================================================================

John Elliott 2017-11-21