Primary source of project understanding for AI agents and human contributors. This document provides comprehensive guidance for understanding, navigating, and contributing to the R-Type Clone project. It is designed to help both AI coding assistants and human developers safely make contributions without introducing regressions or architectural violations.
- Project Overview
- Architecture Summary
- Directory Structure
- Module System
- ECS Architecture
- Networking Architecture
- Lua Scripting Layer
- Build System
- Code Conventions
- Safe Contribution Guidelines
- Common Pitfalls
- Quick Reference
| Attribute | Value |
|---|---|
| Language | C++17 |
| Project Type | Recreation of the classic arcade game R-Type |
| Context | School project (EPITECH-style) |
| Architecture | Entity Component System (ECS) |
| Networking | Client/Server multiplayer (UDP) |
| Scripting | Lua (via Sol2) |
| Platforms | Linux, Windows |
- Recreate the core gameplay of R-Type (side-scrolling space shooter)
- Implement a modular game engine with hot-swappable components
- Support both solo and multiplayer modes with parity
- Use a server-authoritative networking model for fairness
- Modularity: Each subsystem (rendering, physics, networking, ECS) is an independent dynamic library
- Decoupling: Modules communicate exclusively via ZeroMQ pub/sub messaging
- Scriptability: Game logic lives in Lua scripts; C++ provides the engine infrastructure
- Cross-platform: Build system supports both Linux and Windows
┌─────────────────────────────────────────────────────────────────────────────┐
│ APPLICATION HOST │
│ (RTypeClient / RTypeServer) │
│ - Loads modules dynamically │
│ - Wires ZeroMQ endpoints │
│ - Manages lifecycle │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ MODULE MANAGER │
│ - dlopen/LoadLibrary for shared libraries │
│ - createModule() C-style factory │
│ - Module lifecycle: init() → loop() → cleanup() │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ WindowManager │ │ Renderer │ │ LuaECSManager │
│ (SFML) │ │ (GLEW/SFML) │ │ (Sol2/Lua) │
│ │ │ │ │ │
│ - Window events│ │ - 3D/2D render │ │ - ECS logic │
│ - Display │ │ - Particles │ │ - Scripts │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────────┼───────────────────────────┘
▼
┌─────────────────────────────────┐
│ ZeroMQ Message Bus │
│ (Topic-based Pub/Sub) │
│ - Async, non-blocking │
│ - String payloads or MsgPack │
└─────────────────────────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PhysicEngine │ │ NetworkManager │ │ SoundManager │
│ (Bullet3) │ │ (ASIO/UDP) │ │ (SFML Audio) │
│ │ │ │ │ │
│ - Rigid bodies │ │ - UDP sockets │ │ - SFX/Music │
│ - Collisions │ │ - MsgPack │ │ - Playback │
└─────────────────┘ └─────────────────┘ └─────────────────┘
| Decision | Rationale |
|---|---|
| Dynamic modules | Allows independent development, replacement, and testing of subsystems |
| ZeroMQ pub/sub | Lightweight, transport-agnostic messaging; scales from in-process to networked |
| Per-module threads | Prevents slow subsystems from blocking others |
| C-style factory | Avoids C++ ABI mismatches between compiler versions |
| Lua scripting | Enables rapid iteration on game logic without recompilation |
R-type-Clone/
├── agent.md # ← You are here
├── build.py # Python build script
├── CMakeLists.txt # Root CMake configuration
├── CMakePresets.json # CMake presets for Linux/Windows
├── vcpkg.json # Dependency manifest
│
├── assets/ # Game assets (copied to build/)
│ ├── fonts/ # TTF fonts
│ ├── models/ # 3D models (.obj)
│ ├── scripts/ # Lua game logic
│ │ └── space-shooter/ # Main game scripts
│ │ ├── Main.lua # Entry point
│ │ ├── GameLoop.lua # Unified game loop
│ │ ├── components/ # Component definitions
│ │ ├── systems/ # ECS systems
│ │ └── levels/ # Level definitions
│ ├── sounds/ # Audio files
│ └── textures/ # Texture images
│
├── cmake/
│ └── Dependencies.cmake # FetchContent fallback for deps
│
├── docs/ # Documentation
│ ├── CONTRIBUTING.md # Contribution guidelines
│ ├── NETWORK_PROTOCOL.md # Network protocol spec
│ ├── NetworkArchitecture.md # Network system design
│ ├── OVERVIEW.md # Project overview
│ ├── QUICKSTART.md # Build instructions
│ └── rfc/ # Request for Comments
│
├── src/
│ ├── engine/ # Engine infrastructure
│ │ ├── app/ # Application base classes
│ │ ├── modules/ # Module implementations
│ │ │ ├── ECSManager/ # ECS module (LuaECSManager)
│ │ │ ├── NetworkManager/ # UDP networking
│ │ │ ├── PhysicEngine/ # Bullet physics
│ │ │ ├── Renderer/ # OpenGL rendering
│ │ │ ├── SoundManager/ # Audio playback
│ │ │ └── WindowManager/ # Window management
│ │ ├── modulesManager/ # Dynamic module loading
│ │ └── types/ # Shared type definitions
│ │
│ └── game/ # Game-specific code
│ ├── Rtype.cpp/hpp # Base game class
│ ├── client/ # Client application
│ └── server/ # Server application
│
└── vcpkg/ # vcpkg submodule (dependencies)
All modules implement rtypeEngine::IModule:
class IModule {
public:
virtual void init() = 0; // One-time initialization
virtual void loop() = 0; // Called every frame
virtual void cleanup() = 0; // Cleanup before unload
virtual void start() = 0; // Start module thread
virtual void stop() = 0; // Stop module thread
// Messaging
virtual void sendMessage(const std::string& topic, const std::string& message) = 0;
virtual void subscribe(const std::string& topic, MessageHandler handler) = 0;
};Modules are loaded dynamically via AModulesManager:
// Each module exports this C-style factory function
extern "C" IModule* createModule(const char* pubEndpoint, const char* subEndpoint);| Module | Library | Purpose |
|---|---|---|
SFMLWindowManager |
SFMLWindowManager.so |
Window creation, input events |
GLEWSFMLRenderer |
GLEWSFMLRenderer.so |
OpenGL 3D/2D rendering |
LuaECSManager |
LuaECSManager.so |
ECS logic, Lua scripting |
BulletPhysicEngine |
BulletPhysicEngine.so |
Physics simulation |
NetworkManager |
NetworkManager.so |
UDP client/server networking |
SFMLSoundManager |
SFMLSoundManager.so |
Audio playback |
BasicECSSavesManager |
BasicECSSavesManager.so |
Save/load game state |
Modules communicate via ZeroMQ pub/sub:
// Publishing a message
sendMessage("RenderEntityCommand", "CreateEntity:cube:entity_123");
// Subscribing to messages
subscribe("KeyPressed", [](const std::string& key) {
// Handle key press
});Common Topics:
| Topic | Direction | Purpose |
|---|---|---|
RenderEntityCommand |
ECS → Renderer | Create/update/destroy render objects |
PhysicCommand |
ECS → Physics | Create/control physics bodies |
KeyPressed / KeyReleased |
Window → ECS | Input events |
EntityUpdated |
Physics → ECS | Physics simulation results |
NetworkMessage |
Network → ECS | Incoming network data |
RequestNetworkSend |
ECS → Network | Outgoing network data |
LoadScript |
App → ECS | Load Lua script file |
The ECS (Entity Component System) is implemented in Lua, managed by LuaECSManager:
- Entities: UUID strings (e.g.,
"a1b2c3d4-...") - Components: Lua tables with data
- Systems: Lua tables with
init()andupdate(dt)functions
Components are stored in sparse sets for cache-efficient iteration:
struct ComponentPool {
std::vector<sol::table> dense; // Component data
std::vector<std::string> entities; // Entity IDs (parallel to dense)
std::unordered_map<std::string, size_t> sparse; // Entity ID → index
};Defined in assets/scripts/space-shooter/components/Components.lua:
| Component | Purpose |
|---|---|
Transform |
Position, rotation, scale |
Mesh |
3D model path, texture path |
Sprite |
2D texture reference |
Collider |
Physics collision shape |
Physic |
Mass, friction, velocity |
Player |
Player speed, properties |
Enemy |
Enemy AI properties |
Bullet |
Projectile damage |
Life |
Health/HP |
InputState |
Current input state |
NetworkIdentity |
Network ownership |
GameState |
Game state machine |
-- Entity Management
local id = ECS.createEntity()
ECS.destroyEntity(id)
-- Components
ECS.addComponent(id, "Transform", Transform(0, 0, 0))
local t = ECS.getComponent(id, "Transform")
ECS.removeComponent(id, "Transform")
local has = ECS.hasComponent(id, "Transform")
-- Queries
local entities = ECS.getEntitiesWith({"Transform", "Player"})
-- Systems
ECS.registerSystem(MySystem)
-- Messaging
ECS.sendMessage("Topic", "payload")
ECS.subscribe("Topic", function(data) ... end)
-- Network (Binary Protocol)
ECS.sendBinary("INPUT", {k="UP", s=1})
ECS.sendNetworkMessage("TOPIC", "payload")
ECS.broadcastNetworkMessage("TOPIC", "payload")Systems are loaded in dependency order in GameLoop.lua:
-
Core Gameplay (Authority: Server + Solo)
CollisionSystem,PhysicSystem,LifeSystem,EnemySystem,PlayerSystem,GameStateManager
-
Input & Control (All instances)
InputSystem
-
Network Sync (Server + Client in Multiplayer)
NetworkSystem
-
Menu & State (All instances)
MenuSystem
-
Visual & UI (Client + Solo only)
RenderSystem,ParticleSystem,ScoreSystem,BackgroundSystem
| Feature | Specification |
|---|---|
| Transport | UDP (via ASIO) |
| Serialization | MsgPack (binary) |
| Architecture | Server-Authoritative |
| Update Rate | 20Hz (50ms) |
The unified ECS.capabilities table determines behavior:
ECS.capabilities = {
hasAuthority = true/false, -- Can simulate game state
hasRendering = true/false, -- Can render graphics
hasNetworkSync = true/false, -- Network sync enabled
hasLocalInput = true/false, -- Accepts local input
isServer = true/false, -- Is server instance
isLocalMode = true/false, -- Solo mode
isClientMode = true/false -- Network client
}Mode Matrix:
| Mode | Authority | Rendering | NetworkSync | LocalInput |
|---|---|---|---|---|
| Solo | ✅ | ✅ | ❌ | ✅ |
| Server | ✅ | ❌ | ✅ | ❌ |
| Client | ❌ | ✅ | ✅ | ✅ |
| Topic | Direction | Payload |
|---|---|---|
INPUT |
Client → Server | {k: "UP", s: 1} (MsgPack) |
ENTITY_POS |
Server → Clients | Position, rotation, velocity, type |
PLAYER_ASSIGN |
Server → Client | Entity UUID |
CLIENT_READY |
Client → Server | Confirmation |
ENTITY_DESTROY |
Server → Clients | Entity UUID |
[ Topic Length (4 bytes) ] [ Topic String (N bytes) ] [ MsgPack Payload (M bytes) ]
- Solo/Client:
assets/scripts/space-shooter/Main.lua→ Level loader - Unified Logic:
assets/scripts/space-shooter/GameLoop.lua→ System loader
local MySystem = {}
function MySystem.init()
print("[MySystem] Initialized")
-- Subscribe to events
ECS.subscribe("MyEvent", MySystem.onMyEvent)
end
function MySystem.update(dt)
-- Skip if not relevant to this instance
if not ECS.capabilities.hasAuthority then return end
-- Query entities
local entities = ECS.getEntitiesWith({"MyComponent"})
for _, id in ipairs(entities) do
local comp = ECS.getComponent(id, "MyComponent")
-- Update logic
end
end
function MySystem.onMyEvent(data)
-- Handle event
end
-- Register system
ECS.registerSystem(MySystem)
return MySystemCRITICAL: Always guard system logic based on capabilities:
-- Authority-only logic (physics, spawning, game rules)
if not ECS.capabilities.hasAuthority then return end
-- Rendering-only logic (visual effects, UI)
if not ECS.capabilities.hasRendering then return end
-- Input-only logic (keyboard/mouse handling)
if not ECS.capabilities.hasLocalInput then return end
-- Network-only logic (sync messages)
if not ECS.capabilities.hasNetworkSync then return endpython3 build.pyThis script:
- Detects/bootstraps vcpkg
- Installs dependencies
- Configures CMake
- Builds the project
- Copies assets to build directory
# Bootstrap vcpkg
./vcpkg/bootstrap-vcpkg.sh
./vcpkg/vcpkg install
# Configure and build
mkdir -p build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build . -j$(nproc)cd build
# Solo mode (client only)
./r-type_client
# Multiplayer
./r-type_server 1234 # Start server on port 1234
./r-type_client 127.0.0.1 1234 # Connect to server
# Note: May need to set LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$PWD:$PWD/lib:$LD_LIBRARY_PATHsfml- Window, graphics, audioglew- OpenGL extension loadingopengl- Graphics APIzeromq/cppzmq- Message buslua/sol2- Scriptingbullet3- Physics engineasio- Async networkingmsgpack- Binary serializationgtest- Unit testing
- Standard: C++17
- Naming:
- Classes:
PascalCase - Functions/methods:
camelCase - Private members:
_prefixedCamelCase - Constants:
UPPER_SNAKE_CASE
- Classes:
- Namespaces:
rtypeEngine(engine),rtypeGame(game-specific)
- Naming:
- Systems:
PascalCase - Functions:
camelCaseorPascalCasefor system methods - Components:
PascalCasefactory functions - Variables:
camelCase
- Systems:
- Indentation: 4 spaces
<type>(<scope>): <short summary>
Types: feat, fix, docs, style, refactor, perf, test, chore, build, ci
Scope: renderer, lua, network, physics, build, etc.
Examples:
feat(renderer): add particle system support
fix(network): prevent packet loss on high latency
docs: update agent.md with networking section
- Read this document thoroughly
- Understand the scope — Is this engine code, game logic, or configuration?
- Check capability guards — Will this code run in the correct mode?
- Review related systems — What other code depends on this?
- Adding new components in
Components.lua - Creating new systems that follow existing patterns
- Adding new assets (textures, sounds, models)
- Documentation updates
- Bug fixes with clear reproduction steps
- Modifying module interfaces (
IModule,AModule) - Changing ZeroMQ message formats/topics
- Altering the ECS core (
LuaECSManager.cpp) - Modifying network protocol
- Changing capability flag behavior
⚠️ DO NOT:
- Invent new features without explicit request
- Remove capability guards without understanding consequences
- Change wire protocol without updating all consumers
- Modify build system without testing both platforms
- Add blocking calls in module loops
- Use global state in Lua without documenting it
- Solo Mode: Run
./r-type_clientalone - Multiplayer: Run server + 2 clients
- Cross-platform: Test on Linux AND Windows if modifying CMake
-- ❌ WRONG: Runs everywhere, causes errors on server
function MySystem.update(dt)
ECS.sendMessage("RenderEntityCommand", "...") -- Server has no renderer!
end
-- ✅ CORRECT: Only runs where rendering exists
function MySystem.update(dt)
if not ECS.capabilities.hasRendering then return end
ECS.sendMessage("RenderEntityCommand", "...")
end// ❌ WRONG: Blocks the entire module
void MyModule::loop() {
std::this_thread::sleep_for(std::chrono::seconds(5)); // BLOCKS!
}
// ✅ CORRECT: Non-blocking with state tracking
void MyModule::loop() {
if (shouldDoAction()) {
doAction();
_lastActionTime = now();
}
}-- ❌ WRONG: Assumes multiplayer mode
function MySystem.init()
ECS.subscribe("NetworkMessage", ...) -- Fails in solo mode!
end
-- ✅ CORRECT: Check capability first
function MySystem.init()
if ECS.capabilities.hasNetworkSync then
ECS.subscribe("NetworkMessage", ...)
end
end-- ❌ WRONG: May crash if entity was destroyed
local t = ECS.getComponent(id, "Transform")
t.x = t.x + 1 -- Crash if t is nil!
-- ✅ CORRECT: Always validate
local t = ECS.getComponent(id, "Transform")
if t then
t.x = t.x + 1
end| What | Where |
|---|---|
| Game entry (client) | src/game/client/main.cpp |
| Game entry (server) | src/game/server/main.cpp |
| Module base class | src/engine/modules/AModule.hpp |
| ECS manager | src/engine/modules/ECSManager/LuaECSManager/ |
| Game scripts | assets/scripts/space-shooter/ |
| Component defs | assets/scripts/space-shooter/components/Components.lua |
| System scripts | assets/scripts/space-shooter/systems/ |
| Build config | CMakeLists.txt, vcpkg.json |
# Build
python3 build.py
# Run solo
cd build && ./r-type_client
# Run multiplayer
cd build
./r-type_server 1234 &
./r-type_client 127.0.0.1 1234
# Clean rebuild
rm -rf build && python3 build.pyIn Lua scripts:
print("[SystemName] Debug message")
print("[SystemName] Capabilities: Authority=" .. tostring(ECS.capabilities.hasAuthority))In C++ modules:
std::cout << "[ModuleName] Debug message" << std::endl;- QUICKSTART.md — Build instructions
- OVERVIEW.md — Engine architecture details
- NetworkArchitecture.md — Network system design
- NETWORK_PROTOCOL.md — Wire protocol specification
- CONTRIBUTING.md — Contribution guidelines
- RFC-001-BINARY-PROTOCOL.md — Binary protocol RFC
Last updated: January 16, 2026