-- AD Chain Model (ren logik) -- Sporer baglæns fra de produktioner spilleren selv har tændt (rødder) gennem gårdens egne fabrikker og stalde -- til råvarerne. Alle mængder er liter pr. måned; varer og afgrøder er NAVNE. Se spec 2026-09-30-produktionskaeder. ADChainModel = {} ADChainModel.MAX_DEPTH = 8 local function byKey(a, b) return tostring(a.key) < tostring(b.key) end -- producenter pr. vare: tændte først, så stabil rækkefølge local function indexProducers(recipes) local index = {} for _, recipe in ipairs(recipes or {}) do for name, amount in pairs(recipe.outputs or {}) do if (amount or 0) > 0 and (recipe.cyclesPerMonth or 0) > 0 then index[name] = index[name] or {} table.insert(index[name], recipe) end end end for _, list in pairs(index) do table.sort(list, function(a, b) if (a.enabled == true) ~= (b.enabled == true) then return a.enabled == true end return byKey(a, b) end) end return index end local function indexHerds(husbandry) local index = {} for _, herd in ipairs(husbandry or {}) do for name, perMonth in pairs(herd.outputs or {}) do if (perMonth or 0) > 0 then index[name] = index[name] or {} table.insert(index[name], {building = herd.building, buildingName = herd.buildingName, left = perMonth}) end end end return index end local function sortedNames(map) local names = {} for name in pairs(map or {}) do table.insert(names, name) end table.sort(names) return names end function ADChainModel.build(recipes, husbandry, crops) local chain = {roots = {}, demand = {}, fieldDemand = {}, needRecipes = {}, problems = {}} local producers, herds = indexProducers(recipes), indexHerds(husbandry) local capacity, roots = {}, {} for _, recipe in ipairs(recipes or {}) do capacity[recipe.key] = recipe.cyclesPerMonth or 0 if recipe.enabled and not recipe.byChain then table.insert(roots, recipe) end end table.sort(roots, byKey) for _, root in ipairs(roots) do capacity[root.key] = 0 end -- rødderne kører for sig selv local function problem(kind, fillType, rootKey, detail) table.insert(chain.problems, {kind = kind, fillType = fillType, rootKey = rootKey, detail = detail}) end local need local function inputsOf(recipe, cycles, depth, path, rootKey) local children = {} for _, name in ipairs(sortedNames(recipe.inputs)) do local amount = recipe.inputs[name] or 0 if amount > 0 and cycles > 0 then local child = need(name, amount * cycles, depth + 1, path, rootKey) child.consumer, child.consumerName = recipe.building, recipe.buildingName table.insert(children, child) end end return children end need = function(name, perMonth, depth, path, rootKey) local node = {fillType = name, perMonth = perMonth, coveredPerMonth = 0, producers = {}, herds = {}, children = {}} chain.demand[name] = (chain.demand[name] or 0) + perMonth if depth > ADChainModel.MAX_DEPTH or path[name] then node.source = "cycle" problem("cycle", name, rootKey) return node end path[name] = true local rest = perMonth -- a) gårdens fabrikker local makers = producers[name] if makers ~= nil then node.source = "production" for _, recipe in ipairs(makers) do local perCycle = recipe.outputs[name] local cycles = math.min(rest / perCycle, capacity[recipe.key] or 0) if cycles > 0 then capacity[recipe.key] = capacity[recipe.key] - cycles rest = rest - cycles * perCycle chain.needRecipes[recipe.key] = (chain.needRecipes[recipe.key] or 0) + cycles table.insert(node.producers, {key = recipe.key, building = recipe.building, buildingName = recipe.buildingName, cycles = cycles}) for _, child in ipairs(inputsOf(recipe, cycles, depth, path, rootKey)) do table.insert(node.children, child) end end if rest <= 1e-9 then break end end if rest > 1e-9 and #node.producers > 0 then problem("bottleneck", name, rootKey, rest) node.shortPerMonth = rest end end -- b) stalde if rest > 1e-9 and #node.producers == 0 and herds[name] ~= nil then node.source = "husbandry" for _, herd in ipairs(herds[name]) do local take = math.min(rest, herd.left) if take > 0 then herd.left = herd.left - take rest = rest - take table.insert(node.herds, {building = herd.building, buildingName = herd.buildingName, perMonth = take}) end end end -- c) mark if rest > 1e-9 and #node.producers == 0 and #node.herds == 0 and crops ~= nil and crops[name] ~= nil then node.source, node.fruit = "field", crops[name] chain.fieldDemand[node.fruit] = (chain.fieldDemand[node.fruit] or 0) + rest rest = 0 end -- d) ingen kilde (eller rest efter stald) if rest > 1e-9 and (node.source == nil or node.source == "husbandry") then if node.source == nil then node.source = "buy" end node.shortPerMonth = rest problem("buy", name, rootKey, rest) end node.coveredPerMonth = perMonth - (node.shortPerMonth or 0) path[name] = nil return node end for _, root in ipairs(roots) do local cycles = root.cyclesPerMonth or 0 table.insert(chain.roots, {key = root.key, name = root.name, building = root.building, buildingName = root.buildingName, children = inputsOf(root, cycles, 0, {}, root.key)}) end return chain end