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:
- You start with 16 lives, rather than the original 7. Aren't I
generous!
- The sprite used for the lives counter is specified in the room data. A similar change was
made to good effect in Andrew Broad's "JSW: The Lord of the
Rings".
- Each successive "life" sprite shows a different phase, rather
than all of them being the same phase.
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 area | Matthew Mode | Geoff Mode
|
| Item count | #a3ff | #8451
|
| Start address | 33792 | 34758
|
| 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:
- The lower four images.
- 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.
- All eight images. If you're using four-position sprites, the
sprite will change when Willy changes direction.
- 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) | Contents | Comments
|
| 00-7F | Block layout | Each byte specifies a group of 4
adjacent blocks, with two bits per block.
|
| 80-9F | Room name | 32 characters, in ASCII.
|
| A0 | Gas block attribute | AKA "Air".
|
| A1-A8 | Gas block design | Specified by "00" in
the block layout.
|
| A9 | Liquid block attribute | AKA "Water". The ones you can
stand on and walk through.
|
| AA-B1 | Liquid block design | Specified by "01" in the block
layout.
|
| B2 | Solid block attribute | AKA "Earth". The ones you can't
walk through, but can stand on.
|
| B3-BA | Solid block design | Specified by "10" in the block
layout.
|
| BB | Plasma block attribute | AKA "Fire". The ones that
kill you.
|
| BC-C3 | Plasma block design | Specified by "10" in the block
layout.
|
| C4 | Stair block attribute |
|
| C5-CC | Stair block design |
|
| CD | Conveyor block attribute |
|
| CE-D5 | Conveyor block design |
|
| D6 | Conveyor direction | 0 = left; 1 = right
|
| D7-D8 | Location of conveyor left end | in
attribute buffer at #5c00-#5dff
|
| D9 | Conveyor length | Should be <= 32.
|
| DA | Stair direction | 0 = \; 1 = /
|
| DB-DC | Location of stair left end | in
attribute buffer at #5c00-#5dff
|
| DD | Stair length | Should be <= 16.
|
| DE | Border colour | in bottom 3 bits.
|
| DE | Willy on screen | mode, in top 2 bits.
|
| DF | Lives counter | top bit = 0 to use
bottom half of sprite page, 1 to use top half.
|
| E0 | Lives counter | sprite page.
|
| E1-E8 | Item block design |
|
| E9 | Room to left |
|
| EA | Room to right |
|
| EB | Room above |
|
| EC | Room below |
|
| ED | Willy on screen | sprite page.
|
| EE-EF | Address of patch vector |
|
| F0 | Class of first guardian | or #FF if not
present
|
| F1 | Start byte of first guardian |
|
| F2-FF | Data for remaining guardians | in
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:
- 00 specifies "purely horizontal".
- 01 moves the guardian one pixel row every tick, i.e. an
angle of 26.57 degrees.
- 10 moves the guardian one pixel row every two ticks,
i.e. an angle of 14 degrees.
- 11 moves the guardian two pixel rows every tick, i.e. an
angle of 45 degrees.
ttt specifies the type of the guardian, as follows:
- 000: not used
- 001: horizontal guardian, non-wraparound
- 010: vertical guardian, non-wraparound
- 011: rope
- 100: arrow
- 101: horizontal guardian, wraparound
- 110: vertical guardian, wraparound
- 111: rope
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:
- a dummy sprite
- a nice long rope
- an arrow moving right
- an arrow moving left