Architecture Note

# Future - Shared Contract

The safest long-term split is to make a common core own the portable game domain, while Worldshaper owns authoring workflows and World-Workshop owns runtime execution, ECS, rendering, and simulation wiring.

Decision rule: if a module can run unchanged in tests, browser, editor, and runtime without depending on `React`, `Pixi`, `Tauri`, or `bitECS`, it is a strong candidate for the shared core.

## Recommended Ownership Split

### Shared Core

Owns the portable contract, validation, normalization, and pure game-domain logic.

- Authored content types
- Stable IDs and references
- Schema versions and migrations
- Reference resolution
- Pure gameplay formulas and rule evaluation
- Prefab and archetype definitions

### Runtime

Owns the executable simulation and all performance-driven or transient state.

- `bitECS` components and entity storage
- Spawn and assembly from content to ECS
- Systems, ticking, AI, pathfinding integration
- Rendering, audio, input, camera
- Runtime caches and live state

### Editor

Owns source editing UX, layout, tools, and authoring-specific productivity features.

- Inspectors, forms, palettes, map editing
- TS source rewriting and formatting
- Undo/redo, selection, layout preferences
- Authoring warnings and quick fixes
- Studio-only metadata and workflows

## What Belongs In The Shared Core

Area | Safe To Share | Why
--- | --- | ---
Content definitions | `World`, `Chunk`, `NpcTemplate`, `PlacedEntity`, `Ability`, `Dialogue`, `Item` | These are authored domain concepts, not renderer or ECS details.
Type helpers | `defineNpc()`, `defineWorld()`, `definePrefab()` | Both editor and runtime benefit from a single shape and single set of defaults.
IDs and refs | `NpcId`, `WorldId`, `DialogueId`, `SpriteId`, `TemplateId` | Stable identifiers should mean the same thing everywhere.
Validation | Reference checks, required fields, defaulting, normalization, semantic validation | Content should be judged by one source of truth, not two slightly different validators.
Migrations | `schemaVersion` upgrades and canonical output transforms | This keeps old content loadable in both tools.
Pure rules | Dialogue condition checks, loot rolls, stat formulas, cooldown math, faction logic | These are deterministic rules that do not need the runtime shell.
Content registry | Lookup and resolution APIs over loaded definitions | Both projects need the same view of the authored world.

    Shared core example: portable authored contractexport interface PlacedEntity {
      id: EntityInstanceId;
      templateId?: EntityTemplateId;
      archetype: ArchetypeId;
      position: { x: number; y: number; z: number };
      spriteId?: SpriteId;
      dialogueId?: DialogueId;
      factionId?: FactionId;
      tags?: string[];
      overrides?: Partial<EntityTemplate>;
    }

    export function definePlacedEntity(
      value: PlacedEntity,
    ): PlacedEntity {
      return normalizePlacedEntity(value);
    }

## What Should Stay Runtime-Owned

The runtime should keep everything that is shaped primarily for execution speed, frame-to-frame state, ECS storage layout, or engine integration.

### Keep These In World-Workshop

- `bitECS` component arrays and entity IDs
- ECS world creation and system scheduling
- Assembly from authored entities into spawned ECS entities
- Transient state like routes, targets, planner handles, and sprite instances
- Rendering, audio, input, camera, spatial index, pathfinder integration

### Why

- These are not stable authoring concepts.
- They will change when the engine changes.
- They often depend on engine libraries and performance tradeoffs.
- They should be free to evolve without forcing content migrations.

    Runtime example: ECS remains runtime-onlyconst GridPosition = {
      x: new Int16Array(MAX_ENTITIES),
      y: new Int16Array(MAX_ENTITIES),
      z: new Int16Array(MAX_ENTITIES),
    };

    const Target = {
      x: new Int16Array(MAX_ENTITIES),
      y: new Int16Array(MAX_ENTITIES),
      z: new Int16Array(MAX_ENTITIES),
      active: new Uint8Array(MAX_ENTITIES),
    };

    function spawnFriendlyNpc(world: GameWorld, def: ResolvedEntityDef): number {
      const entity = addEntity(world);
      addComponent(world, entity, GridPosition);
      addComponent(world, entity, Target);
      addComponent(world, entity, Agent);
      // Runtime decides the exact ECS shape here.
      return entity;
    }

## What Should Stay Editor-Owned

The editor should keep everything that exists to improve authoring speed, visibility, and safety, especially if it is tied to UI behavior or source editing mechanics.

- Inspectors, map tools, palettes, graphs, and editor windows
- Selection state, undo/redo, dirty tracking, docking, recent files
- TS source rewriting, formatting, and file-structure conventions
- Inline diagnostics, authoring warnings, quick fixes, bulk-edit tools
- Editor-only metadata and local preferences

Important: do not move UI state or source-file IO details into the shared core. Those will create coupling fast and usually provide very little reuse value.

## The Boundary To Protect

The shared core should describe what the authored thing is. The runtime should decide how that thing becomes executable ECS state.

### Good Core Concepts

- `EntityTemplate`
- `PlacedEntity`
- `ArchetypeId`
- `BehaviorId`
- `StatBlock`
- `SpawnRule`
- `VisualRef`

### Bad Core Concepts

- `GridPosition` typed arrays
- `RenderPosition`
- `Target.active`
- Planner handles
- Sprite instances
- Route caches
- Per-frame live ECS state

    The bridge: authored data into runtime assembly// Shared core
    export interface EntityTemplate {
      id: EntityTemplateId;
      archetype: ArchetypeId;
      spriteId?: SpriteId;
      stats?: StatBlock;
      dialogueId?: DialogueId;
    }

    // Runtime
    export function materializeEntity(
      world: GameWorld,
      entity: ResolvedPlacedEntity,
    ): number {
      switch (entity.archetype) {
        case "friendly_npc":
          return spawnFriendlyNpc(world, entity);
        case "monster":
          return spawnMonster(world, entity);
        case "player_spawn":
          return spawnPlayerMarker(world, entity);
      }
    }

## Tempting, But Usually Not Worth Sharing

Raw file IO: how a TS file is discovered, parsed, rewritten, and formatted should usually stay editor-side or build-side.
React components: UI does not become more reusable just because it sits in a shared package.
Pixi renderer code: renderer concerns are runtime concerns.
ECS storage: component arrays and world memory layout are engine implementation details.
Editor state: selections, panels, and tool modes are not game-domain concepts.
Live save-state internals: runtime persistence format may overlap with content, but live simulation state should stay runtime-owned.

## Suggested Package Shape

    A practical next-step layoutpackages/
      world-core/
        src/
          ids/
          content/
          define/
          validate/
          normalize/
          migrate/
          resolve/
          rules/
          registry/

      world-content-build/
        src/
          loadTsModules/
          compileBundle/
          emitArtifacts/

    Worldshaper/
      src/
        editor-ui/
        source-editing/
        tooling/

    World-Workshop/
      src/
        game/
          ecs/
          systems/
          runtime/
          rendering/
          assembly/

## Low-Risk Extraction Order

1. Extract IDs, record types, and `defineX()` helpers.
2. Extract validation, normalization, and migrations.
3. Extract reference resolution and a shared content registry.
4. Extract pure rules used by both projects.
5. Leave ECS assembly, rendering, and editor source-editing mechanics where they are.

Why this order works: it gives immediate reuse and consistency without forcing an early, risky merge of the two apps' most specialized code.