Technical notes on Advanced MM/JSW Trainer [v0.1.0]
==========================================
Contents
--------

<x This document is a draft which contains comments. x>


* Chequered cell-grid (JSW48 & MM48 annexes: subroutine #96F4-9713)

* Chequered cell-grid + reset cells' FLASH-bits (JSW64 main game: subroutine #96F4-9715)

* Print room-number (subroutine+data #9B00-9B54)

* No custom font (JSW64 main game)

* Teleporters (JSW64 main game: teleporter-list #A003-A3FF)

* Harmless guardians (JSW64 main game & JSW48 annex)

* Default room-setup patch-vector (JSW64 main game: subroutine #9B55-9B5A)

* Recolouring visited teleporters (JSW64 main game: subroutines in unused room-space)

* Harmful guardians (JSW64 main game: subroutines in unused room-space)

* Appendix

-------------------
Chequered cell-grid (JSW48 & MM48 annexes)
-------------------

The chequered cell-grid patch changes the colour-attributes of half the cells in the 16x32 playing-area: those whose row- and column-numbers are not both even and not both odd.

If, and only if, a cell has black PAPER, then its PAPER is set to blue just before the secondary attributes-buffer (#5C00-5DFF) is copied to video-RAM, and also before any extra-life colour-cycling (which therefore overrides the chequered cell-grid).

The following subroutine is called every time-frame, at the point when the game-engine is about to copy the secondary pixels-buffer (#6000-6FFF) to video-RAM (the copying of pixels is completely independent of the copying of colour-attributes; I chose this point because the code that gets overwritten by the subroutine-call - which has to be inserted into the subroutine just before it returns - is the same in both MM and JSW):

#87A2: CALL ccg      ; MM48 only
#89F5: CALL ccg      ; JSW48, JSW128, JSW64

ccg:   LD HL,#5C00   ; secondary attributes-buffer
       LD BC,#200    ; going to process 511 bytes (i.e. 32*16 cells)
loop:  LD A,L
       AND %00100001 ; if cell-row and cell-column are both even
       JR Z,skip     ; then skip
       CP %00100001  ; if cell-row and cell-column are both odd
       JR Z,skip     ; then skip
       LD A,(HL)     ; get colour-attribute of current cell
       AND %00111000 ; extract PAPER-colour
       JR NZ,skip    ; if not black (PAPER 0) then skip
       SET 3,(HL)    ; set current cell to PAPER 1 (blue)
skip:  INC HL        ; next cell
       DEC BC        ; decrement counter
       LD A,B        ; going to check if B and C are both 0...
       OR C          ; ...by bitwise-ORing them together
       JR NZ,loop    ; if BC <> 0 then loop back to process next cell
       LD HL,#6000   ; the code that was overwritten by the subroutine-call
       RET

To apply this patch using JSWED's hex-editor:

Variables:
>>>
xHxL: address of ccg subroutine - length #20 bytes
<<<

Recommendation:
>>>
xHxL = #96F4 (last byte at #9713)
<<<

Hex-editor:
>>>
87A2: CD xL xH (MM48)
89F5: CD xL xH (JSW48, JSW128, JSW64)

xHxL: 21 00 5C 01 00 02 7D E6
+#08: 21 28 0B FE 21 28 07 7E
+#10: E6 38 20 02 CB DE 23 0B
+#18: 78 B1 20 EA 21 00 60 C9
<<<

The drawback of this patch is that it slows the game down quite noticeably, because it is called every time-frame, and iterates over all 512 cells in the playing-area. Some performance could be regained by removing the three instructions before SET 3,(HL) - so as not to require the cell to have black PAPER - but I don't like the effect this has on other colour-attributes with even-numbered PAPER-colours.

---------------------------------------------
Chequered cell-grid + reset cells' FLASH-bits (JSW64 main game)
---------------------------------------------

If the game-engine is patched to set all cells to FLASH 0 just before they are drawn, then two cells can have the same colour-attribute - one with FLASH 0, the other FLASH 1.

So I wrote a patch to reset cells' FLASH-bits just before the secondary attributes-buffer is written to video-RAM:
http://groups.yahoo.com/group/manicminerandjetsetwilly/message/5757

I wished to sacrifice FLASHing cells for the ability to give two cells the same colour-attribute in Advanced MM/JSW Trainer, but rather than apply this patch independently of the chequered cell-grid patch, it is far more efficient to insert the RES 7,(HL) instruction into the chequered cell-grid patch:

ccg:   LD HL,#5C00   ; secondary attributes-buffer
       LD BC,#200    ; going to process 511 bytes (i.e. 32*16 cells)
loop:  LD A,L
       AND %00100001 ; if cell-row and cell-column are both even
       JR Z,skip     ; then skip
       CP %00100001  ; if cell-row and cell-column are both odd
       JR Z,skip     ; then skip
       LD A,(HL)     ; get colour-attribute of current cell
       AND %00111000 ; extract PAPER-colour
       JR NZ,skip    ; if not black (PAPER 0) then skip
       SET 3,(HL)    ; set current cell to PAPER 1 (blue)
skip:  RES 7,(HL)    ; set cell to FLASH 0 (Bit 7 of colour-attribute)
       INC HL        ; next cell
       DEC BC        ; decrement counter
       LD A,B        ; going to check if B and C are both 0...
       OR C          ; ...by bitwise-ORing them together
       JR NZ,loop    ; if BC <> 0 then loop back to process next cell
       LD HL,#6000   ; the code that was overwritten by the subroutine-call
       RET

To apply this patch using JSWED's hex-editor:

Variables:
>>>
xHxL: address of ccg subroutine - length #22 bytes
<<<

Recommendation:
>>>
xHxL = #96F4 (last byte at #9715)
<<<

Hex-editor:
>>>
87A2: CD xL xH (MM48)
89F5: CD xL xH (JSW48, JSW128, JSW64)

xHxL: 21 00 5C 01 00 02 7D E6
+#08: 21 28 0B FE 21 28 07 7E
+#10: E6 38 20 02 CB DE CB BE
+#18: 23 0B 78 B1 20 E8 21 00
+#20: 60 C9
<<<

(It is not enough simply to insert the RES 7,(HL) instruction ("CB BE") into the machine-code; the operand of the JR NZ,loop instruction ("20 EA") has to be changed from #EA (-22) to #E8 (-24) to accommodate this 2-byte insertion.)

Only the JSW64 main game needs to reset cells' FLASH-bits; the JSW48 and MM48 annexes use the original chequered cell-grid patch. Indeed, it would be disadvantageous for the MM48 annex to reset cells' FLASH-bits, because that means that the portal does not appear to flash when you have collected all the items in a room.

-----------------
Print room-number
-----------------

The following code is based on John Elliott's patch to print the room-number at the bottom-right of the screen, but I have rewritten it to print square brackets with no leading zeroes.

The following subroutine is called every time-frame, at the point when the number of items collected is to be printed:

#87E8: CALL pr_rn    ; MM48 only
#8A46: CALL pr_rn    ; JSW48, JSW128, JSW64

str:   DB '[[[0]'    ; the string to print at (23,27)

pr_rn: CALL pr_str   ; the code that was overwritten by the subroutine-call
       LD IX,str     ; address of string to print
       LD C,#5B      ; assume going to print a '[' (ASCII #5B)...
       LD (IX+0),C   ; ...in column 27...
       LD (IX+1),C   ; ......column 28...
       LD (IX+2),C   ; ..and column 29
       LD A,(roomno) ; get current room-number
       CP 100        ; if room-number >= 100 
       JR NC,hunds   ; then skip the following code
       LD C,#20      ; going to print a space (ASCII #20)...
       LD (IX+0),C   ; ...in column 27
       CP 10         ; if room-number >= 10
       JR NC,tens    ; then skip the following code
       LD (IX+1),C   ; going to print a space in column 28
       JR units

hunds: LD C,#30      ; assume 'hundreds' digit is '0' (ASCII #30)
looph: SUB 100       ; A:= A - 100
       JR C,skiph    ; break out of the loop when A < 0
       INC C         ; increment 'hundreds' digit
       JR looph      ; repeat
skiph: ADD A,100     ; {0 <= A <= 99}
       LD (IX+1),C   ; going to print 'hundreds' digit in column 28

tens:  LD C,#30      ; assume 'tens' digit is '0' (ASCII #30)
loopt: SUB 10        ; A:= A - 10
       JR C,skipt    ; break out of the loop when A < 0
       INC C         ; increment 'tens' digit
       JR loopt      ; repeat
skipt: ADD A,10      ; {0 <= A <= 9}
       LD (IX+2),C   ; going to print 'tens' digit in column 29

units: ADD A,#30     ; ASCII-codes for '0'-'9' are #30-39
       LD (IX+3),A   ; going to print 'units' digit in column 30
       LD C,5        ; 5 characters to print
       LD DE,#50FB   ; at (23,27)
       JP pr_str     ; print the string and use its RET to return

To apply this patch using JSWED's hex-editor:

Variables:
>>>
pHpL: address of pr_str subroutine (#92BA for MM, #9680 for JSW)
rHrL: address of current room-number (#8407 for MM, #8420 for JSW)
xHxL: address of pr_rn subroutine - length #50 bytes
yHyL: address of default string ("[[[0]") - length 5 bytes
zz: colour-attribute of room-number
<<<

Recommendations:
>>>
yHyL = xHxL + #50
xHxL = #9B00 (last byte at #9B4F)
yHyL = #9B50 (last byte at #9B54)
zz = #47
<<<

Hex-editor:
>>>
87E9: xL xH (MM48 only)
8A47: xL xH (JSW48, JSW128, JSW64)

9FFB: zz zz zz zz zz (MM48 only)
9AFB: zz zz zz zz zz (JSW48, JSW128, JSW64)

xHxL: CD pL pH DD 21 yL yH 0E
+#08: 5B DD 71 00 DD 71 01 DD
+#10: 71 02 3A rL 84 FE 64 30
+#18: 0E 0E 20 DD 71 00 FE 0A
+#20: 30 13 DD 71 01 18 1C 0E
+#28: 30 D6 64 38 03 0C 18 F9
+#30: C6 64 DD 71 01 0E 30 D6
+#38: 0A 38 03 0C 18 F9 C6 0A
+#40: DD 71 02 C6 30 DD 77 03
+#48: 0E 05 11 FB 50 C3 pL pH

yHyL: 5B 5B 5B 30 5D
<<<

--------------
No custom font (JSW64 main game)
--------------

JSW64 is hard-wired to use a custom font which is located at 0:#C100-C3FF.

JSW48's subroutine to look up a character in the standard Spectrum font (ROM #3D00-3FFF) has already been documented in TECHNICA.TXT of Goodnite Luddite. JSW64 modifies this subroutine as follows:

{A contains ASCII-code of character to print}
#9691: LD H,#18  ; HL = #100*#18 = #1800
#9693: LD L,A    ; HL = #1800 + A
#9694: NOP
#9695: NOP
#9696: ADD HL,HL ; HL = #3000 + 2*A
#9697: ADD HL,HL ; HL = #6000 + 4*A
#9698: ADD HL,HL ; HL = #C000 + 8*A
...

So all we have to do is revert it to the original JSW48 subroutine...
#9691: LD H,7
#9693: LD L,A
#9694: SET 7,L
#9696: ADD HL,HL
#9697: ADD HL,HL
#9698: ADD HL,HL
...

To apply this patch using JSWED's hex-editor:
>>>
9692: 07 6F CB FD
<<<
#C100-C3FF are now free, although JSWED's Memory page doesn't allow this fact to be recognised. But we can edit the JSW64 memory-map (see Appendix) manually to say that they are free:
8669: 00 00 00

-----------
Teleporters (JSW64 main game)
-----------

John Elliott's teleporter-patch has already been documented in TECHNICA.TXT of _Jet Set Willy: The Lord of the Rings_.

The JSW64 main game applies it with the subroutine at 0:#EA00, as usual for JSW64, but the teleporter-list has been relocated to #A003-A3FF (enough contiguous space for up to 255 teleporters - the maximum that can be specified by the byte at #A003), leaving #EA4A-EAFF free.

To move the teleporter-list to #A003:
EA05: 03 A0

To mark Pages #A0-A3 as reserved for teleporters, the JSW64 memory-map (see Appendix) has been modified as follows:
8648: 61 61 61 61

------------------
Harmless guardians (JSW64 main game & JSW48 annex)
------------------

There are three methods for making guardians harmless in JSW, as explained in Message 5816 of the Manic Miner and Jet Set Willy Yahoo! Group:
http://groups.yahoo.com/group/manicminerandjetsetwilly/message/5816

The JSW64 main game and the JSW48 annex use harmless, stationary guardians in conjunction with teleporters to simulate portals (which are permanently open, regardless of any items in the room).

For this, they use Method C: the sprite-drawing subroutine is called for guardians with C == 0 to disable collision-detection:

9210: 00 (POKE 37392,0)

Unlike Method B (which modifies the sprite-drawing subroutine to ignore collisions), this POKE has the following properties:

* Each guardian-sprite overwrites everything in its 16x16-pixel square, rather than merging its pixels with what's there already. This means that each guardian covers up Willy like a portal (in contrast to Method B, which merges their pixels).

* It doesn't disable collision-detection for Maria.

-------------------------------
Default room-setup patch-vector (JSW64 main game)
-------------------------------

The default room-setup patch-vector applies POKE 37392,0 (disable collision-detection for guardians) to undo the effect of rooms which override it:
LD HL,#9210 ; 37392
LD (HL),0   ; disable collision-detection for guardians
RET

To apply this particular default room-setup patch-vector to a JSW64 (Variant V or W) game using JSWED's hex-editor:

Variable:
>>>
xHxL: address of default room-setup patch-vector subroutine - length 6 bytes
<<<

Function p calculates the banked address of Room r's room-setup patch-vector:
>>>
p(r) = b : #C0FB + #200*(r mod 32)
where b = 1 if {0 <= r <= 31}
      b = 3 if {32 <= r <= 63}
      b = 4 if {64 <= r <= 95}
      b = 6 if {96 <= r <= 127}
<<<
e.g. p(0) = 1:#C0FB, p(1) = 1:#C2FB, ..., p(32) = 3:#C0FB, ...

Recommendation:
>>>
xHxL = #9B55 (last byte at #9B5A)
<<<

Hex-editor:
>>>
xHxL: 21 10 92 36 00 C9

For each room r with no other room-setup patch-vector (the JSW64 default room-setup vector is #869F, which just returns):
p(r): xL xH
<<<

-------------------------------
Recolouring visited teleporters (JSW64 main game)
-------------------------------

A teleporter to a room which has not been visited yet has a colour-attribute of #46 (bright yellow ink) for its harmless stationary vertical guardian. A teleporter to a room which /has/ been visited has a colour-attribute of #05 (dull cyan ink).

But JSW doesn't store a vertical guardian's colour-attribute in the conventional %FBPPPIII format (Flash, Bright, Paper, Ink). It stores it, in Guardian-offset 1, as %AAAXBIII, where AAA are the animation-mask and X is unused (except in my lifts-patch, where it is used to identify a guardian as being a lift). Thus a bright yellow teleporter has a Guardian-offset 1 of #0E, dull cyan #05.

When using a teleporter in Room S (the source-room) to get to Room T (the target-room), Room T's room-setup patch-vector sets Guardian-offset 1 to #05. This behaviour is hard-coded for every teleporter in the game, /except/ when Room S can only be visited /after/ Room T (e.g. Room T is "Main menu" [0]), in which case the teleporter is coloured cyan in the data for Room S.

In a JSW64 room, the guardians are stored starting at Room-offset 0, and are 8 bytes each (JSW64 abandons the concept of guardian-classes and guardian-instances).

Thus Function a calculates the banked address of Guardian-offset 1 of Guardian g in Room r (where r is the source-room):
>>>
a(r,g) = b : #C001 + #200*(r mod 32) + 8*g
where b = 1 if {0 <= r <= 31}
      b = 3 if {32 <= r <= 63}
      b = 4 if {64 <= r <= 95}
      b = 6 if {96 <= r <= 127}
<<<
e.g. a(0,0) = 1:#C001, a(0,1) = 1:#C009, ...
     a(1,0) = 1:#C201, ...

This means that the room-setup patch-vector must select Bank 1 (for Rooms 0-31) as the one that is paged into #C000-FFFF before it alters the room-datum, and must switch back to Bank 0 afterwards. It does so by writing %00001BBB (where BBB are the bank to page in) to the hardware-switch at I/O address #7FFD.

Generic room-setup patch-vector code:
>>>
CALL #9B55    ; default room-setup patch-vector
LD BC,#7FFD   ; I/O address
LD A,bank+#10 ; bank-number + #10
OUT (C),A     ; write it to I/O address {bank is paged in}
LD HL,address ; address of Guardian-offset 1 of Guardian g in Room r
LD A,5        ; dull cyan
LD (HL),A     ; set Guardian-offset 1
LD A,#10      ; Bank 0
OUT (C),A     ; write it to I/O address {Bank 0 is paged in}
RET

where bank:address = a(r,g)
<<<

e.g. Room 2's room-setup patch-vector sets Guardian 0 in Room 0 to have dull cyan ink; a(0,0) = 1:#C001:
>>>
CALL #9B55    ; default room-setup patch-vector
LD BC,#7FFD   ; I/O address
LD A,#11      ; Bank 1
OUT (C),A     ; write it to I/O address {Bank 1 is paged in}
LD HL,#C001   ; address of Guardian-offset 1 of Guardian 0 in Room 0
LD A,5        ; dull cyan
LD (HL),A     ; set Guardian-offset 1
LD A,#10      ; Bank 0
OUT (C),A     ; write it to I/O address {Bank 0 is paged in}
RET
<<<

The room-setup patch-vector's code (such as the above) goes in the room's unused space (in JSW64:V, Room-offsets #100-13F). It will actually be executed from the current-room buffer, i.e. at #8100, thus the room-setup patch-vector at p(r) is set to #8100.

Thus Function u calculates the banked address of the start of the unused space in Room r (where r is the target-room):
>>>
u(r) = b : #C100 + #200*(r mod 32)
where b = 1 if {0 <= r <= 31}
      b = 3 if {32 <= r <= 63}
      b = 4 if {64 <= r <= 95}
      b = 6 if {96 <= r <= 127}
<<<
e.g. u(0) = 1:#C100, u(1) = 1:#C300, ..., u(32) = 3:#C100, ...


To apply Room S's room-setup patch-vector using JSWED's hex-editor:

Variables:
>>>
xHxL: address of default room-setup patch-vector subroutine (see "Default room-setup patch-vector" section above)
(yy-#10):zHzL = a(s,g) where s is the source-room
<<<

Hex-editor (t is the target-room):
>>>
p(t): 00 81

u(t): CD xL xH 01 FD 7F 3E yy
+#08: ED 79 21 zL zH 3E 05 77
+#10: 3E 10 ED 79 C9
<<<

e.g. Room 2's room-setup patch-vector, operating on Guardian 0 in Room 0, where the default room-setup patch-vector subroutine is at #9B55:
>>>
1:#C4FB: 00 81

1:#C500: CD 55 9B 01 FD 7F 3E 11
1:#C508: ED 79 21 01 C0 3E 05 77
1:#C510: 3E 10 ED 79 C9
<<<

The following table lists all the teleporters in the JSW64 main game that get marked as visited:

+--------+--------+----------------------+
| Target | Source | Alters colour of...  |
+--------+--------+----------------------+
|   [2]  |   [0]  | Guardian 0 (1:#C001) |
|   [4]  |   [0]  | Guardian 1 (1:#C009) |
|   [6]  |   [4]  | Guardian 1 (1:#C809) |
|   [7]  |   [4]  | Guardian 2 (1:#C811) |
|   [8]  |   [4]  | Guardian 3 (1:#C819) |
|   [9]  |   [0]  | Guardian 2 (1:#C011) |
|  [10]  |   [9]  | Guardian 1 (1:#D209) |
|  [11]  |   [9]  | Guardian 2 (1:#D211) |
|  [12]  |   [9]  | Guardian 3 (1:#D219) |
|  [13]  |   [0]  | Guardian 3 (1:#C019) |
|  [14]  |  [13]  | Guardian 1 (1:#DA09) |
|  [15]  |  [13]  | Guardian 2 (1:#DA11) |
<x>
+--------+--------+----------------------+

N.B. The changed colours persist after restarting the game - you'll have to reload it if you want to mark all teleporters as unvisited.

-----------------
Harmful guardians (JSW64 main game)
-----------------

As explained above, the default room-setup patch-vector disables collision-detection for guardians by applying POKE 37392,0.

In the JSW64 main game, rooms which include harmful guardians use the room-setup patch-vector to enable collision-detection (POKE 37392,1).

Their room-setup patch-vector code is as follows (it recolours a teleporter as above, then applies POKE 37392,1):
>>>
CALL #9B55    ; default room-setup patch-vector
LD BC,#7FFD   ; I/O address
LD A,bank+#10 ; bank-number + #10
OUT (C),A     ; write it to I/O address {bank is paged in}
LD HL,address ; address of Guardian-offset 1 of Guardian g in Room r
LD A,5        ; dull cyan
LD (HL),A     ; set Guardian-offset 1
LD A,#10      ; Bank 0
OUT (C),A     ; write it to I/O address {Bank 0 is paged in}
LD HL,#9210   ; 37392
LD (HL),1     ; enable collision-detection for guardians
RET

where bank:address = a(r,g)
<<<

To apply this room-setup patch-vector using JSWED's hex-editor:

Variables:
>>>
xHxL: address of default room-setup patch-vector subroutine (see "Default room-setup patch-vector" section above)
(yy-#10):zHzL = a(s,g) where s is the source-room
<<<

Hex-editor (t is the target-room):
>>>
p(t): 00 81

u(t): CD xL xH 01 FD 7F 3E yy
+#08: ED 79 21 zL zH 3E 05 77
+#10: 3E 10 ED 79 21 10 92 36
+#18: 01 C9
<<<

The rooms that use this patch-vector are [14, <x>].

These rooms use portals rather than teleporters with stationary guardians. Technically the portals are FLASHing so that Willy can enter them without collecting the items, but the patch to reset cells' FLASH-bits means that they don't appear to be FLASHing.

------------------------------
Appendix: The JSW64 memory-map
------------------------------

The JSW64 memory-map is stored at #8640-869E: a byte for each page from #98 to #F6, where each byte has one of the following values:
#00: the page is free;
#60: reserved for sprites;
#61: reserved for teleporters;
#80: reserved for code.

Thus the address in the memory-map of page p {#98 <= p <= #F6} is #85A8+p.

N.B. In JSW128 the memory-map is located 12 bytes later at #864C.
     The 48K MM/JSW games don't have memory-maps.
