From 8e624e509acc88b82c5f5ba942287d3ca8e1dfd2 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 17:59:36 +0200 Subject: [PATCH 1/6] chore(v0.1.1): bump version and add repo.json + CHANGELOG.md Versions-bump from 0.1.0 to 0.1.1 in csproj. Extend Anvil.yaml changelog with the v0.1.1 block; the v0.1.0 entry stays below as release history. Add repo.json (Custom-Repo manifest, AssemblyVersion 0.1.1.0, download links pointing at the v0.1.1 release tag) and CHANGELOG.md (root-level history file with v0.1.1 hotfix notes and the v0.1.0 foundation block). This is the scaffolding commit for the Module 01 spec-sync hotfix cycle. The five spec-edit commits (A1, A2, A3, A4, A5) follow. --- Anvil/Anvil.csproj | 2 +- Anvil/Anvil.yaml | 31 +++++++++++++++++++++++++++++++ CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ repo.json | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 repo.json diff --git a/Anvil/Anvil.csproj b/Anvil/Anvil.csproj index 52208a0..c6bd805 100644 --- a/Anvil/Anvil.csproj +++ b/Anvil/Anvil.csproj @@ -1,6 +1,6 @@ - 0.1.0 + 0.1.1 enable enable latest diff --git a/Anvil/Anvil.yaml b/Anvil/Anvil.yaml index f8f0788..1a4e84e 100644 --- a/Anvil/Anvil.yaml +++ b/Anvil/Anvil.yaml @@ -32,6 +32,37 @@ tags: - UI - Privacy changelog: |- + **v0.1.1 — Module 01 Spec-Sync Hotfix (2026-05-28)** + + Code-sync against the 01-RecipeData Rev 6 spec polish that landed + during the Module 02 (CraftingSimulator) Phase-2 review. No new + surface; the existing v0.1.0 module shape is brought in line with + the spec edits the simulator design needs. + + - `AnvilRecipe.RecipeLevelTableClassJobLevel` added. Carries the + character-level equivalent of a recipe's difficulty (e.g. 90 for + Endwalker 3-star, 100 for Dawntrail endgame). The simulator + module uses this for the mod-penalty branch in BaseProgress and + BaseQuality. Adapter resolves it from + `RecipeLevelTable.Value.ClassJobLevel`. + - `AnvilRecipe.IsCollectable` added. Marks a recipe as a + collectable submission (custom delivery, masterpiece supply, + collectables shop). The simulator module's Summarize branch + uses this discriminator. Adapter resolves it from + `recipe.IsExpert` or the output item's `IsCollectable` flag. + - `AnvilRecipe.IsSplendorCosmic` removed. The Splendorous-tool + bonus is character-equipment state, not recipe state — it lives + in the stats layer of the simulator module instead. + - `ActionMechanicsTable.Reflect` EfficiencyQuality corrected + from 100 to 300, matching the live game value. + - `ActionMechanicsTable.RefinedTouch` IqStackBonus comment + clarified: the +1 IQ stack is unconditional. The combo gating + is a UI/solver hint, not a data-layer rule. + + Inspired by Craftimizer by Asriel Camora (MIT). + + --- + **v0.1.0 — Initial Release (2026-05-27)** First Hellion Forge release of Anvil. Module 01 (RecipeData) is in diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ae90e0d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,44 @@ +# Changelog + +All notable Anvil releases. See the Gitea release history for prior builds. + +## v0.1.1 — Module 01 Spec-Sync Hotfix (2026-05-28) + +Code-sync against the 01-RecipeData Rev 6 spec polish that landed during +the Module 02 (CraftingSimulator) Phase-2 review. No new surface; the +existing v0.1.0 module shape is brought in line with the spec edits the +simulator design needs. + +- `AnvilRecipe.RecipeLevelTableClassJobLevel` added. Carries the + character-level equivalent of a recipe's difficulty (e.g. 90 for + Endwalker 3-star, 100 for Dawntrail endgame). The simulator module + uses this for the mod-penalty branch in BaseProgress and BaseQuality. + Adapter resolves it from `RecipeLevelTable.Value.ClassJobLevel`. +- `AnvilRecipe.IsCollectable` added. Marks a recipe as a collectable + submission (custom delivery, masterpiece supply, collectables shop). + The simulator module's Summarize branch uses this discriminator. + Adapter resolves it from `recipe.IsExpert` or the output item's + `IsCollectable` flag. +- `AnvilRecipe.IsSplendorCosmic` removed. The Splendorous-tool bonus + is character-equipment state, not recipe state — it lives in the + stats layer of the simulator module instead. +- `ActionMechanicsTable.Reflect` EfficiencyQuality corrected from 100 + to 300, matching the live game value. +- `ActionMechanicsTable.RefinedTouch` IqStackBonus comment clarified: + the +1 IQ stack is unconditional. The combo gating is a UI/solver + hint, not a data-layer rule. + +Inspired by Craftimizer by Asriel Camora (MIT). + +## v0.1.0 — Initial Release (2026-05-27) + +First Hellion Forge release of Anvil. Module 01 (RecipeData) is in +place: catalogues every FFXIV crafting recipe, item, action, buff, +condition, and food/medicine from Lumina sheets, surfaces them through +a Dalamud-free read-only API, and verifies the load via the `/xlperf` +SelfTest step. + +Note: This release is the foundation layer. UI windows, simulator, +solver, macros, hooks, and the IPC provider follow in v0.2.0+. + +Inspired by Craftimizer by Asriel Camora (MIT). diff --git a/repo.json b/repo.json new file mode 100644 index 0000000..cee0858 --- /dev/null +++ b/repo.json @@ -0,0 +1,38 @@ +[ + { + "Author": "Jon Kazama (Hellion Forge)", + "Name": "Anvil", + "InternalName": "Anvil", + "AssemblyVersion": "0.1.1.0", + "Description": "A Hellion Forge plugin — privacy-focused FFXIV crafting helper.\n\nAnvil is a clean-room crafting plugin for FINAL FANTASY XIV. It provides a state-machine crafting simulator, a wrapper for the Raphael Rust solver, a recipe-note overlay, a synth-helper overlay, a macro editor, recipe bookmarks, and an opt-in Auto-Craft hook.\n\nPrivacy:\n- Zero telemetry, no network calls\n- Bookmarks and settings stay on your machine\n- Auto-Craft is OFF by default with an explicit warning modal\n\nStatus: v0.1.1 ships the spec-sync hotfix for the Module 01 (RecipeData) foundation. UI windows, simulator, solver wrapper, macro engine, and the IPC provider follow in v0.2.0+.\n\nCosmic Exploration support: planned for v0.2.0 (recipe-flag schema is already in place, mission-flag resolution is deferred).\n\nInspired by Craftimizer by Asriel Camora (MIT). Anvil is an independent re-implementation; no code was ported from Craftimizer.", + "ApplicableVersion": "any", + "RepoUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil", + "Tags": [ + "Crafting", + "Macros", + "Solver", + "UI", + "Privacy" + ], + "DalamudApiLevel": 15, + "LoadRequiredState": 0, + "LoadSync": false, + "CanUnloadAsync": false, + "LoadPriority": 0, + "Punchline": "A Hellion Forge plugin — privacy-focused FFXIV crafting helper with simulator, solver, and bookmarks.", + "Changelog": "**v0.1.1 — Module 01 Spec-Sync Hotfix (2026-05-28)**\n\nCode-sync against the 01-RecipeData Rev 6 spec polish that landed during the Module 02 (CraftingSimulator) Phase-2 review. No new surface; the existing v0.1.0 module shape is brought in line with the spec edits the simulator design needs.\n\n- `AnvilRecipe.RecipeLevelTableClassJobLevel` added. Carries the character-level equivalent of a recipe's difficulty (e.g. 90 for Endwalker 3-star, 100 for Dawntrail endgame). The simulator module uses this for the mod-penalty branch in BaseProgress and BaseQuality. Adapter resolves it from `RecipeLevelTable.Value.ClassJobLevel`.\n- `AnvilRecipe.IsCollectable` added. Marks a recipe as a collectable submission (custom delivery, masterpiece supply, collectables shop). The simulator module's Summarize branch uses this discriminator. Adapter resolves it from `recipe.IsExpert` or the output item's `IsCollectable` flag.\n- `AnvilRecipe.IsSplendorCosmic` removed. The Splendorous-tool bonus is character-equipment state, not recipe state — it lives in the stats layer of the simulator module instead.\n- `ActionMechanicsTable.Reflect` EfficiencyQuality corrected from 100 to 300, matching the live game value.\n- `ActionMechanicsTable.RefinedTouch` IqStackBonus comment clarified: the +1 IQ stack is unconditional. The combo gating is a UI/solver hint, not a data-layer rule.\n\nInspired by Craftimizer by Asriel Camora (MIT).\n\n---\n\n**v0.1.0 — Initial Release (2026-05-27)**\n\nFirst Hellion Forge release of Anvil. Module 01 (RecipeData) is in place: catalogues every FFXIV crafting recipe, item, action, buff, condition, and food/medicine from Lumina sheets, surfaces them through a Dalamud-free read-only API, and verifies the load via the `/xlperf` SelfTest step.\n\nNote: This release is the foundation layer. UI windows, simulator, solver, macros, hooks, and the IPC provider follow in v0.2.0+.\n\nInspired by Craftimizer by Asriel Camora (MIT).\n\n---\n\nFull history: https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil/releases", + "AcceptsFeedback": true, + "DownloadLinkInstall": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil/releases/download/v0.1.1/latest.zip", + "DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil/releases/download/v0.1.1/latest.zip", + "DownloadLinkTesting": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil/releases/download/v0.1.1/latest.zip", + "TestingAssemblyVersion": "0.1.1.0", + "IconUrl": "https://gitea.hellion-forge.cloud/JonKazama-Hellion/Anvil/raw/branch/main/Anvil/images/icon.png", + "ImageUrls": [], + "DownloadCount": 0, + "IsHide": false, + "IsTestingExclusive": false, + "CategoryTags": [ + "utility" + ] + } +] From ce3fdda51cff01fc26c692f9918a531d8790e875 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 18:30:32 +0200 Subject: [PATCH 2/6] Modul 01 Hotfix A2: AnvilRecipe.IsSplendorCosmic entfernt Splendorous-Tool-Bonus is character-equipment state, not recipe state. The flag moved to the simulator's stats layer (CraftStats.HasSplendorousTool in module 02) per the 01-RecipeData Rev 6 spec polish (A2). - AnvilRecipe.cs: IsSplendorCosmic property removed. Cosmic doc-block trimmed to four flags (IsCosmic + two MissionHas* + IsIshgardExpert) and now points consumers at the stats-layer for the Splendor branch. - LuminaRecipeAdapter.cs: assignment in the recipe walk removed. ValidateCosmicInvariant drops the IsSplendorCosmic implication (MissionHas* checks unchanged). - ConditionMechanicsTable.cs: header comment rewritten to describe Splendor as a stats-layer override rather than a recipe-flag override. Sweep over Anvil/RecipeData/, Anvil/SelfTest/, Anvil/Hosting/, Plugin.cs, and PluginHostFactory.cs: zero remaining references to IsSplendorCosmic. Build clean (0/0), csharpier check clean. --- Anvil/RecipeData/AnvilRecipe.cs | 11 ++++++----- Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs | 6 ------ Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs | 7 ++++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Anvil/RecipeData/AnvilRecipe.cs b/Anvil/RecipeData/AnvilRecipe.cs index e6b6f16..eeffeae 100644 --- a/Anvil/RecipeData/AnvilRecipe.cs +++ b/Anvil/RecipeData/AnvilRecipe.cs @@ -28,19 +28,20 @@ public sealed record AnvilRecipe // 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 - // touching the type. IsCosmic / IsSplendorCosmic are still set by the - // recipe-detection path so the catalog knows which recipes belong to the - // Cosmic surface even while the action flags stay dormant. + // touching the type. IsCosmic is still set by the recipe-detection path + // so the catalog knows which recipes belong to the Cosmic surface even + // while the mission flags stay dormant. The Splendorous-tool bonus is + // character-equipment state rather than recipe state, so it lives in + // the simulator's stats layer (CraftStats.HasSplendorousTool in module + // 02) instead of carrying a flag on this record. // // Adapter invariant (validated when the catalog is built): - // IsSplendorCosmic == true implies IsCosmic == true // MissionHasMaterialMiracle == true implies IsCosmic == true // MissionHasSteadyHand == true implies IsCosmic == true // // Inconsistent recipe states (sub-flag without master flag) throw at // catalog-build time instead of silently corrupting the simulator. public required bool IsCosmic { get; init; } - public required bool IsSplendorCosmic { get; init; } public required bool MissionHasMaterialMiracle { get; init; } public required bool MissionHasSteadyHand { get; init; } public required bool IsIshgardExpert { get; init; } diff --git a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs index e5bd358..181799e 100644 --- a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs +++ b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs @@ -577,7 +577,6 @@ internal sealed class LuminaRecipeAdapter IsExpertRecipe = recipe.IsExpert, CanHQ = recipe.CanHq, IsCosmic = isCosmic, - IsSplendorCosmic = false, MissionHasMaterialMiracle = false, MissionHasSteadyHand = false, IsIshgardExpert = false, @@ -636,11 +635,6 @@ internal sealed class LuminaRecipeAdapter // time instead of producing a corrupt simulator state at runtime. private static void ValidateCosmicInvariant(AnvilRecipe recipe) { - if (recipe.IsSplendorCosmic && !recipe.IsCosmic) - throw new InvalidOperationException( - $"AnvilRecipe {recipe.RecipeId}: IsSplendorCosmic implies IsCosmic, " - + "but IsCosmic is false." - ); if (recipe.MissionHasMaterialMiracle && !recipe.IsCosmic) throw new InvalidOperationException( $"AnvilRecipe {recipe.RecipeId}: MissionHasMaterialMiracle implies IsCosmic, " diff --git a/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs b/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs index ace0e77..8136cd9 100644 --- a/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs +++ b/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs @@ -10,9 +10,10 @@ // behaviour for Robust - that wording predates the verified mechanics table // and the values below are the source of truth.) // -// SplendorCosmic raises Good's quality multiplier from 1.5 to 1.75 on -// recipes flagged IsSplendorCosmic. The override lives in the simulator; -// the table holds the non-Splendor default. +// Splendor (the Splendorous-tool bonus) raises Good's quality multiplier +// from 1.5 to 1.75 when the character has the tool equipped. The override +// lives in the simulator's stats layer (CraftStats.HasSplendorousTool in +// module 02); the table holds the non-Splendor default. using System.Collections.Generic; From 12013ba6e6136b585f44e09468dd3c77f1716c0c Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 18:58:56 +0200 Subject: [PATCH 3/6] 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 From 825c5d3c5c0d14d50f7d7593e4395d2d3efaa9c6 Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 19:26:53 +0200 Subject: [PATCH 4/6] =?UTF-8?q?Modul=2001=20Hotfix=20A4:=20AnvilRecipe.IsC?= =?UTF-8?q?ollectable=20erg=C3=A4nzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- Anvil/RecipeData/AnvilRecipe.cs | 7 +++++++ Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs | 12 +++++++++--- Anvil/SelfTest/RecipeDataAdapterLoadStep.cs | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Anvil/RecipeData/AnvilRecipe.cs b/Anvil/RecipeData/AnvilRecipe.cs index 9c44c85..696e7f2 100644 --- a/Anvil/RecipeData/AnvilRecipe.cs +++ b/Anvil/RecipeData/AnvilRecipe.cs @@ -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 diff --git a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs index aa74530..dd5c274 100644 --- a/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs +++ b/Anvil/RecipeData/Internal/LuminaRecipeAdapter.cs @@ -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, diff --git a/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs b/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs index 2763263..298cf84 100644 --- a/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs +++ b/Anvil/SelfTest/RecipeDataAdapterLoadStep.cs @@ -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 From 01f35c65bc0a39698fc4f5ae85fff8b54d25f88c Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 19:57:29 +0200 Subject: [PATCH 5/6] =?UTF-8?q?Modul=2001=20Hotfix=20A3:=20Reflect=20Effic?= =?UTF-8?q?iencyQuality=20100=20=E2=86=92=20300?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 01-RecipeData Rev 6 (A3) corrects the live-game value: Reflect's quality efficiency is 300, not 100. The Phase-3 v0.1.0 table missed the post-6.0 game tuning. Spec mechanics table (§2.3.2) was already on 300 - this brings the code in line. Build clean (0/0), csharpier clean. --- Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs index 93efb7d..c0d9d68 100644 --- a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs +++ b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs @@ -286,7 +286,7 @@ internal static class ActionMechanicsTable CpCost = 6, DurabilityCost = 10, EfficiencyProgress = 0, - EfficiencyQuality = 100, + EfficiencyQuality = 300, IqStackBonus = 1, ChargesPerCraft = 0, GrantsBuff = null, From fd4fe52665f51df30e7ca7c60d73dce721e61d8b Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Thu, 28 May 2026 20:04:04 +0200 Subject: [PATCH 6/6] Modul 01 Hotfix A5: RefinedTouch IqStackBonus unconditional 01-RecipeData Rev 6 (A5) removed the BasicTouch combo gate from the RefinedTouch IQ-stack bonus. The Phase-3 v0.1.0 value was already 1 - only the inline comment suggested a simulator-side combo gate that the spec polish moved out of the data layer entirely. The combo with BasicTouch is now a UI/solver presentation hint, not a catalog rule. This commit rewrites the comment to match: +1 stack is unconditional, the combo lives outside the catalog. No numeric change. Build clean (0/0), csharpier clean. --- Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs index c0d9d68..90448d6 100644 --- a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs +++ b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs @@ -209,7 +209,9 @@ internal static class ActionMechanicsTable DurabilityCost = 10, EfficiencyProgress = 0, EfficiencyQuality = 100, - // +1 only after BasicTouch combo; simulator gates the bonus. + // +1 IQ stack unconditionally. The BasicTouch combo that the + // UI and solver hint at is a presentation layer rule, not a + // data-layer one - the catalog stays free of the gate. IqStackBonus = 1, ChargesPerCraft = 0, GrantsBuff = null,