


Valheim Build Optimization is a BepInEx client-side performance mod for bases.
During testing on a 24,000-instance base, average FPS increased from around 20 FPS to around 55 FPS.
That is an average gain of about 35 FPS, or roughly a 175% increase over the original average.
Results will vary depending on hardware, base layout, active effects, mod configuration, and other installed mods.
If you encounter any issues or have suggestions, please don’t hesitate to DM me on Discord: draco_alias
Its main job is to reduce the rendering, lighting, particle, structural, and optional diagnostic work caused by thousands of loaded build pieces. The mod is designed around targeted optimizations. Expensive systems are changed only when the mod can identify a reasonably safe case, and experimental systems have separate switches so they can remain completely dormant during normal play.
The project began as a build-piece profiler, so it also includes an optional in-game diagnostics window for measuring scene, rendering, physics, fire, batching, collider, script, and network activity.
Special thanks to u/keepoursxnity for providing detailed testing feedback and designing the mod icon! I also want to thank the Valheim community for the support, suggestions, and encouragement.
Install ValheimBuildOptimization.dll in:
Valheim/BepInEx/plugins/
Configuration is created in:
Valheim/BepInEx/config/valheim.buildpieceprofiler.cfg
The BepInEx Configuration Manager mod is recommended, but not required.
For ordinary use:
StaticLight fire mode.Existing configuration files keep their previous values after an update. Use Configuration Manager's Reset button if you want a setting to return to its new default.
Fire optimization works on supported fireplaces, torches, sconces, braziers, hearths, bonfires, and similar fire pieces.
If monkey put fire sticks in banana house, banana house warm, banana house pretty, monkey happy.
But monkey put many fire sticks. Maybe too many fire sticks. Fire sticks not lazy. Fire sticks wiggle flame, throw sparkle, make light, think about shadow, do tiny fire dance. Fire sticks eat FPS banana.
If monkey looking at fire, okay. Fire doing fire job. Monkey see pretty fire.
But if monkey not looking at fire, fire still working. Monkey on other side of house staring at stone wall, but fire sticks still dance like monkey watching. Computer still work. FPS banana still disappear.
Mod says, "Why fire work? Monkey no see fire."
So hidden fire goes sleepy. Big expensive light takes nap. Tiny cheap banana glow stays awake so room still look warm and not dead.
When monkey look back, fire wakes up fast. Fire wiggle again. Fire sparkle again. Fire says, "I was working. Promise."
Same banana house. Same pretty fire when monkey see it. Less invisible fire work when monkey no see it. More FPS banana for monkey.
The fire system discovers only Fireplace components whose piece names match a configurable known-fire allowlist and which contain both lights and particle systems. This avoids treating unrelated decorative effects as fire.
Each candidate is evaluated at a configurable interval rather than every frame. Visibility is determined using the main camera frustum. Optional occlusion testing uses budgeted non-allocating raycasts or spherecasts from the camera to the fire. Occlusion results are cached and only a limited number of fresh physics checks may occur during one optimizer update.
The optimizer uses grace periods in both directions:
These delays reduce state cycling when the camera passes wall edges or the player turns rapidly.
When a fire enters an optimized state, the mod records:
The original fire lights are disabled and removed from rendering through their culling masks. Fire particle emission is disabled and existing particles are cleared. Known fire-light update scripts are paused so vanilla logic does not continually fight the optimized state.
When the fire becomes relevant again, recorded states are restored. The mod checks whether the source is still burning before relighting visual systems, which prevents extinguished fires from being incorrectly restored as lit.
Monkey cannot see ten fire sticks behind wall.
Monkey only sees warm glow through doorway.
Ten fancy lights still do ten jobs. Ten lights eat FPS banana for one glow.
Mod says, "Monkey need glow, not ten tiny suns."
Hidden lights go sleepy. One simple banana lamp covers the group.
Room still cozy. Computer less sad. More FPS banana.
StaticLight disables hidden original fire lights and effects but preserves approximate illumination using shadowless proxy lights.
Proxy lights are pooled. They are reused instead of repeatedly created and destroyed, reducing managed allocations and Unity object churn.
When clustered proxy lights are enabled, hidden fires inside the same world-space cell contribute to one representative light. The cluster averages source position and color, combines intensity conservatively, uses the largest source range, and disables real-time shadows. Each cluster light is updated once per optimizer cycle.
This mode is intended to preserve the visual impression of illuminated rooms while reducing the number of dynamic lights, shadow calculations, flicker scripts, and active particle systems.
Fire hidden behind three stone walls.
Monkey see no flame. Monkey see no glow. Monkey see only rock.
Mod says, "Why fire dance? Monkey see nothing."
Whole fire show goes sleepy until monkey can see it again.
No wasted fire dance. More FPS banana.
FullCull disables hidden original lights, particle emission, fire particles, light-update scripts, and fire mesh shadow casting without creating a replacement light.
It provides the most aggressive fire reduction, but hidden rooms may become visibly darker through openings. StaticLight is the recommended visual-quality mode.
Without batching, monkey gives painter many tiny jobs.
"Paint wall one. Paint wall two. Paint wall three. Paint wall four."
Painter hears many orders. Computer gets tired. FPS banana gets eaten.
With batching, mod says, "These walls live together. Make one big paint job."
Monkey points at whole wall section and says:
"Paint this big wall chunk."
Same stones. Same building. Less yelling at painter. More FPS banana.
Renderer batching divides the world into configurable spatial cells. Eligible opaque, static build-piece renderers inside each cell are grouped by compatible rendering state and combined into generated meshes.
The system keeps original colliders and gameplay components. It disables only source renderers represented by a successful combined mesh.
Pieces are excluded when they are likely to change or require independent rendering. Examples include:
Compatible renderers are grouped by material, layer, shadow mode, probe settings, sorting state, and other rendering properties. Groups are split when they would exceed the configured vertex limit.
Piece discovery and cell rebuilding are budgeted across frames. Placement, destruction, damage, repair, and health changes mark only the affected spatial cell for delayed rebuilding. The delay coalesces rapid construction changes into fewer mesh rebuilds.
Highlighting a batched piece temporarily enables only that piece's original renderers instead of destroying and rebuilding the entire cell.
The system caches stable eligibility, renderer arrays, and LOD classification to avoid repeatedly scanning the same component hierarchies.
Renderer batching is currently the highest-impact optimization in very large static bases.
Monkey squishes many walls into one big painting.
Painter happy. Computer happy.
But sun still remembers old walls.
"Wall one, make shadow. Wall two, make shadow. Wall three, make shadow."
Too many shadow chores. FPS banana gets nibbled.
Shadow optimizer tries to give sun fewer, simpler shadow shapes.
Good idea, but sun is picky. Sometimes sun no like banana math.
Feature is experimental, so it sleeps behind master switch until monkey says yes.
The shadow system is an experimental extension of renderer batching. It considers compatible opaque combined batches that still cast real-time shadows and attempts to replace several shadow casters with one shadow-only mesh.
Clusters are accepted only when they meet configurable requirements for:
Exact shadow meshes contain position and triangle data without unnecessary normals or visual-material data. Source mesh geometry may be cached while exact clusters are active.
An optional simplified structural mode represents configured walls and roofs with box-like shadow geometry. This can greatly reduce vertex cost, but may change shadow silhouettes.
Shadow optimization is disabled by default because exact shadow clusters can cost more geometry and culling precision than the draw calls they save. It should be tested separately from renderer batching.
Normally every building piece keeps asking:
"Am I supported? Is roof above me? Is rain touching me? Am I wet now? How about now?"
Many pieces ask many questions. Computer keeps answering. FPS banana gets chewed.
But monkey base has no collapse and no rain damage anyway.
Mod says, "Why ask about danger that no exist?"
Every piece gets permanent strong banana pillar and permanent invisible umbrella.
Piece feels safe. Piece stops asking. Computer gets quiet.
Same base. Less nervous wood. More FPS banana for monkey.
When enabled, the mod bypasses selected WearNTear methods:
The system intentionally does not bypass:
This changes gameplay. Structures effectively have full support, and rain or weather does not make affected pieces wet. Enable it only when that behavior is desired.
Some decorations look still, but tiny monkey inside keeps turning wheel every frame.
Decoration no move. Decoration no change. Tiny monkey still work. FPS banana gets nibbled.
Sleeper wants to tell approved tiny monkey:
"Take nap. Wake up only when building changes."
Good for boring decorations.
But mod must be careful. Some tiny monkeys look decorative, but secretly run machine. Wrong monkey takes nap, machine breaks.
So feature is cautious and experimental. Only approved tiny monkeys may sleep.
This system profiles build-piece MonoBehaviour types that implement Update, LateUpdate, or FixedUpdate.
Sleeping is restricted by:
Approved components may be disabled after a grace period. Placement, damage, repair, health changes, or highlighting restores them and schedules another sleep attempt.
The system currently has no broadly proven default target in vanilla Valheim, so its master switch, profiling, and sleeping are disabled by default. When the master switch is off, it performs no discovery or event work.
Base floor has ten thousand tiny invisible boxes.
Boxes tell monkey where feet can stand. Boxes also tell hammer which floor piece monkey is pointing at.
Collider clustering says, "Why many tiny boxes in straight line? Make one long banana box."
Fewer boxes means less work. Computer happy.
But monkey hammer is picky. Monkey selection ray still needs to know exact floor piece that got hit.
If wrong box answers, monkey bonks wrong floor. Bad banana.
So feature is clever, risky, and currently experimental.
The collider system searches for enabled, non-trigger, static BoxCollider components on eligible structural pieces.
It excludes:
Compatible boxes are grouped by layer and physics material. Only world-axis-aligned, collinear boxes with matching cross-sections may merge.
Original colliders remain active near the player. Beyond the configured activation distance, eligible cells may enable merged colliders and disable represented originals. A hysteresis gap separates activation and restoration distances.
Generated clusters include a damage proxy that maps a hit back to the nearest original source piece. This cannot perfectly preserve every possible collider lookup or mod interaction.
The system has measured as a performance loss in current large-base tests. It remains available for development but is disabled behind a master switch.
Monkey wants to know which machines keep sending banana paperwork.
Profiler watches paperwork. Profiler counts who writes most forms, who yells most, and who keeps bothering server.
Profiler does not stop paperwork. Profiler only spies and makes report.
Useful when monkey is testing. Not useful for normal banana life.
Leave spy monkey home unless monkey is hunting network problem.
The network diagnostics system maps build-piece ZNetView components to their ZDO records and can measure:
Global write and RPC hooks are installed dynamically only while both the network master switch and profiling option are enabled. They are removed when the system is disabled.
This system does not currently throttle networking. It is a diagnostic foundation for future work and is disabled by default.
Monkey opens clipboard, counts everything, writes numbers, then closes clipboard.
Counting helps monkey see what is eating FPS banana.
But counting giant base also takes work. Clipboard monkey is not free.
Use when testing. Do not ask monkey to recount every second unless monkey actually needs numbers.
Press the configured profiler key, F7 by default, to open the in-game window when the profiler is enabled.
The window is organized into collapsed sections:
Hover any section or statistic for a plain-language explanation.
Poll now performs a synchronous full snapshot. It scans loaded objects and build-piece child components, so a huge world may briefly pause. Automatic polling is disabled by default and is automatically suppressed above the built-in large-world safety limit.
The profiler and automatic polling are both disabled by default for normal gameplay.
Before testing an experimental feature, back up important worlds and change one system at a time. Record FPS and frame time from the same location, camera direction, weather, and graphics settings.
Enable Profiler.EnableProfiler, then press the configured profiler key.
BepInEx preserves values already written to the configuration file. Reset the individual setting in Configuration Manager, or remove the configuration file while the game is closed to regenerate every default.
Add a unique fragment of its internal prefab or object name to Fire Discovery.KnownFirePieceNameTokens. The object must also use a Fireplace component and contain both a light and a particle system.
Reduce the fire restore grace period or occlusion cache duration. Very low values can make state changes more noticeable.
Add a unique internal name fragment to Renderer Batching.ExcludedPieceNameTokens, then reload or toggle renderer batching.
Disable that system's EnableSystem master switch. Collider clustering, shadow optimization, static component sleeping, and network diagnostics are development foundations, not recommended default optimizations.