lsh
2026-04-21 720e0926fa1c94b952c26e111206c5d6e1ed5ba2
src/main/java/com/zy/core/network/fake/ZyStationFakeSegConnect.java
@@ -2,7 +2,6 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.core.common.SpringUtils;
import com.zy.asrs.entity.DeviceConfig;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
@@ -10,29 +9,51 @@
import com.zy.core.enums.StationCommandType;
import com.zy.core.model.CommandResponse;
import com.zy.core.model.command.StationCommand;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.network.api.ZyStationConnectApi;
import com.zy.core.network.entity.ZyStationStatusEntity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicInteger;
public class ZyStationFakeSegConnect implements ZyStationConnectApi {
    private static final long DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS = 10000L;
    private static final long WAIT_SEGMENT_TIMEOUT_MS = 30000L;
    private long getWaitSegmentTimeoutMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_SEGMENT_WAIT_TIMEOUT_MS, 30000L);
    }
    private long getMoveStepDurationMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_MOVE_STEP_DURATION_MS, 500L);
    }
    private long getIdleLoopDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_IDLE_LOOP_DELAY_MS, 200L);
    }
    private long getBlockedLoopDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_BLOCKED_LOOP_DELAY_MS, 1000L);
    }
    private long getInitializeDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_INITIALIZE_DELAY_MS, 0L);
    }
    private long getFinishDelayMs() {
        return FakeConfigSupport.getLong(FakeConfigKeys.FAKE_STATION_FINISH_DELAY_MS, 0L);
    }
    private static final String STATUS_WAITING = "WAITING";
    private static final String STATUS_RUNNING = "RUNNING";
@@ -41,65 +62,66 @@
    private static final String STATUS_TIMEOUT = "TIMEOUT";
    private static final String STATUS_FINISHED = "FINISHED";
    private final Map<Integer, ReentrantLock> stationLocks = new ConcurrentHashMap<Integer, ReentrantLock>();
    private final Map<Integer, List<ZyStationStatusEntity>> deviceStatusMap = new ConcurrentHashMap<Integer, List<ZyStationStatusEntity>>();
    private final Map<Integer, DeviceConfig> deviceConfigMap = new ConcurrentHashMap<Integer, DeviceConfig>();
    private static final String SEGMENT_MERGE_APPEND = "APPEND";
    private static final String SEGMENT_MERGE_REPLACE_TARGET_CHANGED = "REPLACE_TARGET_CHANGED";
    private static final String SEGMENT_MERGE_REPLACE_REROUTE = "REPLACE_REROUTE";
    private static final String SEGMENT_MERGE_IGNORE_DISCONNECTED = "IGNORE_DISCONNECTED";
    private static final String SEGMENT_MERGE_IGNORE_CURRENT_MISSING = "IGNORE_CURRENT_MISSING";
    private final FakeStationStateManager stateManager = new FakeStationStateManager();
    private final FakeStationMoveEngine moveEngine = new FakeStationMoveEngine(stateManager);
    private final FakeStationBlockManager blockManager = new FakeStationBlockManager(stateManager);
    private static final AtomicInteger DEVICE_EXECUTOR_THREAD_SEQ = new AtomicInteger(1);
    private volatile ScheduledExecutorService loopScheduler = createLoopScheduler();
    private RedisUtil redisUtil;
    private final Map<Integer, ExecutorService> deviceExecutors = new ConcurrentHashMap<Integer, ExecutorService>();
    private final Map<Integer, BlockingQueue<StationCommand>> taskQueues = new ConcurrentHashMap<Integer, BlockingQueue<StationCommand>>();
    private final Map<Integer, Long> taskLastUpdateTime = new ConcurrentHashMap<Integer, Long>();
    private final Map<Integer, Boolean> taskRunning = new ConcurrentHashMap<Integer, Boolean>();
    private final ExecutorService executor = Executors.newCachedThreadPool();
    private RedisUtil redisUtil;
    private final Map<Integer, TaskRuntimeContext> taskContexts = new ConcurrentHashMap<Integer, TaskRuntimeContext>();
    private final Map<Integer, AtomicInteger> taskLoopGenerations = new ConcurrentHashMap<Integer, AtomicInteger>();
    private final Map<Integer, Object> taskLifecycleLocks = new ConcurrentHashMap<Integer, Object>();
    private volatile Set<Integer> legalClearStationIds = new HashSet<Integer>();
    private volatile Set<Integer> barcodeStationIds = new HashSet<Integer>();
    public void addFakeConnect(DeviceConfig deviceConfig, RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
        if (deviceConfigMap.containsKey(deviceConfig.getDeviceNo())) {
            return;
        }
        deviceConfigMap.put(deviceConfig.getDeviceNo(), deviceConfig);
        deviceStatusMap.put(deviceConfig.getDeviceNo(), new CopyOnWriteArrayList<ZyStationStatusEntity>());
        refreshLegalClearStationIds();
        refreshBarcodeStationIds();
        stateManager.registerDevice(deviceConfig);
    }
    @Override
    public boolean connect() {
        if (loopScheduler == null || loopScheduler.isShutdown() || loopScheduler.isTerminated()) {
            loopScheduler = createLoopScheduler();
        }
        return true;
    }
    @Override
    public boolean disconnect() {
        executor.shutdownNow();
        ScheduledExecutorService scheduler = loopScheduler;
        if (scheduler != null) {
            scheduler.shutdownNow();
        }
        for (ExecutorService executor : deviceExecutors.values()) {
            executor.shutdownNow();
        }
        deviceExecutors.clear();
        taskQueues.clear();
        taskLastUpdateTime.clear();
        taskRunning.clear();
        taskContexts.clear();
        taskLoopGenerations.clear();
        taskLifecycleLocks.clear();
        return true;
    }
    @Override
    public List<ZyStationStatusEntity> getStatus(Integer deviceNo) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new ArrayList<ZyStationStatusEntity>();
        }
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        if (statusList.isEmpty() && deviceConfig != null) {
            List<ZyStationStatusEntity> init = JSON.parseArray(deviceConfig.getFakeInitStatus(),
                    ZyStationStatusEntity.class);
            if (init != null) {
                statusList.addAll(init);
                for (ZyStationStatusEntity status : statusList) {
                    status.setAutoing(true);
                    status.setLoading(false);
                    status.setInEnable(true);
                    status.setOutEnable(true);
                    status.setEmptyMk(false);
                    status.setFullPlt(false);
                    status.setRunBlock(false);
                    status.setPalletHeight(0);
                    status.setError(0);
                    status.setBarcode("");
                }
            }
        }
        return statusList;
        return stateManager.getStatus(deviceNo);
    }
    @Override
@@ -110,25 +132,44 @@
        }
        if (command.getCommandType() != StationCommandType.MOVE) {
            handleCommand(deviceNo, command);
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        if (isDirectMoveCommand(command)) {
            handleDirectMoveCommand(deviceNo, command);
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>()).offer(command);
        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
        if (taskRunning.putIfAbsent(taskNo, true) == null) {
            executor.submit(new Runnable() {
            News.info("[WCS Debug] fake sendCommand收到非MOVE命令,deviceNo={},taskNo={},stationId={},targetStaNo={},commandType={}",
                    deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType());
            getDeviceExecutor(deviceNo).submit(new Runnable() {
                @Override
                public void run() {
                    runTaskLoop(deviceNo, taskNo);
                    handleCommand(deviceNo, command);
                }
            });
            return new CommandResponse(true, "命令已受理(异步执行)");
        }
        int loopGeneration = 0;
        boolean shouldSchedule = false;
        Object lifecycleLock = getTaskLifecycleLock(taskNo);
        synchronized (lifecycleLock) {
            BlockingQueue<StationCommand> queue = taskQueues.computeIfAbsent(taskNo, key -> new LinkedBlockingQueue<StationCommand>());
            queue.offer(command);
            taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
            News.info("[WCS Debug] fake sendCommand入队,deviceNo={},taskNo={},stationId={},targetStaNo={},segmentNo={},segmentCount={},queueSize={},running={}",
                    deviceNo, taskNo, command.getStationId(), command.getTargetStaNo(), command.getSegmentNo(), command.getSegmentCount(),
                    queue.size(), taskRunning.containsKey(taskNo));
            if (taskRunning.putIfAbsent(taskNo, true) == null) {
                TaskRuntimeContext context = new TaskRuntimeContext(taskNo, deviceNo, stateManager.getThreadImpl(deviceNo));
                taskContexts.put(taskNo, context);
                AtomicInteger generation = taskLoopGenerations.computeIfAbsent(taskNo, key -> new AtomicInteger(0));
                loopGeneration = generation.incrementAndGet();
                context.loopGeneration = loopGeneration;
                shouldSchedule = true;
                News.info("[WCS Debug] fake task准备启动执行线程,deviceNo={},taskNo={},queueSize={},loopGeneration={}",
                        deviceNo, taskNo, queue.size(), loopGeneration);
            } else {
                News.info("[WCS Debug] fake task复用已存在执行线程,deviceNo={},taskNo={},queueSize={}",
                        deviceNo, taskNo, queue.size());
            }
        }
        if (shouldSchedule) {
            scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
        }
        return new CommandResponse(true, "命令已受理(异步执行)");
@@ -141,31 +182,7 @@
    @Override
    public CommandResponse clearTaskBufferSlot(Integer deviceNo, Integer stationId, Integer slotIdx) {
        if (deviceNo == null || stationId == null || slotIdx == null || slotIdx <= 0) {
            return new CommandResponse(false, "清理缓存区槽位参数无效");
        }
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return new CommandResponse(false, "未找到设备状态");
        }
        for (ZyStationStatusEntity status : statusList) {
            if (status == null || !stationId.equals(status.getStationId())) {
                continue;
            }
            List<StationTaskBufferItem> taskBufferItems = status.getTaskBufferItems();
            if (taskBufferItems == null || taskBufferItems.isEmpty()) {
                return new CommandResponse(true, "缓存区槽位已为空");
            }
            for (StationTaskBufferItem item : taskBufferItems) {
                if (item != null && slotIdx.equals(item.getSlotIdx())) {
                    item.setTaskNo(0);
                    item.setTargetStaNo(0);
                    return new CommandResponse(true, "缓存区槽位清理成功");
                }
            }
            return new CommandResponse(false, "未找到缓存区槽位");
        }
        return new CommandResponse(false, "未找到站点状态");
        return stateManager.clearTaskBufferSlot(deviceNo, stationId, slotIdx);
    }
    @Override
@@ -173,116 +190,209 @@
        return new byte[0];
    }
    private boolean isDirectMoveCommand(StationCommand command) {
        if (command == null || command.getCommandType() != StationCommandType.MOVE) {
            return false;
        }
        List<Integer> path = command.getNavigatePath();
        if (command.getStationId() == null || !command.getStationId().equals(command.getTargetStaNo())) {
            return false;
        }
        if (path == null || path.isEmpty()) {
            return true;
        }
        return path.size() == 1 && command.getStationId().equals(path.get(0));
    private ScheduledExecutorService createLoopScheduler() {
        return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "fake-station-loop-scheduler");
                thread.setDaemon(true);
                return thread;
            }
        });
    }
    private void handleDirectMoveCommand(Integer deviceNo, StationCommand command) {
        Integer taskNo = command.getTaskNo();
        Integer stationId = command.getStationId();
        Integer targetStationId = command.getTargetStaNo();
        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
                && stationId.equals(targetStationId)) {
            generateStationData(deviceNo, taskNo, stationId, targetStationId);
        }
        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
        context.startStationId = stationId;
        context.currentStationId = stationId;
        context.finalTargetStationId = targetStationId;
        context.initialized = true;
        context.status = STATUS_RUNNING;
        context.appendStitchedPath(Arrays.asList(stationId));
        context.addPassedStation(stationId);
        context.generateBarcode = checkTaskNoInArea(taskNo);
        traceEvent(deviceNo, context, "MOVE_INIT", "同站点任务直接到位", buildDetails("stationId", stationId), false);
        if (context.generateBarcode) {
            generateStationBarcode(taskNo, stationId, deviceNo);
        }
        traceEvent(deviceNo, context, "ARRIVED", "任务已在起点站点完成", buildDetails("barcodeGenerated",
                context.generateBarcode, "stationId", stationId), false);
        context.status = STATUS_FINISHED;
        traceEvent(deviceNo, context, "TASK_END", "任务执行完成", buildDetails("reason", STATUS_FINISHED), true);
    private ExecutorService getDeviceExecutor(Integer deviceNo) {
        return deviceExecutors.computeIfAbsent(deviceNo, key -> Executors.newSingleThreadExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable,
                        "fake-station-device-" + key + "-" + DEVICE_EXECUTOR_THREAD_SEQ.getAndIncrement());
                thread.setDaemon(true);
                return thread;
            }
        }));
    }
    private void runTaskLoop(Integer deviceNo, Integer taskNo) {
        TaskRuntimeContext context = new TaskRuntimeContext(taskNo, getThreadImpl(deviceNo));
        try {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    if (!isTerminalStatus(context.status)) {
                        context.status = STATUS_CANCELLED;
    private void scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) {
        scheduleTaskLoop(deviceNo, taskNo, loopGeneration, 0L);
    }
    private void scheduleTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) {
        Runnable task = new Runnable() {
            @Override
            public void run() {
                getDeviceExecutor(deviceNo).submit(new Runnable() {
                    @Override
                    public void run() {
                        runTaskLoop(deviceNo, taskNo, loopGeneration);
                    }
                    break;
                }
                });
            }
        };
        if (delayMs <= 0L) {
            task.run();
            return;
        }
        loopScheduler.schedule(task, delayMs, TimeUnit.MILLISECONDS);
    }
                if (hasTaskReset(taskNo)) {
    private void scheduleTaskLoopByDelay(Integer deviceNo, Integer taskNo, int loopGeneration, long delayMs) {
        scheduleTaskLoop(deviceNo, taskNo, loopGeneration, Math.max(delayMs, 1L));
    }
    private void runTaskLoop(Integer deviceNo, Integer taskNo, int loopGeneration) {
        TaskRuntimeContext context = taskContexts.get(taskNo);
        if (context == null) {
            News.info("[WCS Debug] fake task忽略无上下文续跑片段,deviceNo={},taskNo={},loopGeneration={}",
                    deviceNo, taskNo, loopGeneration);
            return;
        }
        if (!deviceNo.equals(context.deviceNo)) {
            News.info("[WCS Debug] fake task忽略跨设备续跑片段,taskNo={},expectedDeviceNo={},actualDeviceNo={},loopGeneration={}",
                    taskNo, context.deviceNo, deviceNo, loopGeneration);
            return;
        }
        if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) {
            News.info("[WCS Debug] fake task忽略过期续跑片段,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}",
                    deviceNo, taskNo, loopGeneration, context.loopGeneration);
            return;
        }
        News.info("[WCS Debug] fake task进入runTaskLoop,deviceNo={},taskNo={},threadImpl={},queueExists={},queueSize={},loopGeneration={},status={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                deviceNo, taskNo, context.threadImpl, taskQueues.containsKey(taskNo),
                taskQueues.containsKey(taskNo) && taskQueues.get(taskNo) != null ? taskQueues.get(taskNo).size() : null, loopGeneration,
                context.status, context.initialized, context.currentStationId, context.finalTargetStationId,
                context.blockedStationId, context.getPendingStationIds());
        long nextDelayMs = 0L;
        boolean shouldContinue = false;
        try {
            if (Thread.currentThread().isInterrupted()) {
                if (!isTerminalStatus(context.status)) {
                    context.status = STATUS_CANCELLED;
                    break;
                }
            } else if (hasTaskReset(taskNo)) {
                context.status = STATUS_CANCELLED;
            } else {
                BlockingQueue<StationCommand> commandQueue = taskQueues.get(taskNo);
                if (commandQueue == null) {
                    break;
                }
                StationCommand command = commandQueue.poll(100, TimeUnit.MILLISECONDS);
                if (command != null) {
                    taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
                    context.lastCommandAt = System.currentTimeMillis();
                    handleIncomingSegment(deviceNo, context, command);
                }
                if (!context.pendingPathQueue.isEmpty()) {
                    if (!context.initialized || context.currentStationId == null) {
                        initializeTaskPosition(deviceNo, context);
                        continue;
                    context.status = STATUS_FINISHED;
                } else {
                    StationCommand command = commandQueue.poll();
                    if (command != null) {
                        taskLastUpdateTime.put(taskNo, System.currentTimeMillis());
                        context.lastCommandAt = System.currentTimeMillis();
                        handleIncomingSegment(deviceNo, context, command);
                    }
                    if (!executeNextMove(deviceNo, context)) {
                        break;
                    if (!isTerminalStatus(context.status)) {
                        if (!context.pendingPathQueue.isEmpty()) {
                            News.info("[WCS Debug] fake task准备推进,deviceNo={},taskNo={},initialized={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                                    deviceNo, taskNo, context.initialized, context.currentStationId,
                                    context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds());
                            if (!context.initialized || context.currentStationId == null) {
                                nextDelayMs = initializeTaskPosition(deviceNo, context);
                            } else {
                                MoveStepResult moveResult = executeNextMove(deviceNo, context);
                                if (!moveResult.shouldContinue()) {
                                    context.status = STATUS_FINISHED;
                                }
                                nextDelayMs = moveResult.getNextDelayMs();
                            }
                        } else {
                            News.info("[WCS Debug] fake task进入空闲态,deviceNo={},taskNo={},status={},currentStationId={},targetStationId={},blockedStationId={},pendingPath={}",
                                    deviceNo, taskNo, context.status, context.currentStationId,
                                    context.finalTargetStationId, context.blockedStationId, context.getPendingStationIds());
                            IdleStepResult idleResult = handleIdleState(deviceNo, context);
                            if (idleResult.isFinished() && !isTerminalStatus(context.status)) {
                                context.status = STATUS_FINISHED;
                            }
                            nextDelayMs = idleResult.getNextDelayMs();
                        }
                    }
                    continue;
                    shouldContinue = !isTerminalStatus(context.status)
                            && taskQueues.containsKey(taskNo)
                            && (commandQueue != null);
                }
            }
        } catch (Exception e) {
            context.status = STATUS_CANCELLED;
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
            News.info("[WCS Debug] 任务{}执行异常,当前站点={},目标站={},待执行路径={},异常类型={},异常信息={}",
                    taskNo, context.currentStationId, context.finalTargetStationId, context.getPendingStationIds(),
                    e.getClass().getSimpleName(), e.getMessage());
        }
                if (handleIdleState(deviceNo, context)) {
                    break;
                }
        if (shouldContinue) {
            if (nextDelayMs > 0L) {
                scheduleTaskLoopByDelay(deviceNo, taskNo, loopGeneration, nextDelayMs);
            } else {
                scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            if (!isTerminalStatus(context.status)) {
                context.status = STATUS_CANCELLED;
            return;
        }
        finishTaskLoop(deviceNo, taskNo, context, loopGeneration);
    }
    private void finishTaskLoop(Integer deviceNo, Integer taskNo, TaskRuntimeContext context, int loopGeneration) {
        Object lifecycleLock = getTaskLifecycleLock(taskNo);
        synchronized (lifecycleLock) {
            if (context.loopGeneration != loopGeneration || !isCurrentLoopGeneration(taskNo, loopGeneration)) {
                News.info("[WCS Debug] fake task忽略过期结束清理,deviceNo={},taskNo={},expectedLoopGeneration={},actualLoopGeneration={}",
                        deviceNo, taskNo, loopGeneration, context.loopGeneration);
                return;
            }
        } finally {
            BlockingQueue<StationCommand> queue = taskQueues.get(taskNo);
            if (queue != null && !queue.isEmpty()) {
                News.info("[WCS Debug] fake task结束清理前发现新命令,恢复续跑,deviceNo={},taskNo={},queueSize={},loopGeneration={}",
                        deviceNo, taskNo, queue.size(), loopGeneration);
                scheduleTaskLoop(deviceNo, taskNo, loopGeneration);
                return;
            }
            News.info("[WCS Debug] fake task即将退出runTaskLoop,deviceNo={},taskNo={},status={},queueSizeBeforeCleanup={},lastCurrentStationId={},targetStationId={},loopGeneration={}",
                    deviceNo, taskNo, context.status, queue == null ? null : queue.size(), context.currentStationId, context.finalTargetStationId, loopGeneration);
            taskQueues.remove(taskNo);
            taskLastUpdateTime.remove(taskNo);
            taskRunning.remove(taskNo);
            if (!isTerminalStatus(context.status)) {
                context.status = STATUS_FINISHED;
            }
            traceEvent(deviceNo, context, "TASK_END", "任务执行结束并清理资源",
                    buildDetails("reason", context.status), true);
            News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={}", taskNo, context.status);
            taskContexts.remove(taskNo);
            taskLoopGenerations.remove(taskNo);
            taskLifecycleLocks.remove(taskNo, lifecycleLock);
        }
        if (!isTerminalStatus(context.status)) {
            context.status = STATUS_FINISHED;
        }
        traceEvent(deviceNo, context, "TASK_END", "任务执行结束并清理资源",
                buildDetails("reason", context.status, "loopGeneration", loopGeneration), true);
        News.info("[WCS Debug] 任务{}执行结束并清理资源,状态={},loopGeneration={}", taskNo, context.status, loopGeneration);
    }
    private void handleIncomingSegment(Integer deviceNo, TaskRuntimeContext context, StationCommand command) {
        if (!deviceNo.equals(context.deviceNo)) {
            traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段来自不同设备车道,已忽略",
                    buildDetails("expectedDeviceNo", context.deviceNo, "actualDeviceNo", deviceNo,
                            "segmentNo", command.getSegmentNo(), "segmentCount", command.getSegmentCount()), false);
            return;
        }
        List<Integer> newPath = normalizePath(command.getNavigatePath());
        Integer lastInQueue = getLastInQueue(context.pendingPathQueue);
        int startIndex = getPathAppendStartIndex(newPath, context.currentStationId, lastInQueue);
        Integer previousTargetStationId = context.finalTargetStationId;
        Integer commandTargetStationId = command.getTargetStaNo();
        boolean targetChanged = commandTargetStationId != null
                && !commandTargetStationId.equals(previousTargetStationId);
        boolean queueEmpty = context.pendingPathQueue.isEmpty();
        boolean newPathContainsCurrent = context.currentStationId != null && newPath.contains(context.currentStationId);
        boolean pathConnectedToTail = startIndex >= 0;
        List<Integer> oldPendingStations = context.getPendingStationIds();
        boolean shouldClearBarcodeSourceOnReroute = context.currentStationId != null
                && context.currentStationId.equals(command.getStationId())
                && isBarcodeStation(context.currentStationId)
                && previousTargetStationId != null
                && previousTargetStationId.equals(context.currentStationId)
                && commandTargetStationId != null
                && !commandTargetStationId.equals(context.currentStationId);
        context.setStartStationIdIfAbsent(command.getStationId());
        if (!context.generateBarcode && checkTaskNoInArea(context.taskNo)) {
@@ -292,15 +402,15 @@
        traceEvent(deviceNo, context, "SEGMENT_RECEIVED", "收到新的路径分段命令",
                buildDetails("segmentPath", newPath, "appendStartIndex", startIndex, "currentStationId",
                        context.currentStationId, "queueTailStationId", lastInQueue, "commandStationId",
                        command.getStationId(), "commandTargetStationId", command.getTargetStaNo()),
                        command.getStationId(), "commandTargetStationId", commandTargetStationId,
                        "previousTargetStationId", previousTargetStationId),
                false);
        Integer commandTargetStationId = command.getTargetStaNo();
        if (commandTargetStationId != null) {
            if (!commandTargetStationId.equals(context.finalTargetStationId)) {
            if (targetChanged) {
                traceEvent(deviceNo, context, "TARGET_SWITCHED",
                        "任务目标站发生切换: " + context.finalTargetStationId + " -> " + commandTargetStationId,
                        buildDetails("fromTargetStationId", context.finalTargetStationId, "toTargetStationId",
                        "任务目标站发生切换: " + previousTargetStationId + " -> " + commandTargetStationId,
                        buildDetails("fromTargetStationId", previousTargetStationId, "toTargetStationId",
                                commandTargetStationId),
                        false);
                context.arrivalHandled = false;
@@ -308,50 +418,140 @@
            context.finalTargetStationId = commandTargetStationId;
            syncCurrentStationTarget(context.taskNo, context.currentStationId, context.finalTargetStationId);
        }
        context.segmentNo = command.getSegmentNo();
        context.segmentCount = command.getSegmentCount();
        if (!newPath.isEmpty() && startIndex < 0) {
            traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法与当前运行上下文衔接,已忽略",
                    buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                            "queueTailStationId", lastInQueue, "ignoreReason", "PATH_NOT_CONNECTED"),
                    false);
            context.latestAppendedPath.clear();
            return;
        boolean tailConnectedAppend = !queueEmpty && pathConnectedToTail;
        boolean shouldReplace = !tailConnectedAppend && !queueEmpty
                && (targetChanged || (!pathConnectedToTail && newPathContainsCurrent));
        String ignoreReason = null;
        if (!shouldReplace && !tailConnectedAppend && !newPath.isEmpty() && !pathConnectedToTail) {
            ignoreReason = newPathContainsCurrent ? SEGMENT_MERGE_IGNORE_DISCONNECTED : SEGMENT_MERGE_IGNORE_CURRENT_MISSING;
            if (queueEmpty && context.currentStationId == null) {
                startIndex = 0;
            } else {
                traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略",
                        buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                                "queueTailStationId", lastInQueue, "ignoreReason", ignoreReason,
                                "queueEmpty", queueEmpty, "tailConnectedAppend", tailConnectedAppend,
                                "targetChanged", targetChanged),
                        false);
                context.latestAppendedPath.clear();
                return;
            }
        }
        if (tailConnectedAppend && startIndex < 0) {
            startIndex = 0;
        }
        List<Integer> appendedPath = new ArrayList<Integer>();
        for (int i = startIndex; i < newPath.size(); i++) {
            Integer stationId = newPath.get(i);
            context.pendingPathQueue.offer(stationId);
            appendedPath.add(stationId);
        }
        context.appendStitchedPath(appendedPath);
        if (!appendedPath.isEmpty()) {
        List<Integer> replacedFuturePath = new ArrayList<Integer>();
        Integer duplicateHeadStationId = null;
        String mergeMode = SEGMENT_MERGE_APPEND;
        if (shouldReplace) {
            replacedFuturePath = rebuildPendingPathFromCurrent(context, newPath);
            boolean atTargetAlready = context.currentStationId != null && context.currentStationId.equals(commandTargetStationId);
            if (context.currentStationId != null && replacedFuturePath.isEmpty() && !atTargetAlready) {
                traceEvent(deviceNo, context, "SEGMENT_IGNORED", "路径分段无法接入当前运行上下文,已忽略",
                        buildDetails("segmentPath", newPath, "currentStationId", context.currentStationId,
                                "queueTailStationId", lastInQueue, "ignoreReason", SEGMENT_MERGE_IGNORE_CURRENT_MISSING),
                        false);
                context.latestAppendedPath.clear();
                return;
            }
            replacePendingPathQueue(context, replacedFuturePath);
            context.replaceLatestPath(replacedFuturePath);
            mergeMode = targetChanged ? SEGMENT_MERGE_REPLACE_TARGET_CHANGED : SEGMENT_MERGE_REPLACE_REROUTE;
            traceEvent(deviceNo, context, "SEGMENT_REPLACED",
                    "新的 reroute 路径已覆盖旧 future queue",
                    buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId,
                            "previousTargetStationId", previousTargetStationId, "newTargetStationId", commandTargetStationId,
                            "oldQueue", oldPendingStations, "replacedFuturePath", replacedFuturePath,
                            "queueSize", context.pendingPathQueue.size()),
                    false);
        } else {
            for (int i = startIndex; i < newPath.size(); i++) {
                Integer stationId = newPath.get(i);
                context.pendingPathQueue.offer(stationId);
                appendedPath.add(stationId);
            }
            if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) {
                duplicateHeadStationId = context.pendingPathQueue.poll();
                if (!appendedPath.isEmpty() && duplicateHeadStationId.equals(appendedPath.get(0))) {
                    appendedPath.remove(0);
                }
            }
            context.appendStitchedPath(appendedPath);
            traceEvent(deviceNo, context, "SEGMENT_APPENDED",
                    "路径分段已追加到待执行队列,队列长度=" + context.pendingPathQueue.size(),
                    buildDetails("segmentPath", newPath, "appendedPath", appendedPath, "appendStartIndex",
                            startIndex, "queueSize", context.pendingPathQueue.size()),
                    buildDetails("mergeMode", mergeMode, "segmentPath", newPath, "appendedPath", appendedPath,
                            "appendStartIndex", startIndex, "queueSize", context.pendingPathQueue.size(),
                            "tailConnectedAppend", tailConnectedAppend, "targetChanged", targetChanged,
                            "duplicateHeadStationId", duplicateHeadStationId),
                    false);
        }
        if (duplicateHeadStationId != null) {
            traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部重复当前站点,已移除",
                    buildDetails("currentStationId", context.currentStationId,
                            "duplicateHeadStationId", duplicateHeadStationId,
                            "queueSize", context.pendingPathQueue.size(),
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
        }
        if (context.currentStationId != null && context.currentStationId.equals(context.pendingPathQueue.peek())) {
            Integer trimmedHead = context.pendingPathQueue.poll();
            traceEvent(deviceNo, context, "SEGMENT_TRIMMED", "待执行队列头部仍与当前站点重复,已再次移除",
                    buildDetails("currentStationId", context.currentStationId,
                            "duplicateHeadStationId", trimmedHead,
                            "queueSize", context.pendingPathQueue.size(),
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
        }
        context.lastCommandAt = System.currentTimeMillis();
        boolean segmentAccepted = !appendedPath.isEmpty() || !replacedFuturePath.isEmpty()
                || (context.currentStationId != null && context.currentStationId.equals(commandTargetStationId))
                || (context.currentStationId != null && context.currentStationId.equals(command.getStationId())
                && commandTargetStationId != null && commandTargetStationId.equals(context.finalTargetStationId));
        if (segmentAccepted) {
            if (shouldClearBarcodeSourceOnReroute && context.currentStationId != null) {
                Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
                if (currentDeviceNo != null) {
                    guardedClearStationForDispatch(currentDeviceNo, context.currentStationId, context.taskNo, "barcodeSourceRerouteAccepted");
                    traceEvent(deviceNo, context, "SOURCE_STATION_CLEARED", "条码源站已完成任务交接,源站状态已清除",
                            buildDetails("stationId", context.currentStationId,
                                    "previousTargetStationId", previousTargetStationId,
                                    "newTargetStationId", commandTargetStationId,
                                    "mergeMode", mergeMode),
                            false);
                }
            }
            resumeFromCurrentStation(deviceNo, context, mergeMode, appendedPath, replacedFuturePath,
                    shouldClearBarcodeSourceOnReroute);
        }
    }
    private void initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) {
    private long initializeTaskPosition(Integer deviceNo, TaskRuntimeContext context) {
        Integer nextStationId = context.pendingPathQueue.peek();
        if (nextStationId == null) {
            return;
            return 0L;
        }
        if (context.currentStationId == null) {
            Integer actualCurrentStationId = findCurrentStationIdByTask(context.taskNo);
            Integer actualCurrentStationId = stateManager.findCurrentStationIdByTask(context.taskNo);
            if (actualCurrentStationId != null) {
                context.currentStationId = actualCurrentStationId;
                context.initialized = true;
                context.status = STATUS_RUNNING;
                context.blockedStationId = null;
                Integer actualDeviceNo = getDeviceNoByStationId(actualCurrentStationId);
                Integer actualDeviceNo = stateManager.getDeviceNoByStationId(actualCurrentStationId);
                if (actualDeviceNo != null) {
                    clearRunBlock(actualCurrentStationId, actualDeviceNo);
                    guardedClearRunBlock(context.taskNo, actualCurrentStationId, actualDeviceNo);
                }
                trimPendingPathToCurrent(context.pendingPathQueue, actualCurrentStationId);
@@ -361,24 +561,26 @@
                context.addPassedStation(actualCurrentStationId);
                context.lastStepAt = System.currentTimeMillis();
                context.lastProgressAt = context.lastStepAt;
                context.lastProgressStationId = context.currentStationId;
                guardedPublishTaskLocation(context.taskNo, actualDeviceNo, actualCurrentStationId, true, false);
                traceEvent(deviceNo, context, "MOVE_INIT", "任务从当前实际站点恢复执行",
                        buildDetails("stationId", actualCurrentStationId, "recovered", true), false);
                return;
                return 0L;
            }
        }
        context.currentStationId = nextStationId;
        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
        if (currentDeviceNo == null) {
            context.pendingPathQueue.poll();
            return;
            return 0L;
        }
        boolean result = initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
        boolean result = moveEngine.initStationMove(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
                context.finalTargetStationId, true, null);
        if (!result) {
            sleep(200);
            return;
            return getIdleLoopDelayMs();
        }
        context.initialized = true;
@@ -386,25 +588,28 @@
        context.pendingPathQueue.poll();
        context.addPassedStation(context.currentStationId);
        context.lastStepAt = System.currentTimeMillis();
        context.lastProgressAt = context.lastStepAt;
        context.lastProgressStationId = context.currentStationId;
        guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false);
        traceEvent(deviceNo, context, "MOVE_INIT", "任务初始化起点站点",
                buildDetails("stationId", context.currentStationId, "recovered", false), false);
        sleep(500);
        return Math.max(getInitializeDelayMs(), getMoveStepDurationMs());
    }
    private boolean executeNextMove(Integer deviceNo, TaskRuntimeContext context) {
    private MoveStepResult executeNextMove(Integer deviceNo, TaskRuntimeContext context) {
        Integer nextStationId = context.pendingPathQueue.peek();
        if (nextStationId == null || context.currentStationId == null) {
            return true;
            return MoveStepResult.continueNow();
        }
        Integer currentDeviceNo = getDeviceNoByStationId(context.currentStationId);
        Integer nextDeviceNo = getDeviceNoByStationId(nextStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
        Integer nextDeviceNo = stateManager.getDeviceNoByStationId(nextStationId);
        if (currentDeviceNo == null || nextDeviceNo == null) {
            context.pendingPathQueue.poll();
            return true;
            return MoveStepResult.continueNow();
        }
        boolean moveSuccess = stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo,
        boolean moveSuccess = moveEngine.stationMoveToNext(context.taskNo, context.currentStationId, currentDeviceNo,
                nextStationId, nextDeviceNo, context.taskNo, context.finalTargetStationId);
        if (moveSuccess) {
            Integer previousStationId = context.currentStationId;
@@ -415,63 +620,174 @@
            context.blockedStationId = null;
            context.status = STATUS_RUNNING;
            context.lastStepAt = System.currentTimeMillis();
            context.lastProgressAt = context.lastStepAt;
            context.lastProgressStationId = context.currentStationId;
            blockManager.clearBlocked(previousStationId);
            blockManager.clearBlocked(nextStationId);
            traceEvent(deviceNo, context, "MOVE_STEP_OK", "任务完成一步站点移动",
                    buildDetails("fromStationId", previousStationId, "toStationId", nextStationId,
                            "remainingPendingPath", context.getPendingStationIds()),
                    false);
            sleep(1000);
            return true;
            return MoveStepResult.continueAfter(getBlockedLoopDelayMs());
        }
        if (!checkTaskNoInArea(context.taskNo) && getFakeAllowCheckBlock()
                && System.currentTimeMillis() - context.lastStepAt > getFakeRunBlockTimeoutMs()) {
            boolean blocked = runBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
        long now = System.currentTimeMillis();
        boolean sameStationNoProgress = context.currentStationId != null
                && context.currentStationId.equals(context.lastProgressStationId);
        long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil);
        long dwellMs = now - context.lastProgressAt;
        if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil)
                && !blockManager.isSpecialStation(context.currentStationId)
                && !blockManager.isBlocked(context.currentStationId)
                && sameStationNoProgress
                && dwellMs > timeoutMs) {
            List<Integer> clearedPendingPath = context.getPendingStationIds();
            News.info("[WCS Debug] fake task清空待执行路径,原因=RUN_BLOCKED,deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}",
                    deviceNo, context.taskNo, context.currentStationId, context.finalTargetStationId,
                    context.blockedStationId, clearedPendingPath);
            context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId,
                    context.finalTargetStationId, context.blockedStationId, "RUN_BLOCKED");
            guardedRunBlockStation(context.taskNo, context.currentStationId, currentDeviceNo, context.taskNo,
                    context.currentStationId);
            if (blocked) {
                context.blockedStationId = context.currentStationId;
                context.status = STATUS_BLOCKED;
                context.pendingPathQueue.clear();
                traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点被标记为堵塞",
                        buildDetails("blockedStationId", context.currentStationId), false);
                return false;
            }
            context.blockedStationId = context.currentStationId;
            context.status = STATUS_BLOCKED;
            traceEvent(deviceNo, context, "RUN_BLOCKED", "任务在当前站点停留超时,被标记为堵塞,待执行路径已清空",
                    buildDetails("blockedStationId", context.currentStationId,
                            "lastProgressStationId", context.lastProgressStationId,
                            "dwellMs", dwellMs,
                            "timeoutMs", timeoutMs,
                            "clearedPendingPath", clearedPendingPath), false);
            return MoveStepResult.continueAfter(getMoveStepDurationMs());
        }
        sleep(500);
        return true;
        context.status = STATUS_WAITING;
        traceEvent(deviceNo, context, "MOVE_STEP_WAIT", "当前站已更新目标但尚未完成落站,保持当前位置等待下一次推进",
                buildDetails("currentStationId", context.currentStationId,
                        "nextStationId", nextStationId,
                        "targetStationId", context.finalTargetStationId,
                        "blockedStationId", context.blockedStationId,
                        "pendingPath", context.getPendingStationIds()), false);
        return MoveStepResult.continueAfter(500L);
    }
    private boolean handleIdleState(Integer deviceNo, TaskRuntimeContext context) {
    private void resumeFromCurrentStation(Integer deviceNo, TaskRuntimeContext context, String mergeMode,
            List<Integer> appendedPath, List<Integer> replacedFuturePath, boolean sourceCleared) {
        if (context.currentStationId != null) {
            Integer currentDeviceNo = stateManager.getDeviceNoByStationId(context.currentStationId);
            if (currentDeviceNo != null) {
                guardedClearRunBlock(context.taskNo, context.currentStationId, currentDeviceNo);
                guardedPublishTaskLocation(context.taskNo, currentDeviceNo, context.currentStationId, true, false);
            }
        }
        context.blockedStationId = null;
        context.status = STATUS_RUNNING;
        context.lastProgressAt = System.currentTimeMillis();
        context.lastProgressStationId = context.currentStationId;
        traceEvent(deviceNo, context, "BLOCK_RESET", "收到新的路径分段,已清除堵塞并重新计时",
                buildDetails("mergeMode", mergeMode, "currentStationId", context.currentStationId,
                        "targetStationId", context.finalTargetStationId, "queueSize", context.pendingPathQueue.size(),
                        "appendedPath", appendedPath, "replacedFuturePath", replacedFuturePath,
                        "sourceCleared", sourceCleared),
                false);
    }
    private boolean shouldAdvanceFromCurrentState(TaskRuntimeContext context) {
        return context != null
                && context.initialized
                && context.currentStationId != null
                && !context.pendingPathQueue.isEmpty()
                && !STATUS_BLOCKED.equals(context.status)
                && !STATUS_CANCELLED.equals(context.status)
                && !STATUS_TIMEOUT.equals(context.status)
                && !STATUS_FINISHED.equals(context.status);
    }
    private IdleStepResult handleIdleState(Integer deviceNo, TaskRuntimeContext context) {
        if (shouldAdvanceFromCurrentState(context)) {
            traceEvent(deviceNo, context, "IDLE_RESUME", "当前站点存在未完成路径,立即从当前站恢复推进",
                    buildDetails("currentStationId", context.currentStationId,
                            "targetStationId", context.finalTargetStationId,
                            "pendingPath", context.getPendingStationIds()), false);
            context.status = STATUS_RUNNING;
            return IdleStepResult.waitNext(1L);
        }
        if (context.currentStationId != null && context.finalTargetStationId != null
                && context.currentStationId.equals(context.finalTargetStationId)) {
            if (!context.arrivalHandled) {
                boolean barcodeGenerated = false;
                if (context.generateBarcode) {
                    Integer targetDeviceNo = getDeviceNoByStationId(context.finalTargetStationId);
                    Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId);
                    if (targetDeviceNo != null) {
                        barcodeGenerated = generateStationBarcode(context.taskNo, context.finalTargetStationId,
                        barcodeGenerated = guardedGenerateStationBarcode(context.taskNo, context.finalTargetStationId,
                                targetDeviceNo);
                    }
                }
                context.arrivalHandled = true;
                context.lastProgressAt = System.currentTimeMillis();
                context.lastProgressStationId = context.currentStationId;
                traceEvent(deviceNo, context, "ARRIVED", "任务到达最终目标站点",
                        buildDetails("stationId", context.currentStationId, "barcodeGenerated", barcodeGenerated),
                        false);
            }
            context.status = STATUS_FINISHED;
            return true;
            Integer targetDeviceNo = stateManager.getDeviceNoByStationId(context.finalTargetStationId);
            if (targetDeviceNo != null) {
                boolean ownerConflict = stateManager.isFinalStationOwnerConflict(targetDeviceNo, context.finalTargetStationId,
                        context.taskNo);
                if (ownerConflict) {
                    logFinalStationOwnershipLost(deviceNo, context);
                    context.status = STATUS_BLOCKED;
                    return IdleStepResult.waitNext(getMoveStepDurationMs());
                }
                boolean stationCleared = stateManager.isStationClearedForTask(targetDeviceNo, context.finalTargetStationId,
                        context.taskNo);
                boolean crnTaken = isCrnTakenByTask(context.taskNo);
                if (stationCleared || crnTaken) {
                    context.status = STATUS_FINISHED;
                    traceEvent(deviceNo, context, "TASK_COMPLETE", "堆垛机已取走货物,任务完成",
                            buildDetails("stationId", context.finalTargetStationId, "stationCleared", stationCleared, "crnTaken", crnTaken), false);
                    return IdleStepResult.finish();
                }
            }
            if (targetDeviceNo != null && !guardedArrivalCompletion(targetDeviceNo, context)) {
                context.status = STATUS_BLOCKED;
                return IdleStepResult.waitNext(getMoveStepDurationMs());
            }
            long dwellMs = System.currentTimeMillis() - context.lastProgressAt;
            long timeoutMs = blockManager.getFakeRunBlockTimeoutMs(redisUtil);
            if (!checkTaskNoInArea(context.taskNo) && blockManager.getFakeAllowCheckBlock(redisUtil)
                    && !blockManager.isSpecialStation(context.currentStationId)
                    && !blockManager.isBlocked(context.currentStationId)
                    && dwellMs > timeoutMs) {
                context.status = STATUS_BLOCKED;
                context.clearPendingPathWithLog(deviceNo, context.taskNo, context.currentStationId,
                        context.finalTargetStationId, context.blockedStationId, "TARGET_RUN_BLOCKED");
                guardedRunBlockStation(context.taskNo, context.currentStationId, targetDeviceNo, context.taskNo,
                        context.currentStationId);
                context.blockedStationId = context.currentStationId;
                traceEvent(deviceNo, context, "RUN_BLOCKED", "任务到达终点站后停留超时,被标记为堵塞,等待堆垛机取货",
                        buildDetails("blockedStationId", context.currentStationId,
                                "lastProgressStationId", context.lastProgressStationId,
                                "dwellMs", dwellMs,
                                "timeoutMs", timeoutMs), false);
                return IdleStepResult.waitNext(getMoveStepDurationMs());
            }
            return IdleStepResult.waitNext(getFinishDelayMs());
        }
        Long lastTime = taskLastUpdateTime.get(context.taskNo);
        if (lastTime != null && System.currentTimeMillis() - lastTime > WAIT_SEGMENT_TIMEOUT_MS) {
            context.status = STATUS_TIMEOUT;
        long waitSegmentTimeoutMs = getWaitSegmentTimeoutMs();
        if (lastTime != null && System.currentTimeMillis() - lastTime > waitSegmentTimeoutMs) {
            traceEvent(deviceNo, context, "WAIT_TIMEOUT", "等待新的路径分段超时",
                    buildDetails("timeoutMs", WAIT_SEGMENT_TIMEOUT_MS, "currentStationId", context.currentStationId,
                    buildDetails("timeoutMs", waitSegmentTimeoutMs, "currentStationId", context.currentStationId,
                            "targetStationId", context.finalTargetStationId),
                    false);
            return true;
            taskLastUpdateTime.put(context.taskNo, System.currentTimeMillis());
        }
        return false;
        return IdleStepResult.waitNext(getIdleLoopDelayMs());
    }
    private List<Integer> normalizePath(List<Integer> path) {
@@ -501,12 +817,40 @@
        }
    }
    private boolean hasTaskReset(Integer taskNo) {
        if (redisUtil == null || taskNo == null) {
            return false;
    private List<Integer> rebuildPendingPathFromCurrent(TaskRuntimeContext context, List<Integer> newPath) {
        List<Integer> rebuilt = new ArrayList<Integer>();
        if (newPath == null || newPath.isEmpty()) {
            return rebuilt;
        }
        Object cancel = redisUtil.get(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo);
        return cancel != null;
        Integer currentStationId = context.currentStationId;
        int startIndex = 0;
        if (currentStationId != null) {
            int currentIndex = newPath.indexOf(currentStationId);
            if (currentIndex < 0) {
                return rebuilt;
            }
            startIndex = currentIndex + 1;
        }
        for (int i = startIndex; i < newPath.size(); i++) {
            rebuilt.add(newPath.get(i));
        }
        return rebuilt;
    }
    private void replacePendingPathQueue(TaskRuntimeContext context, List<Integer> futurePath) {
        context.pendingPathQueue.clear();
        if (futurePath == null) {
            return;
        }
        for (Integer stationId : futurePath) {
            context.pendingPathQueue.offer(stationId);
        }
    }
    private boolean hasTaskReset(Integer taskNo) {
        // 仿真系统不响应外部取消信号(如堵塞重路由触发的 signalSegmentReset),
        // 避免任务在站点行走过程中被意外清除
        return false;
    }
    private Integer getLastInQueue(LinkedBlockingQueue<Integer> queue) {
@@ -548,382 +892,719 @@
        if (currentStationId == null || targetStationId == null) {
            return;
        }
        Integer currentDeviceNo = getDeviceNoByStationId(currentStationId);
        Integer currentDeviceNo = stateManager.getDeviceNoByStationId(currentStationId);
        if (currentDeviceNo == null) {
            return;
        }
        lockStations(currentStationId);
        moveEngine.lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentDeviceNo);
            if (statusList == null) {
                return;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            ZyStationStatusEntity currentStatus = stateManager.findStationStatus(currentDeviceNo, currentStationId);
            if (currentStatus == null) {
                return;
            }
            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
            if (hasOwnerConflict(currentStatus, taskNo)) {
                logOwnerConflict("syncCurrentStationTarget", currentStationId, currentStatus, taskNo, targetStationId, false,
                        "owner_conflict");
                return;
            }
            updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null);
            stateManager.updateStationDataInternal(currentStationId, currentDeviceNo, taskNo, targetStationId, null, null, null);
        } finally {
            unlockStations(currentStationId);
            moveEngine.unlockStations(currentStationId);
        }
    }
    @SuppressWarnings("unchecked")
    private boolean getFakeAllowCheckBlock() {
        boolean fakeAllowCheckBlock = true;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<String, String> systemConfigMap = (Map<String, String>) systemConfigMapObj;
            String value = systemConfigMap.get("fakeAllowCheckBlock");
            if (value != null && !"Y".equals(value)) {
                fakeAllowCheckBlock = false;
            }
        }
        return fakeAllowCheckBlock;
    private boolean hasOwnerConflict(ZyStationStatusEntity currentStatus, Integer incomingTaskNo) {
        return currentStatus != null
                && currentStatus.isLoading()
                && currentStatus.getTaskNo() != null
                && currentStatus.getTaskNo() > 0
                && (incomingTaskNo == null || !currentStatus.getTaskNo().equals(incomingTaskNo));
    }
    private long getFakeRunBlockTimeoutMs() {
        long timeoutMs = DEFAULT_FAKE_RUN_BLOCK_TIMEOUT_MS;
        Object systemConfigMapObj = redisUtil == null ? null : redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
        if (systemConfigMapObj instanceof Map) {
            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
            Object value = systemConfigMap.get("fakeRunBlockTimeoutMs");
            if (value != null) {
                try {
                    long parsed = Long.parseLong(String.valueOf(value).trim());
                    if (parsed > 0) {
                        timeoutMs = parsed;
                    }
                } catch (Exception ignore) {
    private void logOwnerConflict(String operation, Integer stationId, ZyStationStatusEntity currentStatus,
            Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation, String reason) {
        News.info("[WCS Debug] fake station owner冲突,operation={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},incomingTaskNo={},incomingTargetStaNo={},finalStation={},reason={}",
                operation,
                stationId,
                currentStatus == null ? null : currentStatus.getTaskNo(),
                currentStatus == null ? null : currentStatus.isLoading(),
                currentStatus == null ? null : currentStatus.getTargetStaNo(),
                incomingTaskNo,
                incomingTargetStationId,
                finalStation,
                reason);
    }
    private boolean ensureStationWritable(String operation, Integer deviceNo, Integer stationId,
            Integer incomingTaskNo, Integer incomingTargetStationId, boolean finalStation) {
        if (deviceNo == null || stationId == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId);
        if (currentStatus == null) {
            return false;
        }
        if (!hasOwnerConflict(currentStatus, incomingTaskNo)) {
            return true;
        }
        logOwnerConflict(operation, stationId, currentStatus, incomingTaskNo, incomingTargetStationId, finalStation,
                finalStation ? "final_station_owner_conflict" : "owner_conflict");
        return false;
    }
    private boolean ensureStationClearable(String operation, Integer deviceNo, Integer stationId, Integer expectedTaskNo,
            boolean finalStation) {
        if (deviceNo == null || stationId == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = stateManager.findStationStatus(deviceNo, stationId);
        if (currentStatus == null) {
            return false;
        }
        if (!hasOwnerConflict(currentStatus, expectedTaskNo)) {
            return true;
        }
        if (isLegalClearStation(stationId)) {
            return true;
        }
        logOwnerConflict(operation, stationId, currentStatus, expectedTaskNo, currentStatus.getTargetStaNo(), finalStation,
                "illegal_clear_attempt");
        return false;
    }
    private boolean isLegalClearStation(Integer stationId) {
        if (stationId == null) {
            return false;
        }
        Set<Integer> stationIds = legalClearStationIds;
        if (stationIds == null || stationIds.isEmpty()) {
            refreshLegalClearStationIds();
            stationIds = legalClearStationIds;
        }
        return stationIds != null && stationIds.contains(stationId);
    }
    private boolean isBarcodeStation(Integer stationId) {
        if (stationId == null) {
            return false;
        }
        Set<Integer> stationIds = barcodeStationIds;
        if (stationIds == null || stationIds.isEmpty()) {
            refreshBarcodeStationIds();
            stationIds = barcodeStationIds;
        }
        return stationIds != null && stationIds.contains(stationId);
    }
    private void refreshLegalClearStationIds() {
        try {
            Set<Integer> stationIds = new HashSet<Integer>();
            com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class);
            List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list();
            for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) {
                if (basCrnp == null) {
                    continue;
                }
                collectStationIds(stationIds, basCrnp.getInStationList$());
                collectStationIds(stationIds, basCrnp.getOutStationList$());
            }
            com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class);
            List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list();
            for (com.zy.asrs.entity.BasDevp basDevp : basDevps) {
                if (basDevp == null) {
                    continue;
                }
                collectStationIds(stationIds, basDevp.getBarcodeStationList$());
                collectStationIds(stationIds, basDevp.getInStationList$());
                collectStationIds(stationIds, basDevp.getOutStationList$());
            }
            legalClearStationIds = stationIds;
        } catch (Exception e) {
            News.info("[WCS Debug] fake 合法清站白名单刷新失败,异常类型={},异常信息={}",
                    e.getClass().getSimpleName(), e.getMessage());
        }
    }
    private void refreshBarcodeStationIds() {
        try {
            Set<Integer> stationIds = new HashSet<Integer>();
            com.zy.asrs.service.BasDevpService basDevpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasDevpService.class);
            List<com.zy.asrs.entity.BasDevp> basDevps = basDevpService.list();
            for (com.zy.asrs.entity.BasDevp basDevp : basDevps) {
                if (basDevp == null) {
                    continue;
                }
                collectStationIds(stationIds, basDevp.getBarcodeStationList$());
            }
            barcodeStationIds = stationIds;
        } catch (Exception e) {
            News.info("[WCS Debug] fake 条码站白名单刷新失败,异常类型={},异常信息={}",
                    e.getClass().getSimpleName(), e.getMessage());
        }
    }
    private void collectStationIds(Set<Integer> stationIds, List<com.zy.core.model.StationObjModel> stations) {
        if (stationIds == null || stations == null || stations.isEmpty()) {
            return;
        }
        for (com.zy.core.model.StationObjModel station : stations) {
            if (station != null && station.getStationId() != null) {
                stationIds.add(station.getStationId());
            }
        }
        return timeoutMs;
    }
    private boolean isFinalStation(Integer taskNo, Integer stationId) {
        TaskRuntimeContext context = taskNo == null ? null : taskContexts.get(taskNo);
        return context != null && stationId != null && stationId.equals(context.finalTargetStationId);
    }
    private void logFinalStationOwnershipLost(Integer deviceNo, TaskRuntimeContext context) {
        Integer stationId = context == null ? null : context.finalTargetStationId;
        Integer targetDeviceNo = stateManager.getDeviceNoByStationId(stationId);
        ZyStationStatusEntity currentStatus = targetDeviceNo == null ? null : stateManager.snapshotStation(targetDeviceNo, stationId);
        News.info("[WCS Debug] fake 最终站所有权丢失,taskNo={},stationId={},currentTaskNo={},currentLoading={},currentTargetStaNo={},reason={}",
                context == null ? null : context.taskNo,
                stationId,
                currentStatus == null ? null : currentStatus.getTaskNo(),
                currentStatus == null ? null : currentStatus.isLoading(),
                currentStatus == null ? null : currentStatus.getTargetStaNo(),
                "final_station_owner_conflict");
        traceEvent(deviceNo, context, "FINAL_STATION_OWNER_CONFLICT", "最终站仍有物但所有者已被其他任务覆盖",
                buildDetails("stationId", stationId,
                        "currentTaskNo", currentStatus == null ? null : currentStatus.getTaskNo(),
                        "currentLoading", currentStatus == null ? null : currentStatus.isLoading(),
                        "currentTargetStaNo", currentStatus == null ? null : currentStatus.getTargetStaNo()), false);
    }
    private boolean guardedClearStationForDispatch(Integer deviceNo, Integer stationId, Integer expectedTaskNo, String reason) {
        if (!ensureStationClearable("clearStationForDispatch", deviceNo, stationId, expectedTaskNo,
                isFinalStation(expectedTaskNo, stationId))) {
            return false;
        }
        stateManager.clearStationForDispatch(deviceNo, stationId, reason);
        return true;
    }
    private boolean guardedResetStation(Integer deviceNo, Integer stationId, Integer expectedTaskNo) {
        if (!ensureStationClearable("resetStation", deviceNo, stationId, expectedTaskNo,
                isFinalStation(expectedTaskNo, stationId))) {
            return false;
        }
        stateManager.resetStation(deviceNo, stationId);
        return true;
    }
    private boolean guardedUpdateStationBarcode(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) {
        if (!ensureStationWritable("updateStationBarcode", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.updateStationBarcode(deviceNo, stationId, barcode);
        return true;
    }
    private boolean guardedGenerateFakeOutStationData(Integer deviceNo, Integer stationId, Integer taskNo) {
        if (!ensureStationWritable("generateFakeOutStationData", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            News.error("fake 出库站点有物写入被拒绝。deviceNo={},stationId={},taskNo={},reason=station-not-writable",
                    deviceNo, stationId, taskNo);
            return false;
        }
        stateManager.generateFakeOutStationData(deviceNo, stationId);
        News.info("fake 出库站点有物写入成功。deviceNo={},stationId={},taskNo={},loading=true",
                deviceNo, stationId, taskNo);
        return true;
    }
    private boolean guardedHandoffBarcodeStation(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        if (!ensureStationWritable("handoffBarcodeStation", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.handoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId);
        return true;
    }
    private boolean guardedRunBlockStation(Integer taskNo, Integer stationId, Integer deviceNo, Integer ownerTaskNo,
            Integer targetStationId) {
        if (!ensureStationWritable("runBlockStation", deviceNo, stationId, ownerTaskNo, targetStationId,
                isFinalStation(ownerTaskNo, stationId))) {
            return false;
        }
        blockManager.runBlockStation(taskNo, stationId, deviceNo, ownerTaskNo, targetStationId);
        return true;
    }
    private boolean guardedClearRunBlock(Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!ensureStationWritable("clearRunBlock", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        blockManager.clearRunBlock(stationId, deviceNo);
        return true;
    }
    private boolean guardedPublishTaskLocation(Integer taskNo, Integer deviceNo, Integer stationId, boolean loading, boolean runBlock) {
        if (loading && !ensureStationWritable("publishTaskLocation", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.publishTaskLocation(taskNo, deviceNo, stationId, loading, runBlock);
        return true;
    }
    private boolean guardedGenerateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        if (!ensureStationWritable("generateStationData", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        stateManager.generateStationData(deviceNo, taskNo, stationId, targetStationId);
        return true;
    }
    private boolean guardedSyncCurrentStationTarget(Integer taskNo, Integer currentStationId, Integer targetStationId) {
        syncCurrentStationTarget(taskNo, currentStationId, targetStationId);
        return true;
    }
    private boolean guardedClearTaskLocationIfMatches(Integer taskNo, Integer deviceNo, Integer stationId) {
        stateManager.clearTaskLocationIfMatches(taskNo, deviceNo, stationId);
        return true;
    }
    private boolean guardedPublishCurrentLocation(Integer taskNo, Integer deviceNo, Integer stationId) {
        return guardedPublishTaskLocation(taskNo, deviceNo, stationId, true, false);
    }
    private boolean guardedGenerateStationBarcode(Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!ensureStationWritable("generateStationBarcode", deviceNo, stationId, taskNo, null,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        return stateManager.generateStationBarcode(taskNo, stationId, deviceNo);
    }
    private boolean guardedUpdateStationData(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStationId,
            Boolean loading, String barcode, Boolean runBlock) {
        if ((taskNo != null || Boolean.TRUE.equals(loading))
                && !ensureStationWritable("updateStationData", deviceNo, stationId, taskNo, targetStationId,
                isFinalStation(taskNo, stationId))) {
            return false;
        }
        return stateManager.updateStationDataInternal(stationId, deviceNo, taskNo, targetStationId, loading, barcode, runBlock);
    }
    private boolean isFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) {
        return isFinalStation(taskNo, stationId) && stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedResetOrClearBlockedStation(Integer taskNo, Integer stationId, Integer deviceNo) {
        return ensureStationClearable("blockedStationClear", deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStationOccupied(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationWritable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "final_station_owner_conflict");
            return false;
        }
        return true;
    }
    private boolean guardedFinalStationClearable(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        if (!guardedFinalStationOwnership(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "illegal_clear_attempt");
            return false;
        }
        return true;
    }
    private boolean shouldTreatAsFinalStationConflict(Integer taskNo, Integer stationId, Integer deviceNo) {
        return isFinalStation(taskNo, stationId) && stateManager.isFinalStationOwnerConflict(deviceNo, stationId, taskNo);
    }
    private boolean guardedFinalStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo, targetStationId, true,
                    "final_station_owner_conflict");
            return false;
        }
        return true;
    }
    private boolean guardedFinalStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) {
        if (shouldTreatAsFinalStationConflict(taskNo, stationId, deviceNo)) {
            ZyStationStatusEntity currentStatus = stateManager.snapshotStation(deviceNo, stationId);
            logOwnerConflict(operation, stationId, currentStatus, taskNo,
                    currentStatus == null ? null : currentStatus.getTargetStaNo(), true,
                    "illegal_clear_attempt");
            return false;
        }
        return true;
    }
    private boolean guardedStationWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        if (!guardedFinalStationMutation(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        return ensureStationWritable(operation, deviceNo, stationId, taskNo, targetStationId, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStationClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo) {
        if (!guardedFinalStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        return ensureStationClearable(operation, deviceNo, stationId, taskNo, isFinalStation(taskNo, stationId));
    }
    private boolean guardedStateMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStateClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalStationCompletion(Integer deviceNo, TaskRuntimeContext context) {
        if (context == null || context.finalTargetStationId == null || context.taskNo == null) {
            return false;
        }
        return !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedCurrentStationOwnership(Integer taskNo, Integer currentStationId, Integer currentDeviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(currentDeviceNo, currentStationId, taskNo);
    }
    private boolean guardedTargetStationOwnership(Integer taskNo, Integer stationId, Integer deviceNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedCommandWrite(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedCommandClear(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedRunBlockMutation(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId) {
        return guardedStationWrite("runBlockStation", taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedClearMutation(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedArrivalOwnership(Integer deviceNo, TaskRuntimeContext context) {
        return context != null && context.finalTargetStationId != null && context.taskNo != null
                && !stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedArrivalStationWritable(Integer deviceNo, TaskRuntimeContext context, String operation) {
        if (!guardedArrivalOwnership(deviceNo, context)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedArrivalStationClearable(Integer deviceNo, TaskRuntimeContext context, String operation) {
        if (!guardedArrivalOwnership(deviceNo, context)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedStationMutation(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            Runnable mutation, boolean clearOperation) {
        boolean allowed = clearOperation
                ? guardedStationClear(operation, taskNo, stationId, deviceNo)
                : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
        if (!allowed) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalArrivalState(Integer deviceNo, TaskRuntimeContext context) {
        if (context == null || context.finalTargetStationId == null || context.taskNo == null) {
            return false;
        }
        if (stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo)) {
            logFinalStationOwnershipLost(deviceNo, context);
            return false;
        }
        return true;
    }
    private boolean guardedArrivalCompletion(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedStationOwnership(String operation, Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId,
            boolean clearOperation) {
        return clearOperation
                ? guardedStationClear(operation, taskNo, stationId, deviceNo)
                : guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStationMutationIfAllowed(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Integer targetStationId, boolean clearOperation, Runnable mutation) {
        if (!guardedStationOwnership(operation, taskNo, stationId, deviceNo, targetStationId, clearOperation)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalStationState(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedMutateFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearFinalStation(String operation, Integer taskNo, Integer stationId, Integer deviceNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalState(Integer deviceNo, TaskRuntimeContext context) {
        if (!guardedFinalArrivalState(deviceNo, context)) {
            return false;
        }
        return true;
    }
    private boolean guardedCurrentStationWrite(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, Integer targetStationId,
            String operation) {
        return guardedStationWrite(operation, taskNo, currentStationId, currentDeviceNo, targetStationId);
    }
    private boolean guardedCurrentStationClear(Integer taskNo, Integer currentStationId, Integer currentDeviceNo, String operation) {
        return guardedStationClear(operation, taskNo, currentStationId, currentDeviceNo);
    }
    private boolean guardedFinalStationWrite(Integer taskNo, Integer stationId, Integer deviceNo, Integer targetStationId, String operation) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedFinalStationClear(Integer taskNo, Integer stationId, Integer deviceNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedStationBarcodeWrite(Integer deviceNo, Integer stationId, Integer taskNo, String barcode) {
        return guardedUpdateStationBarcode(deviceNo, stationId, taskNo, barcode);
    }
    private boolean guardedStationOccupancyWrite(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            Runnable mutation, String operation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStationOccupancyClear(Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation, String operation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedStationWriteForTask(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            String operation) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStationClearForTask(Integer deviceNo, Integer stationId, Integer taskNo, String operation) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedMutateStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId,
            String operation, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearStationIfOwned(Integer deviceNo, Integer stationId, Integer taskNo, String operation,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalVisibility(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedFinalStationNotOverwritten(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedMutateOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearOccupiedStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedFinalStationStillOwned(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedStateWrite(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedStateClear(String operation, Integer deviceNo, Integer stationId, Integer taskNo) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private boolean guardedArrivalOwnerCheck(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedArrivalConflict(Integer deviceNo, TaskRuntimeContext context) {
        return stateManager.isFinalStationOwnerConflict(deviceNo, context.finalTargetStationId, context.taskNo);
    }
    private boolean guardedCurrentOwnerCheck(Integer deviceNo, Integer stationId, Integer taskNo) {
        return !stateManager.isOccupiedByOtherLoadingTask(deviceNo, stationId, taskNo);
    }
    private boolean guardedMutateStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Integer targetStationId, Runnable mutation) {
        if (!guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedClearStationState(String operation, Integer deviceNo, Integer stationId, Integer taskNo,
            Runnable mutation) {
        if (!guardedStationClear(operation, taskNo, stationId, deviceNo)) {
            return false;
        }
        mutation.run();
        return true;
    }
    private boolean guardedArrivalCompletionState(Integer deviceNo, TaskRuntimeContext context) {
        return guardedFinalArrivalState(deviceNo, context);
    }
    private boolean guardedWriteToStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo, Integer targetStationId) {
        return guardedStationWrite(operation, taskNo, stationId, deviceNo, targetStationId);
    }
    private boolean guardedClearFromStation(String operation, Integer deviceNo, Integer stationId, Integer taskNo) {
        return guardedStationClear(operation, taskNo, stationId, deviceNo);
    }
    private void handleCommand(Integer deviceNo, StationCommand command) {
        News.info("[WCS Debug] 站点仿真模拟(V3)已启动,命令数据={}", JSON.toJSONString(command));
        News.info("[WCS Debug] fake 非MOVE命令进入device串行执行,deviceNo={},stationId={},targetStaNo={},commandType={},命令数据={}",
                deviceNo, command.getStationId(), command.getTargetStaNo(), command.getCommandType(), JSON.toJSONString(command));
        Integer taskNo = command.getTaskNo();
        Integer stationId = command.getStationId();
        Integer targetStationId = command.getTargetStaNo();
        if (command.getCommandType() == StationCommandType.RESET) {
            resetStation(deviceNo, stationId);
            boolean reset = guardedResetStation(deviceNo, stationId, taskNo);
            News.info("[WCS Debug] fake RESET已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, reset);
            return;
        }
        if (command.getCommandType() == StationCommandType.WRITE_INFO) {
            if (command.getBarcode() != null) {
                updateStationBarcode(deviceNo, stationId, command.getBarcode());
                boolean updated = guardedUpdateStationBarcode(deviceNo, stationId, taskNo, command.getBarcode());
                News.info("[WCS Debug] fake WRITE_INFO条码写入已通过统一站点锁执行,deviceNo={},stationId={},barcode={},accepted={}",
                        deviceNo, stationId, command.getBarcode(), updated);
                return;
            }
            if (taskNo == 9998 && targetStationId == 0) {
                generateFakeOutStationData(deviceNo, stationId);
                boolean generated = guardedGenerateFakeOutStationData(deviceNo, stationId, taskNo);
                News.info("[WCS Debug] fake WRITE_INFO有物写入已通过统一站点锁执行,deviceNo={},stationId={},accepted={}", deviceNo, stationId, generated);
                return;
            }
        }
        if (taskNo != null && taskNo > 0 && taskNo != 9999 && taskNo != 9998 && stationId != null
                && stationId.equals(targetStationId)) {
            generateStationData(deviceNo, taskNo, stationId, targetStationId);
        }
    }
    private void generateFakeOutStationData(Integer deviceNo, Integer stationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setLoading(true);
        }
    }
    private void generateStationData(Integer deviceNo, Integer taskNo, Integer stationId, Integer targetStationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setTaskNo(taskNo);
            status.setTargetStaNo(targetStationId);
        }
    }
    private void resetStation(Integer deviceNo, Integer stationId) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setTaskNo(0);
            status.setLoading(false);
            status.setBarcode("");
        }
    }
    private void updateStationBarcode(Integer deviceNo, Integer stationId, String barcode) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return;
        }
        ZyStationStatusEntity status = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (status == null) {
            return;
        }
        synchronized (status) {
            status.setBarcode(barcode);
        }
    }
    private Integer getDeviceNoByStationId(Integer stationId) {
        for (Map.Entry<Integer, List<ZyStationStatusEntity>> entry : deviceStatusMap.entrySet()) {
            List<ZyStationStatusEntity> list = entry.getValue();
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                if (entity.getStationId() != null && entity.getStationId().equals(stationId)) {
                    return entry.getKey();
                }
            }
        }
        return null;
    }
    private Integer findCurrentStationIdByTask(Integer taskNo) {
        for (List<ZyStationStatusEntity> list : deviceStatusMap.values()) {
            if (list == null) {
                continue;
            }
            for (ZyStationStatusEntity entity : list) {
                if (entity.getTaskNo() != null && entity.getTaskNo().equals(taskNo) && entity.isLoading()) {
                    return entity.getStationId();
                }
            }
        }
        return null;
    }
    private void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    private ReentrantLock getStationLock(Integer stationId) {
        return stationLocks.computeIfAbsent(stationId, key -> new ReentrantLock());
    }
    private void lockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (Integer stationId : sorted) {
            getStationLock(stationId).lock();
        }
    }
    private void unlockStations(Integer... stationIds) {
        Integer[] sorted = Arrays.copyOf(stationIds, stationIds.length);
        Arrays.sort(sorted);
        for (int i = sorted.length - 1; i >= 0; i--) {
            getStationLock(sorted[i]).unlock();
        }
    }
    private boolean updateStationDataInternal(Integer stationId, Integer deviceNo, Integer taskNo, Integer targetStaNo,
            Boolean isLoading, String barcode, Boolean runBlock) {
        List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
        if (statusList == null) {
            return false;
        }
        ZyStationStatusEntity currentStatus = statusList.stream()
                .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
        if (currentStatus == null) {
            return false;
        }
        if (taskNo != null) {
            currentStatus.setTaskNo(taskNo);
        }
        if (targetStaNo != null) {
            currentStatus.setTargetStaNo(targetStaNo);
        }
        if (isLoading != null) {
            currentStatus.setLoading(isLoading);
        }
        if (barcode != null) {
            currentStatus.setBarcode(barcode);
        }
        if (runBlock != null) {
            currentStatus.setRunBlock(runBlock);
        }
        return true;
    }
    public boolean initStationMove(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer taskNo, Integer targetStationId, Boolean isLoading, String barcode) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            if (currentStatus.getTaskNo() != null && currentStatus.getTaskNo() > 0
                    && !currentStatus.getTaskNo().equals(taskNo) && currentStatus.isLoading()) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, targetStationId,
                    isLoading, barcode, false);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean stationMoveToNext(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer nextStationId, Integer nextStationDeviceNo, Integer taskNo, Integer targetStaNo) {
        lockStations(currentStationId, nextStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            List<ZyStationStatusEntity> nextStatusList = deviceStatusMap.get(nextStationDeviceNo);
            if (statusList == null || nextStatusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            ZyStationStatusEntity nextStatus = nextStatusList.stream()
                    .filter(item -> item.getStationId().equals(nextStationId)).findFirst().orElse(null);
            if (currentStatus == null || nextStatus == null) {
                return false;
            }
            if (nextStatus.getTaskNo() != null && nextStatus.getTaskNo() > 0 || nextStatus.isLoading()) {
                return false;
            }
            boolean result = updateStationDataInternal(nextStationId, nextStationDeviceNo, taskNo, targetStaNo, true,
                    null, false);
            if (!result) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, 0, 0, false, "", false);
        } finally {
            unlockStations(currentStationId, nextStationId);
        }
    }
    public boolean generateStationBarcode(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            Random random = new Random();
            String barcodeTime = String.valueOf(System.currentTimeMillis());
            String barcode = String.valueOf(random.nextInt(10)) + String.valueOf(random.nextInt(10))
                    + barcodeTime.substring(7);
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, null, null, null, barcode, null);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean clearStation(Integer deviceNo, Integer lockTaskNo, Integer currentStationId) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            return updateStationDataInternal(currentStationId, deviceNo, 0, 0, false, "", false);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public boolean runBlockStation(Integer lockTaskNo, Integer currentStationId, Integer currentStationDeviceNo,
            Integer taskNo, Integer blockStationId) {
        lockStations(currentStationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(currentStationDeviceNo);
            if (statusList == null) {
                return false;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(currentStationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return false;
            }
            return updateStationDataInternal(currentStationId, currentStationDeviceNo, taskNo, blockStationId, true, "",
                    true);
        } finally {
            unlockStations(currentStationId);
        }
    }
    public void clearRunBlock(Integer stationId, Integer deviceNo) {
        lockStations(stationId);
        try {
            List<ZyStationStatusEntity> statusList = deviceStatusMap.get(deviceNo);
            if (statusList == null) {
                return;
            }
            ZyStationStatusEntity currentStatus = statusList.stream()
                    .filter(item -> item.getStationId().equals(stationId)).findFirst().orElse(null);
            if (currentStatus == null) {
                return;
            }
            if (currentStatus.isRunBlock()) {
                currentStatus.setRunBlock(false);
                News.info("[WCS Debug] 站点{}堵塞标记已清除", stationId);
            }
        } finally {
            unlockStations(stationId);
            boolean handedOff = guardedHandoffBarcodeStation(deviceNo, taskNo, stationId, targetStationId);
            News.info("[WCS Debug] fake 条码站任务交接完成,deviceNo={},stationId={},taskNo={},targetStationId={},动作=barcodeTaskHandoff,accepted={}",
                    deviceNo, stationId, taskNo, targetStationId, handedOff);
        }
    }
@@ -962,36 +1643,138 @@
    private void traceEvent(Integer deviceNo, TaskRuntimeContext context, String eventType, String message,
            Map<String, Object> details, boolean terminal) {
        if (context != null && context.deviceNo != null && deviceNo != null && !context.deviceNo.equals(deviceNo)) {
            deviceNo = context.deviceNo;
        }
        if (context == null || context.taskNo == null) {
            return;
        }
        try {
            FakeTaskTraceRegistry registry = SpringUtils.getBean(FakeTaskTraceRegistry.class);
            if (registry == null) {
                return;
            }
            registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : getThreadImpl(deviceNo),
                    context.status, context.startStationId, context.currentStationId, context.finalTargetStationId,
                    context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(),
                    context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, details,
                    terminal);
        } catch (Exception ignore) {
        Map<String, Object> safeDetails = details == null ? new LinkedHashMap<String, Object>() : new LinkedHashMap<String, Object>(details);
        if (!safeDetails.containsKey("segmentNo") && context.segmentNo != null) {
            safeDetails.put("segmentNo", context.segmentNo);
        }
    }
    private String getThreadImpl(Integer deviceNo) {
        DeviceConfig deviceConfig = deviceConfigMap.get(deviceNo);
        return deviceConfig == null ? "" : deviceConfig.getThreadImpl();
        if (!safeDetails.containsKey("segmentCount") && context.segmentCount != null) {
            safeDetails.put("segmentCount", context.segmentCount);
        }
        try {
            FakeTaskTraceRegistry registry = com.core.common.SpringUtils.getBean(FakeTaskTraceRegistry.class);
            if (registry != null) {
                registry.record(context.taskNo, context.threadImpl != null ? context.threadImpl : stateManager.getThreadImpl(deviceNo),
                        context.status, context.startStationId, context.currentStationId, context.finalTargetStationId,
                        context.blockedStationId, context.getStitchedPathStationIds(), context.getPassedStationIds(),
                        context.getPendingStationIds(), context.getLatestAppendedPath(), eventType, message, safeDetails,
                        terminal);
            }
        } catch (Exception e) {
            News.info("[WCS Debug] fake trace记录失败,taskNo={},eventType={},异常类型={},异常信息={}",
                    context.taskNo, eventType, e.getClass().getSimpleName(), e.getMessage());
        }
        News.info("[WCS Debug] fake task event,taskNo={},eventType={},status={},currentStationId={},targetStationId={},pending={},details={}",
                context.taskNo, eventType, context.status, context.currentStationId, context.finalTargetStationId,
                context.getPendingStationIds(), JSON.toJSONString(safeDetails));
    }
    private boolean isTerminalStatus(String status) {
        return STATUS_BLOCKED.equals(status) || STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status)
        return STATUS_CANCELLED.equals(status) || STATUS_TIMEOUT.equals(status)
                || STATUS_FINISHED.equals(status);
    }
    private boolean isCurrentLoopGeneration(Integer taskNo, int loopGeneration) {
        AtomicInteger generation = taskLoopGenerations.get(taskNo);
        return generation != null && generation.get() == loopGeneration;
    }
    private Object getTaskLifecycleLock(Integer taskNo) {
        return taskLifecycleLocks.computeIfAbsent(taskNo, key -> new Object());
    }
    /**
     * 检查堆垛机是否已取走指定任务的货物
     * 通过遍历所有堆垛机设备,检查是否有堆垛机正在执行该任务且已 loaded
     */
    private boolean isCrnTakenByTask(Integer taskNo) {
        if (taskNo == null || taskNo <= 0) {
            return false;
        }
        try {
            com.zy.asrs.service.BasCrnpService basCrnpService = com.core.common.SpringUtils.getBean(com.zy.asrs.service.BasCrnpService.class);
            List<com.zy.asrs.entity.BasCrnp> basCrnps = basCrnpService.list();
            for (com.zy.asrs.entity.BasCrnp basCrnp : basCrnps) {
                com.zy.core.thread.CrnThread ct = (com.zy.core.thread.CrnThread) com.zy.core.cache.SlaveConnection.get(
                        com.zy.core.enums.SlaveType.Crn, basCrnp.getCrnNo());
                if (ct == null) {
                    continue;
                }
                com.zy.core.model.protocol.CrnProtocol protocol = ct.getStatus();
                if (protocol != null && taskNo.equals(protocol.getTaskNo())
                        && protocol.getLoaded() == 1) {
                    return true;
                }
            }
        } catch (Exception ignore) {
        }
        return false;
    }
    private static class MoveStepResult {
        private final boolean shouldContinue;
        private final long nextDelayMs;
        private MoveStepResult(boolean shouldContinue, long nextDelayMs) {
            this.shouldContinue = shouldContinue;
            this.nextDelayMs = nextDelayMs;
        }
        private static MoveStepResult continueNow() {
            return new MoveStepResult(true, 0L);
        }
        private static MoveStepResult continueAfter(long nextDelayMs) {
            return new MoveStepResult(true, nextDelayMs);
        }
        private boolean shouldContinue() {
            return shouldContinue;
        }
        private long getNextDelayMs() {
            return nextDelayMs;
        }
    }
    private static class IdleStepResult {
        private final boolean finished;
        private final long nextDelayMs;
        private IdleStepResult(boolean finished, long nextDelayMs) {
            this.finished = finished;
            this.nextDelayMs = nextDelayMs;
        }
        private static IdleStepResult finish() {
            return new IdleStepResult(true, 0L);
        }
        private static IdleStepResult waitNext(long nextDelayMs) {
            return new IdleStepResult(false, nextDelayMs);
        }
        private boolean isFinished() {
            return finished;
        }
        private long getNextDelayMs() {
            return nextDelayMs;
        }
    }
    private static class TaskRuntimeContext {
        private final Integer taskNo;
        private final Integer deviceNo;
        private final String threadImpl;
        private final LinkedBlockingQueue<Integer> pendingPathQueue = new LinkedBlockingQueue<Integer>();
        private final List<Integer> stitchedPathStationIds = new ArrayList<Integer>();
@@ -1005,12 +1788,18 @@
        private boolean generateBarcode;
        private boolean initialized;
        private boolean arrivalHandled;
        private Integer segmentNo;
        private Integer segmentCount;
        private long lastStepAt = System.currentTimeMillis();
        private long lastCommandAt = System.currentTimeMillis();
        private long lastProgressAt = System.currentTimeMillis();
        private Integer lastProgressStationId;
        private int loopGeneration;
        private String status = STATUS_WAITING;
        private TaskRuntimeContext(Integer taskNo, String threadImpl) {
        private TaskRuntimeContext(Integer taskNo, Integer deviceNo, String threadImpl) {
            this.taskNo = taskNo;
            this.deviceNo = deviceNo;
            this.threadImpl = threadImpl;
        }
@@ -1037,6 +1826,30 @@
            }
        }
        private void replaceLatestPath(List<Integer> path) {
            latestAppendedPath.clear();
            if (path == null) {
                return;
            }
            for (Integer stationId : path) {
                if (stationId != null) {
                    latestAppendedPath.add(stationId);
                }
            }
        }
        private void clearPendingPath() {
            pendingPathQueue.clear();
            latestAppendedPath.clear();
        }
        private void clearPendingPathWithLog(Integer deviceNo, Integer taskNo, Integer currentStationId,
                Integer targetStationId, Integer blockedStationId, String reason) {
            News.info("[WCS Debug] fake task清空待执行路径,原因={},deviceNo={},taskNo={},currentStationId={},targetStationId={},blockedStationId={},pendingBeforeClear={}",
                    reason, deviceNo, taskNo, currentStationId, targetStationId, blockedStationId, getPendingStationIds());
            clearPendingPath();
        }
        private void addPassedStation(Integer stationId) {
            if (stationId == null) {
                return;