Modul 01 Hotfix A4: AnvilRecipe.IsCollectable ergänzt

01-RecipeData Rev 6 (A4) added the field as a Summarize-branch
discriminator for module 02. Resolved as IsExpertRecipe OR the
output item's AlwaysCollectable flag, the latter being the canonical
FFXIV signal for collectable submissions (custom delivery, masterpiece
supply, collectables shop).

- AnvilRecipe.cs: new required bool property after CanHQ, with a
  why-comment pointing at module 02's Summarize branch and noting
  the OR-resolve.
- LuminaRecipeAdapter.cs: resolve folded into the recipe walk via
  the same itemSheet.TryGetRow call that already fetches the
  display name - no bootstrap-order question, no 2-pass needed.
  hasOutputItem is hoisted so the OR-branch can guard against a
  missing item-sheet row without touching the default(Item) struct.
- RecipeDataAdapterLoadStep.cs: new pass criterion #10 that asserts
  at least one recipe surfaces IsCollectable == true. Catches a
  silent resolve-path failure. Existing Cosmic-surface check shifts
  to #11 to keep numbering stable.

Build clean (0/0), csharpier clean.
This commit is contained in:
2026-05-28 19:26:53 +02:00
parent 12013ba6e6
commit 825c5d3c5c
3 changed files with 30 additions and 4 deletions
+7
View File
@@ -33,6 +33,13 @@ public sealed record AnvilRecipe
public required bool IsExpertRecipe { get; init; }
public required bool CanHQ { get; init; }
// True when the recipe is a collectable submission (custom delivery,
// masterpiece supply, collectables shop). Module 02's Summarize branch
// uses this discriminator. Resolved as IsExpertRecipe OR the output
// item's AlwaysCollectable flag - the latter is the canonical FFXIV
// signal for collectable submissions.
public required bool IsCollectable { get; init; }
// Cosmic Exploration (Patch 7.x). v0.1.0 ships with MissionHas* always
// false (SH-15 option B) - the data schema stays in place so v0.2.0+ can
// light up the surface by resolving the WKS mission sheet without
@@ -557,9 +557,14 @@ internal sealed class LuminaRecipeAdapter
var isCosmic = recipe.Number == 0;
var ingredients = ResolveIngredients(recipe);
var displayName = itemSheet.TryGetRow(outputItemId, out var outputItem)
? outputItem.Name.ExtractText()
: string.Empty;
// IsCollectable resolves off Item.AlwaysCollectable from the same
// sheet row we already fetch for the display name. Lumina is the
// canonical source here and folding it into the Recipe walk keeps
// the adapter single-pass (the Item walk runs later and rebuilds
// its own catalog from the same sheet).
var hasOutputItem = itemSheet.TryGetRow(outputItemId, out var outputItem);
var displayName = hasOutputItem ? outputItem.Name.ExtractText() : string.Empty;
var isCollectable = recipe.IsExpert || (hasOutputItem && outputItem.AlwaysCollectable);
var anvilRecipe = new AnvilRecipe
{
@@ -577,6 +582,7 @@ internal sealed class LuminaRecipeAdapter
QualityForHQ = recipe.CanHq ? qualityMax : 0,
IsExpertRecipe = recipe.IsExpert,
CanHQ = recipe.CanHq,
IsCollectable = isCollectable,
IsCosmic = isCosmic,
MissionHasMaterialMiracle = false,
MissionHasSteadyHand = false,
+14 -1
View File
@@ -157,7 +157,20 @@ internal sealed class RecipeDataAdapterLoadStep : ISelfTestStep
failures++;
}
// #10: Cosmic-surface sanity. v0.1.0 ships with SH-15 option B (all
// #10: IsCollectable sanity. FFXIV ships a non-zero number of
// collectable submission recipes (custom delivery, masterpiece
// supply, collectables shop), so the catalog must surface at
// least one. A flat false would mean the resolve path is broken.
if (!_catalog.RecipesById.Values.Any(r => r.IsCollectable))
{
_logger.LogError(
"RecipeDataCatalog.RecipesById has no recipe with "
+ "IsCollectable == true; collectable detection failed."
);
failures++;
}
// #11: Cosmic-surface sanity. v0.1.0 ships with SH-15 option B (all
// MissionHas* flags forced false), so the WARN is expected whenever
// any Cosmic recipe is in the catalog. The step still returns Pass
// because the catalog itself is not broken - the Cosmic sub-surface