9.4 KiB
9.4 KiB
NEO-51 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-51 |
| Title | E3.M3: Server loads item catalog at startup (fail-fast) |
| Linear | https://linear.app/neon-sprawl/issue/NEO-51/e3m3-server-loads-item-catalog-at-startup-fail-fast |
| Module | E3.M3 — ItemizationAndInventorySchema · Epic 3 Slice 1 (E3M3-02) |
| Branch | NEO-51-server-item-catalog-load-fail-fast |
| Precursor | NEO-50 — frozen six-item catalog + CI (Done on main) |
Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| NEO-51 vs NEO-52 | Ship IItemDefinitionRegistry + ItemDefinitionRegistry on this story, or only load + ItemDefinitionCatalog? |
Strict split — NEO-51: loader + ItemDefinitionCatalog + eager fail-fast boot; NEO-52 adds injectable IItemDefinitionRegistry (E3M3-03). Rationale: E3M3 backlog separates E3M3-02/03; skills combined registry in NEO-34 but items already have a dedicated registry ticket. |
User: strict split (kickoff re-ask). |
| Startup failure tests | Loader unit tests only, or also WebApplicationFactory host boot failure? |
Both — mirror NEO-34 (SkillDefinitionCatalogLoaderTests happy path, failure modes, Host_ShouldResolveCatalogFromDi, Host_ShouldFailStartup_WhenCatalogDirectoryInvalid). |
User: both loader + host boot tests (kickoff re-ask). |
| Runtime validation | In-process C# vs subprocess validate_content.py? |
In-process C# mirroring scripts/validate_content.py (Draft 2020-12 + Slice 1 item gate). Same rationale as NEO-34 (no Python on server images; shared constants commented against Python). |
Adopted — NEO-34 precedent; no separate question needed. |
| Options location | New ItemCatalogOptions vs extend ContentPathsOptions? |
Extend ContentPathsOptions with ItemsDirectory / ItemDefSchemaPath (skills + mastery already share Content section). |
Adopted |
| Lookup API on NEO-51 | How does “registry resolves ItemDef by id” land without NEO-52? |
ItemDefinitionCatalog.ById (read-only map) + optional TryGetItem(string id, out ItemDefRow row) on the catalog type; no IItemDefinitionRegistry interface yet. |
Adopted |
Goal, scope, and out-of-scope
Goal: On host startup, load all content/items/*_items.json catalogs (validated in CI per NEO-50), build an in-memory ItemDefinitionCatalog, log item count and resolved paths, and refuse to listen when any file is missing, malformed, has duplicate id, or fails the prototype Slice 1 gate. Mirror E2.M1 / NEO-34 skill-catalog behavior.
In scope (from Linear + E3M3-02):
- Loader + catalog under
server/NeonSprawl.Server/Game/Items/. - Configurable items directory and schema path (auto-discovery + overrides).
- Fail-fast semantics aligned with
scripts/validate_content.pyitem validation (schemaVersion, row schema, duplicateid, frozen six ids,prototypeRolecoverage, equip_stub →equipment+stackMax1). - DI registration of
ItemDefinitionCatalogsingleton; eager resolve inProgram.csbeforeRun(). - Unit + host startup tests (AAA).
Out of scope (from Linear):
- Per-player inventory, HTTP (NEO-53+).
IItemDefinitionRegistry/ItemDefinitionRegistry(NEO-52).
Acceptance criteria checklist
- Server refuses boot when item catalog invalid (missing dir, no
*_items.json, bad JSON,schemaVersion≠ 1, schema violation, duplicateid, Slice 1 gate failure). ItemDefinitionCatalogresolves prototype rows byid(e.g.scrap_metal_bulk,prototype_armor_shell).- Unit tests (AAA): loader happy path + failure modes; host resolves catalog on success; host fails startup on invalid catalog directory.
- Success log includes item count, catalog directory, and file count (Information).
Technical approach
Game/Items/loader —ItemDefinitionCatalogLoader.Load(itemsDirectory, schemaPath, logger):- Enumerate
*_items.json(top-level only), ordered by path. - Per file: parse JSON; require
schemaVersion == 1and top-levelitemsarray. - Per row: validate with
item-def.schema.jsonvia existing JsonSchema.Net; on clean rows, trackid,prototypeRole,inventorySlotKind,stackMaxfor gates; buildItemDefRowdictionary. - Reject duplicate
idacross files with paths in the error message. - Run
PrototypeSlice1ItemCatalogRules.TryGetSlice1GateError(mirrorPROTOTYPE_SLICE1_ITEM_IDS,PROTOTYPE_SLICE1_ITEM_ROLES, equip_stub rules invalidate_content.py). - Throw
InvalidOperationExceptionwith aggregated, sorted messages prefixedItem catalog validation failed:.
- Enumerate
ItemDefinitionCatalog— immutable snapshot:ItemsDirectory,ById,DistinctItemCount,CatalogJsonFileCount;TryGetItem(id, out ItemDefRow?)for lookups.ItemCatalogPathResolution—TryDiscoverItemsDirectory,ResolveItemsDirectory,ResolveItemDefSchemaPath(parallel to skill path resolution: parentschemas/item-def.schema.jsonwhen schema path unset).ItemCatalogServiceCollectionExtensions.AddItemDefinitionCatalog— bindContentPathsOptions; registerItemDefinitionCatalogsingleton factory (load on first resolve / same pattern as skills).Program.cs—AddItemDefinitionCatalog(configuration); afterBuild(),GetRequiredService<ItemDefinitionCatalog>()before routes/Run().- Tests — temp-dir fixtures copying repo
item-def.schema.json; valid six-item JSON matching prototype_items.json; negative cases for each failure class;InMemoryWebApplicationFactorysetsContent:ItemsDirectoryto discovered repo path; host failure uses empty temp dir like skills test.
Files to add
| Path | Purpose |
|---|---|
server/NeonSprawl.Server/Game/Items/ItemDefRow.cs |
Load-time row DTO (Id, DisplayName, PrototypeRole, StackMax, InventorySlotKind, optional forward-compat fields). |
server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalog.cs |
In-memory catalog + ById + TryGetItem. |
server/NeonSprawl.Server/Game/Items/ItemDefinitionCatalogLoader.cs |
Disk I/O, schema validation, Slice 1 gate, logging. |
server/NeonSprawl.Server/Game/Items/ItemCatalogPathResolution.cs |
Directory/schema discovery and config resolution. |
server/NeonSprawl.Server/Game/Items/PrototypeSlice1ItemCatalogRules.cs |
Frozen six ids / six roles + equip_stub slot rules (sync comment to Python). |
server/NeonSprawl.Server/Game/Items/ItemCatalogServiceCollectionExtensions.cs |
AddItemDefinitionCatalog DI registration. |
server/NeonSprawl.Server.Tests/Game/Items/ItemCatalogTestPaths.cs |
Repo content/items + schema discovery for tests. |
server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionCatalogLoaderTests.cs |
AAA loader + host startup tests (mirror SkillDefinitionCatalogLoaderTests). |
Files to modify
| Path | Rationale |
|---|---|
server/NeonSprawl.Server/Game/Skills/ContentPathsOptions.cs |
Add ItemsDirectory, ItemDefSchemaPath under existing Content section. |
server/NeonSprawl.Server/Program.cs |
Register item catalog; eager-resolve after build. |
server/NeonSprawl.Server/appsettings.json |
Optional empty Content:ItemsDirectory / ItemDefSchemaPath keys for override documentation. |
server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs |
Pin Content:ItemsDirectory to repo catalog so existing tests keep booting. |
server/README.md |
New Item catalog (content/items, NEO-51) section (config keys, discovery, fail-fast). |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E3.M3 row — note NEO-51 server load in progress / landed when done. |
Tests
| Test file | What it covers |
|---|---|
server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionCatalogLoaderTests.cs |
Unit: valid six-item prototype catalog loads; items not array; schema violation; duplicate id across files; wrong/missing Slice 1 ids; wrong prototypeRole set; equip_stub not equipment or stackMax ≠ 1; schemaVersion ≠ 1. Host: InMemoryWebApplicationFactory + /health + DI ItemDefinitionCatalog count 6; WebApplicationFactory with empty items dir fails startup with actionable message. |
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
Schema path when only ItemsDirectory is set |
Resolve {itemsDir}/../schemas/item-def.schema.json (same rule as skills/mastery). Document in README + ItemCatalogPathResolution. |
adopted |
| Test cwd | InMemoryWebApplicationFactory must set Content:ItemsDirectory explicitly (do not rely on discovery alone in CI). |
adopted |
| Constants drift vs Python | Comment on PrototypeSlice1ItemCatalogRules pointing at scripts/validate_content.py PROTOTYPE_SLICE1_ITEM_IDS / _prototype_slice1_item_gate. |
adopted |
None blocking beyond the above.