Dr. Andrew Broad
Sinclair ZX Spectrum
Manic Miner/Jet Set Willy

24th April 2007: updates in red below.

I have written Java programs MirrorMM and MirrorJSW which perform lateral inversion on a given MM/JSW game. I must thank Philip Bee for the idea.

I plan to release these programs as part of a future version of SPECSAISIE.

I have now released the results of running these algorithms on the original MM and JSW - reniM cinaM and ylliW teS teJ - as part of the Party Willy box-set. It's most intriguing how mirroring affects the atmosphere of these games! :-)

My goal for MirrorMM and MirrorJSW has been to produce an automatic transformation that is:

  1. fully automatic, requiring no post-editing (MirrorMM) or only slight post-editing (MirrorJSW);
  2. reversible, i.e. MirrorMM(MirrorMM(x)) = x, and MirrorJSW(MirrorJSW(x)) = x.

My goal is not for these transformations to hack the game-engine to bits at the risk of losing generality and reversibility. MirrorMM and MirrorJSW mostly just edit `data' (including instruction-operands) and leave the `code' alone (although MirrorMM does NOP out the CPL instruction at 36259 - or reinstate it in the case of a reverse transformation - which is necessary to mirror solar power correctly). But I have resisted the temptation to plant code-extensions such as a custom font (for mirror-writing), or to fix the Block-Graphics Bug that sadly afflicts reniM cinaM.


MirrorMM simply works through the room-data, plus the Room-19 picture (if Room 19 is to be mirrored - both programs can mirror an entire game, or just one room or a contiguous subrange of rooms).

MirrorMM also performs the following mirroring operations which are not room-specific:


MirrorJSW uses mark-sweep algorithms to mirror the guardian-classes referred to in the rooms, and the sprite-pages referred to in the guardian-classes. Mark-sweep ensures that each guardian-class and each sprite-page will be mirrored at most once, since the mirroring is performed only after the marking is completed.

Whenever a guardian-class is referred to, it is marked as `to mirror' (the program maintains an array of 127 Booleans for this purpose). Whenever a sprite-page is referred to, it is marked as either to mirror as horizontal sprites, or as vertical sprites (the program maintains an array of 256 integers), with horizontal sprites taking precedence whenever a sprite-page is used for both horizontal and vertical guardians.

After initially trying separate routines to mirror bidirectional and unidirectional HG sprite-pages, I decided to use the bidirectional routine for all horizontal sprite-pages, toggling the start-sprite between 0 and 4 for unidirectional HGs. This handles almost all HGs properly, even the `disrespectful' monk in The Chapel!

A corollary of this decision is that I had to swap the two halves of not only horizontal sprite-pages, but also vertical sprite-pages, since some sprite-pages mix horizontal and vertical sprites. This handles almost all VGs correctly, a notable exception occurring in The Kitchen diptych (to fix this would involve fiddling with individual sprites and the animation-mask, which I couldn't be bothered with right now).

MirrorJSW also performs the following mirroring operations which are not room-specific:

The post-editing that the output of MirrorJSW requires is due to the fact that the JSW game-mechanics are asymmetrical (e.g. you cannot jump onto the ledges in laterally inverted versions of "The Wine Cellar" or "Ballroom East"), and also due to the fact that the bed is hard-wired to make Willy run to the right.

Email me 1