NEO-77: Address code review — Bruno folder, E5M1 gate test, docs.

Add ability-catalog folder.bru, extra-id E5M1 gate AAA test, dependency
register NEO-77 note, and strike through resolved review suggestions.
pull/111/head
VinPropane 2026-05-24 21:44:02 -04:00
parent 42ea2598c3
commit fd2ffee089
5 changed files with 26 additions and 6 deletions

View File

@ -0,0 +1,3 @@
meta {
name: ability-catalog
}

View File

@ -76,7 +76,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen
| E5.M3 | EncounterAndRewardTables | E5.M1, E3.M3, E7.M2 | EncounterDef, RewardTable, EncounterCompleteEvent | Prototype | Planned |
| E5.M4 | GroupCombatScaling | E5.M3, E8.M1 | ScalingProfile, PartySizeModifier, CombatDifficultyBand | Pre-production | Planned |
**E5.M1 note:** Epic 5 **Slice 1** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); label **`E5.M1`**. Gig XP on defeat: **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1_CombatRulesEngine.md](E5_M1_CombatRulesEngine.md). Builds on E1.M4 cast funnel (NEO-28/31/32); extends **`POST …/ability-cast`** with **`CombatResolution`**. **NEO-76 landed:** frozen four-ability catalog + CI ([NEO-76 plan](../../plans/NEO-76-implementation-plan.md)).
**E5.M1 note:** Epic 5 **Slice 1** backlog in Linear ([Epic 5 — PvE Combat and Encounter Content](https://linear.app/neon-sprawl/project/epic-5-pve-combat-and-encounter-content-cf3c94c3-5346-40e0-8aaf-281e846f2846)): [NEO-76](https://linear.app/neon-sprawl/issue/NEO-76) → [NEO-86](https://linear.app/neon-sprawl/issue/NEO-86); label **`E5.M1`**. Gig XP on defeat: **E5M1-10** [NEO-44](https://linear.app/neon-sprawl/issue/NEO-44). See [E5M1-prototype-backlog.md](../../plans/E5M1-prototype-backlog.md), [E5_M1_CombatRulesEngine.md](E5_M1_CombatRulesEngine.md). Builds on E1.M4 cast funnel (NEO-28/31/32); extends **`POST …/ability-cast`** with **`CombatResolution`**. **NEO-76 landed:** frozen four-ability catalog + CI ([NEO-76 plan](../../plans/NEO-76-implementation-plan.md)). **NEO-77 landed:** fail-fast server load of `content/abilities/*_abilities.json` ([NEO-77 plan](../../plans/NEO-77-implementation-plan.md)).
### Epic 6 — PvP Security

View File

@ -119,7 +119,7 @@
| Test file | What it covers |
|-----------|----------------|
| `server/NeonSprawl.Server.Tests/Game/Combat/AbilityDefinitionCatalogLoaderTests.cs` | **Unit:** valid four-ability prototype catalog loads; `abilities` not array; schema violation (negative `baseDamage`, zero `cooldownSeconds`, empty `displayName`); duplicate `id` across files; wrong/missing E5M1 ids; `schemaVersion` ≠ 1; missing dir/schema; no `*_abilities.json` files; invalid JSON. **Host:** `InMemoryWebApplicationFactory` + `/health` + DI `AbilityDefinitionCatalog` count 4; `WebApplicationFactory` with empty abilities dir fails startup with actionable message. |
| `server/NeonSprawl.Server.Tests/Game/Combat/AbilityDefinitionCatalogLoaderTests.cs` | **Unit:** valid four-ability prototype catalog loads; `abilities` not array; schema violation (negative `baseDamage`, zero `cooldownSeconds`, empty `displayName`); duplicate `id` across files; E5M1 gate failure (missing id + extra/wrong id); `schemaVersion` ≠ 1; missing dir/schema; no `*_abilities.json` files; invalid JSON. **Host:** `InMemoryWebApplicationFactory` + `/health` + DI `AbilityDefinitionCatalog` count 4; `WebApplicationFactory` with empty abilities dir fails startup with actionable message. |
**Manual QA:** Skipped — no player-visible HTTP; acceptance is fully covered by AAA loader/host tests above.

View File

@ -20,7 +20,7 @@ NEO-77 delivers **E5M1-02**: fail-fast startup load of `content/abilities/*_abil
| [`docs/plans/E5M1-prototype-backlog.md`](../plans/E5M1-prototype-backlog.md) · **E5M1-02** | **Matches** — AC checkboxes + landed note. |
| [`docs/decomposition/modules/E5_M1_CombatRulesEngine.md`](../decomposition/modules/E5_M1_CombatRulesEngine.md) | **Matches** — Status line and freeze rules reference NEO-77 server load + NEO-79 registry deferral. |
| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E5.M1 row updated with NEO-77 landed note; next backlog item NEO-79. |
| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Partially matches** — E5.M1 table **Status** **In Progress** is correct; **E5.M1 note** still cites NEO-76 only (optional bump to mention NEO-77 server load, same pattern as postNEO-66 E3.M2 note lag). |
| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E5.M1 table **Status** **In Progress** is correct; **E5.M1 note** cites NEO-76 + NEO-77 server load. |
| [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **N/A** — server startup catalog only; no client mutation or combat resolution. |
| [`scripts/validate_content.py`](../../scripts/validate_content.py) | **Matches** — C# loader mirrors `_validate_ability_catalogs` + `_prototype_e5m1_ability_gate`; sync comment on `PrototypeE5M1AbilityCatalogRules.ExpectedAbilityIds`. |
| [`server/README.md`](../../server/README.md) | **Matches** — Ability catalog section (config keys, discovery, fail-fast, E5M1 parity). |
@ -35,9 +35,9 @@ None.
## Suggestions
1. **Add `folder.bru` under `bruno/neon-sprawl-server/ability-catalog/`** — Other catalog smoke folders (`item-catalog`, `recipe-catalog`, `resource-node-catalog`, `mastery-catalog`) include a `folder.bru` meta file; ability-catalog currently has only the health request. Add for Bruno collection parity.
1. ~~**Add `folder.bru` under `bruno/neon-sprawl-server/ability-catalog/`** — Other catalog smoke folders (`item-catalog`, `recipe-catalog`, `resource-node-catalog`, `mastery-catalog`) include a `folder.bru` meta file; ability-catalog currently has only the health request. Add for Bruno collection parity.~~ **Done.** Added `bruno/neon-sprawl-server/ability-catalog/folder.bru`.
2. **E5M1 gate test — extra/wrong id**`Load_ShouldThrow_WhenE5M1GateFails` removes one ability (missing id). Plan test table also lists wrong roster; consider an AAA case that swaps in an unknown id (e.g. `prototype_extra`) to cover the “got […]” branch, not only “missing”.
2. ~~**E5M1 gate test — extra/wrong id** — `Load_ShouldThrow_WhenE5M1GateFails` removes one ability (missing id). Plan test table also lists wrong roster; consider an AAA case that swaps in an unknown id (e.g. `prototype_extra`) to cover the “got […]” branch, not only “missing”.~~ **Done.** Renamed missing-id test to `Load_ShouldThrow_WhenE5M1GateFailsWithMissingId`; added `Load_ShouldThrow_WhenE5M1GateFailsWithExtraId`. Plan test table updated.
## Nits

View File

@ -161,7 +161,7 @@ public class AbilityDefinitionCatalogLoaderTests
}
[Fact]
public void Load_ShouldThrow_WhenE5M1GateFails()
public void Load_ShouldThrow_WhenE5M1GateFailsWithMissingId()
{
// Arrange
var (_, abilitiesDir, schemaPath) = CreateTempContentLayout();
@ -178,6 +178,23 @@ public class AbilityDefinitionCatalogLoaderTests
Assert.Contains("prototype E5M1 expects exactly ability ids", ioe.Message, StringComparison.Ordinal);
}
[Fact]
public void Load_ShouldThrow_WhenE5M1GateFailsWithExtraId()
{
// Arrange
var (_, abilitiesDir, schemaPath) = CreateTempContentLayout();
var root = JsonNode.Parse(ValidPrototypeCatalogJson) as JsonObject
?? throw new InvalidOperationException("expected object root");
GetAbilityRow(root, "prototype_burst")["id"] = "prototype_extra";
WriteCatalog(abilitiesDir, root.ToJsonString());
// Act
var ex = Record.Exception(() => LoadCatalog(abilitiesDir, schemaPath));
// Assert
var ioe = Assert.IsType<InvalidOperationException>(ex);
Assert.Contains("prototype E5M1 expects exactly ability ids", ioe.Message, StringComparison.Ordinal);
Assert.Contains("prototype_extra", ioe.Message, StringComparison.Ordinal);
}
[Fact]
public void Load_ShouldThrow_WhenBaseDamageIsNegative()
{