CCCPaste

Draconic Reactor COntroller

-- draconic_autolimit.lua
-- Set output flux gate automatically to hold reactor at T_set (7600C), maximize draw.
-- Place this on your advanced computer and edit sides if needed.

-- CONFIG
local T_SET = 7600
local SAMPLE_INTERVAL = 2       -- seconds between fast samples
local KP_RECALC_INTERVAL = 60   -- seconds to recalc Kp
local SAFETY_FACTOR = 0.85      -- use 85% of instantaneous available power
local MIN_LIMIT = 0
local MAX_LIMIT = 2000000000    -- upper clamp for flux gate (2 billion RF/t)
local EMERGENCY_TEMP = 8000     -- immediate cut if exceeded
local MIN_FIELD_PERCENT = 10    -- emergency if field drops below this percent

-- discovery helpers (tries many common method names)
local function findPeripheralWithMethods(methodsList)
  for _,name in ipairs(peripheral.getNames()) do
    local ok, meths = pcall(peripheral.getMethods, name)
    if ok and meths then
      local sig = {}
      for _,m in ipairs(meths) do sig[m] = true end
      local allFound = true
      for _,m in ipairs(methodsList) do
        if not sig[m] then allFound = false; break end
      end
      if allFound then return peripheral.wrap(name), name end
    end
  end
  return nil
end

-- choose reactor peripheral (search for common method sets)
local reactor = findPeripheralWithMethods({"getTemperature","getGenerationRate"}) or findPeripheralWithMethods({"getTemperature","getEnergyStored"})
if not reactor then error("No reactor peripheral found (wrap/attach fluxgate to computer or use wired modem).") end

-- find output flux gate peripheral (we expect setFlowOverride or equivalent)
local fluxOut = findPeripheralWithMethods({"setFlowOverride"}) or findPeripheralWithMethods({"setFlow"})
if not fluxOut then
  -- try to find any peripheral named like fluxgate
  for _,n in ipairs(peripheral.getNames()) do
    if n:lower():find("flux") or n:lower():find("flow") then
      fluxOut = peripheral.wrap(n); break
    end
  end
end
if not fluxOut then error("No output flux gate peripheral found. Bind a flux gate to the computer or wire it.") end

-- helper fetchers (try multiple possible reactor APIs)
local function getTemperature()
  local s = { "getTemperature","getTemp","getHeat","getCoreTemperature" }
  for _,m in ipairs(s) do
    if reactor[m] then local ok,res = pcall(reactor[m], reactor); if ok and type(res) == "number" then return res end end
  end
  return nil
end

local function getGeneration()
  local s = { "getGenerationRate","getFlux","getEnergyChange","getGeneration" }
  for _,m in ipairs(s) do
    if reactor[m] then local ok,res = pcall(reactor[m], reactor); if ok and type(res) == "number" then return res end end
  end
  return nil
end

local function getFieldLoad()
  local s = { "getFieldLoadRate","getFieldDrainRate","getFieldLoad" }
  for _,m in ipairs(s) do
    if reactor[m] then local ok,res = pcall(reactor[m], reactor); if ok and type(res) == "number" then return res end end
  end
  return nil
end

local function getFieldPercent()
  local s = { "getField","getFieldStrength","getContainmentPercent" }
  for _,m in ipairs(s) do
    if reactor[m] then local ok,res = pcall(reactor[m], reactor); if ok and type(res) == "number" then return res end end
  end
  return nil
end

local function getEnergySaturation()
  local s = { "getEnergySaturation","getSaturation","getInternalEnergy" }
  for _,m in ipairs(s) do
    if reactor[m] then local ok,res = pcall(reactor[m], reactor); if ok and type(res) == "number" then return res end end
  end
  return nil
end

-- set flux gate (attempt setFlowOverride; if it does not exist try alternatives)
local function setFluxLimit(v)
  if v < MIN_LIMIT then v = MIN_LIMIT end
  if v > MAX_LIMIT then v = MAX_LIMIT end
  -- prefer exact override API
  if fluxOut.setFlowOverride then
    pcall(fluxOut.setFlowOverride, fluxOut, math.floor(v))
    return true
  end
  if fluxOut.setFlow then
    pcall(fluxOut.setFlow, fluxOut, math.floor(v))
    return true
  end
  -- fallback: try using redstone on a side (you must wire your flux gate to accept redstone)
  return false
end

-- initial Kp guess
local Kp = 20000   -- RF per °C (will be recalculated)
local lastKpCalc = 0
local lastTemp, lastAllowed = nil, nil

-- auto Kp calc routine: sample two points and compute slope S
local function recalcKp()
  local t1 = getTemperature(); local g1 = getGeneration(); local f1 = getFieldLoad()
  os.sleep(2)
  local t2 = getTemperature(); local g2 = getGeneration(); local f2 = getFieldLoad()
  if not t1 or not t2 or t1 == t2 then return end
  local allowed1 = (g1 or 0) - (f1 or 0)
  local allowed2 = (g2 or 0) - (f2 or 0)
  local S = (allowed2 - allowed1) / (t2 - t1)   -- RF per °C
  -- if injector is externally powered, we should use (g2-g1)/(t2-t1) instead. Adjust if necessary.
  if S < 0 then S = math.abs(S) end
  local newKp = S * SAFETY_FACTOR
  if newKp < 1000 then newKp = 1000 end
  if newKp > 2000000 then newKp = 2000000 end
  Kp = newKp
  lastKpCalc = os.time()
end

-- main loop
local running = true
local lastRecalc = 0
while running do
  pcall(function()
    local T = getTemperature()
    local G = getGeneration() or 0
    local F = getFieldLoad() or 0
    local fieldPct = getFieldPercent() or 100
    if not T then error("Cannot read temperature; check peripheral.") end

    -- emergency checks
    if T >= EMERGENCY_TEMP or fieldPct <= MIN_FIELD_PERCENT then
      -- immediate shutdown: set flux limit to 0 and optionally call reactor.scram if available
      setFluxLimit(0)
      -- attempt to deactivate reactor (if available)
      if reactor.setActive then pcall(reactor.setActive, reactor, false) end
      error(("Emergency stop: T=%.1f, Field=%.1f%%"):format(T, fieldPct))
    end

    -- recompute Kp periodically
    if os.time() - lastKpCalc > KP_RECALC_INTERVAL then
      recalcKp()
    end

    -- instant available power
    -- choose whether you power the injector from output or from external grid
    local injectorFromOutput = true   -- EDIT THIS if your injector is externally powered
    local A = G
    if injectorFromOutput then A = G - F end

    -- compute limit with proportional correction on temp error
    local tempErr = T - T_SET
    local correction = Kp * tempErr
    local rawLimit = SAFETY_FACTOR * A - correction

    -- clamp and write to flux gate
    local limit = math.max(0, math.floor(rawLimit + 0.5))
    setFluxLimit(limit)

    -- small status print
    term.clear(); term.setCursorPos(1,1)
    print(("T: %.1f°C  G: %d  F: %d  A: %d  Kp: %d  Limit: %d"):format(T, G, F, A, Kp, limit))
  end)

  os.sleep(SAMPLE_INTERVAL)
end