Map Format

The game world is stored across several distinct files called maps. These map files define the floors, walls, and ceilings of the world, along with other stationary structures like trees, pipes, and ice formations. Map files also contain a list of all actors that should be inserted into the world, and the starting position for each one. Everything the player encounters while progressing through the levels of each episode is specified in a sequence of map files.

Within the group files, individual maps are stored in entries named A1–11.MNI (for episode one), B1–10.MNI (for episode two), C1–10.MNI (for episode three), and BONUS1–6.MNI (two bonus levels per episode). There are 37 maps across all three episodes, 36 of them unique (A11 and B1 are identical).

Each map file contains the following sections:

  • Global variables, which control the backdrop, music, and other map-specific global parameters.
  • A list of actors to insert into the game world, along with the initial coordinates for each.
  • A two-dimensional grid of (almost) 32,768 map tiles, which defines the static architecture of the game world.

Global Variables

All of the map variables are stored in the first six bytes of the file. The format is as follows:

Offset (Bytes)SizeDescription
0hwordEncoded map variables. See next section.
2hwordWidth of the map, in tiles.
4hwordSize of the actor list, in words.

Map Variables Word

The map variables are packed into a single 16-bit little-endian word, with some values interpreted as boolean flags and others as integers:

Bit PositionSize (Bits)Description
0–4 (least significant bits)5Numeric backdrop ID (0–31).
51Rain flag. 0 = no rain, 1 = rain falls in empty map areas.
61Backdrop horizontal scroll flag. 0 = backdrop is fixed in the horizontal direction, 1 = backdrop scrolls horizontally with the world.
71Backdrop vertical scroll flag. 0 = backdrop is fixed in the vertical direction, 1 = backdrop scrolls vertically with the world.
8–103Numeric palette animation ID (0–7).
11–15 (most significant bits)5Numeric music ID (0–31).

Note: The parenthetical number ranges indicate the smallest and largest values that can be encoded in the map format. Not all expressible IDs are defined, and selecting an invalid ID may cause a read outside of array bounds and other unpredictable behavior.

Map Width and Height

The game world size is conceptually fixed at 32,768 tilesĀ² regardless of the actual dimensions, so the height of the map is constrained by the width. There are a limited number of width values that the game implements:

Width (Tiles)Height (Tiles)mapYPowerNotes
321,0245Not used; width is too small to fill the screen entirely.
645126
1282567
2561288
512649
1,0243210Not used.
2,0481611Not used; height is too small to fill the screen entirely.

Smaller factors of 32,768, while expressible in the map file format, are not implemented by the game and will not work correctly. Non-factors of 32,768 could also be expressed, and will certainly break things.

List of Actors

The actor list has a variable size depending on the number of actors actually present in the map. The list always begins at offset 6h in the map file, and continues for the number of words specified in the preceding map variable.

Each actor list entry has the following structure, repeated at three-word intervals until the total size has been read:

Offset (Bytes)SizeDescription
0hwordMap actor type. Further processing is required to differentiate “special” actors from “normal” ones. See next paragraph.
2hwordInitial X position, in tiles, relative to the west edge of the map.
4hwordInitial Y position, in tiles, relative to the north edge of the map.

The actor type read from the map file may represent either of the following:

  • Special actor: Includes Player Start Position, Moving Platforms, Mud Fountains, and Light Beams.
  • Normal actor: All other actor types typically encountered.

If the map actor type is less than 31, it is treated as a special actor and the type is used unchanged. If the map actor type is 31 or greater, it is treated as a normal actor and the type is decremented by 31 before insertion into the world.

Note: The X/Y coordinates always refer to the leftmost/bottommost tile of multi-tile actors. Some normal actors have a positive or negative “shift” value imposed on their initial X and/or Y positions. If a shift is defined for a particular actor type, the starting position is adjusted by the predefined number of tiles before insertion. The shift value is typically used to compensate for the width or height of an actor’s sprite when aligning it to a specific wall or ceiling coordinate.

Due to the limited number of fields available in the actor list format, it’s not possible to define any per-actor attributes or characteristics – all actors of a given type display the same way, take the same amount of damage, and behave identically.

Limitations

The game has a fixed amount of memory allocated for the different structures that comprise the game world (and scant error handling for some conditions). This means there are practical limits that a valid actor list should never exceed:

Map Actor TypeLimitWhat happens if limit is exceeded?
Player Start Position1Later Player Start Positions overwrite the earlier ones.
Moving Platform10Writes outside of array bounds, leading to memory corruption.
Mud Fountain10Writes outside of array bounds, leading to memory corruption.
Light Beam199Any additional Light Beams are ignored.
Normal Actor410Completely stops reading the actor list from the map file.

Map Tiles

Immediately following the end of the actor list, there are exactly 65,528 bytes of map tile data. This is interpreted as a two-dimensional array of 32,764 unsigned little-endian words in row-major order. Each of these words represents a single graphical tile of the game world, with the four tiles at the southeast corner of the map undefined. (The first tile in the array is at the northwest corner of the world, and the last tile is at the southeast corner.)

The bottom row of tiles is never shown on the screen and the player movement functions disregard anything present there. It is essentially a discarded row of garbage data which protects the undefined tiles from causing visual issues or odd movement behavior. Any “air” or passable tiles immediately above the garbage row may be fallen through to implement bottomless pits.

Weird Numbers

Each map contains exactly four fewer tiles than it should, for reasons that are only partially apparent.

In order to allocate a contiguous block of 65,536 bytes in memory, farmalloc() would need to be used due to the constraints of the underlying 16-bit system. It appears as though malloc() was used instead, which can only provide up to 65,535 usable bytes in a single call. That reduction in space knocked one tile off the total usable number.

The other three missing tiles, I have no idea about.

The map data is largely static; once a map tile has been read into memory it does not generally change. (There are a few specific actor types – mostly those that restrict movement or allow the player/actors to stand on top of them – that manipulate map tile values during gameplay, but these are relatively rare.) The attributes for each map tile value are defined in a separate file and specify how the player/actors interact with each tile during gameplay.

Depending on the numeric value of each map tile, it is drawn as either a solid tile (with no transparency) or a masked tile (with transparent areas where the backdrop shows through). Map tile values below 16,000 are treated as solid tiles, and values 16,000 or above are treated as masked tiles. Tiles with the same value will look the same, have the same tile attributes index, and behave the same way.

Masked Tiles

Masked tiles are relatively simple. First, a conversion is performed to convert the map tile value into a masked tile index:

[masked tile index] = ([map tile value] - 16000) / 40

The subtraction by 16,000 is necessary to remove the offset that differentiates the masked tiles from solid tiles. The division by 40 accounts for the in-memory alignment of the actual graphical data. (This is an implementation detail that is discussed in depth in the drawing chapter.) Map tiles in this range are always an even multiple of 40.

The map format allows up to 1,238 distinct masked tile indices to be expressed, but the game graphics only define 1,000 masked tiles. This means that the only indices that should be encountered in practice are 0–999, corresponding to map tile values 16,000–55,960.

Each masked tile graphic is drawn into the game world at the specified position. Transparent areas within a masked tile graphic are filled with the appropriate piece of the backdrop behind it.

Solid Tiles

Solid tiles work similarly to masked tiles, but they are packed more densely and there are more special cases. To convert the map tile value into a solid tile index:

[solid tile index] = [map tile value] / 8

The division by 8 accounts for the in-memory alignment of the actual graphical data. (Again, this is an implementation detail that is discussed in depth in the drawing chapter.) Map tiles in this range are always an even multiple of 8.

The map format allows 2,000 distinct solid tile indices to be expressed, and all of them are defined in the game graphics. Each solid tile index from 0–1,999 (map tile values 0–15,992) should do something, even if the result is not directly visible on the screen.

  • Indices 10–1,964 are typical cases, where each solid tile is drawn directly into the game world at the specified position.
  • Indices 1,965–1,986 are not directly used in any maps, however solid tiles of this type are dynamically created and removed by various actors during the course of the game. These generally display normally in the game world once created, although sometimes sprites are strategically positioned to hide the tiles from view.
  • Indices 1,987–1,999 are used in menu frames and text display areas, and never display in any part of the game world.
  • Indices 0–9 are never drawn by the game and the backdrop is shown in their place. Typically these are used to represent empty space or sky. Aside from their invisibility, these tiles have special meanings to the game:
Tile IndexDescription
0Empty space where the player and actors can move freely. If a Moving Platform is centered on this tile, it will halt indefinitely.
1Empty space. If a Moving Platform is centered on this tile, it will move north during its next tick.
2Empty space. If a Moving Platform is centered on this tile, it will move northeast during its next tick.
3Empty space. If a Moving Platform is centered on this tile, it will move east during its next tick.
4Empty space. If a Moving Platform is centered on this tile, it will move southeast during its next tick.
5Empty space. If a Moving Platform is centered on this tile, it will move south during its next tick.
6Empty space. If a Moving Platform is centered on this tile, it will move southwest during its next tick.
7Empty space. If a Moving Platform is centered on this tile, it will move west during its next tick.
8Empty space. If a Moving Platform is centered on this tile, it will move northwest during its next tick.
9Invisible tile which blocks southern movement. (Player/actors can walk and jump through the tile, but cannot fall through it.) Not directly used in any maps, however tiles of this type are dynamically created at the tops of Pedestal and Mud Fountain actors.