feat(recipedata): add plain-data records and enums

Module 01 public-API surface: six sealed records with init-only
properties plus their nine enums. All BCL-only - no Lumina or Dalamud
types in any public property, which is what keeps the simulator and
catalog xUnit-testable per the Critical Boundary in 00-Anvil-Scope §3.3.

- AnvilRecipe + AnvilRecipeIngredient: flat representation of the
  Recipe + RecipeLevelTable sheet pair with the five Cosmic-Exploration
  flags. v0.1.0 ships with MissionHas* always false (SH-15 option B);
  the schema stays in place so v0.2.0 can wire the WKS mission sheet
  without touching the type.
- AnvilItem: slim per-recipe / per-food item view (full ~50k-row item
  mirror is left to the UI layer).
- AnvilAction + AnvilActionKind + AnvilActionCategory + AnvilActionFlags:
  one logical action per Kind value with RowIdByClassJob mapping; Cosmic
  actions use ClassJobId 0 as the sentinel for "every crafter".
  AnvilActionFlags bit 1 << 2 is intentionally vacant (ConsumesGreatStrides
  was removed in spec rev 5; consumption logic lives in 02-CraftingSimulator).
- AnvilBuff + AnvilBuffKind + AnvilBuffBehavior + AnvilBuffCategory:
  static buff catalog entry with the duration field that matches Behavior
  (Steps / Seconds / Actions). Two Cosmic buffs (MaterialMiracleBuff,
  StellarSteadyHandBuff).
- AnvilCondition + AnvilConditionKind: eleven conditions including the
  Cosmic Robust variant. DisplayName comes from AnvilStrings.resx, not
  Lumina (the Status sheet does not expose the crafting condition labels
  cleanly).
- AnvilFood + AnvilFoodBonus + AnvilFoodKind + AnvilFoodStat: ItemFood
  mirror with IsRelative flag (percentage vs flat bonus).
This commit is contained in:
2026-05-27 19:53:54 +02:00
parent 96553a849a
commit 47790a3f68
6 changed files with 336 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
// Plain-data record describing a single FFXIV crafting recipe. Public surface
// is intentionally BCL-only (no Lumina or Dalamud types) so the simulator and
// solver can be tested in xUnit without loading Dalamud.dll into the test
// AppDomain. The adapter (Internal/LuminaRecipeAdapter) builds these from the
// Recipe + RecipeLevelTable sheet pair and flattens both sheets into one
// record so consumers never have to do a two-sheet walk.
using System.Collections.Generic;
namespace Anvil.RecipeData;
public sealed record AnvilRecipe
{
public required uint RecipeId { get; init; }
public required uint OutputItemId { get; init; }
public required byte OutputAmount { get; init; }
public required uint ClassJobId { get; init; }
public required ushort RecipeLevel { get; init; }
public required int Difficulty { get; init; }
public required int QualityMax { get; init; }
public required byte Durability { get; init; }
public required int RequiredCraftsmanship { get; init; }
public required int RequiredControl { get; init; }
public required int QualityForHQ { get; init; }
public required bool IsExpertRecipe { get; init; }
public required bool CanHQ { 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
// 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.
//
// 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; }
public required byte Stars { get; init; }
public required byte ProgressDivider { get; init; }
public required byte ProgressModifier { get; init; }
public required byte QualityDivider { get; init; }
public required byte QualityModifier { get; init; }
public required IReadOnlyList<AnvilRecipeIngredient> Ingredients { get; init; }
public required string DisplayName { get; init; }
}
public sealed record AnvilRecipeIngredient(uint ItemId, byte Amount);