Skip to content

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.

  1. Stable architecture
  2. Modular systems
  3. Data-driven design
  4. Automated testing
  5. Gameplay parity
  6. Performance
  7. 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.md and Architecture/ 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.