Source Engine Entity Architecture: Design Constraints for Modding and Performance
Source Engine's 2048 entity limit and 66Hz physics tick rate create hard constraints for modders. This guide covers the CBaseEntity hierarchy, I/O system pitfalls, and verification strategies for performance-critical maps.
02 May 2026, 15:44 UTC

The Entity Limit Problem
When developing for Source Engine, the first constraint you'll encounter is the 2048 active entity limit. This isn't a soft guideline—it's a hardcoded ceiling where performance degrades noticeably as you approach it. Each prop, NPC, trigger, and logic entity consumes from a fixed pool, and the linear allocator backing this pool can fragment under heavy spawn/despawn cycles.
Smallest Suitable Design
The core architecture centers on CBaseEntity, the base class all entities inherit from. Keyvalues parsed at map load time allow customization without code changes. This hierarchical system enables rapid iteration: designers wire outputs to inputs through the I/O system, creating gameplay logic without recompiling.
However, this flexibility has sharp edges. The I/O system lacks built-in debouncing, so connecting an entity's output directly to its own input creates feedback loops that cascade uncontrollably. A trigger_multiple firing every frame can spawn hundreds of NPCs instantly, locking the simulation.
Trust and Data Boundaries
Physics objects in Source Engine are constrained ragdoll primitives with configurable mass and friction. They simulate at 66Hz, which causes tunneling with fast-moving small objects—a 1-unit cube traveling at high velocity may pass through thin brushes without collision detection.
Sound entities use positional audio with distance attenuation and doppler calculation built into the engine. Particle systems are defined through VMT files, with dynamic emission controlled by entity inputs and game events. These systems share the same entity pool, so spawning 1000 particle emitters impacts performance just like 1000 physics props.
Operational Checks
Before shipping, verify these constraints:
- Entity Count: Spawn 2000+ physics props in debug mode and measure frame time. Use
ent_dumpallconsole command to count active entities. - IO Loop Prevention: Create a
trigger_multiplethat spawns 100 NPCs and confirm no cascade loops occur. Addfilter_activator_teamentities to gate inputs. - Physics Validation: Launch a prop through complex brush geometry and observe collision response. Fast projectiles need
prop_physics_overridewith increased collision bounds. - BSP Compile Scaling: Profile compile times using a map with 5000 brushes versus 500 brushes. Hammer's grid-based brushwork creates static world geometry compiled into BSP format with visleaf optimization—complexity scales exponentially.
Failure Modes and Design Changes
If profiling reveals:
- Frame time spikes above 16ms: Reduce entity count by combining triggers, using
logic_autofor initialization, and pooling reusable props instead of spawning/despawning. - Physics tunneling artifacts: Implement swept collision tests in custom entities or reduce object velocities below the 66Hz threshold.
- Compilation timeouts: Split large maps into smallervisleaf-optimized sections or simplify brush topology before final compile.
- Memory fragmentation: Pre-allocate entity pools for frequently spawned object types and reuse
CFlashlightEffectinstances rather than recreating them.
The Hammer editor's grid-based workflow creates static geometry that scales poorly during BSP compilation. For maps exceeding 10,000 brushes, consider using func_detail groups liberally to defer complexity to runtime rather than compile time.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.