From a373d140e86c9c97df9447c106f9fd133e3fbbe1 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 19:45:00 +0200 Subject: [PATCH 1/8] chore: remove cube and goo programs --- CLAUDE.md | 13 +- README.md | 4 - install.lua | 10 +- programs/cube.lua | 342 ----------------- programs/goo.lua | 832 ---------------------------------------- servers/cube-boot.lua | 28 -- servers/cube-server.lua | 90 ----- startup/servers.lua | 4 +- 8 files changed, 7 insertions(+), 1316 deletions(-) delete mode 100644 programs/cube.lua delete mode 100644 programs/goo.lua delete mode 100644 servers/cube-boot.lua delete mode 100644 servers/cube-server.lua diff --git a/CLAUDE.md b/CLAUDE.md index f7c033c..d5e191d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,27 +33,16 @@ Three layers, bottom-up: - `9` — ping - `10` — router / default routing channel -- `64` — cube (deployment/control) These are duplicated as local constants across files; keep them in sync when changing. -## The `cube` deployment system - -`cube` (client `programs/cube.lua`, server `servers/cube-server.lua`) manages a cluster of "cube" machines over channel 64: - -- `cube ls` — list reachable cubes (broadcast ping; each replies with its boot command). `*` marks the local machine. -- `cube deploy` — walk the local filesystem (skipping `IGNORED_PATHS`: `/rom`, `/.cubeboot`, `/.git`, `/.gitignore`, `/startup.lua`), send every file to each remote cube via `deploy-file`, then reboot it. -- `cube set-boot [command]` — write/clear the remote's `/.cubeboot`, then reboot. Empty command deletes the boot hook. -- `cube reboot ` — remote reboot. -- `.cubeboot` holds a per-machine startup shell command, run by `servers/cube-boot.lua` at boot. - ## Boot flow `startup/servers.lua` is the entry point on each machine: it adds `/programs` to `shell.path`, then `parallel.waitForAll` runs an interactive shell alongside every server in `SERVERS`. When all stop, it reboots. ## CraftOS-PC emulation -`startup/servers.lua` detects the `periphemu` global and, when present, creates an emulated modem plus (on computer 0) a few emulated peer/router computers. Code guards CraftOS-PC quirks with `if periphemu then ... end` (e.g. `os.sleep(0.5)` after reboots in `cube.lua` to avoid crashes). Preserve these guards. +`startup/servers.lua` detects the `periphemu` global and, when present, creates an emulated modem plus (on computer 0) a few emulated peer/router computers. Preserve these guards. ## Installation / distribution diff --git a/README.md b/README.md index 09ea0ca..b3df93d 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,10 @@ wget run https://raw.githubusercontent.com/guillaumearm/cc-libs/master/install.l All servers are automatically started at boot. - `/servers/ping-server`: allows a machine to respond to a `ping` command. -- `/servers/cube-server`: allows a machine to be controllable via `cube`. -- `/servers/cube-boot.lua`: `cube` boot script. ## Programs - `router`: routes messages. You need to set up a router to use all `apis/net`-based programs and libraries. - `ping`: pings machines using `apis/net`. -- `cube`: cube client for deployment. Use the `cube help` command for more details. -- `goo`: turtle program for Just Dire Things goo block processing. ## Development See [DEVELOPMENT.md](./DEVELOPMENT.md) for development setup and workflow. diff --git a/install.lua b/install.lua index 8aec1e1..d8a18ce 100644 --- a/install.lua +++ b/install.lua @@ -1,17 +1,13 @@ -local _VERSION = '2.0.3' +local _VERSION = '2.0.4' local LIST_FILES = { -- startup 'startup/servers.lua', -- servers 'servers/ping-server.lua', - 'servers/cube-server.lua', - 'servers/cube-boot.lua', -- programs 'programs/router.lua', -- router is not in servers folder because he's not ran on every machines 'programs/ping.lua', - 'programs/cube.lua', - 'programs/goo.lua', 'programs/upgrade.lua', -- apis 'apis/net.lua', @@ -24,6 +20,10 @@ fs.delete('ping.lua') -- replaced by `programs/ping.lua` fs.delete('cube.lua') -- replaced by `programs/cube.lua` fs.delete('router.lua') -- replaced by `programs/router.lua` fs.delete('servers/cube-startup.lua'); -- replaced by `servers/cube-boot.lua` +fs.delete('programs/cube.lua'); +fs.delete('programs/goo.lua'); +fs.delete('servers/cube-server.lua'); +fs.delete('servers/cube-boot.lua'); local REPO_PREFIX = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/master/' diff --git a/programs/cube.lua b/programs/cube.lua deleted file mode 100644 index 9247a69..0000000 --- a/programs/cube.lua +++ /dev/null @@ -1,342 +0,0 @@ -local _VERSION = '2.3.0'; -local CUBE_CHANNEL = 64; - -local net = require('/apis/net')(); - -local args = table.pack(...); -local cubeCommand = args[1]; -local firstArg = args[2]; -local secondArg = args[3]; - -local function getRemainingArgs(startIndex) - local remainingArgs = {}; - - for i = startIndex, args.n do - table.insert(remainingArgs, tostring(args[i])); - end - - return table.concat(remainingArgs, ' '); -end - -local IGNORED_PATHS = { - ['/rom'] = true, - ['/.cubeboot'] = true, - ['/.git'] = true, - ['/.gitignore'] = true, - ['/startup.lua'] = true, -} - -local function isValidPath(givenPath) - return not IGNORED_PATHS[givenPath] -end - -local function getAllFiles(basePath, result) - basePath = basePath or '/' - result = result or {}; - - local fileNames = fs.list(basePath) - - for i = 1, #fileNames do - local filePath = basePath .. fileNames[i]; - local valid = isValidPath(filePath); - - if valid and fs.isDir(filePath) then - getAllFiles(filePath .. '/', result); - elseif valid and not fs.isDir(filePath) then - table.insert(result, filePath) - end - end - - return result; -end - -local function readFile(path) - local file = fs.open(path, "r"); - - if not file then - return nil; - end - - local contents = file.readAll() - file.close() - - return contents -end - ---- Pads str to length len with char from right -local leftPad = function(str, len, char) - if char == nil then char = ' ' end - local nbRepetition = len - #str; - - if nbRepetition > 0 then - return str .. string.rep(char, len - #str) - end - - return str; -end - -local function getRow(margin, str1, str2, str3) - - margin = margin or ''; - - local row1 = leftPad(margin .. tostring(str1 or ''), 8, ' ') - local row2 = leftPad(tostring(str2 or ''), 16, ' ') - local row3 = leftPad(tostring(str3 or ''), 6, ' ') - - return row1 .. row2 .. row3; -end - -local function isFlag(name) - return function(arg) - return arg == '-' .. name or arg == '--' .. name; - end -end - -local isHelpFlag = isFlag('help'); -local isVersionFlag = isFlag('version'); - -local function printUsage() - print('cube usage:') - print(); - print('\t\t\tcube ls'); - print('\t\t\tcube configure'); - print('\t\t\tcube set-boot [command]') - print('\t\t\tcube reboot ') - print('\t\t\tcube deploy') - print('\t\t\tcube version') - print('\t\t\tcube help ') -end - -local function printUsageCommand(commandName) - local function setBootUsage() - print('\t\t\tcube set-boot [command]') - print('Setup a startup shell command on a remote cube.') - end - - local USAGES = { - ls = function() - print('\t\t\tcube ls'); - print('Print all available cubes in the cluster.') - end, - configure = function() - print('\t\t\tcube configure'); - print('Setup remote slave cubes.') - end, - ["set-boot"] = setBootUsage, - ["setboot"] = setBootUsage, - ["set-start"] = setBootUsage, - ["setstart"] = setBootUsage, - ["set-startup"] = setBootUsage, - ["setstartup"] = setBootUsage, - reboot = function() - print('\t\t\tcube reboot ') - print('Reboot a cube machine.'); - end, - deploy = function() - print('\t\t\tcube deploy') - print('Transfer files on all slave cubes.') - end, - version = function() - print('\t\t\tcube version') - print('Print the program version.') - end, - help = function() - print('\t\t\tcube help ') - print('Print help on commands.') - end, - } - - local usageFn = USAGES[commandName] - - if not usageFn then - return printUsage(); - end - - return usageFn(); -end - -if cubeCommand == nil or cubeCommand == '' or isHelpFlag(cubeCommand) then - printUsage(); - return; -end - ------------- --- reboot -- ------------- -local function rebootCommand(machineId, silentReboot) - if not machineId or machineId == '' then - printUsageCommand('reboot'); - return; - end - - local ok, results, packets = net.sendMultipleRequests(CUBE_CHANNEL, 'reboot', true, machineId); - - if not ok then - error(results); - end - - for k in ipairs(results) do - local packet = packets[k]; - - if silentReboot ~= true then - print('reboot machine \'' .. tostring(packet.sourceId) .. '\''); - end - end -end - --------------- --- set-boot -- --------------- -local function setBootCommand(machineId, shellCommand) - if not machineId then - printUsageCommand('set-boot'); - return; - end - - local ok, results, packets = net.sendMultipleRequests(CUBE_CHANNEL, 'set-boot', shellCommand, machineId); - - if not ok then - error(results); - end - - for k in ipairs(results) do - local packet = packets[k]; - - if shellCommand == nil or shellCommand == '' then - print('boot DELETED'); - else - print('boot UPDATED'); - end - - rebootCommand(packet.sourceId, true); - - -- prevent CraftOS-PC crashes - if periphemu then - os.sleep(0.5) - end - end -end - ------------- --- deploy -- ------------- -local function deployCommand() - local allFiles = getAllFiles() - - -- 1. get all machine ids (except the current one) - local ok, results, packets = net.sendMultipleRequests(CUBE_CHANNEL, 'ping', 'ping'); - - if not ok then - error(results); - end - - local machineIds = {}; - - local localComputerId = os.getComputerID(); - - for k in ipairs(results) do - local packet = packets[k]; - - if packet.sourceId ~= localComputerId then - table.insert(machineIds, packet.sourceId); - end - end - - -- 2. transfer files on all concerned machines - for machineIndex = 1, #machineIds do - local machineId = machineIds[machineIndex]; - - local fileTransfered = 0; - - for i = 1, #allFiles do - local filePath = allFiles[i]; - local fileContent = readFile(filePath) - - local transferOk, res = net.sendRequest(CUBE_CHANNEL, 'deploy-file', { path = filePath, content = fileContent }, machineId); - - if transferOk and res then - fileTransfered = fileTransfered + 1; - else - print('Error transfering file \'' .. filePath .. '\''); - end - end - - print(tostring(fileTransfered) .. ' file(s) transfered on machine ' .. tostring(machineId)) - - rebootCommand(machineId, true); - - -- prevent CraftOS-PC crashes - if periphemu then - os.sleep(0.5) - end - end -end - -local COMMANDS = { - ls = function() - local ok, results, packets = net.sendMultipleRequests(CUBE_CHANNEL, 'ping', 'ping'); - - if not ok then - error(results); - end - - -- print('ID LABEL\t\t\t\tSTARTUP'); - print(getRow(' ', 'ID', 'LABEL', 'BOOT')) - print('--------------------------------------------') - - local localMachineId = os.getComputerID(); - - for k in ipairs(results) do - local result = results[k]; - local packet = packets[k]; - - local prefix = ' '; - - if packet.sourceId == localMachineId then - prefix = '* ' - end - - print(getRow(prefix, packet.sourceId, packet.sourceLabel, result.startup)) - end - end, - configure = function() - print('not implemented yet.'); - end, - ["set-boot"] = setBootCommand, - ["setboot"] = setBootCommand, - ["set-start"] = setBootCommand, - ["setstart"] = setBootCommand, - ["set-startup"] = setBootCommand, - ["setstartup"] = setBootCommand, - reboot = rebootCommand, - deploy = deployCommand, - version = function() - print('cube client v' .. _VERSION); - end, - help = function(commandName) - printUsageCommand(commandName); - end -} - -local cmd; -if isVersionFlag(cubeCommand) then - cmd = COMMANDS.version; -else - cmd = COMMANDS[cubeCommand]; -end - -if not cmd then - printUsage(); - return; -end - -if (isHelpFlag(firstArg)) then - printUsageCommand(cubeCommand); - return; -end - -if cmd == setBootCommand then - cmd(firstArg, getRemainingArgs(3)); -else - cmd(firstArg, secondArg); -end diff --git a/programs/goo.lua b/programs/goo.lua deleted file mode 100644 index eb718a4..0000000 --- a/programs/goo.lua +++ /dev/null @@ -1,832 +0,0 @@ -local _VERSION = "1.0.0" - -local args = table.pack(...) -local command = args[1] - -local GOO_BLOCK_PREFIX = "justdirethings:gooblock_tier" -local MIN_START_FUEL = 50 -local REFUEL_THRESHOLD = 1000 -local WAIT_SECONDS = 2 -local MIN_FEEDING_ITEMS = 4 -local MAX_SUCK_ATTEMPTS = 16 - -local FUEL_ITEMS = { - "justdirethings:coal_t4", - "justdirethings:coal_t3", - "justdirethings:coal_t2", - "justdirethings:coal_t1", - "minecraft:coal", -} - -local PROCESS_ITEMS = { - ["minecraft:iron_block"] = { tier = 1 }, - ["minecraft:coal_block"] = { tier = 1 }, - ["mekanism:block_charcoal"] = { tier = 1 }, - ["justdirethings:coalblock_t1"] = { tier = 1 }, - ["justdirethings:coalblock_t2"] = { tier = 2 }, - ["justdirethings:coalblock_t3"] = { tier = 3 }, - ["justdirethings:coalblock_t4"] = { tier = 4 }, - ["minecraft:gold_block"] = { tier = 2 }, - ["minecraft:diamond_block"] = { tier = 3 }, - ["minecraft:netherite_block"] = { tier = 4 }, -} - -local FEEDING_ITEMS_BY_TIER = { - [1] = { "minecraft:sugar", "minecraft:rotten_flesh" }, - [2] = { "minecraft:nether_wart" }, - [3] = { "minecraft:chorus_fruit" }, - [4] = { "minecraft:sculk" }, -} - -local DIR_NORTH = 0 -local DIR_EAST = 1 -local DIR_SOUTH = 2 -local DIR_WEST = 3 - -local DIRECTION_DELTAS = { - [DIR_NORTH] = { x = 0, z = -1 }, - [DIR_EAST] = { x = 1, z = 0 }, - [DIR_SOUTH] = { x = 0, z = 1 }, - [DIR_WEST] = { x = -1, z = 0 }, -} - -local HOME = { x = -2, y = 0, z = 0, facing = DIR_EAST } -local GOO_CHECK = { x = -1, y = 0, z = 0, facing = DIR_EAST } - -local GOO_CHECKS = { - { name = "west goo check", x = -1, y = 0, z = 0, facing = DIR_EAST, action = "forward" }, - { name = "east goo check", x = 1, y = 0, z = 0, facing = DIR_WEST, action = "forward" }, - { name = "north goo check", x = 0, y = 0, z = -1, facing = DIR_SOUTH, action = "forward" }, - { name = "south goo check", x = 0, y = 0, z = 1, facing = DIR_NORTH, action = "forward" }, - { name = "top goo check", x = 0, y = 1, z = 0, facing = DIR_EAST, action = "down" }, -} - -local TARGETS = { - { name = "west side", x = -2, y = 0, z = 0, facing = DIR_EAST, action = "forward" }, - { name = "east side", x = 2, y = 0, z = 0, facing = DIR_WEST, action = "forward" }, - { name = "north side", x = 0, y = 0, z = -2, facing = DIR_SOUTH, action = "forward" }, - { name = "south side", x = 0, y = 0, z = 2, facing = DIR_NORTH, action = "forward" }, - { name = "top", x = -1, y = 1, z = 0, facing = DIR_EAST, action = "forward" }, -} - -local position = { x = HOME.x, y = HOME.y, z = HOME.z } -local facing = HOME.facing -local loggedBlockedItems = {} -local placedTargets = {} -local lastGooTier = nil - -local function isFlag(name) - return function(arg) - return arg == "-" .. name or arg == "--" .. name - end -end - -local isHelpFlag = isFlag("help") -local isVersionFlag = isFlag("version") - -local function printUsage() - print("goo usage:") - print() - print("\t\t\tgoo start") - print("\t\t\tgoo version") - print("\t\t\tgoo help") -end - -if command == "version" or isVersionFlag(command) then - print("goo v" .. _VERSION) - return -end - -if command == nil or command == "" or command == "help" or isHelpFlag(command) then - printUsage() - return -end - -if command ~= "start" then - printUsage() - return -end - -if not turtle then - error("goo must be run on a turtle") -end - -local function hasPickaxeEquipped() - local left = turtle.getEquippedLeft() - local right = turtle.getEquippedRight() - - return (left and string.match(left.name or "", "_pickaxe$") ~= nil) - or (right and string.match(right.name or "", "_pickaxe$") ~= nil) -end - -local function parseGooTier(blockName) - local tier = string.match(blockName or "", "^" .. GOO_BLOCK_PREFIX .. "(%d+)$") - - return tonumber(tier) -end - -local function isGooBlock(blockName) - return parseGooTier(blockName) ~= nil -end - -local function isProcessBlock(blockName) - return PROCESS_ITEMS[blockName] ~= nil -end - -local function isFuelItem(itemName) - for i = 1, #FUEL_ITEMS do - if FUEL_ITEMS[i] == itemName then - return true - end - end - - return false -end - -local function isFeedingItemForTier(itemName, gooTier) - local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} - - for i = 1, #feedingItems do - if feedingItems[i] == itemName then - return true - end - end - - return false -end - -local function countItem(itemName) - local count = 0 - - for slot = 1, 16 do - local item = turtle.getItemDetail(slot) - - if item and item.name == itemName then - count = count + item.count - end - end - - return count -end - -local function countFeedingItems(gooTier) - local count = 0 - local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} - - for i = 1, #feedingItems do - count = count + countItem(feedingItems[i]) - end - - return count -end - -local function findItemSlot(itemName) - for slot = 1, 16 do - local item = turtle.getItemDetail(slot) - - if item and item.name == itemName then - return slot, item - end - end - - return nil -end - -local function hasFreeSlot() - for slot = 1, 16 do - if turtle.getItemCount(slot) == 0 then - return true - end - end - - return false -end - -local function getFuelLevel() - local fuelLevel = turtle.getFuelLevel() - - if fuelLevel == "unlimited" then - return nil - end - - return fuelLevel -end - -local function getRefuelTarget() - local fuelLimit = turtle.getFuelLimit() - - if fuelLimit == "unlimited" or fuelLimit >= REFUEL_THRESHOLD then - return REFUEL_THRESHOLD - end - - return fuelLimit -end - -local function findFuelSlot() - for i = 1, #FUEL_ITEMS do - local slot, item = findItemSlot(FUEL_ITEMS[i]) - - if slot then - return slot, item - end - end - - return nil -end - -local function turnRight() - turtle.turnRight() - facing = (facing + 1) % 4 -end - -local function turnLeft() - turtle.turnLeft() - facing = (facing + 3) % 4 -end - -local function turnTo(direction) - while facing ~= direction do - local rightTurns = (direction - facing) % 4 - - if rightTurns == 3 then - turnLeft() - else - turnRight() - end - end -end - -local function moveForward() - if not turtle.forward() then - return false - end - - local delta = DIRECTION_DELTAS[facing] - position.x = position.x + delta.x - position.z = position.z + delta.z - - return true -end - -local function moveUp() - if not turtle.up() then - return false - end - - position.y = position.y + 1 - - return true -end - -local function moveDown() - if not turtle.down() then - return false - end - - position.y = position.y - 1 - - return true -end - -local function moveAxis(target, positiveDirection, negativeDirection, axis) - while position[axis] ~= target do - if position[axis] < target then - turnTo(positiveDirection) - else - turnTo(negativeDirection) - end - - if not moveForward() then - return false - end - end - - return true -end - -local function goTo(target) - while position.y < target.y do - if not moveUp() then - return false - end - end - - while position.y > target.y do - if not moveDown() then - return false - end - end - - if not moveAxis(target.x, DIR_EAST, DIR_WEST, "x") then - return false - end - - if not moveAxis(target.z, DIR_SOUTH, DIR_NORTH, "z") then - return false - end - - if target.facing then - turnTo(target.facing) - end - - return true -end - -local function mustGoTo(target, description) - while not goTo(target) do - print("Cannot reach " .. description .. ". Clear the path and waiting...") - os.sleep(WAIT_SECONDS) - end -end - -local function goHome() - mustGoTo(HOME, "home/provider") -end - -local function goToGooCheck() - mustGoTo(GOO_CHECK, "goo check position") -end - -local function inspectDirection(action) - if action == "forward" then - return turtle.inspect() - elseif action == "up" then - return turtle.inspectUp() - elseif action == "down" then - return turtle.inspectDown() - end - - error("unknown inspect action " .. tostring(action)) -end - -local function placeDirection(action) - if action == "forward" then - return turtle.place() - elseif action == "up" then - return turtle.placeUp() - elseif action == "down" then - return turtle.placeDown() - end - - error("unknown place action " .. tostring(action)) -end - -local function refuelFromInventory() - local fuelLevel = getFuelLevel() - - if not fuelLevel or fuelLevel >= getRefuelTarget() then - return false - end - - local previousSlot = turtle.getSelectedSlot() - local consumedFuel = false - - while fuelLevel < getRefuelTarget() do - local slot, item = findFuelSlot() - - if not slot then - break - end - - turtle.select(slot) - - if turtle.refuel(1) then - consumedFuel = true - print("Refueled with " .. item.name .. ". Fuel: " .. tostring(turtle.getFuelLevel())) - fuelLevel = getFuelLevel() - else - print("Could not refuel with " .. item.name .. ".") - break - end - end - - turtle.select(previousSlot) - - return consumedFuel -end - -local function isProviderBelow() - local ok = turtle.inspectDown() - - return ok -end - -local function pullFromProvider() - goHome() - - local pulled = false - - for _ = 1, MAX_SUCK_ATTEMPTS do - if not hasFreeSlot() then - break - end - - if turtle.suckDown() then - pulled = true - else - break - end - end - - return pulled -end - -local function shouldKeepItem(item, gooTier) - if isFuelItem(item.name) then - return true - end - - local processItem = PROCESS_ITEMS[item.name] - - if processItem and processItem.tier <= gooTier then - return true - end - - if isFeedingItemForTier(item.name, gooTier) then - return countFeedingItems(gooTier) <= MIN_FEEDING_ITEMS - end - - return false -end - -local function depositOutputs(gooTier) - goHome() - - local previousSlot = turtle.getSelectedSlot() - - for slot = 1, 16 do - local item = turtle.getItemDetail(slot) - - if item and not shouldKeepItem(item, gooTier) then - turtle.select(slot) - - if turtle.dropDown() then - print("Deposited " .. item.name .. ".") - else - print("Could not deposit " .. item.name .. ". Provider may be full.") - break - end - end - end - - turtle.select(previousSlot) -end - -local function waitForProvider(message) - if message then - print(message) - end - - repeat - os.sleep(WAIT_SECONDS) - until pullFromProvider() -end - -local function ensureStartFuel() - while true do - local fuelLevel = getFuelLevel() - - if not fuelLevel or fuelLevel >= MIN_START_FUEL then - return - end - - refuelFromInventory() - fuelLevel = getFuelLevel() - - if not fuelLevel or fuelLevel >= MIN_START_FUEL then - return - end - - waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.") - end -end - -local function ensureRuntimeFuel() - local fuelLevel = getFuelLevel() - - if not fuelLevel then - return - end - - if fuelLevel < getRefuelTarget() then - refuelFromInventory() - end - - fuelLevel = getFuelLevel() - - while fuelLevel and fuelLevel < MIN_START_FUEL do - waitForProvider("Fuel is below " .. tostring(MIN_START_FUEL) .. ". Add fuel to the provider below home.") - refuelFromInventory() - fuelLevel = getFuelLevel() - end -end - -local function findFeedingSlot(gooTier) - local feedingItems = FEEDING_ITEMS_BY_TIER[gooTier] or {} - - for i = 1, #feedingItems do - local slot, item = findItemSlot(feedingItems[i]) - - if slot then - return slot, item - end - end - - return nil -end - -local function findEligibleProcessSlot(gooTier) - for slot = 1, 16 do - local item = turtle.getItemDetail(slot) - local processItem = item and PROCESS_ITEMS[item.name] - - if processItem then - if processItem.tier <= gooTier then - return slot, item, processItem - end - - local logKey = item.name .. ":" .. tostring(gooTier) - - if not loggedBlockedItems[logKey] then - print( - item.name - .. " requires goo tier " - .. tostring(processItem.tier) - .. ", current tier is " - .. tostring(gooTier) - ) - loggedBlockedItems[logKey] = true - end - end - end - - return nil -end - -local function inspectGoo() - for i = 1, #GOO_CHECKS do - local check = GOO_CHECKS[i] - - if goTo(check) then - local ok, inspected = inspectDirection(check.action) - - if ok then - local tier = parseGooTier(inspected.name) - - if tier then - lastGooTier = tier - - return tier, inspected, check - end - end - end - end - - if lastGooTier then - print("Cannot reach the goo to inspect it. Using last known tier " .. tostring(lastGooTier) .. ".") - - return lastGooTier, { state = { alive = true } }, nil - end - - error("expected a reachable Just Dire Things goo block around the turtle") -end - -local function ensureGooAlive() - while true do - local gooTier, inspected, check = inspectGoo() - - if not check or inspected.state and inspected.state.alive == true then - return gooTier - end - - local slot, item = findFeedingSlot(gooTier) - - if not slot then - waitForProvider("Goo tier " .. tostring(gooTier) .. " is not alive. Add feeding items to the provider.") - else - turtle.select(slot) - print("Goo tier " .. tostring(gooTier) .. " is not alive. Feeding with " .. item.name .. "...") - - if not placeDirection(check.action) then - print("Could not feed the goo. Waiting before retry...") - os.sleep(WAIT_SECONDS) - else - os.sleep(1) - end - end - end -end - -local function selectProcessItem(gooTier) - local slot, item = findEligibleProcessSlot(gooTier) - - if not slot then - return nil - end - - turtle.select(slot) - - return item -end - -local function inspectTarget(target) - if target.action == "forward" then - return turtle.inspect() - elseif target.action == "up" then - return turtle.inspectUp() - elseif target.action == "down" then - return turtle.inspectDown() - end - - error("unknown target action " .. tostring(target.action)) -end - -local function digTarget(target) - if target.action == "forward" then - return turtle.dig() - elseif target.action == "up" then - return turtle.digUp() - elseif target.action == "down" then - return turtle.digDown() - end - - error("unknown target action " .. tostring(target.action)) -end - -local function placeTarget(target) - if target.action == "forward" then - return turtle.place() - elseif target.action == "up" then - return turtle.placeUp() - elseif target.action == "down" then - return turtle.placeDown() - end - - error("unknown target action " .. tostring(target.action)) -end - -local function ensureMiningSpace(gooTier) - while not hasFreeSlot() do - depositOutputs(gooTier) - - if not hasFreeSlot() then - print("Inventory is full and provider may be full. Waiting...") - os.sleep(WAIT_SECONDS) - end - end -end - -local function isTargetQueued(target) - for i = 1, #placedTargets do - if placedTargets[i] == target then - return true - end - end - - return false -end - -local function removeQueuedTarget(index) - table.remove(placedTargets, index) -end - -local function goToTarget(target) - mustGoTo(target, target.name) -end - -local function mineQueuedTarget(gooTier) - if #placedTargets == 0 then - return false - end - - local target = placedTargets[1] - - goToTarget(target) - ensureGooAlive() - goToTarget(target) - - local ok, inspected = inspectTarget(target) - - if not ok then - print("Queued " .. target.name .. " is empty.") - removeQueuedTarget(1) - return true - end - - if isProcessBlock(inspected.name) then - return false - end - - if isGooBlock(inspected.name) then - removeQueuedTarget(1) - return true - end - - print("Mining processed " .. target.name .. " block: " .. tostring(inspected.name)) - ensureMiningSpace(gooTier) - goToTarget(target) - - if digTarget(target) then - removeQueuedTarget(1) - return true - end - - print("Could not mine " .. target.name .. ".") - - return false -end - -local function placeAvailableTarget(gooTier) - for i = 1, #TARGETS do - local target = TARGETS[i] - - if not isTargetQueued(target) then - goToTarget(target) - ensureGooAlive() - goToTarget(target) - - local ok, inspected = inspectTarget(target) - - if ok then - if not isProcessBlock(inspected.name) and not isGooBlock(inspected.name) then - print("Mining existing " .. target.name .. " block: " .. tostring(inspected.name)) - ensureMiningSpace(gooTier) - goToTarget(target) - - if digTarget(target) then - return true - end - end - else - local item = selectProcessItem(gooTier) - - if not item then - return false - end - - print("Placing " .. item.name .. " on goo " .. target.name) - - if placeTarget(target) then - placedTargets[#placedTargets + 1] = target - return true - end - - print("Could not place " .. target.name .. ".") - end - end - end - - return false -end - -local function waitAtOldestTarget() - if #placedTargets == 0 then - goToGooCheck() - else - goToTarget(placedTargets[1]) - end - - print("Waiting for goo processing...") - os.sleep(WAIT_SECONDS) -end - -local function startup() - print("goo started. Layout: provider below turtle, goo two blocks ahead, turtle facing goo.") - - if not hasPickaxeEquipped() then - error("goo requires a turtle with a pickaxe equipped") - end - - if not isProviderBelow() then - error("goo requires a provider/deposit barrel below the turtle") - end - - pullFromProvider() - ensureStartFuel() -end - -startup() - -while true do - ensureRuntimeFuel() - - local gooTier = ensureGooAlive() - depositOutputs(gooTier) - pullFromProvider() - ensureRuntimeFuel() - - gooTier = ensureGooAlive() - local changed = mineQueuedTarget(gooTier) - - if not changed then - changed = placeAvailableTarget(gooTier) - end - - if not changed then - if findEligibleProcessSlot(gooTier) then - waitAtOldestTarget() - else - waitForProvider("No eligible process block found. Add inputs to the provider below home.") - end - end -end diff --git a/servers/cube-boot.lua b/servers/cube-boot.lua deleted file mode 100644 index 44f55a5..0000000 --- a/servers/cube-boot.lua +++ /dev/null @@ -1,28 +0,0 @@ -local _VERSION = '2.0.0'; - -local function trim(s) - return (string.gsub(s, "^%s*(.-)%s*$", "%1")) -end - -local function readFile(path) - local file = fs.open(path, "r"); - - if not file then - return nil; - end - - local contents = file.readAll() - file.close() - - return contents -end - -local startupCommand = trim(readFile('.cubeboot') or ""); - - -if startupCommand ~= "" then - print('cube-boot v' .. _VERSION .. ': execute \'' .. startupCommand .. '\'...'); - shell.run(startupCommand); -else - print('cube-startup v' .. _VERSION .. ' no startup command detected.') -end diff --git a/servers/cube-server.lua b/servers/cube-server.lua deleted file mode 100644 index ff85649..0000000 --- a/servers/cube-server.lua +++ /dev/null @@ -1,90 +0,0 @@ -local _VERSION = '2.1.0'; - -local net = require('/apis/net')(); - -local CUBE_CHANNEL = 64; - -local function trim(s) - return (string.gsub(s, "^%s*(.-)%s*$", "%1")) -end - -local function readFile(path) - local file = fs.open(path, "r"); - - if not file then - return nil; - end - - local contents = file.readAll() - file.close() - - return contents -end - -local function writeFile(path, content) - local file = fs.open(path, "w"); - - if not file then - return false; - end - - file.write(content) - file.close(); - - return true; -end - -local function ensureParentDir(path) - local parentPath = string.match(path, '^(.+)/[^/]+$'); - - if parentPath and parentPath ~= '' and not fs.exists(parentPath) then - fs.makeDir(parentPath); - end -end - -local function getStartupCommand() - return trim(readFile('.cubeboot') or "") -end - --- ping event -net.listenRequest(CUBE_CHANNEL, "ping", function(_, reply) - local startupCommand = getStartupCommand(); - - reply({ startup = startupCommand }); -end) - --- reboot event -net.listenRequest(CUBE_CHANNEL, "reboot", function(_, reply) - reply(true); - - os.sleep(0.2) - os.reboot() -end) - --- set-boot event -net.listenRequest(CUBE_CHANNEL, "set-boot", function(startupCommand, reply) - if startupCommand == nil or startupCommand == '' then - fs.delete('/.cubeboot'); - reply(true); - return; - end - - local res = writeFile('/.cubeboot', startupCommand); - reply(res); -end) - --- deploy-file event -net.listenRequest(CUBE_CHANNEL, "deploy-file", function(payload, reply) - if type(payload) ~= 'table' or type(payload.path) ~= 'string' or type(payload.content) ~= 'string' then - reply(false); - return; - end - - ensureParentDir(payload.path); - reply(writeFile(payload.path, payload.content)); -end) - -print('cube-server v' .. _VERSION .. ' started.') - --- start event loop -net.startLoop(); diff --git a/startup/servers.lua b/startup/servers.lua index 5d9cd66..62dd035 100644 --- a/startup/servers.lua +++ b/startup/servers.lua @@ -1,9 +1,7 @@ -local _VERSION = '1.1.1' +local _VERSION = '1.1.2' local SERVERS = { "servers/ping-server", - "servers/cube-server.lua", - "servers/cube-boot.lua", }; local function init() From 705f64c68ad18ecfb2f8c931cc00273314f35f87 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 19:47:45 +0200 Subject: [PATCH 2/8] feat: add beta install flag --- CLAUDE.md | 2 +- README.md | 6 ++++++ install.lua | 22 ++++++++++++++++++++-- programs/upgrade.lua | 9 ++++++++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d5e191d..130e6c1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,7 @@ These are duplicated as local constants across files; keep them in sync when cha ## Installation / distribution -`install.lua` is fetched and run on a machine via `wget run /install.lua`. It deletes old paths, re-downloads every file in `LIST_FILES` from the `master` branch raw GitHub URL, and runs `startup/servers.lua`. **When adding a new file that ships to machines, add it to `LIST_FILES` in `install.lua`** (and to `SERVERS` in `startup/servers.lua` if it's a server). +`install.lua` is fetched and run on a machine via `wget run /install.lua`. It deletes old paths, re-downloads every file in `LIST_FILES` from the `master` branch raw GitHub URL by default, or from `next` when run with `--beta`, and runs `startup/servers.lua`. **When adding a new file that ships to machines, add it to `LIST_FILES` in `install.lua`** (and to `SERVERS` in `startup/servers.lua` if it's a server). ## Conventions diff --git a/README.md b/README.md index b3df93d..60ebb8a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ wget run https://raw.githubusercontent.com/guillaumearm/cc-libs/master/install.lua ``` +Install the beta branch: +``` +wget run https://raw.githubusercontent.com/guillaumearm/cc-libs/next/install.lua --beta +``` + ## APIs - `/apis/eventloop`: a simple event loop API. - `/apis/net`: an API to simplify sending and receiving routed messages, based on the `eventloop` library. @@ -17,6 +22,7 @@ All servers are automatically started at boot. ## Programs - `router`: routes messages. You need to set up a router to use all `apis/net`-based programs and libraries. - `ping`: pings machines using `apis/net`. +- `upgrade`: upgrades the machine. Use `upgrade --beta` to install from the beta branch. ## Development See [DEVELOPMENT.md](./DEVELOPMENT.md) for development setup and workflow. diff --git a/install.lua b/install.lua index d8a18ce..72b15a8 100644 --- a/install.lua +++ b/install.lua @@ -1,4 +1,6 @@ -local _VERSION = '2.0.4' +local _VERSION = '2.1.0' + +local command = ...; local LIST_FILES = { -- startup @@ -14,6 +16,22 @@ local LIST_FILES = { 'apis/eventloop.lua', }; +local function printUsage() + print('install usage:'); + print(); + print('\t\t\twget run '); + print('\t\t\twget run --beta'); +end + +local branch = 'master'; + +if command == '--beta' or command == '-beta' then + branch = 'next'; +elseif command ~= nil and command ~= '' then + printUsage(); + return; +end + -- remove old files fs.delete('ping-server.lua'); -- replaced by `servers/ping-server.lua` fs.delete('ping.lua') -- replaced by `programs/ping.lua` @@ -25,7 +43,7 @@ fs.delete('programs/goo.lua'); fs.delete('servers/cube-server.lua'); fs.delete('servers/cube-boot.lua'); -local REPO_PREFIX = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/master/' +local REPO_PREFIX = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/' .. branch .. '/' local previousDir = shell.dir() diff --git a/programs/upgrade.lua b/programs/upgrade.lua index 0f4b201..752da3d 100644 --- a/programs/upgrade.lua +++ b/programs/upgrade.lua @@ -1,6 +1,7 @@ -local _VERSION = '1.0.0'; +local _VERSION = '1.1.0'; local INSTALL_URL = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/master/install.lua'; +local BETA_INSTALL_URL = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/next/install.lua'; local command = ...; @@ -8,6 +9,7 @@ local function printUsage() print('upgrade usage:'); print(); print('\t\t\tupgrade'); + print('\t\t\tupgrade --beta'); print('\t\t\tupgrade version'); print('\t\t\tupgrade help'); end @@ -22,6 +24,11 @@ if command == 'help' or command == '-help' or command == '--help' then return; end +if command == '--beta' or command == '-beta' then + shell.execute('wget', 'run', BETA_INSTALL_URL, '--beta'); + return; +end + if command ~= nil and command ~= '' then printUsage(); return; From 0d9d863c4ffaa529e815e65cf8c6a309642dc063 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 21:07:50 +0200 Subject: [PATCH 3/8] docs: add advanced peripherals glossary --- docs/advanced_peripherals_glossary.md | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/advanced_peripherals_glossary.md diff --git a/docs/advanced_peripherals_glossary.md b/docs/advanced_peripherals_glossary.md new file mode 100644 index 0000000..9cc5038 --- /dev/null +++ b/docs/advanced_peripherals_glossary.md @@ -0,0 +1,129 @@ +# Advanced Peripherals Documentation Glossary + +Compact index of Advanced Peripherals documentation pages for version 0.7 from . The first page in each navigation section, such as Chat Box, Chatty Turtle, AR Goggles, Mod Integrations, or Changelog 0.7.24r, exposes the broader section navigation. + +Last checked: 2026-06-07. + +ATM 10 note: [AR Goggles](https://docs.advanced-peripherals.de/0.7/items/ar_goggles/) are documented, but do not work in our current ATM 10 Advanced Peripherals 0.7 setup. + +## Guides + +- [Disabled Peripherals](https://docs.advanced-peripherals.de/0.7/guides/disabled_peripherals/) +- [Lua Objects](https://docs.advanced-peripherals.de/0.7/guides/objects/) +- [Item and Fluid Filters](https://docs.advanced-peripherals.de/0.7/guides/filters/) +- [Cooldowns and Fuel consumption](https://docs.advanced-peripherals.de/0.7/guides/cooldowns_and_fuel_consumption/) +- [Storage System Functions](https://docs.advanced-peripherals.de/0.7/guides/storage_system_functions/) +- [How to: report a bug or request a feature](https://docs.advanced-peripherals.de/0.7/guides/how_to_report/) + +## Peripherals + +- [Chat Box](https://docs.advanced-peripherals.de/0.7/peripherals/chat_box/) +- [Energy Detector](https://docs.advanced-peripherals.de/0.7/peripherals/energy_detector/) +- [Environment Detector](https://docs.advanced-peripherals.de/0.7/peripherals/environment_detector/) +- [Player Detector](https://docs.advanced-peripherals.de/0.7/peripherals/player_detector/) +- [Inventory Manager](https://docs.advanced-peripherals.de/0.7/peripherals/inventory_manager/) +- [NBT Storage](https://docs.advanced-peripherals.de/0.7/peripherals/nbt_storage/) +- [Block Reader](https://docs.advanced-peripherals.de/0.7/peripherals/block_reader/) +- [Geo Scanner](https://docs.advanced-peripherals.de/0.7/peripherals/geo_scanner/) +- [Redstone Integrator](https://docs.advanced-peripherals.de/0.7/peripherals/redstone_integrator/) +- [AR Controller](https://docs.advanced-peripherals.de/0.7/peripherals/ar_controller/) +- [ME Bridge](https://docs.advanced-peripherals.de/0.7/peripherals/me_bridge/) +- [RS Bridge](https://docs.advanced-peripherals.de/0.7/peripherals/rs_bridge/) +- [Colony Integrator](https://docs.advanced-peripherals.de/0.7/peripherals/colony_integrator/) + +## Turtles + +- [Chatty Turtle](https://docs.advanced-peripherals.de/0.7/turtles/chatty_turtle/) +- [Chunky Turtle](https://docs.advanced-peripherals.de/0.7/turtles/chunky_turtle/) +- [Environment Turtle](https://docs.advanced-peripherals.de/0.7/turtles/environment_turtle/) +- [Player Turtle](https://docs.advanced-peripherals.de/0.7/turtles/player_turtle/) +- [Geoscanning Turtle](https://docs.advanced-peripherals.de/0.7/turtles/geo_scanner_turtle/) + +## Metaphysics Turtles + +- [Weak Automata](https://docs.advanced-peripherals.de/0.7/turtles/metaphysics/weak_automata/) +- [Husbandry Automata](https://docs.advanced-peripherals.de/0.7/turtles/metaphysics/husbandry_automata/) +- [End Automata](https://docs.advanced-peripherals.de/0.7/turtles/metaphysics/end_automata/) +- [Overpowered Automata](https://docs.advanced-peripherals.de/0.7/turtles/metaphysics/overpowered_automata/) + +## Items + +- [AR Goggles](https://docs.advanced-peripherals.de/0.7/items/ar_goggles/) (documented, currently not working in our ATM 10/AP 0.7 setup) +- [Chunk Controller](https://docs.advanced-peripherals.de/0.7/items/chunk_controller/) +- [Computer Tool](https://docs.advanced-peripherals.de/0.7/items/computer_tool/) +- [Memory Card](https://docs.advanced-peripherals.de/0.7/items/memory_card/) +- [Pocket Computers](https://docs.advanced-peripherals.de/0.7/items/pocket_computer/) + +## Mod Integrations + +- [Mod Integrations overview](https://docs.advanced-peripherals.de/0.7/integrations/) + +## Minecraft Integrations + +- [Beacon](https://docs.advanced-peripherals.de/0.7/integrations/minecraft/beacon/) +- [Note Block](https://docs.advanced-peripherals.de/0.7/integrations/minecraft/noteblock/) + +## Botania Integrations + +- [Flowers](https://docs.advanced-peripherals.de/0.7/integrations/botania/flowers/) +- [Mana Pool](https://docs.advanced-peripherals.de/0.7/integrations/botania/pool/) +- [Mana Spreader](https://docs.advanced-peripherals.de/0.7/integrations/botania/spreader/) + +## Create Integrations + +- [Basin](https://docs.advanced-peripherals.de/0.7/integrations/create/basin/) +- [Blaze Burner](https://docs.advanced-peripherals.de/0.7/integrations/create/blazeburner/) +- [Fluid Tank](https://docs.advanced-peripherals.de/0.7/integrations/create/fluidtank/) +- [Mechanical Mixer](https://docs.advanced-peripherals.de/0.7/integrations/create/mechanicalmixer/) +- [Blocks with Scroll Behaviour](https://docs.advanced-peripherals.de/0.7/integrations/create/scrollbehaviour/) + +## Draconic Evolution Integrations + +- [Draconic Evolution overview](https://docs.advanced-peripherals.de/0.7/integrations/draconic_evolution/) +- [Energy Core](https://docs.advanced-peripherals.de/0.7/integrations/draconic_evolution/energy_core/) +- [Reactor](https://docs.advanced-peripherals.de/0.7/integrations/draconic_evolution/reactor/) + +## Immersive Engineering Integrations + +- [Redstone Wire Connector](https://docs.advanced-peripherals.de/0.7/integrations/immersive_engineering/connector/) +- [Redstone Probe](https://docs.advanced-peripherals.de/0.7/integrations/immersive_engineering/probe/) + +## Integrated Dynamics Integrations + +- [Variable Store](https://docs.advanced-peripherals.de/0.7/integrations/integrated_dynamics/variable_store/) + +## Mekanism Integrations + +- [Mekanism overview](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/) +- [Boiler Valve](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/boiler/) +- [Chemical Tank](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/chemical/) +- [Digital Miner](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/digital_miner/) +- [Dynamic Tank](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/dynamic_tank/) +- [Fission](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/fission/) +- [Fluid Tank](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/fluid_tank/) +- [Fusion](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/fusion/) +- [Generic Mekanism](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/generic/) +- [Induction Valve](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/induction/) +- [Solar Evaporation](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/solar_evaporation/) +- [Transmitter](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/transmitter/) +- [Turbine](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/turbine/) +- [Waste Barrel](https://docs.advanced-peripherals.de/0.7/integrations/mekanism/waste_barrel/) + +## Powah Integrations + +- [Ender Cell](https://docs.advanced-peripherals.de/0.7/integrations/powah/ender_cell/) +- [Energy Cell](https://docs.advanced-peripherals.de/0.7/integrations/powah/energy_cell/) +- [Furnator](https://docs.advanced-peripherals.de/0.7/integrations/powah/furnator/) +- [Magmator](https://docs.advanced-peripherals.de/0.7/integrations/powah/magmator/) +- [Reactor](https://docs.advanced-peripherals.de/0.7/integrations/powah/reactor/) +- [Solar Panel](https://docs.advanced-peripherals.de/0.7/integrations/powah/solar_panel/) +- [Thermo Generator](https://docs.advanced-peripherals.de/0.7/integrations/powah/thermo_generator/) + +## Storage Drawers Integrations + +- [Drawer](https://docs.advanced-peripherals.de/0.7/integrations/storage_drawers/drawer/) + +## Changelogs + +- [Changelog 0.7.24r](https://docs.advanced-peripherals.de/0.7/changelogs/0.7.24r/) +- [Changelog 0.7r](https://docs.advanced-peripherals.de/0.7/changelogs/0.7r/) From d676a33365a17de0eb38b3bc09ee877bb24c2805 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 21:16:03 +0200 Subject: [PATCH 4/8] docs: add create integration glossary --- CLAUDE.md | 75 ++++++++++-------------------- docs/README.md | 15 ++++++ docs/create_cc_tweaked_glossary.md | 36 ++++++++++++++ 3 files changed, 75 insertions(+), 51 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/create_cc_tweaked_glossary.md diff --git a/CLAUDE.md b/CLAUDE.md index 130e6c1..98c6f26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,67 +1,40 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +Concise guidance for agents working in this repository. -## What this is +## Project -A collection of [ComputerCraft](https://tweaked.cc/) (CC:Tweaked) Lua APIs, servers, and programs for in-game networked computers. Targets **CC:Tweaked for Minecraft 1.21** (latest). Runtime is the ComputerCraft Lua sandbox (`fs`, `peripheral`, `os.pullEvent`, `shell`, `parallel`, `modem.transmit`, etc.), not standard Lua. +ComputerCraft / CC:Tweaked Lua APIs, servers, and programs for Minecraft 1.21. Code runs in the ComputerCraft sandbox, not standard Lua. -When more CC:Tweaked API details are needed, is a useful documentation entrypoint. It is not mandatory to fetch every time, but its left-hand navigation includes the broader glossary and links to globals, modules (for example `cc.base64` and `cc.pretty`), peripherals, and events, even when the topic is not turtle-specific. A compact local index of these documentation pages is maintained in `docs/cc_glossary.md`. +Use `docs/README.md` as the entrypoint for CC:Tweaked, Advanced Peripherals, and Create integration documentation links. -## Tooling constraints +## Constraints -- **There is no way to run, build, or test this code yet.** It only executes inside ComputerCraft (in-game or under CraftOS-PC). Do not attempt to run Lua locally or add a test harness unless asked. -- **Linting:** `just check` runs `luacheck` over all Lua source. **Always run `just check` after editing a Lua file, and fix any warnings before considering the change done.** Config lives in `.luacheckrc` (a custom `lua51+cc` std that knows the ComputerCraft sandbox globals and the `os`/`table` extensions). If a new genuine global is needed, add it there rather than suppressing the warning inline. -- Match the existing style (2-space indent, semicolons, `local function`). -- `require` paths are ComputerCraft-resolved and absolute from the computer root, e.g. `require('/apis/net')`. Most modules return a *factory* function — call it once to get the API: `local net = require('/apis/net')()`. +- Do not run Lua locally or add a test harness unless asked; code executes in-game or CraftOS-PC. +- After editing Lua, run `just check` and fix all `luacheck` warnings. +- Use 2-space indent, semicolons, and `local function`. +- `require` paths are absolute ComputerCraft paths, for example `require('/apis/net')()`. +- Most API modules return factories; call the required module once before use. ## Architecture -Three layers, bottom-up: +- `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`. +- `apis/net.lua` builds modem packet messaging, routing, and request/response RPC on the event loop. +- `servers/` listen for requests and start loops; `programs/` are clients that send requests and exit. +- Well-known channels: `9` ping, `10` router/default routing. Keep duplicated constants in sync. -1. **`apis/eventloop.lua`** — the foundation. A single-threaded event loop wrapping `os.pullEventRaw`. `register(eventName, handler)` returns a disposer; handlers returning `api.STOP` auto-unregister. Also provides `setTimeout`/clearTimeout, `onStart`/`onStop`, and `runLoop`. The loop auto-stops when no handlers/timeouts remain (unless `runLoop(true)`). Mutations during dispatch are queued (`unregisterQueue`, `removeTimeoutQueue`, `timeoutFactories`) and flushed after, so handlers can safely (un)register. +## Boot And Install -2. **`apis/net.lua`** — messaging built on the event loop. Sends typed packets over modems. Key concepts: - - **Packet** = `{ sourceId, sourceLabel, routerId, destId, message }`. `destId` may be a numeric computer ID, a string label, or `nil` (broadcast). - - **Routing**: messages go out on `DEFAULT_ROUTING_CHANNEL` (10) to be picked up by a router, *unless* the sender is itself a router (`_G.isRouterEnabled`). **A router must be running somewhere on the network for net-based programs to reach other machines.** - - **Request/response**: `sendRequest`/`listenRequest` implement RPC — the responder replies on `eventType .. "_response"`. `sendRequest` spins up a *private* event loop + net instance, waits up to `timeoutInSec` (default 0.5s), and returns `ok, result, packet`. `sendMultipleRequests` collects replies from many machines until timeout. - - Higher-level helpers: `createRequest(channel, eventType)` and `createEvent(channel, eventType)`. - -3. **`servers/`** and **`programs/`** — concrete uses of `net`. Servers `listenRequest` and call `net.startLoop()`; programs (clients) fire `sendRequest`/`sendMultipleRequests` and exit. - -### Channels (well-known ports) - -- `9` — ping -- `10` — router / default routing channel - -These are duplicated as local constants across files; keep them in sync when changing. - -## Boot flow - -`startup/servers.lua` is the entry point on each machine: it adds `/programs` to `shell.path`, then `parallel.waitForAll` runs an interactive shell alongside every server in `SERVERS`. When all stop, it reboots. - -## CraftOS-PC emulation - -`startup/servers.lua` detects the `periphemu` global and, when present, creates an emulated modem plus (on computer 0) a few emulated peer/router computers. Preserve these guards. - -## Installation / distribution - -`install.lua` is fetched and run on a machine via `wget run /install.lua`. It deletes old paths, re-downloads every file in `LIST_FILES` from the `master` branch raw GitHub URL by default, or from `next` when run with `--beta`, and runs `startup/servers.lua`. **When adding a new file that ships to machines, add it to `LIST_FILES` in `install.lua`** (and to `SERVERS` in `startup/servers.lua` if it's a server). +- `startup/servers.lua` starts `/programs`, the shell, and configured servers via `parallel.waitForAll`. +- Preserve `periphemu` guards used for CraftOS-PC emulation. +- `install.lua` downloads files listed in `LIST_FILES`; add shipped files there. +- Add new servers to `startup/servers.lua` as needed. ## Conventions -- Each module starts with `local _VERSION = '...'`; bump it when changing that module's behavior. -- Programs accept `-version`/`--version` and `-help`/`--help` (and `-silent`/`--silent` for the router) via vararg `...`. -- French comments appear throughout — fine to add either language, match the surrounding file. +- Bump `local _VERSION = '...'` when changing module behavior. +- Programs support `-version`/`--version` and `-help`/`--help`; router also supports `-silent`/`--silent`. +- French or English comments are fine; match surrounding code. +- Commit messages use lightweight conventional style: `topic(scope): description` or `topic: description`. -### Commit messages - -Commit messages roughly follow Angular-style conventional commits, but the convention is intentionally lightweight. Use either `topic(scope): description` or `topic: description`. - -- Common topics include `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, and `revert`. -- Keep the description short, lowercase, imperative, and without a trailing period. -- The exact topic/scope matters less than making the commit easy to scan. - -## Development setup - -See `DEVELOPMENT.md` for local development requirements and setup steps. +See `DEVELOPMENT.md` for local setup. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2236c21 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,15 @@ +# Documentation + +Start here when looking up ComputerCraft-related APIs, peripherals, or mod integrations used by this repository. + +## Indexes + +- [`cc_glossary.md`](cc_glossary.md) - CC:Tweaked globals, modules, peripherals, events, and guides. +- [`advanced_peripherals_glossary.md`](advanced_peripherals_glossary.md) - Advanced Peripherals 0.7 guides, peripherals, turtles, integrations, and changelog pages. +- [`create_cc_tweaked_glossary.md`](create_cc_tweaked_glossary.md) - Create CC:Tweaked integration pages. + +## Notes + +- These files are compact navigation indexes, not full documentation mirrors. +- Prefer the local indexes first, then open the linked upstream docs for details. +- Update the relevant `Last checked` line when refreshing an index. diff --git a/docs/create_cc_tweaked_glossary.md b/docs/create_cc_tweaked_glossary.md new file mode 100644 index 0000000..a599d4c --- /dev/null +++ b/docs/create_cc_tweaked_glossary.md @@ -0,0 +1,36 @@ +# Create CC:Tweaked Integration Documentation Glossary + +Compact index of Create's CC:Tweaked integration documentation pages from . The sidebar HTML on that page exposes the broader ComputerCraft integration navigation. + +Last checked: 2026-06-07. + +## Logistics + +- [Packager](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/packager) +- [Re-Packager](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/repackager) +- [Stock Ticker](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/stock-ticker) +- [Redstone Requester](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/redstone-requester) +- [Table Cloth](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/table-cloth) +- [Package Frogport](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/package-frogport) +- [Postbox](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/postbox) +- [Package Object](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/package-object) +- [Order Data Object](https://wiki.createmod.net/users/cc-tweaked-integration/logistics/order-data-object) + +## Trains + +- [Train Station](https://wiki.createmod.net/users/cc-tweaked-integration/train/train-station) +- [Train Signal](https://wiki.createmod.net/users/cc-tweaked-integration/train/train-signal) +- [Train Observer](https://wiki.createmod.net/users/cc-tweaked-integration/train/train-observer) +- [Train Schedule](https://wiki.createmod.net/users/cc-tweaked-integration/train/train-schedule) +- [Libraries](https://wiki.createmod.net/users/cc-tweaked-integration/train/libraries) + +## Peripherals + +- [Nixie Tube](https://wiki.createmod.net/users/cc-tweaked-integration/nixie-tube) +- [Display Link](https://wiki.createmod.net/users/cc-tweaked-integration/display-link) +- [Sticker](https://wiki.createmod.net/users/cc-tweaked-integration/sticker) +- [Sequenced Gearshift](https://wiki.createmod.net/users/cc-tweaked-integration/sequenced-gearshift) +- [Rotational Speed Controller](https://wiki.createmod.net/users/cc-tweaked-integration/rotational-speed-controller) +- [Creative Motor](https://wiki.createmod.net/users/cc-tweaked-integration/creative-motor) +- [Speedometer](https://wiki.createmod.net/users/cc-tweaked-integration/speedometer) +- [Stressometer](https://wiki.createmod.net/users/cc-tweaked-integration/stressometer) From dff6af066601057a853a3ae7408c7593f169ed08 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 21:44:01 +0200 Subject: [PATCH 5/8] docs: add architecture decision records --- CLAUDE.md | 9 ++--- docs/README.md | 1 + docs/adrs/README.md | 13 +++++++ docs/adrs/adr-0001-target-computercraft.md | 28 +++++++++++++++ .../adr-0002-use-eventloop-for-async-code.md | 28 +++++++++++++++ docs/adrs/adr-0003-current-net-api-state.md | 36 +++++++++++++++++++ 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 docs/adrs/README.md create mode 100644 docs/adrs/adr-0001-target-computercraft.md create mode 100644 docs/adrs/adr-0002-use-eventloop-for-async-code.md create mode 100644 docs/adrs/adr-0003-current-net-api-state.md diff --git a/CLAUDE.md b/CLAUDE.md index 98c6f26..d039e23 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ Concise guidance for agents working in this repository. ComputerCraft / CC:Tweaked Lua APIs, servers, and programs for Minecraft 1.21. Code runs in the ComputerCraft sandbox, not standard Lua. -Use `docs/README.md` as the entrypoint for CC:Tweaked, Advanced Peripherals, and Create integration documentation links. +Use `docs/README.md` as the entrypoint for CC:Tweaked, Advanced Peripherals, and Create integration documentation links. Use `docs/adrs/README.md` for repository architecture decisions. ## Constraints @@ -18,8 +18,9 @@ Use `docs/README.md` as the entrypoint for CC:Tweaked, Advanced Peripherals, and ## Architecture -- `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`. -- `apis/net.lua` builds modem packet messaging, routing, and request/response RPC on the event loop. +- `apis/eventloop.lua` is the single-threaded event loop around `os.pullEventRaw`; consider using it everywhere async behavior is needed. A handler that returns `api.STOP` auto-unregisters. +- `apis/net.lua` builds modem packet messaging, routing, and request/response RPC on the event loop. `sendRequest` returns `ok, result, packet` and defaults to a 0.5s timeout. +- A router (`/programs/router.lua`) must be running somewhere on the network; without it, packets lack `routerId`, `isPacketOk` rejects them, and cross-machine messaging silently fails. - `servers/` listen for requests and start loops; `programs/` are clients that send requests and exit. - Well-known channels: `9` ping, `10` router/default routing. Keep duplicated constants in sync. @@ -27,7 +28,7 @@ Use `docs/README.md` as the entrypoint for CC:Tweaked, Advanced Peripherals, and - `startup/servers.lua` starts `/programs`, the shell, and configured servers via `parallel.waitForAll`. - Preserve `periphemu` guards used for CraftOS-PC emulation. -- `install.lua` downloads files listed in `LIST_FILES`; add shipped files there. +- `install.lua` downloads files listed in `LIST_FILES` from `master` by default, or from `next` with `--beta`; add shipped files there. - Add new servers to `startup/servers.lua` as needed. ## Conventions diff --git a/docs/README.md b/docs/README.md index 2236c21..2dba7a5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ Start here when looking up ComputerCraft-related APIs, peripherals, or mod integ - [`cc_glossary.md`](cc_glossary.md) - CC:Tweaked globals, modules, peripherals, events, and guides. - [`advanced_peripherals_glossary.md`](advanced_peripherals_glossary.md) - Advanced Peripherals 0.7 guides, peripherals, turtles, integrations, and changelog pages. - [`create_cc_tweaked_glossary.md`](create_cc_tweaked_glossary.md) - Create CC:Tweaked integration pages. +- [`adrs/`](adrs/) - Lightweight Architecture Decision Records for this repository. ## Notes diff --git a/docs/adrs/README.md b/docs/adrs/README.md new file mode 100644 index 0000000..24ceb58 --- /dev/null +++ b/docs/adrs/README.md @@ -0,0 +1,13 @@ +# Architecture Decision Records + +This directory contains lightweight Architecture Decision Records for this repository. + +The goal is simple: keep a short memory of why we made repo-level choices while building ComputerCraft / CC:Tweaked code for Minecraft. This is not meant to be a heavy process. + +Future ADRs can reuse the shape of the existing files when it is useful. + +## Records + +- [`adr-0001-target-computercraft.md`](adr-0001-target-computercraft.md) - Target ComputerCraft. +- [`adr-0002-use-eventloop-for-async-code.md`](adr-0002-use-eventloop-for-async-code.md) - Use eventloop for async code. +- [`adr-0003-current-net-api-state.md`](adr-0003-current-net-api-state.md) - Current net API state. diff --git a/docs/adrs/adr-0001-target-computercraft.md b/docs/adrs/adr-0001-target-computercraft.md new file mode 100644 index 0000000..fbbb457 --- /dev/null +++ b/docs/adrs/adr-0001-target-computercraft.md @@ -0,0 +1,28 @@ +# ADR 0001: Target ComputerCraft + +## Status + +Accepted + +## Date + +2026-06-07 + +## Context + +This repository exists to build APIs, servers, and programs for ComputerCraft / CC:Tweaked inside Minecraft. + +The code is Lua, but the runtime is not generic local Lua. The real environment provides ComputerCraft APIs such as `os.pullEvent`, `peripheral`, modems, timers, labels, computer IDs, and the CraftOS filesystem. + +## Decision + +This repo targets ComputerCraft / CC:Tweaked first. + +Local Lua compatibility is not a goal by itself. Code can assume the ComputerCraft runtime and use ComputerCraft conventions when they make the in-game code clearer. + +## Consequences + +- Prefer ComputerCraft-style absolute `require` paths, such as `require('/apis/net')()` (most API modules return factories — call the result once to get the API). +- Local checks are useful, but runtime behavior should be validated in-game or with CraftOS-PC when needed. +- Avoid adding a local Lua harness unless there is a clear reason. +- Keep the repository practical: this is for playing Minecraft, not designing a general Lua framework. diff --git a/docs/adrs/adr-0002-use-eventloop-for-async-code.md b/docs/adrs/adr-0002-use-eventloop-for-async-code.md new file mode 100644 index 0000000..631fdf2 --- /dev/null +++ b/docs/adrs/adr-0002-use-eventloop-for-async-code.md @@ -0,0 +1,28 @@ +# ADR 0002: Use Eventloop For Async Code + +## Status + +Accepted + +## Date + +2026-06-07 + +## Context + +ComputerCraft is event-driven. Direct `os.pullEvent` loops are easy to write, but they are hard to compose when multiple things need to happen at the same time. + +This matters for servers, network listeners, timers, peripheral events, and future UI code. UI code especially will need to handle input, redraws, network replies, and timers together. + +## Decision + +New async code should use `/apis/eventloop`. + +Event handlers, timers, server listeners, and future UI behavior should compose through the event loop instead of each feature owning its own blocking event loop. + +## Consequences + +- Prefer `eventloop.register`, `setTimeout`, `onStart`, `onStop`, and `startLoop` for async behavior. +- APIs that listen for events should accept an existing event loop as a constructor argument, the way `/apis/net` already takes one. Do not create a private loop inside a module. +- Direct `os.pullEvent` loops should be rare and justified. +- Existing code can stay as-is for now, but future async, server, and UI code should move toward eventloop composition. diff --git a/docs/adrs/adr-0003-current-net-api-state.md b/docs/adrs/adr-0003-current-net-api-state.md new file mode 100644 index 0000000..9a76a8a --- /dev/null +++ b/docs/adrs/adr-0003-current-net-api-state.md @@ -0,0 +1,36 @@ +# ADR 0003: Current Net API State + +## Status + +Accepted + +## Date + +2026-06-07 + +## Context + +`/apis/net` is the current networking abstraction in this repository. + +It wraps modem messages with packet metadata and uses `/apis/eventloop` for listeners and request/response flows. It is useful for today's basic routed messages and RPC-like requests, but it is not a final protocol design. + +## Decision + +Keep using `/apis/net` for simple program and server messaging. + +Document the current behavior as the baseline, without over-designing the future protocol before real needs appear. + +## Current State + +- Default routing channel is `10`. +- Ping channel is `9`. +- Packets include `sourceId`, `sourceLabel`, `routerId`, `destId`, and `message`. +- Main convenience APIs include `send`, `listen`, `sendRequest`, `sendMultipleRequests`, `listenRequest`, `createEvent`, `createRequest`, and `openChannel` (alias `open`). Listening on a non-default channel requires `openChannel` first. +- `sendRequest` and `sendMultipleRequests` run a private event loop, default to a `0.5s` timeout, and return `ok, result, packet` (or `ok, results, packets` for the multi variant). +- Router behavior currently lives separately in `/programs/router.lua`. A router must be running on the network — otherwise non-router senders produce packets with `routerId = nil` and `isPacketOk` drops them on receive, so cross-machine messages silently fail. + +## Consequences + +- Use `/apis/net` for current basic messaging needs. +- Keep duplicated well-known channel constants in sync while they remain duplicated. +- Future ADRs can replace or refine this one if the network protocol gains discovery, retries, schemas, versioning, auth, or a different routing model. From efc7e88b9bad8c5ebec0fd1b85e034a3d681aac4 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 21:55:17 +0200 Subject: [PATCH 6/8] feat: add events program --- install.lua | 1 + programs/events.lua | 47 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 programs/events.lua diff --git a/install.lua b/install.lua index 72b15a8..8ce4f07 100644 --- a/install.lua +++ b/install.lua @@ -9,6 +9,7 @@ local LIST_FILES = { 'servers/ping-server.lua', -- programs 'programs/router.lua', -- router is not in servers folder because he's not ran on every machines + 'programs/events.lua', 'programs/ping.lua', 'programs/upgrade.lua', -- apis diff --git a/programs/events.lua b/programs/events.lua new file mode 100644 index 0000000..13ff406 --- /dev/null +++ b/programs/events.lua @@ -0,0 +1,47 @@ +local _VERSION = '1.0.0'; + +local command = ...; + +local function printUsage() + print('events usage:'); + print(); + print('\t\t\tevents'); + print('\t\t\tevents version'); + print('\t\t\tevents help'); +end + +local function valueToString(value) + if type(value) == 'string' then + return value; + end + + return textutils.serialize(value, { compact = true }); +end + +if command == 'version' or command == '-version' or command == '--version' then + print('events v' .. _VERSION); + return; +end + +if command == 'help' or command == '-help' or command == '--help' then + printUsage(); + return; +end + +if command ~= nil and command ~= '' then + printUsage(); + return; +end + +print('Listening events... Press Ctrl+T to stop.'); + +while true do + local event = table.pack(os.pullEventRaw()); + local parts = {}; + + for i = 1, event.n do + parts[i] = valueToString(event[i]); + end + + print(table.concat(parts, ' ')); +end From b070d96de5bd757bc2f164b8deb6aaf44a50999c Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 22:01:30 +0200 Subject: [PATCH 7/8] fix(events): exit on terminate --- programs/events.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/programs/events.lua b/programs/events.lua index 13ff406..c414a53 100644 --- a/programs/events.lua +++ b/programs/events.lua @@ -1,4 +1,4 @@ -local _VERSION = '1.0.0'; +local _VERSION = '1.0.1'; local command = ...; @@ -37,6 +37,11 @@ print('Listening events... Press Ctrl+T to stop.'); while true do local event = table.pack(os.pullEventRaw()); + + if event[1] == 'terminate' then + return; + end + local parts = {}; for i = 1, event.n do From b9a776485a54c42fc6d88ec22fca2d2aa29bef24 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Sun, 7 Jun 2026 22:21:23 +0200 Subject: [PATCH 8/8] fix(events): tolerate unserializable values --- programs/events.lua | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/programs/events.lua b/programs/events.lua index c414a53..9bcc02e 100644 --- a/programs/events.lua +++ b/programs/events.lua @@ -1,4 +1,4 @@ -local _VERSION = '1.0.1'; +local _VERSION = '1.0.2'; local command = ...; @@ -10,12 +10,44 @@ local function printUsage() print('\t\t\tevents help'); end +local function sanitize(value, seen) + local valueType = type(value); + + if valueType == 'function' then + return ''; + end + + if valueType ~= 'table' then + return value; + end + + seen = seen or {}; + if seen[value] then + return ''; + end + + seen[value] = true; + + local result = {}; + for k, v in pairs(value) do + result[sanitize(k, seen)] = sanitize(v, seen); + end + + seen[value] = nil; + return result; +end + local function valueToString(value) if type(value) == 'string' then return value; end - return textutils.serialize(value, { compact = true }); + local ok, serialized = pcall(textutils.serialize, sanitize(value), { compact = true }); + if ok then + return serialized; + end + + return '<' .. type(value) .. '>'; end if command == 'version' or command == '-version' or command == '--version' then