Skip to content

Porting Sven Co‐op Maps to Half‐Life

wootguy edited this page Apr 3, 2025 · 3 revisions

Introduction

The Sven Co-op engine is a slightly modified GoldSrc engine. The biggest difference is higher limits which, in theory, allow for much larger maps with more detail. In practice, mappers still hit the clipnodes limit before anything else, and that limit hasn't changed. Most SC 5.x maps don't have features that truly require the higher limits. In those cases you can run a few commands with bspguy and get them running in Half-Life.

This guide goes over the problems you'll face trying to run a modern Sven Co-op map in Half-Life, and the tools you have to solve them. Some of these tools also have use in map editing and merging.

Contents

Textures Too Big

This applies to all visual assets in the game. Maps, models, sprites, and skyboxes. In Sven Co-op, the max resolution for textures is 1048576 pixels, which is 1024x1024 for square textures. In Half-Life, the max resolution is 262144 pixels, or 512x512 for square textures.

If you exceed the texture size limit, the Half-Life client crashes with no error message. Thankfully, no other porting-related problem causes this. So, when you crash with no error, you can be confident it's a texture that's too big. Most often it's an MDL file. Delete every entity and try loading the map to test just the map textures.

To resize invalid map textures, select Tools -> Textures -> Downscale Invalid. This will resize any textures that are too large down to the next power-of-2 size. If a large texture is stored in a WAD, it is first embedded into the BSP before being downscaled.

For a map series you may want to edit the WAD instead, but you will first need to downscale in bspguy to adjust the texture coordinates. You can unembed the texture later after downscaling the texture in the WAD.

Bad Surface Extents

Surface Extents define the area of a face's lightmap. In Sven Co-op, lightmaps can be up to 64x64 pixels each. In Half-Life, only 16x16 pixels. This is why maps have broken lighting when you select the wrong engine for a Sven Co-op map. The editor is displaying lightmaps assuming a smaller max size.

image

Bad surface extents cannot be fixed blindly with an automated tool. You need to balance texture quality with polygon counts. After fixing the invalid faces, you will then need to run the map through a RAD compiler to generate valid lightmaps for Half-Life.

See below for solutions.

Subdivide Faces

The easiest solution to fix this problem is to subdivide every invalid face with Tools -> Fix Bad Extents -> Subdivide. This will instantly fix every bad face, but at a great cost. Polygon counts will be much higher than before, and BSP data counts will skyrocket. In-game performance will be worse or borderline unplayable, and you may exceed an engine limit.

image

This is what happened when I tried to subdivide all the bad faces in crystal_mission_1b. I'm now at 142% the vertexes limit, and the map looks like it's been thrown into a blender. Time to click Undo and try something else. It won't always be obvious that you've exceeded a limit. Keep an eye on the Map Limits tab.

Here's an example where using the automatic Subdivide tool makes sense.

image

On the left is the original map. On the right is after I subdivided the invalid faces. You can see there are significantly more polygons, but it's still only about 100 total in this area. That's nothing. Nobody is going to lag from that. If the wpoly count in-game is over 1000 then I will start to worry. To see the wpoly count in Half-Life, type developer 1 and r_speeds 1 into the console.

Downscale Textures

This tool likely won't fix every bad face, but it can fix the vast majority of them. I most often start with this then selectively downscale and subdivide afterward.

In this example, I'm using crystal_mission_1b. I've clicked Validate and see that there are 4484 faces with bad extents. That's a lot. I wouldn't want to go through all of those manually. Let's look at the texture sizes. Sven mappers often use HD textures which, in my opinion, make the game look worse and less like Half-Life.

image

Take this texture for instance. It's basically random noise colored grey. Does this really need an HD texture to communicate that? Compare the left and right images. I downscaled it to 256x256 on the right. Can you notice a difference? It's hard to tell even when they're side-by-side.

Let's go a step further. 128x128.

image

It's only now beginning to look out of place because it's sitting next to a similar 512x512 gravel texture. This is more consistent with what a Half-Life texture looks like though.

There are a lot of HD textures like this that I think can be downscaled without noticeably degrading quality. However, beware of textures with text in them:

image

256x256 downscaling made this sign unreadable. I will have to manually downscale big textures in con3_1 to avoid breaking this one texture.

These are the number of "bad surface extents" faces after using each of the automated downscale options on crystal_mission_1b:

  • No downscaling: 4494
  • Downscale 256x256: 3658
  • Downscale 128x128: 2128
  • Downscale 64x64: 1366

None of these completely solve the problem, but 128x128 and 64x64 greatly reduce it. Once it's down to about 1000 faces or so, it's much safer to subdivide. For a large map like this, you should hardly notice the increase in polycount. If the bad faces are concentrated in a single area then you will want to do some manual downscaling to keep wpoly down.

To summarize, downscale textures until the number of bad faces is manageable, then subdivide the rest. If any one area has too high of a wpoly count, undo the subdivde and downscale more textures. Try to find a balance between texture quality and polygon count, leaning toward the side of low polygon counts. "This map makes me lag" is a much worse problem than "these textures look bad".

Scale All Faces

This is a lazy "just fix it" solution that usually ruins the map. Choose Tools -> Fix Bad Extents -> Scale. All invalid faces will have their textures scaled up. Unlike subdividing, this does not increase poly counts nor cause overflows. The downside to this method is misalignment and lower apparent texture quality. An example:

image

Windows have been shifted out of existence, and fences reconstructed with rebar. Scaling up textures on something like the road makes sense because it looks about the same even when shifted. Besides that, this is a terrible result.

Selectively Scale Faces

Here's an example where you might want to scale a face instead of subdividing it or downscaling the texture (sc_arctic_escape):

image

I've rescaled the snow and rock textures from 1.0 to 2.2. By doing this, I've reduced the invalid face count from 596 to 143. The other faces I might subdivide for a minor increase in poly count across different areas of the map.

It doesn't look bad in this case because the map is very simplistic and the positioning of terrain textures doesn't matter. I argue that it looks better because the tiling is less obvious and the lower detail better matches the low detail of the map geometry. If you want to keep the positioning and general look of the texture identical then you should choose to downscale the texture instead.

Remember that scale: 2.2. This is a magic number that fixes any face with bad extents. Multiply whatever the scale is now by 2.2 and you will fix the face. The reason for this is that Sven Co-op compilers subdivide faces larger than 528, which is 2.2x the Half-Life default of 240.

AllocBlock Full

An AllocBlock is a chunk of space used to store lightmaps. It's not easily calculated because the lightmap generation code for GoldSrc is private. You can get a rough idea of how much space is used in the Map Limits widget. Sometimes you will see it overflowing when it's actually fine, or not overflowing when you get an error in-game. I don't know how valve implemented their lightmap atlases.

"AllocBlock Full" is an error that Sven Co-op has solved by increasing the limit 16x (something else will overflow first). In Half-Life, this is almost as annoying as clipnodes. The solution is the same as fixing faces with bad surface extents. You need to scale faces or downscale textures to reduce lightmap sizes.

Downscaling Textures

Go to the AllocBlock tab in the Map Limits widget to see which textures are consuming the most AllocBlocks. In this example I'm looking at sc_castlevania:

image

Double click a texture name to focus the camera on a face using it.

image

It's my lucky day. The first texture is one I think is too HD for Half-Life anyway. The second is one of those random noise textures that looks about the same at any size. Downscaling these saved me about 14 AllocBlock.

If the downscale option is greyed out, click the "Embedded" checkbox in the Face Editor widget. Only embedded textures can be downscaled.

Scale Invisible Faces

Select Tools -> Scale Invisible Faces. This will scale up textures on entities like trigger_once, which sometimes take up lightmap space despite being invisible (the aaatrigger texture was not used). There often aren't many of these so it doesn't reduce AllocBlocks much at all, but it's a one-click fix with no downsides.

Too Many Models

The maximum number of models in Half-Life is 512. This limit is shared between BSP models, MDL files, and SPR files. In Sven Co-op, the limit 8192. Before this limit was raised, mappers often had to combine similar BSP models to keep the model count down. With the Sven Co-op engine, you can get away with things like this:

image

Each handrail and post is its own func_wall entity. Details like this have raised the map's model count to 1065. For Half-Life, you want to get that down to about 400 max, so there is room to precache MDL and SPR files.

Deduplicate Models

The first step is easy, and can often solve the problem on its own. Select Tools -> Deduplicate Models. This will scan for BSP models that look exactly the same, and update the map entities to use only one model from a set of duplicated models. This effectively lowers the model count and allows more game MDL and SPR files to be precached.

If there are more than 512 BSP models in the map, including ones that are no longer referenced, run Tools -> Delete BSP Data -> Clean to delete unused the BSP models as well. Otherwise the game will crash while parsing the BSP. This is not done automatically in case you want to make a ripent-only edit (doesn't require players to delete their version of the map).

There is a drawback to using this command. Entities that share the same model will have the same lighting and decals applied to them. Shooting one them will create a bullet hole on all of those entities simultaneously. Without unique lightmaps, some entities may appear too bright or too dark. Here's an example (sc_darknebula):

image

The right image is after I've deduplicated models. The glowing whiteboard was used in an entity somewhere else in a brighter room. Now that it's sharing the same model with that entity, it looks too bright. To prevent that from happening, Cut the entity before doing Deduplicate Models, then Paste at original origin afterward. This prevents the model being considered for deduplication.

Merge Models (WIP)

Deduplication isn't always enough. In crystal_mission_1b, there are lots of func_wall entities that share a small area, but they aren't duplicates of each other. In this case, the models should be merged to form a larger model.

Take sc_propanic for example. It's a huge map that was designed before limits were raised.

image

These func_illusionary entities were all tied together to avoid overflowing the models limit. This is what has to be done for more recent Sven Co-op maps to get them running in Half-Life.

...

Oops. I haven't made a tool to merge models yet. That will come in v6 probably.

Map Too Big

Sven Co-op has increased the max world size from +/-4046 to +/-32768. Technically the new size is +/-131072 but the game breaks down at that distance. If not for the clipnode limit, this would be the most difficult problem to solve. Mappers tend to keep building and building until the compiler tells them to stop. Unless the map is designed very simply, the clipnode limit will overflow long before the +/-32k grid is full.

Below are solutions to fitting Sven Co-op maps inside the smaller Half-Life limits.

Move Worldspawn

In many cases this is what I see. A map is built using the upgraded limits, but it fits entirely inside a +/-4096 box. It's just placed in such a way that it doesn't run in HL as is. The fix for this is simple:

  1. Select worldspawn.
  2. Enable world boundary rendering with View -> Map Boundaries.
  3. Open the Transform widget.
  4. Move worldspawn until it fits inside the green box.
  5. Click Apply BSP Move.

image

It is very important that you click Apply BSP Move. Bad things happen when the worldspawn entity has a non-zero origin.

Split and Merge (Basic)

In this case, the map is larger than a +/-4096 box can contain, but it is composed of several enclosed areas connected with teleports. Mappers rarely fill up the vertical space of the mapping grid, so you can usually split and stack the disconnected areas to fit inside the cube. An example:

image

This map (mogul) is entirely out of bounds. I've tried shifting it inside of the green cube but it's not even close to fitting inside.

The good news is that the individual areas in this map can fit inside the cube by themselves, and all of them combined don't use more than 8192 units of vertical space. Here's how to split and merge the map to fit.

Isolate each area into its own BSP file

Follow these steps for each enclosed area:

  1. Move it inside the green box.
  2. Use Tools -> Delete BSP Data -> OOB __ Axis to delete the other areas.
  3. File -> Save a Copy As... and choose a unique name.
  4. File -> Reload to get the original map back.
  5. Repeat for the other areas.

ezgif com-optimize(1)

Merge the isolated areas back together

Merging multiple maps together is fastest using the CLI. You could use the File -> Merge option too but it can only merge 2 maps at a time (as of v5) and may not pack as tightly.

I named each of the 4 areas of mogul a.bsp, b.bsp, c.bsp, and d.bsp. The command I ran to merge them is:
bspguy merge mogul_hl -maps "a,b,c,d" -hl -noscript -noripent

-hl was added for the smaller world size of +/-4096. -noscript -noripent were added because I don't want bspguy to treat these BSPs as separate maps and try to adjust entity logic. It's the same map just split into pieces. Here's the result:

image

A perfect fit.

Split and Merge (Advanced)

This is the same process as with disconnected areas, except that you need to break up large areas and reconnect them with teleports.

Take bts_rc for example. The map doesn't use much vertical space, so it will fit in the cube if split and merged. However, the main area is too big on its own. A good split point needs to be found where you can place a relative teleport.

image

This looks like a good place to split. It's a hallway somewhat near the mid-point of the map that bisects the main area. It doesn't look like much action takes place there either. With some luck, players won't even notice they've been teleported.

Here's what each side looks like after isolating with Tools -> Delete BSP Data -> OOB __ Axis:

image

It shouldn't be split exactly in the middle of the hallway because then you will see the void when approaching the teleport (which has yet to be placed). You want to keep some faces around the corners too. There are some duplicated floating parts that should be removed with the Cull Box tool, but this gives you the general idea.

From here the process is the same as previous section. After you've merged the maps, place a relative teleport to connect the 2 halves. Click here for a tutorial.

Update Your Mod

A few maps have areas that are too big and and can't be split without significant drawbacks. Look at snd.bsp

image

There's no way I'm trying to split this one up. It doesn't use much vertical space, but the map is one giant area with no obvious splitting points. I would have to make relative teleports that stretch across the entire +/-4096 world boundary. Technically doable, but that would be a jarring effect to see in-game. Apaches and grunts would be popping in and out of existence regularly. A better option is to update your mod to support large maps like this.

Supporting larger maps in a Half-Life mod isn't difficult but it is a big job and requires a custom client. After you increase the limits of origins in delta.lst you need to replace all of the default NetworkMessage effects with custom ones that can use larger values for world coordinates (things like SVC_SOUND and SVC_TEMPENTITY).

Broken Ladders

If a func_ladder has a non-zero origin attached to it, you're gonna have a bad time. Assuming you don't crash instantly on contact, you will be launched backward at warp speed and probably die from fall damage.

The fix is easy. Duplicate the ladder's BSP model and move its Origin to 0 0 0 with the Transformation widget (set Target to Origin). This can also be done with Tools -> Zero Entity Origins. This tool automatically zeroes origins on problematic entities (func_ladder, func_water, func_mortarfield).

This problem generally only happens in merged Sven Co-op maps (my fault). Ladders don't usually have Origin brushes attached unless they are very far from the map origin. In that case an origin brush prevents horrible prediction issues.

Broken Lightmaps

Lightmaps break any time you scale faces, subdivide faces, or downscale textures. To fix the lightmaps, you need to run the map through a RAD compiler to regenerate them. The process is usually as simple as copying hlrad.exe and lights.rad to the same folder as the map, then running this command:

hlrad.exe -estimate -nobleedfix -notextures -bounce 3 -extra mapname.bsp

  • -nobleedfix fixes choppy lighting if faces were subdivided
  • -notextures allows you to recompile without finding all the WADs and moving them to the same folder. Remove this if the recompiled lighting looks significantly different.

Both of these options are unique to vluzacn's compile tools (VHLT). The Sven Co-op compilers (SCHLT) are based on VHLT.

The other options make the lighting look smoother and more natural (-bounce 3 and -extra). You might want to compile with -fast instead of -extra for testing.

Texture Lights

Your lights.rad file needs to match what the mapper used. If it doesn't, the lighting will look wrong and/or some areas will be pitch black. In most cases the default file provided with Hammer is used. Here's a map (bmsl) that uses a custom lights.rad entry:

image

When recompiled with the default lights.rad, this vent becomes pitch black. To fix that:

  1. Disable View -> Textures so that you can see the lightmap colors.
  2. Find the texture emitting light (~spotred in this case).
  3. Take a screenshot and use the eyedropper tool in paint.net or something to get an RGB value for the light.

image

The color of this light is 208 0 0. Add the following line in lights.rad:
~spotred 208 0 0 1000
The brightness value 1000 is just a guess. Add or remove zeros until the brightness matches the original map.

RAD Settings

It's not always enough to have an accurate lights.rad file. Sometimes you need to reconfigure RAD too. Here's an example (con3_3):

image

There's no light entity here so this must be a texture light. Disable View -> Textures to see the lightmap colors:

image

Huh? How is this white light coloring the walls orange?

The answer is Texture Reflection. This is a relatively new feature in VHLT. Textures in the map affect the light that bounces off of them. In this case, the lava texture is mostly orange. The white light from that same texture is reflecting off itself to create an orange tint.

To get the same effect when recompiling, remove -nowadtextures from the command and copy all needed WADs into the same folder. Texture reflection is enabled by default in VHLT but can't be used with -nowadtextures. The program will complain if you forget a WAD. If that happens, delete the mapname.err file and try again.

There are many other RAD settings that affect light colors and brightness. In my experience, the default settings often get you close enough to the original lighting, but you might want to experiment. I've used -minlight and -ambient in my own maps to brighten up dark areas. I also know a mapper who uses -bounce 1 for sharper lighting. -scale and -gamma could be used to change the overall brightness of the map. Other settings I don't think would be be touched often.

Example Workflow

Now that you understand how to solve all these problems in depth. Here's a workflow I follow to port a map that has every possible problem.

  1. Move the world or split+merge if it doesn't fit in the +/-4k box.
  2. Tools -> Textures -> Downscale Invalid
  3. Tools -> Scale Invisible Faces
  4. Tools -> Zero Entity Origins
  5. Tools -> Deduplicate Models
  6. Tools -> Fix Bad Extents -> Downscale 256x256
  7. Tools -> Fix Bad Extents -> Subdivide
  8. hlrad.exe -estimate -nobleedfix -notextures -bounce 3 -extra mapname.bsp
  9. Test

This process is most likely to fail at the subdivision step. wpoly may be too high or some limit may be overflowing. In that case I will undo the subdivide and selectively scale/downscale and try again.

I use other programs for converting invalid MDL, SPR, TGA, and audio files.