14 KiB
NEO-88 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-88 |
| Title | E5M2-02: Server NPC behavior catalog load (fail-fast) |
| Linear | https://linear.app/neon-sprawl/issue/NEO-88/e5m2-02-server-npc-behavior-catalog-load-fail-fast |
| Module | E5.M2 — NpcAiAndBehaviorProfiles · Epic 5 Slice 2 · backlog E5M2-02 |
| Branch | NEO-88-e5m2-server-npc-behavior-catalog-load |
| Precursor | NEO-87 — frozen three-behavior catalog + CI gates (Done on main) |
| Pattern | NEO-77 — fail-fast catalog loader + ContentPathsOptions + eager Program.cs resolve; strict split before registry ticket |
| Blocks | NEO-89 — INpcBehaviorDefinitionRegistry + DI (E5M2-03) |
Kickoff clarifications
| Topic | Question | Agent recommendation | Answer |
|---|---|---|---|
| NEO-88 vs NEO-89 (DI scope) | Ship INpcBehaviorDefinitionRegistry on this story, or only load + NpcBehaviorDefinitionCatalog? |
Strict split — NEO-88: loader + NpcBehaviorDefinitionCatalog + eager fail-fast boot; NEO-89 adds injectable INpcBehaviorDefinitionRegistry (E5M2-03). Rationale: E5M2 backlog separates E5M2-02/03; mirrors NEO-77/NEO-79. |
User: NpcBehaviorDefinitionCatalog singleton only (kickoff). |
| Startup failure tests | Loader unit tests only, or also WebApplicationFactory host boot failure? |
Both — mirror AbilityDefinitionCatalogLoaderTests (NEO-77). |
User: loader AAA + host boot tests (kickoff). |
| Runtime validation | In-process C# vs subprocess validate_content.py? |
In-process C# mirroring scripts/validate_content.py (Draft 2020-12 + E5M2 three-id gate + leash > aggro numeric gate). Rationale: NEO-77 precedent; no cross-catalog refs in Slice 2. |
Adopted — NEO-77 precedent; no separate question. |
| Client counterpart | Godot issue for this story? | None — server-only load; player-visible work starts at E5M2-04+ / NEO-90 / NEO-98. | Adopted (E5M2-02 out of scope). |
Goal, scope, and out-of-scope
Goal: On host startup, load all content/npc-behaviors/*_npc_behaviors.json catalogs (validated in CI per NEO-87), build an in-memory NpcBehaviorDefinitionCatalog, and refuse to listen when any file is missing, malformed, has duplicate id, fails the prototype E5M2 three-id gate, or violates the leash > aggro numeric invariant.
In scope (from Linear + E5M2-02):
- Loader + catalog types under
server/NeonSprawl.Server/Game/Npc/. - Configurable npc-behaviors directory and schema path (
ContentPathsOptionsextension). - CI-parity validation:
schemaVersion, row schema (npc-behavior-def), duplicateid, E5M2 three-id allowlist gate,leashRadius > aggroRadiusper row. - DI registration of
NpcBehaviorDefinitionCatalogsingleton; eager resolve inProgram.cs. - Unit + host startup tests (AAA).
server/README.mdsection.
Out of scope (from Linear):
INpcBehaviorDefinitionRegistry/ DI registry (NEO-89, E5M2-03).- HTTP projection (NEO-90, E5M2-04), NPC runtime tick, dummy migration (E5M2-05+).
Acceptance criteria checklist
- Server refuses boot when NPC behavior catalog invalid (missing dir, no
*_npc_behaviors.json, bad JSON,schemaVersion≠ 1, schema violation, duplicateid, E5M2 gate failure, leash ≤ aggro). NpcBehaviorDefinitionCatalogresolves prototype rows by behaviorid(e.g.prototype_melee_pressure) with all schema fields on the row DTO.- Unit tests (AAA): loader happy path + failure modes; host resolves catalog on success; host fails startup on invalid catalog directory.
- Success log includes behavior count, catalog directory, and file count (Information).
Implementation reconciliation (shipped)
- Loader:
NpcBehaviorDefinitionCatalogLoader— CI-parity validation via JsonSchema.Net + E5M2 id gate + leash > aggro numeric gate. - Catalog:
NpcBehaviorDefinitionCatalog+NpcBehaviorDefRow—TryGetBehaviorlookup by id. - Gate rules:
PrototypeE5M2NpcBehaviorCatalogRules— sync comment toscripts/validate_content.pyPROTOTYPE_E5M2_NPC_BEHAVIOR_IDS. - DI / startup:
NpcBehaviorCatalogServiceCollectionExtensions+ eager resolve inProgram.cs. - Tests:
NpcBehaviorDefinitionCatalogLoaderTests— 16 AAA cases (loader + host). - Docs:
server/README.mdNPC behavior catalog section; E5.M2 module doc + alignment register + E5M2 backlog E5M2-02 checkboxes. - Unchanged: No
INpcBehaviorDefinitionRegistry— migration deferred to NEO-89.
Technical approach
-
Game/Npc/row DTO —NpcBehaviorDefRow(Id,DisplayName,ArchetypeKind,MaxHp,AggroRadius,LeashRadius,TelegraphWindupSeconds,AttackDamage,AttackCooldownSeconds).ArchetypeKindstored asstringmatching schema enum values (mirrorAbilityDefRow.AbilityKindpattern). -
NpcBehaviorDefinitionCatalogLoader.Load(npcBehaviorsDirectory, schemaPath, logger)— single-pass load mirroringvalidate_content.py_validate_npc_behavior_catalogs+_prototype_e5m2_npc_behavior_gate+_prototype_e5m2_npc_behavior_numeric_gate:- Enumerate
*_npc_behaviors.json(top-level only), ordered by path. - Build JsonSchema.Net evaluator from
npc-behavior-def.schema.json. - Per file: parse JSON; require
schemaVersion == 1and top-levelnpcBehaviorsarray. - Per row: validate against schema; on clean rows track
id; buildNpcBehaviorDefRow; reject duplicateidacross files. - Run
PrototypeE5M2NpcBehaviorCatalogRules.TryGetE5M2GateError(mirrorPROTOTYPE_E5M2_NPC_BEHAVIOR_IDS/_prototype_e5m2_npc_behavior_gate). - Run
PrototypeE5M2NpcBehaviorCatalogRules.TryGetNumericGateError(mirror_prototype_e5m2_npc_behavior_numeric_gate:leashRadius > aggroRadius). - Throw
InvalidOperationExceptionwith aggregated, sorted messages prefixedNPC behavior catalog validation failed:.
- Enumerate
-
NpcBehaviorDefinitionCatalog— immutable snapshot:NpcBehaviorsDirectory,ById,DistinctBehaviorCount,CatalogJsonFileCount;TryGetBehavior(id, out NpcBehaviorDefRow?)for lookups. -
NpcBehaviorCatalogPathResolution—TryDiscoverNpcBehaviorsDirectory,ResolveNpcBehaviorsDirectory,ResolveNpcBehaviorDefSchemaPath(parentcontent/schemas/layout when unset). -
PrototypeE5M2NpcBehaviorCatalogRules— frozen three behavior ids; sync comment toscripts/validate_content.pyPROTOTYPE_E5M2_NPC_BEHAVIOR_IDS. Ids:prototype_melee_pressure,prototype_ranged_control,prototype_elite_mini_boss(same set as E5.M2 freeze table). -
NpcBehaviorCatalogServiceCollectionExtensions.AddNpcBehaviorDefinitionCatalog— bindContentPathsOptions; registerNpcBehaviorDefinitionCatalogsingleton factory that resolves paths and calls loader. No dependency on ability/item catalogs (Slice 2 behavior rows have no cross-ref fields). -
Program.cs—AddNpcBehaviorDefinitionCatalog(configuration)alongside existing catalog registrations; afterBuild(),GetRequiredService<NpcBehaviorDefinitionCatalog>()with other eager catalog resolves. -
Tests — temp-dir fixtures copying repo
npc-behavior-def.schema.json; valid three-behavior JSON matching prototype_npc_behaviors.json; negative cases (duplicate id, wrong/missing E5M2 roster, bad numeric fields, emptydisplayName,schemaVersion≠ 1, missing dir/schema, no*_npc_behaviors.json, invalid JSON,leashRadius <= aggroRadius);InMemoryWebApplicationFactoryhost test +WebApplicationFactorywith empty npc-behaviors dir fails startup. -
Test factories — pin
Content:NpcBehaviorsDirectoryinInMemoryWebApplicationFactoryand otherWebApplicationFactorysubclasses that already pin recipes/items/abilities (same pattern as NEO-77).
Files to add
| Path | Purpose |
|---|---|
server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefRow.cs |
Load-time NpcBehaviorDef row DTO. |
server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalog.cs |
In-memory catalog + TryGetBehavior. |
server/NeonSprawl.Server/Game/Npc/NpcBehaviorDefinitionCatalogLoader.cs |
Disk I/O, schema validation, E5M2 gates. |
server/NeonSprawl.Server/Game/Npc/NpcBehaviorCatalogPathResolution.cs |
Directory/schema discovery and config resolution. |
server/NeonSprawl.Server/Game/Npc/PrototypeE5M2NpcBehaviorCatalogRules.cs |
Frozen three ids + numeric gate (sync comment to Python). |
server/NeonSprawl.Server/Game/Npc/NpcBehaviorCatalogServiceCollectionExtensions.cs |
AddNpcBehaviorDefinitionCatalog DI registration. |
server/NeonSprawl.Server.Tests/Game/Npc/NpcBehaviorCatalogTestPaths.cs |
Repo content/npc-behaviors + schema discovery for tests. |
server/NeonSprawl.Server.Tests/Game/Npc/NpcBehaviorDefinitionCatalogLoaderTests.cs |
AAA loader + host startup tests. |
docs/plans/NEO-88-implementation-plan.md |
This plan. |
Files to modify
| Path | Rationale |
|---|---|
server/NeonSprawl.Server/Game/Skills/ContentPathsOptions.cs |
Add NpcBehaviorsDirectory, NpcBehaviorDefSchemaPath under Content section. |
server/NeonSprawl.Server/Program.cs |
Register and eagerly resolve NpcBehaviorDefinitionCatalog at startup. |
server/NeonSprawl.Server/appsettings.json |
Optional empty Content:NpcBehaviorsDirectory / schema path keys for override documentation (mirror other catalogs). |
server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs |
Pin Content:NpcBehaviorsDirectory to discovered repo path so existing tests keep booting. |
server/NeonSprawl.Server.Tests/Game/PositionState/PostgresWebApplicationFactory.cs |
Pin npc-behaviors directory (same as recipes/items/abilities). |
server/NeonSprawl.Server.Tests/Game/Interaction/SalvageActivityDeniedRegistryWebApplicationFactory.cs |
Pin npc-behaviors directory. |
server/NeonSprawl.Server.Tests/Game/Skills/RefineActivityDeniedRegistryWebApplicationFactory.cs |
Pin npc-behaviors directory. |
server/NeonSprawl.Server.Tests/Game/Skills/MissionRewardDeniedRegistryWebApplicationFactory.cs |
Pin npc-behaviors directory. |
server/README.md |
New NPC behavior catalog (content/npc-behaviors, NEO-88) section (config keys, discovery, fail-fast, E5M2 parity with CI). |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E5.M2 row — note NEO-88 server load when implementation completes. |
docs/decomposition/modules/E5_M2_NpcAiAndBehaviorProfiles.md |
Status line — E5M2-02 server load landed when complete. |
docs/plans/E5M2-prototype-backlog.md |
E5M2-02 checkboxes when implementation completes. |
Tests
| Test file | What it covers |
|---|---|
server/NeonSprawl.Server.Tests/Game/Npc/NpcBehaviorDefinitionCatalogLoaderTests.cs |
Unit: valid three-behavior prototype catalog loads; npcBehaviors not array; schema violation (zero aggroRadius, empty displayName, invalid maxHp); duplicate id across files; E5M2 gate failure (missing id + extra/wrong id); numeric gate failure (leashRadius <= aggroRadius); schemaVersion ≠ 1; missing dir/schema; no *_npc_behaviors.json files; invalid JSON. Host: InMemoryWebApplicationFactory + /health + DI NpcBehaviorDefinitionCatalog count 3; WebApplicationFactory with empty npc-behaviors dir fails startup with actionable message. |
Manual QA: Skipped — no player-visible HTTP; acceptance is fully covered by AAA loader/host tests above.
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
| Constants drift vs Python | Comment on PrototypeE5M2NpcBehaviorCatalogRules pointing at scripts/validate_content.py PROTOTYPE_E5M2_NPC_BEHAVIOR_IDS / gate helpers. |
adopted |
| Test cwd | InMemoryWebApplicationFactory must set Content:NpcBehaviorsDirectory explicitly (do not rely on discovery alone in CI). |
adopted |
| Registry consumers | Runtime NPC logic uses INpcBehaviorDefinitionRegistry in NEO-89+; this story only exposes NpcBehaviorDefinitionCatalog for boot + direct test lookup. |
adopted |
| Alpha/beta dummies still active | Expected — catalog lands before E5M2-05 migrates combat targets to NPC instance ids. | adopted |
| Numeric gate not in JSON Schema | Enforce in C# gate class (same as Python _prototype_e5m2_npc_behavior_numeric_gate); schema only requires strictly positive radii. |
adopted |
Decisions (kickoff)
NpcBehaviorDefinitionCatalogsingleton only — deferINpcBehaviorDefinitionRegistryto NEO-89 (user confirmed kickoff).- In-process validation + loader and host boot tests per NEO-77 precedent.
- No cross-catalog checks — behavior rows have no ability/item refs in Slice 2; loader has no catalog dependencies.