diff --git a/bruno/neon-sprawl-server/item-definitions/Get item definitions.bru b/bruno/neon-sprawl-server/item-definitions/Get item definitions.bru index e15f6af..1ee5110 100644 --- a/bruno/neon-sprawl-server/item-definitions/Get item definitions.bru +++ b/bruno/neon-sprawl-server/item-definitions/Get item definitions.bru @@ -23,10 +23,24 @@ tests { test("items are ascending by id (ordinal)", function () { const body = res.getBody(); const ids = body.items.map((x) => x.id); + // Match server StringComparer.Ordinal (prototype ids are ASCII snake_case). const sorted = [...ids].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); expect(ids).to.eql(sorted); }); + test("frozen prototype six matches registry id order", function () { + const body = res.getBody(); + const ids = body.items.map((x) => x.id); + expect(ids).to.eql([ + "contract_handoff_token", + "field_stim_mk0", + "prototype_armor_shell", + "refined_plate_stock", + "scrap_metal_bulk", + "survey_drone_kit", + ]); + }); + test("frozen prototype six is present", function () { const body = res.getBody(); const ids = new Set(body.items.map((x) => x.id)); diff --git a/docs/decomposition/modules/module_dependency_register.md b/docs/decomposition/modules/module_dependency_register.md index 72f6c5c..6204f1e 100644 --- a/docs/decomposition/modules/module_dependency_register.md +++ b/docs/decomposition/modules/module_dependency_register.md @@ -48,7 +48,7 @@ Fleshed-out scope, contracts, and integration notes live in **per-module documen | E3.M4 | SinkAndDurabilityLifecycle | E3.M3, E8.M3 | DurabilityState, ItemSinkEvent, RepairCostRule | Pre-production | Planned | | E3.M5 | EconomyBalancePolicy | E3.M4, E9.M2 | EconomyPolicy, PriceBandRule, FaucetSinkRatio | Pre-production | Planned | -**E3.M3 note:** Epic 3 **Slice 1** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) → [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56); label **`E3.M3`**. See [E3M3-prototype-backlog.md](../../plans/E3M3-prototype-backlog.md), [E3_M3_ItemizationAndInventorySchema.md](E3_M3_ItemizationAndInventorySchema.md). **NEO-50** (content + CI), **NEO-51** (server fail-fast load), and **NEO-52** (`IItemDefinitionRegistry` + DI) moved the register row to **In Progress**; later slices update the alignment table as they land. +**E3.M3 note:** Epic 3 **Slice 1** backlog in Linear ([Epic 3 — Crafting, Gathering, and Itemization Economy](https://linear.app/neon-sprawl/project/epic-3-crafting-gathering-and-itemization-economy-65785ed05bc2)): [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) → [NEO-56](https://linear.app/neon-sprawl/issue/NEO-56); label **`E3.M3`**. See [E3M3-prototype-backlog.md](../../plans/E3M3-prototype-backlog.md), [E3_M3_ItemizationAndInventorySchema.md](E3_M3_ItemizationAndInventorySchema.md). **NEO-50** (content + CI), **NEO-51** (server fail-fast load), **NEO-52** (`IItemDefinitionRegistry` + DI), and **NEO-53** (`GET /game/world/item-definitions`) moved the register row to **In Progress**; later slices update the alignment table as they land. ### Epic 4 — World Topology diff --git a/docs/reviews/2026-05-23-NEO-53.md b/docs/reviews/2026-05-23-NEO-53.md index 21b197e..6f17f6a 100644 --- a/docs/reviews/2026-05-23-NEO-53.md +++ b/docs/reviews/2026-05-23-NEO-53.md @@ -19,7 +19,7 @@ NEO-53 adds **`GET /game/world/item-definitions`**: a versioned read-only JSON p | [`docs/plans/NEO-53-implementation-plan.md`](../plans/NEO-53-implementation-plan.md) | **Matches** — route, five required row fields, registry-only injection, frozen six order, Bruno/manual QA/README, acceptance checklist complete. | | [`docs/decomposition/modules/E3_M3_ItemizationAndInventorySchema.md`](../decomposition/modules/E3_M3_ItemizationAndInventorySchema.md) | **Matches** — NEO-53 HTTP bullet under **Related implementation slices**; frozen roster table aligns with test/Bruno assertions. | | [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E3.M3 row notes NEO-53 landed with plan, manual QA, README, Bruno links. | -| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Partially matches** — E3.M3 **In Progress**; footnote still lists NEO-50–NEO-52 only (see Suggestion 1). | +| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E3.M3 **In Progress**; footnote includes NEO-53. | | [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **Matches** — read-only world catalog; server remains source of truth; no client-trusted mutation. | | [`docs/manual-qa/NEO-53.md`](../manual-qa/NEO-53.md) | **Matches** — curl, schema v1, six ids, spot-checks aligned with automated tests. | | [`server/README.md`](../../server/README.md) | **Matches** — Item definitions section with GET path and curl example. | @@ -32,15 +32,15 @@ None. ## Suggestions -1. **Dependency register footnote (optional)** — Extend the **E3.M3 note** in [`module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) to mention **NEO-53** alongside NEO-50–NEO-52 so the register matches the alignment table (same pattern as NEO-52 review). +1. ~~**Dependency register footnote (optional)** — Extend the **E3.M3 note** in [`module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) to mention **NEO-53** alongside NEO-50–NEO-52 so the register matches the alignment table (same pattern as NEO-52 review).~~ **Done.** -2. **Bruno — exact id order (optional)** — C# integration test asserts the full frozen six sequence; Bruno only checks ascending sort plus set membership. Adding an explicit `ids` array assertion (or mirroring the C# frozen list) would catch registry ordering regressions in manual runs without running `dotnet test`. +2. ~~**Bruno — exact id order (optional)** — C# integration test asserts the full frozen six sequence; Bruno only checks ascending sort plus set membership. Adding an explicit `ids` array assertion (or mirroring the C# frozen list) would catch registry ordering regressions in manual runs without running `dotnet test`.~~ **Done.** — `frozen prototype six matches registry id order` test in [`Get item definitions.bru`](../../bruno/neon-sprawl-server/item-definitions/Get%20item%20definitions.bru). ## Nits -- Nit: **`ItemDefinitionsWorldApiTests`** — `[Fact]` and method body use **2-space** indent while the class uses **4-space**; [`SkillDefinitionsWorldApiTests.cs`](../../server/NeonSprawl.Server.Tests/Game/Skills/SkillDefinitionsWorldApiTests.cs) is consistently 4-space. Format-only tidy. +- ~~Nit: **`ItemDefinitionsWorldApiTests`** — `[Fact]` and method body use **2-space** indent while the class uses **4-space**; [`SkillDefinitionsWorldApiTests.cs`](../../server/NeonSprawl.Server.Tests/Game/Skills/SkillDefinitionsWorldApiTests.cs) is consistently 4-space. Format-only tidy.~~ **Done.** -- Nit: Bruno **`items are ascending by id`** block omits the **StringComparer.Ordinal** comment present in [`Get skill definitions.bru`](../../bruno/neon-sprawl-server/skill-definitions/Get%20skill%20definitions.bru); harmless for ASCII snake_case ids but copy the comment for parity if touching the file. +- ~~Nit: Bruno **`items are ascending by id`** block omits the **StringComparer.Ordinal** comment present in [`Get skill definitions.bru`](../../bruno/neon-sprawl-server/skill-definitions/Get%20skill%20definitions.bru); harmless for ASCII snake_case ids but copy the comment for parity if touching the file.~~ **Done.** - Nit: **`ItemDefinitionsWorldApi`** allocates a `List` per request — same pattern as skills; fine at six rows. diff --git a/server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionsWorldApiTests.cs b/server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionsWorldApiTests.cs index 9db39e3..9f6946c 100644 --- a/server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionsWorldApiTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Items/ItemDefinitionsWorldApiTests.cs @@ -18,32 +18,32 @@ public class ItemDefinitionsWorldApiTests "survey_drone_kit", ]; - [Fact] - public async Task GetItemDefinitions_ShouldReturnSchemaV1_WithFrozenSixInIdOrder() - { - // Arrange - await using var factory = new InMemoryWebApplicationFactory(); - var client = factory.CreateClient(); - // Act - var response = await client.GetAsync("/game/world/item-definitions"); - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadFromJsonAsync(); - Assert.NotNull(body); - Assert.Equal(ItemDefinitionsListResponse.CurrentSchemaVersion, body!.SchemaVersion); - Assert.NotNull(body.Items); - var ids = body.Items.Select(static i => i.Id).ToList(); - Assert.Equal(FrozenSixInIdOrder, ids); + [Fact] + public async Task GetItemDefinitions_ShouldReturnSchemaV1_WithFrozenSixInIdOrder() + { + // Arrange + await using var factory = new InMemoryWebApplicationFactory(); + var client = factory.CreateClient(); + // Act + var response = await client.GetAsync("/game/world/item-definitions"); + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(body); + Assert.Equal(ItemDefinitionsListResponse.CurrentSchemaVersion, body!.SchemaVersion); + Assert.NotNull(body.Items); + var ids = body.Items.Select(static i => i.Id).ToList(); + Assert.Equal(FrozenSixInIdOrder, ids); - var scrap = body.Items.Single(i => i.Id == "scrap_metal_bulk"); - Assert.Equal("Scrap Metal (Bulk)", scrap.DisplayName); - Assert.Equal("material", scrap.PrototypeRole); - Assert.Equal(999, scrap.StackMax); - Assert.Equal("bag", scrap.InventorySlotKind); + var scrap = body.Items.Single(i => i.Id == "scrap_metal_bulk"); + Assert.Equal("Scrap Metal (Bulk)", scrap.DisplayName); + Assert.Equal("material", scrap.PrototypeRole); + Assert.Equal(999, scrap.StackMax); + Assert.Equal("bag", scrap.InventorySlotKind); - var armor = body.Items.Single(i => i.Id == "prototype_armor_shell"); - Assert.Equal("equip_stub", armor.PrototypeRole); - Assert.Equal(1, armor.StackMax); - Assert.Equal("equipment", armor.InventorySlotKind); - } + var armor = body.Items.Single(i => i.Id == "prototype_armor_shell"); + Assert.Equal("equip_stub", armor.PrototypeRole); + Assert.Equal(1, armor.StackMax); + Assert.Equal("equipment", armor.InventorySlotKind); + } }