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
- Extract IDs, record types, and
defineX() helpers.
- Extract validation, normalization, and migrations.
- Extract reference resolution and a shared content registry.
- Extract pure rules used by both projects.
- 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.