Geoff Mode

Last update: 23 October 2002

Geoff's homepage -> Jet Set Willy -> Geoff Mode explained


What is "Geoff Mode"?

It's a set of changes to the original JSW game engine, which is correspondingly known as "Matthew Mode". There are three types of change:

You can also look at a disassembly of the Geoff Mode engine.


Cosmetic changes

These don't affect the gameplay, but are the easiest ways to tell if you're playing a Geoff Mode game by just looking at it.

The lives counter

Three changes here:

The time counter

This no longer shows a time, but a 7-digit number which counts down from 9999999. This dates from the earliest days of Geoff Mode, and I can no longer remember why I did it.

The pause colour changes

I think there's a difference here, but I can't remember what!

The tune

In Matthew Mode, the general pitch (i.e. the key) of the tune drops with each life lost; with sixteen lives, this means that the tune is unlistenable at the beginning. In Geoff Mode, the key depends on the room number instead (specifically, the room number ANDed with 0x1C). If you want to change this, the code takes up 9 bytes at #8B51.

The items

These animate through eight colours as in JSWII, not just four as in Matthew Mode.

Functional modifications

These are either invisible changes which don't affect the gameplay, or bugfixes.

The Block Graphics Bug

In Matthew Mode, the basic room layout is generated by drawing the attributes first, then using a CPIR to find the block design:

8D4E DD7E00   LD   A, (IX+#00)
8D51 21A080   LD   HL, #80A0
8D54 013600   LD   BC, #0036
8D57 EDB1     CPIR

The bug works like this. Suppose your stair block is yellow on black (i.e. the attribute byte is #06), and your liquid (or water) block contains a line like this:

[possibly more rows]
.....##.    06  <------
[possibly more rows]

The CPIR will find the byte marked with the arrow and draw the stair block using the eight bytes following it, meaning that your staircase won't look right. The solution is to ensure that the attribute byte for a given block does not appear in the data for an earlier block.

In Geoff Mode, the room-drawing routine has been completely redrawn, and this bug has been slain. The core of the routine is at #8D96, which prints a single block; it starts with HL pointing at the block's attribute btye (i.e. #80a0 + 9 * block_number) and DE pointing at the address of the attributes on the screen.

A side-effect of this is that you can have two different blocks with the same attributes, and they'll be displayed differently. But they'll both behave like each other, so if you do this with stairs and file/plasma blocks, you'll have two sets of deadly staircase blocks. There's no easy way to fix this.

Data areas have been moved around

This was done so that the data areas would be less fragmented. The areas affected are:

Data areaMatthew ModeGeoff Mode
Item count#a3ff#8451
Start address3379234758
Title screen attributes#9800-#9aff#b700-#b9ff
Item table#a400-#a5ff#ba00-#bbff
Guardian class definitions#a000-#a3ff#bc00-#bfff


Enhancements

These changes are, of course, your principal reasons for abandoning Matthew Mode for good.

The patch vector

Two bytes within the room data specify the location of a subroutine which is called every tick, after Willy and all the guardians, items and arrows have been displayed but before the room data is finally copied to the screen. This subroutine, which may be different for each room, is known as the "patch vector", for now-forgotten reasons.

Patch vectors are potentially very powerful; they are used to implement the teleport, for example. To see what patch vectors are capable of, have a look at the disassemblies of the patch vectors in my own JSW games. The data at locations #85CB to #85E2 is often useful within patch vectors; for example, ANDing the "timer" byte at #85CB with something and doing a RET NZ afterwards will execute the patch vector once every few ticks. This might be useful, for example, if your patch vector reverses the directions of the conveyors.

Because it's a subroutine, it should end with either a RET as usual, or a jump to a subroutine which ends with a RET.

The teleporter

This facility is most conveniently called within a patch vector, like this example from "Downhill" in "Willy Takes A Trip"

8728 11D55C   LD   DE, #5CD5
872B 0E02     LD   C, #02
872D C3B287   JP   #87B2     ; SEE NOTE BELOW!!!

The teleporter is activated when Willy is at the location given by DE; he is transported to the same location in the room specified by C. Obviously, this should be a different room from the one he's currently in, otherwise he'll be teleported to where he is over and over again. It is probably possible to alter the routine so that you're transported to a different screen address, but I'll leave this to someone else to implement.

NOTE: the teleporter has been moved to #8C02 in the latest version of Geoff Mode, and is likely to stay there for the forseeable future. #87B2 is currently empty.

Willy's Sprite

As with the lives counter, the sprite which displays Willy on the screen is specified in the room data, rather than being hardcoded to the Nightmare Room as in Matthew Mode. The sprite may use the entries within a page in one of the following modes:

  1. The lower four images.
  2. The upper four images. With this option, and the preceding, Willy will always face in the same direction if you're using eight-position sprites.
  3. All eight images. If you're using four-position sprites, the sprite will change when Willy changes direction.
  4. All eight images, in reverse order. With this you can make Willy go backwards.

Ropes are nicer

In Matthew Mode, because of some lazily inefficient programming, the data for a "rope" guardian takes up sixteen bytes rather than the usual eight. This means that within the room data a rope must be followed by a dummy guardian.

In Geoff Mode, the code has been rewritten so that it (1) takes up less space and (2) only needs the usual eight bytes. So you can have eight ropes in one room.

NOTE: Strange and rather annoying effects have been noticed with some pathological ropes. I don't remember if this was due to a bug in the code or weird interactions with other data.

Arrows work differently

In Matthew Mode, the time of firing of any given "arrow" guardian is specified within the guardian class, and the arrow reappears every 256 ticks. In Geoff Mode, the time of firing is specified within the guardian's start byte in the room data, and reappears every 128 ticks. This means that, in practice, you only need one arrow class for each direction (right and left). You also lose the ability to specify the arrow's row at pixel level, but I really don't think anyone's going to miss that.

Guardians can wrap around

Instead of trotting back and forward as in Matthew Mode, guardians can move in one direction only and "wrap around" at the end of their path.

Horizontal guardians can go fast

Yes, just like in JSWII, rather than just crawling as in Matthew Mode. Actually, speeds of above four times the Matthew Mode crawl aren't terribly useful in practice. See the guardian class format for details.

Horizontal guardians can go diagonally

Yes, just like in JSWII again. See the guardian class format for details.

New room format

As everybody knows, the data for room N is stored in 256 bytes starting at #C000 + 256 * N, and is copied to a working area at #8000-#80FF when the room is entered. Changes are highlighted in blue below.

Offset (hex)ContentsComments
00-7FBlock layoutEach byte specifies a group of 4 adjacent blocks, with two bits per block.
80-9FRoom name32 characters, in ASCII.
A0Gas block attributeAKA "Air".
A1-A8Gas block design Specified by "00" in the block layout.
A9Liquid block attributeAKA "Water". The ones you can stand on and walk through.
AA-B1Liquid block designSpecified by "01" in the block layout.
B2Solid block attributeAKA "Earth". The ones you can't walk through, but can stand on.
B3-BASolid block designSpecified by "10" in the block layout.
BBPlasma block attributeAKA "Fire". The ones that kill you.
BC-C3Plasma block designSpecified by "10" in the block layout.
C4Stair block attribute
C5-CCStair block design
CDConveyor block attribute
CE-D5Conveyor block design
D6Conveyor direction0 = left; 1 = right
D7-D8Location of conveyor left endin attribute buffer at #5c00-#5dff
D9Conveyor lengthShould be <= 32.
DAStair direction0 = \; 1 = /
DB-DCLocation of stair left endin attribute buffer at #5c00-#5dff
DDStair lengthShould be <= 16.
DEBorder colour in bottom 3 bits.
DEWilly on screenmode, in top 2 bits.
DFLives countertop bit = 0 to use bottom half of sprite page, 1 to use top half.
E0Lives countersprite page.
E1-E8Item block design
E9Room to left
EARoom to right
EBRoom above
ECRoom below
EDWilly on screensprite page.
EE-EFAddress of patch vector
F0Class of first guardianor #FF if not present
F1Start byte of first guardian
F2-FFData for remaining guardiansin the same format.


New guardian class format

The data for guardian class N is stored in 8 bytes at #bc00 + 8 * N. The bytes have the following meanings.

Byte 0: diiggttt

d is the starting dirction of the guardian: 0 = left, 1 = right. This is not meaningful for vertical guardians.

iishould be 00. It holds the current phase of the guardian.

gg is only meaningful for horizontal guardians:

ttt specifies the type of the guardian, as follows:

Byte 1: ppphbccc

For ropes, this is the starting position of the rope within its swing. For arrows, it is ignored. Otherwise:

pppp specifies the number of phases the guardian is animated with. The bits should be 111, 011, 001 or 000, which respectively specify 8, 4, 2 or 1 phases.

h is ignored for vertical and purely horizontal guardians. For diagonal guardians, it specifies whether the guardian is moved up (if 1) or down (if 0) to start with. It is flipped when non-wraparound guardians change direction.

b is 1 if the guardian is bright, which looks horrible if the room's backgrouns isn't black, and 0 otherwise.

ccc is the colour of the guardian.

Byte 2: sssxxxxx

NOTE: This byte is taken from the room data, not the guardian data.

For arrows, the top nybble is the y-coordinate of the row (measured by character blocks, not pixels), and the bottom nybble is one-eighth of the delay before the arrow appears. For ropes, xxxxx is the x-coordinate of the top pixel, and sss should be 000. Otherwise:

sss is the start phase of the guardian. This is ORed with (ppp AND dii) to select the sprite which is displayed.

xxxxx is the starting column of the guardian.

Byte 3

For ropes, for some reason this should be #74. For arrows it should be #ff if the arrow moves to the right and #20 if it moves to the left. Otherwise, it specifies 2 * the starting pixel row of horizontal or vertical guardians, or equivalently 16 * the starting character cell row.

Byte 4

For horizontal guardians, it gives the speed, which is negative if the guardian initially moves to the left. For vertical guardians it gives 2 * the speed, which is negative if the guardian initially moves upwards. For arrows it is ignored; for ropes it gives the length.

Byte 5

For ropes, for some reason this should be #98. For arrows it is ignored. Otherwise it specifies the sprite page.

Byte 6

For ropes, for some reason this should be #08. For arrows it specifies the bit pattern for the top and bottom of the arrow; the middle uses #7e. Otherwise, it gives the number of ticks before the guardian's path is reset. This byte is decremented once per tick; when it hits zero, it is loaded again from byte 7.

Byte 7

For ropes, this gives the period of one swing in ticks; note that short ropes with short periods won't appear to swing at all. For arrows it is ignored. Otherwise, it specifies the length of a guardian's path in ticks.

Note

Bytes 6 and 7 have different meanings for horizontal and vertical guardians from Matthew Mode. This change, which follows JSWII practice, is necessary because it's otherwise very difficult to work out where to put wraparound diagonal guardians once they've wrapped around. A side effect of this is that you don't specify a path between endpoints as you do in Matthew Mode.

If you're confused...

Do what I do! Set the first four entries of your guardian class table to this:

bc00  00 00 00 00 00 00 00 00 ........
bc08  03 22 09 74 20 98 08 36 .".t ..6
bc10  84 00 00 00 ff 00 84 00 ........
bc18  04 00 00 00 20 00 21 00 .... .!.

These define, respectively: