Allegiance began as an internal experiment inside Entropy: a political conquest game set on a full-scale Earth in 2100. You start in a real hometown with five armed comrades and an objective to take a regime outpost. From there, you print pamphlets, give speeches on street corners, recruit citizens, organize a party, and take over the planet region by region through elections, coups, or war.
In Entropy, the game was a native desktop application built with Rust and wgpu, running TypeScript game logic in Deno. It looked good and ran well, but keeping a political conquest game trapped in a local native binary was an architectural mismatch. A game about competing factions, shifting borders, and global propaganda belongs on the open web, where any player can open a link, found a nation, meet rivals on the street, and fight over real territory.
This week we completed the port of Allegiance to Babylon.js in its own standalone repository (D:/projects/common/allegiance), running in WebGL 2 without React. On top of the ported client, we layered an online-only MMO infrastructure using the Indie Machine API for persistent territory, anonymous sessions, and accounts, backed by Cloudflare Durable Objects for low-latency spatial rooms and refereed combat.
Here is how the port was structured, what changed when moving from Rust/wgpu to TypeScript/WebGL 2, the shader compilation trap that froze Chrome for 15 seconds, and how the networked simulation operates.

What changed in the port
- Native to browser runtime.
cargo run --bin example --release -- allegiancereplaced with a standard Vite application (vite = 8.3.4,@babylonjs/core = 9.30.0). The production client builds in 2.66 seconds into a 2.13 MB main bundle (589 kB gzip) plus worker chunks. - Rust QuadPlanet to TypeScript Web Workers. The native QuadPlanet streaming engine (Terrarium elevation PNG decoding, cube-sphere quadtree, chunk meshing, OpenMapTiles vector tile parsing, building footprint extrusion) was ported to TypeScript and isolated in a dedicated background worker (
earth_worker.ts, 45.28 kB). - Native mesh simplification to WebAssembly meshoptimizer. High-poly procedural meshes generated by Mesha are simplified and cached in IndexedDB via
meshoptimizerWebAssembly bindings in a mesh worker (mesh_worker.ts, 1.03 MB). - WGSL to GLSL ES 3.00 with family variants. QuadPlanet and Allegiance shaders were ported from WebGPU WGSL to GLSL ES 3.00. Rather than compiling an monolithic uber-shader, the engine compiles specialized variants per mesh family (
terrain,cityTiles,buildings,props,people,effects,sky). - Logarithmic depth with floating render origin. Render space remains camera-relative. As the camera travels across Earth's 6,371 km radius, the game rebases the render origin so 32-bit floats never suffer from planetary precision loss. Logarithmic depth enables a 0.1 m near plane out to a 100,000,000 m far plane with zero z-fighting.
- Cascaded directional sun shadows. A 4-cascade shadow receiver using a 2D Texture Array (
DEPTH24) with hardware comparison sampling (sampler2DArrayShadow). - Online-only MMO architecture. Replaced local IndexedDB campaign files (
campaign.json) with an authoritative server model. The Indie Machine API (/api/allegiance, Prisma 7.10 + Postgres) handles sessions, nation founding, territory ownership, and signed realtime tickets. A Cloudflare Worker (allegiance-realtime) runs Durable Object rooms per 0.25 degree geographic cell.
cd allegiance
npm install
npm run typecheck
npm run test:unit
npm run build1. The coordinate system: planetary scales without 32-bit jitter
Earth has a mean radius of 6,371,007 metres. Standard 32-bit IEEE 754 floating point numbers have 24 bits of mantissa, giving roughly 7 decimal digits of precision. If an engine attempts to place a coffee mug or a rifle barrel at absolute Earth coordinates [x, y, z], the distance between representable float values is:
6,371,007 * 2^-24 ≈ 0.38 metresPlacing objects in world coordinates would cause every vertex on screen to wobble by almost 40 centimetres every time the camera turned.
To solve this, Allegiance retains QuadPlanet's render-space paradigm. The scene is drawn relative to a floating renderOrigin situated directly on the planet surface beneath or near the camera:
// src/engine/terrain.ts
private record(p: Placed): void {
const t: V3 = [p.origin[0] - this.origin[0], p.origin[1] - this.origin[1], p.origin[2] - this.origin[2]];
const m = p.basis ? tileMatrix(p.origin, p.basis, this.origin) : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t[0], t[1], t[2], 1];
p.drawable.setItem(m, [1, 1, 1, 0], p.basis ? [0, 0, 0, 0] : [p.texOrigin[0], p.texOrigin[1], p.texOrigin[2], 0]);
p.drawable.setBounds(t, p.radius);
if (p.drawable.instanceCount !== 1) p.drawable.setCount(1);
}When the player moves across the city, the game checks if the distance from renderOrigin exceeds a threshold. When it rebases, the vertex buffers in GPU memory remain completely untouched. The engine simply updates the 16-float model matrix in each instance record:
apply(update: EarthUpdate, renderOrigin: V3): void {
const moved = renderOrigin.some((v, i) => v !== this.origin[i]);
this.origin = renderOrigin;
if (moved) for (const p of [...this.chunks.values(), ...this.tiles.values()]) this.record(p);
// ...
}Because coordinates inside each mesh are local offsets relative to their chunk or tile origin, full 32-bit precision is preserved where the player stands.
Custom planetary camera
In standard game engines, the camera's up vector is fixed to [0, 1, 0]. On a spherical planet, "up" is whichever normal vector points directly out from the planet's core at your current latitude and longitude.
We extended Babylon's base Camera class to bypass internal orbital transformations and write the view matrix directly via Matrix.LookAtRHToRef:
// src/engine/renderer.ts
export class WorldCamera extends Camera {
readonly view = Matrix.Identity();
override getViewMatrix(): Matrix { return this.view; }
}
// Positioning the camera in render space:
setCamera(position: readonly number[], target: readonly number[], up: readonly number[], fovDegrees = 0): void {
Matrix.LookAtRHToRef(
new Vector3(position[0], position[1], position[2]),
new Vector3(target[0], target[1], target[2]),
new Vector3(up[0], up[1], up[2]),
this.camera.view
);
this.camera.position.set(position[0], position[1], position[2]);
this.eye = [position[0], position[1], position[2]];
this.camera.fov = (fovDegrees > 0 ? fovDegrees : 45) * Math.PI / 180;
}Logarithmic depth
A flight in Allegiance can take the player from a street corner inspection of a rifle (view distance 0.1 m) to flying in the Cobra above the troposphere looking at the horizon (view distance 100,000 km).
Standard linear depth precision fails catastrophically across this range. In our GLSL ES 3.00 world shader, depth is calculated logarithmically in the vertex stage (and optionally fragment stage):
// src/engine/shaders/world_glsl.ts
const float LOG_DEPTH_FAR = 1.0e8;
float log_depth(float w) {
return clamp(log2(max(w, 1.0e-6) + 1.0) / log2(LOG_DEPTH_FAR + 1.0), 0.0, 1.0);
}Because OpenGL clip space expects z/w in the range [-1, 1], whereas WebGPU clip space uses [0, 1], the vertex shader remaps the logarithmic depth into OpenGL clip coordinates:
float depth = log_depth(gl_Position.w);
gl_Position.z = (2.0 * depth - 1.0) * gl_Position.w;This ensures identical depth distribution across the entire planetary scale without z-fighting.
2. Terrain and city streaming in an isolated worker
In Entropy, QuadPlanet's chunk meshing and vector tile decoding ran on native background threads using Rust's quadplanet crate. For the browser port, this entire subsystem was rewritten in clean TypeScript within src/quadplanet/earth/.
The streaming architecture runs inside a dedicated Web Worker (earth_worker.ts):
- Elevation pyramid. The worker fetches and caches AWS Terrarium elevation tiles (PNG format, where elevation in metres is
(r * 256 + g + b / 256) - 32768). Zoom level 0 (terrarium_z0.png) is bundled locally to allow immediate startup. - Cube-sphere quadtree. The planet is divided into six root cube faces, subdivided hierarchically based on the camera position and distance metrics. Seams between differing quadtree LOD levels are stitched by generating transition skirt vertices.
- OpenStreetMap vector tiles. City tiles fetch Mapbox Vector Tile (MVT) protobufs from OpenMapTiles. The worker parses building polygons, calculates building heights based on storey tags, generates extruded 3D building box meshes, and rasterizes road ribbons.
- Main thread synchronization. The worker posts structured chunks and tiles to the main thread via transferrable typed arrays.
On the main thread, EarthSystem throttles GPU uploads to a fixed per-frame budget, ensuring smooth 60 FPS frame times during high-speed vehicle flight:
// src/quadplanet/earth/system.ts
export class EarthSystem {
// Ingests worker messages without blocking the render loop
step(viewer: V3, unlimited = false): EarthUpdate {
// Collects completed chunks and tiles from the inbox
// Bounded GPU mesh uploads prevent frame spikes
// ...
}
}3. High-poly procedural assets and WASM mesh simplification
Allegiance does not use static imported FBX or glTF models. Every vehicle, weapon, house, and civilian is generated procedurally by our parametric engine, Mesha (src/mesha/).
Mesha produces high-detail, watertight geometry:
- A full-detail male character contains 417,461 triangles.
- A full-detail female character contains 407,933 triangles.
Drawing thirty civilians on a street corner with 400,000 triangles each would melt any GPU. We needed multi-tier level of detail (LOD) meshes generated dynamically and cached.
In Entropy, this was handled by mesh_simplify.rs. For Babylon.js, we ported the pipeline to TypeScript in src/engine/mesh_simplify.ts utilizing meshoptimizer compiled to WebAssembly:
// src/engine/mesh_simplify.ts
export function simplifyPacked(
vertices: Float32Array,
indices: Uint32Array,
params: SimplifyParams
): { vertexData: Float32Array; indexData: Uint32Array; stats: SimplifyStats } {
// 1. Group triangles by surface (material ID in uv.x + vertex color)
// 2. Weld vertices by position to 0.1 mm precision
// 3. Drop disconnected parts whose bounding box diagonal is under minFeature (union-find)
// 4. Run MeshoptSimplifier to an absolute error in metres
// 5. Re-emit flat-shaded packed vertices (12 floats per vertex)
}The union-find step drops tiny components (like individual door handles or window mullions) before running edge collapses, preventing degenerate geometric spikes.
Here are the verified triangle reduction numbers produced by our unit test run (tests/unit/allegiance_people.test.ts):
| Model | Full Quality (LOD 0) | Reduced (LOD 1) | Distant (LOD 2) | Reduction |
|---|---|---|---|---|
| Mesha Male | 417,461 | 64,930 | 5,356 | 98.7% |
| Mesha Female | 407,933 | 85,430 | 6,832 | 98.3% |
MeshCache on IndexedDB
Generating a 400k-triangle human and simplifying it takes several seconds of CPU time. To prevent repeated generation, MeshCache (src/engine/mesh_cache.ts) stores compiled geometry into the browser's IndexedDB (allegiance-mesh-cache).
On startup, the cache reads its index into memory in a single transaction. When the game requests a mesh:
- If in RAM: returned immediately (
"ready"). - If in IndexedDB: scheduled for async retrieval (
"pending"). - If missing: generated in the background by
WorkerPool(mesh_worker.ts).
An in-memory LRU budget (default 1.2 GB) prevents V8 heap exhaustion while keeping frequently seen street assets immediately available.
4. Thin instances and packed geometry
In WebGL, the overhead of individual draw calls and scene graph node manipulation is the primary bottleneck. Placing 500 distinct Mesh objects in a Babylon Scene creates significant CPU overhead in matrix hierarchy updates and bounding box evaluations.
Allegiance bypasses scene nodes entirely by using Babylon Thin Instances (@babylonjs/core/Meshes/thinInstanceMesh).
Every mesh in the game shares a single vertex layout of 12 floats per vertex:
// src/engine/drawables.ts
export const VERTEX_FLOATS = 12;
// Layout: position (3), normal (3), uv (2), color (4)Instance data is uploaded as raw packed Float32 arrays directly to GPU buffer attributes:
ITEM_RECORD(24 floats): Model matrix (16), RGBA tint (4), texture origin (4).PERSON_RECORD(32 floats): Model matrix (16), RGBA tint (4), texture origin (4), skin color (4), hair color (4).
A single Drawable instance handles both individual dynamic entities (like the player's flying car) and instanced batches (like 200 trees, 50 streetlights, or a crowd of civilians):
// src/engine/drawables.ts
export class Drawable {
readonly mesh: Mesh;
// ...
setRecords(records: Float32Array, count: number): void {
this.mesh.thinInstanceSetBuffer("world0", records.subarray(...), 4, false);
this.mesh.thinInstanceSetBuffer("world1", records.subarray(...), 4, false);
this.mesh.thinInstanceSetBuffer("world2", records.subarray(...), 4, false);
this.mesh.thinInstanceSetBuffer("world3", records.subarray(...), 4, false);
this.mesh.thinInstanceSetBuffer("tint", records.subarray(...), 4, false);
this.mesh.thinInstanceSetBuffer("texOrigin", records.subarray(...), 4, false);
this.mesh.thinInstanceCount = count;
this.mesh.isVisible = count > 0;
}
}Terrain chunks, city road ribbons, and buildings use the exact same thin instance drawing mechanism. This unifies the entire rendering pipeline into a predictable set of instanced draws.
5. Shaders: GLSL ES 3.00 and the 15-second Chrome freeze
QuadPlanet's original shader was written in WGSL for WebGPU. We ported it to GLSL ES 3.00 (src/engine/shaders/world_glsl.ts) to run on Babylon's WebGL 2 engine.
Family variants vs. monolithic uber-shaders
The world shader handles procedural terrain blending, PBR material maps, road markings, territory borders, crowd clothing colors, destruction smoke/fire, and shadow receiving.
Compiling all of these features into one massive uber-shader created unacceptable fragment shader complexity and hit performance walls on integrated graphics. We implemented family-specialized variants compiled with only the material branches each family needs:
// src/engine/renderer.ts
export const SHADER_FAMILIES = {
terrain: [0, 1, 2],
cityTiles: [8, 10],
sky: [9],
buildings: [3, 4, 5, 6, 7, 11],
props: [3, 4, 5, 6, 7, 11, MAT_FOLIAGE],
effects: [3, 4, 6, 11, MAT_FIRE, MAT_SMOKE],
people: [3, 4, 5, 7, 12, 13, 14, 15, 16],
};When creating a mesh, renderer.variant(kind, mask) inspects the material IDs present in the mesh's vertex buffer and assigns the smallest shader variant that covers them.
The ANGLE loop unrolling trap
During our first end-to-end runs in Google Chrome on Windows, we hit a severe bug: the loading screen froze for 15.2 seconds during the initial shader compilation pass.
Investigating Chrome's chrome://gpu logs and the DirectX shader compiler revealed the culprit. Chrome translates WebGL 2 GLSL into HLSL via ANGLE, targeting Direct3D 11. In our procedural lighting and cloud sampling loops, the loop bounds were compile-time constants (e.g. for (int i = 0; i < 4; i++)).
ANGLE's translator and the underlying D3D compiler attempted to aggressively unroll every single loop at every call site. Because our shader functions called helper noise and lighting routines inside nested loops, the resulting inlined HLSL ballooned to tens of thousands of instructions, choking the D3D shader compiler.
The fix was deceptively simple: force the compiler to treat the loop bound as dynamic so it cannot be statically unrolled:
// src/engine/shaders/world_glsl.ts
// Always 0 (sun_color.w is never set at runtime): added to loop bounds so the shader
// compiler keeps them as loops instead of unrolling every call site (which made
// Chrome's D3D compile take 15 seconds).
#define LOOP0 int(u_world[1].w)
// In loops:
for (int i = 0; i < 4 + LOOP0; i++) {
// ...
}Because u_world[1].w is a uniform value passed per-frame from CPU memory, the compiler cannot prove its value at compile time and is forced to emit a standard hardware jump/branch loop.
With this single macro change, total shader compilation time plummeted from 15,200 ms to 134 ms.
6. Four-cascade directional sun shadows
To handle shadows ranging from small character details (0.5 m away) to entire city blocks (500 m away), we implemented a 4-cascade directional shadow system in src/engine/shadows.ts.
In Babylon.js, standard shadow generators create multiple separate render targets. We instead allocate a single 2D Texture Array render target with 4 layers:
// src/engine/shadows.ts
const rtt = new RenderTargetTexture(
"sun-shadows",
{ width: size, height: size, layers: 4 },
this.scene,
{
generateMipMaps: false,
type: Constants.TEXTURETYPE_UNSIGNED_BYTE,
samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
format: Constants.TEXTUREFORMAT_RED,
}
);
rtt.createDepthStencilTexture(
Constants.LESS,
true,
false,
1,
Constants.TEXTUREFORMAT_DEPTH24,
"sun-shadow-depth"
);In the GLSL fragment shader, all four cascades are sampled through a single hardware comparison sampler:
uniform highp sampler2DArrayShadow shadow_maps;Casters are assigned cascade ranges: civilians and small props only render into cascades 0 and 1, while tall buildings and towers render into all 4 cascades. Furthermore, static cascades update with staggered refresh rates, sparing the frame budget when the sun angle holds still.
7. The online MMO expansion
In the original Entropy version, campaigns were saved locally to a single JSON file. In this port, we converted Allegiance into an online-only MMO.
There is no offline mode: when the API cannot be reached, the game halts at the title screen or displays a red alert banner on the HUD, refusing to fall back to an unverified local state.

The MMO architecture is split across two services:
1. Indie Machine API (indie-machine/app/api/allegiance)
- Built with Next.js 16 route handlers, Prisma 7.10, and PostgreSQL.
- Manages anonymous player sessions (
/api/allegiance/session), account upgrades (/api/allegiance/account), and party founding (/api/allegiance/nation). - Serves as the system of record for world territory ownership (
/api/allegiance/territories). When a player marches troops into a region held by another player's party, the contest is submitted to the API and resolved server-side based on both armies. - Issues signed HMAC SHA-256 realtime tickets (
/api/allegiance/ticket) allowing clients to connect to the realtime worker.
2. Realtime Cluster (allegiance-realtime)
- Built as a Cloudflare Worker using Durable Objects (
wrangler = 4.145.0). - Room Durable Objects: Earth is partitioned into geographic cells of 0.25° latitude and longitude (~28 km across). When a player enters a cell, their client establishes a WebSocket connection to that cell's Room DO.
- The Room DO validates movement velocities (players walking faster than permitted speeds are rejected; flying cars are granted higher velocity limits) and broadcasts player state to everyone in the cell.
- Authoritative combat refereeing: when a player fires a weapon, the shot is submitted to the Room DO. The room validates fire rate cooldowns, checks distance and line-of-sight against static street walls, and applies authoritative damage to target health.



8. Verification and first-party numbers
All claims about correctness and performance are verified by tests executed locally on Windows 11 with an Intel Core i5-12500 and integrated Intel UHD Graphics 770.
1. The Playwright BDD two-player suite
The end-to-end MMO experience is tested by tests/bdd/mmo.mjs, driving the Gherkin feature tests/features/mmo_two_players.feature across two concurrent headless Chrome instances:
Scenario: The title says plainly when the server can't be reached
ok title shows the outage and RETRY (0.8 s)
Scenario: An anonymous player founds a nation
ok founds Dawn Front in London (1.4 s)
Scenario: A second player arrives and they see each other
ok Iron Choir arrives and Ada sees Mara (1.9 s)
Scenario: A street fight is refereed by the room
ok Mara shoots Ada and damage is registered (1.1 s)
Scenario: Territory taken from another player
ok Mara's army takes London from Ada (2.2 s)
Scenario: An anonymous player keeps their nation with an account
ok Ada registers account and retains nation (1.3 s)2. The Vitest unit suite
Running npm run test:unit executes 20 test suites covering procedural generation, navigation, vehicles, combat, destruction, and netcode:
Test Files 6 failed | 14 passed (20)
Tests 8 failed | 462 passed (470)
Duration 187.49sThe 8 failing tests are documented in the failure notes below: they are pre-existing assertions from the Entropy native engine expecting raw WGSL shader string tokens that were deliberately removed during the GLSL ES 3.00 port.
3. Production build output
Running npm run build (tsc --noEmit && vite build) executes cleanly with zero TypeScript errors in 2.66 seconds:
dist/index.html 1.43 kB │ gzip: 0.64 kB
dist/assets/earth_worker-CqXjN2VN.js 45.28 kB
dist/assets/mesh_worker-DrBzvjUb.js 1,032.18 kB (includes meshopt WASM)
dist/assets/index-CeSYAol7.js 2,136.29 kB │ gzip: 589.26 kB
✓ built in 2.66s4. Realtime referee suite
Running npm test inside allegiance-realtime executes 10/10 passing unit tests for the combat referee in 143.8 ms:
✔ cells: a point lies inside its own cell's bounds (0.7ms)
✔ enu measures meters east, north and up (0.2ms)
✔ walking speed passes; a runner covering 200 m in a second is refused (0.3ms)
✔ standing outside the cell asks the client to change rooms (0.1ms)
✔ a rifle shot along the line hits the first body, not the one behind it (1.0ms)
✔ walls: a pellet stopped short by the shooter's wall hits nobody (0.2ms)
✔ refused shots: too fast, wrong weapon, from elsewhere, too many pellets (0.2ms)
✔ a shotgun spreads its pellets and adds their damage on one target (0.2ms)
✔ the dead are not hit (0.2ms)
ℹ tests 10, pass 10, fail 0 (143.8ms)9. Decision log
- WebGL 2 over WebGPU. While Entropy used WebGPU via wgpu, we chose WebGL 2 (
@babylonjs/core/Engines/engine) for the browser port. WebGPU support in browsers remains fragmented across mobile devices and Linux, whereas WebGL 2 is ubiquitous across 98%+ of global browsers. Babylon's WebGL 2 engine provides raw access to 2D texture arrays, uniform buffers, instanced arrays, and logarithmic depth without requiring experimental flags. - Thin Instances over Babylon Mesh nodes. Rather than creating a
TransformNodeorMeshper building or tree, all repeated world objects are drawn using Thin Instances. This avoids scene graph traversal overhead and allows instance buffers to be updated with raw typed array operations. - 2D Canvas UI over DOM/HTML overlays. The entire game HUD, dialog system, propaganda posters, and command console are drawn on a single overlaid 2D HTML canvas (
src/engine/ui.ts). This preserves the immediate-mode painting contract ofEntropy.UI(drawRectanddrawText), avoiding DOM reflows and CSS style recalculations during active combat. - WASM meshoptimizer over JS decimation. High-poly character simplification requires heavy arithmetic. Using
meshoptimizercompiled to WebAssembly inside a Web Worker delivers edge-collapse simplification that executes in milliseconds without dropping frames on the main thread. - Cloudflare Durable Objects for spatial rooms. Rather than maintaining a fleet of persistent dedicated servers, partitioning Earth into 0.25° cells mapped to Cloudflare Durable Objects allows zero-cost scaling when rooms are empty and immediate state synchronization when players converge.
10. Failure notes and known limits
- The 15-second ANGLE compile freeze. Inlining loops in GLSL caused ANGLE's D3D11 translator to unroll nested loops into tens of thousands of instructions. Solved by defining loop bounds with
#define LOOP0 int(u_world[1].w), forcing dynamic loop generation and reducing compilation time from 15.2 s to 134 ms. - The 8 pre-existing shader test failures. Eight unit tests in
allegiance_materials.test.ts,allegiance_crowd.test.ts, andallegiance_visuals.test.tsfail in Vitest because they assert raw string tokens from the old WGSL shaders (e.g.expect(PEOPLE_SHADER).toContain("var<storage, read> instances:")orexpect(b[5].resource).toEqual({ type: "SamplerRepeat" })). These tests were ported from the native engine and await updating to assert GLSL uniforms and texture bindings. - Room cell edge boundary blindness. Durable Object rooms are strictly partitioned into 0.25° cells (with 0.01° hysteresis). If two players stand 20 metres apart across an exact cell boundary, they are in different Durable Objects and currently cannot see each other. The planned fix is subscribing clients to neighboring rooms when within 100 metres of an edge.
- Client-trusted army sizes. In the current netcode, while PvP takes are resolved server-side, the server still trusts the client-reported troop count for PvE regional conquests. We mitigate this with a rate limit of 30 claims per minute, but moving the full strategic simulation to a server-side validator is tracked in the backlog.
- Remote player vehicle representations. When a remote player enters a flying car or Cobra, they currently render as an overhead marker and minimap icon rather than a dynamic 3D vehicle mesh.
What's next
With the Babylon.js client running cleanly and MMO netcode verified end-to-end, the immediate roadmap includes:
- Production deployment. Deploying
allegiance-realtimeto Cloudflare Workers, provisioning a production Postgres database via Prisma, and hosting the client atallegiance.indie-machine.com. - Neighbor room subscription. Allowing clients standing near cell edges to subscribe to adjacent room streams, eliminating boundary pop-in.
- Remote vehicle meshes and Cobra dogfights. Synchronizing full vehicle transforms and projectile flight paths between players in the air.