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.
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.
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.
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.
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.
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.
Dalamud's plugin validator flagged two issues on v0.1.0:
- "The plugin does not register a config UI callback."
- "The plugin does not register a main UI callback."
v0.1.0 ships the RecipeData layer only - the actual SettingsWindow
and MainWindow arrive with module 08 (UI). To pass validation and to
give users a useful response when they click "Open Settings" or "Open
Main" in the plugin installer, both callbacks now fire toast
notifications via INotificationManager that point at the roadmap.
The handlers wire up in the constructor right after the host build
and unwire in DisposeAsync. INotificationManager joins the Block A
Dalamud services in PluginHostDependencies + PluginHostFactory so the
DI container also exposes it for later modules.
Four-block pre-push gate matching the HellionChat pattern:
- Block A (verify-version-consistency.sh): csproj <Version> vs
repo.json AssemblyVersion / TestingAssemblyVersion / DownloadLink*
tag presence. Tolerant of repo.json being absent so v0.1.0 (which
has no public release manifest yet) does not fail at push time;
the missing-file path turns into the full cross-check once repo.json
lands.
- Block B: dotnet build Anvil.sln -p:Platform=x64 -c Release. Platform
pin is forge-wide (Forgeimizer v0.1.0 lesson: solution build defaults
drift to AnyCPU otherwise).
- Block C: dotnet csharpier check ./Anvil. Catches the accumulated
formatter drift that hit HellionChat v1.5.6 (12 files) when only
build was checked per task.
- Block D: markdownlint-cli2 over the repo's *.md files (excludes node_modules,
bin, obj, .claude, CLAUDE.md).
Plus setup-hooks.sh as the one-shot installer that points
core.hooksPath at .githooks/ and chmods the scripts.
README.md: MD040 fix for the custom-repo URL fence (added text language tag).
The plugin is now loadable. Dalamud injects five services into the Plugin
constructor (Lightless pattern), the constructor builds the generic-host
container synchronously, and PluginLifecycle drives StartAsync from
LoadAsync. Module 02+ extends PluginHostFactory; this file set stays put.
- PluginHostDependencies (record): bundles the five Dalamud services
v0.1.0 needs (IDalamudPluginInterface, IPluginLog, IDataManager,
IFramework, ISelfTestRegistry).
- PluginHostFactory.Build: HostBuilder + AddDalamudLogging + the four
service blocks (Dalamud services, Anvil singletons, ISelfTestStep
collection, IHostedService init chain). Every registration uses an
explicit factory lambda - the default activator only sees public
ctors and Anvil follows the internal-sealed convention.
- PluginLifecycle (IAsyncDisposable): owns Host.StartAsync, marshals the
Host.Dispose call onto the framework thread, idempotency guard via
Interlocked, ExceptionDispatchInfo.Capture preserves the original
load-throw stack when a failure cascades.
- Plugin (IAsyncDalamudPlugin): constructor injection of the five
Dalamud services, builds the dependencies record, kicks off the host
build, hands DisposeAsync to the lifecycle.
- Hosting/RecipeDataLoadHostedService: dispatches LuminaRecipeAdapter
.LoadInternal onto the framework thread on StartAsync. Lumina sheet
reads have no documented thread safety; conservative default.
- Hosting/SelfTestRegistrationHostedService: collects every
ISelfTestStep registration from DI and hands them to
ISelfTestRegistry.RegisterTestSteps once the host is up.
- SelfTest/RecipeDataAdapterLoadStep: nine pass criteria per spec §4.1
(IsLoaded, RecipeCount > 0, ActionCount in 30..80, BuffsByKind.Count
== 14, ConditionsByKind.Count == 11, Foods >= 30, Medicines >= 5,
BasicSynthesis.RowIdByClassJob[8] == 100001, Cosmic-surface
silent-degradation warning). Returns Waiting while the catalog is
still loading.
- Infrastructure/Logging trio: DalamudLogger maps
Microsoft.Extensions.Logging levels to IPluginLog, the provider
emits an Anvil bootstrap banner with a Forge-Bronze fingerprint on
ctor, the extension wires the provider into the ILoggingBuilder via
TryAddEnumerable.
The adapter is the only place in the plugin that touches Lumina and
Dalamud types - everything outside Anvil.RecipeData.Internal stays on
the BCL surface (Critical Boundary per 00-Anvil-Scope §3.3).
- LuminaRecipeAdapter: walks Status, CraftAction, Action, Item, ItemFood,
and Recipe + RecipeLevelTable, builds the AnvilXxx records, and
swaps the CatalogState into RecipeDataCatalog in one atomic write.
Cosmic actions normalise the Action-sheet ClassJob=-1 sentinel to
RowIdByClassJob key 0 (spec §2.3.1); the legacy ARR singletons
(Manipulation 278, Waste Not 279, Innovation 284, Waste Not II 285)
drop out via the PrimaryCostValue == 0 filter (spec §3.1 #3). The
CraftAction sheet wins the canonical icon at the CRP variant
(ClassJobId 8). Per-job rows are cross-checked for Cost, ClassJobLevel,
and Specialist consistency; mismatches log a warning.
- SH-15 option B is hard-wired: every AnvilRecipe ships with
MissionHasMaterialMiracle = false, MissionHasSteadyHand = false, and
IsSplendorCosmic = false in v0.1.0. IsCosmic is still set from
Recipe.Number == 0 so the catalog can label Cosmic recipes; the WKS
mission sheet chain is not walked. v0.2.0 will replace those constant
assignments with the resolver and the rest of the adapter stays put.
- The adapter invariant (Cosmic sub-flag implies the master IsCosmic
flag) throws on inconsistent recipe states at catalog-build time so
v0.2.0+ catches a broken WKS resolver before it corrupts the simulator.
- ItemFood walk uses ItemAction.Data[0] (48 = "well fed", 49 =
"medicated") instead of an ItemAction.Type column that does not exist
in the current Lumina schema. The bonus extractor filters
ItemFood.Params to the three crafting BaseParam ids (CP=11,
Craftsmanship=70, Control=71). Item rows are populated only for the
ItemIds that recipes / foods actually reference - no 50k-row mirror.
- AnvilStrings.resx + AnvilStrings.de.resx + AnvilStrings.Designer.cs:
Localization layer for condition display names and the SelfTest step
name. The adapter looks the condition strings up through the generated
ResourceManager so culture-switching is a no-restart change later on.
Two concerns bundled into one commit because the csharpier tool restore
reformatted four earlier files as soon as the manifest landed:
- .config/dotnet-tools.json: csharpier 1.2.6 as local tool, follows
the standard .config layout (dotnet tool restore will find it).
- Anvil/packages.lock.json: produced by dotnet restore, pinned alongside
the csproj's RestorePackagesWithLockFile=true.
- Anvil/RecipeData/RecipeDataLoadResult.cs: public record carrying the
adapter's load-pass summary (counts + duration + warnings). Surfaced
to the SelfTest step and future UI status surfaces.
- Anvil/RecipeData/RecipeDataCatalog.cs: public DI singleton with all
seven dictionary lookups, the two helper methods (ActionsForClassJob,
TryResolveActionRowId), and the four meta properties from spec §2.7.
Backed by a private CatalogState record that the adapter swaps in via
a volatile reference - readers either see the empty initial state or
the fully populated post-load snapshot, never a partial mid-build.
TryResolveActionRowId carries the Cosmic-sentinel logic so hook code
does not need to know about the CosmicOnly flag itself.
- Reformatting in AnvilAction.cs, ActionMechanicsTable.cs,
ConditionMechanicsTable.cs, and Anvil.csproj: csharpier-mandated
line-wrap / blank-line tweaks. No semantic changes.
Three game-mechanics tables for the simulator-relevant constants that the
Lumina sheets do not expose, plus the reverse name map the adapter uses
to walk the CraftAction + Action sheets.
- ActionMechanicsTable: 38 entries with Category, CP cost, durability,
efficiency, IQ-stack bonus, charges, granted buff, and flags.
Cross-checked against the spec mechanics table in 01-RecipeData.md
§2.3.2 (verified there against Artisan's RawInformation/Character/
Skills.cs). Combo-state CP costs (Standard/Advanced Touch) carry the
base value; the simulator applies the discount. TrainedEye uses
short.MaxValue as the sentinel for "fill Recipe.QualityMax in one
step"; ByregotsBlessing carries base EfficiencyQuality=100 with the
IQ multiplier added by the simulator.
- BuffMechanicsTable: 14 entries with StatusId, Icon, StackMax, Behavior,
the three duration fields (Steps / Seconds / Actions populated per
Behavior), Category, and LegacyStatusId for the older Innovation 259
and Manipulation 258 ids. Cross-checked against ffxiv-datamining
Status.csv (StatusId / Icon / StackMax) and Artisan's tooltip-derived
step durations. Expedience uses the explicit StatusId 3812 - the other
two "Expedience" rows (2712 / 3092) are non-crafting status effects
that would otherwise produce an ambiguous name match.
- ConditionMechanicsTable: 11 entries with Quality / Progress / CP /
Durability multipliers plus a BaseProbability slot. Robust mirrors
Sturdy's durability discount and keeps Quality neutral (per the
spec mechanics table - the enum doc comment in AnvilCondition.cs
predates rev 5 and is noted as superseded in the table header).
SplendorCosmic's Good=1.75 override lives in the simulator.
BaseProbability stays 0.0; the static catalog has no useful per-recipe
spawn distribution because FFXIV derives that from Recipe.IsExpert +
RecipeLevelTable.Stars at runtime.
- ActionKindByName: reverse map from English action names to
AnvilActionKind, plus the Action-sheet whitelist (seven step-counter
buff actions + two Cosmic singletons) used to filter the Action sheet
walk down to the crafting subset.
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).