Engine & GUI Stack
three.js with WebGPU for the 3D world, Lit + plain HTML/CSS for the GUI shell.
Split
State Management
A central store lives at game/src/state (extracted from game/src/simulation/state to top-level for clarity). This is the single source of truth for all game state (plans, terrain, camera, UI state). Lit components dispatch actions or read state via this store; no prop drilling, no scattered context providers.
Boundary
three.js owns the 3D canvas exclusively. Lit owns everything in the DOM overlay. The two talk through a small typed event/store bridge (selection, camera state, tool mode) — kept intentionally thin so either side can be iterated on independently.
WebGPU as a Hard Gate
WebGPU browser support is not negotiable — no WebGL fallback is planned. Target: Chrome, Firefox, and Edge on Windows/macOS/Linux. Safari is out-of-scope. This is acceptable for the target audience: resource-intensive city builders naturally attract players with modern systems that already have robust WebGPU support, and the performance benefits of compute shaders and procedural GPU generation justify the gate.
Coordinate System
The game engine is strictly z-up, right-handed: +z is up, +x and +y form the ground plane, yaw rotates around +z, and pitch tilts forward / backward with positive pitch looking up. Every coordinate triple carried by game state, simulation, and the render layer is in this frame; three.js (whose camera and lights default to y-up) is used as a z-up world here and the render layer never remaps axes to three's y-up convention. The ground plane's position attribute is a flat x/y quad at z=0, so THREE.PlaneGeometry matches the engine's frame directly with no mesh rotation.
This is a load-bearing invariant: rotating axes at the render layer (e.g. an "engine-to-three roll" to align with three's y-up default) breaks the lookAt math as soon as the camera has a non-zero z, because the position and the lookAt target end up in different frames. The render layer keeps both in the engine frame and lets lookAt orient the camera's local -Z along the engine forward.
Face Winding
Front faces are clockwise when viewed from the surface's outward normal. For a horizontal surface in the ground plane (a road, a parcel, a terrain tile), the outward normal is +z, so the index buffer must list the ring of vertices in CW order when projected onto the x/y plane and viewed from above (i.e. from +z). Three's computeVertexNormals and front-face culling both rely on this convention; getting the winding backwards inverts the normal and back-face culls the surface away from any camera looking down at it.
Coordinate Precision
Simulation state (splines, blocks, parcels, plats, and every other game data model) is authored and stored in cm, as plain JS number — a float64, with sub-nanometer absolute precision even hundreds of kilometers from the origin. There is no reason to narrow this for gameplay logic: CPU-side geometry and simulation math get the full precision float64 offers, at any city scale.
The risk is downstream, at the GPU vertex-buffer layer: WebGPU stores vertex data as float32, whose absolute precision degrades with distance from the origin (~1.2mm of error by 10km out). Uploading raw world-space cm coordinates straight to the GPU will visibly jitter or z-fight well before a city reaches "tens of kilometers" across.
Resolution: keep authoritative geometry in cm/float64 everywhere in simulation and other game data models; only marshal the data dependent on a render pass through a floating-origin (camera-relative / "relative to eye") transform immediately before the CPU→GPU handoff — recentering a local float32 origin near the camera or the currently-edited sketch. This is a standard technique for large worlds (Cesium, flight sims) and three.js's camera utilities can handle most of this translation layer. It's a rendering-layer transform, not a units change: nothing upstream of the GPU handoff ever deals in float32.
Why This Pairing
- WebGPU compute is the enabling tech for on-GPU procedural materials and mesh generation (see Materials doc).
- Avoids committing to a heavyweight general game engine for a web-native, browser-first target.
- Standard web tech (Lit, HTML/CSS) for GUI keeps iteration speed and accessibility tooling native, rather than drawing UI inside the 3D scene.
Browser Support
| Browser | Windows | macOS | Linux | Mobile |
|---|---|---|---|---|
| Chrome | ✅ | ✅ | ✅ | Future (DLC) |
| Firefox | ✅ | ✅ | ✅ | Future (DLC) |
| Edge | ✅ | ✅ | ❌ | — |
| Safari | — | ❌ out-of-scope | — | ❌ out-of-scope |
Future Ideas (DLC)
- Mobile browsers — iOS Safari, Android Chrome/Firefox. Requires touch input patterns (pan/pinch zoom), smaller viewport layouts, and testing on mobile GPUs. Not v1.