diff --git a/FS25_ADSmartPickup/adChainModel.lua b/FS25_ADSmartPickup/adChainModel.lua
new file mode 100644
index 0000000..44bdb83
--- /dev/null
+++ b/FS25_ADSmartPickup/adChainModel.lua
@@ -0,0 +1,145 @@
+-- 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
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index e441228..8a16652 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -55,6 +55,7 @@
+