Jump to content


Member Since 23 Aug 2015
Online Last Active Jul 23 2019 09:00 PM

#11277 [File] Jet Set Willy - Mark Woodmass's fast version

Posted by IRF on 23 July 2019 - 02:13 PM

Further to the above, the Z80 Heaven website which I quoted makes no distinction between the number of T-States for LD HL, (xx) or LD IX, (xx)


hl, (XX) 20 ix, (XX) 20 iy, (XX) 20 (XX), hl 20 (XX), rr 20 (XX), ix 20 (XX), iy 20


Clearly the source I quoted is wrong (and Norman's source was right), because the shift byte inevitably slows down the operation (by 4 T-States).


The Z80 Heaven site also fails to give a T-State value (correct or otherwise) for LD rr, (XX) operations e.g. LD BC, (XX).  Which presumably does take 20 T-States because again there's a shift opcode involved.  (EDIT: Another source confirms this: http://z80.info/z80flag.htm )





An unrelated query: I've often wondered? why the operand of relative jumps is sometimes given in the format &4546


e.g. at http://www.z80.info/z80oplist.txt you see these examples:


10 DJNZ &4546

18 JR &4546


#11276 [File] Jet Set Mini

Posted by IRF on 19 July 2019 - 07:11 AM

I forgot to add: the hidden message disappears when you exit from The Nghtmare Room of course, because the status bar's pixels are all wiped clear by the Room Setup routine upon entry to another room. But walk back into the Nightmare Room and the message will re-appear [unless you paused the game in the intervening period], because the status bar's attributes are NOT cleared by the Room Setup routine!

#11273 source code for JSW

Posted by IRF on 18 July 2019 - 12:43 PM

I've just noticed that the two commands in the raster copy routine which you previously struck through:


  ld b,0                         ; this was set for usage in a different routine

  ld c,32                             ; this was set for usage in a different routine


are no longer struck out in posts 59 and 60.  Although the accompanying comment after the semicolon remains in place, suggesting that those commands are still not necessary (and from my interpretation of the code, they aren't needed).


Perhaps the strikethrough font 'fell off' in the process of copying and pasting?




The LDI subroutine in post 60 gets around the problem in the 'Lose a Life' routine (#8C01), whereby the screen attributes are updated repeatedly on a loop, whilst the A register is used to count down the INK colours (the 'fade to black' moment). :)

#11269 source code for JSW

Posted by IRF on 18 July 2019 - 07:19 AM

I suppose you could modify this routine to use a similar raster copy routine as the one posted above.

Surely the raster copy code would have to revert back to your earlier method of using the Accumulator to keep track of when it is time to copy a row of attributes. Because if you are using the shared (and not self-modified) subroutine Shift_block32, then that subroutine always RETurns with the same conditionality of the Overflow Flag.

e.g. use the same code as in this post, only replacing the two LDIR commands with CALLs to Shift_block32:

#11268 [File] Jet Set Mini

Posted by IRF on 17 July 2019 - 11:53 PM

3 - The implications for the most efficient route through the layout

As I recall Danny, your preferred route through a standard Jet Set Willy 'mansion layout' (which this game has, broadly speaking, albeit laterally inverted) leads you to complete The Nightmare Room last of all, before making your way to the Master Bedroom for the Toilet Dash.

Now, in Jet Set Mini, there is a double-height 'periscope' guardian in 'First Landing', which you can jump over from a high vantage point on the First Landing ramp in order to enter The Nightmare Room. But you can't jump back over the periscope if you exit from The Nightmare Room into First Landing. So that is - apparently - a one-way route, which appears to force you to go from The Nightmare Room via a circuitous route through The Banyan Tree, down the Back Stairway and through the Kitchen screens and the Main Stairway.


#11267 [File] Jet Set Mini

Posted by IRF on 17 July 2019 - 11:43 PM

2 - How the feature which the hidden message is alluding to is implemented;

The wall in The Nightmare Room appears to be entirely solid.  However, upon entry to the room, a Room Setup Patch overwrites part of the wall with a different attribute value to that assigned to the rooms' Earth blocks, creating a pair of 'pseudo-Earth cells'.
You can see the same process going on in the adjacent room 'The Banyan Tree', where there is another 'pseudo-Earth cell' (just under the ramp and above/to the left of the Barrel sprite).  Note that this 'pseudo-Earth cell' has the same pixel pattern as the regular Earth cells, but a different colour attribute, meaning that Willy can pass through it [in either direction; not just because of the asymmetrical test for head height Earth blocks] - bear in mind that Water cell behaviour is the default status of all non-standard room blocks.
However, whilst the pseudo-Earth cell in The Banyan Tree is visibly a different colour to the rest of the Earth cells, that is not the case in The Nightmare Room.  Because in that room, there is also a Main Loop Patch which colours the two pseudo-Earth cells (at the level of the secondary attribute buffer) back to the same colour as the regular Earth cells!  So there is no visible difference between any of the Earth cells.  (The pixel patterns being identical because the pseudo-Earth cells started off as regular Earth cells, and the Room Setup Patch is implemented after the room data has been decompressed and the pixels for the block types have been expanded out.)
But these pseudo-Earth cells behave differently, in terms of their ability to block Willy from passing through them [or in this case, their lack of being able to do so - Water cell behaviour being the default status of all non-standard room blocks], because the Main Loop Patch is implemented after the 'Move Willy' routine has been CALLed from the Main Loop.  So at the moment when 'Move Willy' is executed, these cells hold a non-Earth attribute value 'behind the scenes', which is then overwritten by the Earth colour attribute prior to the physical screen update (so what you see, apparently, is regular Earth blocks).

And then during the start of the next pass through the Main Loop, the cells in question are restored back to their default Water behaviour, when the primary attribute buffer is copied to the secondary attribute buffer (only for those cells to be 'disguised' again, after the next execution of 'Move Willy', by the Main Loop Patch.  And so on.)


All of the above led me to include this credit in the Readme file for 'Jet Set Mini':

- Stuart Brady, for his Cell-Graphics Bug Fix, and for his helpful explanation of the way that the various 'screen buffers' work in the JSW game engine (primary and secondary buffers, for both the display and attribute files). A deep insight into how the screen buffers operate was essential when devising many of the Patch Vector effects in 'Jet Set Mini'.


EDIT: You may also notice that there is another 'disguised' cell in 'The Banyan Tree': the cell underneath the uppermost item is a 'pseudo-Water cell' (it has the same pixel pattern as the regular Water cells, but with a different colour attribute) - but here it unexpectedly behaves as an Earth cell!  At the level of the primary attribute buffer, this cell has the same red/yellow colour attributes as the Earth blocks (thanks to a Room Setup Patch), but that is then overwritten by the Main Loop Patch, giving it the cyan colour you see that distinguishes it from the regular, darker blue colour of the Water cells in the Banyan Tree.

#11266 [File] Jet Set Mini

Posted by IRF on 17 July 2019 - 11:11 PM

There are three things left for me to explain (at a later date):


1 - How the 'hidden' message is implemented (and why it is only visible sometimes);


Explanation for Point 1 above:


The message in 'The Nightmare Room' is printed across the top of the status bar, upon entry to the room.  However, by default, the top character row of attributes for the status bar (addresses #5A00-#5A1F) hold the value zero.  So the message is printed in black INK on black PAPER, and thus is normally invisible.




Now, you may recall that in the Kitchen diptych of rooms, a flickering screen effect takes place - as you climb up through that pair of rooms, the screen flickers first in yellow, and then in red (it's hot in the kitchen, and heat rises!)


That flickering effect is implemented by overwriting the physical screen attributes (addresses #5800-#59FF) with yellow or red INK during each game tick.  (As opposed to the 'standard' screen flash [such as you see in Manic Miner when you gain an extra life], which is implemented by overwriting the secondary attribute buffer (addresses #5C00-#5DFF) before the secondary buffers are copied across to the physical screen.)


However, when I wrote the patch for the Kitchen screens, I made sure that the overwriting of the screen attributes 'overshoots' the range of addresses that you would expect - instead of a LDIR length of #0200 (or rather #01FF) for the sixteen character rows of attributes of the playable screen, I used BC = #021F for the length of the LDIR loop, so it covers seventeen character rows.  This causes the yellow or red INK also to be spread across addresses #5A00-#5A1F - although this is not noticeable in the Kitchens (because nothing is printed across that character row's pixels in that room).


Now, unlike the top two-thirds of the screen attributes during the Kitchen flickering, which are all refreshed during the next tick of the game, the overwriting of the top character row of the status bar's attributes is semi-permanent.  Therefore if you climb up through the Kitchen screens, emerge in the Banyan Tree and then proceed into The Nightmare Room, then the previously invisible message in that room becomes visible!  (In red INK - although if you climb halfway up the Kitchen screens until the yellow flickering occurs, but then abandon that route and instead make your way back up the Main Stairway to approach The Nightmare Room from the other direction, then you can actually see the message printed in yellow INK!)




Note that I said the change in colour of the top character row of the status is semi-permanent.  If you abandon the game and start another game, then the black INK of that character row will be restored and so the Nightmare Room message reverts to invisible.  The same is true if you pause and unpause the game ('in-game', so to speak, using keys A-G), since the Main Loop compensates when you come out of a pause for the colour-cycling which takes place during the pause, by restoring the attributes of the whole of the status bar back to their default values. (See #8B07-#8B11.)


So if you try pausing and then resuming the game in The Nightmare Room at a time when the 'hidden' message is initially visible, you'll notice that the message promptly disappears - even if you don't actually pause for long enough for the colour-cycling effect to begin.

#11251 source code for JSW

Posted by IRF on 17 July 2019 - 09:50 AM

So Norman's code (featuring only one CALL and RET) is certainly faster than my version!


Compare and contrast (for each pass through the Main Loop):


Primary to secondary pixel buffer = 128 raster lines

Primary to secondary attribute buffer = 16 character rows


So my method uses 144 CALLs and RETs.

Norman's method only requires 2 CALLs and RETs (for the pixel loop and for the attribute loop).


Unconditional CALL = 17 T-States

Unconditional RET = 10 T-States


So the difference in T-States is 142 x 27 = 3888 T-States (the amount by which Norman's method is faster than mine).



[There should be no difference in terms of the copying of the secondary buffers to the physical screen, because the Jagged Finger fix means that the data isn't copied contiguously (in terms of the way that it is stored in memory).  So there are separate CALLs to the subroutine for each individual raster line, in both Norman's and my method.]




However, that 3888 is only a modest difference when you compare it with the overall saving achieved by abandoning LDIR in favour of the 32-consecutive-LDI method.  Norman worked out that copying the pixels (4096 bytes) between buffers is faster by 22528 T-States.  For the 512 bytes of attributes across 16 character rows of the playable screen, there is an additional saving of 2816 T-States.


So the total saving (per Main Loop pass) achieved is 25344 T-States before you account for the time taken to perform CALLs and RETs.

#11249 source code for JSW

Posted by IRF on 17 July 2019 - 07:18 AM

My thinking is that the number of T-States which it takes to perform a relative jump is proportional to the distance through the code which has to be jumped - 67 bytes in Norman's case, and only 5 bytes in mine.


On reflection, my variant might not be faster after all - my subroutine is CALLed #10 or #80 times during every pass through each part of the Main Loop that performs a block copy operation.

The number of T-States for that many CALL/RET commands (versus just one CALL/RET in Norman's code) may well outweigh the saving in T-States achieved by shortening the length of the relative jump!


Further investigation is required...

#11248 source code for JSW

Posted by IRF on 16 July 2019 - 11:06 PM

A query [EDIT: Which I think I've answered myself in subsequent posts!]:

In the Main Loop of a recent project, I have this arrangement (repeated four times, for copying pixels twice and attributes twice - primary to secondary buffer and then buffer to physical screen):


LD HL, source

LD DE, destination

; No need to define BC; it's not used now, so LD BC, xxxx command has been deleted

LD A, #80 or LD A, #10 ; For copying the pixels (128 raster lines) or attributes (16 character rows) respectively
CALL subroutine
JR NZ, loop
; Once A reaches zero, flow of execution continues through the Main Loop

The subroutine which is CALLed consists of 32 consecutive LDI commands, followed by a RET.

This was obviously based on one of Norman Sword's suggestions (duly credited in the readme file for the project in question). However, there is a slight difference - Norman's subroutine incorporates the DEC A and JR NZ commands (after the final LDI and before the RET), whereas in my version, those commands are located in the Main Loop.

In terms of memory, Norman's version is obviously more efficient (because I have to repeat the DEC A and JR NZ commands four times within the Main Loop, rather than just once in Norman's subroutine).

However - and here is my query - would my version be slightly faster? [I don't mean the game as a whole - Norman has done lots of other things to speed up the game - I mean purely in terms of comparing the two variants of the LDI method like-for-like.]

My thinking is that the number of T-States which it takes to perform a relative jump is proportional to the distance through the code which has to be jumped - 67 bytes in Norman's case, and only 5 bytes in mine.



N.B. My method may complicate things in cases where a chunk of code is being overwritten with a single value - where the first byte is overwritten directly and then the number of bytes to which the same values is to be copied in a loop is [size of chunk of code] minus one. e.g. for attribute update with a single value (such as for a screen flash effect), use #01FF instead of #0200 to define the size of the loop.

Norman's code deals with such cases by CALLing a late entry point into his subroutine, coinciding with the second LDI command in the subroutine. (But the JR NZ at the end of the subroutine jumps back to the first LDI in the subroutine.)

In such cases, I think my method would unavoidably end up 'overshooting', and overwriting one more byte than it should. (But in the aforementioned project, I didn't actually use an LDI method for 'block fill' purposes, only for 'block move'.)

EDIT: For reference:

Note also my comment/query here about a couple of presumed typos:

#11245 [File] Jet Set Mini

Posted by IRF on 16 July 2019 - 04:21 PM

That's a good thought! Yes there are enough clues scattered about now, possibly erm 'trim' any posts a little bit too if you think its warranted.


It's okay, I don't mind leaving the clues!


By the way Andy, did you try to solve the rest of the puzzle yet (without watching Danny's recording)?

#11243 [File] Jet Set Mini

Posted by IRF on 16 July 2019 - 10:55 AM

Anyway, I'm glad you both enjoyed the puzzle!


There are three things left for me to explain (at a later date):


1 - How the 'hidden' message is implemented (and why it is only visible sometimes);

2 - How the feature which it is alluding to is implemented;

3 - The implications for the most efficient route through the layout (Danny may figure this out for himself!)

#11242 [File] Jet Set Mini

Posted by IRF on 16 July 2019 - 10:49 AM

Since Andy has now grabbed Danny's recording, I've taken the liberty of removing it from his post.  I'd rather it wasn't freely available as it's a bit of a giveaway when the sole focus of the recording is that manoeuvre.  (I don't mind the manoeuvre being shown in the context of a complete walkthrough of the whole game to submit to the RZX Archive, where it would be 'buried in the mix'.)

#11240 [File] Jet Set Mini

Posted by IRF on 16 July 2019 - 08:41 AM

Not yet ;) I'll take a look at this later today


I think you've missed a whole page of 'developments' there, Andy!

#11238 [File] Jet Set Mini

Posted by IRF on 16 July 2019 - 07:27 AM

OK, I couldn't sleep and so I had a go at it and I found it :D

The attached recording shows it:

Don't watch the recording if you prefer to find the exit yourself, Andy! It's not difficult, to be honest, once you know you need to look for it somewhere in this room.

Well done on completing the third and final part of the puzzle, Danny!

Hopefully Andy will also be able to independently perform this feat before he watches your recording. (Another little clue:

A more difficult task to perform, when time permits, will be to meditate on how that affects the most optimal route through the game...

It's more a case of 'getting you back on track' with your usual preferred route.

Anyway, thanks for this little mystery, Ian, and for leading us to the solution! :) :thumbsup:

You're welcome! :)