player/ARCHITECTURE.md
МИН 87444ee2c8 Initial public release: Rublox Player v1.0
Open-source web player for Rublox games, dual-licensed under
AGPL-3.0 + Commercial.

Highlights:
- Babylon.js 7 + React 18 + Vite 5 stack
- Self-contained engine (~46k lines): BlockManager, ModelManager,
  PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD
  gamemodes
- Configurable backend via VITE_API_BASE and friends — works against
  staging (dev-api.rublox.pro) out of the box
- Standalone mode (VITE_STANDALONE=true) loads a bundled sample game
  for first-run without any backend
- Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG
- Lint + format scaffolding (ESLint + Prettier + EditorConfig)
- Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md
- Issue templates: bug_report, feature_request, security_disclosure

Removed before public release:
- frontend_deploy.py (contained production SSH credentials)
- ~27 admin endpoints (kept in private repo)
- Hard-coded internal URLs and IPs
- All previous git history (clean repo init)
2026-05-27 23:04:04 +03:00

146 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architecture — Rublox Player
How a 3D game is loaded, rendered, and synced. ~5 minutes read.
## Top-level flow
```
URL = /<gameId> 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.