From 12013ba6e6136b585f44e09468dd3c77f1716c0c Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 18:58:56 +0200 Subject: [PATCH] Modul 01 Hotfix A1: AnvilRecipe.RecipeLevelTableClassJobLevel ergaenzt 01-RecipeData Rev 6 (A1) added the field as the character-level equivalent of a recipe's RecipeLevelTable, so module 02 can branch the mod-penalty in BaseProgress and BaseQuality. The adapter resolves it from the existing RecipeLevelTable.Value in the recipe walk. - AnvilRecipe.cs: new required byte property after RecipeLevel, with a why-comment that points consumers at module 02 and warns against confusing the field with the row-id one above it. - LuminaRecipeAdapter.cs: levelTable.ClassJobLevel assignment in the Recipe walk - levelTable was already resolved earlier in the loop. - RecipeDataAdapterLoadStep.cs: new pass criterion #9 that asserts at least one recipe carries a positive RecipeLevelTableClassJobLevel. Catches a silent RecipeLevelTable-walk failure that would otherwise only surface inside the simulator. Existing Cosmic-surface check shifted to #10 to keep the spec numbering stable. Build clean (0/0), csharpier clean. --- Anvil/RecipeData/AnvilRecipe.cs | 8 ++++++++ Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs | 1 + Anvil/SelfTest/RecipeDataAdapterLoadStep.cs | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Anvil/RecipeData/AnvilRecipe.cs b/Anvil/RecipeData/AnvilRecipe.cs index eeffeae..9c44c85 100644 --- a/Anvil/RecipeData/AnvilRecipe.cs +++ b/Anvil/RecipeData/AnvilRecipe.cs @@ -16,6 +16,14 @@ public sealed record AnvilRecipe public required byte OutputAmount { get; init; } public required uint ClassJobId { get; init; } public required ushort RecipeLevel { get; init; } + + // Character-level equivalent of the recipe's difficulty (e.g. 90 for an + // Endwalker 3-star, 100 for a Dawntrail endgame recipe). Not the same as + // RecipeLevel above - that field is the RecipeLevelTable row id, this one + // is its ClassJobLevel column. Module 02 uses it for the mod-penalty + // branch in BaseProgress and BaseQuality. + public required byte RecipeLevelTableClassJobLevel { get; init; } + public required int Difficulty { get; init; } public required int QualityMax { get; init; } public required byte Durability { get; init; } diff --git a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs index 181799e..aa74530 100644 --- a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs +++ b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs @@ -568,6 +568,7 @@ internal sealed class LuminaRecipeAdapter OutputAmount = recipe.AmountResult, ClassJobId = classJobId, RecipeLevel = (ushort)recipe.RecipeLevelTable.RowId, + RecipeLevelTableClassJobLevel = levelTable.ClassJobLevel, Difficulty = difficulty, QualityMax = qualityMax, Durability = durability, diff --git a/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs b/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs index e2603c6..2763263 100644 --- a/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs +++ b/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs @@ -144,7 +144,20 @@ internal sealed class RecipeDataAdapterLoadStep : ISelfTestStep } } - // #9: Cosmic-surface sanity. v0.1.0 ships with SH-15 option B (all + // #9: RecipeLevelTableClassJobLevel sanity. At least one recipe must + // resolve a positive value from RecipeLevelTable.ClassJobLevel - if + // every recipe shows zero the adapter never walked the second sheet, + // which would silently break the simulator's mod-penalty branch. + if (!_catalog.RecipesById.Values.Any(r => r.RecipeLevelTableClassJobLevel > 0)) + { + _logger.LogError( + "RecipeDataCatalog.RecipesById has no recipe with " + + "RecipeLevelTableClassJobLevel > 0; RecipeLevelTable walk failed." + ); + failures++; + } + + // #10: 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