-- ATM10 compact AE2 pattern audit via Advanced Peripherals ME Bridge -- Read-only: does not craft, export, import, cancel or modify patterns. local VERSION = "1.1.0" local OUTPUT_FILE = "me_pattern_debug.txt" local SAMPLE_COUNT = 3 local SAMPLE_LIMIT = 8000 local LINE_LIMIT = 500 local bridgeName local function hasType(name, wanted) if peripheral.hasType then return peripheral.hasType(name, wanted) end return peripheral.getType(name) == wanted end for _, name in ipairs(peripheral.getNames()) do if hasType(name, "me_bridge") then bridgeName = name break end end if not bridgeName then error("Keine ME Bridge am Computer gefunden", 0) end local function sortedKeys(value) local keys = {} for key in pairs(value or {}) do keys[#keys + 1] = key end table.sort(keys, function(a, b) return tostring(a) < tostring(b) end) return keys end local function compact(value, depth, seen) depth, seen = depth or 0, seen or {} local kind = type(value) if kind == "string" then return string.format("%q", value) end if kind ~= "table" then return tostring(value) end if seen[value] then return "" end if depth >= 8 then return "" end seen[value] = true local parts = {} for _, key in ipairs(sortedKeys(value)) do parts[#parts + 1] = "[" .. compact(key, depth + 1, seen) .. "]=" .. compact(value[key], depth + 1, seen) end seen[value] = nil return "{" .. table.concat(parts, ",") .. "}" end local function truncate(value, limit) value, limit = tostring(value), limit or LINE_LIMIT if #value <= limit then return value end return value:sub(1, limit) .. "...<" .. tostring(#value - limit) .. " chars>" end local function callRead(method, ...) local result = table.pack(pcall(peripheral.call, bridgeName, method, ...)) if not result[1] then return false, tostring(result[2]) end if result.n == 2 then return true, result[2] end local values = { n = result.n - 1 } for index = 2, result.n do values[index - 1] = result[index] end return true, values end local function listValues(value) local result = {} if type(value) ~= "table" then return result end for key, entry in pairs(value) do if key ~= "n" and type(entry) == "table" then result[#result + 1] = entry end end return result end local function first(value, keys) if type(value) ~= "table" then return nil end for _, key in ipairs(keys) do if value[key] ~= nil then return value[key] end end end local function itemName(value) if type(value) == "string" then return value end if type(value) ~= "table" then return nil end local name = first(value, { "name", "id", "item", "itemId", "key" }) if type(name) == "string" then return name end if type(name) == "table" then return itemName(name) end local what = first(value, { "what", "stack", "resource" }) if what ~= nil and what ~= value then return itemName(what) end end local function itemToken(value) local name = itemName(value) if not name then return truncate(compact(value), 180) end local count = type(value) == "table" and first(value, { "count", "amount", "quantity", "size" }) or nil local token = name .. " x" .. tostring(tonumber(count) or 1) if type(value) == "table" then local components = first(value, { "components", "nbt", "tag" }) if components ~= nil then token = token .. " " .. truncate(compact(components), 120) end end return token end local function sideEntries(side) if side == nil then return {} end if type(side) ~= "table" or itemName(side) then return { itemToken(side) } end local entries = {} for _, key in ipairs(sortedKeys(side)) do if key ~= "n" then entries[#entries + 1] = tostring(key) .. ":" .. itemToken(side[key]) end end return entries end local function sideText(side) local entries = sideEntries(side) return #entries > 0 and table.concat(entries, " + ") or "-" end local INPUT_KEYS = { "inputs", "input", "ingredients" } local OUTPUT_KEYS = { "outputs", "output", "results", "result" } local function patternInputs(pattern) return first(pattern, INPUT_KEYS) end local function patternOutputs(pattern) return first(pattern, OUTPUT_KEYS) end local function patternType(pattern) local value = first(pattern, { "type", "patternType", "crafting", "processing", "isCrafting" }) return value == nil and "unknown" or tostring(value) end local function providerText(pattern) local value = first(pattern, { "provider", "providerName", "patternProvider", "machine", "location", "pos", "position" }) return value == nil and "" or truncate(compact(value), 180) end local function exactFingerprint(pattern) return patternType(pattern) .. "|IN=" .. table.concat(sideEntries(patternInputs(pattern)), "|") .. "|OUT=" .. table.concat(sideEntries(patternOutputs(pattern)), "|") end local function outputFingerprint(pattern) return table.concat(sideEntries(patternOutputs(pattern)), "|") end local okPatterns, rawPatterns = callRead("getPatterns") if not okPatterns then okPatterns, rawPatterns = callRead("getPatterns", {}) end if not okPatterns then error("getPatterns fehlgeschlagen: " .. tostring(rawPatterns), 0) end local patterns = listValues(rawPatterns) local exactGroups, outputGroups = {}, {} for index, pattern in ipairs(patterns) do local exact, output = exactFingerprint(pattern), outputFingerprint(pattern) exactGroups[exact] = exactGroups[exact] or {} exactGroups[exact][#exactGroups[exact] + 1] = index if output ~= "" then outputGroups[output] = outputGroups[output] or {} outputGroups[output][#outputGroups[output] + 1] = index end end local function duplicatesOf(groups) local duplicates = {} for signature, indexes in pairs(groups) do if #indexes > 1 then duplicates[#duplicates + 1] = { signature = signature, indexes = indexes } end end table.sort(duplicates, function(a, b) if #a.indexes == #b.indexes then return a.signature < b.signature end return #a.indexes > #b.indexes end) return duplicates end local exactDuplicates = duplicatesOf(exactGroups) local outputDuplicates = duplicatesOf(outputGroups) if fs.exists(OUTPUT_FILE) then fs.delete(OUTPUT_FILE) end local file = assert(fs.open(OUTPUT_FILE, "w")) local function write(value) file.writeLine(tostring(value)) end write("ATM10 AE2 COMPACT PATTERN AUDIT V" .. VERSION) write("Computer ID: " .. tostring(os.getComputerID())) write("Computer label: " .. tostring(os.getComputerLabel() or "")) write("ME Bridge: " .. bridgeName) write("Pattern records: " .. tostring(#patterns)) write("Exact duplicate groups: " .. tostring(#exactDuplicates)) write("Same-output groups: " .. tostring(#outputDuplicates)) write("") write("EXACT DUPLICATES") write(string.rep("=", 72)) if #exactDuplicates == 0 then write("None") end for groupIndex, group in ipairs(exactDuplicates) do write("GROUP " .. groupIndex .. " | copies=" .. #group.indexes .. " | indexes=" .. table.concat(group.indexes, ",")) write(truncate(group.signature)) for _, index in ipairs(group.indexes) do write(" #" .. index .. " provider=" .. providerText(patterns[index])) end end write("") write("SAME OUTPUT (CHECK MANUALLY; MAY BE INTENTIONAL)") write(string.rep("=", 72)) if #outputDuplicates == 0 then write("None") end for groupIndex, group in ipairs(outputDuplicates) do write("GROUP " .. groupIndex .. " | recipes=" .. #group.indexes .. " | indexes=" .. table.concat(group.indexes, ",") .. " | output=" .. truncate(group.signature, 220)) end write("") write("COMPACT PATTERN INDEX") write(string.rep("=", 72)) for index, pattern in ipairs(patterns) do write("#" .. index .. " | " .. patternType(pattern) .. " | IN " .. truncate(sideText(patternInputs(pattern)), 220) .. " | OUT " .. truncate(sideText(patternOutputs(pattern)), 220) .. " | PROVIDER " .. providerText(pattern)) end write("") write("RAW SCHEMA SAMPLES") write(string.rep("=", 72)) for index = 1, math.min(SAMPLE_COUNT, #patterns) do write("SAMPLE #" .. index) write(truncate(compact(patterns[index]), SAMPLE_LIMIT)) end file.close() print("Kompakter ME Pattern Audit abgeschlossen.") print("Pattern: " .. tostring(#patterns)) print("Exakte Duplikat-Gruppen: " .. tostring(#exactDuplicates)) print("Gleicher Output: " .. tostring(#outputDuplicates)) print("Erstellt: " .. OUTPUT_FILE) print("Bitte danach Codex 'fertig' schreiben.")