# Architecture — Rublox Player How a 3D game is loaded, rendered, and synced. ~5 minutes read. ## Top-level flow ``` URL = / e.g. /265 │ ▼ PlayerAuthProvider checks JWT in localStorage["player_jwt"] │ OR exchanges URL #ticket=... for JWT ▼ useAuth().isAuthenticated │ ▼ KubikonPlayer.jsx main container, gets {projectId} from useParams │ ├── GET /api-storys/kubikon3d/projects/{id} → project_data (JSON) │ ▼ BabylonScene.create() construct Babylon engine, scene, lights, skybox │ ▼ GameRuntime.loadProject() parse project_data, instantiate everything: │ - BlockManager.placeBlock() × N (Minecraft-style blocks) │ - ModelManager.spawnModel() × N (Kenney prop GLBs) │ - DecoManager.placeDeco() × N (terrain decorations) │ - PrimitiveManager.add() × N (cubes/spheres/cylinders) │ - PlayerController.spawn() (R15 character + camera) │ ▼ ScriptSandboxWorker user JS scripts run in a Web Worker sandbox. │ Exposed API: game.player, scene, ui, broadcast. │ ▼ MultiplayerSync optional. Colyseus room joins, syncs positions. │ ▼ RENDER LOOP (60 fps) scene.render() each frame ``` ## Key modules ### `engine/BabylonScene.js` Wraps Babylon `Engine` + `Scene`. Creates lighting (HemisphericLight + DirectionalLight + shadow generator), skybox, fog. Single source of truth for `scene` reference. ### `engine/GameRuntime.js` Orchestrator. Reads `project_data` (the JSON saved by the studio editor) and dispatches each item to the right manager. Has lifecycle hooks: `loadProject()`, `start()`, `pause()`, `dispose()`. Also handles "external URL" resolution (`_resolveExternalUrl`) — game scripts can call `game.openUrl('/kubikon/play/12')` and it correctly resolves to the player itself or the main Rublox site. ### `engine/PlayerController.js` R15 character (15-bone Mixamo rig). First-person and third-person cameras. WASD/touch input. Jumping, gravity, collision against block grid + AABB models. Spawning/respawning. Special GD modes: `setAutoRun(true)`, `setShipMode(true)` — used by Geometry Dash gamemodes (cube/ship/wave/UFO/ball/spider). ### `engine/BlockManager.js` / `TerrainVoxelBuilder.js` Voxel terrain. Blocks are uint16 type IDs in chunked arrays. `rebuildChunk(chunkId)` does greedy meshing → single mesh per material per chunk (~40-100× fewer draw calls than naive). ### `engine/ModelManager.js` Loads `.glb` Kenney models (or designer-uploaded GLBs from `/api-storys/assets/rublox-designer/models/...`). Caches `AssetContainer` per `modelTypeId`, instantiates clones per spawn. ### `engine/DecoManager.js` Lightweight decorative props (rocks, plants, signs). Uses `ThinInstanceCount` for massive performance (thousands of instances → single draw call per type). ### `engine/scripts/ScriptSandboxWorker.js` User-uploaded JS scripts run in a separate Web Worker. Exposed API surface (read-only): ```js // In a user script: game.onTick((dt) => { ... }); game.onKey('space', () => { player.jump(); }); game.broadcast('event', payload); game.player.setHealth(100); scene.findOne('Cube1').rotateY(0.1); ui.set({ score: 42 }); ``` Scripts CANNOT access `window`, `document`, `fetch`, `localStorage`, network. They run isolated in a Worker with a strict postMessage bridge. ### `engine/multiplayer/MultiplayerSync.js` Colyseus 0.16 client. Joins a room per `gameId` if multiplayer is enabled. Syncs player positions/rotations/animations at 20 Hz. Server is at `VITE_REALTIME_WS`. ### `engine/gd/*` (Geometry Dash modules) 30+ classes implementing GD-style 2D autorunner gamemodes within a 3D world: - `GdCube.js` — basic jump - `GdShip.js` — gravity-flip flight - `GdWave.js` — sine-wave dash - `GdBall.js` — flip-jump ball - `GdUfo.js` — multi-tap jumps - `GdSpider.js` — instant teleport - + portals, speedpads, spikes, finish lines, trail effects, checkpoint music Factories for each (`GdSpikeFactory`, `GdPortalFactory`, `GdMusicFactory`, ...) live under `AdminPreview/gd*/`. ## Data flow (single frame) ``` 1. INPUT keyboard/touch/gamepad → PlayerController.onInput() 2. PHYSICS gravity + velocity + collision against blocks/models 3. SCRIPTS user script onTick(dt) callbacks (in worker, asynchronous) 4. MULTIPLAYER read remote positions, lerp other players 5. RENDER Babylon scene.render() → WebGL2 draw calls 6. UI React re-renders only if game.ui.set() called (throttled 250ms) ``` ## What's NOT in the player - **Editing** — that lives in [Studio](https://git.rublox.pro/rublox/studio). - **Project listing / search / publishing** — handled by main site (`rublox.pro/app`). - **Authentication UI** — players arrive with JWT/ticket. The player only consumes them. - **Admin / moderation** — moved out to private repo (`disaster-recovery/` for owner). ## Performance hotspots (places to look first when laggy) 1. **`game.ui.set()` called every frame** — React setState 60Hz tanks FPS. Throttle to 250ms with a diff check. 2. **`scene.findOne()` on init** — `sceneSnapshot` arrives via rAF; reference is null. Move into `onTick` or `setTimeout(0)`. 3. **Babylon's `blockMaterialDirtyMechanism=true`** — DON'T enable. Breaks new mesh rendering (debris, tracers). 4. **`createOrUpdateSelectionOctree()`** — same. Breaks editor preview ghosts. 5. **GLB load via `SceneLoader` instead of `AssetContainer`** — leaks materials. Use `AssetContainer` + `instantiateModelsToScene()`. ## Where to start a feature | What you want to add | Start here | |---|---| | New block type | `engine/CONST/blockTypes.js` + texture in `public/kubikon-assets/blocks/` | | New scripting API | `engine/scripts/ScriptSandboxAPI.js` (add to `apiSurface`) | | New GD gamemode | Copy `engine/gd/GdBall.js`, register in `GdGameModeRegistry.js` | | New HUD widget | `editor-shared/GameHud.jsx` | | New preview route for designer | `AdminPreview/` (e.g. `gdSkins/PreviewGdSkins.jsx`) | | Multiplayer event | Colyseus schema in `engine/multiplayer/schemas/` + handler in `MultiplayerSync.js` | ## License notes for contributors By contributing you agree to license your changes under AGPL-3.0 AND grant the project maintainer a non-exclusive irrevocable license to sublicense (see [CLA.md](./CLA.md)). This is required so we can offer commercial licenses to enterprises.