Technical Notes on Manic Miner: Neighbours - Allana Truman (MM:N-AT)

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



As well as incorporating John Elliott's Manic Miner patch [http://www.seasip.demon.co.uk/Jsw/mmdiffs.html], I have hacked the MM game-engine in five ways:



* to make the Jet Set Willy item-collection sound (new subroutine at 37714-37737);



* to play as a different character in Room 13, and to fix the bug which corrupts Room 7 when you fall off the bottom of the screen (new subroutine at 37738-37760);



* to play the in-game tune at half-speed (new subroutine at 37761-37765);



* not to OR the colour-attribute of a vertical guardian with background;



* to alter the Room 19 swordfish-routine.



----------------------------------------------

Making the Jet Set Willy item-collection sound

----------------------------------------------



The Manic Miner game-engine first checks the colour-attribute of the character-square on which an item is to be drawn. If this square has white ink, then it adds 100 to the score and marks the item as collected (by setting its colour-attribute to 0), otherwise it draws the item.



It calls the following subroutine to increment the score:



36741: CALL 37118



This is as good a time as any to make the item-collection sound, so I decided to replace this call with a call to a new subroutine (located at 37714, which is the next free address after John Elliott's MM patch) to implement the item-collection sound from Jet Set Willy:



36741: CALL 37714



Item-sound subroutine (37714-37737):

37714: LD A,(32883) ; get current room's border-colour (32768 + 627-512)

37717: LD C,128     ; FOR C= 128 TO 2 STEP -2

37719: OUT (254),A  ; Port 254: Bits 0-2 = border; Bit 4 = loudspeaker

37721: XOR 00011000 ; toggle Bits 3 and 4 of A

37723: LD E,A

37724: LD A,144

37726: SUB C        ; LET A= 144 - C

37727: LD B,A

37728: LD A,E

37729: DJNZ 37729   ; FOR B= A TO 1 STEP -1: NEXT B

37731: DEC C

37732: DEC C

37733: JR NZ,37719  ; NEXT C

37735: JP 37118     ; jump across to score-incrementing subroutine



The CPU returns to the main item-handling routine when it executes the RET instruction at the end of the score-incrementing subroutine, and carries on like nothing happened.



Note for trainee machine-code programmers:

>>>

The subroutine at 37714 overwrites registers A, B, C and E, but this doesn't matter because neither the score-incrementing subroutine nor the main item-handling routine need the values in these registers to be preserved.



There /would/ be a problem if the item-sound subroutine overwrote registers H or L, because the main item-handling routine uses the HL register-pair to pass a value to the score-incrementing subroutine, and that value would be overwritten if the item-sound subroutine were to write to H or L.



If this were the case, we would use a PUSH HL instruction to save the value in HL on the stack, and a POP HL instruction to restore this value to HL afterwards.

<<<



To reuse the item-sound subroutine:

1. Copy 37714-37737 (24 bytes) to your game.

2. POKE 36742,82: POKE 36743,147



------------------------------------------------------------------------

Overriding the player's sprite, and fixing the bug which corrupts Room 7

------------------------------------------------------------------------



In MM:N-AT, the routine which draws the player's sprite has been modified in two ways:



1. Just as in Jet Set Willy you play as a different character in Room 29 ("The Nightmare Room"), so in MM:N-AT you play as a different character in Room 13;



2. I have fixed the bug where erroneous blocks appear in the Room 7 screen-layout if you fall off the bottom of the screen or jump off the top.



These two changes are right next to each other in the player-sprite-drawing code, so let us first study this code in the original Manic Miner, in order to understand how Room 7 gets corrupted.



37503: LD A,(32872) ; Willy's pixel-position (32768 + 616-512)

37506: LD IXH,131

37509: LD IXL,A     ; IX is an index into the X,Y lookup-table (33536 to 33791)

37511: LD A,(32874) ; Willy's direction (32768 + 618-512): 0=right, 1=left

37514: AND 1

37516: RRCA         ; Willy's direction now in MSB of A, i.e. 0=right, 128=left

37517: LD E,A

37518: LD A,(32873) ; Willy's sprite (32768 + 617-512): {0,1,2,3}

37521: AND 00000011

37523: RRCA

37524: RRCA

37525: RRCA         ; Willy's sprite now 0SS0000, i.e. multiplied by 32: {0,32,64,96}

37526: OR E         ; combine it with Willy's direction to form offset of sprite in sprite-page

37527: LD E,A

37528: LD D,130     ; use Sprite-Page 130 for the player, i.e. 33280; DE holds address of sprite

37530: LD B,16      ; it's going to loop 16 times: once for each pixel-row in the sprite

37532: LD A,(32876) ; Willy's colour-position, low byte (32768 + 619-512)

37535: AND 00011111 ; extract Willy's horizontal position

37537: LD C,A



Now it's about to enter the loop which draws each of the 16 pixel-rows. This loop uses IX to look up the address to poke each byte to, storing this address in HL. The valid ranges are {33536 <= IX < 33791} (the lookup-table) and {24576 <= HL <= 28671} (the secondary pixel-buffer). Note that IX must be an even number, since each entry in the lookup-table is 16 bits.



37538: LD A,(IX+0) ; low byte of address to poke pixel-row to

37541: LD H,(IX+1) ; high byte of address to poke pixel-row to

37544: OR C        ; add Willy's horizontal position to this address

37545: LD L,A      ; HL now holds the complete address to poke the pixel-row to

37546: LD A,(DE)   ; next left pixel-row of the sprite

37547: OR (HL)     ; superimpose it with any pixels already on the screen

37548: LD (HL),A   ; poke it to the secondary pixel-buffer

37549: INC HL      ; move right one character

37550: INC DE      ;

37551: LD A,(DE)   ; next right pixel-row of the sprite

37552: OR (HL)     ;

37553: LD (HL),A   ; ditto for the right side of the sprite

37554: INC IX      ;

37556: INC IX      ; point to next entry in the lookup-table

37558: INC DE      ; point to next pixel-row of the sprite

37559: DJNZ 37538  ; decrement B; if B > 0 then go back to the start of the loop

37561: RET



The problem when Willy is falling off the bottom of the screen is that IX >= 33792, which is pointing off the end of the lookup-table, into the first few instructions of Manic Miner! So it tries to interpret the codes for these instructions as though they were entries in the lookup table, thus generating all kinds of erroneous addresses to poke to! Some of these addresses happen to be in the screen-layout of Room 7.



I can think of three solutions to this bug:



1. Test IX each time round the loop, breaking out of the loop if IX >= 33792. This seems woefully inefficient and could even slow the game down, considering that it would have to execute a test and a conditional branch 16 times every time-frame!



2. Test IX before the loop, and skip over the loop if IX > 33760 (33760 is the last safe value of IX going into the loop, because it will be 33760 + 15*2 = 33790 on the final pass before being incremented to 33792 at the end of that pass). This means that Willy would disappear as soon as his feet did!



3. Test IX before the loop. If IX > 33760 then decrease the initial value in B (16 by default) so that the loop terminates before attempting to draw pixel-rows off the bottom of the screen. Hence the initial value in B should be (33792 - IX)/2 = (256 - IXL)/2.



I have written a subroutine to implement the third solution. This subroutine also selects the horizontal-guardian sprite from Room 0 (Allana) as the player sprite for Room 13.



To do this, I replaced the code from the original Manic Miner...



37527: LD E,A

37528: LD D,130     ; use Sprite-Page 130 for the player, i.e. 33280

37530: LD B,16      ; it's going to loop 16 times: once for each pixel-row in the sprite



...with the following code:



37527: LD B,16      ; set default-value first - it will be adjusted if necessary

37529: CALL 37738



37738: LD E,A       ; precondition: A holds offset of sprite in sprite-page

37739: LD D,130

37741: LD A,(33799) ; get current room-number

37744: CP 13        ; if in Room 13...

37746: JR NZ,37750  ;

37748: LD D,179     ; ...then use Sprite-Page 179, i.e. 45824 (Room 0 horizontal guardian)

37750: LD A,IXL     ; get low byte of IX

37752: CP 225       ;

37754: RET C        ; return if IXL < 225 (i.e. if IX <= 33760)

37755: NEG          ; 256 - IXL

37757: SRL A        ; shift right to divide by 2

37759: LD B,A       ; set initial loop-counter

37760: RET



To reuse this player-sprite-drawing subroutine:

1. Copy 37527-37531 (5 bytes) to your game.

2. Copy 37738-37760 (23 bytes) to your game.

3. POKE 37745,r to change the player-sprite in Room r.

4. If you don't want to change the player-sprite, get rid of the code at 37741-37749, e.g. by shunting the code at 37750-37760 down over it:

FOR a= 37741 TO 37751: POKE a, PEEK (a+9): NEXT a



--------------------------------------

Playing the in-game tune at half-speed

--------------------------------------



Usually, each note is held for two time-frames, due to the following code which converts the time-frame counter (0 to 255) to the number of the note to play (0 to 63, two time-frames each):



34881: AND 01111110

34883: RRCA



To hold each note for four time-frames, I replaced the above code with the following:



34881: CALL 37761



37761: AND 11111100

37763: RRCA

37764: RRCA

37765: RET



To reuse this subroutine:

1. Copy 34881-34883 (3 bytes) to your game.

2. Copy 37761-37765 (5 bytes) to your game.



----------------------------

Colouring vertical guardians

----------------------------



The standard Manic Miner engine ORs the colour-attribute of a vertical guardian with the background colour-attribute - not the INK; just PAPER, BRIGHT and FLASH. So if you try to have a red-paper vertical guardian on a blue-paper background, it will actually be a magenta-paper vertical guardian because 010 OR 001 = 011.



The following subroutine is used to colour vertical guardians in Manic Miner:



36447: LD (HL),A    ; write VG colour-attribute to screen-buffer

36448: LD A,(32800) ; get background colour-attribute of current room

36451: AND 11111000 ; ignore INK (lower three bits)

36453: OR (HL)      ; OR VG colour-attribute with background colour-attribute

36454: LD (HL),A    ; colour top-left character-square (again)

36455: LD DE,31

36458: INC HL       ; one character-column to the right

36459: LD (HL),A    ; colour top-right character-square

36460: ADD HL,DE    ; one character-row down (+32), one column left (-1)

36461: LD (HL),A    ; colour middle-left character-square

36462: INC HL

36463: LD (HL),A    ; colour middle-right character-square

36464: ADD HL,DE

36465: LD (HL),A    ; colour bottom-left character-square

36466: INC HL

36467: LD (HL),A    ; colour bottom-right character-square

36468: RET



This subroutine is called by the Skylab code...



36583: CALL 36447



...by the vertical-guardian code...



36697: CALL 36477



...and jumped-to from the Eugene code (but we're not going to bother about that because Eugene has black PAPER, so we /do/ want him to inherit the background paper-colour).



MM:N-AT bypasses the ORing of the VG colour-attribute with the background colour-attribute by entering the above subroutine at 36454:



36583: CALL 36454 ; in Skylab code



36697: CALL 36454 ; in vertical-guardian code



These changes can be applied to the standard Manic Miner engine (Bug-Byte edition, NOT the Software Projects edition) with the following POKEs:



POKE 36584,102 (Skylabs)

POKE 36698,102 (vertical guardians)



-------------------------

Room 19 swordfish-routine

-------------------------



On completing Room 19, unless the "6031769" teleport-mode is enabled,* the portal turns into a swordfish, the player is drawn three columns above the swordfish, and the player's legs are colour-erased from the column below the portal. The swordfish-routine in the original Manic Miner is as follows:

---

* a restriction which can be circumvented using POKE 36924,0



36912: LD A,(33882) ; if DEMO

36915: OR A         ; = 0

36916: JP NZ,37008  ; then jump to 37008 (normal cavern-completion routine; restart at Room 0)

36919: LD A,(33885) ; if CHEAT

36922: CP 7         ; = 7 ("6031769" teleport-mode enabled)

36924: JR Z,37008   ; then jump to 37008 (normal cavern-completion routine; restart at Room 0)

36926: LD C,0       ; no collision-detection when sprite-drawing subroutine is called

36928: LD DE,33376  ; address of Sprite 3 of player (facing right, rightmost frame)

36931: LD HL,16467  ; pixel-address of character-square (2,19) in video-RAM

36934: CALL 36852   ; call sprite-drawing subroutine

36937: LD DE,45792  ; address of swordfish-sprite (Special Graphic in Room 0)

36940: LD HL,16563  ; pixel-address of character-square (5,19) in video-RAM

36943: CALL 36852   ; call sprite-drawing subroutine

36946: LD HL,22611  ; colour-address of character-square (2,19) in video-RAM

36949: LD DE,31

36952: LD (HL),47   ; set colour-attribute at (2,19) to white ink on cyan paper (player above portal)

36954: INC HL

36955: LD (HL),47   ; set colour-attribute at (2,20) to white ink on cyan paper

36957: ADD HL,DE    ; one character-row down (+32), one column left (-1)

36958: LD (HL),39   ; set colour-attribute at (3,19) to white ink on green paper

36960: INC HL

36961: LD (HL),39   ; set colour-attribute at (3,20) to white ink on green paper

36963: ADD HL,DE

36964: INC HL

36965: ADD HL,DE

36966: LD (HL),69   ; set colour-attribute at (5,19) to cyan ink on black paper, BRIGHT 1 (swordfish)

36968: INC HL

36969: LD (HL),69   ; set colour-attribute at (5,20) to cyan ink on black paper, BRIGHT 1

36971: ADD HL,DE

36972: LD (HL),70   ; set colour-attribute at (6,19) to yellow ink on black paper, BRIGHT 1

36974: INC HL

36975: LD (HL),71   ; set colour-attribute at (6,20) to white ink on black paper, BRIGHT 1

36977: ADD HL,DE

36978: LD (HL),0    ; set colour-attribute at (7,19) to black ink on black paper (erase player's legs)

36980: INC HL

36981: LD (HL),0    ; set colour-attribute at (7,20) to black ink on black paper



MM:N-AT modifies the above code to:

* not draw the player above the swordfish;

* change the position of the `swordfish' from (5,19) to (6,21);

* edit the colour-attributes of the `swordfish';

* erase not only the two squares directly below the portal, but also the square to the left of them.



The altered code is as follows:



36928: NOP

36929: NOP

36930: NOP

36931: NOP

36932: NOP

36933: NOP

36934: NOP

36935: NOP

36936: NOP

36937: LD DE,45792  ; address of swordfish-sprite (Special Graphic in Room 0)

36940: LD HL,16597  ; pixel-address of character-square (6,21) in video-RAM

36943: CALL 36852   ; call sprite-drawing subroutine

36946: LD HL,22741  ; colour-address of character-square (6,21) in video-RAM

36949: LD DE,31

36952: LD (HL),66   ; set colour-attribute at (6,21) to red ink on black paper, BRIGHT 1

36954: INC HL

36955: LD (HL),70   ; set colour-attribute at (6,22) to yellow ink on black paper, BRIGHT 1

36957: ADD HL,DE

36958: LD (HL),66   ; set colour-attribute at (7,21) to red ink on black paper, BRIGHT 1

36960: INC HL

36961: LD (HL),70   ; set colour-attribute at (7,22) to yellow ink on black paper, BRIGHT 1

36963: ADD HL,DE

36964: DEC HL

36965: JR 36975     ; minimise possible effects of MM-editors overwriting 36967/70/73 ;-)

36967: NOP

36968: NOP

36969: NOP

36970: NOP

36971: NOP

36972: NOP

36973: NOP

36974: NOP

36975: LD (HL),64    ; set colour-attribute at (8,20) to black ink on black paper, BRIGHT 1

36977: INC HL

36978: LD (HL),64    ; set colour-attribute at (8,21) to black ink on black paper, BRIGHT 1

36980: INC HL

36981: LD (HL),64    ; set colour-attribute at (8,22) to black ink on black paper, BRIGHT 1

