-
Notifications
You must be signed in to change notification settings - Fork 5
Maps
Maps are one of the most important and most complex subsystems in the game. They define the world that the player navigates.
Internally, the game sometimes refers to maps as "tracks", since this engine was originally developed for Diddy Kong Racing.
- List of maps
- Romlist files - defines placement of objects in a map
- WARPTAB.bin - defines warp points
There are five layers of map grids, which maps are placed on to create a game world. The only way to move between layers is via warps or scripted events.
- Layer -2: Deep underground; contains only the DarkIce Mines boss arena.
- Layer -1: Underground; contains a few caves.
- Layer 0: Surface; contains most maps.
- Layer 1: Surface; contains Drakor boss arena and ThornTail shop.
- Layer 2: Space; contains Arwing levels.
Since the grids do not contain any height information, the choice of layer for a map is arbitrary. Layers -1 and 1 are used to allow one map to be placed over/under another; layers -2 and 2 are most likely used to prevent the other layers from being too large.
Each map can be referred to by its Map ID or its Directory ID. The game uses these IDs in various places, which can be confusing. A table which gives the corresponding Map ID for each Directory ID is embedded in the executable.
The Directory ID is an index into a list of directory names. Several unused indices point to the name animtest
or to names of directories that are missing or empty. This directory contains the map's assets.
In many cases, such as save files, instead of specifying a map ID, the game specifies a set of global coordinates and a map layer.
Several coordinate systems are used by various parts of the game:
- Global Coordinates: Used by objects in RAM, the player's saved location, etc. These specify an exact location within a map layer, and are usually paired with a layer number.
- Global Grid Coordinates: Equal to Global Coordinates, but with X and Z each divided by 640 and truncated to integer. Y is often omitted.
- Map Coordinates: Used by romlist entries.
- Map Grid Coordinates: Equal to Map Coordinates, but with X and Z each divided by 640 and truncated to integer.
Each map's directory contains the following files: (Note the inconsistent use of case)
-
ANIM.BIN
,ANIM.TAB
: Animation data -
ANIMCURV.bin
,ANIMCURV.tab
: Animation curves -
MODELIND.bin
: A table mapping model IDs to indices intoMODELS.tab
-
MODELS.bin
,MODELS.tab
: Character and object models -
OBJSEQ.bin
,OBJSEQ.tab
: Object animation sequence data -
OBJSEQ2C.tab
: Assigns animation curves to object sequences -
TEX0.bin
,TEX0.tab
: Texture graphics (primarily for the map geometry) -
TEX1.bin
,TEX1.tab
: Texture graphics (primarily for character models) -
VOXMAP.bin
,VOXMAP.tab
: Voxel data, relates to camera, possibly unused
Each map contains a copy of every model, texture, and animation it uses. This improves loading times by reducing the amount of disc seeking needed.
Each map directory also contains at least one modXX.zlb.bin
file and corresponding modXX.tab
, where XX
is a number. These contain the map block models.
Several map directories also have unused tab
files. There are also several modXX.zlb.bin
and modXX.tab
files in the disc root, which seem to be unused. In the Kiosk Demo version, there are also modXX.bin
and modXX.lzo
files.
This file (and MAPS.tab
) contain information about each map's layout.
MAPS.tab
contains one entry for each map, indexed by map ID (not directory ID):
Offset | Type | Name | Description |
---|---|---|---|
000000 | s32 | infoOffset | Offset of map info |
000004 | s32 | blockTable | Offset of map block list |
000008 | s32 | rects1 | rects that somehow define visible regions |
00000C | s32 | rects2 | more rects |
000010 | s32 | rects3 | more rects |
000014 | s32 | rects4 | more rects |
000018 | s32 | listSize | a FACEFEED header which gives the size to allocate for the romlist |
Each field is an offset into MAPS.bin
.
- In the kiosk demo version, the romlist is in
MAPS.bin
following the FACEFEED header. In the final version, it's moved to an external file, with only the FACEFEED header left inMAPS.bin
. The header's uncompressed size tells the game how much memory to allocate before it loads the file. - The final version will still load a romlist from
MAPS.bin
if the external file is missing, but this is never the case. (TODO: check if any old romlists are left here.)
Location of a structure defining the basic map geometry:
Offset | Type | Name | Description |
---|---|---|---|
000000 | u16 | sizeX | Number of columns |
000002 | u16 | sizeZ | Number of rows |
000004 | u16 | originX | Which column is the origin |
000006 | u16 | originZ | Which row is the origin |
000008 | u32 | ? | |
00000C | u32[4] | ? | possibly related to rects |
00001C | s16 | nBlocks | XXX more info |
00001E | u16 | ? | possibly flags or padding |
The origin is used when placing this map on the global grid. For example if a map is at global coordinates (100,100), and its origin is (2,3), then its first block will be placed at global coordinates (98,97). There does not necessarily need to be a block at the origin, and the origin does not need to be within the specified rectangle.
Each map is made up of one or more blocks, each of which has its own model. Each block is 640x640 units on the X and Z axes, and variable in height.
MAPS.bin contains a list of blocks, which is sizeX * sizeZ
u32
values, each specifying one block:
unk1 = val >> 31; //probably unused
mod = (val >> 23) & 0x00FF;
sub = (val >> 17) & 0x003F;
unk2 = val & 0x01FF;
If mod
is 0xFF, there is no block here. Otherwise, if mod
is >= 5, add 1 to mod
. mod
is then used to identify the file the block is in: /%s/mod%d.bin
and /%s/mod%d.tab
, where %s
is the map's directory and %d
is the mod
number. Each modXX
file contains the block models, where sub
specifies which model to use.
The file TRKBLK.tab
specifies an offset for each map. This file is an array of u16
indexed by the map's directory ID. The offset found here plus the block's sub
value gives the index into the tab
file to use for this block.
Internally, the game refers to the files BLOCKS.bin
and BLOCKS.tab
. These files do not exist; the code that would read them instead reads the modXX
files.
A map's dimensions appear to be limited to a total area of 512 blocks (regardless how many blocks are actually present); it's not known how the Drakor boss map is able to exceed this. (see 0x80059248)
The tab
file works like other tab
files: it contains an array of u32
values in which the highest 8 bits are flags (meaning currently unknown) and the rest are an offset into the corresponding bin
file.
At the resulting offset in the bin
file is a ZLB archive containing the block model, which has a similar format to character models:
Of | Type | Name | Description |
---|---|---|---|
00 | u32 | unused00 | set from 80060b90 - XXX confirm unused |
04 | u16 | flags_0x4 | 40=need init hits? 1=toggled on render |
06 | ? | ||
08 | s32 | length | file size |
0C | Mtx43 | mtx | unsure; seems unused in file |
3C | ? | ||
4C | pointer | GCpolygons | invisible, used for hit detection; can be null |
50 | pointer | polygonGroups | related to hit detection; can be null |
54 | u32* | textures | texture IDs |
58 | vec3s* | vertexPositions | vertex coordinates |
5C | u16* | vertexColors | vertex colors (RGBA4444) |
60 | vec2s* | vertexTexCoords | vertex texture coords |
64 | Shader* | shaders | defines how to render polygons |
68 | DisplayListPtr* | displayLists | native display lists, referenced by render streams |
6C | LineHit* | linehits | |
70 | HitsBinEntry* | hits | data from HITS.bin, size from HITS.tab; 0 in file |
74 | ?32 | ? | set to 0 in initHits |
78 | BitStream* | renderInstrsMain | normal geometry |
7C | BitStream* | renderInstrsTransp | transparent geometry, glow effects |
80 | BitStream* | renderInstrsWater | reflective geometry, water |
84 | u16 | nRenderInstrsMain | stream size in bytes |
86 | u16 | nRenderInstrsTransp | |
88 | u16 | nRenderInstrsWater | |
8A | s16 | yMin | related to bounds |
8C | s16 | yMax | |
8E | s16 | yOffset | must be added to vertex Y positions |
90 | u16 | nVtxs | number of vertex positions |
92 | u16 | nUnk | guessed |
94 | u16 | nColors | number of vertex colors |
96 | u16 | nTexCoords | number of texture coords |
98 | u16 | nPolygons | number of GCpolygons |
9A | u16 | nPolyGroups | number of polygon groups |
9C | u16 | nHits | number of HITS.bin entries (0 in file) |
9E | ?16 | hitField_9e | set to 0 in initHits |
A0 | u8 | nTextures | number of textures |
A1 | u8 | nDlists | number of display lists |
A2 | u8 | nShaders | number of shaders |
A3 | u8 | probably nSomething or padding | |
A4 | char[11] | name | size guessed from OBJECTS.bin; not used; eg "mod6.12" |
AF | ? | total struct size = 0xB8 |
(some names inferred from debug messages)
The three BitStream fields point to bit-packed render instructions that render the block's model:
Each stream is a series of bit-packed opcodes and parameters (high bits first). Decoding is as follows: Read 4-bit opcode, execute, repeat.
Opcodes:
- 0: Unused, but does the same as 4
- 1: Select a texture and shader
- Read index (6 bits)
- Set the current shader to
block->shaders[index]
- 2: Call a display list
- Read index (8 bits)
- If the current shader doesn't have the "hidden" flag set (or there is no current shader), call
block->displayLists[index]
- 3: Change the vertex format for VAT 5 (map blocks) or 6 (character models)
- 1 bit: POS format (0:INDEX8, 1:INDEX16)
- Character models have 1 bit for NRM format here; blocks do not.
- if
(curShader.attrFlags & 2) != 0
:- 1 bit: COL0 size (0:INDEX8, 1:INDEX16)
- else, this field is not present (0 bits)
- 1 bit: TEX size (0:INDEX8, 1:INDEX16); applies to all enabled texture slots
- If no shader, only TEX0 is used; otherwise, number of slots =
curShader.nLayers
- disabled slots are set to format 0 (NONE)
- Character models always have shaders
- If no shader, only TEX0 is used; otherwise, number of slots =
- 4: Read matrix data
- 4 bits: number of matrices
- 8 bits per matrix: index into matrix data
- Unsure where the matrix data is, or where this data is written to (presumably somewhere in XF memory)
- For map blocks, the matrix indices are read, but not used
- 5: End of stream
- Else: unused, probably behaves the same as others above
Each block is a separate model, but textures are all read from the map's directory and TEXPRE.bin
.
Although a block's size is fixed at 640 units on the X and Z axes, there is nothing preventing a block from having geometry outside of this range; however, since only a small number of blocks are rendered at one time, geometry far beyond this range might spontaneously appear and disappear as the player moves around.
The blocks listed in MAPS.bin
are arranged into a rectangular grid, with dimensions specified by the list header. This defines how each block is placed relative to the map's origin.
The list header specifies which grid cell is the origin. Specifically, the coordinates 0,0,0 relative to this cell are the origin point. Many maps have the origin in a cell that does not contain a block, and a few have it outside the rectangle entirely.
The origin point is used as a reference for placing objects, as well as placing the map's blocks on the global map grid.
Many maps' rectangles are much larger than necessary, with the excess regions filled with empty blocks. Many also contain block IDs that aren't found anywhere on the game disc.
The file globalma.bin
places each map on one of five "layer" grids, assigning it an absolute position in the game world. These grids define how maps are placed relative to each other, and are used to translate global coordinates to map coordinates.
This file is an array of structs, which ends when map < 0
.
Offset | Type | Name | Note |
---|---|---|---|
000000 | s16 | x | Global grid X coordinate |
000002 | s16 | z | Global grid Z coordinate |
000004 | s16 | layer | Range -2 to 2, cast to s8 |
000006 | s16 | map | Map ID found here; if < 0, marks end of list. |
000008 | s16[2] | link | Linked map IDs |
The specified map is placed on the specified layer grid such that its origin falls at the specified coordinates. Cells with no block are ignored, allowing maps to overlap.
The two linked map IDs are used to improve load times. If an entry is not 0xFFFF, that map's romlist will be loaded when the player is in this map. eg the objects in swapholbot
are loaded when the player is in swaphol
and vice versa. (Curiously, dragrock
has itself listed here.)
globalmap.bin
is an older, unused version of this file.
Collision with map geometry is defined by a secondary mesh (contained in the block model) plus a set of lines (contained in HITS.bin
). The latter is mostly only used for jumping up/down from ledges; deleting the file entirely has little impact on the game.
The collision mesh uses the same vertices as the visible geometry (but not necessarily connected in the same way). It is made entirely of triangles.
The GCpolygons
field in the block header points to an array of polygon definitions, each of which defines one triangle. The nPolygons
field gives the length of this array.
Offs | Type | Name | Note |
---|---|---|---|
0000 | u16 | v0 | Vertex 0 (index into vertexPositions array) |
0002 | u16 | v1 | Vertex 1 |
0004 | u16 | v2 | Vertex 2 |
0006 | u16 | subBlocks | Which sub-blocks this polygon covers |
The actual triangle's coordinates are the raw vertex coordinates divided by 8. (This is hardcoded, not derived from the GX POSSHFT value.)
The name GCpolygons
is extracted from default.dol
. GC here doesn't seem to stand for GameCube; perhaps Geometry Collision?
The subBlocks
field is used to optimize collision detection by not checking against far-away polygons. The high byte represents the Z axis, and the low byte represents the X axis.
For the X axis: divide the block into 8 strips (from Z=0 to Z=640, each 80 units wide); each bit is 1 if any part of the polygon overlaps this strip. The lowest bit corresponds to X=(0..80), and the highest corresponds to X=(560..640). The Z axis works the same as the X axis.
The polygonGroups
field points to an array of polygon group definitions, with length given in the nPolyGroups
field.
Offs | Type | Name | Note |
---|---|---|---|
0000 | u16 | firstPolygon | Index into GCpolygons[]
|
0002 | s16 | x1 | Group boundary box |
0004 | s16 | x2 | |
0006 | s16 | y1 | |
0008 | s16 | y2 | |
000A | s16 | z1 | |
000C | s16 | z2 | |
000E | ? | ||
000F | ? | ||
0010 | u8 | id | Game accesses these fields as u32 |
0011 | u8 | surfaceType | |
0012 | u16 | flags |
The collision mesh appears to have been automatically generated from the visible geometry, since it includes areas that are completely unreachable, polygons that are only meant to be glow effects, etc.
These define how objects interact with the polygon: what sound effects play when walking on or striking it; whether it hurts to touch; etc.
ID | Description |
---|---|
00 | Generic; eg underwater ground, out-of-bounds trees |
01 | Grass |
02 | Sand |
03 | Snow |
08 | Instant Death (only used in DragRockBot) |
09 | Ice Platform? (used under cannon in Ice Mountain) |
0D | Ice (slippery) |
0E | Water (makes splash effects) |
10 | Gold (used in CloudRunner mine) |
12 | Rough stone |
13 | Magic Cave walls and floors |
18 | Wood |
19 | Stone and other hard surfaces (includes hot stone in DarkIce Mines) |
1A | Lava (sets you on fire) |
1B | Ice walls |
1D | Conveyor belts |
21 | Unknown; seen in Arwing levels |
22 | Metal |
Defines invisible planes that characters interact with. Examples of their function include:
- Defines the regions of ladders and climbable walls
- Defines how the player behaves when walking off a platform (falling off, jumping off, or being blocked)
- Defines whether the player can jump up to a ledge
- Creates "invisible walls"; eg Arwing landing spots have an outline of the Arwing to prevent the player from walking through it. (Unclear why this method is used as opposed to actual invisible walls.)
- Creates barriers that block enemies, the camera, etc, but not the player
- Defines the boundaries where you can push a block
Each entry is in the format:
Offs | Type | Name | Description |
---|---|---|---|
0000 | s16 | x1 | Position within the map block |
0002 | s16 | x2 | |
0004 | s16 | y1 | |
0006 | s16 | y2 | |
0008 | s16 | z1 | |
000A | s16 | z2 | |
000C | u8[2] | height | |
000E | u8 | flags | |
000F | u8 | type | See list below |
0010 | ?16 | ||
0012 | ?16 |
The height values are interpreted depending on the highest bit of the flags byte. If zero, the bytes are two different heights (y1 and y2). If one, both bytes are read as a single s16
value and applied to both ends of the line.
The result is a plane or triangle with corners: (x1,y1,z1), (x1,y1+height1,z1), (x2,y2+height2,z2), (x2,y2,z2). The block's Y offset is not applied to these coordinates.
These define how characters interact with the plane. The highest two bits of this value have some other meaning.
ID | Description |
---|---|
01 | General barriers |
02 | Ledge which, if you walk off, you automatically grab the edge |
03 | Climbable wall |
04 | Ledge you can jump off |
05 | Ledge you can jump off (unsure how this differs; used in Animtest) |
06 | Ledge you can climb/jump up to (automatic by height) |
0A | Ladder |
0D | Barrier for pushing blocks |
0E | Tunnel you have to crawl through |
10 | Presumably lets you climb out of water |
11 | Unknown; the game clears the highest 2 bits and changes the type to 0x13 |
HITS.tab
gives the offset of each entry, indexed by the block's mod# (plus the offset in TRKBLK.tab
) plus its sub#. The number of entries is determined by subtracting the tab entry from the following entry.
Several maps have leftover entries out of bounds or in the sky, which form shapes suggesting there used to be geometry here. Some of these possibly could be moved in-bounds at some point?
(TODO: check if any of these actually belong to different maps and are incorrectly being shown)
A few maps also seem to have nonsensical entries at the beginning of some blocks; for example, ThornTail Hollow has some lines floating in the sky and deep underground where nothing can reach them. As best I can tell, the game does, in fact, read these lines (i.e. they don't appear to be an artifact of a bug in my own code); whether they're actually used, or just included by mistake, is unknown.
Each display list pointer (referenced by the displayLists
field in the block header) includes a bounding box definition:
Offs | Type | Name | Description |
---|---|---|---|
0000 | void* | list | Points to list data (raw GX commands) |
0004 | u16 | size | List data size in bytes |
0006 | vec3s[2] | bbox | Bounding box |
0012 | ? | ||
0013 | u8 | shaderId | |
0014 | u16 | specialBitAddr | offset relating to shaders |
0016 | u16 | offset of some sort | |
0018 | u32 | always 07 00 00 00? (Leftover from N64 segmented addresses?) |
The bounding box is used to determine whether the list should be rendered. (More info needed here...)
This file contains the name, type, and some unknown parameters for each map. This info is mostly unused by the game; most fields are left over from development.
Offset | Type | Name | Note |
---|---|---|---|
000000 | char[28] | name | Null-padded but not terminated |
00001C | u8 | type | Map type |
00001D | u8 | ? | Always 6 |
00001E | u16 | objType | Unused |
- name: Would be displayed in a debug menu; unused in final version.
-
type: Type of map:
- 0: Normal map
- 1: Normal sub-map (unsure what's different about this)
- 2: Special map (unused, deletes all objects when loaded)
- 3: Special sub-map (unused, deletes all objects when loaded)
- 4: Special map (Arwing levels, title screen, world map)
- Only types 0, 1, and 4 are used, with the exception of one unused map which uses type 3.
- objType: Not used. In previous versions, was an ObjDef ID to use as the player object instead of Sabre/Krystal. Only valid for maps of type 1.
Each map directory contains VOXMAP.bin
and VOXMAP.tab
. Their purpose is unknown. Replacing the ones in swaphol
with those of warlock
, or removing them entirely, doesn't have any noticeable effect.