Repository navigation
Large World Support
SparkEngine provides two systems for supporting game worlds larger than traditional floating-point precision allows: WorldOriginSystem for floating-point origin rebasing, and SeamlessAreaManager for seamless area streaming without loading screens.
Both systems are inspired by HeroEngine's Seamless World 2.0 technology.
In a standard game engine, all positions are stored as 32-bit floats relative to the world origin (0,0,0). At large distances (>5000 units), floating-point precision degrades causing:
- Visual jitter on meshes and particles
- Physics instability (objects vibrating, falling through floors)
- Audio positioning errors
WorldOriginSystem periodically shifts all entity positions so the player is always near the local origin. The accumulated offset is tracked, enabling conversion between local and absolute coordinates at any time.
Spark::World::WorldOriginSystem originSystem;
originSystem.SetRebasingThreshold(5000.0f); // Rebase when player > 5000 units from origin
originSystem.SetEnabled(true);
// Register callbacks so other systems (physics, audio, particles) shift too
originSystem.RegisterRebaseCallback([](const XMFLOAT3& offset) {
// Shift physics world, audio listeners, particle emitters, etc.
});
// In the game loop:
bool rebased = originSystem.Update(registry, playerPosition);- Each frame,
Update()checks the player's distance from the local origin - If the distance exceeds the threshold,
ForceRebase()is called - All root entity transforms are shifted (children inherit via hierarchy)
- Registered callbacks are fired so external systems can shift their data
- The accumulated offset is tracked for coordinate conversion
// Convert local position to absolute world coordinates
XMFLOAT3 absolute = originSystem.LocalToAbsolute(localPos);
// Convert absolute world coordinates to local
XMFLOAT3 local = originSystem.AbsoluteToLocal(absolutePos);| Method | Description | Default |
|---|---|---|
SetRebasingThreshold(float) |
Distance that triggers rebasing | 5000.0 |
SetEnabled(bool) |
Enable/disable rebasing | false |
RegisterRebaseCallback(fn) |
Register shift notification | — |
SeamlessAreaManager divides the game world into areas (tiles) and manages their loading/unloading based on player position. Adjacent areas share configurable border overlap regions where entities from both areas coexist.
Spark::Streaming::SeamlessAreaManager areaManager;
areaManager.Initialize();
// Define world areas
WorldArea town;
town.name = "Town";
town.scenePath = "Scenes/Town.scene";
town.worldPosition = {0, 0, 0};
town.worldSize = {1000, 100, 1000};
town.loadDistance = 2000.0f;
town.unloadDistance = 3000.0f;
areaManager.RegisterArea(town);
WorldArea forest;
forest.name = "Forest";
forest.scenePath = "Scenes/Forest.scene";
forest.worldPosition = {1000, 0, 0};
forest.worldSize = {1000, 100, 1000};
areaManager.RegisterArea(forest);
// Define border overlap
areaManager.DefineBorder("Town", "Forest", 50.0f);
// In game loop:
areaManager.Update(playerPosition, deltaTime);| State | Description |
|---|---|
Unloaded |
Not loaded, no resources consumed |
Loading |
Being loaded in the background |
Active |
Fully loaded and simulating |
Border |
In border overlap mode |
Unloading |
Being unloaded |
Failed |
Loading failed |
Border regions are overlap zones between adjacent areas. When a player enters a border region, both areas are loaded and active, ensuring seamless gameplay across boundaries.
// Check if player is in a border region
if (areaManager.IsInBorderRegion()) {
// Both areas are active — entities from both are visible
}
// Translate position between area coordinate spaces
XMFLOAT3 forestPos = areaManager.TranslatePosition(townPos, "Town", "Forest");areaManager.RegisterTransitionCallback([](const AreaTransitionEvent& event) {
// event.fromArea, event.toArea, event.crossingPosition, event.isSeamless
LOG("Player transitioned from %s to %s", event.fromArea, event.toArea);
});| Field | Type | Default | Description |
|---|---|---|---|
name |
string |
— | Unique area name |
scenePath |
string |
— | Path to area's scene file |
worldPosition |
XMFLOAT3 |
(0,0,0) | Position in absolute world space |
worldSize |
XMFLOAT3 |
(1000,100,1000) | Area dimensions |
localOrigin |
XMFLOAT3 |
(0,0,0) | Local coordinate origin offset |
loadDistance |
float |
2000.0 | Distance to start loading |
unloadDistance |
float |
3000.0 | Distance to unload |
priority |
int |
0 | Loading priority |
alwaysLoaded |
bool |
false | Never unload this area |
For the best large-world experience, use both systems together:
- WorldOriginSystem keeps the player near the local origin for precision
- SeamlessAreaManager streams area scenes in/out based on proximity
- Each area can define its own
localOriginfor coordinate translation
Integration with Area Server Architecture enables server-side area management for multiplayer worlds.
| File | Description |
|---|---|
SparkEngine/Source/Engine/World/WorldOriginSystem.h |
Origin rebasing system |
SparkEngine/Source/Engine/World/WorldOriginSystem.cpp |
Origin rebasing implementation |
SparkEngine/Source/Engine/Streaming/SeamlessAreaManager.h |
Seamless area manager |
SparkEngine/Source/Engine/Streaming/SeamlessAreaManager.cpp |
Seamless area implementation |
SparkEngine/Source/Engine/Streaming/DirectStorageLoader.h |
Async storage/loading |
SparkEngine/Source/Engine/Streaming/SceneManifest.h |
Area manifest parsing and its budgets |
SparkEngine/Source/Engine/Streaming/AreaAssetLoader.h |
Consumption point for manifest asset paths |
-
ParseFromStringandParseFromFileboth enforceMAX_SCENE_MANIFEST_BYTES(8 MB).ParseFromFilefails closed whenstd::filesystem::file_sizereports an error — a path that can be opened but not stat'd yields an empty manifest rather than an unbounded read. -
MAX_SCENE_MANIFEST_ENTRIES(100000) truncation is logged once instead of dropping silently. - Path containment for streamed assets is enforced at the consumption point,
AreaAssetLoader::BeginAreaLoad, not in the parser: aSceneManifestcan be built in code (SeamlessAreaManagerdoes) and handed toSetManifestwithout ever passing throughParseFromString, so the parser'sIsVirtualPathSafefilter is defence in depth only. - When every declared path is rejected the area still completes — the callback fires — rather than hanging on a completion count that can never be reached.
-
SceneManifest.hincludesEngine/Modding/VirtualFileSystem.h, soAreaAssetLoader.h,SeamlessAreaManager.hand the OpenWorld game module carry a link dependency on the Modding TU.
When Update() detects the reference position exceeds the threshold, it calls ForceRebase():
// Pseudocode of the rebasing process
void WorldOriginSystem::ForceRebase(entt::registry& registry, const XMFLOAT3& offset)
{
// Step 1: Shift all entity Transform positions
ShiftAllTransforms(registry, offset);
// Step 2: Accumulate the offset for coordinate conversion
m_accumulatedOffset.x += offset.x;
m_accumulatedOffset.y += offset.y;
m_accumulatedOffset.z += offset.z;
// Step 3: Notify all registered callbacks (physics, audio, particles, navmesh)
NotifyCallbacks(offset);
// Step 4: Update statistics
m_stats.totalRebases++;
m_stats.currentOffset = m_accumulatedOffset;
}ShiftAllTransforms() iterates over every entity with a Transform component and subtracts the offset from its position. Child entities inherit the shift through the hierarchy, so only root transforms need adjustment.
Every system that maintains world-space positions independently of the ECS must register a callback:
Spark::World::WorldOriginSystem originSystem;
// Physics system callback
originSystem.RegisterRebaseCallback([&physicsSystem](const XMFLOAT3& offset) {
// Shift all physics body positions by -offset
physicsSystem.ShiftAllBodies(-offset.x, -offset.y, -offset.z);
});
// Audio system callback
originSystem.RegisterRebaseCallback([&audioSystem](const XMFLOAT3& offset) {
// Shift all audio source positions and listener position
audioSystem.ShiftWorldPositions(offset);
});
// Particle system callback
originSystem.RegisterRebaseCallback([&particleSystem](const XMFLOAT3& offset) {
particleSystem.ShiftEmitters(offset);
});
// NavMesh callback
originSystem.RegisterRebaseCallback([&navMesh](const XMFLOAT3& offset) {
navMesh.ShiftMesh(offset);
});The accumulated offset enables conversion between local (rebased) and absolute world coordinates at any time:
// Local position: what the ECS and renderer see (near origin)
XMFLOAT3 localPos = entity.GetPosition(); // e.g., (50, 0, 30)
// Absolute position: the "true" world position
XMFLOAT3 absolutePos = originSystem.LocalToAbsolute(localPos);
// If accumulated offset is (150000, 0, 80000), absolutePos = (150050, 0, 80030)
// Convert back for display or save
XMFLOAT3 backToLocal = originSystem.AbsoluteToLocal(absolutePos);
// backToLocal == (50, 0, 30)const Spark::World::OriginRebasingStats& stats = originSystem.GetStats();
LOG_INFO("Origin Rebasing Stats:");
LOG_INFO(" Total rebases: {}", stats.totalRebases);
LOG_INFO(" Current offset: ({:.1f}, {:.1f}, {:.1f})",
stats.currentOffset.x, stats.currentOffset.y, stats.currentOffset.z);
LOG_INFO(" Last rebase distance: {:.1f}", stats.lastRebaseDistance);
LOG_INFO(" Max distance observed: {:.1f}", stats.maxDistanceFromOrigin);SeamlessAreaManager uses the player's velocity and camera direction to predict which areas will be needed before the player arrives:
// The manager tracks player state (thread-safe)
auto& mgr = Spark::Streaming::SeamlessAreaManager::GetInstance();
// Called from gameplay or physics thread
mgr.SetPlayerState(
playerPosition, // Current world position
playerVelocity, // Current velocity (meters/second)
cameraForward // Normalized camera direction
);
// Internally, PredictFuturePosition() calculates:
// predictedPos = position + velocity * lookaheadTime
// This predicted position is used for area distance calculationsSpark::Streaming::StreamingConfig config;
config.loadRadius = 500.0f; // Start loading when player is within 500 units
config.unloadRadius = 800.0f; // Unload when player is beyond 800 units
config.lookaheadTime = 3.0f; // Predict 3 seconds ahead
config.updateInterval = 0.25f; // Recalculate every 250ms
config.maxConcurrentLoads = 2; // Max 2 areas loading simultaneously
mgr.SetConfig(config);| Parameter | Default | Description | Tuning Guidance |
|---|---|---|---|
loadRadius |
500.0 | Distance at which areas begin loading | Increase for fast-moving games (vehicles, flying) |
unloadRadius |
800.0 | Distance at which loaded areas unload | Must be > loadRadius to prevent thrashing |
lookaheadTime |
3.0 | Seconds to predict ahead using velocity | Increase for high-speed gameplay |
updateInterval |
0.25 | Seconds between prediction recalculations | Lower = more responsive, higher = less CPU |
maxConcurrentLoads |
2 | Maximum areas loading at once | Increase on fast storage (NVMe) |
Areas transition through the following states:
┌──────────┐
│ Unloaded │ ◄──────────────────────┐
└────┬─────┘ │
│ Player enters loadRadius │
▼ │
┌──────────┐ │
│ Loading │ │
└────┬─────┘ │
│ Async load complete │
▼ │
┌──────────┐ │
│ Loaded │ │
└────┬─────┘ │
│ Player exits unloadRadius │
▼ │
┌───────────┐ │
│ Unloading │ ───────────────────────┘
└───────────┘
// Query current area state
Spark::Streaming::AreaState state = mgr.GetAreaState(areaId);
switch (state)
{
case Spark::Streaming::AreaState::Unloaded:
// No resources allocated
break;
case Spark::Streaming::AreaState::Loading:
// Async load in progress
break;
case Spark::Streaming::AreaState::Loaded:
// Fully active, entities simulating
break;
case Spark::Streaming::AreaState::Unloading:
// Resources being released
break;
}mgr.RegisterStateCallback([](Spark::Streaming::AreaID areaId,
Spark::Streaming::AreaState newState) {
switch (newState)
{
case Spark::Streaming::AreaState::Loaded:
LOG_INFO("Area {} loaded and active", areaId);
// Spawn NPCs, start ambient sounds, etc.
break;
case Spark::Streaming::AreaState::Unloading:
LOG_INFO("Area {} unloading", areaId);
// Save transient state, despawn actors
break;
default:
break;
}
});32-bit floats provide approximately 7 decimal digits of precision. At large distances, this means:
| Distance from Origin | Precision | Visible Effect |
|---|---|---|
| 1,000 units | ~0.0001 units | None |
| 10,000 units | ~0.001 units | Minor jitter on small objects |
| 100,000 units | ~0.01 units | Visible jitter, physics instability |
| 1,000,000 units | ~0.1 units | Severe jitter, broken physics |
The default threshold of 5000 units provides a safety margin:
// Conservative: rebase more often for precision-critical games
originSystem.SetRebasingThreshold(2000.0f);
// Default: good for most scenarios
originSystem.SetRebasingThreshold(5000.0f);
// Aggressive: fewer rebases but lower precision at edges
originSystem.SetRebasingThreshold(10000.0f);For scientific simulations or extremely large worlds, the engine supports double-precision physics via the SPARK_DOUBLE_PRECISION_PHYSICS CMake flag. This extends usable range but doubles memory usage for position data.
In a client-server architecture, each client may have a different local origin offset. The key rule is: always transmit absolute positions over the network.
// Client sends position to server
XMFLOAT3 absolutePos = originSystem.LocalToAbsolute(localPlayerPos);
SendPositionToServer(absolutePos);
// Client receives position from server
XMFLOAT3 receivedAbsolutePos = ReceivePositionFromServer();
XMFLOAT3 localOtherPlayer = originSystem.AbsoluteToLocal(receivedAbsolutePos);
// Render other player at localOtherPlayerWhen using the Area Server Architecture, each AreaServer has its own coordinate space. The WorldServer mediates coordinate translation during entity migration:
// Server A's local space -> absolute -> Server B's local space
XMFLOAT3 absolutePos = serverA_origin.LocalToAbsolute(entityLocalPos);
MigratingEntity entity;
entity.position = absolutePos; // Always store absolute in migration data
// On the receiving side:
XMFLOAT3 localInB = serverB_origin.AbsoluteToLocal(entity.position);Both systems expose console-friendly status methods:
// SeamlessAreaManager diagnostics
std::string areaStatus = mgr.Console_GetStatus();
// Example: "SeamlessAreaManager: 3 areas registered, 2 loaded, current=TownSquare
// Player: (150.0, 0.0, 80.0) vel=(5.0, 0.0, 0.0)
// Load queue: 0 pending, 0 active loads"
// WorldOriginSystem diagnostics
const auto& stats = originSystem.GetStats();
LOG_INFO("Rebases: {} | Offset: ({:.0f}, {:.0f}, {:.0f}) | Last dist: {:.0f}",
stats.totalRebases,
stats.currentOffset.x, stats.currentOffset.y, stats.currentOffset.z,
stats.lastRebaseDistance);// List all currently loaded areas
const auto& loadedIds = mgr.GetLoadedAreas();
for (AreaID id : loadedIds)
{
LOG_INFO(" Loaded area: {}", id);
}
// Get the area the player is currently inside
AreaID current = mgr.GetCurrentArea();// Reset origin system to initial state (clears offset and stats)
originSystem.Reset();
// Reinitialize streaming manager
mgr.Shutdown();
mgr.Initialize();- Area Server Architecture — Multiplayer area servers
- Scene Management — Scene hierarchy and prefabs
- Networking — Base networking system
Published from 2b03dc797148. Edit the canonical source in wiki/.
- Documentation
- Docs route
- Wiki index
- Guides
- Tutorials
- Samples
- Examples
- API Reference
- API route
- Reference
- Build Guide
- Dependencies
- FAQ
- Changelog
- Roadmap
- Contributing
- Code of Conduct
- Home
- FAQ
- Getting Started
- Quick-Start Tutorial
- Making Your First Game
- Making Your First Multiplayer Game
- Artist Workflow Guide
- Editor Walkthrough
- Migration Guide
- How SparkEngine Works
- Architecture Overview
- Engine Architecture Flowchart
- Creating a Game Module
- Game Modules (catalog)
- Entity Component System
- Rendering and Graphics
- Physics
- Cloth Simulation
- Audio
- Input System
- Camera System
- Scripting with AngelScript
- Visual Scripting
- AI and Navigation
- Animation
- 2D Systems
- Networking
- Dedicated Server
- Multiplayer Quick Start
- Area Server Architecture
- Scene Management
- Large World Support
- Collaborative Editing
- Coroutine System
- Event System
- Event Response System
- Job System
- UI System
- UI Layout Extensions
- Localization
- Dialogue System
- Destruction System
- Replay System
- Achievement System
- Loading System
- Mod System
- Content Delivery
- Tween System
- Memory Integrity
- Gameplay Systems
- Terrain and Procedural Generation
- Save System
- Persistence System
- Day Night Cycle and Weather
- Cinematic Sequencer
- Runtime Prefabs
- SparkEditor
- Editor Tutorials
- SparkConsole
- SparkDaemon
- Shader Pipeline
- Asset Pipeline
- Asset Validation
- Asset Migration
- Game Packaging
- Online Services
- DataTable System
- Loot and Crafting System
- CSG System
- Font System
- Timer Manager
- Movie Render Pipeline
- HLOD and World Partition
- Remote Debug System
- Selection Manager
- Asset Dependency Graph
- Editor Automation
- File Watcher
- Project Templates
- System Requirements
- VR Support
- Mobile Platform
- Accessibility
- Platform Input
- Platform Certification
- Cross-Compilation: Wine Testing
- RHI Abstraction Layer
- D3D11 Backend
- D3D12 Backend
- Vulkan Backend
- OpenGL Backend
- Metal Backend
- DXR Raytracing
- Hybrid Ray Tracing
- Upscaling (DLSS/FSR)
- Render Graph
- Shader Graph
- GPU Particles
- GPU-Driven Rendering
- Volumetric Fog
- Volumetric Clouds
- Global Illumination
- Virtual Texturing
- Water Rendering
- Clustered Lighting
- Material System
- Post-Processing
- Shadow System
- Particle System
- Decal System
- Sky and Atmosphere
- Foliage System
- Mesh Shaders
- Neural Rendering
- Configuration Reference
- Performance Tips
- Benchmark Framework
- Threading Model
- Fuzz Policy and Parser Security
- Memory Safety
- Memory Management Patterns
- Build System and CMake Modules
- Profiler and Debugging
- Performance Profiling Guide
- Telemetry System
- Crash Reporting
- Golden Image Testing
- Utilities
- Testing
- Fuzz Policy and Parser Security
- Codebase Statistics
- Codebase Health
- Error Handling Patterns
- Hot Reload Overview
- Troubleshooting
- Contributing
- Workflow Patterns
- Build Optimizations
- CI Reproducible Builds
- GitHub API and PR Checks
- Git Rebase Conflicts
- Clang-Format
- Code Quality Violations
- AI Bloat Pattern
- MinGW + Wine Cross-Compilation
- Live Editor Testing
- Engine & Renderer Landscape
- DuetOS Portability Catalog
- Five-Engine Analysis
- Eleven-Engine Analysis
- ThorVG / Unity Graphics Analysis
- Advanced Techniques Catalog
- Third-Party Library Evaluation
- Engine Viability Evaluation
- Engine Feature Recommendations
- Project Recommendations
- Mac Compatibility Analysis
- Codebase Observations
- Codebase Bloat Audit
- Test Suite Audit
- Documentation Coverage Audit
- ThirdParty Dependencies Audit
- Load Test Baseline
- Gameplay Systems Status
- SparkGame Module Status
- Stub and Abandoned Features
- Memory Integrity System
- Memory Safety Evaluation
- Hardware Acceleration Systems
- Jolt Physics Integration
- GPU/CPU Separation Plan
- Daemon Services Architecture
- Reflection & Polymorphism Refactoring Plan
- SparkBuild In-Tree
- Wine No-JobSystem Breakthrough
- Wine Role and Fallback Tiers