Project Layout¶
Where a new file goes, and why. This document exists so that layout is a decision made once, not re-litigated per pull request.
1. Top level¶
AppleJackRP's&box/
├── Applejack.sbproj S&box project descriptor. Declares the addon, its type (game),
│ its ident, and its asset/code roots. Generated by the editor
│ on project creation; hand-edited only for metadata.
├── Assets/ GameResource assets: items, jobs, recipes, prefabs. No code.
├── Code/ All C# source, one folder per module.
├── UnitTests/ Engine-agnostic tests. Auto-detected by the S&box editor,
│ which generates the MSTest project — see
│ Standards/03_TESTING_STANDARDS.md §2.
├── .editorconfig Authoritative formatting. Warnings are errors.
├── README.md The project's front door, for someone who has never seen it.
├── CLAUDE.md Binding instructions for AI-assisted sessions. A pointer to
│ Standards/, never a duplicate of it.
├── tasks.md In progress / Backlog / Done. The working task list.
├── handoverdaily.md Dated session log, newest first. Append-only.
└── Documentation/ This tree.
tasks.md and handoverdaily.md are process files, not specification — they record what
happened and what is next. Where either disagrees with Documentation/, Documentation/
wins and the process file is stale.
legacy/ and Applejack_Framework_Docs/ are not part of the shipped project — the former is
gitignored content plus the two reference codebases described in
Legacy/00_OVERVIEW.md §3; the latter no longer
exists, having been superseded in full by this tree.
2. Code/¶
One folder per module, matching ADR-0002. Modules marked planned have no folder yet — they arrive with ROADMAP.md's Milestone 7:
Code/
├── Core/ Subdivided by concern -- it is the only module with enough
│ ├── README.md distinct concerns to need it.
│ ├── Logging/
│ ├── Configuration/
│ ├── Events/
│ ├── Services/
│ ├── Persistence/
│ ├── Modules/ GameModule base type, lifecycle runner, dependency resolver
│ └── Plugins/ Plugin/PluginManifest/IPluginCatalog, PluginDiscovery,
│ PluginRunner -- the framework's OWN plugin infrastructure. See
│ Documentation/Architecture/23_PLUGIN_SYSTEM.md.
├── Plugins/ Third-party (in-repo, Stage 1) plugins live here, one folder
│ └── RicherEconomy/ each -- distinct from Core/Plugins/ above, which is the loader
│ itself, not a plugin. RicherEconomy is the reference plugin;
│ see its own README.md.
├── Character/
│ ├── README.md
│ ├── Character.cs Flat: the data model, the service contract, the store, and
│ ├── CharacterStore.cs the permission rules all sit at the module root.
│ ├── PermissionSet.cs
│ └── CharacterNetworkComponent.cs
├── Items/
├── Inventory/
│ └── Ui/ Razor panels -- the one sub-folder a gameplay module takes.
├── Interaction/
├── Ownership/
├── Economy/
│ └── Ui/
├── World/
├── Ui/ The HUD registry itself, plus HudRoot.razor.
├── Bulletin/
│ └── Ui/
├── Jobs/ planned
├── Needs/ planned
├── Crime/ planned
├── Law/ planned
├── Chat/ planned
└── Commands/ planned
Rules, unconditional:
- A module folder gets a
README.mdbefore its first line of code, using the template in Standards/02_DOCUMENTATION_STANDARDS.md §2. A folder without one is not a module yet, it is a stub. - The namespace matches the folder path.
Code/Inventories/Ui/isApplejack.Inventories.Ui. See Standards/01_NAMING.md §8. - One type per file, filename matching the type.
InventoryStore.cscontains exactlyInventoryStore. - A gameplay module is flat by default. Types sit at the module root; the only sub-folder
a gameplay module currently takes is
Ui/. Add a sub-folder when a module's root becomes genuinely hard to scan, not pre-emptively —Core/earned its six, and no gameplay module has yet earned one. - Sub-folders, when they appear, are by concern, not by C# construct. There is no
Interfaces/orModels/folder — an interface lives beside the concern it belongs to, not sorted by language feature.
The engine-agnostic boundary¶
Standards/03_TESTING_STANDARDS.md §1
requires a testable core with no GameObject, Component, Scene, or Connection
reference. That boundary is drawn by type, not by folder. Within a module:
| Kind of type | Convention | Engine-bound? |
|---|---|---|
| Data model, service contract, store, rules | Plain type at the module root | No — this is what UnitTests/ compiles against |
| Replication and RPC surface | Named *Component.cs, derives Component |
Yes |
| Panels | Under Ui/, .razor |
Yes |
| Module registration | Named *Module.cs, derives GameModule |
No, but untested — it is wiring |
An earlier version of this document specified a
Rules/sub-folder per module for the engine-agnostic core, withComponents/andPersistence/beside it. Implementation through Milestone 6 did not do that — the split fell naturally along the*Componentnaming convention instead, and adding three sub-folders to hold two or three files each would have obscured more than it organised. This section documents what was built. The constraint it enforces is unchanged and is the point:CharacterStoremust stay free of engine types whether it lives inRules/or not.
Ui/ is listed as a module folder because it needs the same structure and README discipline,
but it is architecturally distinct — see
11_UI_ARCHITECTURE.md. It owns no gameplay state and reads
every other module only through public interfaces and events.
3. Assets/¶
Assets/
├── Items/ 19 definitions as of Milestone 5, including the
│ ├── README.md base_food / base_container / base_weapon inheritance roots.
│ ├── Food/
│ │ └── cake.item
│ ├── Misc/
│ ├── Packaging/
│ └── Weapons/
├── Jobs/ planned -- Milestone 7
│ └── police_officer.job
├── Recipes/ planned
└── Prefabs/ planned
└── WorldItem.prefab
Mirrors the content categories, not the module structure — Assets/Items/Food/ is a content
grouping for authors, unrelated to Code/Items/. Asset filenames are snake_case, matching
legacy item IDs deliberately, per
Standards/01_NAMING.md §10. Prefabs are
PascalCase.prefab.
No model, material or sound assets are checked in. Legacy/00_OVERVIEW.md
§3 explains why legacy/content/ cannot ship;
this project's own art direction and asset sourcing is out of scope for Documentation/ and
tracked separately once art production starts.
4. UnitTests/¶
Not hand-created. Per
Standards/03_TESTING_STANDARDS.md §2, adding
a UnitTests/ directory at the project root and restarting the editor causes S&box to
generate the MSTest project. Structure inside it mirrors Code/:
UnitTests/
├── Character/
│ ├── CharacterStoreTests.cs
│ └── PermissionSetTests.cs
├── Core/
│ ├── Events/
│ ├── Modules/
│ ├── Persistence/
│ │ └── Fixtures/ Test doubles: InMemoryPersistenceBackend, ...
│ └── Services/
├── Interaction/
├── Inventory/
├── Items/
│ └── Fixtures/
└── ...
Test files are <TypeUnderTest>Tests.cs, per
Standards/01_NAMING.md §10. A Fixtures/
sub-folder holds test doubles, which are not themselves tests and must not be named as
though they were. Only engine-agnostic types are exercised here — anything needing a Scene
is integration or multiplayer testing, covered manually per
Standards/03_TESTING_STANDARDS.md §5
until automated. In practice this means no *Component type has a unit test, and that is
expected rather than a coverage gap to close.
5. Where does X go? — a worked checklist¶
| You are adding… | Goes in | Because |
|---|---|---|
| A new gameplay rule (arithmetic, resolution, validation) | Code/<Module>/ root, a plain type |
Must run under dotnet test with no engine |
A Component that holds runtime state |
Code/<Module>/<Name>Component.cs |
Thin — calls into the module's plain types, replicates the result |
| A service interface another module will call | Code/<Module>/ (root of the module) |
Public surface, not a sub-concern |
| A typed event | Code/<Module>/ alongside the type that publishes it |
Discoverable from the publisher |
| A new item, job, or recipe | Assets/ |
Data, not code — ADR-0004 |
| A Razor panel | Code/Ui/ or Code/<Module>/Ui/ |
See 11_UI_ARCHITECTURE.md |
| A cross-cutting concern (a new kind of log sink, a new config source) | Code/Core/ |
Not gameplay |
| A behaviour composable onto multiple item types | Code/Items/, deriving ItemBehaviour |
See 09_ITEM_FRAMEWORK.md |
If nothing on this list fits, that is a signal the change needs a module boundary decision before it needs a file path — raise it per 00_CONSTITUTION.md §7 rather than guessing.