diff --git a/.cursor/rules/csharp-style.md b/.cursor/rules/csharp-style.md index 1201727..335739c 100644 --- a/.cursor/rules/csharp-style.md +++ b/.cursor/rules/csharp-style.md @@ -81,6 +81,18 @@ return query.ToArray(); - **Keep braced `using (var …) { … }`** when the block has **multiple statements** (Arrange + Act + Assert inside one scope, seed loops, or any sequence that shares locals). - **Do not flatten** multi-statement blocks into `using var` at method scope when inner scopes reuse names like `store`, `registry`, or `scope` — that causes **CS0136** name clashes. Use distinct names (`seedStore`, `actScope`, `readStore`) or keep the braced block. +## Local `const` (RCS1118) + +- When a local is initialized from a **compile-time constant** (string literal, numeric literal, or `public const` field), declare it **`const`** instead of **`var`**. + +```csharp +const string questId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId; +``` + +## Inline declarations (IDE0018) + +- Prefer **inline initialization** over declare-then-assign when the analyzer suggests it, e.g. `var outcome = Operation(...)` and `TryGet(..., out var snapshot)` instead of separate upfront declarations. + ```csharp // Prefer — single disposable, single follow-on statement await using var conn = new NpgsqlConnection(cs); diff --git a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs index 11a386a..f8aaae4 100644 --- a/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Contracts/ContractOutcomePersistenceIntegrationTests.cs @@ -85,25 +85,23 @@ public sealed class ContractOutcomePersistenceIntegrationTests(PostgresIntegrati [], RecordedAt); - using (var scope = Factory.Services.CreateScope()) - { - var instanceStore = scope.ServiceProvider.GetRequiredService(); - var outcomeStore = scope.ServiceProvider.GetRequiredService(); - Assert.True(instanceStore.TryCreateActive( - PlayerId, - InstanceId, - TemplateId, - SeedBucket, - IssuedAt, - out _)); - Assert.True(outcomeStore.TryAppend(outcomeRow)); - // Act - var duplicateId = outcomeStore.TryAppend(outcomeRow with { IdempotencyKey = "other-key" }); - var duplicateKey = outcomeStore.TryAppend(outcomeRow with { Id = "outcome-row-pg-002" }); - // Assert - Assert.False(duplicateId); - Assert.False(duplicateKey); - } + using var scope = Factory.Services.CreateScope(); + var instanceStore = scope.ServiceProvider.GetRequiredService(); + var outcomeStore = scope.ServiceProvider.GetRequiredService(); + Assert.True(instanceStore.TryCreateActive( + PlayerId, + InstanceId, + TemplateId, + SeedBucket, + IssuedAt, + out _)); + Assert.True(outcomeStore.TryAppend(outcomeRow)); + // Act + var duplicateId = outcomeStore.TryAppend(outcomeRow with { IdempotencyKey = "other-key" }); + var duplicateKey = outcomeStore.TryAppend(outcomeRow with { Id = "outcome-row-pg-002" }); + // Assert + Assert.False(duplicateId); + Assert.False(duplicateKey); } [RequirePostgresFact] diff --git a/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs index 29f5ec6..06fbac7 100644 --- a/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Gathering/ResourceNodeInstancePersistenceIntegrationTests.cs @@ -57,14 +57,12 @@ public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresInte } // Act - ResourceNodeInstanceMutationOutcome denyOutcome; - ResourceNodeInstanceSnapshot persisted; await using var secondFactory = new PostgresWebApplicationFactory(); using var actScope = secondFactory.Services.CreateScope(); var actRegistry = actScope.ServiceProvider.GetRequiredService(); var actStore = actScope.ServiceProvider.GetRequiredService(); - denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, actRegistry, actStore); - actStore.TryGetRemainingGathers(AlphaId, out persisted); + var denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, actRegistry, actStore); + actStore.TryGetRemainingGathers(AlphaId, out var persisted); // Assert Assert.Equal(ResourceNodeInstanceMutationKind.Denied, denyOutcome.Kind); diff --git a/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresDockerHelper.cs b/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresDockerHelper.cs index 50373da..d4b9c3a 100644 --- a/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresDockerHelper.cs +++ b/server/NeonSprawl.Server.Tests/Game/PositionState/PostgresDockerHelper.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Threading; using Npgsql; namespace NeonSprawl.Server.Tests.Game.PositionState; @@ -13,7 +14,7 @@ namespace NeonSprawl.Server.Tests.Game.PositionState; /// internal static class PostgresDockerHelper { - private static readonly object StateLock = new(); + private static readonly Lock StateLock = new(); private static Task? _ensureTask; private static bool _composeStartedByUs; private static string? _repoRoot; diff --git a/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs b/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs index 60f43fa..3f7398b 100644 --- a/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs +++ b/server/NeonSprawl.Server.Tests/Game/Quests/PlayerQuestProgressPersistenceIntegrationTests.cs @@ -22,7 +22,7 @@ public sealed class PlayerQuestProgressPersistenceIntegrationTests(PostgresInteg // Arrange await ResetQuestProgressTableAsync(); const int concurrentCalls = 8; - var questId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId; + const string questId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId; // Act var results = await Task.WhenAll( @@ -93,7 +93,7 @@ public sealed class PlayerQuestProgressPersistenceIntegrationTests(PostgresInteg { // Arrange await ResetQuestProgressTableAsync(); - var questId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId; + const string questId = PrototypeE7M1QuestCatalogRules.GatherIntroQuestId; // Act using (var firstScope = Factory.Services.CreateScope()) diff --git a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs index c30840b..141d462 100644 --- a/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs +++ b/server/NeonSprawl.Server/Game/Quests/PrototypeE7M3QuestFactionRules.cs @@ -107,7 +107,7 @@ public static class PrototypeE7M3QuestFactionRules /// Returns a human-readable error if grid contract quest shape fails, otherwise . public static string? TryGetGridContractShapeError(IReadOnlyDictionary rowsById) { - var qid = PrototypeE7M1QuestCatalogRules.GridContractQuestId; + const string qid = PrototypeE7M1QuestCatalogRules.GridContractQuestId; if (!rowsById.TryGetValue(qid, out var row)) return $"error: missing quest '{qid}'";