NEO-149: fix CraftResult and craft API analyzer warnings

Move CraftResult param docs to primary-constructor summaries (RCS1263),
use CraftIoAppliedJson[] return and collection expression in MapIoRows,
prefer is true for debug log guard (CDT1001), and add MapResponse deny test.
pull/190/head
VinPropane 2026-06-27 21:56:52 -04:00
parent 6f735f8643
commit eb6a4fedde
5 changed files with 29 additions and 7 deletions

View File

@ -5,7 +5,7 @@ meta {
} }
docs { docs {
NEO-70 prototype spine: accumulate >=11 scrap_metal_bulk (gather when nodes allow, reuse persisted inventory after dotnet test), refine_scrap_efficient when refined_plate_stock < 2, then make_field_stim_mk0. NEO-118: best-effort quest objective wiring on craft success (active craft_recipe quests). No inventory POST shortcuts for spine materials. NEO-70 prototype spine: accumulate >=11 scrap_metal_bulk (gather when nodes allow, reuse persisted inventory after dotnet test), refine_scrap_efficient when refined_plate_stock < 2, then make_field_stim_mk0. NEO-118: best-effort quest objective wiring on craft success (active craft_recipe quests). Deny envelopes return empty inputsConsumed/outputsGranted arrays. No inventory POST shortcuts for spine materials.
} }
script:pre-request { script:pre-request {

View File

@ -266,6 +266,28 @@ public sealed class PlayerCraftApiTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact]
public void MapResponse_ShouldReturnEmptyIoArrays_WhenCraftDenied()
{
// Arrange
var result = new CraftResult(
Success: false,
ReasonCode: CraftReasonCodes.UnknownRecipe,
InputsConsumed: [],
OutputsGranted: [],
XpGrantSummary: null);
// Act
var response = PlayerCraftApi.MapResponse(result);
// Assert
Assert.False(response.Success);
Assert.Equal(CraftReasonCodes.UnknownRecipe, response.ReasonCode);
Assert.Empty(response.InputsConsumed);
Assert.Empty(response.OutputsGranted);
Assert.Null(response.XpGrantSummary);
}
private static void SeedStack(InMemoryWebApplicationFactory factory, string itemId, int quantity) private static void SeedStack(InMemoryWebApplicationFactory factory, string itemId, int quantity)
{ {
var itemRegistry = factory.Services.GetRequiredService<IItemDefinitionRegistry>(); var itemRegistry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();

View File

@ -4,12 +4,12 @@ namespace NeonSprawl.Server.Game.Crafting;
/// Server-internal craft resolution envelope (NEO-69); promoted to wire JSON via <see cref="CraftResponse"/> (NEO-70). /// Server-internal craft resolution envelope (NEO-69); promoted to wire JSON via <see cref="CraftResponse"/> (NEO-70).
/// NEO-71 telemetry hook sites live in <see cref="CraftOperations.TryCraft"/> (success path + centralized <c>Deny</c> helper). /// NEO-71 telemetry hook sites live in <see cref="CraftOperations.TryCraft"/> (success path + centralized <c>Deny</c> helper).
/// </summary> /// </summary>
/// <param name="InputsConsumed">Scaled inputs removed on success; empty when denied.</param>
/// <param name="OutputsGranted">Scaled outputs added on success; empty when denied.</param>
/// <param name="XpGrantSummary">Refine skill XP applied on success; null when denied.</param>
public readonly record struct CraftResult( public readonly record struct CraftResult(
bool Success, bool Success,
string? ReasonCode, string? ReasonCode,
/// <summary>Scaled inputs removed on success; empty when denied.</summary>
IReadOnlyList<CraftIoApplied> InputsConsumed, IReadOnlyList<CraftIoApplied> InputsConsumed,
/// <summary>Scaled outputs added on success; empty when denied.</summary>
IReadOnlyList<CraftIoApplied> OutputsGranted, IReadOnlyList<CraftIoApplied> OutputsGranted,
/// <summary>Refine skill XP applied on success; null when denied.</summary>
CraftXpGrantSummary? XpGrantSummary); CraftXpGrantSummary? XpGrantSummary);

View File

@ -96,11 +96,11 @@ public static class PlayerCraftApi
}; };
} }
private static IReadOnlyList<CraftIoAppliedJson> MapIoRows(IReadOnlyList<CraftIoApplied> rows) private static CraftIoAppliedJson[] MapIoRows(IReadOnlyList<CraftIoApplied> rows)
{ {
if (rows.Count == 0) if (rows.Count == 0)
{ {
return Array.Empty<CraftIoAppliedJson>(); return [];
} }
var mapped = new CraftIoAppliedJson[rows.Count]; var mapped = new CraftIoAppliedJson[rows.Count];

View File

@ -116,7 +116,7 @@ public static class EncounterCombatWiring
if (!completion.Success) if (!completion.Success)
{ {
if (logger?.IsEnabled(LogLevel.Debug) == true) if (logger?.IsEnabled(LogLevel.Debug) is true)
{ {
logger.LogDebug( logger.LogDebug(
"Encounter completion grant skipped for player {PlayerId} encounter {EncounterId} ({ReasonCode})", "Encounter completion grant skipped for player {PlayerId} encounter {EncounterId} ({ReasonCode})",