Economy checks: ConsumerDemand tests recipes, not keys; pin upkeep reached through a chain

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-27 09:32:13 +01:00
commit 8e6fd43d23
2 changed files with 10 additions and 8 deletions

View file

@ -417,20 +417,20 @@ public static class EconomyChecks
{
var recipes = AllRecipes(catalog).Where(p => catalog.Get(p.Recipe.Output) != null).ToDictionary(p => p.Recipe.Output.Key.Value, p => p.Recipe, StringComparer.Ordinal);
var drawn = Drawn(catalog);
bool IsCommodityKey(string key) => recipes.TryGetValue(key, out var r) && Recipes.IsCommodity(catalog.Get(r.Output));
bool IsCommodityRecipe(RecipeData r) => Recipes.IsCommodity(catalog.Get(r.Output));
var consumerRoles = new HashSet<string>(recipes.Values.Where(r => Recipes.IsConsumerGood(catalog, r))
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
var gearRoles = new HashSet<string>(recipes.Values.Where(r => !Recipes.IsCommodity(catalog.Get(r.Output)))
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
var upkeep = new HashSet<string>(recipes.Values.SelectMany(r => r.Upkeep).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
var takenBy = recipes.Values.Where(r => IsCommodityKey(r.Output.Key.Value))
var takenBy = recipes.Values.Where(IsCommodityRecipe)
.SelectMany(r => r.Inputs.Select(input => (Input: input.Commodity.Key.Value, Output: r.Output.Key.Value)))
.ToLookup(edge => edge.Input, edge => edge.Output, StringComparer.Ordinal);
var faults = new List<EconomyFault>();
foreach (var key in recipes.Keys.OrderBy(k => k, StringComparer.Ordinal))
{
var recipe = recipes[key];
if (!IsCommodityKey(key) || Recipes.IsRaw(catalog, recipe) || Recipes.IsConsumerGood(catalog, recipe)) continue;
if (!IsCommodityRecipe(recipe) || Recipes.IsRaw(catalog, recipe) || Recipes.IsConsumerGood(catalog, recipe)) continue;
var reached = new HashSet<string>(StringComparer.Ordinal) { key };
var open = new Stack<string>(reached);
while (open.Count > 0)

View file

@ -377,14 +377,16 @@ public sealed class EconomyChecksTests
using var f = new EconomyFixture();
f.Good("ore");
f.Good("coke", inputs: new[] { ("ore", 1) });
f.Good("plate", inputs: new[] { ("ore", 1) }, fills: new[] { "Plate" }, upkeep: new[] { ("coke", 1) });
f.Good("fuel", inputs: new[] { ("coke", 1) });
f.Good("plate", inputs: new[] { ("ore", 1) }, fills: new[] { "Plate" }, upkeep: new[] { ("fuel", 1) });
f.Hull("tug", HullType.Ship, new[] { ("Plate", 1) });
var fault = Assert.Single(EconomyChecks.ConsumerDemand(f.Cache));
Assert.Equal("recipe:commodity:coke", fault.Record);
Assert.Contains("'coke' leads into upkeep but no use leads into a consumer good.", fault.Message);
// Fuel is upkeep itself; coke is shared because a good it leads into is.
var faults = EconomyChecks.ConsumerDemand(f.Cache);
Assert.Equal(new[] { "recipe:commodity:coke", "recipe:commodity:fuel" }, faults.Select(x => x.Record));
Assert.Contains("'fuel' leads into upkeep but no use leads into a consumer good.", faults[1].Message);
f.Good("pot", roles: new[] { ("Casing", 1) });
f.EditRecipe("commodity:coke", r => r.Fills.Add("Casing"));
f.EditRecipe("commodity:fuel", r => r.Fills.Add("Casing"));
Assert.Empty(EconomyChecks.ConsumerDemand(f.Cache));
}