SourceCode/Bond/Servo/CMaster.cpp
@@ -372,6 +372,20 @@
         return -1;
      }
      lock();
      // Ensure stale transfer state does not leak across runs.
      m_nContinuousTransferStep = CTStep_Unknow;
      m_nContinuousWorkingPort = 0;
      m_nContinuousWorkingSlot = 0;
      if (m_pActiveRobotTask != nullptr) {
         LOGW("<Master>start: stale active robot task found in READY, clearing it.");
         delete m_pActiveRobotTask;
         m_pActiveRobotTask = nullptr;
      }
      unlock();
      LOGI("<Master>start reset transfer state(step=%d, port=%d, slot=%d).",
         m_nContinuousTransferStep, m_nContinuousWorkingPort, m_nContinuousWorkingSlot);
      m_bContinuousTransfer = false;
      m_bBatch = false;
      setState(MASTERSTATE::STARTING);
@@ -386,6 +400,20 @@
         return -1;
      }
      lock();
      // Continuous transfer must start from a clean state every time.
      m_nContinuousTransferStep = CTStep_Unknow;
      m_nContinuousWorkingPort = 0;
      m_nContinuousWorkingSlot = 0;
      if (m_pActiveRobotTask != nullptr) {
         LOGW("<Master>startContinuousTransfer: stale active robot task found in READY, clearing it.");
         delete m_pActiveRobotTask;
         m_pActiveRobotTask = nullptr;
      }
      unlock();
      LOGI("<Master>startContinuousTransfer reset transfer state(step=%d, port=%d, slot=%d).",
         m_nContinuousTransferStep, m_nContinuousWorkingPort, m_nContinuousWorkingSlot);
      m_bContinuousTransfer = true;
      m_bBatch = false;
      setState(MASTERSTATE::STARTING);
@@ -399,6 +427,20 @@
      if (m_state != MASTERSTATE::READY) {
         return -1;
      }
      lock();
      // Keep behavior consistent with other start paths.
      m_nContinuousTransferStep = CTStep_Unknow;
      m_nContinuousWorkingPort = 0;
      m_nContinuousWorkingSlot = 0;
      if (m_pActiveRobotTask != nullptr) {
         LOGW("<Master>startBatch: stale active robot task found in READY, clearing it.");
         delete m_pActiveRobotTask;
         m_pActiveRobotTask = nullptr;
      }
      unlock();
      LOGI("<Master>startBatch reset transfer state(step=%d, port=%d, slot=%d).",
         m_nContinuousTransferStep, m_nContinuousWorkingPort, m_nContinuousWorkingSlot);
      m_bContinuousTransfer = false;
      m_bBatch = true;
@@ -707,6 +749,91 @@
               continue;
            }
            // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。
            auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool {
               if (m_schedulingMode != SchedulingMode::Production) return true;
               const bool isG1Port = (portIndex == 0 || portIndex == 2);
               if (type == MaterialsType::G1) return isG1Port;
               if (type == MaterialsType::G2) return !isG1Port;
               return true;
            };
            // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。
            auto getPreferredPortForType = [&](MaterialsType targetType) -> int {
               if (m_schedulingMode != SchedulingMode::Production) return -1;
               auto preferG1ByG2 = [&](CGlass* pG2) -> int {
                  if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1;
                  int originPort = -1, originSlot = -1;
                  pG2->getOrginPort(originPort, originSlot);
                  if (originPort == 1) return 0; // Port2 -> Port1
                  if (originPort == 3) return 2; // Port4 -> Port3
                  return -1;
               };
               auto preferG2ByG1 = [&](CGlass* pG1) -> int {
                  if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1;
                  int originPort = -1, originSlot = -1;
                  pG1->getOrginPort(originPort, originSlot);
                  if (originPort == 0) return 1; // Port1 -> Port2
                  if (originPort == 2) return 3; // Port3 -> Port4
                  return -1;
               };
               if (targetType == MaterialsType::G1) {
                  int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG1ByG2(pFliper->getGlassFromSlot(1));   if (p >= 0) return p;
                  p = preferG1ByG2(pAligner->getGlassFromSlot(1));  if (p >= 0) return p;
               }
               else if (targetType == MaterialsType::G2) {
                  int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p;
               }
               return -1;
            };
            // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。
            auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool {
               if (m_pActiveRobotTask == nullptr) return false;
               CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext();
               CGlass* pExistingG2 = pBonder->getGlassFromSlot(0);
               if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true;
               CProcessJob* pjG1 = pIncomingG1->getProcessJob();
               CProcessJob* pjG2 = pExistingG2->getProcessJob();
               if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) {
                  std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id();
                  std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id();
                  LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。",
                     bonderName, pj1.c_str(), pj2.c_str());
                  delete m_pActiveRobotTask;
                  m_pActiveRobotTask = nullptr;
                  return false;
               }
               // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。
               if (m_schedulingMode == SchedulingMode::Production) {
                  int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0;
                  pIncomingG1->getOrginPort(g1Port, g1Slot);
                  pExistingG2->getOrginPort(g2Port, g2Slot);
                  const bool pairOk =
                     (g1Port == 0 && g2Port == 1) ||
                     (g1Port == 2 && g2Port == 3);
                  if (!pairOk) {
                     LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。",
                        bonderName, g1Port + 1, g2Port + 1);
                     delete m_pActiveRobotTask;
                     m_pActiveRobotTask = nullptr;
                     return false;
                  }
               }
               return true;
            };
            // Bonder1、Bonder2、Fliper、VacuumBake、Aligner,统计G2和G1的数量, 配对组数, 多出的类型
            int nG2Count = 0, nG1Count = 0, nGlassGroup, nExtraType;
@@ -837,11 +964,17 @@
            // VacuumBake(G1) -> Bonder
            if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) {
               m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0);
               if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) {
                  // 同 PJ 校验失败,本轮不下发搬送
               }
               CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
            }
            if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) {
               m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0);
               if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) {
                  // 同 PJ 校验失败,本轮不下发搬送
               }
               CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
            }
@@ -880,15 +1013,45 @@
            else {
               primaryType = MaterialsType::G1;
            }
            const int preferredPortForPrimary = getPreferredPortForType(primaryType);
            {
               static int s_prevPrimaryType = -1;
               static int s_prevPreferredPort = -2;
               const int curPrimaryType = (int)primaryType;
               if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) {
                  LOGI("<Master>LoadPort->Aligner规则(RUNNING): primaryType=%d, preferredPort=%d",
                     curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0);
                  s_prevPrimaryType = curPrimaryType;
                  s_prevPreferredPort = preferredPortForPrimary;
               }
            }
            for (int s = 0; s < 4; s++) {
               PortType pt = pLoadPorts[s]->getPortType();
               if (!rmd.armState[0] && pLoadPorts[s]->isEnable()
                  && (pt == PortType::Loading || pt == PortType::Both)
                  && pLoadPorts[s]->getPortStatus() == PORT_INUSE) {
                  if (!isProductionPortTypeMatch(s, primaryType)) {
                     continue;
                  }
                  if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) {
                     continue;
                  }
                  m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode);
                  if (m_pActiveRobotTask != nullptr) {
                     CGlass* pGlass = (CGlass*)m_pActiveRobotTask->getContext();
                     CProcessJob* pPJ = (pGlass != nullptr) ? pGlass->getProcessJob() : nullptr;
                     LOGI("<Master>LoadPort->Aligner命中(RUNNING): port=%d, primaryType=%d, preferredPort=%d, glass=%s, scheduled=%d, pj=%s",
                        s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0,
                        pGlass != nullptr ? pGlass->getID().c_str() : "",
                        (pGlass != nullptr && pGlass->isScheduledForProcessing()) ? 1 : 0,
                        pPJ != nullptr ? pPJ->id().c_str() : "NULL");
                     if (pGlass == nullptr) {
                        LOGE("<Master>LoadPort->Aligner命中(RUNNING)但context为空,任务已丢弃.");
                        delete m_pActiveRobotTask;
                        m_pActiveRobotTask = nullptr;
                        continue;
                     }
                     if (pGlass->getBuddy() != nullptr) {
                        delete m_pActiveRobotTask;
                        m_pActiveRobotTask = nullptr;
@@ -969,6 +1132,91 @@
               unlock(); // 等当前任务完成或中止后继续
               continue;
            }
            // 生产模式固定映射:Port1/3 -> G1,Port2/4 -> G2。
            auto isProductionPortTypeMatch = [&](int portIndex, MaterialsType type) -> bool {
               if (m_schedulingMode != SchedulingMode::Production) return true;
               const bool isG1Port = (portIndex == 0 || portIndex == 2);
               if (type == MaterialsType::G1) return isG1Port;
               if (type == MaterialsType::G2) return !isG1Port;
               return true;
            };
            // 生产模式:根据线上未配对玻璃反推下一片应来自哪个端口(Port1<->2, Port3<->4)。
            auto getPreferredPortForType = [&](MaterialsType targetType) -> int {
               if (m_schedulingMode != SchedulingMode::Production) return -1;
               auto preferG1ByG2 = [&](CGlass* pG2) -> int {
                  if (pG2 == nullptr || pG2->getType() != MaterialsType::G2 || pG2->getBuddy() != nullptr) return -1;
                  int originPort = -1, originSlot = -1;
                  pG2->getOrginPort(originPort, originSlot);
                  if (originPort == 1) return 0; // Port2 -> Port1
                  if (originPort == 3) return 2; // Port4 -> Port3
                  return -1;
               };
               auto preferG2ByG1 = [&](CGlass* pG1) -> int {
                  if (pG1 == nullptr || pG1->getType() != MaterialsType::G1 || pG1->getBuddy() != nullptr) return -1;
                  int originPort = -1, originSlot = -1;
                  pG1->getOrginPort(originPort, originSlot);
                  if (originPort == 0) return 1; // Port1 -> Port2
                  if (originPort == 2) return 3; // Port3 -> Port4
                  return -1;
               };
               if (targetType == MaterialsType::G1) {
                  int p = preferG1ByG2(pBonder1->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG1ByG2(pBonder2->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG1ByG2(pFliper->getGlassFromSlot(1));   if (p >= 0) return p;
                  p = preferG1ByG2(pAligner->getGlassFromSlot(1));  if (p >= 0) return p;
               }
               else if (targetType == MaterialsType::G2) {
                  int p = preferG2ByG1(pBonder1->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pBonder2->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pVacuumBake->getGlassFromSlot(1)); if (p >= 0) return p;
                  p = preferG2ByG1(pVacuumBake->getGlassFromSlot(2)); if (p >= 0) return p;
                  p = preferG2ByG1(pAligner->getGlassFromSlot(1)); if (p >= 0) return p;
               }
               return -1;
            };
            // Job 模式下要求 Bonder 内的 G1/G2 来自同一个 ProcessJob。
            auto validateBonderPairProcessJob = [&](CEquipment* pBonder, const char* bonderName) -> bool {
               if (m_pActiveRobotTask == nullptr) return false;
               CGlass* pIncomingG1 = (CGlass*)m_pActiveRobotTask->getContext();
               CGlass* pExistingG2 = pBonder->getGlassFromSlot(0);
               if (pIncomingG1 == nullptr || pExistingG2 == nullptr) return true;
               CProcessJob* pjG1 = pIncomingG1->getProcessJob();
               CProcessJob* pjG2 = pExistingG2->getProcessJob();
               if (m_bJobMode && (pjG1 == nullptr || pjG2 == nullptr || pjG1 != pjG2)) {
                  std::string pj1 = (pjG1 == nullptr) ? "NULL" : pjG1->id();
                  std::string pj2 = (pjG2 == nullptr) ? "NULL" : pjG2->id();
                  LOGW("<Master>%s配对拦截:G1/G2来自不同ProcessJob(G1=%s,G2=%s)。",
                     bonderName, pj1.c_str(), pj2.c_str());
                  delete m_pActiveRobotTask;
                  m_pActiveRobotTask = nullptr;
                  return false;
               }
               // 生产模式固定端口对:Port1<->Port2,Port3<->Port4。
               if (m_schedulingMode == SchedulingMode::Production) {
                  int g1Port = 0, g1Slot = 0, g2Port = 0, g2Slot = 0;
                  pIncomingG1->getOrginPort(g1Port, g1Slot);
                  pExistingG2->getOrginPort(g2Port, g2Slot);
                  const bool pairOk =
                     (g1Port == 0 && g2Port == 1) ||
                     (g1Port == 2 && g2Port == 3);
                  if (!pairOk) {
                     LOGW("<Master>%s配对拦截:端口对不匹配(要求1<->2或3<->4, 实际G1Port=%d,G2Port=%d)。",
                        bonderName, g1Port + 1, g2Port + 1);
                     delete m_pActiveRobotTask;
                     m_pActiveRobotTask = nullptr;
                     return false;
                  }
               }
               return true;
            };
            // 5.5) 暂停状态检查:若 CJ 或在制 PJ 处于 Paused,暂缓调度新的搬送
            bool pausedByEvent = false;
@@ -1098,10 +1346,16 @@
            // 13) VacuumBake(G1) -> Bonder(槽级判定:slot0(G2) 已有且 slot1(G1) 为空)
            if (!rmd.armState[0] && pBonder1->slotHasGlass(0) && !pBonder1->slotHasGlass(1)) {
               m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder1, MaterialsType::G1, MaterialsType::G0);
               if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder1, "Bonder1")) {
                  // 同 PJ 校验失败,本轮不下发搬送
               }
               CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
            }
            if (!rmd.armState[0] && pBonder2->slotHasGlass(0) && !pBonder2->slotHasGlass(1)) {
               m_pActiveRobotTask = createTransferTask(pVacuumBake, pBonder2, MaterialsType::G1, MaterialsType::G0);
               if (m_pActiveRobotTask != nullptr && !validateBonderPairProcessJob(pBonder2, "Bonder2")) {
                  // 同 PJ 校验失败,本轮不下发搬送
               }
               CHECK_RUN_ACTIVE_ROBOT_TASK(m_pActiveRobotTask);
            }
@@ -1123,16 +1377,46 @@
            // 16) LoadPort -> Aligner(受组数门限控制;统一 buddy/状态时序)
            if (blockLoadFromLP) { unlock(); continue; }
            const int preferredPortForPrimary = getPreferredPortForType(primaryType);
            {
               static int s_prevPrimaryType = -1;
               static int s_prevPreferredPort = -2;
               const int curPrimaryType = (int)primaryType;
               if (s_prevPrimaryType != curPrimaryType || s_prevPreferredPort != preferredPortForPrimary) {
                  LOGI("<Master>LoadPort->Aligner规则(RUNNING_BATCH): primaryType=%d, preferredPort=%d",
                     curPrimaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0);
                  s_prevPrimaryType = curPrimaryType;
                  s_prevPreferredPort = preferredPortForPrimary;
               }
            }
            for (int s = 0; s < 4; s++) {
               PortType pt = pLoadPorts[s]->getPortType();
               if (!rmd.armState[0] && pLoadPorts[s]->isEnable()
                  && (pt == PortType::Loading || pt == PortType::Both)
                  && pLoadPorts[s]->getPortStatus() == PORT_INUSE) {
                  if (!isProductionPortTypeMatch(s, primaryType)) {
                     continue;
                  }
                  if (preferredPortForPrimary >= 0 && s != preferredPortForPrimary) {
                     continue;
                  }
                  m_pActiveRobotTask = createTransferTask(pLoadPorts[s], pAligner, primaryType, secondaryType, 1, m_bJobMode);
                  if (m_pActiveRobotTask != nullptr) {
                     auto* pGlass = static_cast<CGlass*>(m_pActiveRobotTask->getContext());
                     CProcessJob* pPJ = (pGlass != nullptr) ? pGlass->getProcessJob() : nullptr;
                     LOGI("<Master>LoadPort->Aligner命中(RUNNING_BATCH): port=%d, primaryType=%d, preferredPort=%d, glass=%s, scheduled=%d, pj=%s",
                        s + 1, (int)primaryType, preferredPortForPrimary >= 0 ? (preferredPortForPrimary + 1) : 0,
                        pGlass != nullptr ? pGlass->getID().c_str() : "",
                        (pGlass != nullptr && pGlass->isScheduledForProcessing()) ? 1 : 0,
                        pPJ != nullptr ? pPJ->id().c_str() : "NULL");
                     if (pGlass == nullptr) {
                        LOGE("<Master>LoadPort->Aligner命中(RUNNING_BATCH)但context为空,任务已丢弃.");
                        delete m_pActiveRobotTask;
                        m_pActiveRobotTask = nullptr;
                        continue;
                     }
                     if (pGlass->getBuddy() != nullptr) {
                        delete m_pActiveRobotTask; m_pActiveRobotTask = nullptr;
                        continue;
@@ -3139,12 +3423,51 @@
         }
      }
      // 先清空所有端口玻璃的调度标记,避免沿用历史 PJ 状态。
      int clearedGlassCount = 0;
      for (int i = 0; i < 4; i++) {
         auto* pPort = (CLoadPort*)getEquipment(EQ_ID_LOADPORT1 + i);
         if (pPort == nullptr) continue;
         for (int slot = 1; slot <= SLOT_MAX; slot++) {
            auto* pGlass = pPort->getGlassFromSlot(slot);
            if (pGlass == nullptr) continue;
            pGlass->setProcessJob(nullptr);
            pGlass->setScheduledForProcessing(FALSE);
            clearedGlassCount++;
         }
      }
      LOGI("<Master>setProcessJobs: cleared scheduling marks on %d glass(es).", clearedGlassCount);
      auto toPortIndex = [](int eqid) -> int {
         switch (eqid) {
         case EQ_ID_LOADPORT1: return 0;
         case EQ_ID_LOADPORT2: return 1;
         case EQ_ID_LOADPORT3: return 2;
         case EQ_ID_LOADPORT4: return 3;
         default: return -1;
         }
      };
      auto hasAnySelectedPorts = [](const PJWarp& warp) -> bool {
         for (int p = 0; p < 4; p++) {
            if (warp.selectedPorts[p]) return true;
         }
         return false;
      };
      auto hasAnyLegacyCheckedSlots = [](const PJWarp& warp) -> bool {
         for (int s = 0; s < 8; s++) {
            if (warp.checkSlot[s]) return true;
         }
         return false;
      };
      // 更新context
      for (auto pj : m_processJobs) {
         for (auto& c : pj->carriers()) {
            auto pPort = getPortWithCarrierId(c.carrierId);
            if (pPort == nullptr) continue;
            const int portIndex = toPortIndex(pPort->getID());
            short downloadMap = 0;
            for (auto s : c.slots) {
@@ -3156,12 +3479,37 @@
            std::vector<uint8_t> newSlots;
            std::vector<void*> newContexts;
            PJWarp& warp = pj->getPjWarp();
            const bool useMultiPortWarp = hasAnySelectedPorts(warp);
            const bool useLegacyWarp = !useMultiPortWarp && hasAnyLegacyCheckedSlots(warp);
            for (auto s : c.slots) {
               auto pGlass = pPort->getGlassFromSlot(s);
               if (pGlass == nullptr) continue;
               newSlots.push_back(s);
               newContexts.push_back(pGlass);
               // 默认:Host 下发但未携带 PJWarp 细节时,carrier slots 视为应加工。
               BOOL scheduled = TRUE;
               int material = (pGlass->getType() == MaterialsType::G2) ? 2 : 1;
               if (1 <= s && s <= 8) {
                  if (useMultiPortWarp && 0 <= portIndex && portIndex < 4 && warp.selectedPorts[portIndex]) {
                     scheduled = warp.checkSlots[portIndex][s - 1];
                     material = warp.materialSlots[portIndex][s - 1];
                  }
                  else if (useLegacyWarp) {
                     scheduled = warp.checkSlot[s - 1];
                     material = warp.material[s - 1];
                  }
               }
               material = (material == 2) ? 2 : 1;
               pGlass->setProcessJob(pj);
               pGlass->setScheduledForProcessing(scheduled);
               pGlass->setType(static_cast<MaterialsType>(material));
               LOGI("<Master>setProcessJobs bind: pj=%s, port=%d, slot=%d, glass=%s, scheduled=%d, material=%d",
                  pj->id().c_str(), portIndex + 1, (int)s, pGlass->getID().c_str(), scheduled ? 1 : 0, material);
            }
            pj->setCarrierSlotsAndContexts(c.carrierId, newSlots, newContexts);
@@ -3463,6 +3811,33 @@
      }
      m_pControlJob->setPJs(tempPjs);
      // 重建运行态缓存,避免重启后仅依赖 Queued 选择导致找不到当前 PJ。
      m_inProcesJobs.clear();
      m_completeProcessJobs.clear();
      m_queueGlasses.clear();
      m_inProcesGlasses.clear();
      m_completeGlasses.clear();
      for (auto pj : tempPjs) {
         if (pj == nullptr) continue;
         switch (pj->state()) {
         case PJState::InProcess:
         case PJState::Paused:
            m_inProcesJobs.push_back(pj);
            break;
         case PJState::Completed:
         case PJState::Aborted:
         case PJState::Failed:
            m_completeProcessJobs.push_back(pj);
            break;
         default:
            break;
         }
      }
      LOGI("<Master>loadState: ProcessJob rebuild done. total=%d, inProcess=%d, complete=%d",
         (int)tempPjs.size(),
         (int)m_inProcesJobs.size(),
         (int)m_completeProcessJobs.size());
      // 更新contexts
      auto pjs = m_pControlJob->getPjs();
@@ -3495,14 +3870,25 @@
   CProcessJob* CMaster::acquireNextProcessJob()
   {
      if (m_pControlJob == nullptr) return nullptr;
      auto& pjs = m_pControlJob->getPjs();
      for (const auto pj : pjs) {
         if (pj->state() == PJState::Queued) {
            LOGI("<Master>acquireNextProcessJob: start queued PJ(%s)", pj->id().c_str());
            pj->start();
            return pj;
         }
      }
      // 若没有 Queued,继续复用已经在制/暂停的 PJ(例如 loadState 恢复后的场景)。
      for (const auto pj : pjs) {
         if (pj->state() == PJState::InProcess || pj->state() == PJState::Paused) {
            LOGI("<Master>acquireNextProcessJob: reuse PJ(%s), state=%d",
               pj->id().c_str(), (int)pj->state());
            return pj;
         }
      }
      return nullptr;
   }