Table of Contents

Miscellany

These are various odds and ends that do not fit into the earlier categories.

Version

Every level file begins with a uint32_t version number. This seems to be used by the engine to guarantee compatibility between various level editor versions and the game engine version. More generally, it can be used to determine what sort of level is being read.

Here are the known (observed) values for the version header:

Early TR4 demos (e.g. September 15 demo) have whole level file packed into a single zlib chunk. Therefore, there is no header.
TR5 version header is equal to TR4 version header. So there is no way to tell TR4 level from TR5 level judging only by this header — you need to check filename extension as well.
As it was noted, retail version of TR4 expects to load sound samples compressed in MS-ADPCM format, while TRLE version of TR4 loads uncompressed samples only. There is no way to tell retail version from TRLE version, as their version numbers are equal.

Palette

This consists of 256 [tr_colour] structs, one for each palette entry. However, the individual colour values range from 0 to 63; they must be multiplied by 4 (or bitshifted by 2 to the left) to get the correct values. Palette is used for all 8-bit colour, such as 8-bit textures.

TR1 only First entry in palette is treated as transparent colour used for textures with alpha testing. In later games, transparent colour was replaced by so-called “magenta transparency”, meaning that any pixel with red and blue values at maximum and green value at zero, is treated as completely transparent.

Object Textures

An object texture (or texture details in TRLE terms) keeps detailed information about each texture independently used in the game. While it’s not a texture image itself (these are kept inside texture atlases), it’s rather a reference to a particular texture in an atlas, kept with all other necessary information to display this texture.

Object Texture Vertex Structure

This sub-structure used by object textures specifies a vertex location in texture tile coordinates. The Xcoordinate and Ycoordinate are the actual coordinates of the vertex's pixel. If the object texture is used to specify a triangle, then the fourth vertex's values will all be zero.

struct tr_object_texture_vert // 4 bytes
{
    ufixed16 Xcoordinate;
    ufixed16 Ycoordinate;
};
Actual texture coordinade format was unknown before the end of 2017, when TE developer MontyTRC uncovered that used format is similar to fixed-point format used for fractional values in animation structures, yet it uses 2 bytes instead of 4 (i.e. one byte for whole part and another for fractional part). However, since classic TR games built with native tools produced only whole coordinates (e.g. 64×64, 16×32, and so on) and there was no occurence of seeing fractional coordinates in levels, for many years it was believed that low byte in each field must always be of specific value (not 0 as expected, but either 1 or 255 which is probably caused by rounding error in Core's native conversion tools).

Object Texture Structure (TR1-3)

It’s object texture structure itself. These, thee contents of ObjectTextures[], are used for specifying texture mapping for the world geometry and for mesh objects.

struct tr_object_texture  // 20 bytes
{
    uint16_t               Attribute;
    uint16_t               AtlasAndFlag;
    tr_object_texture_vert Vertices[4]; // The four corners of the texture
};

AtlasAndFlag is a combined field:

Attribute specifies transparency mode (i.e. blending mode) used for face with this texture applied. There are several ones available:

While blending modes 0, 1 and 2 were the only ones directly available for implementation in original level and animation editors, and therefore, only ones which can be encountered in object textures, there are actually several internal blending modes, which were primarily used for different sprite and particle types. These will be listed below:
  • 3 — Not implemented properly in PC version, but on PlayStation this type produces alpha blending with inversion operation, thus converting all the bright zones to dark, and dark zones to bright. This blending mode was used for smooth textured shadows, footprints and black smoke sprites. There is a remnant of this blending mode in the form of entity type named smoke emitter black.
  • 4 — Alpha-tested face without Z testing, i.e. depth information is ignored. Used for GUI elements (such as fonts) and skyboxes.
  • 5 — Unused. Possibly was used in PlayStation versions.
  • 6 — Wireframe mode. Used for “line particles”, such as gun sparks, water drops and laser beams. Possibly was also used for debug purposes.
  • 7 — TR4 onlyTR5 only Forced alpha value. It’s ordinary alpha-tested face, but alpha value for this face is overridden with global variable. Used to “fade out” specific meshes, like vanishing enemy bodies or Semerkhet ghost in “Tomb of Semerkhet” level.

Object Texture Structure (TR4-5)

The structure introduced many new fields in TR4, partly related to new bump mapping feature.

For bump mapping, TR4 used fairly simple approach, which was actually not a true bump mapping, but multitexturing with additive operation. Therefore, bump maps were not normal maps mainly used for bump-mapping nowadays, but simple monochrome heightmaps automatically generated by level editor. This is a test screenshot comparison demonstrating same scene with and without bump mapping:

illustrations/bump-no.jpgillustrations/bump-yes.jpg
Bump mapping turned off Bump mapping turned on

Assignment of bump maps happened inside level editor, where each texture piece could be marked as either level 1 or level 2 of the bump map effect. When the level was converted, all texture pieces with bumpmaps were placed into separate texture atlases after all other texture atlases, followed by the same amount of texture atlases with auto-generated bump maps arranged in the same manner as the original texture atlases. The number of bump map atlases was kept in a separate variable as well (see TR4 Level Format section).

So, when the engine rendered a face with a texture marked as bump-mapped, it rendered the original texture first, then it jumped to the texture atlas plus the number of bump-mapped texture atlases, and rendered one more texture pass on this face using the texture from the resulting texture atlas and the same UV coordinates.

struct tr4_object_texture // 38 bytes
{
    uint16_t               Attribute;
    uint16_t               AtlasAndFlag;
    uint16_t               NewFlags;
 
    tr_object_texture_vert Vertices[4]; // The four corners of the texture
 
    uint32_t               OriginalU;
    uint32_t               OriginalV;
    uint32_t               Width;     // Actually width-1
    uint32_t               Height;    // Actually height-1
};

NewFlags is a bit field with flags:

illustrations/mapping-correction.png
Triangle mapping correction types. Orange shapes indicate normal (non-mirrored) texture coordinates, while blue shapes indicate mirrored ones. Mirrored coordinates mean that they are placed in counterclockwise order.

Width and Height are helper values which specify width and height for a given object texture.

OriginalU and OriginalV are unused values, which seem to identify original UV coordinates of object texture in TRLE texture page listings. These coordinates are getting messed up when level is compiled, so one shouldn’t bother about parsing them correctly.

TR5 only There is also null uint16_t filler in the end of each [tr4_object_texture].

Animated Textures

Animated textures describe sets of object textures that are cycled through to produce texture animations; they are a set of int16_t’s with the following format (not a “real” C/C++ structure):

int16_t NumAnimatedTextures
 
virtual struct
{
    int16_t NumTextureIDs; // Actually, this is the number of texture ID's - 1.
    int16_t TextureIDs[NumTextureIDs + 1]; // offsets into ObjectTextures[], in animation order.
} AnimatedTextures[NumAnimatedTextures];

If a texture belongs to an animated-texture group, it will automatically be animated by the engine.

There are two types of animated textures — classic frames and UVRotate:

illustrations/uvrotate.jpg
In foreground, you can see alpha-blended waterfall object animated with UVRotate. In background, UVRotate animation is also applied to room mesh.

UVRotate mode is engaged by specifying UVRotate command in level script entry, which takes rotation speed as an argument, where speed is amount of pixel shift per each frame, considering fixed 30 FPS framerate (speed value may be negative, and in such cases pixel shift occurs in reverse direction). If such command is found (and argument is not zero — for example, UVRotate = 4), engine uses special variable value kept in level file, NumUVRotates, to determine if animation range belongs to UVRotate mode or classic frames mode. Then, if it belongs to UVRotate mode, each frame of this range is treated as individual rotating texture.

There is also special case when UVRotate texture mode is engaged. When a texture is applied to a model with specific ID (so-called waterfall objects), then it is also considered UVRotate animated texture, even if it doesn’t belong to animated texture range, but only if it is a texture applied to a first face in the first mesh of the model. If there are other textures applied to other faces of a waterfall object, they won’t be considered as UVRotate.

The speed of animation for waterfall objects is not affected by UVRotate script command. Instead, it is hardcoded value of 7.

Cameras and Sinks

This data block serves for two different purposes, albeit keeping the same structure for both. First purpose is to provide positions to switch the camera to using Camera trigger action, and the second purpose is to move Lara to specified position when she is underwater, and Underwater Current trigger action was used.

struct tr_camera // 16 bytes
{
    int32_t  x;
    int32_t  y;
    int32_t  z;
    int16_t  Room;
    uint16_t Flag;
};

X, Y and Z values are coordinates of a given camera or sink. When used with camera, it is an origin point of a camera. When used with sink, it is a point, towards which Lara is pushed.

Room value specifies the room where camera is placed. For sink cases, this value is used to define strength of the current which moves Lara underwater.

Flag value for cameras specifies whether it’s possible to breakout from fixed camera angle. If first bit of this field is set, camera becomes persistent, and it’s not possible to bypass it with look or draw weapon buttons. For sinks, Flag value contains Box index — which is used as pathfinding reference when Lara is pushed towards sink via underwater current trigger action.

Flyby Cameras

TR4 onlyTR5 only Flyby cameras are cinematic interludes, in which camera flies from one point to another using spline trajectory. Each point in such sequence is a single flyby camera, and current camera properties (position, direction, roll, FOV, speed, and some more) are calculated by interpolating corresponding values from such flyby camera points — for example, if camera 0 has speed value of 10, and camera 1 has speed value of 5, then speed will gradually change from 10 to 5 when moving from one to another.

struct tr4_flyby_camera  // 40 bytes
{
    int32_t  x;    // Camera position
    int32_t  y;
    int32_t  z;
    int32_t  dx;   // Camera angles (so called "look at" vector)
    int32_t  dy;
    int32_t  dz;
 
    uint8_t  Sequence;
    uint8_t  Index;
 
    uint16_t FOV;
    int16_t  Roll;
    uint16_t Timer;
    uint16_t Speed;
    uint16_t Flags;
 
    uint32_t Room_ID;
};

Sequence is a number of flyby camera “chain” this particular camera belongs to. Maximum amount of flyby sequences in single level is 8 (however, this limit was raised to 64 in TREP).

Index specifies order of the cameras in this particular sequence. Camera with index 0 will be first one in sequence, index 1 means camera will be second in sequence, and so on.

Room_ID should be valid for a given flyby camera, so it will display properly, as well as have the ability to activate heavy triggers.

FOV changes this particular camera’s field of view. The value is 182 times higher than the value entered in the TRLE.

Roll changes roll factor of a particular camera. When this parameter is not zero, camera will rotate either left or right along roll axis, creating so-called “dutch angle”. The value is 182 times higher than the value entered in the TRLE.

Timer field mainly used to stop camera movement for a given time (in game frames). As this parameter is temporal, it won’t be interpolated between two cameras.

Speed specifies movement speed for this particular camera. The value is 655 times higher than the value entered in the TRLE.

Flags is an array of bit flags specifying different camera options:

Hex Bit Description
0x0001 0 Make a cut to flyby from Lara camera position. Without it, it’ll pan smoothly.
0x0002 1 TR4 only Tracks specified entity position (from Entities[] array).
TR5 only Creates a vignette around the picture, giving impression of “subjective” camera.
0x0004 2 Infinitely loop sequence.
0x0008 3 Used only with first camera in a sequence: whole sequence is treated merely as a camera “rails”, and camera itself focuses on Lara, thus creating “tracking” camera. Best example is “tracking” view in ALEXHUB2.TR4, rooms #23 and #31.
0x0010 4 TR4 only Camera focuses on Lara’s last head position.
TR5 only For TR5, this flag is now used to hide Lara for this camera.
0x0020 5 Camera continuously focuses on Lara’s head, overriding own angle.
0x0040 6 Used only with last camera in a sequence: camera smoothly pans back to Lara camera position.
0x0080 7 When flyby arrives to this position, cuts to specific camera in same sequence. Next camera number is specified in Timer field of this camera.
0x0100 8 Stops camera movement for a given time (see Timer field).
0x0200 9 Disables look keypress breakout.
0x0400 10 Disables all Lara controls for all next camera points. Also engages widescreen bars to create cinematic feel.
0x0800 11 Overrides Bit 10 controls lock, enabling them back. Widescreen bars remain unaffected.
0x1000 12 TR5 only Make screen fade-in.
0x2000 13 TR5 only Make screen fade-out.
0x4000 14 Camera can activate heavy triggers, just like particular kinds of entities (boulders, pushables, etc.). When camera is moving right above heavy trigger sector, it will be activated.
0x8000 15 TR5 only TRLE for TR5 says this flag is used to make camera one-shot, but it’s not true. Actual one-shot flag is placed in extra uint16_t field at 0x0100 for flyby camera TrigAction.

Cinematic Frames

These are camera positionings and properties for cutscene frames. All the entity animations are specified separately, and they are not synced with actual camera positions.

TR1 only For each cutscene, game applies certain hardcoded set of parameters:
switch (CutLevelID) {
    case 0: // CUT1.PHD
        levelPosX = 36668;	// X pivot position
        levelPosZ = 63180;      // Z pivot position
        levelRotY = -23312;     // Y rotation
        trackID = 23;		// Audio track index
        break;
    case 1: // CUT2.PHD
        levelPosX = 51962;
        levelPosZ = 53760;
        levelRotY = 16380;
        trackID = 25;
        break;
    case 2: // CUT3.PHD
        levelRotY = 0x4000;     // Default rotation, 90 degrees
        FlipMap();		// Do a flipmap
        trackID = 24;
        break;
    case 3: // CUT4.PHD
        levelRotY = 0x4000;
        trackID = 22;
        break;
}

levelPosX, levelPosZ and levelRotY parameters are used, if defined, to set-up initial camera pivot position and Y axis rotation. If some of these parameters are not defined (which is the case for CUT3.PHD and CUT4.PHD), they are borrowed from X and Z position and Y rotation of entity with ID #77 (slot used for main cutscene actor, usually Lara). Also, same parameters are used as master model matrix (i. e. multiplied by them instead of using their own position and rotation) for models with IDs #77-79 (which are slots for cutscene actors).

struct tr_cinematic_frame // 16 bytes
{
    int16_t targetX; // Camera look at position about X axis, 
    int16_t targetY; // Camera look at position about Y axis
    int16_t target2; // Camera look at position about Z axis
    int16_t posZ;    // Camera position about Z axis
    int16_t posY;    // Camera position relative to something (see posZ)
    int16_t posX;    // Camera position relative to something (see posZ)
    int16_t fov;
    int16_t roll;    // Rotation about X axis
};

All target and pos parameters, as well as roll parameter, are encoded in the same manner as Angle parameter for [tr_entity] structure.

LightMap

A 32*256 array of uint8_t which is apparently for applying light to 8-bit colour, in some documentation called ColourMap. The current palette index and lighting value are used to calcuate an index to this table, which is a table of palette indices.

The Tomb Raider series' software rendering, like that of most real-time-3D games, uses 8-bit colour for speed and low bulk; however, there is the serious problem of how to do lighting with 8-bit colour, because doing it directly is computationally expensive. The usual solution is to arrange the palettes' colours in ramps, which the engine then follows in the appropriate directions. However, the TR series' palettes generally lack such neat ramps.

But the TR series has a more general solution, one that does not require palettes to have colour ramps. It uses precalculated lighting tables, the ColourMap objects. These contain translations of a colour value and a lighting value, listed by palette index. The translation goes as follows:

$n = ColourMap[256 \cdot k + i]$

where $i$ is the original palette index, $k$ is determined from the lighting value, and $n$ is the new palette index. The lighting index $k$ varies from $0$ to $31$, and the corresponding lighting value is $2 - k / 16$ for TR1 and $2 - (k + 1) / 16$ for TR2 and TR3.

This may be associated with the curious fact of the lighting values in the data files increasing in the “wrong” direction in TR1 and TR2, with $0$ being full brightness and greater values being darker.

Flipeffects

The Concept

As it was briefly mentioned earlier, flipeffect is a special pre-compiled routine which is called when some non-trivial event occurs.

The concept of flipeffect is somewhat similar to task, i.e. when some flipeffect is engaged, it could be flagged by engine to call every game frame (however, there are primarily one-shot flipeffects present). Such setup is needed for some complex flipeffect events, like flickering lights in TR1 Atlantis (see FLICKER_FX description), which stops automatically after some time.

If flipeffect is flagged to execute every game frame, this flag can only be unset by own current flipeffect code (when its task is done — for this purpose, special internal flip timer is used to count how much time have passed since flipeffect activation) or replaced by any other flipeffect call (however, newly called flipeffect doesn’t necessarily overwrite current flipeffect flag, so you can have one-shot flipeffect executed with another one queued for continuous execution). Ergo, it’s not possible to queue more than one _continuous flipeffect at a time, but it’s possible to have one one-shot and another continuous flipeffect executed every game frame.

Continuous flipeffects are mostly present in TR1 and TR2. In TR3, only two “legacy” continuous flipeffects remained, and in TR4 and TR5 there are no continuous flipeffects at all. However, there’s still legacy code that checks if there’s any continuous flipeffect queued.

Complete Flipeffect List

In this chapter, we’ll try to describe each flipeffect for every TR engine version. Given the fact that flipeffect listing changed from version to version, yet retaining common ones, the easiest way to lay them down is to create a table with flipeffect indexes corresponding to each game version.

There are some guidelines to flipeffect table:

As mentioned here, flipeffect could be called in two ways — either by an entity via AnimCommand, or by trigger action. However, there are certain flipeffect which strictly require caller entity ID to work with (see effect descriptions for that). In such case, if flipeffect is called by trigger action, resulting outcome is undefined in original engine. The most sane way to deal with this situation is to pass an ID of entity which activated given trigger.

On contrary, some flipeffects may require certain trigger action and/or certain trigger type to be called at the moment. In such case, if flipeffect is called via AnimCommand, resulting outcome is undefined in original engine.

IndexTR1 TR2 TR3 TR4 TR5
0 TURN180 TURN180 TURN180 ROTATE_180 ROTATE_180
1 DINO_STOMP FLOOR_SHAKE FLOOR_SHAKE FLOOR_SHAKE FLOOR_SHAKE
2 LARA_NORMAL LARA_NORMAL LARA_NORMAL FLOOD_FX FLOOD_FX
3 LARA_BUBBLES LARA_BUBBLES LARA_BUBBLES LARA_BUBBLES LARA_BUBBLES
4 FINISH_LEVEL FINISH_LEVEL FINISH_LEVEL FINISH_LEVEL FINISH_LEVEL
5 EARTHQUAKE_FX FLOOD_FX FLOOD_FX ACTIVATE_CAMERA ACTIVATE_CAMERA
6 FLOOD_FX CHANDELIER_FXCHANDELIER_FXACTIVATE_KEY ACTIVATE_KEY
7 RAISINGBLOCK_FX RUBBLE_FX RUBBLE_FX RUBBLE_FX RUBBLE_FX
8 STAIRS2SLOPE_FXPISTON_FX PISTON_FX SWAP_CROWBAR SWAP_CROWBAR
9 SAND_FX CURTAIN_FX CURTAIN_FX - -
10 POWERUP_FX SETCHANGE_FX SETCHANGE_FX TIMER_FIELD_FXTIMER_FIELD_FX
11 EXPLOSION_FX EXPLOSION_FX EXPLOSION_FX EXPLOSION_FX EXPLOSION_FX
12 LARA_HANDSFREE LARA_HANDSFREE LARA_HANDSFREE LARA_HANDSFREE LARA_HANDSFREE
13 FLIP_MAP FLIP_MAP FLIP_MAP - -
14 DRAW_RIGHTGUN DRAW_RIGHTGUN DRAW_RIGHTGUN DRAW_RIGHTGUN -
15 CHAINBLOCK_FX DRAW_LEFTGUN DRAW_LEFTGUN DRAW_LEFTGUN -
16 FLICKER_FX - SHOOT_RIGHTGUN SHOOT_RIGHTGUN SHOOT_RIGHTGUN
17 - SHOOT_LEFTGUN SHOOT_LEFTGUN SHOOT_LEFTGUN
18 MESH_SWAP1 MESH_SWAP1 MESH_SWAP1 -
19 MESH_SWAP2 MESH_SWAP2 MESH_SWAP2 -
20 MESH_SWAP3 MESH_SWAP3 MESH_SWAP3 -
21 INV_ON INV_ON INV_ON INV_ON
22 INV_OFF INV_OFF INV_OFF INV_OFF
23 DYN_ON DYN_ON - -
24 DYN_OFF DYN_OFF - -
25 STATUE_FX STATUE_FX - -
26 RESET_HAIR RESET_HAIR RESET_HAIR RESET_HAIR
27 BOILER_FX BOILER_FX - -
28 ASSAULT_RESET ASSAULT_RESET SETFOG SETFOG
29 ASSAULT_STOP ASSAULT_STOP GHOSTTRAP -
30 ASSAULT_START ASSAULT_START LARALOCATION LARALOCATION
31 ASSAULT_FINISHED ASSAULT_FINISHED CLEARSCARABS RESET_TEST (?)
32 FOOTPRINT_FX FOOTPRINT_FX FOOTPRINT_FX
33 ASSAULT_PENALTY_8 - CLEAR_SPIDERS_PATCH (?)
34 RACETRACK_START - -
35 RACETRACK_RESET - -
36 RACETRACK_FINISHED - -
37 ASSAULT_PENALTY_30 - -
38 GYM_HINT_1 - -
39 GYM_HINT_2 - -
40 GYM_HINT_3 - -
41 GYM_HINT_4 - -
42 GYM_HINT_5 - -
43 GYM_HINT_6 POURSWAP_ON -
44 GYM_HINT_7 POURSWAP_OFF -
45 GYM_HINT_8 LARALOCATIONPAD LARALOCATIONPAD
46 GYM_HINT_9 KILLACTIVEBADDIES KILLACTIVEBADDIES
47 GYM_HINT_10 TUT_HINT_1
48 GYM_HINT_11 TUT_HINT_2
49 GYM_HINT_12 TUT_HINT_3
50 GYM_HINT_13 TUT_HINT_4
51 GYM_HINT_14 TUT_HINT_5
52 GYM_HINT_15 TUT_HINT_6
53 GYM_HINT_16 TUT_HINT_7
54 GYM_HINT_17 TUT_HINT_8
55 GYM_HINT_18 TUT_HINT_9
56 GYM_HINT_19 TUT_HINT_10
57 GYM_HINT_RESET TUT_HINT_11
58 TUT_HINT_12
In original engines, all flipeffects which name begins with LARA_ prefix automatically take Lara character as an entity to work with. Also, most flipeffects with _FX postfix are simple sound effect events.