25 KiB
World2 Game Project - Agent Guide
Project Overview
World2 is a 3D life simulation game built using the OGRE3D rendering engine. The project features an Entity-Component-System (ECS) architecture powered by Flecs, with Jolt Physics for simulation, Lua scripting for story content, and ImGui for user interfaces.
Technology Stack
Core Dependencies
- Rendering: OGRE3D 14.x+ (Object-Oriented Graphics Rendering Engine)
- ECS Framework: Flecs (Fast Lightweight Entity Component System)
- Physics: Jolt Physics with double precision support (JPH_DOUBLE_PRECISION)
- Scripting: Lua 5.4 with LPEG library
- Audio: miniaudio (single-header audio library)
- UI: ImGui (integrated via Ogre::ImGuiOverlay)
- Model Loading: Assimp with pugixml
- Navigation: Recast/Detour for crowd simulation
- Procedural Generation: OgreProcedural
- Profiling: Tracy frame profiler
Build Requirements
- CMake 3.13+
- C++17 compatible compiler
- Blender (for asset processing, path configurable via
BLENDERCMake variable) - OGRE SDK with components: Bites, Paging, Terrain, MeshLodGenerator
Project Structure
/
├── CMakeLists.txt # Main build configuration
├── resources.cfg # OGRE resource locations
├── .clang-format # Linux kernel code style config
│
├── Game.cpp # Main game executable entry point (legacy)
├── Editor.cpp # World editor executable (legacy)
├── Procedural.cpp # Procedural generation test / character controller demo (legacy)
├── Bootstrap.cpp # Legacy character controller demo (legacy)
├── terrain.cpp # Terrain system test executable (legacy)
│
├── src/ # Source code
│ ├── gamedata/ # Core ECS modules and game logic (legacy)
│ │ ├── GameData.cpp/h # ECS world setup and management (legacy)
│ │ ├── Components.h # ECS component definitions (legacy)
│ │ ├── *Module.cpp/h # Individual ECS modules (legacy)
│ │ └── items/ # Game item implementations (harbour, temple, town, etc.) (legacy)
│ ├── lua/ # Lua integration
│ │ ├── lua-5.4.8/ # Lua source code
│ │ └── lpeg-1.1.0/ # LPEG parsing library
│ ├── physics/ # Jolt Physics wrapper (physics.h/cpp) (legacy - for buildiung legacy code only)
│ ├── sound/ # Audio system (miniaudio)
│ ├── crowd/ # Recast/Detour navigation
│ │ ├── include/ # Crowd headers (OgreRecast, OgreDetourCrowd, etc.)
│ │ └── src/ # Crowd implementations
│ ├── editor/ # Editor-specific code (legacy)
│ │ ├── EditorGizmoModule.h/cpp
│ │ ├── EditorInputModule.h/cpp
│ │ └── main.cpp
│ ├── features/ # Feature modules
│ │ ├── characters/ # Character rendering test
│ │ ├── editScene/ # Scene editor with ECS, it has game mode and all features, current work is here
│ │ └── sceneEditor/ # some other editor code
│ ├── world/ # World generation and GOAP AI (legacy)
│ ├── terrain/ # Terrain system (legacy)
│ ├── tests/ # Test utilities
│ ├── text_editor/ # ImGui text editor component
│ ├── miniaudio/ # Audio library
│ ├── sceneloader/ # Scene loading utilitie (legacy)
│ ├── aitoolkit/ # AI utilities (GOAP, FSM, Behavior Trees) (legacy)
│ └── vehicles/ # Vehicle definitions (legacy)
│
├── assets/ # Source assets (Blender files)
│ ├── blender/ # Blender source files
│ │ ├── buildings/ # Building models (.blend)
│ │ ├── characters/ # Character models and clothes (characters asset pipeline)
│ │ ├── vehicles/ # Vehicle models
│ │ ├── scripts/ # Blender Python scripts for export
│ │ └── world/ # World terrain models
│ ├── textures/ # Source textures
│ └── fonts/ # Game fonts
│
├── lua-scripts/ # Lua game scripts
│ ├── narrator/ # Ink narrative parser
│ ├── stories/ # Story scripts (.ink format, compiled to .lua)
│ └── json/ # JSON library for Lua
│
├── water/ # Water rendering system (legacy)
│ ├── water.h/cpp # Water class
│ ├── *.frag, *.vert # GLSL shaders
│ └── water.compositor # OGRE compositor script
│
├── skybox/ # Sky rendering resources
├── resources/ # Runtime resources (build output)
├── audio/ # Audio resources
├── morph/ # Character morphing data
└── build/ # build directory
└── build-vscode/ # VSCode build directory
Critical Constraints & Rules
- The source code is the only source of truth. Please check code as documentation might be out of sync.
- Do not modify legacy code. Current code is at src/features/editScene and its dependencies.
- Please ask questions if anything is unclear.
- Please update tests, documentation and examples every time API or modules and systems change.
Build Instructions
Prerequisites
- Install OGRE3D SDK with Bites, Paging, Terrain, MeshLodGenerator components
- Install dependencies: Jolt Physics, Flecs, Assimp, pugixml, OgreProcedural, Tracy, SDL2, ZLIB
- Set
CMAKE_PREFIX_PATHto OGRE installation - Configure Blender path in CMake (default:
${CMAKE_SOURCE_DIR}/../../blender-bin/bin/blender)
Build Commands
# Configure (adjust paths as needed)
cmake -B build -S . -DCMAKE_PREFIX_PATH=/path/to/ogre-sdk
# Build all targets
cmake --build build
# Build specific targets
cmake --build build --target Game # Main game executable (legacy)
cmake --build build --target Editor # World editor (legacy)
cmake --build build --target Procedural # Procedural test (legacy)
cmake --build build --target TerrainTest # Terrain test (legacy)
cmake --build build --target TerrainTest # Terrain test (legacy)
cmake --build build --target editSceneEditor # Current executable for scene editor and game
cmake --build build --target demoLuaSceneScript # Demo: game-mode inline scene/prefab Lua scripts
cmake --build build --target demoSceneSwitchingExtra # Demo: scene switching with CellGrid interior
cmake --build build --target demoSokoban # Demo: forklift sokoban activity (vehicle + crates + quest HUD)
cmake --build build --target demoTaxi # Demo: taxi driver (procedural town, sedans, NPC traffic, fare chain)
Build Outputs
build/Game- Main game executable (legacy)build/Editor- World editor executable (legacy)build/Procedural- Procedural generation test (legacy)build/TerrainTest- Terrain system test (legacy)build//src/features/editScene/editSceneEditor- current executablebuild//src/features/editScene/demos/demo-lua-scene-script/demoLuaSceneScript- SceneScriptComponent demo (game mode, inline Lua); self-contained runtime staged in the same directorybuild//src/features/editScene/demos/demo-scene-switching-extra/demoSceneSwitchingExtra- Scene-switching demo with a CellGrid interior (own-entity ProceduralMaterial/ProceduralTexture); self-contained runtime staged in the same directorybuild//src/features/editScene/demos/demo-interior-exterior-dynamics/demoInteriorExteriorDynamics- Same scene-switching demo, but the exterior scene has a streaming terrain (baseNoise archipelago - a large island at the content site amid an ocean with more islands) + sky + water instead of the flat floor; self-contained runtime staged in the same directorybuild//src/features/editScene/demos/demo-sokoban/demoSokoban- Forklift sokoban activity demo (drivable Jolt VehicleConstraint forklift, pushable crates, target pads, completion HUD); based on demo-interior-exterior-dynamics, see demos/demo-sokoban/PLAN.md; self-contained runtime staged in the same directorybuild//src/features/editScene/demos/demo-taxi/demoTaxi- Taxi driver demo (procedurally generated town, drivable sedans, passenger/foot-traffic/vehicle-traffic NPCs, chained timed fares with doubling payout); based on demo-interior-exterior-dynamics with vehicle ideas from demo-sokoban, see demos/demo-taxi/PLAN.md; self-contained runtime staged in the same directorybuild/resources/- Staged game resourcesbuild/characters/- Processed character modelsbuild/water/- Water assets (legacy)
Code Style Guidelines
This project follows the Linux Kernel Coding Style for C++:
Formatting Rules
- Indentation: 8-character tabs (UseTab: Always)
- Line Length: 80 characters maximum
- Braces:
- Functions: Opening brace on new line
- Control structures: Opening brace on same line
- Pointer Alignment: Right (
int *ptrnotint* ptr) - Namespaces: No indentation inside namespaces
Example
void myFunction(int arg)
{
if (arg > 0) {
doSomething();
return;
}
for (int i = 0; i < arg; i++) {
process(i);
}
}
Running clang-format
clang-format -i src/gamedata/*.cpp src/gamedata/*.h
Architecture
ECS (Entity Component System)
The game uses Flecs for ECS architecture. Core systems:
Components (src/gamedata/Components.h) (legacy)
GameData- Global game stateEngineData- Engine configuration (SceneManager, delta time, debug draw)Input- Input state (keyboard, mouse, controls)Camera- Camera node and configurationRenderWindow- Window and DPI informationCollisionShape- Physics shape referencesEditorSceneSwitch- Editor scene switchingGameState- Game running state
Modules (src/gamedata/*Module.h) (legacy)
Modules are ECS systems organized by functionality:
| Module | Purpose |
|---|---|
| AppModule | Application context management |
| GUIModule | ImGui interface management |
| EditorGUIModule | Editor UI |
| PhysicsModule | Jolt physics integration |
| CharacterModule | Character spawning and management |
| CharacterManagerModule | Player/NPC character management |
| CharacterAnimationModule | Animation state machines |
| CharacterAIModule | AI behavior (GOAP-based) |
| TerrainModule | Terrain generation and rendering |
| WaterModule | Water surface and physics |
| SunModule | Day/night cycle and lighting |
| WorldMapModule | World map overlay |
| QuestModule | Quest system |
| EventModule | Game event handling |
| EventTriggerModule | Event triggers |
| SlotsModule | Inventory slot management |
| BoatModule | Boat physics and control |
| VehicleManagerModule | Vehicle management |
| PlayerActionModule | Player action handling |
| LuaModule | Lua scripting integration |
| StaticGeometryModule | Static geometry management |
EditScene Modules (src/features/editScene)
The editor/game mode executable adds its own ECS components.
The editor/game mode executable adds its own ECS modules:
| Module | Purpose |
|---|---|
| CharacterSlotSystem | Catalog-driven multi-slot character mesh builder |
| CharacterSpawnerSystem | Distance-based spawn/despawn of registry characters |
| TerrainPrefabSpawnerSystem | Distance-based terrain prefab spawn/despawn, terrain snap and compliance flattening |
| PlayerControllerSystem | Player input, camera, locomotion and animation state |
| ActuatorSystem | Player interaction prompts and smart-object/item actions |
| DoorSystem | Swing animation and collider toggle for CellGrid door entities |
| EditorUISystem | ImGui property panels and scene management |
| ProceduralTextureSystem | Runtime procedural texture generation |
| ProceduralMaterialSystem | Runtime procedural material creation |
| AnimationTreeRegistry | Global named animation tree definitions |
| AnimationTreeSystem | Runtime evaluation of registry-referenced animation trees |
| SceneScriptSystem | Execute-once scene/prefab Lua scripts (SceneScriptComponent) and scene_loaded/prefab_loaded events |
| VehicleSystem | Jolt VehicleConstraint vehicles (VehicleComponent): chassis constraint lifetime, driver input, wheel visual nodes |
PlayerControllerSystem
PlayerControllerSystem reads the shared GameInputState, drives the TPS/FPS
camera and sets the locomotion animation state (idle/walking/running and
the swim equivalents) on the controlled entity. In game mode it adds the
PlayerControlledComponent tag to the target so AI systems know to leave it
alone.
When a controller's targetCharacterName points at an entity with
CharacterSpawnerComponent, the system locks the spawner and forces an
immediate spawn; the actual controlled entity is the spawned character
instance, not the spawner.
The TPS camera boom is collision-clamped via a physics raycast from the
pivot to the desired camera position (instant pull-in, eased recovery), and
the character geometry is hidden when the clamped camera gets too close to
the body — see src/features/editScene/AGENTS.md for details.
Player Character Resolution
Systems that need the live player character (inventory UI, character sheet,
save/load, actuators) must not resolve the controller's
targetCharacterName against EntityNameComponent directly – that returns the
spawner entity when the controller targets a spawner. Instead use:
flecs::entity player = editorApp->getPlayerCharacterEntity();
EditorApp::getPlayerCharacterEntity() returns the controller's current
target, spawning the character through CharacterSpawnerSystem if necessary.
CharacterSpawnerSystem::getSpawnerForCharacter() can be used in save/load to
keep the controller pointed at the spawner rather than the transient instance.
Game Mode Input
Game mode movement keys (W, A, S, D, left/right Shift) are handled
inside keyPressed/keyReleased by keysym.sym. Repeated KEYDOWN events
(evt.repeat != 0) are ignored for movement keys, because releasing Shift
while W/A/S/D is held can generate a repeated keydown with a different
symbol after the physical release, which would otherwise leave the key stuck.
One-shot actions (E, F, I, Space) are also matched by keysym.sym,
and their *Pressed edges ignore repeats too — otherwise holding E (e.g.
the hold-E-to-exit a vehicle) re-fires the door actuators and re-enters.
CharacterSpawnerComponent
registryId- Character registry ID to spawn.spawnDistanceSq- Squared distance to camera at which the character spawns (default 100²).despawnDistanceSq- Squared distance to camera at which the character despawns (default 200²).
The spawner uses the entity's TransformComponent for spawn position/rotation. When the spawner itself moves/rotates/scales in the editor, the spawned character is updated to match; when the spawner is stationary, the character is left alone so it can move on its own. Spawned characters are created without EditorMarkerComponent and removed from the editor UI cache so they remain visible but are not selectable/editable in the editor. Registry changes that bump the character version trigger an immediate respawn. Scene serialization stores plain spawnDistance/despawnDistance values for readability while the component keeps squared values for comparisons.
AnimationTreeComponent
treeName- Name of the animation tree inAnimationTreeRegistry.enabled- Whether the tree is evaluated.useRootMotion- Whether root motion from the animation drives the character's linear velocity.
The actual tree definition (node hierarchy, state machines, animations) lives in the AnimationTreeRegistry, persisted to animation_tree.json. Trees are authored in the registry editor (Tools -> Animation Tree Registry), where each entry can also select a skeleton source from the Characters resource group (one representative mesh per skeleton) to populate animation-name dropdowns. Old scenes and prefabs that stored the tree inline or referenced an AnimationTreeTemplate are migrated automatically on load.
Road Region Streaming (M5)
When the terrain has streamingEnabled, RoadSystem persists the road graph per terrain page in heightmaps/<terrainId>/roads/<px>_<py>.json (managed by RoadRegionStore) instead of inline in the scene JSON — SceneSerializer::serializeTerrain then skips roadNodes/roadEdges and keeps only roadConfig. Region files store absolute world-space doubles; a cross-page edge is written to the region file of each endpoint page with the foreign endpoint repeated inline. On the first sync a non-empty inline graph is migrated to region files automatically. Merges match nodes by world XZ (0.01 eps) so reloaded pages never duplicate nodes; EditorUISystem::saveScene calls RoadSystem::flushRegionStore() before writing the scene. Page mesh rebuilds are gated by a rebase-invariant FNV-1a content signature (world positions quantized to cm), so a render-origin rebase shifts node render positions without re-dirtying pages; navmesh dirtying is debounced (~15 quiet frames) while streaming.
Terrain Streaming Architecture
The terrain can span up to 40,000,000 x 40,000,000 units (TerrainComponent::worldSizeUnits, default; 20000 x 20000 pages at the default 2000-unit page size, clamped to 32768 per axis for TerrainGroup's signed 16-bit slot packing). Streaming is opt-in per terrain via TerrainComponent::streamingEnabled.
Coordinate spaces. Three spaces coexist (see TerrainSystem.hpp "Coordinate spaces"):
- WORLD space — absolute double-precision coordinates; authoritative.
TransformComponent::worldX/Y/Z, region files, bookmarks and the Navigation panel all use it. - RENDER space — floats relative to the floating render origin:
render = world - renderOrigin. Scene nodes, mouse rays and the editor camera live here.RenderOriginSystem(singleton,RenderOriginSystem::getInstance()) owns the origin and rebases when the camera moves further thanREBASE_THRESHOLD(8192 render units) from it: the origin snaps to integer world coordinates near the camera and every render-space position shifts by the exact negated delta. Convert withRenderOriginSystem::worldToRender(x,y,z)/renderToWorld(Ogre::Vector3). - PHYSICS space — Jolt
DVec3/RVec3in absolute world coordinates viaphysics/physics.h; terrain colliders live here, soTerrainSystem::raycastTerrainconverts its render-space ray to world space for the collider query.
TerrainSystem additionally exposes renderToWorldX/Y/Z, worldToRender, and visualToPhysicalX/Z / physicalToVisualX/Z for the physical heightmap space (physical X shifted by half a page, physical Z mirrored within each page).
Page streaming window. With streamingEnabled, pages are not defined up front; TerrainSystem::updateStreamingWindow() loads a square window of pageLoadRadius pages around the camera page each frame (2 loads/frame cap) and unloads pages beyond pageHoldRadius (4 unloads/frame cap), all synchronously to avoid WorkQueue shutdown hangs. Base heights come from procedural TerrainComponent::baseNoise (FastNoiseLite, double precision) instead of the legacy heightmap buffer; setHeightAt writes through the LRU-capped fixup chunk layer. Pages are indexed in PHYSICAL page coords [0, N-1]; the Ogre TerrainGroup slot for physical page (x, y) is (x, -y) because ALIGN_X_Z negates Z. The world is bounded — page indices and teleports clamp to [0, N-1].
Region storage. Per-page data lives under heightmaps/<terrainId>/: painted blend maps, aux maps, fixup chunks, plus the streamed content stores spawners/<px>_<py>.json (prefab spawner defs, see below) and roads/<px>_<py>.json (road graph, see "Road Region Streaming (M5)" above).
Editor navigation tools (M3). EditorCamera supports mouse-wheel zoom (handleMouseWheel), Shift boost (x10) and a configurable fly speed (setFlySpeed, 1..100000 units/s). Tools -> Navigation opens NavigationPanel (teleport by world X/Y/Z or by page index, clamped to world bounds; named world bookmarks stored in the scene JSON top-level bookmarks array, tolerated when missing on load). Tools -> World Map opens WorldMapPanel (ImGui canvas over WorldMapData: coarse heights, loaded pages, roads, spawners, bookmarks and camera overlays; drag pan, wheel zoom, double-click teleport). TerrainEditor also has a compact "Navigation" section sharing the same static teleport backend.
Streamed vs always-loaded spawners (M4). Scene-embedded TerrainPrefabSpawnerComponent entities behave as always-loaded spawners for small/non-streaming scenes. When the terrain streams, spawner definitions persist per page in heightmaps/<terrainId>/spawners/<px>_<py>.json (via SpawnerRegionStore); TerrainPrefabSpawnerSystem::syncStreamedSpawners() polls TerrainGroup::getTerrainSlots() and creates a region spawner entity (tagged StreamedSpawnerTag{pageX,pageY,defId}, excluded from scene JSON) when a page loads, destroying it and writing edits back (including page-boundary crossings) when it unloads. The editor "prefab spawn mode" click-to-place writes to the region store when streaming.
Physics System
Uses Jolt Physics with custom wrapper (legacy - src/physics/physics.h, current - src/features/editScene/physics/physics.h):
// Example: Creating a physics body
JoltPhysicsWrapper *physics = new JoltPhysicsWrapper(scnMgr, cameraNode);
JPH::ShapeRefC shape = physics->createBoxShape(Ogre::Vector3(1, 1, 1));
JPH::BodyID body = physics->createBody(shape, 1.0f, position, rotation,
JPH::EMotionType::Dynamic, Layers::MOVING);
Rendering Pipeline
- Main Render: Standard OGRE render with RTSS (Real Time Shader System)
- Water: Reflection/refraction with custom shaders
- Sky: Dynamic skybox with day/night cycle
- Shadows: Configurable shadow techniques
Asset Pipeline
- Source: Blender
.blendfiles inassets/ - Export: Python scripts in
assets/blender/scripts/export_buildings.py- Building export to glTFexport_characters_ogre.py- Character export to OGRE formatexport_vehicles.py- Vehicle export (car modeling contract:assets/blender/vehicles/CAR_MODELING.md)gen_sokoban_assets.py- One-shot generator for the demo-sokobanforklift/crate/pad.blend files (blender2ogre names .mesh files after mesh data names — set both object and data names)gen_taxi_assets.py- Generator for the demo-taxisedan-a/sedan-taxi.blend files (body + hinge-local door/hood/trunk parts, shared wheel meshes; body colors baked as material variants)gen_vehicle.py- Convention-based vehicle pipeline for hand-modeled cars (sedan-v2.blendis the reference): classifies parts by name (* Tire,* Door,* hood/* trunk,* Steering,seat_*empties), normalizes wheel/steering axes, unpacks texture atlases, and emits a ready-to-merge<name>.vehicle.jsonscene entity (contract:assets/blender/vehicles/CAR_MODELING.md)import_vrm.py- VRM character import
- Build: CMake processes assets during build
- Runtime: Assets loaded from
resources.cfgpaths
Key Conventions
Module Registration
Modules register themselves with Flecs using the FLECS_CPP_NO_AUTO_REGISTRATION macro:
// In module header
struct MyModule {
MyModule(flecs::world &ecs);
};
// In module implementation
MyModule::MyModule(flecs::world &ecs) {
// Register systems, components, etc.
}
Scene Setup
Scenes are configured through ECS setup functions:
// Setup exterior game scene
ECS::setupExteriorScene(scnMgr, cameraNode, camera, renderWindow);
// Setup interior scene
ECS::setupInteriorScene(scnMgr, cameraNode, camera, renderWindow);
// Setup editor
ECS::setupEditor(scnMgr, cameraNode, camera, renderWindow);
Lua Scripting
Story content is written in Ink format and parsed to Lua:
- Source:
lua-scripts/stories/*.ink - Compiled:
lua-scripts/stories/*.lua - Runtime: Narrator parser loads and executes stories
Development Workflow
Adding Assets
- Create/modify
.blendfile inassets/blender/ - Add export logic to relevant Python script if needed
- Add CMake custom command in
CMakeLists.txtif new asset type - Rebuild to process assets
Debugging
- Use
ECS::EngineData.enableDbgDrawfor physics debug visualization - Tracy profiler integration for performance analysis
- FPS and draw call stats printed to console
Security Considerations
- Lua scripts run in sandboxed environment
- File system access restricted to resource directories
- No network functionality currently implemented
Additional Resources
- OGRE3D Docs: https://ogrecave.github.io/ogre/api/latest/
- Flecs Manual: https://www.flecs.dev/flecs/
- Jolt Physics: https://jrouwe.github.io/JoltPhysics/
- Ink Narrative: https://www.inklestudios.com/ink/