SM64’s collision system is built around individual triangles called surfaces. Every floor, wall, ceiling, and special trigger in a level is one or moreDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/n64decomp/sm64/llms.txt
Use this file to discover all available pages before exploring further.
Surface triangles. Each frame, Mario’s movement step functions query a spatial grid of these triangles to detect floors below, ceilings above, and walls to the side, then apply the appropriate response.
The system lives in src/engine/surface_collision.c and src/engine/surface_load.c. Surface type constants are in include/surface_terrains.h.
The Surface struct
TerrainData is s16 (same as Collision). Vec3Terrain is s16[3], a compact integer triplet. All vertex coordinates are in game units (integers, not floats) because the original N64 level format stored them as 16-bit integers.
Identifies what kind of surface this is. The collision system and gameplay code branch on this value to apply special behavior. See Surface types below.
An optional parameter stored alongside the type. For
SURFACE_FLOWING_WATER it encodes the flow direction; for SURFACE_HORIZONTAL_WIND it encodes the wind direction (shifted left by 8 bits when spawning wind particles). Zero for most surfaces.Bitmask of per-triangle flags:
| Constant | Value | Meaning |
|---|---|---|
SURFACE_FLAG_DYNAMIC | 1 << 0 | Surface belongs to a moving object; lives in gDynamicSurfacePartition |
SURFACE_FLAG_NO_CAM_COLLISION | 1 << 1 | Camera passes through this surface |
SURFACE_FLAG_X_PROJECTION | 1 << 3 | Wall uses X-axis projection for point-in-triangle test instead of Z-axis |
Used in Big Boo’s Haunt to partition the level into rooms and prevent collision leaking between disconnected spaces. 0 in most levels.
Pre-computed Y-range of the triangle (min/max of the three vertex Y values). The wall collision iterator uses these for a fast early-out: if the query point’s Y is below
lowerY or above upperY, the triangle is skipped immediately without a full point-in-triangle test.The three corner vertices of the triangle in world space. Stored as signed 16-bit integers. Floor and ceiling tests use vertex X and Z components; wall tests also use Y.
Precomputed unit normal vector. For floors,
normal.y > 0; for ceilings, normal.y < 0; for walls, normal.y ≈ 0. Used in the plane equation to compute the surface height at any XZ position.The constant term in the plane equation:
normal · vertex1. Together with normal, this defines the plane the triangle lies in. Height at (x, z) is computed as:For surfaces loaded from a moving object (
load_object_collision_model()), this points to the owning object. NULL for static level geometry. Allows gameplay code to identify the object associated with a surface (e.g. to check if Mario is standing on a platform object).Spatial partition
The level world spans[-8192, +8192] in X and Z (LEVEL_BOUNDARY_MAX = 0x2000). This 16384×16384 unit area is divided into a 16×16 grid of cells, each 1024 units wide (CELL_SIZE = 1 << 10).
SurfaceNode pointers (floors, ceilings, walls), one for static geometry and one for dynamic (object) surfaces. Any triangle that spans multiple cells is added to every cell it touches.
Dynamic surfaces are rebuilt every frame by
clear_dynamic_surfaces() followed by each object’s load_object_collision_model() call. Static surfaces are loaded once at level start by load_area_terrain() and persist until the level changes.Three detection types
Floors
Floor detection finds the highest surface below a point whose normal has a nonzero Y component and whose plane equation puts the query point above the surface (within a 78-unit snapping buffer). The core test fromfind_floor_from_list() (surface_collision.c:401):
Ceilings
Ceiling detection is the vertical mirror of floor detection. The point-in-triangle test is identical but the winding is opposite (ceilings face downward,normal.y < 0). The buffer is +78 instead of -78:
Walls
Wall detection uses a different algorithm. Instead of computing height, it computes the signed distance from the query point to the triangle’s plane and applies a push if the point is within the collision radius:WallCollisionData.walls[]; beyond that, additional walls are counted but not stored (the “Unreferenced Walls” bug).
Key query functions
All declared insrc/engine/surface_collision.h:
Returns the Y height of the highest floor surface below Checks dynamic surfaces first, then static, and returns whichever is higher. Used by
(xPos, yPos, zPos) and writes the Surface * to *pfloor. Returns FLOOR_LOWER_LIMIT (-11000) and sets *pfloor = NULL if no floor is found.init_mario(), every movement step function, and many object behaviors.Returns the Y height of the lowest ceiling above Internally casts position to
(posX, posY, posZ) and writes the Surface * to *pceil. Returns CELL_HEIGHT_LIMIT (20000) and sets *pceil = NULL if no ceiling exists.s16 before the grid lookup, which is the root cause of the Parallel Universes (PU) glitch: at high float positions the cast wraps around and queries a completely different grid cell.Populates a
WallCollisionData struct with all wall surfaces within the specified radius of the given position. Returns the total number of collisions found. The struct also has its x and z fields modified to reflect the pushed-out position.Convenience wrapper around
find_wall_collisions() that takes direct float pointers instead of a WallCollisionData struct and writes the pushed position back to the pointer arguments.Declared in
src/game/mario.h, implemented in src/game/mario.c. Calls find_wall_collisions() and applies the resulting position push back to pos. Returns the first wall surface encountered, or NULL.Variant of
find_ceil() that takes a Vec3f position and an explicit height offset for the query. Declared in src/game/mario.h.Like
find_floor() but also populates a FloorGeometry struct with the floor’s normal components and originOffset. Used by the shadow system and slope calculations.Finds the water surface height at a given XZ coordinate by scanning environment waterbox data embedded in the level collision. Returns
-11000 if no waterbox covers the point.Surface types
include/surface_terrains.h defines every surface type value. Selected important ones:
Gameplay-affecting floor types
Gameplay-affecting floor types
| Constant | Value | Effect |
|---|---|---|
SURFACE_DEFAULT | 0x0000 | Standard floor, no special behavior |
SURFACE_BURNING | 0x0001 | Lava/frostbite; damages Mario on contact |
SURFACE_DEATH_PLANE | 0x000A | Instant death floor |
SURFACE_VERY_SLIPPERY | 0x0013 | High-friction slide surface (slides) |
SURFACE_SLIPPERY | 0x0014 | Moderate slip |
SURFACE_NOT_SLIPPERY | 0x0015 | No slip, also used for climbable ceilings |
SURFACE_ICE | 0x002E | Slippery ice (CCM, THI water floor) |
SURFACE_HARD | 0x0030 | Always causes fall damage regardless of height |
SURFACE_SHALLOW_QUICKSAND | 0x0021 | 10 units deep, escapable |
SURFACE_DEEP_QUICKSAND | 0x0022 | 160 units deep, lethal |
SURFACE_INSTANT_QUICKSAND | 0x0023 | Lethal immediately |
SURFACE_HORIZONTAL_WIND | 0x002C | Applies horizontal wind force; force encodes direction |
SURFACE_VERTICAL_WIND | 0x0038 | Upward wind in vertical wind areas |
SURFACE_HANGABLE | 0x0005 | Ceiling Mario can hang and crawl on |
Warp and camera types
Warp and camera types
| Constant | Value | Effect |
|---|---|---|
SURFACE_INSTANT_WARP_1B–1E | 0x001B–0x001E | Teleport to another area immediately on contact |
SURFACE_WARP | 0x0032 | Generic surface warp |
SURFACE_CAMERA_BOUNDARY | 0x0072 | Invisible intangible wall only the camera collides with |
SURFACE_VANISH_CAP_WALLS | 0x007B | Passable with Vanish Cap or ACTIVE_FLAG_MOVE_THROUGH_GRATE |
SURFACE_BOSS_FIGHT_CAMERA | 0x0065 | Widens camera for BoB/WF boss arenas |
Surface class macros
Surface class macros
mario_get_floor_class() in src/game/mario.c maps the floor’s raw type field to one of these four class constants, which are what the movement code actually branches on for friction calculations.Collision data format in level scripts
Static collision geometry is compiled into arrays ofCollision (s16) values embedded in level segment data. The format is read by load_area_terrain() in surface_load.c. The collision macro helpers from include/surface_terrains.h:
Object collision
Dynamic objects register their collision model viaload_object_collision_model(), called each frame for any active object with a collision data pointer (collisionData field on struct Object). This re-inserts all triangles of the object’s mesh into gDynamicSurfacePartition transformed to world space, with surface->object pointing back to the owning object and SURFACE_FLAG_DYNAMIC set in the flags.
Terrain sound types
Theterrain_type level script command (cmd 0x31) sets the level’s terrain sound category, which is mixed into footstep and landing sound IDs:
mario_get_terrain_sound_addend() in src/game/mario.c returns a value derived from the floor’s surface type and the level terrain type. This addend is stored in MarioState.terrainSoundAddend and added to the base sound ID when playing footstep sounds, selecting the correct surface-specific audio variant.