Files
fs25-adsmartpickup/tests/test_adRunsController.lua
T

707 lines
47 KiB
Lua

-- Kør: luajit tests/test_adRunsController.lua (fra repo-roden)
-- Controlleren oversætter et run til AutoDrive-tilstand pr. traktor. Mock'er AD + de
-- få ADSmartPickup-opslag den bruger.
local STRAW, TMR, WATER, MILK = 31, 116, 8, 30
local FILL_TYPE_NAMES = {[STRAW] = "STRAW", [TMR] = "FORAGE", [WATER] = "WATER", [MILK] = "MILK"}
local FILL_TYPE_INDEX = {}
for index, name in pairs(FILL_TYPE_NAMES) do FILL_TYPE_INDEX[name] = index end
g_fillTypeManager = {
getFillTypeNameByIndex = function(_, index) return FILL_TYPE_NAMES[index] end,
getFillTypeIndexByName = function(_, name) return FILL_TYPE_INDEX[name] end,
getFillTypes = function()
return {
{index = STRAW, name = "STRAW", title = "Halm"},
{index = TMR, name = "FORAGE", title = "TMR"},
{index = WATER, name = "WATER", title = "Vand"},
{index = MILK, name = "MILK", title = "Mælk"},
}
end,
}
local logLines = {}
Logging = {
info = function(fmt, ...) table.insert(logLines, string.format(fmt, ...)) end,
warning = function(fmt, ...) table.insert(logLines, "WARN " .. string.format(fmt, ...)) end,
}
Utils = {prependedFunction = function(original, extra) return function(...) extra(...); return original(...) end end}
ItemSystem = {save = function() end}
g_currentMission = {getFarmId = function() return 1 end, missionInfo = {}}
-- ---- AutoDrive-mock ----
local RT_NONE, RT_ONLYPICKUP = 1, 2
local MODE_PICKUPANDDELIVER = 2
local vehicles, markers, wayPoints = {}, {}, {}
FS25_AutoDrive = {
AutoDrive = {
RT_NONE = RT_NONE, RT_ONLYPICKUP = RT_ONLYPICKUP, MODE_PICKUPANDDELIVER = MODE_PICKUPANDDELIVER,
settings = {
useFolders = {values = {false, true}, current = 1, isVehicleSpecific = false},
rotateTargets = {values = {1, 2, 3, 4}, current = 1, isVehicleSpecific = true},
ALUnload = {values = {0, 1, 2, 3, 4}, current = 1, isVehicleSpecific = true},
},
getAllVehicles = function() return vehicles end,
getAllUnits = function(vehicle) return vehicle.trailers end,
getAllFillLevels = function(trailers) return trailers.level or 0, trailers.capacity or 0, false, 0 end,
getSupportedFillTypesOfAllUnitsAlphabetically = function(vehicle) return vehicle.carriable or {} end,
},
ADGraphManager = {
getMapMarkers = function() return markers end,
getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end,
getWayPointById = function(_, id) return wayPoints[id] end,
getDistanceFromNetwork = function(_, vehicle) return vehicle.networkDistance or 0 end,
},
}
local function addMarker(id, name, group, x, z)
wayPoints[id] = {x = x, z = z, incoming = {}}
table.insert(markers, {id = id, markerIndex = #markers + 1, name = name, group = group})
end
-- ADSmartPickup-opslag: hvilke waypoints har hvilken slags station, og hvad målet tager
local unloadAt, loadAt = {}, {}
ADSmartPickup = {
getFarmId = function(vehicle) return vehicle.farmId end,
getUnloadStationAtWayPointId = function(_, id) return unloadAt[id] end,
getLoadStationAtWayPointId = function(_, id) return loadAt[id] end,
getStationLevelAndCapacity = function(station, fillType)
if station.accepts[fillType] == nil then return nil end
return (station.levels or {})[fillType] or 0, station.accepts[fillType]
end,
-- forenklet rangering: mest fri plads (relativt) først; fulde sidst
getTargetNeedsRanked = function(station, fillTypes)
local ranked = {}
for _, fillType in ipairs(fillTypes) do
local capacity = station.accepts[fillType]
if capacity ~= nil then
local level = (station.levels or {})[fillType] or 0
table.insert(ranked, {fillType = fillType, level = level, capacity = capacity, freeSpace = capacity - level, score = (capacity - level) / capacity})
end
end
table.sort(ranked, function(a, b) return a.score > b.score end)
return ranked
end,
}
ADSupplyPlanner = {isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end}
local function makeVehicle(uniqueId, name, carriable, capacity, x, z)
local stateModule = {selectedFillTypes = {}, fillType = 0, active = false, loopCounter = 5, loopsDone = 3, dirtyCount = 0}
stateModule.setMode = function(self, mode) self.mode = mode end
stateModule.setFirstMarkerByWayPointId = function(self, id) self.firstMarkerId = id end
stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end
stateModule.setFillType = function(self, fillType) self.fillType = fillType end
stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end
stateModule.setLoopsDone = function(self, n) self.loopsDone = n end
stateModule.isActive = function(self) return self.active end
-- Bruges af C13: getTargetFillTypes skal følge AD's LIVE mål (mock'er AD's eget getSecondMarker).
stateModule.getSecondMarker = function(self) return self.secondMarkerId ~= nil and {id = self.secondMarkerId} or nil end
local vehicle = {
uniqueId = uniqueId, farmId = 1, rootNode = uniqueId,
carriable = carriable, trailers = {level = 0, capacity = capacity},
getName = function() return name end,
getOwnerFarmId = function() return 1 end,
startAutoDrive = function(self) self.ad.stateModule.active = true; self.started = (self.started or 0) + 1 end,
getClosestWayPoint = function() return nil, 0 end,
stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = (self.stopped or 0) + 1 end,
ad = {stateModule = stateModule, settings = {rotateTargets = {values = {1, 2, 3, 4}, current = 1, new = 1, isVehicleSpecific = true}, ALUnload = {values = {0, 1, 2, 3, 4}, current = 1, new = 1, isVehicleSpecific = true}}},
}
nodePositions = nodePositions or {}
nodePositions[uniqueId] = {x = x or 0, z = z or 0}
table.insert(vehicles, vehicle)
return vehicle
end
function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0, p.z end
dofile("FS25_ADSmartPickup/adRuns.lua")
dofile("FS25_ADSmartPickup/adRunsStorage.lua")
dofile("FS25_ADSmartPickup/adWaitPool.lua")
dofile("FS25_ADSmartPickup/adRunsController.lua")
local adEnv = FS25_AutoDrive
local failures = 0
local function check(name, actual, expected)
if actual == expected then
print("OK " .. name)
else
failures = failures + 1
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
end
end
local function buildWorld()
vehicles, markers, wayPoints, unloadAt, loadAt, logLines = {}, {}, {}, {}, {}, {}
ADRunsController.store = ADRuns.newStore()
addMarker(94, "Cow 1 Food", "Cow Food", 5000, 0); unloadAt[94] = {accepts = {[STRAW] = 300000, [TMR] = 300000, [WATER] = 50000}}
addMarker(44, "10 Silo Storage", "Siloer", 0, 0); loadAt[44] = {}
addMarker(75, "TMR Load", "Fabrication", 900, 0); loadAt[75] = {}
addMarker(100, "Cow Wait", "Cow Wait", 5100, 0)
addMarker(101, "Cow Wait", "Cow Wait", 5200, 0)
addMarker(200, "F91 Wait", "F91", 8000, 0)
addMarker(300, "Pig Wait", "Pig Wait", 9000, 0)
end
-- C1: køretøjs-id og farmens AD-køretøjer
buildWorld()
local fastracA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000, 100, 0)
local fastracB = makeVehicle("vehicleB", "Fastrac B", {STRAW}, 700000, 4000, 0)
local noAd = {uniqueId = "vehicleC", getOwnerFarmId = function() return 1 end}
table.insert(vehicles, noAd)
local otherFarm = makeVehicle("vehicleD", "Nabo", {STRAW}, 1000, 0, 0); otherFarm.getOwnerFarmId = function() return 2 end
check("C1 uniqueId", ADRunsController.getVehicleId(fastracA), "vehicleA")
check("C1 fallback savegame-id", ADRunsController.getVehicleId({currentSavegameId = 7}), "7")
check("C1 intet id -> nil", ADRunsController.getVehicleId({}), nil)
check("C1 farmens AD-køretøjer", #ADRunsController.getFarmVehicles(adEnv), 2)
check("C1 opslag på id", ADRunsController.findVehicleById(adEnv, "vehicleB"), fastracB)
check("C1 ukendt id -> nil", ADRunsController.findVehicleById(adEnv, "nope"), nil)
-- C2: opret run, tildel; opslag run for køretøj; flyt mellem runs
local cow = ADRunsController.createRun("Cow Feed")
check("C2 run oprettet", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed")
check("C2 ingen tidligere", ADRunsController.assignVehicle(cow.id, fastracA), nil)
check("C2 run for køretøj", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
check("C2 ikke tildelt -> nil", ADRunsController.getRunForVehicle(fastracB), nil)
local pig = ADRunsController.createRun("Pig Feed")
check("C2 flyttet fra Cow", ADRunsController.assignVehicle(pig.id, fastracA), cow.id)
check("C2 nu i Pig", ADRunsController.getRunForVehicle(fastracA).id, pig.id)
ADRunsController.unassignVehicle(fastracA)
check("C2 fjernet", ADRunsController.getRunForVehicle(fastracA), nil)
ADRunsController.deleteRun(pig.id)
check("C2 slettet", ADRuns.getRun(ADRunsController.store, pig.id), nil)
-- C3: Auto-vareliste = vognens varer ∩ målets varer; manual = navne -> indeks (ukendte droppes)
ADRunsController.updateRun(cow.id, {targetWayPointId = 94})
local cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
check("C3 auto: halm+TMR (vand kan vognen ikke bære)", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), STRAW .. "," .. TMR)
check("C3 auto: kun halm for halm-vogn", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracB), ","), tostring(STRAW))
ADRunsController.updateRun(cow.id, {ingredientMode = "manual", ingredients = {"FORAGE", "UNKNOWN_THING"}})
cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
check("C3 manual: FORAGE -> indeks, ukendt droppes", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), tostring(TMR))
ADRunsController.updateRun(cow.id, {ingredientMode = "auto"})
local noTarget = ADRunsController.createRun("Uden mål")
check("C3 uden mål -> tom liste", #ADRunsController.getRunIngredients(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA), 0)
-- C4: validering
cowRun = ADRuns.getRun(ADRunsController.store, cow.id)
check("C4 ok", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).ok, true)
check("C4 kapacitet", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).capacity, 1000000)
local noTrailer = makeVehicle("vehicleE", "Uden vogn", {}, 0)
check("C4 ingen vogn", ADRunsController.validateVehicle(adEnv, cowRun, noTrailer).reason, "noTrailer")
local milkTruck = makeVehicle("vehicleF", "Mælkebil", {MILK}, 20000)
check("C4 kan ikke bære", ADRunsController.validateVehicle(adEnv, cowRun, milkTruck).reason, "cannotCarry")
fastracB.networkDistance = 31
check("C4 for langt fra vejnettet", ADRunsController.validateVehicle(adEnv, cowRun, fastracB).reason, "farFromNetwork")
fastracB.networkDistance = 0
check("C4 intet mål", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget")
ADRunsController.updateRun(noTarget.id, {targetWayPointId = 4242})
check("C4 mål-markør findes ikke", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget")
-- C5: Start skriver AD-tilstand og starter; ✖-traktorer springes over
ADRunsController.updateRun(cow.id, {loops = 4, waitPoolGroup = "Cow Wait"})
ADRunsController.assignVehicle(cow.id, fastracA)
ADRunsController.assignVehicle(cow.id, noTrailer)
local results = ADRunsController.startRun(adEnv, cow.id)
check("C5 to resultater", #results, 2)
check("C5 A startet", fastracA.started, 1)
check("C5 A ok", results[1].ok, true)
check("C5 uden vogn sprunget over", noTrailer.started, nil)
check("C5 uden vogn grund", results[2].reason, "noTrailer")
local state = fastracA.ad.stateModule
check("C5 mode P&D", state.mode, MODE_PICKUPANDDELIVER)
check("C5 mål = run'ets mål", state.secondMarkerId, 94)
check("C5 start-pickup = nærmeste læssestation", state.firstMarkerId, 44)
check("C5 varer = auto-listen", table.concat(state.selectedFillTypes, ","), STRAW .. "," .. TMR)
check("C5 fillType = første", state.fillType, STRAW)
check("C5 loops", state.loopCounter, 4)
check("C5 loopsDone nulstillet", state.loopsDone, 0)
check("C5 rotateTargets = kun pålæsning (current)", fastracA.ad.settings.rotateTargets.current, 2)
check("C5 rotateTargets = kun pålæsning (new)", fastracA.ad.settings.rotateTargets.new, 2)
check("C5 useFolders globalt til", adEnv.AutoDrive.settings.useFolders.current, 2)
check("C5 dirty flag", state.dirtyCount > 0, true)
check("C5 status", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1)
check("C5 total", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).total, 2)
check("C5 ukendt run -> tom", #ADRunsController.startRun(adEnv, 999), 0)
-- C6: Stop
check("C6 stoppede", ADRunsController.stopRun(adEnv, cow.id), 1)
check("C6 A stoppet", fastracA.stopped, 1)
check("C6 status 0", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 0)
check("C6 medlemskab bevaret", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
-- C7: Start på traktor der allerede kører AD (fx startet manuelt) -> stoppes og startes på run'et
fastracA.ad.stateModule.active = true
fastracA.stopped = 0
ADRunsController.startRun(adEnv, cow.id)
check("C7 kørende traktor stoppes først", fastracA.stopped, 1)
check("C7 og startes på run'et", fastracA.started, 2)
check("C7 run markeret som kørende", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, true)
ADRunsController.stopRun(adEnv, cow.id)
check("C7 stop rydder markeringen", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
-- en traktor der kører AD uden at run'et er startet af os, gør IKKE run'et "kørende"
fastracA.ad.stateModule.active = true
check("C7 manuel AD tæller ikke som kørende run", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
check("C7 men aktive tæller stadig", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1)
fastracA.ad.stateModule.active = false
-- sprungne traktorer logges med årsag
logLines = {}
ADRunsController.startRun(adEnv, cow.id)
local skippedLogged = false
for _, line in ipairs(logLines) do if line:find("sprunget over", 1, true) and line:find("noTrailer", 1, true) then skippedLogged = true end end
check("C7 sprunget over logges med årsag", skippedLogged, true)
-- kapacitet under 100 l (kun trækkerens egne tanke) = ingen vogn
local tinyCapacity = makeVehicle("vehicleTiny", "T255", {STRAW, TMR}, 1, 100, 0)
check("C7 1 l kapacitet -> noTrailer", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, cow.id), tinyCapacity).reason, "noTrailer")
-- UAL-køretøj: AutoDrive melder kapacitet i PALLER (læssede + 1), så en tom pallebil har "1" — det er ikke "ingen vogn"
adEnv.AutoDrive.hasAL = function(_, unit) return unit.ual == true end
local palletTruck = makeVehicle("vehicleDragon", "Dragon", {}, 1, 100, 0)
palletTruck.trailers[1] = {ual = true}
local truckValidation = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, cow.id), palletTruck)
check("C7 pallebil: ikke noTrailer", truckValidation.reason ~= "noTrailer", true)
check("C7 pallebil: kapacitet i paller markeret", truckValidation.isAutoload, true)
check("C7 pallebil: liter-kapacitet ukendt", truckValidation.capacity, nil)
adEnv.AutoDrive.hasAL = nil
-- C8: mål-markører (kun stalde og fabrikker — siloer tager alt og er ikke mål; stalde først,
-- så fabrikker, så navn) og Wait-puljer til dropdowns
unloadAt[94].owningPlaceable = {getHusbandryFillLevel = function() return 0 end}
addMarker(96, "8 Silo Storage", "Siloer", 100, 0); unloadAt[96] = {accepts = {[STRAW] = 1}, owningPlaceable = {spec_silo = {}}}
addMarker(97, "TMR Unload", "Fabrication", 900, 0); unloadAt[97] = {accepts = {[STRAW] = 1}, owningPlaceable = {spec_productionPoint = {}}}
addMarker(98, "Bull Food", "Cow Food", 5100, 0); unloadAt[98] = {accepts = {[STRAW] = 1}, owningPlaceable = {getHusbandryFillLevel = function() return 0 end}}
local targets = ADRunsController.getTargetMarkers(adEnv, 1)
check("C8 kun stalde og fabrikker (ikke siloen)", #targets, 3)
check("C8 stalde først, alfabetisk", targets[1].name .. "," .. targets[2].name, "Bull Food,Cow 1 Food")
check("C8 fabrik sidst", targets[3].name, "TMR Unload")
local pools = ADRunsController.getWaitPools(adEnv)
check("C8 to puljer (F91 er ikke en Wait-mappe)", #pools, 2)
check("C8 sorteret", pools[1].group, "Cow Wait")
check("C8 antal", pools[1].count, 2)
-- C9: gem/indlæs; ukendte køretøjs-id'er logges ved indlæsning men beholdes i run'et
local savedRecords
ADRunsStorage.save = function(store) savedRecords = ADRuns.toRecords(store); return true end
ADRunsStorage.load = function() return ADRuns.fromRecords(savedRecords) end
ADRunsController.assignVehicle(cow.id, {uniqueId = "vehicleGONE", ad = {}, getOwnerFarmId = function() return 1 end, getName = function() return "Væk" end})
check("C9 gemt", ADRunsController.save(), true)
ADRunsController.store = ADRuns.newStore()
ADRunsController.load(adEnv)
check("C9 indlæst", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed")
check("C9 ukendt køretøj logget", logLines[#logLines]:find("vehicleGONE", 1, true) ~= nil, true)
check("C9 run beholder kendte", ADRunsController.getRunForVehicle(fastracA).id, cow.id)
local hooked = false
ItemSystem.save = function() hooked = true end
ADRunsController.installSaveHook()
ItemSystem.save()
check("C9 save-hook kaldes før ItemSystem.save", hooked, true)
-- C11: vogn uden egen ad.settings-tabel (ingen rotateTargets pr. køretøj) -> den
-- køretøjs-specifikke indstilling skrives IKKE til den globale settings-tabel
adEnv.AutoDrive.settings.rotateTargets.current = 1
adEnv.AutoDrive.settings.useFolders.current = 1
local settingsRun = ADRunsController.createRun("Settings Test")
ADRunsController.updateRun(settingsRun.id, {targetWayPointId = 94, loops = 1})
local noSettings = makeVehicle("vehicleH", "Uden AD-settings", {STRAW}, 500000, 200, 0)
noSettings.ad.settings = {}
ADRunsController.assignVehicle(settingsRun.id, noSettings)
ADRunsController.startRun(adEnv, settingsRun.id)
check("C11 global rotateTargets urørt", adEnv.AutoDrive.settings.rotateTargets.current, 1)
check("C11 useFolders global stadig skrevet", adEnv.AutoDrive.settings.useFolders.current, 2)
check("C11 køretøjet startet", noSettings.started, 1)
-- C12: startAutoDrive() kaster for én traktor -> pcall'et applyAndStart fanger fejlen,
-- resultatet markeres ok=false/reason="error", og de ØVRIGE traktorer i run'et startes stadig
-- (fejlen må ikke undslippe startRun-loopet).
buildWorld()
local brokenRun = ADRunsController.createRun("Broken Start")
ADRunsController.updateRun(brokenRun.id, {targetWayPointId = 94, loops = 2})
local broken = makeVehicle("vehicleBroken", "Fejl-traktor", {STRAW, TMR}, 500000, 100, 0)
broken.startAutoDrive = function() error("boom") end
local normal = makeVehicle("vehicleNormal", "Normal-traktor", {STRAW, TMR}, 500000, 200, 0)
ADRunsController.assignVehicle(brokenRun.id, broken)
ADRunsController.assignVehicle(brokenRun.id, normal)
local c12Results = ADRunsController.startRun(adEnv, brokenRun.id)
check("C12 to resultater", #c12Results, 2)
check("C12 fejl-traktor ok=false", c12Results[1].ok, false)
check("C12 fejl-traktor reason=error", c12Results[1].reason, "error")
check("C12 fejl-traktor ikke startet", broken.started, nil)
check("C12 normal-traktor ok=true", c12Results[2].ok, true)
check("C12 normal-traktor startet", normal.started, 1)
-- C13: Auto-listen følger traktorens LIVE AD-mål (stateModule:getSecondMarker()) mens den kører,
-- IKKE run'ets gemte targetWayPointId — ellers afviger listen fra det choosePickup/findSupplyPickup
-- faktisk bruger, hvis brugeren redigerer run'ets Mål mens traktoren kører.
addMarker(95, "Alt Cow Food", "Cow Food", 5300, 0)
unloadAt[95] = {accepts = {[STRAW] = 1000}}
local liveRun = ADRunsController.createRun("Live Target")
ADRunsController.updateRun(liveRun.id, {targetWayPointId = 94})
local liveRunData = ADRuns.getRun(ADRunsController.store, liveRun.id)
local liveVehicle = makeVehicle("vehicleLive", "Live-traktor", {STRAW, TMR}, 500000, 300, 0)
check("C13 inaktiv: auto-liste fra run'ets mål (94)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), STRAW .. "," .. TMR)
liveVehicle.ad.stateModule.active = true
liveVehicle.ad.stateModule.secondMarkerId = 95
check("C13 aktiv: auto-liste fra AD's live mål (95)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), tostring(STRAW))
liveVehicle.ad.stateModule.active = false
liveVehicle.ad.stateModule.secondMarkerId = nil
-- C10: målets accepterede varer (til afkrydsning i GUI'et) + køretøjs-label
buildWorld()
local labelA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000)
local labelB = makeVehicle("vehicleB", "Fastrac B", {STRAW}, 700000)
local accRun = ADRunsController.createRun("Cow Feed")
check("C10 uden mål -> tom", #ADRunsController.getTargetAcceptedFillTypes(adEnv, ADRuns.getRun(ADRunsController.store, accRun.id)), 0)
ADRunsController.updateRun(accRun.id, {targetWayPointId = 94})
local accepted = ADRunsController.getTargetAcceptedFillTypes(adEnv, ADRuns.getRun(ADRunsController.store, accRun.id))
check("C10 tre varer (halm, TMR, vand)", #accepted, 3)
check("C10 sorteret på titel", accepted[1].title .. "," .. accepted[2].title .. "," .. accepted[3].title, "Halm,TMR,Vand")
check("C10 navn med", accepted[1].name, "STRAW")
check("C10 label", ADRunsController.getVehicleLabel(adEnv, labelB), "Fastrac B (#2)")
-- C14: Start går gennem AD's mode:start(USER_PLAYER) (som HUD'en), ikke kun startAutoDrive
buildWorld()
local modeVehicle = makeVehicle("vehicleM", "Mode-traktor", {STRAW, TMR}, 1000000, 100, 0)
local modeStarts = {}
modeVehicle.ad.stateModule.getCurrentMode = function() return {start = function(_, user) table.insert(modeStarts, user); modeVehicle:startAutoDrive() end} end
modeVehicle.ad.stateModule.setActualFarmId = function(self, farmId) self.actualFarmId = farmId end
adEnv.AutoDrive.USER_PLAYER = 1
local modeRun = ADRunsController.createRun("Mode")
ADRunsController.updateRun(modeRun.id, {targetWayPointId = 94})
ADRunsController.assignVehicle(modeRun.id, modeVehicle)
ADRunsController.startRun(adEnv, modeRun.id)
check("C14 mode:start kaldt med USER_PLAYER", modeStarts[1], 1)
check("C14 traktor aktiv", modeVehicle.started, 1)
check("C14 farm-id sat", modeVehicle.ad.stateModule.actualFarmId, 1)
check("C14 loopsDone nulstillet", modeVehicle.ad.stateModule.loopsDone, 0)
-- C15: aktivitet til overblikket — stoppet / venter / med læs / tom på vej efter vare
ADUnloadWait = {getWaitFor = function(v) return v.waitInfo end, getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end}
ADSmartPickup.getActivePickup = function(v) return v.pickupInfo end
buildWorld()
local actVehicle = makeVehicle("vehicleAct", "Akt", {STRAW, TMR}, 1000000, 100, 0)
check("C15 stoppet", ADRunsController.getVehicleActivity(adEnv, actVehicle).kind, "stopped")
actVehicle.ad.stateModule.active = true
actVehicle.ad.stateModule.secondMarkerId = 94
actVehicle.loadType, actVehicle.loadLiters = STRAW, 8000
local loaded = ADRunsController.getVehicleActivity(adEnv, actVehicle)
check("C15 med læs", loaded.kind .. "/" .. loaded.fillType .. "/" .. loaded.liters, "loaded/" .. STRAW .. "/8000")
actVehicle.loadLiters = 0
actVehicle.pickupInfo = {markerName = "Silo 3", fillType = TMR}
local empty = ADRunsController.getVehicleActivity(adEnv, actVehicle)
check("C15 tom på vej efter TMR", empty.kind .. "/" .. tostring(empty.place) .. "/" .. tostring(empty.fillType), "empty/Silo 3/" .. TMR)
actVehicle.waitInfo = {markerName = "Cow Wait", fillType = STRAW, rest = 5000}
check("C15 venter", ADRunsController.getVehicleActivity(adEnv, actVehicle).kind, "waiting")
ADUnloadWait = nil
-- C16: flere mål pr. run — Auto-varer = union; chooseTarget = den stald der mangler mest; validering
buildWorld()
addMarker(99, "Cow 2 Food", "Cow Food", 5200, 0); unloadAt[99] = {accepts = {[STRAW] = 300000, [TMR] = 300000}, levels = {[STRAW] = 0, [TMR] = 0}}
unloadAt[94].levels = {[STRAW] = 250000, [TMR] = 250000, [WATER] = 50000}
local multiVehicle = makeVehicle("vehicleMulti", "Multi", {STRAW, TMR, WATER}, 1000000, 100, 0)
local multi = ADRunsController.createRun("Kvæg")
ADRunsController.updateRun(multi.id, {targetWayPointIds = {94, 99}})
local multiRun = ADRuns.getRun(ADRunsController.store, multi.id)
check("C16 auto-varer = union (vand kun i stald 1)", table.concat(ADRunsController.getRunIngredients(adEnv, multiRun, multiVehicle), ","), WATER .. "," .. STRAW .. "," .. TMR)
check("C16 accepterede varer = union", #ADRunsController.getTargetAcceptedFillTypes(adEnv, multiRun), 3)
check("C16 vælger stald 2 (tom)", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 99)
unloadAt[99].levels = {[STRAW] = 290000, [TMR] = 290000}
check("C16 vælger stald 1 når stald 2 er fuld", ADRunsController.chooseTarget(adEnv, multiRun, multiVehicle), 94)
check("C16 validering ok med flere mål", ADRunsController.validateVehicle(adEnv, multiRun, multiVehicle).ok, true)
ADRunsController.updateRun(multi.id, {targetWayPointIds = {4242}})
check("C16 kun ukendte mål -> noTarget", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, multi.id), multiVehicle).reason, "noTarget")
-- C17: kørende traktor der fjernes fra run stoppes; slettes run, stoppes dets traktorer; Stop logger
buildWorld()
local runVehicle = makeVehicle("vehicleR", "Kørende", {STRAW, TMR}, 1000000, 100, 0)
local stopRun = ADRunsController.createRun("Stop-test")
ADRunsController.updateRun(stopRun.id, {targetWayPointId = 94})
ADRunsController.assignVehicle(stopRun.id, runVehicle)
ADRunsController.startRun(adEnv, stopRun.id)
check("C17 kører", runVehicle.started, 1)
ADRunsController.unassignVehicle(runVehicle)
check("C17 fjernet fra run -> stoppet", runVehicle.stopped, 1)
ADRunsController.assignVehicle(stopRun.id, runVehicle)
ADRunsController.startRun(adEnv, stopRun.id)
ADRunsController.deleteRun(stopRun.id, adEnv)
check("C17 run slettet -> stoppet", runVehicle.stopped, 2)
check("C17 stop logget", logLines[#logLines]:find("stoppet", 1, true) ~= nil, true)
-- C18: forkert last ved Start (halm på vognen, run mod sukkerfabrik) -> første stop = silo der tager halm
ADUnloadWait = {getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end, getWaitFor = function() return nil end,
hasRouteBothWays = function() return true end}
ADLoadSwap = {findDumpMarker = function(_, v, fillType, rest, homeId, isReachable) return {id = 44, name = "10 Silo Storage", markerIndex = 2} end}
buildWorld()
addMarker(77, "sugar offload", "Fabrication", 950, 0); unloadAt[77] = {accepts = {[TMR] = 360000}} -- "fabrik" der ikke tager halm (TMR står for roer her)
local sugarVehicle = makeVehicle("vehicleSugar", "Roe-traktor", {STRAW, TMR}, 20000, 100, 0)
sugarVehicle.loadType, sugarVehicle.loadLiters = STRAW, 8000
local sugar = ADRunsController.createRun("Sugar")
ADRunsController.updateRun(sugar.id, {targetWayPointId = 77})
ADRunsController.assignVehicle(sugar.id, sugarVehicle)
ADRunsController.startRun(adEnv, sugar.id)
check("C18 første stop = siloen (aflæs forkert last)", sugarVehicle.ad.stateModule.secondMarkerId, 44)
check("C18 logget", logLines[#logLines]:find("læsser", 1, true) ~= nil or logLines[#logLines - 1]:find("læsser", 1, true) ~= nil, true)
-- rigtig last (TMR=roer) -> direkte til fabrikken
sugarVehicle.loadType = TMR
sugarVehicle.ad.stateModule.active = false
ADRunsController.startRun(adEnv, sugar.id)
check("C18 rigtig last -> målet", sugarVehicle.ad.stateModule.secondMarkerId, 77)
ADUnloadWait = nil; ADLoadSwap = nil
-- C19: Wait-markører er aldrig mål — heller ikke når de står inden for 40 m af staldens trigger
buildWorld()
-- staldens station med bygning; Wait-markøren ligger i triggerens rækkevidde og "mangler mere" end 94
local barnPlaceable = {getHusbandryFillLevel = function() return 0 end}
unloadAt[94].owningPlaceable = barnPlaceable
addMarker(400, "Cow Wait", "Cow Wait", 5005, 0); unloadAt[400] = {accepts = {[STRAW] = 300000, [TMR] = 300000}, levels = {}, owningPlaceable = barnPlaceable}
local targetNames = {}
for _, marker in ipairs(ADRunsController.getTargetMarkers(adEnv, 1)) do table.insert(targetNames, marker.name) end
check("C19 Wait-markør ikke i mål-listen", table.concat(targetNames, ","):find("Cow Wait", 1, true), nil)
local waitTargetRun = ADRunsController.createRun("Wait som mål")
ADRunsController.updateRun(waitTargetRun.id, {targetWayPointIds = {94, 400}})
local waitVehicle = makeVehicle("vehicleW", "W", {STRAW, TMR}, 20000, 5000, 0)
check("C19 chooseTarget springer Wait over", ADRunsController.chooseTarget(adEnv, ADRuns.getRun(ADRunsController.store, waitTargetRun.id), waitVehicle), 94)
-- C-out: udkørsels-runs — oprettelse, kilder, produkter, validering, start, status
local BASKET = 7
FILL_TYPE_NAMES[BASKET] = "STRAW_BASKET"; FILL_TYPE_INDEX["STRAW_BASKET"] = BASKET
local sourcesAt, productsAt = {}, {}
ADSources = {
identify = function(_, id) return sourcesAt[id] end,
getProducts = function(_, source) return productsAt[source.wayPointId] or {} end,
getSellMarkers = function() return {} end,
stationAccepts = function() return false end,
getSourcePlaceables = function() return {} end,
}
local outboundStatus = {reasons = {"SUGAR 78 % af top (kræver 90), lager 41 %"}, warning = nil}
local holdFor = {}
ADOutbound = {
isOutboundVehicle = function(v) local r = ADRunsController.getRunForVehicle(v) return r ~= nil and r.kind == "outbound" end,
getRunStatus = function() return outboundStatus end,
getHoldFor = function(v) return holdFor[v] end,
getTripFor = function() return nil end,
releaseInactive = function() end,
}
ADUnloadWait = {getRestLoad = function() return nil, 0 end, getWaitFor = function() return nil end}
buildWorld()
addMarker(1, "Suger Load", "Pickup points", 100, 0); loadAt[1] = {}
addMarker(3, "Objektlager", "Pickup points", 300, 0)
addMarker(10, "selling station", "selling", 500, 0)
sourcesAt[1] = {kind = "factory", wayPointId = 1, station = loadAt[1], name = "Sugar mill"}
sourcesAt[3] = {kind = "objectStorage", wayPointId = 3, name = "Objektlager"}
productsAt[1] = {{fillType = TMR, name = "FORAGE", title = "TMR", available = 150000, level = 150000, capacity = 180000, isPallet = false}}
productsAt[3] = {{fillType = BASKET, name = "STRAW_BASKET", title = "Halmkurve", available = 8000, level = 8, capacity = 4000, isPallet = true}}
local bulkTractor = makeVehicle("vehicleBulk", "MT635", {STRAW, TMR}, 20000, 100, 0)
local ualTractor = makeVehicle("vehicleUal", "Fendt", {}, 8000, 300, 0)
ualTractor.trailers[1] = {ual = true}
adEnv.AutoDrive.hasAL = function(_, trailer) return trailer.ual == true end
local out = ADRunsController.createRun("Sugar out", "outbound")
check("Cout1 kind", ADRuns.getRun(ADRunsController.store, out.id).kind, "outbound")
check("Cout1 supply-run har kind supply", ADRunsController.createRun("Cows").kind, "supply")
check("Cout1 kilde-markører", #ADRunsController.getSourceMarkers(adEnv, 1), 2)
check("Cout1 første kilde-markør", ADRunsController.getSourceMarkers(adEnv, 1)[1].marker.name, "Objektlager")
-- Cout2: validering
local outRun = ADRuns.getRun(ADRunsController.store, out.id)
-- diagnose: hvad moden ser på enhederne, når ingen autoload findes (logges én gang pr. traktor,
-- første gang varerne slås op — dvs. allerede ved noProducts-tjekket nedenfor)
logLines = {}
bulkTractor.trailers[1] = {spec_universalAutoload = {isAutoloadAvailable = false, autoloadDisabled = true}, getName = function() return "Vogn" end}
check("Cout2 ingen kilder -> noSource", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "noSource")
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, out.id, 1)
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, out.id, 3)
outRun = ADRuns.getRun(ADRunsController.store, out.id)
check("Cout2 ingen vare valgt -> noProducts", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "noProducts")
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, out.id, "STRAW_BASKET", "sell")
outRun = ADRuns.getRun(ADRunsController.store, out.id)
check("Cout2 kun paller uden UAL -> needsAutoload", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).reason, "needsAutoload")
local diag = nil
for _, line in ipairs(logLines) do if line:find("ingen autoload", 1, true) then diag = line end end
check("Cout2 diagnose logges", diag ~= nil, true)
check("Cout2 diagnose viser UAL-flag", diag ~= nil and diag:find("isAutoloadAvailable=false", 1, true) ~= nil, true)
logLines = {}
ADRunsController.validateVehicle(adEnv, outRun, bulkTractor)
check("Cout2 diagnose kun én gang", #logLines, 0)
bulkTractor.trailers[1] = nil
check("Cout2 UAL-vogn ok", ADRunsController.validateVehicle(adEnv, outRun, ualTractor).ok, true)
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, out.id, "FORAGE", "store")
outRun = ADRuns.getRun(ADRunsController.store, out.id)
check("Cout2 bulk-vogn ok med TMR", ADRunsController.validateVehicle(adEnv, outRun, bulkTractor).ok, true)
local strawOnly = makeVehicle("vehicleStraw", "Halm", {STRAW}, 20000, 100, 0)
check("Cout2 kan ikke bære -> cannotCarry", ADRunsController.validateVehicle(adEnv, outRun, strawOnly).reason, "cannotCarry")
-- Cout3: start sætter mode, 1./2. markør = første kilde, varevalg = det vognen kan bære af run'ets varer
ADRunsController.assignVehicle(out.id, bulkTractor)
ADRunsController.assignVehicle(out.id, ualTractor)
local results = ADRunsController.startRun(adEnv, out.id)
check("Cout3 to startet", #results, 2)
check("Cout3 mode P&D", bulkTractor.ad.stateModule.mode, MODE_PICKUPANDDELIVER)
check("Cout3 1. markør = første kilde", bulkTractor.ad.stateModule.firstMarkerId, 1)
check("Cout3 2. markør = første kilde (pladsholder)", bulkTractor.ad.stateModule.secondMarkerId, 1)
check("Cout3 bulk-vogn: kun TMR", table.concat(bulkTractor.ad.stateModule.selectedFillTypes, ","), tostring(TMR))
check("Cout3 UAL-vogn: kurve", table.concat(ualTractor.ad.stateModule.selectedFillTypes, ","), tostring(BASKET))
check("Cout3 rotation kun pålæsning", bulkTractor.ad.settings.rotateTargets.current, 2)
check("Cout3 startet", bulkTractor.started, 1)
-- AD's "AL unload" er slået FRA som standard (values[1] = 0): autoload-vognen ville aldrig læsse af
-- ved salgsstedet. Ved Start sættes den til "center" hvis den er fra; et brugervalg (left/right/behind) røres ikke.
check("Cout3 ALUnload sat til center for autoload-bil", ualTractor.ad.settings.ALUnload.current, 2)
check("Cout3 ALUnload rører ikke tipvogn", bulkTractor.ad.settings.ALUnload.current, 1)
ualTractor.ad.settings.ALUnload.current = 4
ualTractor.ad.stateModule.active = false
ADRunsController.startRun(adEnv, out.id)
check("Cout3 ALUnload brugervalg bevares", ualTractor.ad.settings.ALUnload.current, 4)
-- Cout4: produkter til fanen (politik slået op, sorteret på titel) og status
local list = ADRunsController.getRunProducts(adEnv, outRun)
check("Cout4 to produkter", #list, 2)
check("Cout4 første = Halmkurve", list[1].title, "Halmkurve")
check("Cout4 politik", list[1].policy, "sell")
check("Cout4 TMR lager", list[2].policy, "store")
check("Cout4 available", list[2].available, 150000)
local st = ADRunsController.getOutboundStatus(adEnv, outRun)
check("Cout4 status-begrundelse", st.reasons[1], "SUGAR 78 % af top (kræver 90), lager 41 %")
holdFor[bulkTractor] = {markerName = "Wait 3", reasons = {"SUGAR 78 % af top (kræver 90), lager 41 %"}}
local activity = ADRunsController.getVehicleActivity(adEnv, bulkTractor)
check("Cout4 aktivitet = holder", activity.kind, "holding")
check("Cout4 aktivitet sted", activity.place, "Wait 3")
check("Cout4 udkørsel har ingen mål", #ADRunsController.getRunTargets(outRun), 0)
-- Cout5 (I2): Start kræver et Wait-punkt i puljen; Cout6 (I3): last på vognen ved Start får eget mål
ADSupplyPlanner.isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end
local noWait = ADRunsController.createRun("No wait", "outbound")
ADRunsController.store = ADRuns.toggleSource(ADRunsController.store, noWait.id, 1)
ADRunsController.store = ADRuns.setProductPolicy(ADRunsController.store, noWait.id, "FORAGE", "store")
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Pig Wait"})
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Findes ikke"})
local noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
check("Cout5 ingen Wait-punkt i puljen -> noWaitPoint", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "noWaitPoint")
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, noWait.id, {waitPoolGroup = "Cow Wait"})
noWaitRun = ADRuns.getRun(ADRunsController.store, noWait.id)
check("Cout5 Wait-punkt findes -> ok", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).ok, true)
ADUnloadWait.getRestLoad = function(_, v) return v.loadType, v.loadLiters or 0 end
ADOutbound.planCargoTarget = function(adEnv, run, vehicle, fillType) return fillType == TMR and 10 or nil end
bulkTractor.loadType, bulkTractor.loadLiters = TMR, 8000
ADRunsController.assignVehicle(noWait.id, bulkTractor)
bulkTractor.ad.stateModule.active = false
ADRunsController.startRun(adEnv, noWait.id)
check("Cout6 last på vognen -> 2. markør = lastens mål", bulkTractor.ad.stateModule.secondMarkerId, 10)
bulkTractor.loadType = STRAW
check("Cout6 last uden mål -> cargoNoTarget", ADRunsController.validateVehicle(adEnv, noWaitRun, bulkTractor).reason, "cargoNoTarget")
bulkTractor.loadType = nil
ADUnloadWait = nil; ADOutbound = nil; ADSources = nil
-- C-flow: bygninger -> markører (resolveMarkers), migration af gamle markør-runs, noMarker, flow-rækker
local buildings = {
{id = "barn1", name = "Kostald 1", kind = "husbandry", loadMarker = {id = 10, name = "cow 1 milk", distance = 5}, loadMarkers = {{id = 10, name = "cow 1 milk", distance = 5}, {id = 11, name = "cow 1 slurry", distance = 3}}, unloadMarker = {id = 94, name = "Cow 1 Food", distance = 8}, canBeSource = true, canBeTarget = true, canSell = false},
{id = "shed", name = "Palleskur", kind = "objectStorage", loadMarker = nil, unloadMarker = nil, canBeSource = true, canBeTarget = false, canSell = false},
{id = "mill", name = "Sukkerfabrik", kind = "factory", loadMarker = {id = 12694, name = "Suger Load", distance = 3}, unloadMarker = {id = 77, name = "sugar offload", distance = 4}, canBeSource = true, canBeTarget = true, canSell = false},
}
ADBuildings = {
list = function() return buildings end,
find = function(_, _, id) for _, b in ipairs(buildings) do if b.id == id then return b end end end,
byMarker = function(_, _, wayPointId, role)
for _, b in ipairs(buildings) do
if role == "unload" then
if b.unloadMarker ~= nil and b.unloadMarker.id == wayPointId then return b end
else
for _, m in ipairs(b.loadMarkers or {b.loadMarker}) do if m ~= nil and m.id == wayPointId then return b end end
end
end
end,
}
ADSources = {identify = function(_, id) return (id == 10 or id == 12694 or id == 555) and {kind = "x", wayPointId = id, name = "k"} or nil end, getProducts = function() return {} end, getSellMarkers = function() return {} end, stationAccepts = function() return false end, getSourcePlaceables = function() return {} end}
ADOutbound = {isOutboundVehicle = function() return false end, getRunStatus = function() return {reasons = {"SUGAR 78 % af top"}} end, getHoldFor = function() return nil end, getTripFor = function() return nil end, releaseInactive = function() end}
buildWorld()
addMarker(10, "cow 1 milk", "Milk", 100, 0); loadAt[10] = {}
addMarker(555, "raa markoer", "Pickup", 200, 0); loadAt[555] = {}
addMarker(77, "sugar offload", "production", 950, 0); unloadAt[77] = {accepts = {[TMR] = 360000}}
-- CF1: udkørsel: bygninger + rå markør -> sourceWayPointIds; manglende markør rapporteres
local flowOut = ADRunsController.createRun("Milk", "outbound")
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}, sourceWayPointIds = {555}})
local resolved = ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
check("CF1 kildemarkører = ALLE bygningens læssemarkører + rå", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
check("CF1 manglende markør", resolved.missing[1], "Palleskur")
-- en markør der HØRER til en bygning som ikke længere er valgt, fjernes (den er ikke rå)
ADRunsController.updateRun(flowOut.id, {sourceWayPointIds = {12694, 555}})
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
check("CF1 fravalgt bygnings markør fjernes", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
-- CF1b: gamle rå markører ryddes: ikke-kilde (identify = nil) og markør der hører til en valgt bygning
ADSources.identify = function(_, id)
if id == 10 or id == 12694 or id == 555 then return {kind = "x", wayPointId = id, name = "k"} end
if id == 556 then return {kind = "husbandry", wayPointId = id, name = "Kostald 1", placeable = "barn1-placeable"} end
return nil
end
buildings[1].placeable = "barn1-placeable"
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, sourceWayPointIds = {10, 11, 555, 556, 999}})
logLines = {}
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
check("CF1b rå ikke-kilde og dublet-bygning fjernet", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
local pruneLogged = false
for _, line in ipairs(logLines) do if line:find("gamle markører fjernet", 1, true) then pruneLogged = true end end
check("CF1b logget", pruneLogged, true)
-- CF1c: et gammelt kilde-id der ikke længere er en AD-MARKØR (AutoDrive har omnummereret waypoints)
-- fjernes, selv om punktet ligger ved en kilde — ellers kører traktoren til et tilfældigt vejpunkt
local identifyBefore = ADSources.identify
ADSources.identify = function(adEnv_, id, farmId)
if id == 10873 then return {kind = "husbandry", wayPointId = id, name = "Anden stald", placeable = "anden-stald"} end
return identifyBefore(adEnv_, id, farmId)
end
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, sourceWayPointIds = {10, 11, 555, 10873}})
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
check("CF1c vejpunkt uden markør fjernet", table.concat(ADRuns.getRun(ADRunsController.store, flowOut.id).sourceWayPointIds, ","), "10,11,555")
ADSources.identify = identifyBefore
-- CF2: forsyning: mål-bygninger -> targetWayPointIds + targetWayPointId
local flowIn = ADRunsController.createRun("Foder", "supply")
ADRunsController.updateRun(flowIn.id, {targetBuildingIds = {"barn1", "mill"}})
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
check("CF2 målmarkører", table.concat(ADRunsController.getRunTargets(ADRuns.getRun(ADRunsController.store, flowIn.id)), ","), "94,77")
check("CF2 primært mål", ADRuns.getRun(ADRunsController.store, flowIn.id).targetWayPointId, 94)
-- CF2b: gamle rå mål der ikke er en markør (slettet i AD) eller hører til en valgt bygning, ryddes
addMarker(78, "Anden stald food", "Cow Food", 3000, 0); unloadAt[78] = {accepts = {[TMR] = 1000}, owningPlaceable = "anden-stald"}
unloadAt[11062] = {accepts = {[TMR] = 1000}, owningPlaceable = "anden-stald"} -- waypoint uden markør
ADRunsController.updateRun(flowIn.id, {targetWayPointIds = {94, 77, 11062, 78}})
ADRunsController.resolveMarkers(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
check("CF2b ikke-markør fjernet, rå markør med station beholdt", table.concat(ADRunsController.getRunTargets(ADRuns.getRun(ADRunsController.store, flowIn.id)), ","), "94,77,78")
ADRunsController.updateRun(flowIn.id, {targetWayPointIds = {94, 77}})
-- CF3: migration: gamle markør-runs får bygninger
local oldOut = ADRunsController.createRun("Gammel ud", "outbound")
ADRunsController.updateRun(oldOut.id, {sourceWayPointIds = {12694, 555}})
local oldIn = ADRunsController.createRun("Gammel ind", "supply")
ADRunsController.updateRun(oldIn.id, {targetWayPointIds = {94}})
ADRunsController.migrateBuildings(adEnv)
check("CF3 ud: markør -> bygning", ADRuns.getRun(ADRunsController.store, oldOut.id).sourceBuildingIds[1], "mill")
check("CF3 ud: rå markør uden bygning beholdes", table.concat(ADRuns.getRun(ADRunsController.store, oldOut.id).sourceWayPointIds, ","), "12694,555")
check("CF3 ind: markør -> bygning", ADRuns.getRun(ADRunsController.store, oldIn.id).targetBuildingIds[1], "barn1")
ADRunsController.migrateBuildings(adEnv)
check("CF3 migration er idempotent", #ADRuns.getRun(ADRunsController.store, oldOut.id).sourceBuildingIds, 1)
-- CF4: validering: valgt bygning uden markør -> noMarker
local cfVehicle = makeVehicle("vehicleCF", "CF", {STRAW, TMR}, 20000, 100, 0)
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"shed"}, sourceWayPointIds = {}})
local cfValidation = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id), cfVehicle)
check("CF4 noMarker", cfValidation.reason, "noMarker")
check("CF4 hvilken bygning", cfValidation.missingBuilding, "Palleskur")
-- CF4b: ÉN bygning uden markør blandt flere med markør -> start alligevel, advar om den ene
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}, sourceWayPointIds = {}})
logLines = {}
local cfValidation2 = ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id), cfVehicle)
check("CF4b ikke afvist pga. markør", cfValidation2.reason ~= "noMarker", true)
local warned = false
for _, line in ipairs(logLines) do if line:find("Palleskur", 1, true) and line:find("springes over", 1, true) then warned = true end end
check("CF4b advarsel nævner bygningen", warned, true)
-- CF5: flow-række til tavlen
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1"}, products = {{fillType = "SUGAR", policy = "sell"}, {fillType = "FORAGE", policy = "store"}}, collectRound = true})
ADRunsController.assignVehicle(flowOut.id, cfVehicle)
local row = ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id))
check("CF5 vare-navne", table.concat(row.productNames, ","), "SUGAR,FORAGE")
check("CF5 fra", row.fromNames[1], "Kostald 1")
check("CF5 politikker", table.concat(row.policies, ","), "sell,store")
check("CF5 traktor", row.vehicleNames[1], "CF")
check("CF5 begrundelse", row.reason, "SUGAR 78 % af top")
check("CF5 runde", row.collectRound, true)
ADRunsController.updateRun(flowOut.id, {sourceBuildingIds = {"barn1", "shed"}})
check("CF5 manglende bygning markeres", ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowOut.id)).missing[1], "Palleskur")
local rowIn = ADRunsController.getFlowRow(adEnv, ADRuns.getRun(ADRunsController.store, flowIn.id))
check("CF5 forsyning fra = alle siloer", rowIn.fromAllSilos, true)
check("CF5 forsyning til", table.concat(rowIn.toNames, ","), "Kostald 1,Sukkerfabrik")
check("CF5 forsyning auto-varer", rowIn.productAuto, true)
ADBuildings = nil; ADSources = nil; ADOutbound = nil
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)