United Offensive BSP Sepecification =================================== .. meta:: :description: Runtime-verified Call of Duty: United Offensive bsp file format, including its header, 33 lumps, record layouts, and cross-lump references. :keywords: call of duty, united offensive, cod uo, d3dbsp, ibsp, bsp, file format, map format, modding Format verdict -------------- Call of Duty: United Offensive 1.51 uses the Call of Duty 1 ``bsp`` layout family. The file is little-endian ``IBSP`` version 59 (``0x3b``), has a fixed 272-byte header, and contains 33 lump descriptors. Every fixed record size proved by the CoD:UO collision and renderer loaders matches the corresponding record size on the CoD 1 format page. It is not the substantially different CoD 2 version-4 format. The equivalence claim is deliberately bounded: the container, directory, records, and cross-references documented below match. It does not imply that the CoD and CoD:UO map compilers always emit identical payloads, nor that their entity and script content is interchangeable. The CoD:UO recovery resolves two points left unclear by the older CoD 1 page: - A lump descriptor stores ``fileLength`` first and ``fileOffset`` second. - Lump 32 is part of the 33-entry header. It is an optional, fixed-topology light-visibility cache, not data outside the lump directory. Slots 5 and 31 remain unidentified because no recovered CoD:UO runtime loader consumes them. They are kept as honest unknowns rather than assigned a plausible name. Binary conventions ------------------ Unless a field says otherwise: - Integers and IEEE-754 floats are little-endian. - Offsets are absolute byte offsets from the start of the file. - ``int16`` and ``int32`` are signed; ``uint8``, ``uint16``, and ``uint32`` are unsigned. - ``vec2`` is two consecutive 32-bit floats; ``vec3`` is three. - Record arrays are tightly packed according to the sizes below. - The stock ``CM_SaveLump`` writer lays lumps out in index order and aligns each payload start to four bytes. Readers use the offsets in the directory and do not otherwise require physical index order. Header ------ The header occupies bytes ``0x000`` through ``0x10f``. ========= ==== ============ =========== ===================================================== Offset Size Type Field Meaning ========= ==== ============ =========== ===================================================== ``0x000`` 4 ``char[4]`` ``ident`` ASCII ``IBSP``; little-endian integer ``0x50534249``. ``0x004`` 4 ``int32`` ``version`` ``59`` (``0x3b``). ``0x008`` 264 ``Lump[33]`` ``lumps`` Thirty-three 8-byte descriptors. ========= ==== ============ =========== ===================================================== Each directory entry is: =============== ==== ========= ============== ======================== Relative offset Size Type Field Meaning =============== ==== ========= ============== ======================== ``0x00`` 4 ``int32`` ``fileLength`` Payload length in bytes. ``0x04`` 4 ``int32`` ``fileOffset`` Absolute payload offset. =============== ==== ========= ============== ======================== Equivalent C declarations are: .. code:: c typedef struct { int32_t fileLength; int32_t fileOffset; } Lump; typedef struct { int32_t ident; int32_t version; Lump lumps[33]; } BspHeader; /* 272 bytes / 0x110 */ The retail loaders copy and byte-normalize all 68 header words and compare the version with 59. The collision loader does not reject a wrong ``ident``, but a format reader should still require ``IBSP``. Lump directory at a glance -------------------------- ``Variable`` means the payload is not a homogeneous fixed-record array. A dash means that the CoD:UO runtime supplies no proved record size or interpretation. ===== ====================== ========= ====================== ============================================================== Index Lump Unit size Primary consumer Summary ===== ====================== ========= ====================== ============================================================== 0 Materials / shaders 72 Collision and renderer Name plus surface and content flags. 1 Lightmaps 786,432 Renderer One 512 x 512 RGB image per unit. 2 Planes 16 Collision and renderer Normal and distance. 3 Brush sides 8 Collision Axial distance or plane index, plus material. 4 Brushes 4 Collision Side count and material; side spans are implicit. 5 Unknown / unused - None found Directory slot exists; runtime meaning unresolved. 6 Triangle-soup surfaces 16 Renderer Material, lightmap, vertex span, and index span. 7 Draw vertices 44 Renderer Position, UVs, lightmap UVs, normal, and color. 8 Draw indices 2 Renderer Signed local vertex indexes. 9 Cull groups 32 Renderer Bounds and surface span. 10 Cull-group indexes 4 Renderer Indexes selected by cells. 11 Portal vertices 12 Renderer Shared 3D vertex pool. 12 Occluders 20 Renderer Plane, edge, and vertex spans. 13 Occluder plane indexes 4 Renderer Indexes into lump 2. 14 Occluder edges 4 Renderer Two local plane and two local vertex indexes. 15 Occluder indexes 2 Renderer Indexes selected by cells. 16 AABB trees 12 Renderer Surface spans and implicit child counts. 17 Cells 52 Renderer Bounds and spans of portals, groups, and occluders. 18 Portals 16 Renderer Plane, destination cell, and vertex span. 19 Light indexes 2 Renderer Per-leaf light references and sun sentinel. 20 Nodes 36 Collision and renderer BSP plane, children, and integer bounds. 21 Leaves 36 Collision and renderer Cluster, area, terrain, brush, cell, and light spans. 22 Leaf-brush indexes 4 Collision Indexes into lump 4. 23 Leaf-surface indexes 4 Collision CoD:UO collision uses these as terrain-patch indexes. 24 Terrain patches 16 Collision Curve or indexed-terrain descriptor. 25 Terrain vertices 12 Collision Shared 3D vertex pool. 26 Terrain indexes 2 Collision Signed local vertex indexes for terrain triangles. 27 Models 48 Collision and renderer Bounds plus surface, leaf-surface, and brush spans. 28 Visibility Variable Collision Two-word header plus cluster visibility rows. 29 Entities Variable Collision and renderer NUL-terminated text entity definitions. 30 Static lights 72 Renderer Typed light record with a 16-byte parameter union. 31 Unknown / unused - None found Directory slot exists; runtime meaning unresolved. 32 Light-visibility cache 12 Renderer Optional 8192 x 32-entry cache; 3,145,728 bytes when complete. ===== ====================== ========= ====================== ============================================================== Lump details ------------ .. _lump-0---materials--shaders: Lump 0 - Materials / shaders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each 72-byte material record is shared directly by the collision and renderer loaders. ======== ==== ============ ================ ========================================== Offset Size Type Field Meaning ======== ==== ============ ================ ========================================== ``0x00`` 64 ``char[64]`` ``name`` NUL-terminated material or shader path. ``0x40`` 4 ``int32`` ``surfaceFlags`` Surface behavior and material class flags. ``0x44`` 4 ``int32`` ``contentFlags`` Collision/content mask. ======== ==== ============ ================ ========================================== The fixed name field can hold at most 63 bytes plus its terminator. Lump 1 - Lightmaps ~~~~~~~~~~~~~~~~~~ The lump is a concatenation of 512 x 512, three-channel lightmaps. Channels are stored as one byte each, giving: .. code:: text 512 * 512 * 3 = 786,432 bytes per lightmap The renderer derives the expected number of images from the maximum nonnegative ``lightmapNum`` used by lump 6. A nonempty lump must contain exactly that many images. At load time, the original 512-square RGB images may be packed into larger RGBA GPU atlases; that atlas is runtime state, not part of the file. Lump 2 - Planes ~~~~~~~~~~~~~~~ ======== ==== ============ ============ ========================= Offset Size Type Field Meaning ======== ==== ============ ============ ========================= ``0x00`` 12 ``float[3]`` ``normal`` Plane normal. ``0x0c`` 4 ``float`` ``distance`` Distance from the origin. ======== ==== ============ ============ ========================= The plane equation used by collision traversal is ``dot(normal, point) - distance``. Lumps 3 and 4 - Brush sides and brushes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each 4-byte brush record is: ======== ==== ========= ================= ========================================= Offset Size Type Field Meaning ======== ==== ========= ================= ========================================= ``0x00`` 2 ``int16`` ``numSides`` Number of consecutive records in lump 3. ``0x02`` 2 ``int16`` ``materialIndex`` Index into lump 0 for the brush contents. ======== ==== ========= ================= ========================================= Brushes do not store a first-side index. Their spans are concatenated in lump 3, so a reader maintains a running side cursor across lump 4. Each 8-byte brush-side record is: ======== ==== ====================== ================= ====================================================================================== Offset Size Type Field Meaning ======== ==== ====================== ================= ====================================================================================== ``0x00`` 4 ``float`` or ``int32`` ``plane`` Axial distance for the first six sides of a brush; lump-2 plane index for later sides. ``0x04`` 4 ``int32`` ``materialIndex`` Index into lump 0 for this side. ======== ==== ====================== ================= ====================================================================================== The first six sides encode the axis-aligned bounds. Additional sides describe non-axial clipping planes. A valid collision brush therefore has at least six sides. .. _lump-5---unknown--runtime-unused: Lump 5 - Unknown / runtime-unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The slot is present in the 33-entry header but is not passed to any CoD:UO collision or renderer loader recovered in this tree. No record size or meaning is assigned here. Lump 6 - Triangle-soup surfaces ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The older CoD 1 page calls these triangle soups. CoD:UO uses one 16-byte record per render surface: ======== ==== ========== ================= ============================================================= Offset Size Type Field Meaning ======== ==== ========== ================= ============================================================= ``0x00`` 2 ``int16`` ``materialIndex`` Index into lump 0. ``0x02`` 2 ``int16`` ``lightmapNum`` Lightmap index; negative values select non-lightmapped modes. ``0x04`` 4 ``int32`` ``firstVertex`` First record in lump 7. ``0x08`` 2 ``uint16`` ``vertexCount`` Number of vertices in the surface-local span. ``0x0a`` 2 ``uint16`` ``indexCount`` Number of indexes in the surface-local span. ``0x0c`` 4 ``uint32`` ``firstIndex`` First record in lump 8. ======== ==== ========== ================= ============================================================= ``indexCount`` is normally a multiple of three. Each selected lump-8 value is local to the surface and is added to ``firstVertex``. Lump 7 - Draw vertices ~~~~~~~~~~~~~~~~~~~~~~ Each renderer vertex is 44 bytes: ======== ==== ============ ============ ===================================== Offset Size Type Field Meaning ======== ==== ============ ============ ===================================== ``0x00`` 12 ``float[3]`` ``position`` World-space position. ``0x0c`` 8 ``float[2]`` ``st`` Base material texture coordinates. ``0x14`` 8 ``float[2]`` ``lightmap`` Lightmap texture coordinates. ``0x1c`` 12 ``float[3]`` ``normal`` Vertex normal. ``0x28`` 4 ``uint8[4]`` ``color`` Packed vertex color, including alpha. ======== ==== ============ ============ ===================================== This compact 44-byte layout is one of the clear differences from the 68-byte CoD 2 vertex record. Lump 8 - Draw indexes ~~~~~~~~~~~~~~~~~~~~~ The lump is an array of signed 16-bit indexes. Indexes are relative to the owning lump-6 surface's ``firstVertex``, not absolute indexes into the complete vertex lump. Lump 9 - Cull groups ~~~~~~~~~~~~~~~~~~~~ ======== ==== ============ ================ ===================== Offset Size Type Field Meaning ======== ==== ============ ================ ===================== ``0x00`` 12 ``float[3]`` ``mins`` Minimum bounds. ``0x0c`` 12 ``float[3]`` ``maxs`` Maximum bounds. ``0x18`` 4 ``int32`` ``firstSurface`` First lump-6 surface. ``0x1c`` 4 ``int32`` ``surfaceCount`` Number of surfaces. ======== ==== ============ ================ ===================== Lump 10 - Cull-group indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The lump is an array of 32-bit indexes into lump 9. Cells select contiguous spans of this index array through ``firstCullGroup`` and ``cullGroupCount``. Lump 11 - Portal vertices ~~~~~~~~~~~~~~~~~~~~~~~~~ The lump is a shared array of ``vec3`` positions, 12 bytes each. Lump 18 portals and lump 12 occluders both select spans from this pool. Lump 12 - Occluders ~~~~~~~~~~~~~~~~~~~ ======== ==== ========= =============== ============================================= Offset Size Type Field Meaning ======== ==== ========= =============== ============================================= ``0x00`` 4 ``int32`` ``firstPlane`` First record in lump 13. ``0x04`` 2 ``int16`` ``planeCount`` Number of occluder planes. ``0x06`` 2 ``int16`` ``edgeCount`` Number of occluder edges. ``0x08`` 4 ``int32`` ``firstEdge`` Runtime destination offset for the edge span. ``0x0c`` 4 ``int32`` ``firstVertex`` First vertex in lump 11. ``0x10`` 2 ``int16`` ``vertexCount`` Number of vertices. ``0x12`` 2 padding - Natural trailing padding; not consumed. ======== ==== ========= =============== ============================================= There is one original-loader subtlety: the renderer restarts its input lookup at lump-14 edge zero for each occluder, while ``firstEdge`` selects the runtime destination span. Tools seeking retail behavior should preserve that access pattern instead of automatically adding ``firstEdge`` to the input index. Lump 13 - Occluder plane indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The lump is an array of signed 32-bit indexes into lump 2. Lump-14 plane bytes are local indexes into the owning occluder's selected plane span. Lump 14 - Occluder edges ~~~~~~~~~~~~~~~~~~~~~~~~ Each 4-byte record is: ======== ==== ============ ================= ====================================================== Offset Size Type Field Meaning ======== ==== ============ ================= ====================================================== ``0x00`` 2 ``uint8[2]`` ``planeIndexes`` Two local indexes into the owning occluder's planes. ``0x02`` 2 ``uint8[2]`` ``vertexIndexes`` Two local indexes into the owning occluder's vertices. ======== ==== ============ ================= ====================================================== Lump 15 - Occluder indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~ The lump is an array of signed 16-bit indexes into lump 12. Cells select contiguous spans through ``firstOccluder`` and ``occluderCount``. Lump 16 - AABB trees ~~~~~~~~~~~~~~~~~~~~ ======== ==== ========= ================ ========================================= Offset Size Type Field Meaning ======== ==== ========= ================ ========================================= ``0x00`` 4 ``int32`` ``firstSurface`` First lump-6 surface for a terminal tree. ``0x04`` 4 ``int32`` ``surfaceCount`` Number of terminal surfaces. ``0x08`` 4 ``int32`` ``childCount`` Number of immediate child records. ======== ==== ========= ================ ========================================= Tree bounds are not serialized in this lump. Records are stored in depth-first order. When ``childCount`` is nonzero, the child records immediately follow the parent; the renderer recursively derives bounds from descendants. A terminal record has ``childCount == 0`` and derives bounds from its surfaces. Lump 17 - Cells ~~~~~~~~~~~~~~~ ======== ==== ============ ================== ============================= Offset Size Type Field Meaning ======== ==== ============ ================== ============================= ``0x00`` 12 ``float[3]`` ``mins`` Minimum bounds. ``0x0c`` 12 ``float[3]`` ``maxs`` Maximum bounds. ``0x18`` 4 ``int32`` ``aabbTreeIndex`` Index into lump 16. ``0x1c`` 4 ``int32`` ``firstPortal`` First record in lump 18. ``0x20`` 4 ``int32`` ``portalCount`` Number of portals. ``0x24`` 4 ``int32`` ``firstCullGroup`` First record in lump 10. ``0x28`` 4 ``int32`` ``cullGroupCount`` Number of cull-group indexes. ``0x2c`` 4 ``int32`` ``firstOccluder`` First record in lump 15. ``0x30`` 4 ``int32`` ``occluderCount`` Number of occluder indexes. ======== ==== ============ ================== ============================= Lump 18 - Portals ~~~~~~~~~~~~~~~~~ ======== ==== ========= =============== ============================ Offset Size Type Field Meaning ======== ==== ========= =============== ============================ ``0x00`` 4 ``int32`` ``planeIndex`` Index into lump 2. ``0x04`` 4 ``int32`` ``cellIndex`` Destination cell in lump 17. ``0x08`` 4 ``int32`` ``firstVertex`` First vertex in lump 11. ``0x0c`` 4 ``int32`` ``vertexCount`` Number of portal vertices. ======== ==== ========= =============== ============================ Portals are directed records. The owning cell is determined by the cell's portal span; ``cellIndex`` identifies the cell reached through the portal. Lump 19 - Light indexes ~~~~~~~~~~~~~~~~~~~~~~~ The lump is an array of signed 16-bit indexes into lump 30. Each leaf selects a span through ``firstLightIndex`` and ``lightCount``. If the first selected value is negative, the renderer treats it as a sunlight marker, skips that element, and uses the remaining indexes as ordinary static-light references. Lump 20 - Nodes ~~~~~~~~~~~~~~~ ======== ==== ============ ============== ================================================== Offset Size Type Field Meaning ======== ==== ============ ============== ================================================== ``0x00`` 4 ``int32`` ``planeIndex`` Split plane in lump 2. ``0x04`` 8 ``int32[2]`` ``children`` Nonnegative node indexes; negative leaf encodings. ``0x0c`` 12 ``int32[3]`` ``mins`` Serialized integer minimum bounds. ``0x18`` 12 ``int32[3]`` ``maxs`` Serialized integer maximum bounds. ======== ==== ============ ============== ================================================== A negative child value ``n`` selects leaf ``-1 - n``, so ``-1`` is leaf 0. The CoD:UO collision and renderer loaders advance over the bounds but do not use them when building their runtime node records. Lump 21 - Leaves ~~~~~~~~~~~~~~~~ ======== ==== ========= ===================== =========================================================== Offset Size Type Field Meaning ======== ==== ========= ===================== =========================================================== ``0x00`` 4 ``int32`` ``cluster`` Visibility cluster; negative means no cluster. ``0x04`` 4 ``int32`` ``area`` Collision-area index. ``0x08`` 4 ``int32`` ``firstTerrainPatch`` First record in lump 23. ``0x0c`` 4 ``int32`` ``terrainPatchCount`` Number of lump-23 entries. ``0x10`` 4 ``int32`` ``firstLeafBrush`` First record in lump 22. ``0x14`` 4 ``int32`` ``leafBrushCount`` Number of lump-22 entries. ``0x18`` 4 ``int32`` ``cellIndex`` Renderer cell in lump 17. ``0x1c`` 4 ``int32`` ``firstLightIndex`` First record in lump 19. ``0x20`` 4 ``int32`` ``lightCount`` Number of lump-19 entries, including a possible sun marker. ======== ==== ========= ===================== =========================================================== The renderer consumes cluster, cell, and light fields. Collision consumes cluster, area, terrain-patch, and brush fields. The shared 36-byte layout is therefore proved by two independent loader paths. Lump 22 - Leaf-brush indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The lump is an array of signed 32-bit indexes into lump 4. Leaves and models select contiguous spans from it. Lump 23 - Leaf-surface indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The historical name is leaf surfaces, but the CoD:UO collision loader treats each 32-bit entry as an index into lump 24. Leaves and models select spans of terrain/curve collision patches through this array. Lump 24 - Terrain and curve collision patches ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Every 16-byte record begins with a common four-byte prefix: ======== ==== ========= ================= ====================================================================== Offset Size Type Field Meaning ======== ==== ========= ================= ====================================================================== ``0x00`` 2 ``int16`` ``materialIndex`` Index into lump 0. ``0x02`` 1 ``uint8`` ``collisionMode`` ``0`` selects a quadratic curve grid; nonzero selects indexed terrain. ``0x03`` 1 padding - Alignment before the 12-byte union. ======== ==== ========= ================= ====================================================================== When ``collisionMode == 0``, bytes ``0x04`` through ``0x0f`` are: ======== ==== ========== =============== ==================================== Offset Size Type Field Meaning ======== ==== ========== =============== ==================================== ``0x04`` 2 ``int16`` ``width`` Curve-grid width. ``0x06`` 2 ``int16`` ``height`` Curve-grid height. ``0x08`` 4 ``int32`` ``maxError`` Integer subdivision-error threshold. ``0x0c`` 4 ``uint32`` ``firstVertex`` First grid point in lump 25. ======== ==== ========== =============== ==================================== When ``collisionMode != 0``, the union is: ======== ==== ========== =============== ==================================== Offset Size Type Field Meaning ======== ==== ========== =============== ==================================== ``0x04`` 2 ``int16`` ``vertexCount`` Number of selected lump-25 vertices. ``0x06`` 2 ``int16`` ``indexCount`` Number of selected lump-26 indexes. ``0x08`` 4 ``uint32`` ``firstVertex`` First vertex in lump 25. ``0x0c`` 4 ``uint32`` ``firstIndex`` First index in lump 26. ======== ==== ========== =============== ==================================== Lumps 25 and 26 - Terrain vertices and indexes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Lump 25 is a shared array of 12-byte ``vec3`` positions. Lump 26 is an array of signed 16-bit indexes used by terrain-mode lump-24 records. Those indexes are local to the selected vertex span and are consumed in triangle groups. Curve-mode records use a rectangular ``width * height`` sequence from lump 25 and do not select lump-26 indexes. Lump 27 - Models ~~~~~~~~~~~~~~~~ ======== ==== ============ ==================== ============================ Offset Size Type Field Meaning ======== ==== ============ ==================== ============================ ``0x00`` 12 ``float[3]`` ``mins`` Minimum bounds. ``0x0c`` 12 ``float[3]`` ``maxs`` Maximum bounds. ``0x18`` 4 ``int32`` ``firstSurface`` First lump-6 render surface. ``0x1c`` 4 ``int32`` ``surfaceCount`` Number of render surfaces. ``0x20`` 4 ``int32`` ``firstLeafSurface`` First record in lump 23. ``0x24`` 4 ``int32`` ``leafSurfaceCount`` Number of lump-23 entries. ``0x28`` 4 ``int32`` ``firstLeafBrush`` First record in lump 22. ``0x2c`` 4 ``int32`` ``leafBrushCount`` Number of lump-22 entries. ======== ==== ============ ==================== ============================ Model 0 is the world model. Later records are inline brush models addressed by the engine as ``*1``, ``*2``, and so on. Lump 28 - Visibility ~~~~~~~~~~~~~~~~~~~~ A nonempty visibility lump begins with: ======== ======== =========== =================== ============================================= Offset Size Type Field Meaning ======== ======== =========== =================== ============================================= ``0x00`` 4 ``int32`` ``clusterCount`` Number of visibility rows. ``0x04`` 4 ``int32`` ``bytesPerCluster`` Bytes in each row. ``0x08`` Variable ``uint8[]`` ``data`` ``clusterCount * bytesPerCluster`` row bytes. ======== ======== =========== =================== ============================================= ``CM_ClusterPVS(cluster)`` returns ``data + cluster * bytesPerCluster``. An empty lump is allowed; the collision system then creates an all-visible fallback row whose byte count is the cluster count rounded up to a 32-byte boundary. Lump 29 - Entities ~~~~~~~~~~~~~~~~~~ This lump is a byte string containing brace-delimited, quoted key/value entity definitions. A minimal shape is: .. code:: text { "classname" "worldspawn" "ambient" "0.2" } The format should end the payload with a NUL byte. The renderer reads lighting keys from ``worldspawn`` and recognizes renderer-only entities such as ``misc_model`` and ``corona``; the game module parses the same text for gameplay entities. Entity classes and keys are content conventions layered on the BSP container, not additional binary records. Lump 30 - Static lights ~~~~~~~~~~~~~~~~~~~~~~~ Each light record is 72 bytes: ======== ==== ============ ============== ================================================================== Offset Size Type Field Meaning ======== ==== ============ ============== ================================================================== ``0x00`` 4 ``int32`` ``type`` Light type, values below. ``0x04`` 12 ``float[3]`` ``color`` Source RGB light color. ``0x10`` 12 ``float[3]`` ``position`` Point/spot origin. ``0x1c`` 12 ``float[3]`` ``direction`` Sun or spot direction. ``0x28`` 16 union ``parameters`` Type-selected attenuation/spot parameters. ``0x38`` 16 bytes ``unconsumed`` Present in the record; no CoD:UO renderer read proves its meaning. ======== ==== ============ ============== ================================================================== Proved type values are: ===== ========================================= ============================================================================================= Value Type Parameter use ===== ========================================= ============================================================================================= 1 Sun Direction; no union member used. 2 Point Position; implicit quadratic attenuation 1.0. 3 Linear point ``float linearAttenuation`` at ``0x28``. 4 Custom point ``float quadraticAttenuation`` at ``0x28``, ``float constantAttenuation`` at ``0x2c``. 5 Spot ``float cutoffCos`` at ``0x28``, ``int32 exponent`` at ``0x2c``. 6 Color-only / exact source name unresolved Common color terms only. 7 Custom spot Quadratic at ``0x28``, constant at ``0x2c``, cutoff cosine at ``0x30``, exponent at ``0x34``. 8 Diffuse sun No additional disk fields consumed by the static-light loader. ===== ========================================= ============================================================================================= The last 16 bytes are intentionally left unnamed. Their presence and extent are proved, but no field-specific read in the CoD:UO renderer establishes a source meaning. .. _lump-31---unknown--runtime-unused: Lump 31 - Unknown / runtime-unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As with lump 5, the directory slot exists but no recovered CoD:UO collision or renderer path consumes it. No format claim beyond that fact is made here. Lump 32 - Light-visibility cache ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This optional cache is part of the normal 33-lump directory. When fully compiled, it contains 8192 hash buckets with 32 entries per bucket. Each entry is 12 bytes, so the complete payload is: .. code:: text 8192 * 32 * 12 = 3,145,728 bytes (3 MiB) Each disk entry is: ======== ==== ============ ======================== ====================================================================== Offset Size Type Field Meaning ======== ==== ============ ======================== ====================================================================== ``0x00`` 4 ``uint32`` ``key`` Packed Y/X/cluster lookup key. ``0x04`` 1 ``uint8`` ``sampleState`` ``0`` empty, ``1`` valid, ``2`` blocked. ``0x05`` 5 ``uint8[5]`` ``diffuseSunVisibility`` Results for diffuse-sun step counts 1 through 5. ``0x0a`` 2 ``uint16`` ``visibleLightBits`` Per-leaf light visibility bits; bit ``0x8000`` represents diffuse sun. ======== ==== ============ ======================== ====================================================================== The key and bucket are computed as: .. code:: c key = ((uint32_t)gridY << 22) | (((uint32_t)gridX & 0x3ff) << 12) | ((uint32_t)cluster & 0xfff); bucket = (reverse_bits((uint32_t)gridZ) + (uint32_t)gridY * 0x0c41 - (uint32_t)gridX * 0x0c3d) & 0x1fff; The renderer selects one of the five diffuse-sun bytes according to its current sampling setting when it converts the disk cache to the smaller runtime cache. A zero-length lump means that no precompiled cache is present. The shipped loader contains a notable validation bug: its cache initializer returns success even when the payload size is wrong, making the caller's "funny lump size" error unreachable. Format tools should require either zero bytes or the complete 3 MiB payload rather than copying that bug. Cross-lump relationships ------------------------ ================= ============================================= =================================== Source Fields Target ================= ============================================= =================================== Lump 3 brush side ``materialIndex``, non-axial ``planeIndex`` Lumps 0 and 2 Lump 4 brush ``materialIndex``; implicit running side span Lumps 0 and 3 Lump 6 surface material, lightmap, vertex, and index fields Lumps 0, 1, 7, and 8 Lump 9 cull group surface span Lump 6 Lump 10 index cull-group index Lump 9 Lump 12 occluder plane, edge, and vertex spans Lumps 13, 14, and 11 Lump 13 index plane index Lump 2 Lump 14 edge local plane and vertex indexes Owning lump-12 spans Lump 15 index occluder index Lump 12 Lump 16 AABB tree surface span or implicit children Lump 6 or following lump-16 records Lump 17 cell AABB tree and index spans Lumps 16, 18, 10, and 15 Lump 18 portal plane, destination cell, and vertex span Lumps 2, 17, and 11 Lump 19 index light index or negative sun marker Lump 30 Lump 20 node plane and child indexes Lump 2, lump 20, and lump 21 Lump 21 leaf terrain, brush, cell, and light spans Lumps 23, 22, 17, and 19 Lump 22 index brush index Lump 4 Lump 23 index terrain-patch index Lump 24 Lump 24 patch material and vertex/index spans Lumps 0, 25, and 26 Lump 27 model render-surface, leaf-surface, and brush spans Lumps 6, 23, and 22 ================= ============================================= =================================== Relationship to the CoD 1 and CoD 2 pages ----------------------------------------- The older CoD 1 page and the CoD:UO loaders agree on version 59, all named lump indexes, and every fixed size listed there. CoD:UO therefore belongs to that same format generation. This recovery adds field layouts, cross-reference semantics, the length-before-offset directory order, and the identity of lump 32. The CoD 2 page describes a different format generation: version 4, different lump numbering, additional light-grid/collision sections, and a 68-byte draw vertex. Those CoD 2 structures must not be applied to CoD:UO maps. Evidence and confidence ----------------------- This specification is derived from maintained recovered types and direct loader behavior, not from a guessed extension of the two wiki pages. Primary CoD:UO evidence: - ``src/qcommon/bsp_types.h``: version, descriptor order, 33-lump enumeration, and shared disk types. - ``src/qcommon/collision_map_types.h``: 72-byte material record. - ``src/collision/collision_map_load.c``: collision-owned records and cross-lump behavior. Its loader graph is matched against CoDUOMP.exe ``0x0041c400..0x0041d7d1`` and ``coduo_lnxded`` ``0x0804a06c..0x0804bc57``. - ``src/client/engine/renderer/backend.h`` and ``src/client/engine/renderer/renderer_world_load.c``: renderer-owned records and load graph. The Windows world-loader range is ``0x0050ab70..0x0050e774`` across its direct helpers. - ``src/client/engine/renderer/renderer_lightmap.c``: 512-square RGB lightmaps. - ``src/client/engine/renderer/renderer_light_visibility.c``: lump-32 topology, record fields, key, hash, and save path. - ``docs/MAP_VALIDATOR_RULES.md``: stricter validation rules for safely accepting CoD:UO multiplayer maps. The checked Linux dedicated binary is SHA-256 ``24b0d8269a1ae97e9fe29cd338288e18d804678bdc184a43990a29dad77961f1``. At ``0x0804b58e`` it copies ``0x110`` header bytes; at ``0x0804b616`` it compares the version with ``0x3b``; and at ``0x0804b65f..0x0804b750`` it dispatches the collision lump descriptors at the indexes documented above. Unresolved claims are kept narrow: - No runtime meaning is claimed for lumps 5 or 31. - No semantic field names are claimed for the final 16 bytes of each lump-30 light record. - This is a runtime format specification, not a full description of every map-compiler intermediate or authoring rule. External references ------------------- - `Call of Duty 1: bsp `__ - `Call of Duty 2: d3dbsp `__