The decal system is responsible for rendering bullet holes, blood splatter, explosion marks, and other flat textures applied over over BSP surfaces (decals cannot appear on objects). Decal tags describe the appearance and lifetime of these effects.

Dynamic decals

Dynamic decals are created from effects, such as explosions and projectile impacts. It is recommended to give the decal a maximum lifetime to avoid poor framerates. The game supports up to 2048 decal "slots". However it may also reach a limit at fewer decals when it can no longer allocate decal vertices. This is extremely unlikely unless there are many decals with a large radius being created, but the console will log:

### WUT? decals: failed to allocate vertices (locked=37, permanent=4)

This means there are 37 "locked" decals (unexpired). When these decals reach the end of their lifetime, they will be destroyed and their vertices released back to the pool. Rough testing shows that the pool is likely 16*2048 vertices, with each decal able to use as many vertices from the pool as needed to conform to the BSP.

Permanent decals

Also called environment decals, these are placed throughout the scenario using Sapien (under game data in the hierarchy window).

Chain decals

Chain decals are when multiple decals are created at the same time. A decal tag can specify another decal which should be generated at the same location. It's useful for composite effects, especially when mixing blending modes. For example, you might want to have a plasma impact generate both a short-lived add glowing decal and a long-lived multiply scorch mark. You could spawn more decals from effects to achieve a similar result, but doing it by chain decals means it's easier to re-use across different effects and allows you to share geometry.

Decal meshes

When a decal needs to be created the game goes through a process to generate geometry for it. In most cases decals are created against flat surfaces so are built as simple 4-sided quads. However, if the decal needs to cover an area which is not flat, the game may need to conform the decal to the underlying BSP geometry (depending on type) which may result in dozens of faces and hundreds of vertices.

You can observe decal mesh generation using debug_decals 1. Original corner vertices of decal meshes stand off from their background with a small margin to avoid Z-fighting, controlled by rasterizer_zoffset, while additional vertices generated from the conformation process are not necessarily z-offset. Decals can wrap onto +sky faces, but not onto or past breakable surfaces even after the surface is broken.

Decals only render when their origin point is in a visible cluster. If the decal wraps into another cluster and the origin cluster goes off-screen, the decal will abruptly disappear.

Related HaloScript




Displays red numbers over each dynamic and permanent decal in the environment. The mesh of the most recently created decal (initially a permanent decal if one exists, then any new dynamic one) will also be highlighted so you can see how it conforms around the BSP.

White points indicate the original 4 corners of the decal, while red ones were added during the conformation to the BSP. The meaning of the number is not known definitely, but seems to be the number of vertices needed if we assume each projected decal region must be converted into quads.


Toggles the display of yellow bounding spheres around each permanent decal in the environment.

(<void> rasterizer_decals_flush)

Destroys all dynamic and permanent decals. You shouldn't use this to optimize your map's framerate because it doesn't preserve decorative environmental decals. Instead, set reasonable lifetimes for your dynamic decals and limit the number of decals created by custom effects.


Toggles the display and creation of both permanent and dynamic decals. While false, effects cannot create new decals but previous ones will reappear when reset to true.

(rasterizer_zoffset [real])

Controls how far away from surfaces new decals are generated, e.g. for projectile impacts. Defaults to 0.003906. The units are not world units.


Structure and fields

geometry inherited by next decal in chain0x1

If using chain decals, the next decal will inherit the same mesh as this one rather than being generated anew. This is useful if aspects of the texture need to line up from one decal to the next, e.g. if plasma splatter patterns fade into scorch marks that need to be aligned.

interpolate color in hsv0x2

When choosing a random colour between color lower bounds and color upper bounds, interpolate in HSV space rather than RGB. The interpolation will happen along the shortest hue distance.

more colors0x4

When interpolate color in hsv is set, this causes interpolation to happen along the longer hue distance. For example, instead of interpolating orange between red and yellow, it will select random colors among purples, blues, and greens.

no random rotation0x8
water effect0x10
sapien snap to axis0x20
sapien incremental counter0x40
animation loop0x80
preserve aspect0x100
disabled in remastered by blood setting0x200

Controls how the decal geometry is generated.


The decal will conform to the underlying geometry.


No observed difference from scratch.


No observed difference from scratch.

painted sign0x3

The decal will be clipped at the edges of the underlying geometry rather than wrap around (neither convex nor concave).


Determines what stage of rendering the decal will be drawn in.


Draws after the environment diffuse texture.


Draws after primary, allowing these decals to cover them.


Draws after diffuse light, but before diffuse textures, allowing these decals to act like lightmaps.

alpha tested0x3
next decal in chainTagDependency: decal

The next decal to generate at this location. Do not form a circular chain of decals.

  • Unit: world units
  • Default: 0.125,0.125

Sets the world units scale for 16px of decal. For example, a value of 1 world unit means that a 16x16px decal will be a 1x1wu square in-game, and a 32x32px decal will be 2x2wu, etc. 0 defaults to 0.125. The ingame radius caps out at approximately 16 world units even if the tag value is set higher.

  • Min: 0
  • Max: 1
  • Default: 1,1

Sets lower and upper bounds for how visible the decal is. Defaults to fully visible (1-1) if set to 0-0, but otherwise the game randomly selects an intensity within the given range.

color lower boundsColorRGB
  • Default: 1,1,1

A lower bound for color that will be multiplied with the decal. Defaults to white. The color will be selected at random between this and the upper bound.

color upper boundsColorRGB
  • Default: 1,1,1

An upper bound for color that will be multiplied with the decal. Defaults to white.

animation loop frameuint16
animation speeduint16
  • Unit: ticks per frame
  • Default: 1
  • Unit: seconds

Controls how long the decal will exist for.

decay timeBounds?
  • Unit: seconds

Controls how long it takes for the decal to fade out at the end of its lifetime. This does not extend the lifetime, but rather the decal begins fading out this many seconds before it will expire.

framebuffer blend functionenum

Sets how this decal will be blended into its background. Examples are:

  • add for plasma,
  • double multiple for bullet holes on metal,
  • multiply for blood or burns
alpha blend0x0
double multiply0x2
component min0x5
component max0x6
alpha multiply add0x7
mapTagDependency: bitmap

The texture to use for the decal.

maximum sprite extentfloat


Thanks to the following individuals for their research or contributions to this topic:

  • Conscars (Testing functions and globals, tag fields, decal geometry)
  • Ifafudafi (Explaining chain decals and radius scaling)
  • Kavawuvi (Invader tag definitions)
  • MosesOfEgypt (Tag structure research)