register_id: DOC-A-ARCHITECTURE category: A tier: 1 lifecycle: LOCKED owner: Crystalka version: "0.4" next_review_due: 2027-05-12 register_view_url: docs/governance/REGISTER_RENDER.md#DOC-A-ARCHITECTURE
The project is built around one rigid principle: a system MUST NOT access another system's data directly. Interaction happens only through contracts. Any deviation is caught by the isolation guard and produces an immediate crash with diagnostics, not silent state corruption.
- v0.3 (2026-04): Phase 4 architectural debt closed.
[Deferred]/[Immediate]delivery is implemented inDomainEventBus(per-bus queue, drain between phases, subscriberSystemExecutionContextcapture); a sixth domain bus,IPowerBus, is added forElectricGridSystem+ConverterSystem; the ElectricGrid↔Converter component cycle is broken via[Deferred] ConverterPowerOutputEvent;ItemAddedEvent/ItemRemovedEvent/ItemReservedEventare marked[Deferred]—StorageComponentmutation runs in theInventorySystemcontext, preservingHaulSystem.writes=[]isolation;BridgeImplementationAttribute(Phase = N)is introduced and applied to every system with a stubOnInitialize. - v0.2 (2026-04): Added Lease models (RESOURCE_MODELS, EVENT_BUS), two-phase commit for multi-bus requests (COMPOSITE_REQUESTS), feedback-loop resolution through tick lag (FEEDBACK_LOOPS), deterministic damage resolution (COMBO_RESOLUTION), golem ownership states (OWNERSHIP_TRANSITION), and the bridge pattern between Phases 5 and 6 (ROADMAP).
- v0.1 (2026-03): Initial scaffolding: four layers, five domain buses, declarative isolation, parallel scheduler.
RimWorld is the popular starting point for colony simulators, but its architecture carries three chronic problems, and Dual Frontier fixes each of them.
RimWorld's combat system reaches into storage itself to check for ammo. A hundred pawns, sixty ticks per second, an O(n) item walk — the result is tens of thousands of scans per second even on a small colony. Dual Frontier replaces direct scans with a domain bus and invalidation caches: the combat system publishes an AmmoIntent, the storage system answers from cache in O(1), and a batch of a hundred intents is processed in one pass.
RimWorld is single-threaded: every system runs in sequence, even when no conflicts exist between them. Dual Frontier forces every system to declare its readable and writable components through [SystemAccess]. The scheduler builds the dependency graph once at startup, topologically sorts it into phases, and runs unrelated systems in parallel — on 8 cores, phases run up to 6–7 threads simultaneously.
In RimWorld a mod patches any private method via Harmony and breaks it. Dual Frontier loads every mod into its own AssemblyLoadContext: the mod physically cannot see DualFrontier.Core, has no reference to World or to any concrete system. Mods interact with each other through IModContract — a public-API declaration — not through reflection.
The architecture is split into four layers. Each layer knows only the layers below it.
┌─────────────────────────────────────────────────────┐
│ PRESENTATION │
│ Vulkan substrate, sprite render, UI, Input │
│ Main thread only. Visuals only. │
├─────────────────────────────────────────────────────┤
│ APPLICATION │
│ GameLoop, SaveSystem, ScenarioManager │
│ Command queue Domain → Presentation │
├─────────────────────────────────────────────────────┤
│ DOMAIN │
│ Systems, Entities, Components, Contracts │
│ Multithreaded. Renderer-agnostic. │
├─────────────────────────────────────────────────────┤
│ INFRASTRUCTURE │
│ EventBus (domain buses), Pathfinding, │
│ SpatialGrid, ParallelScheduler │
└─────────────────────────────────────────────────────┘
The DualFrontier.Launcher assembly (Vulkan substrate via DualFrontier.Runtime). Implements the IRenderer contract from Application. Reserved DevKit-tier extension IDevKitRenderer remains dormant per К-extensions cascade #2 (2026-05-23) — reserved для future first-party DevKit work над Vulkan substrate. Works only in the renderer main thread. Does not call Domain directly — reads commands from PresentationBridge and dispatches via RenderCommandDispatcher. Current authority: VULKAN_SUBSTRATE. Historical (superseded): VISUAL_ENGINE, GODOT_INTEGRATION — pre-V-substrate dual-backend Godot DevKit + Silk.NET Native state, retired в К-extensions cascade #2.
The DualFrontier.Application assembly. A glue layer: the main game loop (GameLoop), the save system, the scenario loader, and ModLoader. Contains PresentationBridge — a unidirectional command queue from Domain to Presentation.
The DualFrontier.Systems, DualFrontier.Components, DualFrontier.Events, and DualFrontier.AI assemblies. All game rules. Multithreaded: systems execute in parallel. Never imports renderer-specific code (Vulkan, Win32, et al.) — renderer-agnostic per layer contract.
The DualFrontier.Core assembly. ECS infrastructure: World, ComponentStore, DomainEventBus, ParallelSystemScheduler, DependencyGraph, SpatialGrid. Everything is internal — only contracts are visible from outside. DualFrontier.Systems is granted access via InternalsVisibleTo.
The dependency-arrow direction is strictly top-to-bottom. A violation is an architectural-review error.
Contractsdepends on nothing exceptSystem.*.ComponentsandEventsdepend only onContracts.Coredepends onContracts.Systemsdepends onContracts,Components,Events, andCorethroughInternalsVisibleTo.AIdepends onContractsandComponents.Applicationdepends onCoreandSystems.Launcherdepends onApplicationand on the Vulkan substrate (VULKAN_SUBSTRATE —vulkan-1.dllvia pure P/Invoke + Win32 P/Invoke). К-extensions cascade #2 (2026-05-23) retired the historical Godot DevKit + Silk.NET + OpenGL dual-backend;DualFrontier.Launcheris the single production renderer.- Mods depend only on
Contracts. A reference toCorefrom a mod is blocked byAssemblyLoadContext.
SpellSystem publishes a ManaIntent to IMagicBus. ManaSystem collects all intents in the next phase, checks the reserve via ManaComponent, and answers ManaGranted or ManaRefused. Between the two steps, the scheduler runs WeatherSystem and NeedsSystem in parallel. This is exactly what is impossible in single-threaded RimWorld.
The VoidMagic mod loads into its own AssemblyLoadContext. It registers the VoidAffinityComponent component through IModApi, registers VoidSpellSystem with the [SystemAccess(reads: [VoidAffinityComponent])] declaration, and publishes the IVoidMagicContract contract. Another mod discovers the contract via api.TryGetContract<IVoidMagicContract> and builds its own logic on top — when VoidMagic is unloaded, the other mod simply does not subscribe, no crashes.
DamageSystem drops HealthComponent.Current to zero and publishes a DeathEvent (marked [Deferred]) to IPawnBus. In the next phase, MoodSystem reacts to the death, SocialSystem adjusts relationships, and Application places a PawnDiedCommand on the PresentationBridge queue — Launcher picks up the command in its per-frame iteration and dispatches via RenderCommandDispatcher (К-extensions cascade #2 2026-05-23 architecture). Domain and Presentation never meet.
┌────────────────────────────┐
│ DualFrontier.Contracts │
│ (interfaces, attributes) │
└──────────────┬─────────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────────┐ ┌────────────────────┐
│ Components │ │ Events │ │ Core │
│ (POCO only) │ │ (records only) │ │ (ECS internal) │
└────────┬────────┘ └──────────┬──────────┘ └──────────┬─────────┘
│ │ │
│ │ │ InternalsVisibleTo
▼ ▼ ▼
┌────────────────────────────────────────────────────────────┐
│ DualFrontier.Systems │
│ Combat / Magic / Pawn / Inventory / Power / World / ... │
└──────────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────┐
│ DualFrontier.Application │
│ GameLoop / Save / Mods │
└──────────────┬─────────────┘
│
▼
┌────────────────────────────┐
│ DualFrontier.Launcher │
│ Vulkan substrate / Renderer│
└────────────────────────────┘
┌──────────────────────┐
│ Mods/AnyMod.dll │ ──► depends ONLY on Contracts
└──────────────────────┘