diff --git a/de_react_auto b/de_react_auto index e2f02fe..a9507df 100644 --- a/de_react_auto +++ b/de_react_auto @@ -347,7 +347,18 @@ local function calculateFieldInput() -- Add 10% safety margin, but ensure minimum of 1M RF/t during warmup when drain is 0 local minimumInput = math.max(drainRate * 1.1, 1000000) - return math.max(minimumInput, math.floor(totalInput)) + local result = math.max(minimumInput, math.floor(totalInput)) + + -- Verbose output + local fieldPct = string.format("%.1f%%", currentStrength * 100) + local targetPct = string.format("%.1f%%", targetStrength * 100) + if currentStrength < targetStrength - 0.01 then + print("[FIELD] " .. fieldPct .. " < " .. targetPct .. " target -> increasing input to " .. formatNumber(result) .. " RF/t") + elseif currentStrength > targetStrength + 0.01 then + print("[FIELD] " .. fieldPct .. " > " .. targetPct .. " target -> reducing input to " .. formatNumber(result) .. " RF/t") + end + + return result end -- Calculate safe output rate based on temperature and saturation @@ -368,52 +379,66 @@ local function calculateOutputRate() local temp = reactorInfo.temperature or 0 local saturation = reactorInfo.energySaturation / reactorInfo.maxEnergySaturation local generationRate = reactorInfo.generationRate or 0 + local result = 0 + local reason = "" -- If we're in emergency or cooling, limit output if state == STATES.EMERGENCY then - return 0 + result = 0 + reason = "EMERGENCY state" + elseif state == STATES.COOLING then + result = math.floor(generationRate * 0.5) + reason = "COOLING - 50% of generation" + elseif state == STATES.CHARGING or state == STATES.WARMING_UP then + result = math.floor(generationRate * 0.8) + reason = state .. " - 80% of generation" + else + -- Normal operation + local tempFactor = 1.0 + if temp > CONFIG.critical_temperature then + local overTemp = temp - CONFIG.critical_temperature + local tempRange = CONFIG.max_temperature - CONFIG.critical_temperature + tempFactor = 1.0 - (overTemp / tempRange) * 0.5 + reason = "temp " .. formatTemp(temp) .. " > critical, factor=" .. string.format("%.2f", tempFactor) + end + + local outputRate = generationRate * tempFactor + + if saturation > 0.8 then + outputRate = generationRate * 1.1 * tempFactor + if reason == "" then reason = "high saturation (110%)" end + elseif reason == "" then + reason = "normal operation" + end + + result = math.max(0, math.floor(outputRate)) end - if state == STATES.COOLING then - -- During cooling, output less than we generate to build saturation - return math.floor(generationRate * 0.5) + if generationRate > 0 then + print("[OUTPUT] " .. formatNumber(result) .. " RF/t (" .. reason .. ")") end - if state == STATES.CHARGING or state == STATES.WARMING_UP then - -- During warmup, output slightly less than generation - return math.floor(generationRate * 0.8) - end - - -- Normal operation: output slightly more than generation to keep temp rising slowly - -- But cap it if temperature is getting high - local tempFactor = 1.0 - if temp > CONFIG.critical_temperature then - -- Reduce output as we approach max temp - local overTemp = temp - CONFIG.critical_temperature - local tempRange = CONFIG.max_temperature - CONFIG.critical_temperature - tempFactor = 1.0 - (overTemp / tempRange) * 0.5 - end - - -- Output rate based on generation and saturation - local outputRate = generationRate * tempFactor - - -- If saturation is high, we can output more - if saturation > 0.8 then - outputRate = generationRate * 1.1 * tempFactor - end - - return math.max(0, math.floor(outputRate)) + return result end -- ============================================================================ -- STATE MACHINE LOGIC -- ============================================================================ +local lastState = nil + +local function setState(newState, reason) + if state ~= newState then + print("[STATE] " .. state .. " -> " .. newState .. " (" .. reason .. ")") + state = newState + end +end + local function processStateMachine() if not reactorInfo or not reactorInfo.status then -- SAFETY: Can't read reactor, go to emergency to maintain field if state ~= STATES.OFFLINE and state ~= STATES.ERROR then - state = STATES.EMERGENCY + setState(STATES.EMERGENCY, "cannot read reactor data") end return end @@ -429,15 +454,16 @@ local function processStateMachine() local fieldStrength = (reactorInfo.fieldStrength or 0) / maxField local saturation = (reactorInfo.energySaturation or 0) / maxSat + local fieldPct = string.format("%.1f%%", fieldStrength * 100) -- Check for emergency conditions first (always) if reactorStatus ~= "cold" and reactorStatus ~= "cooling" then if fieldStrength < CONFIG.min_field_strength then - state = STATES.EMERGENCY + setState(STATES.EMERGENCY, "field " .. fieldPct .. " below minimum " .. formatPercent(CONFIG.min_field_strength)) return end if temp > CONFIG.max_temperature then - state = STATES.EMERGENCY + setState(STATES.EMERGENCY, "temp " .. formatTemp(temp) .. " exceeds max " .. formatTemp(CONFIG.max_temperature)) return end end @@ -445,26 +471,25 @@ local function processStateMachine() -- Check for shutdown request if shutdownRequested then if reactorStatus == "cold" or reactorStatus == "cooling" then - state = STATES.SHUTDOWN + setState(STATES.SHUTDOWN, "shutdown requested, reactor cooling") shutdownRequested = false elseif state ~= STATES.EMERGENCY then - state = STATES.SHUTDOWN + setState(STATES.SHUTDOWN, "shutdown requested") end return end -- State transitions based on reactor status if reactorStatus == "cold" or reactorStatus == "invalid" then - state = STATES.OFFLINE + setState(STATES.OFFLINE, "reactor status: " .. reactorStatus) return end -- Clear charge request flag once field is sufficiently charged if chargeRequested then - local fieldPercent = fieldStrength -- Already calculated above as ratio - if fieldPercent >= CONFIG.charge_field_target then + if fieldStrength >= CONFIG.charge_field_target then chargeRequested = false - print("[INFO] Field charged to " .. string.format("%.1f%%", fieldPercent * 100) .. ", charge request cleared") + print("[INFO] Field charged to " .. fieldPct .. ", charge request cleared") end end @@ -473,33 +498,33 @@ local function processStateMachine() -- Keep current state during cooldown return end - state = STATES.COOLING + setState(STATES.COOLING, "reactor cooling down") return end if reactorStatus == "warming_up" then -- Use 0.9 multiplier for hysteresis (consistent with RUNNING state) if fieldStrength < CONFIG.charge_field_target * 0.9 then - state = STATES.CHARGING + setState(STATES.CHARGING, "field " .. fieldPct .. " below charge target") else - state = STATES.WARMING_UP + setState(STATES.WARMING_UP, "field " .. fieldPct .. " sufficient, warming up") end return end if reactorStatus == "running" then if fieldStrength < CONFIG.charge_field_target * 0.9 then - state = STATES.CHARGING + setState(STATES.CHARGING, "field " .. fieldPct .. " dropped below threshold while running") elseif temp > CONFIG.critical_temperature then - state = STATES.COOLING + setState(STATES.COOLING, "temp " .. formatTemp(temp) .. " above critical " .. formatTemp(CONFIG.critical_temperature)) else - state = STATES.RUNNING + setState(STATES.RUNNING, "normal operation") end return end if reactorStatus == "stopping" then - state = STATES.SHUTDOWN + setState(STATES.SHUTDOWN, "reactor stopping") return end end @@ -602,7 +627,7 @@ local function controlReactor() -- User requested charge - provide input flux to start charging the field inputRate = CONFIG.emergency_field_input outputRate = 0 - print("[DEBUG] OFFLINE+chargeRequested: inputRate=" .. inputRate) + print("[CHARGE] Providing " .. formatNumber(inputRate) .. " RF/t to charge field") else -- No action needed inputRate = 0 @@ -623,7 +648,6 @@ local function controlReactor() lastInputRate = inputRate lastOutputRate = outputRate - print("[DEBUG] Setting flux gates: input=" .. inputRate .. " output=" .. outputRate) local inputSuccess = pcall(function() inputGate.setFlowOverride(math.floor(inputRate)) end)