Philosophy & Design Principles
Web Engine Dev is built around a set of architectural principles that inform every design decision, from package boundaries to API surface to memory layout.
Bring Your Own Engine
The foundational philosophy is that no one engine fits all projects. Rather than a monolithic framework, Web Engine Dev provides a ecosystem of composable packages. You assemble the engine that fits your project:
- Building a 2D puzzle game? Use
math,ecs,sprites,input, andphysics2d. - Building a 3D shooter? Add
renderer,gltf,animation,physics3d,netcode, andaudio. - Building a simulation tool? Use
ecs,spatial,ai, andpathfindingwith no rendering at all.
Each package declares its dependencies explicitly and can be installed independently via npm. The umbrella @web-engine-dev/engine package re-exports everything for convenience, but it is never required.
Modularity Through Layered Dependencies
Packages are organized into dependency layers (Layer 0 through Layer 9). A package at Layer N may only depend on packages at Layer N-1 or below. This rule is enforced by design and prevents circular dependencies:
Layer 0: math (zero dependencies)
Layer 1: ecs, events, time, scheduler, hierarchy, resources, change-detection
Layer 2: serialization, reflection, splines, scripting
Layer 3: input, audio, spatial
Layer 4: physics2d, physics3d, cloth, ragdoll, destruction
Layer 5: animation, character, gesture
Layer 6: render-graph, shader-compiler
Layer 7: renderer, gltf, particles, sprites, text, terrain, tilemap, ui, vfx, gizmos
Layer 8: scene, prefab, save, assets, netcode, ai, pathfinding, procgen, state
Layer 9: engine (umbrella), editor-core, editor-uiThis layering means that @web-engine-dev/math has zero dependencies, @web-engine-dev/ecs depends only on Layer 0-1 packages, and the renderer depends only on layers below it. You get exactly the transitive dependency tree you expect.
Data-Oriented Design
The engine uses data-oriented design (DOD) principles throughout, prioritizing cache-friendly memory access patterns over object-oriented hierarchies.
ECS with SoA Storage
The Entity Component System uses archetype-based Struct of Arrays (SoA) columnar storage. Entities with the same set of components share an archetype, and each component type is stored in a contiguous array within that archetype. This means iterating over all positions is a linear memory scan rather than pointer chasing through scattered objects.
Archetype [Position, Velocity]
Position column: [x0,y0,z0, x1,y1,z1, x2,y2,z2, ...] (contiguous)
Velocity column: [x0,y0,z0, x1,y1,z1, x2,y2,z2, ...] (contiguous)Zero-Allocation Hot Paths
Systems that run per-frame (ECS queries, transform propagation, rendering, physics) are designed to avoid heap allocations in steady state. The math library provides three API tiers for this purpose:
- Immutable API (default) -- returns new instances, safe and readable
- Mutable API (
addMut,scaleMut) -- modifies in place, no allocation - Batch API (
BatchMath) -- operates on packedFloat32Arraybuffers
Object pools (MathPool, PosePool, buffer pools) are available for temporary objects in hot paths.
WebGPU-First Rendering
The renderer targets WebGPU with WebGPU-only runtime execution. This means:
- Runtime shaders are authored in WGSL
- Compute-driven features (GPU culling, GPU particles, GPU-driven rendering) are first-class paths
- The device abstraction (
GpuDevice) exposes a stable API while runtime execution remains WebGPU
import { createDevice } from '@web-engine-dev/renderer';
// WebGPU runtime (preferredBackend retained for API compatibility)
const { device, backend } = await createDevice({
canvas,
preferredBackend: 'auto',
});The rendering pipeline includes physically-based materials (metallic-roughness workflow aligned with glTF), cascaded shadow maps, image-based lighting with anti-firefly techniques, clustered forward lighting, and a post-processing pipeline with 22 effects.
TypeScript-Native
Web Engine Dev is written in TypeScript from the ground up, not bolted on as type definitions for a JavaScript library. This enables:
- Strict mode with
noUncheckedIndexedAccess-- indexed access returnsT | undefined, catching off-by-one errors at compile time - Branded types for domain-specific values (entity IDs, resource descriptors) that prevent accidental misuse
- Discriminated unions for state machines and variant types that the compiler can exhaustively check
- Generic constraints on ECS queries, resource descriptors, and event types that preserve type safety through the entire pipeline
The build target is ES2022 with ESNext module format and verbatimModuleSyntax. Each package produces ESM and CJS output with .d.ts type declarations.
Performance as a Feature
Performance is not an afterthought in a game engine. Key design decisions reflect this:
- Algorithm complexity is documented -- JSDoc annotations include
@remarks O(n log n)where applicable - Data structures are chosen for access patterns -- spatial hashes for O(1) lookups, intrusive heaps for O(1) decrease-key in pathfinding, bucket queues for flow fields
- Determinism is required for gameplay systems -- physics, netcode, and animation produce identical results given the same inputs. No
Date.now()orMath.random()in gameplay code (use seeded RNG) - Parallel execution is supported -- the ECS scheduler detects read/write conflicts between systems and can execute non-conflicting systems in parallel via Web Workers
Interface-First Design
External systems like physics, audio, and storage are accessed through interfaces (ports), not concrete implementations:
@web-engine-dev/physics2ddefines the physics interface (bodies, shapes, joints, collision detection)@web-engine-dev/physics2d-rapierprovides the Rapier adapter for that interface- The same pattern applies to 3D physics (
physics3d/physics3d-rapier)
This means you can swap physics backends without changing game code, and the core engine never imports concrete implementations directly.
Fail Fast, Fail Loud
The engine uses invariant() assertions throughout to catch invalid states as early as possible. A corrupted entity ID discovered at creation time (with a clear error message and stack trace) is vastly preferable to silent data corruption that manifests three systems later. In development builds, these assertions are active; in production, they are stripped.
Next Steps
- Feature Overview -- See the complete feature matrix
- Comparison with Other Engines -- How Web Engine Dev compares to alternatives
- Architecture Overview -- Deep dive into the system architecture
- Data-Oriented Design -- ECS storage and memory patterns