Skip to content

Architecture Overview

This document is the map. It defines the layer diagram every other Architecture/ document assumes, states the one dependency rule that keeps the codebase from becoming ply.cider again, and lists the engine surface the rest of this folder relies on — verified, not assumed.

If you read one page before writing code, read this one.


1. The layers

graph TD
    ENGINE["S&box engine<br/>GameObject · Component · Scene · GameResource · Networking"]

    subgraph Core["Applejack.Core — depends on nothing but the engine"]
        LOG[Logging]
        CFG[Configuration]
        EVT[Event bus]
        SVC[Service registry]
        PER[Persistence]
        MOD[Module lifecycle]
    end

    subgraph Modules["Gameplay modules — depend on Core, and on each other only by declared dependency"]
        CHAR[Character]
        ITEMS[Items]
        INV[Inventory]
        INTER[Interaction]
        OWN[Ownership]
        ECON[Economy]
        JOBS[Jobs]
        NEEDS[Needs]
        CRIME[Crime]
        LAW[Law]
        CHAT[Chat]
        CMD[Commands]
        WORLD[World]
    end

    UI["UI — reads other modules' interfaces, owned by none of them"]

    ENGINE --> Core
    Core --> Modules
    Modules --> UI
    Core -.-> UI

    classDef core fill:#dbe9ff,stroke:#3366cc
    classDef mod fill:#e6ffed,stroke:#2c8a3d
    class LOG,CFG,EVT,SVC,PER,MOD core
    class CHAR,ITEMS,INV,INTER,OWN,ECON,JOBS,NEEDS,CRIME,LAW,CHAT,CMD,WORLD mod

Four layers, three rules:

  1. Engine is S&box itself. Nothing in this project wraps it — 00_CONSTITUTION.md §3 forbids a compatibility layer over GameObject, Component, Scene or GameResource.
  2. Core (Applejack.Core) owns the cross-cutting concerns every module needs — logging, configuration, the event bus, the service registry, persistence, and the module lifecycle itself. Core depends on the engine and nothing else in this project.
  3. Modules are gameplay: Character, Items, Inventory, Interaction, Ownership, Economy, Jobs, Needs, Crime, Law, Chat, Commands, World. Each depends on Core and on whichever other modules it explicitly declares — never sideways through a shared table, never downward into another module's concrete types. The full dependency table, and the reasoning for where each boundary was cut, is in Legacy/13_DEPENDENCY_GRAPH.md §4.
  4. UI sits on top, reads modules through their public interfaces and events, and is owned by none of them. See 11_UI_ARCHITECTURE.md.

2. The dependency rule

Every piece of state has exactly one owning module. Everyone else asks. (Legacy/13_DEPENDENCY_GRAPH.md §5)

Concretely, enforced at review and largely at compile time (ADR-0002):

  • A module's concrete types are internal. Other modules see only what it registers as a service interface, publishes as an event, or exposes as a GameResource.
  • The module dependency graph is a DAG. Crime provides Arrest; Law decides who may call it; Crime does not know Law exists. If a new feature seems to need a cycle, the shared concept belongs in Core, or the communication should be an event.
  • Nothing resolves a dependency by touching a global at file scope. See D-08 for what that cost the original.

3. Module lifecycle

Every module is one type deriving from GameModule, declaring its dependencies, resolved in topological order at boot:

Configure → RegisterServices → Initialize → Ready → Shutdown

A missing dependency or a cycle is a hard startup failure naming the modules involved — never a silent misordering. Full treatment, including the GameModule contract and what belongs in each lifecycle stage, is in 02_MODULE_MODEL.md.

4. Engine baseline

All documentation in this folder is written against S&box as of July 2026 and cites the specific engine feature it relies on. This table is the index; each Architecture/ document goes into the detail relevant to it.

Concern Engine feature Verified against Used in
Component lifecycle OnAwakeOnStartOnUpdate / OnFixedUpdateOnDestroy, plus OnEnabled / OnDisabled Component Interfaces, Sandbox.Component Every module
Making an object networked GameObject.NetworkSpawn() Networking & Multiplayer 07_NETWORKING.md
State replication [Sync], [Sync(SyncFlags.FromHost)] Sync Properties, Networked Objects 07_NETWORKING.md
Remote calls [Rpc.Broadcast], [Rpc.Host], [Rpc.Owner] Networking & Multiplayer, above 07_NETWORKING.md
Data-driven content GameResource, [GameResource], [Property] Custom Assets 09_ITEM_FRAMEWORK.md
Local persistence FileSystem.Data.WriteJson / ReadJson File System 06_PERSISTENCE.md
Remote persistence Http.RequestAsync HTTP Requests 06_PERSISTENCE.md
Reusable/shared code Libraries Libraries ADR-0002
Automated tests UnitTests/ at project root, auto-detected, generates an MSTest project Standards/03_TESTING_STANDARDS.md §2 Every module

Two engine facts shape the architecture broadly enough to state here rather than repeat in every document:

  • Serialisation is get/set properties only. GameResource and the save envelope both serialise properties with both a getter and a setter; a public field is silently skipped. Every persisted or inspector-exposed type is written as properties, never fields.
  • [Sync] is permissive by default; [Sync(SyncFlags.FromHost)] is the safe one. The shorter attribute name is the one that lets the owning client write. Anything a player could profit from forging is FromHost, always — see ADR-0005.

If an engine API changes in a way that invalidates a fact on this page, that is a documentation bug — the whole point of pinning a baseline date is so a drift is visible rather than silently wrong.

5. How to read the rest of this folder

Document Answers
01_PROJECT_LAYOUT.md Where does a new file go?
02_MODULE_MODEL.md What is a module, exactly, and how does it boot?
03_SERVICE_REGISTRY.md How does one module call another?
04_EVENT_BUS.md How does one module observe another without depending on it?
05_CONFIGURATION.md How does an operator change a number without recompiling?
06_PERSISTENCE.md How is anything saved, and how does it survive a schema change?
07_NETWORKING.md What may a client tell the host, and who is trusted?
08_DATA_MODEL.md What is a Character, an Item, a Container?
09_ITEM_FRAMEWORK.md How is an item defined, and how does it get a behaviour?
10_INTERACTION_FRAMEWORK.md How does a timed player action work, generically?
11_UI_ARCHITECTURE.md How does a panel get built and registered?
12_LOGGING_AND_DIAGNOSTICS.md How does an operator find out what happened?
13_ERROR_HANDLING.md Exception or result? When does the game refuse to boot?
14_NEEDS_FRAMEWORK.md What need decays over time, and how fast?
15_COMMANDS_FRAMEWORK.md How does a chat-typed player action get registered and gated?
16_JOBS_FRAMEWORK.md What is the group→gang→team hierarchy, and how does a character join a job?
17_CRIME_FRAMEWORK.md How does a warrant get issued, and how does an arrest work?
18_LAW_FRAMEWORK.md What is the ten-laws noticeboard, and why is it deliberately inert?
19_CHAT_FRAMEWORK.md How does a chat message get routed to its recipients?
20_WORLD_LOCKS_AND_CONTAINERS.md How do locks, seals, and a second container type work?
21_EXTENSION_SURFACE.md What counts as an extension interface, and has it been stable long enough to revisit ADR-0002?
22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md How does an admin see, move, and moderate connected players?
23_UI_DESIGN_SYSTEM.md What visual language and component patterns should every panel share?
24_PLAYER_MENU.md How does the player-facing tabbed menu get built and registered?

Each document carries real C# signatures, not policy statements — see 00_CONSTITUTION.md §1 for why that distinction matters. Where a document's design departs from legacy behaviour, it cites the ADR or defect that justifies the departure.