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 {
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 {

View File

@ -266,6 +266,28 @@ public sealed class PlayerCraftApiTests
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)
{
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).
/// NEO-71 telemetry hook sites live in <see cref="CraftOperations.TryCraft"/> (success path + centralized <c>Deny</c> helper).
/// </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(
bool Success,
string? ReasonCode,
/// <summary>Scaled inputs removed on success; empty when denied.</summary>
IReadOnlyList<CraftIoApplied> InputsConsumed,
/// <summary>Scaled outputs added on success; empty when denied.</summary>
IReadOnlyList<CraftIoApplied> OutputsGranted,
/// <summary>Refine skill XP applied on success; null when denied.</summary>
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)
{
return Array.Empty<CraftIoAppliedJson>();
return [];
}
var mapped = new CraftIoAppliedJson[rows.Count];

View File

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