Skip to content

System Architecture

Web Engine Dev is a modular, composable web game engine ecosystem built with TypeScript. It follows a "Bring Your Own Engine" (BYOE) philosophy where every package can be used independently or combined with others. All packages are published under the @web-engine-dev scope.

Design Principles

The engine is built on five core architectural principles:

  • Modular and Composable -- Every package works standalone or together. No monolithic engine lock-in.
  • Data-Oriented Design -- All game state lives in the ECS World. Components are stored in contiguous typed arrays. Systems are pure functions that iterate data. See Data-Oriented Design for details.
  • WebGPU-First -- The rendering pipeline standardizes on WebGPU runtime execution. See ADR-003.
  • Type-Safe -- Full TypeScript strict mode with noUncheckedIndexedAccess, branded types, and discriminated unions to catch errors at compile time.
  • Production-Ready -- 80%+ test coverage enforced per package, with performance-critical paths designed for zero-allocation steady state.

Monorepo Structure

The repository is organized into packages (libraries) and apps (runnable applications):

@web-engine-dev/monorepo
├── packages/
│   ├── core/              # Foundation (math, ecs, events, time, scheduler, hierarchy,
│   │                      #   resources, change-detection, serialization, reflection,
│   │                      #   scripting, splines)
│   ├── systems/           # Runtime systems (input, audio, physics2d, physics3d,
│   │                      #   physics2d-rapier, physics3d-rapier, animation, spatial,
│   │                      #   netcode, netcode-ecs, netcode-server, matchmaking,
│   │                      #   character, gesture, cloth, ragdoll, destruction)
│   ├── rendering/         # Graphics (render-graph, renderer, shader-compiler, gltf,
│   │                      #   particles, sprites, text, terrain, tilemap, ui, vfx, gizmos)
│   ├── gameplay/          # Game logic (ai, ai-ml, pathfinding, procgen, state)
│   ├── infrastructure/    # Support (assets, asset-pipeline, scene, prefab, save,
│   │                      #   streaming, pooling, debug, hot-reload, build, storage,
│   │                      #   level, binary-compression, texture-compression,
│   │                      #   geometry-compression)
│   ├── meta/              # Utilities (signals, tween, timer, camera, replay, devtools,
│   │                      #   engine, benchmark, screenshot, timeline, design-tokens,
│   │                      #   editor-core, editor-ui, editor-viewport)
│   ├── player/            # Player features (i18n, achievements, telemetry, accessibility)
│   ├── platform/          # Platform services (analytics, social, monetization, ugc,
│   │                      #   moderation, discovery, sandbox, embed, publishing)
│   ├── runtime/           # Runtime targets (mobile, pwa, xr)
│   └── games/             # Reference games (space-shooter)
├── apps/
│   ├── playground/        # Interactive demo environment (Vite-based)
│   ├── editor/            # Web-based visual editor (React + dockview)
│   ├── editor-server/     # Editor backend server
│   ├── docs/              # Documentation site (VitePress)
│   ├── examples/          # Example applications
│   ├── benchmarks/        # Performance benchmarks
│   └── server/            # Game server
├── shared/                # Shared configs (tsup, vitest, tsconfig)
├── tools/                 # Developer tooling (MCP server, visual diff)
└── testing/               # Integration test infrastructure

Package Categories

CategoryPackage CountPurpose
Core12Foundation types, math, ECS, events, time, scheduling
Systems17Runtime systems for input, physics, audio, networking
Rendering12Graphics pipeline, shaders, particles, sprites, text, UI
Gameplay5AI, pathfinding, procedural generation, state machines
Infrastructure15Assets, scenes, prefabs, serialization, builds
Meta16Engine umbrella, editor, utilities, dev tools
Player4Accessibility, i18n, achievements, telemetry
Platform9Analytics, social, monetization, UGC, publishing
Runtime3Mobile, PWA, XR deployment targets

Dependency Layer Model

Packages are organized into strict layers numbered 0 through 9. A package may only depend on packages in lower layers. No upward imports. No circular dependencies.

Layer Details

LayerNamePackagesDescription
0FoundationmathZero dependencies. Pure computation. Vectors, matrices, quaternions, geometry primitives.
1Primitivesecs, events, time, scheduler, hierarchy, resources, change-detectionStandalone primitives usable without the ECS. Each provides a focused capability.
2Integrationserialization, reflection, splines, scriptingBuilds on Layer 1 for data processing, type introspection, and scripting lifecycle.
3System Primitivesinput, audio, spatialCore runtime systems. May use ECS or run standalone.
4Simulationphysics2d, physics3d, physics2d-rapier, physics3d-rapier, cloth, ragdoll, destructionPhysics and simulation. Collision detection via Rapier (ADR-005).
5Animationanimation, character, gestureAnimation systems and character controllers.
6Render Infrastructurerender-graph, shader-compilerRender graph DAG scheduling and WGSL-to-GLSL transpilation.
7Rendering Outputrenderer, gltf, particles, sprites, text, terrain, tilemap, ui, vfx, gizmosVisual output systems. The renderer is the core graphics package.
8Content & Editorscene, prefab, save, assets, netcode, netcode-ecs, netcode-server, ai, pathfinding, procgen, state, streaming, level, build, editor-core, editor-uiContent management, gameplay, networking, and editor infrastructure.
9UmbrellaengineRe-exports all packages as namespaced modules.

Layer Rules

  1. No upward imports -- A Layer N package may depend on Layer N-1 or below, never on its own layer or above.
  2. No circular dependencies -- Enforced by ESLint import/no-cycle and the dependency layer model.
  3. Interface segregation -- Systems with swappable backends (physics, audio) define interfaces at the abstract layer and implementations at the concrete layer (e.g., physics2d defines the interface, physics2d-rapier provides the Rapier implementation).

The Engine Umbrella Package

The @web-engine-dev/engine package at Layer 9 re-exports all modules as namespaces, providing a single import for the entire engine:

typescript
// Import namespaced modules
import { Math, ECS, Input, Audio, Renderer } from '@web-engine-dev/engine';

const vec = Math.vec3(1, 2, 3);
const world = new ECS.World();

For tree-shaking and fine-grained control over bundle size, import individual packages directly:

typescript
import { Vec3, Mat4 } from '@web-engine-dev/math';
import { World, Entity } from '@web-engine-dev/ecs';

The engine package also provides:

ExportDescription
Engine, createEngineCore engine class and factory
Presets (Game2DPreset, Game3DPreset, etc.)Pre-configured engine setups for common use cases
definePlugin, PluginRegistryPlugin lifecycle system
Built-in plugins (RenderPlugin, InputPlugin, etc.)Ready-to-use system plugins
registerAllComponentsScene component registration for serialization
DiagnosticRegistryRuntime diagnostic event system
createDebugBridgeMCP/DevTools integration for AI-assisted debugging
ConfigRegistryCVar-like configuration system
EngineSets, CoreScheduleSystem scheduling and registration

Engine Presets

Presets provide pre-configured engine setups for common game types:

typescript
import { createEngineFromPreset } from '@web-engine-dev/engine';

// Quick start with sensible defaults
const engine = await createEngineFromPreset('game3d', { canvas });

Available presets: minimal, game2d, game3d, highPerformance, turnBased, simulation, mobile, headless.

Build System and Toolchain

Build Pipeline

Every package builds through the same pipeline:

All packages publish as dual ESM + CJS (ADR-002) with TypeScript declarations:

json
{
  "type": "module",
  "main": "dist/index.cjs",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
      "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
    }
  }
}

TypeScript Configuration

SettingValueRationale
targetES2022Modern features (top-level await, private fields)
moduleESNextTree-shakeable output
moduleResolutionbundlerModern bundler support
stricttrueFull strict mode
noUncheckedIndexedAccesstrueArray/object index safety
verbatimModuleSyntaxtrueEnforce import type for type-only imports
isolatedModulestrueRequired for tsup/esbuild

Shared configuration is centralized in the shared/ directory:

  • shared/tsup.config.ts -- ESM + CJS, dts generation, ES2022 target, tree-shaking
  • shared/vitest.config.ts -- Node environment, v8 coverage, globals: false
  • shared/tsconfig.package.json -- Extends tsconfig.base.json, strict mode
  • shared/vitest-diagnostic-setup.ts -- Auto-catches invariant() violations in tests

Turborepo Orchestration

Key commands:

bash
pnpm build              # Build all packages (dependency-aware, cached)
pnpm build:affected     # Build only packages changed since origin/main
pnpm test               # Run all tests
pnpm test:affected      # Test only changed packages
pnpm typecheck          # Type-check all packages
pnpm lint               # Lint all packages

Code Quality Tools

ToolPurpose
ESLint 9 (flat config)Import ordering, consistent-type-imports, no-console restriction
PrettierConsistent formatting (single quotes, 2-space indent)
VitestUnit tests with v8 coverage provider
PlaywrightBrowser integration tests and visual regression
ChangesetsSemver versioning with automated changelog generation

Versioning

Packages are published under the @web-engine-dev/* scope using Changesets for version management:

  1. Run pnpm changeset to describe changes
  2. Select affected packages and semver bump type
  3. Changesets are committed and processed on release

Linked packages version together (e.g., physics2d + physics3d).

How Packages Compose

The engine's composability manifests at three levels:

1. Package-Level Composition

Import only what you need. Dependencies are pulled in automatically:

typescript
// Using just math -- zero other dependencies
import { Vec3, Mat4 } from '@web-engine-dev/math';

// Using ECS -- pulls in events, time, scheduler, resources, change-detection
import { World, defineComponent, defineSystem } from '@web-engine-dev/ecs';

// Using renderer -- pulls in render-graph, shader-compiler, math, ecs
import { createDevice, ForwardRenderer } from '@web-engine-dev/renderer';

2. Plugin-Level Composition

The engine plugin system lets you compose systems declaratively:

typescript
import { createEngine, RenderPlugin, InputPlugin, Physics3DPlugin } from '@web-engine-dev/engine';

const engine = await createEngine({
  canvas,
  plugins: [
    RenderPlugin({ enableSystems: true }),
    InputPlugin(),
    Physics3DPlugin(),
  ],
});

3. ECS-Level Composition

All systems coordinate through the ECS World. Components and systems from different packages interoperate seamlessly:

typescript
// Renderer components + Physics components + Custom components on the same entity
const entity = world.spawn(
  Transform3D.create({ position: [0, 1, 0] }),
  MeshComponent.create({ meshId }),
  RigidBody3D.create({ type: 'dynamic' }),
  Health.create({ value: 100 }),
);

Cross-Cutting Patterns

Hexagonal Architecture (Ports and Adapters)

Systems with swappable backends use the ports-and-adapters pattern. The core defines interfaces (ports); concrete implementations connect as adapters:

physics2d (interface) ───> physics2d-rapier (Rapier WASM adapter)
physics3d (interface) ───> physics3d-rapier (Rapier WASM adapter)
render-graph (abstraction) ───> WebGPU backend

This enables backend substitution without changing consumer code. See ADR-004 for the third-party integration strategy.

ECS as Central Coordinator

All game state lives in the ECS World (ADR-007). Systems from different packages communicate through:

  • Components -- Shared data attached to entities
  • Resources -- Global singletons accessible by any system
  • Events -- Double-buffered event queues for system-to-system communication
  • Observers -- Lifecycle triggers (OnAdd, OnRemove, OnChange) for reactive updates
  • Commands -- Deferred world mutations that prevent iterator invalidation

Plugin System

Plugins encapsulate system registration, resource initialization, and lifecycle management:

typescript
const MyPlugin = definePlugin({
  name: 'my-plugin',
  build(app) {
    // Register components, systems, resources
    app.addSystem(MySystem);
    app.insertResource(MyResource, initialValue);
  },
});

Further Reading

Proprietary software. All rights reserved.