Architecture
DINOForge is structured as a three-product architecture with clear layering boundaries.
System Overview
Runtime Execution Flow
The runtime bootstrap follows a precise sequence to initialize the mod platform:
Three Products
Product A — Runtime / Hook Layer
The lowest level. Most brittle, fewest agents should touch.
- Boots into DINO via BepInEx + modified Doorstop pre-loader
- Locates ECS systems, entities, components, assets
- Exposes safe patch points
- Handles version checks and rollback/fallback
- Provides debug surfaces (entity dumper, system enumerator, debug overlay)
Output: BepInEx/ecs_plugins/DINOForge.Runtime.dll
Product B — Mod API / Domain SDK
The real scaffold. Where trivial modding becomes possible.
- High-level mod definition interfaces
- Hides engine internals behind stable abstractions
- Provides schemas, registries, validators, pack loaders
- Supports multiple mod classes (content, balance, ruleset, total conversion, utility)
Key components: Pack manifest model, YAML loader (YamlDotNet), dependency resolver, schema validation (JsonSchema.Net)
Product C — Mod Packs / Content Packs
Where actual mods live. Mostly declarative and content-driven.
- Warfare packs (Modern, Star Wars)
- Balance packs, economy packs
- QoL/UI packs, debug packs
- Each pack has explicit metadata: id, version, dependencies, conflicts
Content Layering Model
DINOForge applies a 5-layer content model:
| Layer | Description | Frequency |
|---|---|---|
| Content Packs | YAML/JSON data — factions, units, stats, waves, localization | Most mods |
| Asset Packs | Bundled art/audio/prefab with manifests | Visual/audio mods |
| Code Plugins | C# plugin API through SDK interfaces | Advanced mods |
| Patch Layer | Controlled Harmony patches (marked unsafe) | Rare |
| Tooling | CLI tools, validators, inspectors | Development |
Design Principles
- Wrap, don't handroll — Use established libraries, wrap them thinly
- Framework before content — Platform first, themed mods second
- Declarative before imperative — YAML manifests over C# patches
- Stable abstraction over unstable internals — Isolate ECS glue
- Agent-first repo design — Optimize for autonomous agent development
- Observability is first-class — Logs, overlays, reports, validators
- Domain extensibility — Warfare is first plugin, not the only one
- Compatibility-aware packaging — Explicit deps, conflicts, versions
- Graceful degradation — Fail loudly with fallbacks
Repository Layout
DINOForge/
src/
Runtime/ # Product A — BepInEx plugin
Bridge/ # ECS bridge (component mapping, stat modifiers, queries)
HotReload/ # Hot reload bridge
UI/ # Mod menu overlay (F10), settings panel
SDK/ # Product B — Public mod API
Assets/ # Asset service, addressables catalog
Dependencies/ # Dependency resolver
HotReload/ # Pack file watcher
Models/ # Content data models
Registry/ # Generic registry with conflict detection
Universe/ # Universe Bible system
Validation/ # Schema validation (NJsonSchema)
Bridge/
Protocol/ # JSON-RPC types, IGameBridge interface
Client/ # Out-of-process game client
Domains/
Warfare/ # Warfare domain (factions, doctrines, combat)
Economy/ # Economy domain (rates, trade, balance)
Scenario/ # Scenario domain (scripting, conditions)
UI/ # UI domain (HUD injection, menus)
Tools/
Cli/ # dinoforge CLI (11 commands)
McpServer/ # MCP server for Claude Code (17 tools)
PackCompiler/ # Pack compiler (validate, build)
DumpTools/ # Dump analysis (Spectre.Console)
Installer/ # BepInEx + DINOForge installer
Tests/ # Unit tests (xUnit + FluentAssertions)
Integration/ # Integration tests
packs/ # Product C — Content packs (6 example packs)
schemas/ # Canonical schema definitions (17 schemas)
docs/ # This documentation site
manifests/ # Ownership map, extension pointsThe Two-Boot Cycle
A critical runtime pattern ensures the mod platform survives scene reloads and maintains persistent state.
Why It Exists
DINO's game flow causes the Doorstop pre-loader to initialize twice per playthrough:
- Boot 1: Game launcher → load BepInEx → load Runtime plugin
- Intermediate: Scene loads, ECS world initializes
- Boot 2: Scene transition (or new game → continue) → Doorstop re-runs → must NOT double-initialize
Mechanism: HideAndDontSave + DontDestroyOnLoad
The root GameObject persists across scene reloads via:
- HideAndDontSave flag — Mark runtime root as not player-saveable
- DontDestroyOnLoad marker — Persist from Boot 1 → Boot 2
- RuntimeDriver.OnDestroy resurrection — If root destroyed by accident, create new one from marker
- ModPlatform singleton pattern — Only one instance ever exists; subsequent boots detect via static reference
Why Harmony Patches Failed
Earlier versions used Harmony patches to manipulate the lifecycle. This backfired because:
- LazyPatch intercepted object creation with custom logic
- DeltaTimeResurrectionPatch fought with the natural DontDestroyOnLoad flow
- Patch state leaked across scene boundaries
- Framework beat the patches; patches beat DontDestroyOnLoad
Resolution (commit df3b55e): Removed all patches, trusted the native HideAndDontSave + DontDestroyOnLoad mechanism.
Reference Models
DINOForge draws from the best modding ecosystems:
| System | What We Take |
|---|---|
| Factorio | API shape, manifests, dependency/version handling |
| RimWorld | Declarative content + imperative code escape hatch |
| Satisfactory/BepInEx | Mod loaders, plugin bootstrap |
| Minecraft Bedrock | Pack schemas, folder conventions |
| UEFN/Roblox | End-to-end creation pipeline concept |
Products
Product A: Desktop Companion (WinUI 3)
A standalone Windows desktop GUI that mirrors the in-game F9/F10 overlays. Allows pack configuration and debugging without launching the game.
- UI Framework: WinUI 3 + Mica background
- Shell: NavigationView with pack list and debug panel tabs
- State Parity: Reads/writes
disabled_packs.jsonshared with game runtime - Use Case: Configure packs, test pack compatibility, debug issues before launching game
- Status: In Progress (M9)
See ADR-011 for detailed design.