Jump to content
Jet Set Willy & Manic Miner Community

Free space and code optimisation in "JSW"


jetsetdanny

Recommended Posts

Yes, since I wrote my post last night I was playing around in one of your earlier test files, and I noted that the 'sinking into the wall' didn't occur as Willy walked along the top of the 'parapet' wall block towards the bottom of the ramp in 'On top of the house', so I assumed you must have also altered the 'Draw Willy' code in some way (not necessarily in the same way that I suggested above).

Edited by IRF
Link to comment
Share on other sites

For clarity on your code it would benefit from a few additions regarding the usage of bit 7 (direction). This bit is being toggled in several places, with no mention as to why.  e.g.

 

          XOR #80        ;restore direction (bit 7)  which was inverted

 

         XOR #E0         ; invert direction (bit 7) and invert animation bits (6 and 5)

 

It could appear to be an unnecessary duplication by toggling Bit 7 twice (which would leave it back where it started), but if you consider the possible paths through the code, this doesn't always occur.

 

The XOR #80 is used when the animation frame has been incremented/decremented beyond the range of appropriate values for the current direction of travel (i.e. to bring it back into the correct half of the sprite-page).

 

The XOR #E0 is used at the point when the horizontal guardian changes direction.  It's effectively doing two things simultaneously: a XOR #80 (to swap to the other half of the sprite-page), and a XOR #60 (to toggle between left-most and right-most sprite-frames, without which the turnaround doesn't occur as smoothly).

 

 

and finally for clarity , an explanation of what the carry bit is being used for.

 

; the direction bit (bit 7 = #80) must be taken into account

;animation right adding #20      #80, #a0, #c0, #e0    carry clear with each addition but not  #eo+#20  = #00

;                left   adding #e0       #60, #40, #20, #00   carry set with each addition but not      #00+#eo  = #eo

 

You've hit the nail on the head.  In the original code, the conditionality of the Carry Flag happens in the same way for both direction, but that's because left and right movements are handled via a SUB #20 and an ADD #20 respectively.  But here we're using the same operation (an ADD), but changing the operands for left (#E0 = - #20 in two's complement) and right (#20 = +#20 in two's complement).  Which means that the effect on Carry is polar opposite for left and right motion, so we have to use a CCF.

Edited by IRF
Link to comment
Share on other sites

However, the following method eliminates the need to Complement the Carry Flag.

 

Starting at #9133 (changes from previous code highlighted in bold):

 

      PUSH IX   ; copy the address of Byte 0 of the guardian definition
      POP HL    ; to the HL register-pair

 

      LD DE,#E0FF     ; parameters for a

      LD B A,(IX+$06)    ; guardian moving left

      BIT 7,(HL)

      JR NZ, left

      LD DE,#2001     ; parameters for a 

      LD B A,(IX+$07)    ; guardian moving right

 

left:

      LD B,A

 

      LD A,(HL)

      ADD A,D                 ; increment animation-frame of guardian

      BIT 7,(HL)               ; sets the Zero Flag for a left-moving guardian

      LD (HL),A                ; update guardian definition Byte 0 with correct animation-frame

      JR Z,carry_okay

      CCF                        ; Complement the Carry Flag for a right-moving guardian

 

carry_okay:

      JP PO,#91B6            ; not time to update the guardian's x-coordinate; proceed to Move the next guardian

 

If we get here then the Overflow Flag has been set (for both directions of travel)

 

      XOR #80

      LD (HL),A               ; time to update the guardian's x-coordinate, so reset the animation-frame to the first one for the next cell-column

 

      LD A,(IX+$02)        ; guardian definition byte 2

      LD C,A                   ; temporarily save in the C register

      AND #1F                ; extract guardian's current x-coordinate

      CP B                       ; has the guardian reached the extremity of its range in the current direction of travel?

      JR Z,turn_around   ; if so, jump to reverse its direction of travel

      LD A,C                   ; retrieve guardian definition byte 2

      ADD A,E                ; adjust x-coordinate

      LD (IX+$02),A        ; and save

      JR #91B6               ; proceed to Move the next guardian

 

turn_around:

      LD A,(HL)               ; we're not updating the x-coordinate after all,

      XOR #E0                ; instead we are switching to consider the other set of four animation-frames

      LD (HL),A               ; [assuming it's an eight-frame bidirectional sprite; if not, then filtering out of unwanted frames is done at the 'Draw the guardians' stage]

      JR #91B6               ; proceed to Move the next guardian

 

[#9166 to #917E is now free - that's four more bytes than with previous method]

 

 

This effectively reverses the status of Bit 7 of the guardian definition Byte 0, so that Bit 7 cleared = Moving Right, Bit 7 set = Moving Left.

 

;right adding #20      #00, #20, #40, #60    The Overflow Flag is clear with each addition but not #60+#20 = #80 (which is minus #80 in two's complement)
;left   adding #E0 (minus #20)       #E0 (minus #20), #C0 (minus #40), #A0 (minus #60), #80 (minus #80)   Overflow clear with each addition but not #80+#E0 = #60

 

 

N.B.  You would now need to adjust all the horizontal guardians' data as follows:

 

- Transpose all the half-pages of the eight-frame bidirectional horizontal sprite (e.g. put right-facing Monk at #B400-#B47F, and left-facing Monk at #B480-#B4FF), so that their sprite-frames are arranged within the data in the same way as Willy's - if you don't do this, then they'll all walk backwards!;

 

- Toggle Bit 7 of Byte 0 for all the horizontal guardian classes (i.e. all the relevant entries in pages #A0-#A3), in order to preserve the initial direction of travel upon Willy's entry to each room (e.g. for the green rolly thing in The Bathroom, change the value of #A1D8 from #01 to #81).

 

 

All of the above is probably not worth the bother just to save another four bytes in the 'Move the guardians' routine.  (Although it will save a few more bytes in the 'Draw Willy' routine as well, because there's no longer a need to swap half-pages when using the Flying Pig as the playing sprite in The Nightmare Room.)

 

It was more of an interesting academic exercise to me, and it allowed me to gain a better grasp of the functioning of the Overflow Flag.  :)

Edited by IRF
Link to comment
Share on other sites

Incidentally, the aforementioned Overflow Flag also doubles up as the Parity Flag.  I have wondered for some time about a possible viable use for the Parity Flag.  And I've now discovered one, which relates to a possible efficiency for one of Dr Andrew Broad's patches from his 'Advanced MM/JSW Trainer' project.

 

From the accompanying TECHNICA.txt document for that project:

 

 


-------------------
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

 

The three commands in bold above (six bytes) can be replaced with a three byte jump JP PE,skip which responds to the conditionality of the Parity Flag (thus saving three bytes).

 

N.B.  In this circumstance (after the AND #21 gate), the Parity Flag is set if a cell's row and column numbers are both even, or if they are both odd.  If the row number is even but the column number is odd, or vice versa, then the Parity Flag is cleared.

Edited by IRF
Link to comment
Share on other sites

An arrow's y-coordinate can be determined on an individual arrow-instance basis, via its specification byte 1 (which is copied to definition byte 2 in the current room's guardian buffer).

 

However, Bit 0 of that specification byte is effectively unused - it must be left clear, or else the arrow when drawn will corrupt the game file at run-time (because erroneous addresses are looked up from the table at page #82 of the code - that's what caused the infamous Attic Bug).

 

In the arrow-drawing code, the following command picks up Byte 2 of the arrow definition as follows:

 

LD E,(IX+$02)

 

If that were to be replaced with:

 

LD A,(IX+$02)

AND #FE

LD E,A

 

then this would prevent any accidental occurrences of 'Attic Bug' style problems caused by an odd value being assigned for an arrow's y-coordinate.

 

Furthermore, Bit 1 of the arrow's y-coordinate specification byte could then be used for other purposes. (There are plenty of other unused entries in an arrow's definition, but those are all set on an arrow class basis, so they would hold the same values for all left-moving or all right-moving arrows in the original game.)

 

As an example, you could introduce a check, just before making the arrow warning sound when the arrow is about to enter the current room - if Bit 0 of the arrow's definition Byte 2 is set, then bypass the code which generates the firing noise. So in some rooms, Willy gets an audible warning of an impending arrow, but in other rooms he does not!

Link to comment
Share on other sites

Re arrows:-

 

The arrows, my first play with them introduced a second arrow into the same guardian slot.

 

Mentioning wasted bits I decided to implement something I had thought about concerning the arrows.

 

This file shows what the wasted bits can do. Only demonstrated in the bathroom. 

 

This arrow demo, is tagged onto a file that keeps on being added to. (it needs deleting)

 

 

mega volly.tap.tap

Link to comment
Share on other sites

The seven empty slots. Which because of the code that double up arrows, gives fourteen arrows in that room. Of note it only uses ONE guardian definition, the others are defined by the position. E.g. the code below is all it takes in the room set up.

 

But first a quick run down of my thoughts...

 

bit 0 gives the arrow bug

.
but just taking the first 16 values and removing that bug leaves values 00,02,04,06,08,10,12,14

.

we also have a feature/bug that stops the code working on the extremes of chars, so values of 00 or 14, should also be avoided.
leaving 02,04,06,08,10,12 = 6 values from the first 16.  or for the full screen playing area 16*6 values. 96 values from 256 in total.

.

I fail to see why this is used. If the arrow had been confined to a fixed position within the char. Then only four bits are needed to define an arrow.
The other four bits could be used for any multitude of applications.

.

At present an arrow is defined by a fixed guardian. If a JSW2 style volley is wanted then each arrow would need its own guardian slot/definition.

.

By juggling those four bits a shift can/could be introduced into the fixed timing phase of the arrow. (done at room set up)
This then permits one guardian to be defined as having up to 16 sub classes, just by allowing a phase change on just the one guardian definition.

.

patched into the room set up, after testing for an arrow type guardian. With no attempt at reducing the code size.

 

ld a,(iy+arrow_y)
ld b,a
and 15
add a,a
add a,a

add a,(iy+arrow_x)
ld (iy+arrow_x),a
ld a,b
and $f0
add a,8  ;middle of char
ld (iy+arrow_y),a

Edited by Norman Sword
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.