Skip to content

Roadmap

Milestones and their exit criteria. This is the plan for turning Documentation/ into a running, forkable gamemode.


How to read this

Every milestone below ends with a working, tested, documented subset — never a broad, partially-working whole. This is 01_VISION.md's explicit non-goal: "Not feature-complete at v1." A milestone is not "done enough"; it meets every line of the Definition of Done or it is still in progress, regardless of how much of it works.

Tags follow Standards/04_GIT_WORKFLOW.md §5 exactly — this document is what each of those tags means, not a separate plan.

Two different senses of "done" appear below, and they are not interchangeable:

Marker Means
Complete Every exit criterion met, including the ones needing a running game.
🟡 Written Code and tests are on main and meet the design, but no compiler or running client has ever confirmed it.
Not started Named and scoped here so the order and dependencies are on record, but no architecture doc, ADR, or code exists yet.

Milestones 2 through 11 are currently 🟡. Milestones 12 onward are ⚪ — they cover every row still marked ❌ in Legacy/01_FEATURE_INVENTORY.md's "Not present at all" table, the same table Milestone 8 used as its own exit criterion for the content that did have legacy precedent. OfflineTests/ (landed mid-Milestone-8) compiles this repository's real source as an ordinary .NET class library and runs the real UnitTests/ suite with dotnet test — so the code genuinely compiles and its logic is genuinely tested — but no milestone has ever been opened in the real S&box editor, and no live client has ever connected. See § Verification debt at the foot of this document, which is a milestone gate in its own right and not a footnote.


Milestone 1 — Documentation ✅

Status: complete.

  • [x] Legacy/ — behavioural specification of the original, defect list, dependency graph
  • [x] Standards/ — coding, naming, documentation, testing, git
  • [x] ADR/ — six accepted decisions covering module architecture, persistence, networking, items, and inventory
  • [x] Architecture/ — 14 documents, real signatures, engine-verified against S&box (July 2026)
  • [x] Guides/ and this roadmap

No .sbproj existed at the close of this milestone. That was deliberate — see 00_CONSTITUTION.md §4: documentation and design precede implementation, not the reverse. Applejack.sbproj arrived with Milestone 2.

Exit criterion met: every Architecture/ document has been reviewed against the ADRs and the Legacy defect list for consistency, and cites real, verified engine APIs rather than assumed ones.


Milestone 2 — Core framework — v0.1.0

Status: 🟡 written. On main; tag not cut. Everything under Code/Core/ except Persistence/ (Milestone 3) landed here, plus Applejack.sbproj, UnitTests/ and .editorconfig. The two placeholder modules that proved boot ordering were deleted in Milestone 5 once CharacterModule → Items/Inventory → Persistence/Core proved the same guarantee with real modules.

The spine everything else attaches to. No gameplay yet.

Build: - Code/Core/Modules/GameModule, the topological dependency resolver, the bootstrap file (02_MODULE_MODEL.md) - Code/Core/Services/IServiceRegistry, IServiceResolver (03_SERVICE_REGISTRY.md) - Code/Core/Events/IEventBus, IEvent, CancellableEvent (04_EVENT_BUS.md) - Code/Core/Logging/IGameLog (12_LOGGING_AND_DIAGNOSTICS.md) - Code/Core/Configuration/IModuleConfigurationContext (05_CONFIGURATION.md) - Result / Result<T> (13_ERROR_HANDLING.md) - The .sbproj, UnitTests/ scaffold, .editorconfig

Exit criteria (in addition to the full Definition of Done): - A module with a declared dependency, a missing-dependency case, and a cyclic case each have a passing/failing regression test per 02_MODULE_MODEL.md §3. - An event bus test proves a throwing handler does not block a second handler (04_EVENT_BUS.md §6). - Two trivial placeholder modules boot in the correct order, each logging through IGameLog. - Zero gameplay code exists. If a reviewer finds gameplay logic in this milestone, that is a scope violation, not a bonus.


Milestone 3 — Persistence — v0.2.0

Status: 🟡 written. On main; tag not cut. Code/Core/Persistence/ — both backends, the migration chain, and PersistenceResult's named Corrupt case. The §6 conformance suite is written as an abstract base class so any backend can be run through it, but only an in-memory fixture backend is currently run through it: JsonPersistenceBackend needs FileSystem.Data and HttpPersistenceBackend needs a live endpoint, so neither can be exercised under dotnet test. The "passes against both backends" criterion below is therefore not met — it needs the editor, and is part of the verification debt.

Build: - IPersistenceBackend, SaveDocument<T>, DocumentKey (06_PERSISTENCE.md) - JsonPersistenceBackend (FileSystem.Data) - HttpPersistenceBackend, with the REST contract documented for operators - The migration chain (ISchemaMigration<T>) and the write-through cache pattern

Exit criteria: - The conformance suite from 06_PERSISTENCE.md §6 passes against both backends. - A fixture schema with two versions and one migration round-trips correctly. - A corrupted local save file produces a named Corrupt result, not a crash. - Guides/00_GETTING_STARTED.md's "configuring persistence" section is accurate against the real implementation (guides are load-bearing documentation — Standards/02_DOCUMENTATION_STANDARDS.md §6).


Milestone 4 — Character — v0.3.0

Status: 🟡 written. On main; tag not cut. Account/Character/CharacterId/ AccountModeration, CharacterStore, PermissionSet (flat exact-match, 12 tests), ConnectionExtensions.GetCharacter(), and CharacterNetworkComponent — the first [Rpc.Host] surface, walked against the exploit checklist at review. The milestone also forced an architecture fix: GameModule.RegisterServices now takes an IServiceResolver, so a module can resolve a DependsOn service at registration time.

The two exit criteria needing a running game — the save/disconnect/reconnect round trip and the two-client multiplayer test — are not met.

Build: - Account and Character (08_DATA_MODEL.md §1) - Character lifecycle: creation, spawn, IsInitialised, save, load - Permission resolution (engine-agnostic rule type, per Standards/03_TESTING_STANDARDS.md §3) - The first real [Rpc.Host] surface and its exploit-checklist pass (07_NETWORKING.md)

Exit criteria: - A character created, saved, disconnected, and reconnected loads with identical state. - Permission resolution across player/team/gang is unit-tested per Standards/03_TESTING_STANDARDS.md §3. - Multiplayer-tested with two connected clients, per the Definition of Done. - Every [Rpc.Host] method added this milestone is walked against the exploit checklist at review.


Milestone 5 — Items and Inventory — v0.4.0

Status: 🟡 written. On main; tag not cut. ItemDefinition with multi-level inheritance resolution and cycle detection, ItemRegistry, the Edible/Equippable/Container behaviour library (Contraband deferred), ItemInstance/ItemStateBag, the one shared Inventory implementation behind both holder kinds, InventoryStore, and 19 real item assets under Assets/Items/ drawn from the legacy source rather than invented. All four named regression tests (D-03, D-04, D-09, D-10) and the forced-mid-transfer atomicity test are written.

Two caveats carried forward: the .item JSON shape — specifically the polymorphic Behaviours discriminator — is an unverified best guess (see Assets/Items/README.md), and the "verified by actually adding an item following the guide" criterion needs the editor.

Build: - ItemDefinition, inheritance resolution, ItemRegistry (09_ITEM_FRAMEWORK.md) - The initial behaviour library: Edible, Equippable, Container - ItemInstance, IInventory, IInventoryHolder (08_DATA_MODEL.md §2–3) - Character inventory and one world container type, sharing the one implementation - A first batch of real item assets (10–20, not all 139) proving the authoring flow

Exit criteria: - D03_PerItemStackLimitIsEnforced, D04_ItemWithUnresolvableBaseFailsLoudly, D09_CharacterAndContainerInventoriesShareOneImplementation, D10_TwoInstancesOfTheSameDefinitionAreDistinguishable all pass — the named regression tests from Standards/03_TESTING_STANDARDS.md §3. - A transfer between character inventory and a world container is atomic under a forced mid-transfer failure (test with a fake backend that fails on the second write). - Guides/02_ADDING_AN_ITEM.md is verified by actually adding an item with no code, following the guide exactly.


Milestone 6 — First playable — v0.5.0

Status: 🟡 every build item written; no exit criterion met. This is the current milestone, and the honest summary is that its code is finished and its definition is not — every exit criterion below requires a running client, which nothing in this project has ever had.

Written, on main:

Build item Landed as
Interaction framework InteractionRunner (one singleton, keyed by CharacterId), the StillInRange/StillAlive/StillHasItem/TargetUnchanged/NotInterrupted condition library, InteractionComponent
Minimal Economy IEconomyService/EconomyStore, CharacterEconomyComponent
Minimal Ownership Ownership/OwnerRef/AccessGrant/AccessLevel, OwnershipStore
The RequestPurchase flow PropertyStore.PurchaseAsync + DoorComponent.RequestPurchaseDoor, traced end to end in 07_NETWORKING.md §7
Minimal Ui IHudRegistry/HudRegistry/HudRoot.razor, with MoneyDisplay and InventoryPanel as its first registrants
The Guide's example module Bulletin, built for real and booted from ApplejackBootstrap

Also closed here: ItemDefinition.CreateUseInteraction and CharacterInventoryComponent.RequestUseItem, the RPC entry point that finally connects an item behaviour's built InteractionRequest to IInteractionRunner.TryStart over the wire.

Three deliberate gaps are named rather than hidden — RequestPurchaseDoor's exploit checklist marks Distance N/A because no player-to-world-position binding exists yet; IOwnershipService.HasAccess cannot resolve Team/Gang ownership because Jobs does not exist yet; and whether [Sync] accepts a List<InventorySlotView> directly is unverified. Each is recorded in the owning module's README.md under "Not responsible for".

The first milestone a person can actually play, however small.

Build: - Interaction framework (10_INTERACTION_FRAMEWORK.md) - Minimal Economy — money, a single RequestPurchase-shaped flow (a door, per 07_NETWORKING.md §7) - Minimal Ownership — the Ownership record, one ownable entity type (doors) - Minimal Ui — a HUD showing money and a basic inventory panel (11_UI_ARCHITECTURE.md) - Guides/01_WRITING_A_MODULE.md's example module, built for real (not just documented)

Exit criteria: - Two players can join a server, pick up an item, buy a door, store something in it, and reconnect without losing state. - Idle bandwidth per player measured near zero, per 07_NETWORKING.md §9 — the direct regression test for D-11. - A short recorded or narrated playtest exists, referenced from the milestone's closing commit.


Milestone 7 — Remaining gameplay

Status: 🟡 written. On main; tag not cut. Full Jobs, Needs, Crime/Law, Chat, Commands, and the rest of World — each landed as its own small commits, per this milestone's own exit criterion below.

Everything in Legacy/13_DEPENDENCY_GRAPH.md §4 not yet built: full Jobs (group/gang/team, demotion, mutiny), Needs (hunger, stamina, thirst as configuration), Crime and Law (warrants, arrest, laws), Chat (proximity, channels), full Commands, the rest of World (containers beyond the first type, locks, seals).

Exit criteria: each system meets the Definition of Done independently and lands as its own set of small commits — this milestone is explicitly not "one big merge"; per 00_CONSTITUTION.md §4, Small → Test → Review → Expand applies within it exactly as it does everywhere else. No version tag is cut until the whole milestone closes, but nothing here waits for the others to start.


Milestone 8 — Content migration

Status: 🟡 written. On main; tag not cut. All 116 real legacy items and 16 jobs authored as Assets/, every item in Legacy/01_FEATURE_INVENTORY.md given a disposition — this milestone's own exit criterion below, met.

The 139 legacy items, all jobs, and all recipes, authored as assets per Guides/02_ADDING_AN_ITEM.md — a content task, not an engineering one, deliberately deferred until here per ADR-0004.

Exit criteria: every item in Legacy/01_FEATURE_INVENTORY.md has a disposition — migrated, deliberately dropped (with a one-line reason), or deliberately redesigned (with an ADR or 01_VISION.md §3 citation).


Milestone 9 — Polish and hardening

Status: 🟡 partial. On main; tag not cut. The exploit-checklist pass across every [Rpc.Host] entry point and closing Legacy/'s > **Gap:** markers are both done (zero remain). Performance review and stress testing are named, not done — both need a live S&box server connected to real clients, an environment limitation standing since Milestone 2; see Milestone 10 and Verification debt below.

Performance review (allocation, network bytes, tick cost per 00_CONSTITUTION.md's Definition of Done), stress testing at realistic player counts, a full exploit-checklist pass across every [Rpc.Host] entry point accumulated so far, and closing any > **Gap:** markers left in Legacy/.


Milestone 10 — v9.8.0 — Release-prep

The repository is public. ADR-0008 records the decision to go public during this milestone — ahead of the original plan below — in order to host a public documentation website. v1.0.0 no longer marks "goes public"; that already happened. It now means the first stable, feature-complete release, independent of visibility. This milestone still tags v9.8.0, not v1.0.0: going public is a visibility change, not a claim that everything below is finished — the live-server-gated items are still named, not faked. See Standards/04_GIT_WORKFLOW.md §5 for the full tag table.

Superseded framing, kept for context. This milestone was originally planned around "Reserving v1.0.0" — the idea that v1.0.0 marks the repository (then private) going public, and that pre-launch tags use the v9.x range specifically so they can never be confused with that moment. ADR-0008 changed that plan; the v9.x range is kept for its own sake (renumbering released tags is worse than an explanation that's evolved), but no longer marks a countdown to public launch.

Exit criteria, split by what this environment can actually verify: - Done this pass: a way to become an admin at all (CharacterConfiguration's bootstrap list — nothing could grant a permission before this), /give (closing Guides/02_ADDING_AN_ITEM.md's own named blocker on spawning a newly-authored item), the Equippable/Container field documentation gap, a real Guides/04_SERVER_ADMIN_GUIDE.md for the "stand up and reconfigure a fork" persona this milestone's exit criterion actually names, and the nullability warnings-as-errors policy enforced for real in OfflineTests/ (tasks.md #37). - Also done this pass: a real, functional admin panel (/grant, /revoke, /kick, all reached through AdminPanel.razor) and dev menu (item/job browser+spawn, module boot-order status, a live config inspector, a log tail — DevMenuPanel.razor), this codebase's first interactive UI and its first on-demand-panel pattern (Architecture/11_UI_ARCHITECTURE.md §7, Code/Admin/README.md). Explicitly, deliberately deferred rather than faked: teleport, noclip, and spectate all need a Character↔pawn Transform binding that is completely absent from this codebase (not partial — zero code anywhere reads or writes a player's world position), and real ban records/mute enforcement need their own design pass. Both are named, scoped follow-up work, not silently dropped scope. - Still blocked on a live S&box server, unchanged by this pass: Milestone 6's playtest exit criteria (two players, idle-bandwidth measurement, a recorded session) and Milestone 9's performance/stress-testing halves. Every milestone above is written, not all of them are closed — see the ✅/🟡 status convention at the top of this document. - A GitHub Release is not cut for v9.8.0 — per Standards/04_GIT_WORKFLOW.md §5, Releases are for major versions, public previews, RCs, and v1.0 itself. The repository being public as of ADR-0008 doesn't change that — a Release is still for announcing something to an audience, and v9.8.0 isn't that moment. - "Without reading Code/" replaces the earlier, more literal "using only Guides/ and Assets/" — Guides/00_GETTING_STARTED.md itself routes readers into Architecture// ADR//Standards/ for orientation, which is fine (documentation, not implementation) but was in literal tension with the old wording.


Milestone 11 — Player presence & admin tools — v9.9.0

Status: 🟡 every build item landed; no exit criterion formally verified. On main; tag not cut. All six build items below are landed: Applejack.Movement (PawnComponent, IPawnLocator), DoorComponent's real distance checks, the admin movement commands (/goto, /bring, /tp, /noclip, /spectate, /unspectate), the admin player-list panel (PlayerListPanel.razor), Account persistence (AccountStore/IAccountService, accounts/{steamId}), and real ban records + mute enforcement (AccountModeration/ ModerationStatus, /ban//unban//mute//unmute, enforced in CharacterNetworkComponent.RequestCreateCharacter and ChatRouter.SendAsync). Every engine-agnostic rule has a real unit test written; dotnet test now runs to completion (312/312 passing) — the compile breakage previously flagged here (a concurrent player-menu workstream, plus a since-found second break from the whitelist/obsolete-API migration leaving OfflineTests/Shims/ out of sync on [AssetType], SteamId.ValueUnsigned, and Http's cancellationToken parameter name) is resolved — see Verification debt below. Scoped following Milestone 10's admin panel/dev menu, which named three things it deliberately did not build — teleport/noclip/spectate and real ban records — both blocked on "a Character↔pawn Transform binding that doesn't exist anywhere in this codebase" (Code/Admin/README.md). ADR-0007 is that binding's design; Architecture/22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md is its concrete shape.

Two things turned out bigger than "add a Transform field" once actually scoped: this codebase has no physical player presence at all yet — no .scene, no .prefab, empty StartupScene/MapStartupScene in Applejack.sbproj — so this milestone includes building a minimal reference player prefab and spawn point, not just the binding layer; and Account has no persistence of any kind yet — no store, no schema, no service — so real ban records need that stood up first. Neither has legacy precedent to preserve: a full search of Documentation/Legacy/ found no admin teleport, no spectate, no admin overview map, and no server ban/mute mechanism (only a job/item _Blacklist, a different system) — everything here is new design, which is why it has its own ADR rather than a legacy citation.

Build, each landing as its own independent set of small commits — per this milestone's own exit criterion below, none blocks another from starting: - Applejack.MovementPawnComponent, IPawnLocator, the reference player prefab and one spawn point (22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §1) - Real distance checks on DoorComponent's purchase/lock RPCs, closing the "Distance: N/A" gap named since Milestone 6 (§2) - Admin movement commands — /goto, /bring, /tp, /noclip, /spectate, /unspectate, each behind its own new permission (admin.teleport, admin.noclip, admin.spectate) (§3) - An admin player-list panel — name, live position, distance from the viewing admin; a sortable list, not a textured minimap (no per-map coordinate convention exists to build one against yet) (§4) - Account persistence, standing up for the first time: AccountStore, IAccountService, the accounts/{steamId} schema - Real ban records (AccountModeration becomes reason/issuer/timestamps, not a bare enum) and mute enforcement in ChatRouter.SendAsync, plus /ban, /unban, /mute, /unmute (§6)

Exit criteria: - Every build item above meets the full Definition of Done independently and lands as its own commits — explicitly not one big merge, per 00_CONSTITUTION.md §4, the same rule Milestone 7 held itself to. - Character gains no engine-typed field — reviewed against ADR-0007's Decision directly. - Every new [Rpc.Host]-reachable path (teleport/noclip/spectate/ban/mute, all routed through the existing CommandDispatchComponent.RequestExecuteCommand, per §3's refinement over ADR-0007's illustrative sketch) is walked against the exploit checklist. - The four unverified engine assumptions named in 22_PLAYER_PRESENCE_AND_ADMIN_TOOLS.md §5 are either confirmed or still explicitly named as open — never silently assumed correct. - Like every milestone since Milestone 6, the parts needing a running client (does the pawn actually move on someone else's screen, does spectate's camera reparent work, does the reference prefab even compile as an S&box addon) are not met by this pass and are not claimed to be — see Verification debt below, unchanged by this milestone.


Milestones 12–18 — Legacy content: everything still marked ❌

Status: Milestone 12 🟡 every build item landed, no exit criterion formally verified against a live client; Milestones 13–18 ⚪ not started. Sequenced by dependency and leverage, not by list order — see the sequencing note before each milestone for why it sits where it does. Every item here is dispositioned New in Legacy/01_FEATURE_INVENTORY.md: legacy gave none of it real behaviour to preserve, so this is design work, not a port. One partial exception is named explicitly in Milestone 12 below (Vehicles).

Each milestone follows the same per-system pattern Milestones 7 and 8 already established: Architecture doc (and an ADR where the decision is genuinely costly to reverse) written before the module, OfflineTests coverage, content assets, docs sync — landing as its own small commits per 00_CONSTITUTION.md §4, never one merge per milestone. The Plugin System work tracked separately in the project's task log (ADR-0010, Phase 2 — the in-repo loader — complete as of 2026-07-31; Phase 3, Library-assembly plugins, gated behind an editor spike) does not gate any of this — that work is for third-party extension without a recompile; everything below is first-party gameplay and ships as an ordinary module under Code/, the same way Jobs, Crime, and Vehicles already did.


Milestone 12 — Independent extensions — v9.10.0

Status: 🟡 every build item landed and merged to main; live-client verification still open. Built as four genuinely parallel, independently-reviewed worktrees, exactly as the sequencing note below anticipated — each landed as its own PR, independently verified (dotnet test run fresh against each branch, diff read in full) before merging, main green throughout (398/398 passing, 0 warnings, as of the last merge): - Vehicles: entering, exiting, and driving — PR #9. - Thirst, Temperature, Disease — PR #7. - Weapon attachments and jamming — PR #11. - Target resolution by name (wired to /pm) — PR #8.

Sequencing note: these four items share no state and depend only on modules that already exist (Needs, Items, Movement, Character). Nothing here waits on anything else in Milestones 12–18, so this is the one milestone genuinely safe to build as four parallel worktrees, mirroring the pattern already used for Plugin System Phase 1a/1b/1c.

Build: - Vehicles: entering, exiting, and driving. The one item on this list with real legacy precedent for the surrounding feature — ownership, purchase, and the key-as-item design are already built (ADR-0009). Driving itself has no legacy precedent either (legacy never had a Transform/pawn binding at all), and ADR-0009 named its own unblock condition explicitly: "Movement has a second real consumer beyond admin teleport/spectate." Movement (PawnComponent/IPawnLocator) landed in Milestone 11 — the condition is met. See ADR-0012 (Accepted). This pass supports exactly one driver seat and one passenger seat per vehicle, not a general seat-count system — see that ADR's Notes. - Thirst, Temperature, Disease — extends NeedsConfiguration with three more NeedDefinition entries, mirroring Hunger's existing pattern (14_NEEDS_FRAMEWORK.md) exactly. No new module. - Weapon attachments and jamming — extends the Equippable item behaviour (09_ITEM_FRAMEWORK.md). No new module. - Target resolution by name — not on the original list, but cheap and load-bearing: it currently blocks /warrant, /pm, and offline ban/mute, and will block Milestone 17's fines/records commands if left for later. A small Character service extension, not a new module.

Exit criteria: - Each of the four lands independently and meets the full Definition of Done on its own — explicitly not one merge, per Milestone 7's own precedent for this phrasing. Met — see Status above. - A vehicle can be entered, exited, and driven by its owner or anyone holding an AccessLevel of Passenger or Drive; every new [Rpc.Host] surface this adds is walked against the exploit checklist. Code-complete and exploit-checklisted; not yet exercised against a real client — same live-verification gap as Milestone 11's own admin tools, see Verification debt below. - Guides/02_ADDING_AN_ITEM.md documents adding an attachment the same way it documents adding any other item. Met.


Milestone 13 — Character: creation, multiple characters, biography, attributes, skills — v9.11.0

Sequencing note: deliberately front-loaded ahead of Milestones 14–18 even though nothing in Milestone 12 depends on it. Medical (15), Crafting (16), Licences (14), and Criminal Records (17) all want to hang state off "a specific character's" attributes, skills, or biography. Building those four first against today's Account↔Character 1:1 model and retrofitting Character afterward would mean migrating four modules instead of giving one clean surface up front. This is the one milestone in this batch that reshapes an existing module rather than adding a new one, which is why it gets its own ADR rather than an in-place extension.

Build: - Character creation, and multiple characters per account with selection and deletion — legacy's own gap was total: "a character is a Steam account" (Legacy/01_FEATURE_INVENTORY.md). See ADR-0011 (Proposed) for the Account↔Character cardinality decision this forces. - Biography and description fields. - Attributes and skills, and the first real consumer of them (even if it's just Milestone 16's crafting success/speed check). - New architecture doc: 25_CHARACTER_CREATION_AND_PROGRESSION.md, plus updates to 08_DATA_MODEL.md §1.

Exit criteria: - A player can create, select, and delete a character; every existing system that currently assumes "the" character for a connection (persistence, the admin panel, CommandContext) is reviewed and updated to resolve the active character explicitly, not implicitly. - Character gains no new engine-typed field, matching the discipline ADR-0007 already set for Movement. - Save/load round-trips a character with biography, attributes, and skills populated.


Milestone 14 — Economy and property extensions — v9.12.0

Status: 🟡 every build item landed and merged to main; live-client verification still open. Built as four genuinely parallel worktrees, same pattern as Milestone 12 — ADR-0013 (Housing's open decision) resolved first, then all four launched together, each landed as its own PR, independently verified (dotnet test run fresh against each branch, full diff read) before merging, main green throughout (581/581 passing, 0 warnings, as of the last merge): - Housing (property groups) — PR #19, stacked on the ADR resolution itself, PR #15. - Banks — PR #16. - Vendors / NPC shops — PR #17. - Licences — PR #18.

Sequencing note: four independent additions to already-built modules (Economy, Ownership, World, Items, Interaction), safe to build in parallel once Milestone 13 lands (Licences references Character's new permission/attribute surface; the other three don't strictly need it but are grouped here for a single milestone of "things the economy and world can now sell or gate").

Build: - Banks — legacy built the enabling hook and never the bank (Legacy/01_FEATURE_INVENTORY.md). New doc 26_BANKING.md. - Vendors / NPC shops — deliberately scoped as a static, ownerless shop entity reusing WorldContainer's shape, no AI. Ships here, well before Milestone 18's NPC framework, on purpose — nothing about a shop actually requires a person behind the counter. New doc 27_VENDORS.md. - Licences — never existed in legacy at all. New doc 28_LICENCES.md. - Housing — legacy had nothing beyond door ownership. This is explicitly the "master/slave door linking → property groups" follow-on Milestone 7's World system already named and deferred, not a new concept invented from nothing. See ADR-0013 (Accepted). New doc 29_HOUSING.md.

Exit criteria: - Each of the four meets the Definition of Done independently and lands as its own commits. Met — see Status above. - A player can open an account at a bank, buy from a static vendor, hold a licence that gates at least one existing check (e.g. carrying a weapon category), and own a multi-room property as one linked group rather than N independent doors. Code-complete and exploit-checklisted; not yet exercised against a real client — same live-verification gap as Milestone 12's own vehicles/needs work, see Verification debt below. Licences gates weapon equip specifically (CharacterInventoryComponent.RequestUseItem's Equippable flow), since no existing pickup/possession check existed to hook into instead — see 28_LICENCES.md §4.


Milestone 15 — Medical — v9.13.0

Sequencing note: after Milestone 12 so Thirst/Disease exist as real needs axes to attach injury/illness severity to, and after Milestone 13 so severity can optionally scale against attributes. Reuses Crime's existing knockout/incapacitate primitives rather than inventing a second one.

Build: a real Medical system replacing legacy's drugs items + sleep regen (Legacy/01_FEATURE_INVENTORY.md) — injury states, treatment items, and a revival/incapacitation path that composes with Crime's existing Incapacitate primitive rather than duplicating it. New doc 30_MEDICAL_FRAMEWORK.md.

Exit criteria: - A character can be injured, treated, and revived through the new system end to end. - The new injury states compose with Crime's existing knockout/incapacitate rather than introducing a second, competing "is this character down" concept.


Milestone 16 — Crafting — v9.14.0

Sequencing note: genuinely unblocked, not just next in line. ItemDefinition.BatchSize and JobDefinition.CanManufacture were built in Milestone 8 specifically for this and have sat unused since; the missing "spawn the result at the crafter's position" gap that kept /manufacture a purchase instead of real crafting is closed by Milestone 11's Movement module.

Build: a real crafting system — recipes, a timed Interaction (reusing the primitive Interaction already provides, not a new one), and a result that spawns at the crafter's position via IPawnLocator — replacing /manufacture's "purchase dressed as crafting" ( Legacy/01_FEATURE_INVENTORY.md ). Optionally gated by Milestone 13's skills, if that lands first. New doc 31_CRAFTING_FRAMEWORK.md.

Exit criteria: - A player can craft a real recipe through a timed interaction and receive the result in the world at their own position, cancellable the same way any other Interaction is. - JobDefinition.CanManufacture gates who may craft what, closing the gap named at Milestone 8.


Milestone 17 — Evidence, fines, criminal records, bail, trials, sentencing — v9.15.0

Sequencing note: split into three waves within one milestone, smallest first, because the three pieces are genuinely different sizes. All three need Milestone 12's target-resolution.

Build: - Wave 1 — Fines and criminal records. Small, additive to Crime, Economy, and Character's new biography surface. New doc 32_CRIMINAL_JUSTICE_EXTENSIONS.md. - Wave 2 — Evidence. Needs a real collection/physical-item mechanic on top of Items and Inventory — an evidence item is not just another Container entry, it needs a chain-of- custody concept Items doesn't have yet. - Wave 3 — Bail, trials, sentencing. The heaviest piece in this entire batch: a real multi-party "court" state machine, where legacy's only precedent is a single fixed 300-second sentence with no trial at all. See ADR-0015 (Proposed) for the state-machine shape. New doc 33_TRIALS_AND_SENTENCING.md.

Exit criteria: - Each wave meets the Definition of Done independently; Wave 3 does not block Waves 1–2 from shipping first. - A fine or criminal record can be issued to a named, currently-connected player using Milestone 12's target-resolution, not a raw SteamId. - A full bail → trial → sentencing cycle completes end to end for at least one crime.


Milestone 18 — NPC framework — v9.16.0

Sequencing note: last, deliberately. Legacy left only a stub base class — no AI, no pathing, nothing to preserve. This needs S&box pathing/navmesh, which is completely unverified in this codebase, the same category of risk the Verification Debt section already tracks for other engine-facing unknowns. Nothing else in Milestones 12–17 requires it — Milestone 14's Vendors was deliberately scoped as a static entity for exactly this reason. Once this lands, Milestone 14's Vendors may be upgraded to NPC-driven as a pure enhancement, not a rebuild.

Build: a real NPC framework — pathing, basic behaviour, and at least one real consumer (a patrol NPC, or an NPC-driven Vendor upgrade). See ADR-0014 (Proposed) for the AI/pathing-approach decision this needs before any code. New doc 34_NPC_FRAMEWORK.md.

Exit criteria: - At least one NPC exists in a running scene, paths between two points, and is exercised by a real gameplay consumer — not a standalone tech demo. - This is the last row in Legacy/01_FEATURE_INVENTORY.md's "Not present at all" table to close. Once it and the remaining Verification Debt items below are both settled, v1.0.0 — "the first stable, feature-complete release" per Standards/04_GIT_WORKFLOW.md §5 — is achievable. This roadmap does not pre-declare that tag; it is cut when the criteria are actually met, not before.


Verification debt

Milestones 2 through 6 were written without a compiler. That is a deliberate consequence of the development environment, not a standards lapse — but it is debt, it compounds, and it was the single largest risk to this project. OfflineTests/ (Milestone 8) closed the largest piece of it; what's below reflects that, not the original, fully-uncompiled state.

What OfflineTests/ actually settled: it compiles Code/'s engine-agnostic source (everything except thin engine-facing Components — see that project's own .csproj for the exact exclusion list) plus the real UnitTests/ suite into an ordinary .NET class library, with hand-written shims standing in for the S&box engine surface. dotnet test genuinely runs — 258/258 passing as of this milestone — and it has already caught one real bug the uncompiled milestones before it could not have (a lambda-parameter-shadowing typo in Edible.cs's OnComplete, found and fixed mid-Milestone-8). This means "the project has never been compiled" and "dotnet test has never been run" are no longer true — they were true through Milestone 7, and the record here now reflects when that changed.

What still hasn't happened, unchanged by OfflineTests/:

  • No S&box editor has ever opened Applejack.sbproj. OfflineTests/ proves the code compiles as ordinary C#; it says nothing about whether it compiles as an S&box addon — engine attributes, GameResource serialization, and hot reload are all unverified. Assets/Prefabs/PlayerPawn.prefab and Assets/scenes/main.scene (with Applejack.sbproj's StartupScene/DedicatedServerStartupScene now pointing at the latter) exist as of this pass specifically to make that first real load testable via dedicated-server tooling on a machine without editor-capable graphics — see Assets/Prefabs/README.md's own FORMAT UNCERTAINTY section for exactly what in those two files is hand-authored-from-public-example rather than confirmed.
  • No client has ever connected. Every [Rpc.Host] method has been walked against the exploit checklist by reading, never by exercising over a real connection.
  • OfflineTests/ is a stand-in, not the real thing. Delete it once the real editor-generated UnitTests/ project is available and verified on a machine with the editor — see that project's own header comment.
  • dotnet test compiles and passes again (312/312) as of the fix/whitelist-obsolete-apis-and-spawn branch. Two separate breaks accumulated and are now both resolved: a concurrent player-menu workstream that added Applejack.Characters.Ui/Applejack.Commands.Ui/Applejack.Law.Ui namespaces and Code/Ui/PlayerMenuRootComponent.cs without the matching OfflineTests.csproj exclusion (fixed by excluding the file, same pattern as DoorComponent.cs); and a second break from the whitelist/obsolete-API migration (db7c83a), which moved Code/ to [AssetType], SteamId.ValueUnsigned, and Http.RequestAsync(..., cancellationToken:) without updating OfflineTests/Shims/ to match — GameResource.cs, SteamId.cs, and Http.cs now define/name those to compile again. Milestone 11's own unit tests have now actually executed end to end, not just been hand-checked for absence of new errors.
  • ModuleRunner.Shutdown() is now called. The scene-teardown counterpart hook this section previously said was unconfirmed is now confirmed against the real engine source at /home/kyle/Projects/sbox-public: GameObjectSystem (the base class of ApplejackBootstrap) implements IDisposable (engine/Sandbox.Engine/Scene/GameObjectSystem/GameObjectSystem.cs:11), and Scene.ShutdownSystems() (engine/Sandbox.Engine/Scene/Scene/Scene.System.cs:13-32, called from Scene.DestroyInternal() at Scene.cs:150, itself called from Scene.Destroy() at Scene.cs:132-135) calls Dispose() on every registered GameObjectSystem, wrapped in a per-system try/catch. ApplejackBootstrap now overrides Dispose() to call ModuleRunner.Shutdown() before base.Dispose() — see Code/Core/Modules/ApplejackBootstrap.cs's header comment and its Dispose() override for the full citation and ordering rationale.
  • GameObject.SetParent's existence and replication are now confirmed (Milestone 12, ADR-0012's vehicle-driving work), the same check-source-first discipline as the ModuleRunner.Shutdown() entry above: GameObject.SetParent(GameObject value, bool keepWorldPosition = true) exists (engine/Sandbox.Engine/Scene/GameObject/GameObject.cs:487-512), accepts null to re-parent back to the scene root (value ??= Scene, same file), and — for a NetworkSpawn()'d GameObject — replicates the change to every observing client automatically via an internal [Rpc.Broadcast] Msg_SetParent call (GameObject.cs:511, handled by SetParentFromNetwork at GameObject.cs:517-536, the RPC itself in GameObject.Network.cs:314-340). No custom [Sync] mirror is needed. GameObject.LocalPosition/LocalRotation (GameObject.LocalTransform.cs:10-30) and Transform.PointToWorld (used throughout engine/Sandbox.Engine/, e.g. Editor/Gizmos/Hitbox/Hitbox.cs:193) are confirmed too. Code/ Movement/PawnComponent.cs's new EnterVehicle/ExitVehicle methods use all four. What is not confirmed: runtime behaviour with a second connected client actually watching a reparent happen (interpolation smoothness, whether a client-side prediction/camera system needs its own awareness of the switch) — the API existing and broadcasting is settled, the live-session experience of it is not.
  • A single suspend-movement bool is confirmed insufficient on its own (same Milestone 12 dig, PawnComponent.IsInVehicle, mirroring IsNoclipping's open question from Milestone 11): the real stock movement controller, PlayerController, exposes UseInputControls (PlayerController.Input.cs:6), and OnFixedUpdate/DefaultControls genuinely gates InputMove()/InputJump()/ducking behind it (PlayerController.DefaultControls.cs:75) — so a bool does stop new input, confirming the shape IsNoclipping already assumed. But PlayerController.PrePhysicsStep() (PlayerController.cs:185-194) still applies gravity/velocity/ground-friction physics regardless of that flag (only IsProxy skips it), so residual velocity is not zeroed by the flag alone. Fully freezing a pawn, if that matters once this is exercised in a real session, needs either Component.Enabled = false on PlayerController itself or an explicit velocity reset — IsInVehicle ships anyway (real, useful signal for animation/UI state and for a future PlayerController integration to read), with this limitation named rather than silently assumed away. Also resolves, by the same finding, what IsNoclipping's own still-open bullet above was asking: UseInputControls is the settable mode flag that bullet speculated might exist, though whether it is sufficient for noclip specifically (which additionally needs collision suppressed, not just input) is unconfirmed here and remains that bullet's own open question, not resolved by this one.

Specific known-unverified assumptions, each already flagged at its own site: the .item/ .job asset JSON shape's polymorphic Behaviours discriminator; [Sync] support for a List<InventorySlotView>; CharacterStore's internal visibility needing InternalsVisibleTo wiring in a generated .csproj; SteamId construction in tests, and (Milestone 10) whether SteamId.Value is really a ulong SteamID64 the way CharacterConfiguration's admin bootstrap and the OfflineTests/Shims/SteamId.cs shim both assume; and (Milestone 10) whether Applejack.sbproj's Compiler.WarningsAsErrors: "true" is actually interpreted as "treat every warning as an error" by the S&box compiler wrapper, or whether CS1591 (missing XML doc) can even fire given no GenerateDocumentationFile-equivalent key is visible anywhere in the .sbproj schema; and (Milestone 10's admin panel/dev menu, this codebase's first interactive UI) Connection.All/Connection.Kick() as the API for finding and disconnecting a specific player's connection, Input.Pressed(string) as a named-action per-frame keybind check, onclick="@(() => ...)" as S&box Razor's real click-binding syntax, and toggling a child GameObject.Enabled as how a panel's visibility is actually shown/hidden — all four are the most plausible real S&box surface for what they're used for, at the same confidence level this codebase already extends to Rpc.Caller.SteamId and [Sync] on a List<T>, none independently confirmed against engine source this pass.

  • 2026-07-31 session — closing the character-binding gap, and a new blocker found while doing it. Investigating a report that "everything except movement/UI" fails on a real rp_downtown (thieves.rpdowntown3t) session traced the cause to PlayerPawn.prefab never carrying CharacterNetworkComponent or any of its sibling per-character components (InteractionComponent, CharacterEconomyComponent, CharacterNeedsComponent, CharacterInventoryComponent, CharacterBankAccountComponent) — every module keyed off GameObject.Components.Get<CharacterNetworkComponent>() had nothing to find. All six are now added (Milestone 11's PlayerPawn.prefab), none carry [Property] fields so the JSON entries needed no new format guesses. Separately, PawnComponent.CharacterId never left its prefab default of Guid.Empty after spawn — IPawnLocator's Register/Find was always keyed on a value no [Rpc.Host] caller could ever match — closed via a new OnUpdate poll-and-rebind on PawnComponent itself (ADR-0007 Addendum, Code/Movement/PawnComponent.cs).
  • GameObject-reference and Component-reference [Property] JSON shapes are now confirmed, not guessed: pulled Facepunch/sbox-hc1's current Assets/prefabs/player/player.prefab and Assets/prefabs/hud.prefab directly (gh api/curl, this session). A same-file [Property] GameObject reference serializes as {"_type": "gameobject", "go": "<the target GameObject's own __guid>"} (player.prefab's Head/CameraObject/Target fields, cross- checked against the referenced __guid appearing as a real Children entry elsewhere in the same file). A [Property] typed as a specific Component subclass serializes as {"_type": "component", "component_id": "<that component's own __guid>", "go": "<the GameObject it lives on>"} (player.prefab's Body/HealthComponent/Player fields). Used to wire CharacterSelectRootComponent.SelectPanel and CharacterSelectPanel.CharacterNetwork into PlayerPawn.prefab this same session — the character-creation/selection screen a player actually needs to reach RequestCreateCharacter now has a real, scene-placed path. Also confirms Sandbox.ScreenPanel as a sibling component (not a parent/ancestor) alongside a PanelComponent-derived type on one GameObject is the real, current pattern (hud.prefab's root carries Facepunch.UI.MainHUD + Sandbox.ScreenPanel + Facepunch.UI.DeveloperMenu + a second Sandbox.ScreenPanel + Facepunch.Crosshair, all as siblings) — resolves this section's own former "toggling a child GameObject.Enabled... none independently confirmed" line for the reference-shape question specifically (visibility-toggle-via-Enabled itself is still unconfirmed, a separate question).
  • New blocker found, not yet fixed: Code/Ui/HudRoot.razor and Code/Ui/PlayerMenu.razor both have their actual per-module content-instantiation call (TypeLibrary.Create<Panel>(element.PanelType) / TypeLibrary.Create<Panel>(tab.PanelType)) commented out, each citing "a compiler whitelist violation." Grepping the codebase confirms this isn't cosmetic: every RegisterHudElement<T>/RegisterMenuTab<T> call site across every module (Code/Economy/EconomyModule.cs, Code/Ui/UiModule.cs's own Info/Changelog/Credits tabs, etc.) is also commented out, so IHudRegistry.RegisteredElements and IMenuRegistry.RegisteredTabs are unconditionally empty today. Scene-placing HudRoot/ PlayerMenu right now would render a real but completely empty shell (a ScreenPanel with zero HUD elements and a nav rail with zero tabs) — not the fix "UI doesn't fully work" needs. Checked ~/Projects/sbox-public's Sandbox.Internal.TypeLibrary.Create<T>(Type, object[]) (engine/Sandbox.Reflection/TypeLibrary/Instancing.cs:53-67) directly: the engine's own implementation is a plain IsAllowedType check plus Activator.CreateInstance, nothing that obviously bans a whitelisted addon type — but whether the addon-side Roslyn compiler restrictions genuinely block calling this generic method with a runtime Type argument from game code (as opposed to the engine's own internal use of it) is a separate, closed-source compiler-analyzer question this public engine mirror can't settle. Not fixed this session — flipping it blind, under this project's WarningsAsErrors: true, risks a hard compile failure with no local way to verify against the real S&box Roslyn analyzer. Needs either a real editor/compiler session, or a second public sample demonstrating a working TypeLibrary.Create<Panel>(Type) call from addon (not engine) code before re-enabling.
  • Deliberately deferred this session: AdminToolsRootComponent's three panel references (AdminPanel/DevMenuPanel/PlayerListPanel) and PlayerMenuRootComponent.Menu — same confirmed reference shape as CharacterSelectRootComponent above, could be wired the same way, but lower priority than closing the character-creation path, and HudRoot/PlayerMenu scene-placement was skipped pending the TypeLibrary.Create question above. Also deferred: placing map-anchored gameplay entities (DoorComponent/BankComponent/VendorComponent/ HousingListingComponent/WorldContainerComponent) into Assets/scenes/main.scene — no reference-shape blocker for these (none take a GameObject/Component [Property]), purely scoped out of this pass; see Assets/Prefabs/README.md/Assets/scenes/README.md for the current per-file state.

Opening the real S&box editor for the first time still takes precedence over new feature work, exactly as before: compile, run dotnet test (both the real editor-generated project and, while it still exists, OfflineTests/), fix what falls out, and only then attempt Milestone 6's playtest criteria. A green build in the real editor is a prerequisite for the playtest, not a parallel activity.


What is deliberately not on this roadmap

Per 01_VISION.md §5: Garry's Mod addon/save compatibility, DarkRP-style job-spam economies, and single-server-only assumptions anywhere in the design. If a milestone's implementation starts trending toward any of these, that is a signal to stop and re-read 01_VISION.md, not a scope addition to accept quietly.