-
Notifications
You must be signed in to change notification settings - Fork 3
Architecture Overview
Q2RTXPerimental builds upon the Quake 2 foundation with a modernized C++ architecture, enhanced entity system, and improved separation of concerns. This page provides a high-level overview of the engine's design.
Q2RTXPerimental maintains Quake 2's client-server separation while modernizing the implementation:
Preserved from Vanilla:
- Client-server architecture for network transparency
- DLL-based game modules for modding flexibility
- Entity-centric game world
- BSP-based level geometry
Modernizations:
- C++ OOP: Class hierarchies replace C structs and function pointers
- Type Safety: Strong typing and compile-time checks
-
Three-Module Design:
clgame,svgame,sharedgamewith clear boundaries - Enhanced Systems: Lua scripting, skeletal animation, signal I/O
- Higher Tick Rate: 40 Hz server update rate (vs. 20 Hz in vanilla)
┌────────────────────────────────────────────────────────────┐
│ Q2RTXPerimental │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Client Side │ │ Server Side │ │
│ ├──────────────────┤ ├──────────────────┤ │
│ │ │ │ │ │
│ │ Engine Client │◄────────────►│ Engine Server │ │
│ │ - Rendering │ Network │ - Networking │ │
│ │ - Input │ or │ - Physics │ │
│ │ - Sound │ Loopback │ - Collision │ │
│ │ - UI │ │ - Level Mgmt │ │
│ │ │ │ │ │
│ ├──────────────────┤ ├──────────────────┤ │
│ │ │ │ │ │
│ │ clgame.dll │ │ svgame.dll │ │
│ │ (Client Game) │ │ (Server Game) │ │
│ │ - Prediction │ │ - Entities │ │
│ │ - Interpolation │ │ - AI │ │
│ │ - Effects │ │ - Combat │ │
│ │ - HUD │ │ - Physics │ │
│ │ │ │ - Lua Scripts │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
│ │ ┌──────────────────┐ │ │
│ └───────►│ sharedgame.lib │◄─────┘ │
│ │ (Shared Code) │ │
│ │ - Entity Types │ │
│ │ - Events │ │
│ │ - Game Modes │ │
│ │ - Player Move │ │
│ │ - Animation │ │
│ └──────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
Location: src/client/, src/server/, src/common/, src/refresh/
The engine provides low-level systems:
-
Rendering: Vulkan-based path-traced renderer (
src/refresh/vkpt/) - Networking: Client-server communication, snapshot delta compression
- Asset Loading: BSP, models, textures, sounds
- Input/Output: Keyboard, mouse, gamepad, audio output
- Console/Commands: Command system, cvars, scripting
- File System: PAK/PKZ archive support
The engine is written in C and provides a C API to game modules.
Location: src/baseq2rtxp/svgame/
Output: game.dll / game.so
The authoritative server-side game logic:
svgame/
├── entities/ # Entity class hierarchy
│ ├── svg_base_edict.h/.cpp # Base entity class
│ ├── func/ # Brush entities (doors, platforms)
│ ├── trigger/ # Trigger entities
│ ├── misc/ # Miscellaneous entities
│ ├── monster/ # Monster entities
│ └── ...
├── gamemodes/ # Game mode implementations
├── lua/ # Lua scripting integration
├── monsters/ # AI implementations
├── player/ # Player-specific logic
├── save/ # Save/load system
├── svg_main.cpp # Module entry point
├── svg_spawn.cpp # Entity spawning
├── svg_physics.cpp # Physics simulation
├── svg_combat.cpp # Damage and combat
├── svg_ai.cpp # AI utilities
└── ...
Key Responsibilities:
- Entity lifecycle management (spawn, think, die)
- Physics simulation and collision detection
- AI behavior and pathfinding
- Combat system (damage, health, death)
- Game rules and scoring
- Lua script execution
- Authoritative game state
See: Server Game Module
Location: src/baseq2rtxp/clgame/
Output: cgame.dll / cgame.so
Client-side presentation and prediction:
clgame/
├── effects/ # Visual effect implementations
├── hud/ # HUD element implementations
├── local_entities/ # Client-local entities
├── packet_entities/ # Server entity processing
├── temp_entities/ # Temporary entity handlers
├── clg_main.cpp # Module entry point
├── clg_predict.cpp # Client-side prediction
├── clg_packet_entities.cpp # Entity interpolation
├── clg_temp_entities.cpp # Temp entity spawning
├── clg_effects.cpp # Effect systems
├── clg_view.cpp # View calculation
├── clg_hud.cpp # HUD rendering
└── ...
Key Responsibilities:
- Client-side prediction (player movement)
- Entity interpolation (smooth movement between snapshots)
- Temporary entities (particles, beams, effects)
- Visual effects (muzzle flashes, explosions, blood)
- HUD rendering (health, ammo, scores)
- View calculation (bobbing, kick, offsets)
- Event handling (footsteps, weapon sounds)
See: Client Game Module
Location: src/baseq2rtxp/sharedgame/
Output: Static library linked into both clgame and svgame
Code and data structures shared between client and server:
sharedgame/
├── pmove/ # Player movement code
├── game_bindings/ # Lua bindings
├── sg_entity_types.h # Entity type constants
├── sg_entity_flags.h # Entity flag constants
├── sg_entity_events.h # Entity event types
├── sg_tempentity_events.h # Temp entity events
├── sg_gamemode.cpp # Game mode definitions
├── sg_skm.cpp # Skeletal animation system
├── sg_skm_rootmotion.cpp # Root motion system
├── sg_usetarget_hints.cpp # Use target hint system
└── ...
Key Responsibilities:
- Entity type definitions (used by both client and server)
- Entity event definitions (synchronized between client/server)
- Player movement code (client prediction needs same code as server)
- Game mode definitions
- Skeletal animation system
- Constants and enumerations
Design Rationale:
- Prediction: Client needs identical movement code to predict player position
- Consistency: Ensures client and server interpret data identically
- DRY Principle: Avoid duplicating code between modules
- Type Safety: Shared enums prevent client/server mismatch
See: Shared Game Module
Every server frame (25ms at 40Hz):
-
Server Simulation
SVG_RunFrame() { ProcessPlayerCommands(); // Apply player input RunEntityThink(); // Execute entity logic RunPhysics(); // Simulate movement/collision BuildSnapshots(); // Package state for clients }
-
Snapshot Creation
- Server creates snapshot containing:
- Player state (position, health, ammo, etc.)
- Visible entities (in PVS - Potentially Visible Set)
- Events (weapon fire, footsteps, etc.)
- Delta compressed against client's last acknowledged snapshot
- Server creates snapshot containing:
-
Network Transmission
- Snapshot sent via UDP (unreliable with ack)
- Reliable messages sent separately (important events)
-
Client Reception
CLG_ParseFrame(snapshot) { UpdatePlayerState(); // Update local player ParsePacketEntities(); // Update entity states ProcessEvents(); // Handle entity events ProcessTempEntities(); // Spawn temp effects }
-
Client Rendering
CLG_AddEntities() { InterpolateEntities(); // Smooth between snapshots PredictPlayerMovement(); // Predict ahead AddViewWeapon(); // Render weapon AddEntities(); // Add to render scene }
Every client frame:
-
Input Sampling
CL_CreateCmd() { SampleInput(); // Read keyboard/mouse BuildUserCommand(); // Create usercmd_t }
-
Command Transmission
- User command sent to server
- Includes: angles, movement, buttons, impulse
-
Server Processing
SVG_ClientThink(client, cmd) { ValidateCommand(cmd); // Anti-cheat checks PlayerMove(client, cmd); // Apply movement ProcessButtons(cmd); // Handle fire, use, etc. }
Q2RTXPerimental uses an OOP entity hierarchy:
svg_base_edict_t (Base class for all entities)
├── svg_player_edict_t (Player entities)
├── svg_item_edict_t (Pickup items)
├── svg_pushmove_edict_t (Moving brush entities)
│ ├── svg_func_door_t
│ ├── svg_func_plat_t
│ └── svg_func_train_t
├── svg_worldspawn_edict_t (World entity)
└── (Many entity types...)
├── Triggers (trigger_hurt, trigger_teleport)
├── Monsters (monster_soldier, monster_tank)
├── Misc (misc_teleporter, info_player_start)
└── Targets (target_speaker, target_explosion)
Base Class Features:
- Virtual callback methods (Spawn, Think, Touch, Use, etc.)
- Entity state (
entity_state_t s) for networking - Save/load system with automatic serialization
- Signal I/O system for entity communication
- UseTargets system for interactive entities
- Entity dictionary for spawn parameters
Vanilla:
edict_t *ent = G_Spawn();
ent->classname = "monster_soldier";
ent->think = soldier_think;
ent->touch = soldier_touch;Q2RTXPerimental:
auto *soldier = SVG_Spawn<svg_monster_soldier_t>();
soldier->Spawn(); // Calls virtual method
// Think, Touch, etc. are virtual methodsBenefits: Type safety, inheritance, virtual dispatch, less boilerplate
- Higher Tick Rate: 40 Hz (vs. 20 Hz)
- Improved Delta Compression: More efficient bandwidth usage
- Enhanced Prediction: Better client-side prediction accuracy
Server-side Lua integration for game logic:
-- Define custom entity behavior in Lua
function OnEntitySpawn(entity)
entity:SetHealth(100)
entity:SetModel("models/custom.iqm")
end
function OnEntityThink(entity)
-- Custom AI logic
endSee: Lua Scripting
Support for skeletal (SKM) models with:
- Bone-based animation
- Animation blending
- Root motion (animation-driven movement)
- IQM format support
See: Skeletal Animation, Root Motion
Entity communication through signals:
// Entity A emits signal
EmitSignal("door_opened", this);
// Entity B listens for signal
RegisterSignalHandler("door_opened", &MyEntity::OnDoorOpened);See: Signal I/O System
Enhanced player interaction with entities:
class svg_func_button_t : public svg_base_edict_t {
UseTargetHint GetUseTargetHint() override {
return UseTargetHint::PRESSABLE; // Shows "Press E" hint
}
void OnUseTargetPressed(svg_base_edict_t *user) override {
// Handle button press
}
};See: UseTargets System
Entities are allocated from a fixed pool:
- Fast allocation/deallocation (no malloc/free)
- Cache-friendly (contiguous memory)
- Deterministic performance
Not all entities think every frame:
- Entities set
nextThinkTimefor when they need to think - Engine only calls think on entities that need it
- Reduces CPU load for idle entities
- PVS culling: Only send entities visible to player
- Delta compression: Only send changed fields
- Priority system: Important entities updated more frequently
- Area grid for fast collision queries
- BSP tree for visibility determination
- Frustum culling for rendering
- Create header/cpp in
src/baseq2rtxp/svgame/entities/ - Derive from
svg_base_edict_t - Implement virtual methods (Spawn, Think, etc.)
- Register with spawn system using
DECLARE_EDICT_SPAWN_INFO - Add to CMakeLists.txt
- Create effect handler in
src/baseq2rtxp/clgame/effects/ - Register with temp entity system or entity event handler
- Implement rendering/particle logic
- Add to
src/baseq2rtxp/sharedgame/ - Use
#ifdef SERVER/#ifdef CLIENTif needed - Ensure both modules can access the code
During development:
+set developer 1
# Make code changes
# Rebuild
game_restart # Reload game DLLs without restarting engine
Q2RTXPerimental game modules are single-threaded:
- Game logic runs on main thread
- No synchronization needed for game code
- Engine may use threads for rendering, but game modules don't see this
Important: Don't use threads in game module code without careful synchronization.
Now that you understand the architecture, dive deeper into specific modules:
- Server Game Module - Detailed svgame documentation
- Client Game Module - Detailed clgame documentation
- Shared Game Module - Detailed sharedgame documentation
- Entity System Overview - Entity architecture in depth
- Engine Integration - How game modules communicate with engine
Previous: Vanilla Game Module Architecture