Project Constitution¶
This document governs how Applejack is built. It is short on purpose. Everything in it is binding; everything else is a recommendation.
1. The prime directive¶
The finished project should feel like Applejack and should not resemble Applejack internally.
The original is a gameplay specification and a historical record. It is never an implementation reference. Concretely:
- ✅ "The original charged $150 for a door and refunded half on sale." → preserve.
- ✅ "The original let a pocket item increase your carry capacity." → preserve, that is good design.
- ❌ "The original stored inventory as a flat name→count map." → that is an implementation detail, and a poor one. Redesign it. (ADR-0006)
- ❌ "The original monkey-patched the global hook dispatcher." → never reproduce.
When in doubt, ask: is this a decision a player can perceive? If yes it is gameplay and is probably worth preserving. If no it is implementation and we owe it nothing.
2. Priority order¶
When two goals conflict, the higher one wins. This ordering is not negotiable per-feature.
- Stable architecture
- Modular systems
- Data-driven design
- Automated testing
- Gameplay parity
- Performance
- Polish
Note that gameplay parity sits below testability. A feature that cannot be tested is not shipped, even if the original had it. Note also that performance sits below testing: premature optimisation that damages testability is a regression, not an improvement.
3. Core principles¶
Modules, not a monolith. Every gameplay system is a module with an explicit boundary. Modules communicate only through registered service interfaces and typed events — never by reaching into each other's types. See Architecture/02_MODULE_MODEL.md.
Engine-native. Use S&box's GameObject, Component, Prefab, Scene, GameResource
and networking directly. Do not build a compatibility layer over them. Do not recreate
Garry's Mod patterns (hook.Add, metatable patching, stringly-typed networked vars) out of
nostalgia.
Thin adapter layer, bounded. The framework may abstract cross-cutting concerns only: logging, configuration, persistence, commands, permissions, events, module lifecycle. It must not wrap GameObjects, Components, Prefabs, Scenes, or any normal gameplay API. A developer who knows S&box must be able to work in this codebase without learning a parallel universe.
Data-driven content. Items, jobs, recipes and prices are assets, not code. Adding a sandwich must not require a compiler. See Architecture/09_ITEM_FRAMEWORK.md.
Server-authoritative. The client renders and requests; it never decides. Any state a player could profit from forging — money, inventory, access, arrest status — is host-owned and host-validated. See Architecture/07_NETWORKING.md.
Testable core. Rules and maths (capacity, stacking, pricing, permissions, save
migration) live in engine-agnostic types that run under dotnet test with no scene, no
host, and no player. This constraint is upstream of everything; it is why the architecture
is shaped the way it is.
Documentation is a deliverable, not an afterthought. A subsystem's README.md and
public API docs are written before its implementation. See
Standards/02_DOCUMENTATION_STANDARDS.md.
4. Development order¶
Never build a large system in one pass. Every feature follows:
Small → Test → Review → Expand
And every subsystem follows the full loop:
Research → Read legacy behaviour → Document behaviour → Design → Review
→ ADR (if architectural) → Implement → Compile → Unit test
→ Integration test → Performance review → Refactor → Document → Commit
No stage is skipped. In particular: documentation precedes implementation, and the legacy behaviour is written down before the new design is drawn.
5. Definition of Done¶
A subsystem is complete only when all of the following hold. Anything less is in-progress, regardless of whether it works.
- [ ] Behaviour implemented and matching its
Legacy/spec (or the departure is justified by an ADR) - [ ] Code reviewed
- [ ] Subsystem
README.mdandArchitecture/doc updated - [ ] XML documentation on every public type and member
- [ ] Automated tests passing, including the engine-agnostic rule tests
- [ ] Multiplayer tested with at least two connected clients
- [ ] Save → restart → load verified, including a migration from the previous schema version
- [ ] No compiler warnings
- [ ] No known critical bugs
- [ ] Performance reviewed (allocation, network bytes, tick cost)
6. Decision records¶
Any decision that would be expensive to reverse gets an ADR. That includes: data model shape, persistence format, networking authority model, module boundaries, third-party dependencies.
ADRs are superseded, never deleted. The record of a decision we later abandoned is often more valuable than the decision that replaced it.
7. Authority¶
Contributors — human or AI — are expected to:
- Challenge weak designs rather than implement them.
- Refuse to add coupling between modules.
- Ask whether a feature belongs in its own module before growing an existing one.
- Prefer deleting code to adding a flag.
- Explain reasoning. "Because the original did it" is never sufficient justification.
Quality is more important than speed. There is no deadline that justifies technical debt in the framework layer.