Загрузка данных


 // ============================================================
    // Синхронизация планов с физическим состоянием линии
    // ============================================================
    FOR i := 1 TO 17 DO
        IF GVL.vanBusy[i] THEN
            GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], 
                                          MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                              GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
            GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], 
                                           MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                               GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
        ELSE
            GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.sysTime);
            GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.sysTime);
        END_IF
    END_FOR

    // ============================================================
    // Однократная инициализация (firstScan – VAR_STAT в FB)
    // ============================================================
    IF gvl.firstScan THEN
        FOR i := 1 TO 17 DO
            GVL.planVatFreeTime[i] := GVL.sysTime;
            GVL.planVatBusyUntil[i] := GVL.sysTime;
        END_FOR;
        GVL.loadPosBusy := FALSE;
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentPos = 0 THEN
                GVL.loadPosBusy := TRUE;
                EXIT;
            END_IF
        END_FOR

        FOR a := 1 TO 4 DO
            IF (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                GVL.podveskas[a].bufferEntryTime := GVL.sysTime;
            END_IF
        END_FOR
        RebuildBufferAndLoadStatus();
        GVL.aoJobSource := -1;
        GVL.aoJobDest   := -1;
        gvl.firstScan := FALSE;
    END_IF

    // ============================================================
    // Обработка кнопки Reset
    // ============================================================
    IF GVL.Reset THEN
        GVL.Reset := FALSE;
        GVL.resetActive := TRUE;
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentStep >= 0 THEN
                GVL.podveskas[a].currentStep := -1;
                GVL.podveskas[a].xLoaded := FALSE;
                GVL.podveskas[a].xUnloaded := FALSE;
            END_IF
        END_FOR
        RebuildBufferAndLoadStatus();
    END_IF

    IF GVL.resetActive THEN
        allEvacuated := TRUE;
        FOR a := 1 TO 4 DO
            IF NOT ((GVL.podveskas[a].currentPos = 0) OR 
                    (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103)) THEN
                allEvacuated := FALSE;
                EXIT;
            END_IF
        END_FOR
        IF allEvacuated THEN
            GVL.resetActive := FALSE;
        END_IF
    END_IF

    // ============================================================
    // Основной блок – только когда автооператор свободен
    // ============================================================
    IF NOT GVL.aoBusy THEN
        // Сброс переменных задания перед каждым поиском
        RebuildBufferAndLoadStatus();
        GVL.aoJobSource := -1;
        GVL.aoJobDest   := -1;
        GVL.candidate := 0;

        // ---------- Обработка кнопок "Загружена" / "Разгружена" ----------
        IF NOT GVL.resetActive THEN
            podAtPos0 := 0;
            FOR a := 1 TO 4 DO
                IF GVL.podveskas[a].currentPos = 0 THEN
                    podAtPos0 := a;
                    EXIT;
                END_IF
            END_FOR

            IF podAtPos0 > 0 THEN
                // ----- На позиции 0 уже стоит подвеска -----
                IF GVL.PodLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
                    IF (Control_HMI.processType = E_RouteType.eTin) OR 
                       (Control_HMI.processType = E_RouteType.eSilver) THEN
                        GVL.podveskas[podAtPos0].processType := Control_HMI.processType;
                        GVL.podveskas[podAtPos0].xLoaded := TRUE;
                        GVL.podveskas[podAtPos0].xUnloaded := FALSE;
                        GVL.podveskas[podAtPos0].currentStep := 0;
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;

                        // Очищаем старые планы перед новой симуляцией
                        FOR i := 1 TO 17 DO
                            GVL.podveskas[podAtPos0].simStartTime[i] := DT#1970-01-01-00:00:00;
                            //GVL.podveskas[podAtPos0].simFreeTime[i]  := DT#1970-01-01-00:00:00;
                            GVL.podveskas[podAtPos0].simBusyUntil[i] := DT#1970-01-01-00:00:00;
                        END_FOR;

                        route := GVL.podveskas[podAtPos0].processType;
                        firstVat := GetVatFromRecipe(route, 1);

                        IF firstVat > 0 THEN
                            IF EvaluatePodveska(podveska := GVL.podveskas[podAtPos0]) THEN
                                // Симуляция успешна – копируем планы и стартуем в линию
                                FOR i := 1 TO 17 DO
                                    //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[podAtPos0].simFreeTime[i]);
                                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[podAtPos0].simBusyUntil[i]);
                                END_FOR;
                                GVL.aoJobSource := 0;
                                GVL.aoJobDest := firstVat;
                                GVL.candidate := podAtPos0;
                                GVL.aoBusy := TRUE;
                                RETURN;
                            ELSE
                                // Симуляция неудачна – отправляем в буфер
                                IF Main.bufferHolds[1] = 0 THEN
                                    GVL.aoJobDest := 101;
                                ELSIF Main.bufferHolds[2] = 0 THEN
                                    GVL.aoJobDest := 102;
                                ELSIF Main.bufferHolds[3] = 0 THEN
                                    GVL.aoJobDest := 103;
                                ELSE
                                    GVL.aoJobDest := -1;
                                END_IF
                                IF GVL.aoJobDest > 0 THEN
                                    GVL.aoJobSource := 0;
                                    GVL.candidate := podAtPos0;
                                    GVL.aoBusy := TRUE;
                                    RETURN;
                                END_IF
                                // Если все буферы заняты, подвеска остаётся на позиции 0
                            END_IF
                        END_IF
                    ELSE
                        GVL.PodLoaded := FALSE;
                    END_IF
                END_IF

                // *** ИЗМЕНЁННАЯ ОБРАБОТКА КНОПКИ "РАЗГРУЖЕНО" ***
                IF GVL.PodUnLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
                    GVL.podveskas[podAtPos0].xUnloaded := TRUE;
                    GVL.podveskas[podAtPos0].xLoaded := FALSE;
                    GVL.PodUnLoaded := FALSE;
                    GVL.BtnUnLoaded_push := FALSE;

                    // Если все буферы заняты, немедленно перемещаем разгруженную подвеску в сушку (1)
                    IF (Main.bufferHolds[1] <> 0) AND (Main.bufferHolds[2] <> 0) AND (Main.bufferHolds[3] <> 0) THEN
                        IF NOT GVL.vanBusy[1] THEN
                            GVL.aoJobSource := 0;
                            GVL.aoJobDest := 1;
                            GVL.candidate := podAtPos0;
                            GVL.aoBusy := TRUE;
                            gvl.AutoLoadFromBuffer := TRUE;   // запрос на автоматическую загрузку
                            RETURN;
                        END_IF
                    END_IF
                END_IF
            ELSE
                // ----- Позиция 0 свободна -----

                // *** АВТОМАТИЧЕСКИЙ ВЫЗОВ ПОДВЕСКИ ИЗ БУФЕРА ПОСЛЕ ЭКСТРЕННОЙ РАЗГРУЗКИ ***
                IF gvl.AutoLoadFromBuffer THEN
                    bestIdx := 0;
                    bestTime := DT#2100-01-01-00:00:00;
                    FOR a := 1 TO 4 DO
                        IF (GVL.podveskas[a].currentStep = -1) AND 
                           (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                            IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                bestTime := GVL.podveskas[a].bufferEntryTime;
                                bestIdx := a;
                            END_IF
                        END_IF
                    END_FOR
                    IF bestIdx > 0 THEN
                        GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
                        GVL.aoJobDest := 0;
                        GVL.candidate := bestIdx;
                        gvl.AutoLoadFromBuffer := FALSE;
                        GVL.podveskas[bestIdx].xLoaded := FALSE;
                        GVL.podveskas[bestIdx].xUnloaded := FALSE;
                        GVL.aoBusy := TRUE;
                        RETURN;
                    ELSE
                        gvl.AutoLoadFromBuffer := FALSE;
                    END_IF
                END_IF

                IF GVL.PodLoaded THEN
                    bestIdx := 0;
                    bestTime := DT#2100-01-01-00:00:00;
                    
                    // Первый проход: ищем подвески с xUnloaded = FALSE (готовые к загрузке)
                    FOR a := 1 TO 4 DO
                        IF (GVL.podveskas[a].currentStep = -1) AND 
                           (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) AND
                           (NOT GVL.podveskas[a].xUnloaded) THEN
                            IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                bestTime := GVL.podveskas[a].bufferEntryTime;
                                bestIdx := a;
                            END_IF
                        END_IF
                    END_FOR
                    
                    // Если не нашли готовых к загрузке, берём любую завершённую (xUnloaded = TRUE)
                    IF bestIdx = 0 THEN
                        FOR a := 1 TO 4 DO
                            IF (GVL.podveskas[a].currentStep = -1) AND 
                               (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                                IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                    bestTime := GVL.podveskas[a].bufferEntryTime;
                                    bestIdx := a;
                                END_IF
                            END_IF
                        END_FOR
                    END_IF
                
                    IF bestIdx > 0 THEN
                        GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
                        GVL.aoJobDest := 0;
                        GVL.candidate := bestIdx;
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;
                        GVL.podveskas[bestIdx].xLoaded := FALSE;
                        GVL.podveskas[bestIdx].xUnloaded := FALSE;
                        GVL.aoBusy := TRUE;
                        RETURN;
                    ELSE
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;
                    END_IF
                END_IF
            END_IF
        END_IF

        // ============================================================
        // НАКОПЛЕНИЕ ПЛАНОВ ВСЕХ АКТИВНЫХ ПОДВЕСОК В ГЛОБАЛЬНЫЙ ПЛАН
        // ============================================================
        // 1. Сброс глобальных планов до базового состояния (только факт. занятость)
        FOR i := 1 TO 17 DO
            IF GVL.vanBusy[i] THEN
                GVL.planVatFreeTime[i] := MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                             GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0));
                GVL.planVatBusyUntil[i] := GVL.planVatFreeTime[i];
            ELSE
                GVL.planVatFreeTime[i] := GVL.sysTime;
                GVL.planVatBusyUntil[i] := GVL.sysTime;
            END_IF
        END_FOR;

        // 2. Последовательное добавление планов активных подвесок
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentStep >= 0 THEN
                EvaluatePodveska(podveska := GVL.podveskas[a]);
                FOR i := 1 TO 17 DO
                    GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simBusyUntil[i]);
                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                END_FOR;
            END_IF;
        END_FOR;

        // ---------- Основной цикл выбора победителя ----------
        highestPrio := -201;
        candidateLocal := 0;
        aoJobSourceLocal := -1;
        aoJobDestLocal := -1;
        evacuateLeader := FALSE;

        FOR a := 1 TO 4 DO
            evacuateCandidate := FALSE;

            sourcePos := GVL.podveskas[a].currentPos;
            route := GVL.podveskas[a].processType;
            curStep := GVL.podveskas[a].currentStep;

            expectedVat := GetVatFromRecipe(route, curStep);

            GetTargetVatForPodveska := 0;
            IF curStep >= 0 AND curStep <= 13 THEN
                CASE route OF
                    E_RouteType.eTin:
                        GetTargetVatForPodveska := TargetVars.recipeTin[curStep + 1].vatNo;
                    E_RouteType.eSilver:
                        GetTargetVatForPodveska := TargetVars.recipeSilver[curStep + 1].vatNo;
                END_CASE
            END_IF
            targetVat := GetTargetVatForPodveska;

            // ---------- Логика для завершённых подвесок ----------
            IF curStep < 0 THEN
                IF sourcePos = 0 THEN
                    IF GVL.podveskas[a].xUnloaded THEN
                        IF Main.bufferHolds[1] = 0 THEN
                            targetVat := 101;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSIF Main.bufferHolds[2] = 0 THEN
                            targetVat := 102;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSIF Main.bufferHolds[3] = 0 THEN
                            targetVat := 103;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSE
                            // Все буферы заняты – пытаемся освободить через ванну 1
                            IF NOT GVL.vanBusy[1] THEN
                                bestIdx := 0;
                                bestTime := DT#2100-01-01-00:00:00;
                                FOR a2 := 1 TO 4 DO
                                    IF (GVL.podveskas[a2].currentStep = -1) AND 
                                       (GVL.podveskas[a2].currentPos >= 101 AND GVL.podveskas[a2].currentPos <= 103) THEN
                                        IF (GVL.podveskas[a2].bufferEntryTime < bestTime) THEN
                                            bestTime := GVL.podveskas[a2].bufferEntryTime;
                                            bestIdx := a2;
                                        END_IF
                                    END_IF
                                END_FOR
                                IF bestIdx > 0 THEN
                                    candidateLocal := bestIdx;
                                    aoJobSourceLocal := GVL.podveskas[bestIdx].currentPos;
                                    aoJobDestLocal := 1;
                                    evacuateLeader := FALSE;
                                    EXIT;
                                END_IF
                            END_IF
                            IsPodveskaReadyToUnload := FALSE;
                        END_IF
                    ELSE
                        IsPodveskaReadyToUnload := FALSE;
                        CONTINUE;
                    END_IF
                ELSIF sourcePos >= 101 AND sourcePos <= 103 THEN
                    CONTINUE;
                ELSIF sourcePos >= 1 AND sourcePos <= 17 THEN
                    IsPodveskaReadyToUnload := TRUE;
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                    ELSIF NOT GVL.loadPosBusy THEN
                        targetVat := 0;
                    ELSE
                        targetVat := -1;
                    END_IF
                ELSE
                    CONTINUE;
                END_IF
            ELSE
                IsPodveskaReadyToUnload := FALSE;

                IF sourcePos = 0 OR (sourcePos >= 101 AND sourcePos <= 103) THEN
                    IsPodveskaReadyToUnload := TRUE;
                END_IF

                IF sourcePos >= 1 AND sourcePos <= 17 THEN
                    IF sourcePos <> expectedVat THEN
                        IsPodveskaReadyToUnload := FALSE;
                    END_IF
                    IF GVL.vanEndTime[sourcePos] < GVL.sysTime THEN
                        IsPodveskaReadyToUnload := TRUE;
                    END_IF
                    IF (sourcePos = 4 OR sourcePos = 8 OR sourcePos = 13 OR sourcePos = 16) AND GVL.vanBusy[sourcePos - 1] THEN
                        IsPodveskaReadyToUnload := FALSE;
                    END_IF
                END_IF
            END_IF

            IF IsPodveskaReadyToUnload THEN
                IF targetVat < 0 THEN
                    CanLoadTargetVat := FALSE;
                ELSIF targetVat = 0 THEN
                    CanLoadTargetVat := (NOT GVL.loadPosBusy);
                ELSIF targetVat >= 1 AND targetVat <= 17 THEN
                    CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
                ELSIF targetVat >= 101 AND targetVat <= 103 THEN
                    CanLoadTargetVat := (Main.bufferHolds[targetVat - 100] = 0);
                ELSE
                    CanLoadTargetVat := FALSE;
                END_IF

                IF (sourcePos >= 1 AND sourcePos <= 17) AND (curStep >= 0) AND (targetVat = 0) AND (NOT CanLoadTargetVat) THEN
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    END_IF
                END_IF

                IF (sourcePos = 0) AND (curStep >= 0) AND (NOT CanLoadTargetVat) THEN
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                        CanLoadTargetVat := TRUE;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                        CanLoadTargetVat := TRUE;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                        CanLoadTargetVat := TRUE;
                    ELSE
                        CanLoadTargetVat := FALSE;
                    END_IF
                END_IF

                IF CanLoadTargetVat THEN
                    // Сохраняем текущий коллективный план
                    FOR i := 1 TO 17 DO
                        savedFree[i] := GVL.planVatFreeTime[i];
                        savedBusy[i] := GVL.planVatBusyUntil[i];
                    END_FOR;

                    // Временно добавляем планы кандидата
                    FOR i := 1 TO 17 DO
                        //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simFreeTime[i]);
                        GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                    END_FOR;

                    // Рассчитываем приоритет
                    prio := 0;
                    IF (route = E_RouteType.eTin AND curStep = 8 AND sourcePos = 10) OR
                       (route = E_RouteType.eSilver AND curStep = 8 AND sourcePos = 6) THEN
                        prio := prio + 1000;
                    END_IF
                    IF sourcePos >= 1 AND sourcePos <= 17 AND curStep > 0 THEN
                        prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.vanEndTime[sourcePos]) / 1000);
                    END_IF
                    IF (route = E_RouteType.eTin AND targetVat = 11) OR
                       (route = E_RouteType.eSilver AND targetVat = 5) THEN
                        prio := prio + 500;
                    END_IF
                    IF ((sourcePos = 0) OR (sourcePos >= 101 AND sourcePos <= 103)) AND (curStep >= 0) THEN
                        prio := prio + 700;
                    END_IF
                    IF sourcePos >= 101 AND sourcePos <= 103 THEN
                        prio := prio - 200;
                        prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.podveskas[a].bufferEntryTime) / 1000);
                    END_IF
                    IF (sourcePos = 3 OR sourcePos = 7 OR sourcePos = 12 OR sourcePos = 15) THEN
                        IF GVL.vanBusy[sourcePos + 1] THEN
                            prio := prio + 600;
                        END_IF
                    END_IF
                    IF (curStep < 0) AND (sourcePos = 0) THEN
                        prio := prio + 2600;
                    END_IF
                    IF (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
                        prio := prio + 2500;
                    END_IF
                    IF GVL.resetActive AND (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
                        prio := prio + 3000;
                    END_IF

                    gvl.testPrio[a] := prio;

                    IF prio > highestPrio THEN
                        highestPrio := prio;
                        candidateLocal := a;
                        aoJobSourceLocal := sourcePos;
                        aoJobDestLocal := targetVat;
                        evacuateLeader := evacuateCandidate;
                    END_IF;

                    // Восстанавливаем коллективный план
                    FOR i := 1 TO 17 DO
                        GVL.planVatFreeTime[i] := savedFree[i];
                        GVL.planVatBusyUntil[i] := savedBusy[i];
                    END_FOR;
                END_IF
            END_IF
        END_FOR

        // ----------------------------------------------------------
        // Запуск движения для выбранного кандидата
        // ----------------------------------------------------------
        IF candidateLocal > 0 THEN
            GVL.candidate := candidateLocal;
            GVL.aoJobSource := aoJobSourceLocal;
            GVL.aoJobDest := aoJobDestLocal;

            IF evacuateLeader THEN
                GVL.podveskas[GVL.candidate].currentStep := -1;
                GVL.podveskas[GVL.candidate].xLoaded := FALSE;
            END_IF;

            // Окончательная фиксация: копируем личные планы победителя в глобальные
            FOR i := 1 TO 17 DO
                //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[GVL.candidate].simFreeTime[i]);
                GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[GVL.candidate].simBusyUntil[i]);
            END_FOR;

            GVL.aoBusy := TRUE;
        END_IF;
    END_IF;   // конец IF NOT GVL.aoBusy
 // ============================================================
    // Синхронизация планов с физическим состоянием линии
    // ============================================================
    FOR i := 1 TO 17 DO
        IF GVL.vanBusy[i] THEN
            GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], 
                                          MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                              GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
            GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], 
                                           MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                               GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0)));
        ELSE
            GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.sysTime);
            GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.sysTime);
        END_IF
    END_FOR

    // ============================================================
    // Однократная инициализация (firstScan – VAR_STAT в FB)
    // ============================================================
    IF gvl.firstScan THEN
        FOR i := 1 TO 17 DO
            GVL.planVatFreeTime[i] := GVL.sysTime;
            GVL.planVatBusyUntil[i] := GVL.sysTime;
        END_FOR;
        GVL.loadPosBusy := FALSE;
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentPos = 0 THEN
                GVL.loadPosBusy := TRUE;
                EXIT;
            END_IF
        END_FOR

        FOR a := 1 TO 4 DO
            IF (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                GVL.podveskas[a].bufferEntryTime := GVL.sysTime;
            END_IF
        END_FOR
        RebuildBufferAndLoadStatus();
        GVL.aoJobSource := -1;
        GVL.aoJobDest   := -1;
        gvl.firstScan := FALSE;
    END_IF

    // ============================================================
    // Обработка кнопки Reset
    // ============================================================
    IF GVL.Reset THEN
        GVL.Reset := FALSE;
        GVL.resetActive := TRUE;
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentStep >= 0 THEN
                GVL.podveskas[a].currentStep := -1;
                GVL.podveskas[a].xLoaded := FALSE;
                GVL.podveskas[a].xUnloaded := FALSE;
            END_IF
        END_FOR
        RebuildBufferAndLoadStatus();
    END_IF

    IF GVL.resetActive THEN
        allEvacuated := TRUE;
        FOR a := 1 TO 4 DO
            IF NOT ((GVL.podveskas[a].currentPos = 0) OR 
                    (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103)) THEN
                allEvacuated := FALSE;
                EXIT;
            END_IF
        END_FOR
        IF allEvacuated THEN
            GVL.resetActive := FALSE;
        END_IF
    END_IF

    // ============================================================
    // Основной блок – только когда автооператор свободен
    // ============================================================
    IF NOT GVL.aoBusy THEN
        // Сброс переменных задания перед каждым поиском
        RebuildBufferAndLoadStatus();
        GVL.aoJobSource := -1;
        GVL.aoJobDest   := -1;
        GVL.candidate := 0;

        // ---------- Обработка кнопок "Загружена" / "Разгружена" ----------
        IF NOT GVL.resetActive THEN
            podAtPos0 := 0;
            FOR a := 1 TO 4 DO
                IF GVL.podveskas[a].currentPos = 0 THEN
                    podAtPos0 := a;
                    EXIT;
                END_IF
            END_FOR

            IF podAtPos0 > 0 THEN
                // ----- На позиции 0 уже стоит подвеска -----
                IF GVL.PodLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
                    IF (Control_HMI.processType = E_RouteType.eTin) OR 
                       (Control_HMI.processType = E_RouteType.eSilver) THEN
                        GVL.podveskas[podAtPos0].processType := Control_HMI.processType;
                        GVL.podveskas[podAtPos0].xLoaded := TRUE;
                        GVL.podveskas[podAtPos0].xUnloaded := FALSE;
                        GVL.podveskas[podAtPos0].currentStep := 0;
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;

                        // Очищаем старые планы перед новой симуляцией
                        FOR i := 1 TO 17 DO
                            GVL.podveskas[podAtPos0].simStartTime[i] := DT#1970-01-01-00:00:00;
                            //GVL.podveskas[podAtPos0].simFreeTime[i]  := DT#1970-01-01-00:00:00;
                            GVL.podveskas[podAtPos0].simBusyUntil[i] := DT#1970-01-01-00:00:00;
                        END_FOR;

                        route := GVL.podveskas[podAtPos0].processType;
                        firstVat := GetVatFromRecipe(route, 1);

                        IF firstVat > 0 THEN
                            IF EvaluatePodveska(podveska := GVL.podveskas[podAtPos0]) THEN
                                // Симуляция успешна – копируем планы и стартуем в линию
                                FOR i := 1 TO 17 DO
                                    //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[podAtPos0].simFreeTime[i]);
                                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[podAtPos0].simBusyUntil[i]);
                                END_FOR;
                                GVL.aoJobSource := 0;
                                GVL.aoJobDest := firstVat;
                                GVL.candidate := podAtPos0;
                                GVL.aoBusy := TRUE;
                                RETURN;
                            ELSE
                                // Симуляция неудачна – отправляем в буфер
                                IF Main.bufferHolds[1] = 0 THEN
                                    GVL.aoJobDest := 101;
                                ELSIF Main.bufferHolds[2] = 0 THEN
                                    GVL.aoJobDest := 102;
                                ELSIF Main.bufferHolds[3] = 0 THEN
                                    GVL.aoJobDest := 103;
                                ELSE
                                    GVL.aoJobDest := -1;
                                END_IF
                                IF GVL.aoJobDest > 0 THEN
                                    GVL.aoJobSource := 0;
                                    GVL.candidate := podAtPos0;
                                    GVL.aoBusy := TRUE;
                                    RETURN;
                                END_IF
                                // Если все буферы заняты, подвеска остаётся на позиции 0
                            END_IF
                        END_IF
                    ELSE
                        GVL.PodLoaded := FALSE;
                    END_IF
                END_IF

                // *** ИЗМЕНЁННАЯ ОБРАБОТКА КНОПКИ "РАЗГРУЖЕНО" ***
                IF GVL.PodUnLoaded AND (GVL.podveskas[podAtPos0].currentStep = -1) THEN
                    GVL.podveskas[podAtPos0].xUnloaded := TRUE;
                    GVL.podveskas[podAtPos0].xLoaded := FALSE;
                    GVL.PodUnLoaded := FALSE;
                    GVL.BtnUnLoaded_push := FALSE;

                    // Если все буферы заняты, немедленно перемещаем разгруженную подвеску в сушку (1)
                    IF (Main.bufferHolds[1] <> 0) AND (Main.bufferHolds[2] <> 0) AND (Main.bufferHolds[3] <> 0) THEN
                        IF NOT GVL.vanBusy[1] THEN
                            GVL.aoJobSource := 0;
                            GVL.aoJobDest := 1;
                            GVL.candidate := podAtPos0;
                            GVL.aoBusy := TRUE;
                            gvl.AutoLoadFromBuffer := TRUE;   // запрос на автоматическую загрузку
                            RETURN;
                        END_IF
                    END_IF
                END_IF
            ELSE
                // ----- Позиция 0 свободна -----

                // *** АВТОМАТИЧЕСКИЙ ВЫЗОВ ПОДВЕСКИ ИЗ БУФЕРА ПОСЛЕ ЭКСТРЕННОЙ РАЗГРУЗКИ ***
                IF gvl.AutoLoadFromBuffer THEN
                    bestIdx := 0;
                    bestTime := DT#2100-01-01-00:00:00;
                    FOR a := 1 TO 4 DO
                        IF (GVL.podveskas[a].currentStep = -1) AND 
                           (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                            IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                bestTime := GVL.podveskas[a].bufferEntryTime;
                                bestIdx := a;
                            END_IF
                        END_IF
                    END_FOR
                    IF bestIdx > 0 THEN
                        GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
                        GVL.aoJobDest := 0;
                        GVL.candidate := bestIdx;
                        gvl.AutoLoadFromBuffer := FALSE;
                        GVL.podveskas[bestIdx].xLoaded := FALSE;
                        GVL.podveskas[bestIdx].xUnloaded := FALSE;
                        GVL.aoBusy := TRUE;
                        RETURN;
                    ELSE
                        gvl.AutoLoadFromBuffer := FALSE;
                    END_IF
                END_IF

                IF GVL.PodLoaded THEN
                    bestIdx := 0;
                    bestTime := DT#2100-01-01-00:00:00;
                    
                    // Первый проход: ищем подвески с xUnloaded = FALSE (готовые к загрузке)
                    FOR a := 1 TO 4 DO
                        IF (GVL.podveskas[a].currentStep = -1) AND 
                           (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) AND
                           (NOT GVL.podveskas[a].xUnloaded) THEN
                            IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                bestTime := GVL.podveskas[a].bufferEntryTime;
                                bestIdx := a;
                            END_IF
                        END_IF
                    END_FOR
                    
                    // Если не нашли готовых к загрузке, берём любую завершённую (xUnloaded = TRUE)
                    IF bestIdx = 0 THEN
                        FOR a := 1 TO 4 DO
                            IF (GVL.podveskas[a].currentStep = -1) AND 
                               (GVL.podveskas[a].currentPos >= 101 AND GVL.podveskas[a].currentPos <= 103) THEN
                                IF (GVL.podveskas[a].bufferEntryTime < bestTime) THEN
                                    bestTime := GVL.podveskas[a].bufferEntryTime;
                                    bestIdx := a;
                                END_IF
                            END_IF
                        END_FOR
                    END_IF
                
                    IF bestIdx > 0 THEN
                        GVL.aoJobSource := GVL.podveskas[bestIdx].currentPos;
                        GVL.aoJobDest := 0;
                        GVL.candidate := bestIdx;
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;
                        GVL.podveskas[bestIdx].xLoaded := FALSE;
                        GVL.podveskas[bestIdx].xUnloaded := FALSE;
                        GVL.aoBusy := TRUE;
                        RETURN;
                    ELSE
                        GVL.PodLoaded := FALSE;
                        GVL.BtnLoaded_Sn_push := FALSE;
                        GVL.BtnLoaded_Ag_push := FALSE;
                    END_IF
                END_IF
            END_IF
        END_IF

        // ============================================================
        // НАКОПЛЕНИЕ ПЛАНОВ ВСЕХ АКТИВНЫХ ПОДВЕСОК В ГЛОБАЛЬНЫЙ ПЛАН
        // ============================================================
        // 1. Сброс глобальных планов до базового состояния (только факт. занятость)
        FOR i := 1 TO 17 DO
            IF GVL.vanBusy[i] THEN
                GVL.planVatFreeTime[i] := MAX(GVL.vanEndTime[i] + GVL.vanTransitOut[i], 
                                             GVL.sysTime + REAL_TO_TIME(PV.transitTimed[MapToTransitIndex(GVL.aoPosition), i] * 1000.0));
                GVL.planVatBusyUntil[i] := GVL.planVatFreeTime[i];
            ELSE
                GVL.planVatFreeTime[i] := GVL.sysTime;
                GVL.planVatBusyUntil[i] := GVL.sysTime;
            END_IF
        END_FOR;

        // 2. Последовательное добавление планов активных подвесок
        FOR a := 1 TO 4 DO
            IF GVL.podveskas[a].currentStep >= 0 THEN
                EvaluatePodveska(podveska := GVL.podveskas[a]);
                FOR i := 1 TO 17 DO
                    GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simBusyUntil[i]);
                    GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                END_FOR;
            END_IF;
        END_FOR;

        // ---------- Основной цикл выбора победителя ----------
        highestPrio := -201;
        candidateLocal := 0;
        aoJobSourceLocal := -1;
        aoJobDestLocal := -1;
        evacuateLeader := FALSE;

        FOR a := 1 TO 4 DO
            evacuateCandidate := FALSE;

            sourcePos := GVL.podveskas[a].currentPos;
            route := GVL.podveskas[a].processType;
            curStep := GVL.podveskas[a].currentStep;

            expectedVat := GetVatFromRecipe(route, curStep);

            GetTargetVatForPodveska := 0;
            IF curStep >= 0 AND curStep <= 13 THEN
                CASE route OF
                    E_RouteType.eTin:
                        GetTargetVatForPodveska := TargetVars.recipeTin[curStep + 1].vatNo;
                    E_RouteType.eSilver:
                        GetTargetVatForPodveska := TargetVars.recipeSilver[curStep + 1].vatNo;
                END_CASE
            END_IF
            targetVat := GetTargetVatForPodveska;

            // ---------- Логика для завершённых подвесок ----------
            IF curStep < 0 THEN
                IF sourcePos = 0 THEN
                    IF GVL.podveskas[a].xUnloaded THEN
                        IF Main.bufferHolds[1] = 0 THEN
                            targetVat := 101;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSIF Main.bufferHolds[2] = 0 THEN
                            targetVat := 102;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSIF Main.bufferHolds[3] = 0 THEN
                            targetVat := 103;
                            IsPodveskaReadyToUnload := TRUE;
                        ELSE
                            // Все буферы заняты – пытаемся освободить через ванну 1
                            IF NOT GVL.vanBusy[1] THEN
                                bestIdx := 0;
                                bestTime := DT#2100-01-01-00:00:00;
                                FOR a2 := 1 TO 4 DO
                                    IF (GVL.podveskas[a2].currentStep = -1) AND 
                                       (GVL.podveskas[a2].currentPos >= 101 AND GVL.podveskas[a2].currentPos <= 103) THEN
                                        IF (GVL.podveskas[a2].bufferEntryTime < bestTime) THEN
                                            bestTime := GVL.podveskas[a2].bufferEntryTime;
                                            bestIdx := a2;
                                        END_IF
                                    END_IF
                                END_FOR
                                IF bestIdx > 0 THEN
                                    candidateLocal := bestIdx;
                                    aoJobSourceLocal := GVL.podveskas[bestIdx].currentPos;
                                    aoJobDestLocal := 1;
                                    evacuateLeader := FALSE;
                                    EXIT;
                                END_IF
                            END_IF
                            IsPodveskaReadyToUnload := FALSE;
                        END_IF
                    ELSE
                        IsPodveskaReadyToUnload := FALSE;
                        CONTINUE;
                    END_IF
                ELSIF sourcePos >= 101 AND sourcePos <= 103 THEN
                    CONTINUE;
                ELSIF sourcePos >= 1 AND sourcePos <= 17 THEN
                    IsPodveskaReadyToUnload := TRUE;
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                    ELSIF NOT GVL.loadPosBusy THEN
                        targetVat := 0;
                    ELSE
                        targetVat := -1;
                    END_IF
                ELSE
                    CONTINUE;
                END_IF
            ELSE
                IsPodveskaReadyToUnload := FALSE;

                IF sourcePos = 0 OR (sourcePos >= 101 AND sourcePos <= 103) THEN
                    IsPodveskaReadyToUnload := TRUE;
                END_IF

                IF sourcePos >= 1 AND sourcePos <= 17 THEN
                    IF sourcePos <> expectedVat THEN
                        IsPodveskaReadyToUnload := FALSE;
                    END_IF
                    IF GVL.vanEndTime[sourcePos] < GVL.sysTime THEN
                        IsPodveskaReadyToUnload := TRUE;
                    END_IF
                    IF (sourcePos = 4 OR sourcePos = 8 OR sourcePos = 13 OR sourcePos = 16) AND GVL.vanBusy[sourcePos - 1] THEN
                        IsPodveskaReadyToUnload := FALSE;
                    END_IF
                END_IF
            END_IF

            IF IsPodveskaReadyToUnload THEN
                IF targetVat < 0 THEN
                    CanLoadTargetVat := FALSE;
                ELSIF targetVat = 0 THEN
                    CanLoadTargetVat := (NOT GVL.loadPosBusy);
                ELSIF targetVat >= 1 AND targetVat <= 17 THEN
                    CanLoadTargetVat := NOT GVL.vanBusy[targetVat];
                ELSIF targetVat >= 101 AND targetVat <= 103 THEN
                    CanLoadTargetVat := (Main.bufferHolds[targetVat - 100] = 0);
                ELSE
                    CanLoadTargetVat := FALSE;
                END_IF

                IF (sourcePos >= 1 AND sourcePos <= 17) AND (curStep >= 0) AND (targetVat = 0) AND (NOT CanLoadTargetVat) THEN
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                        CanLoadTargetVat := TRUE;
                        evacuateCandidate := TRUE;
                    END_IF
                END_IF

                IF (sourcePos = 0) AND (curStep >= 0) AND (NOT CanLoadTargetVat) THEN
                    IF Main.bufferHolds[1] = 0 THEN
                        targetVat := 101;
                        CanLoadTargetVat := TRUE;
                    ELSIF Main.bufferHolds[2] = 0 THEN
                        targetVat := 102;
                        CanLoadTargetVat := TRUE;
                    ELSIF Main.bufferHolds[3] = 0 THEN
                        targetVat := 103;
                        CanLoadTargetVat := TRUE;
                    ELSE
                        CanLoadTargetVat := FALSE;
                    END_IF
                END_IF

                IF CanLoadTargetVat THEN
                    // Сохраняем текущий коллективный план
                    FOR i := 1 TO 17 DO
                        savedFree[i] := GVL.planVatFreeTime[i];
                        savedBusy[i] := GVL.planVatBusyUntil[i];
                    END_FOR;

                    // Временно добавляем планы кандидата
                    FOR i := 1 TO 17 DO
                        //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[a].simFreeTime[i]);
                        GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[a].simBusyUntil[i]);
                    END_FOR;

                    // Рассчитываем приоритет
                    prio := 0;
                    IF (route = E_RouteType.eTin AND curStep = 8 AND sourcePos = 10) OR
                       (route = E_RouteType.eSilver AND curStep = 8 AND sourcePos = 6) THEN
                        prio := prio + 1000;
                    END_IF
                    IF sourcePos >= 1 AND sourcePos <= 17 AND curStep > 0 THEN
                        prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.vanEndTime[sourcePos]) / 1000);
                    END_IF
                    IF (route = E_RouteType.eTin AND targetVat = 11) OR
                       (route = E_RouteType.eSilver AND targetVat = 5) THEN
                        prio := prio + 500;
                    END_IF
                    IF ((sourcePos = 0) OR (sourcePos >= 101 AND sourcePos <= 103)) AND (curStep >= 0) THEN
                        prio := prio + 700;
                    END_IF
                    IF sourcePos >= 101 AND sourcePos <= 103 THEN
                        prio := prio - 200;
                        prio := prio + ULINT_TO_INT(TIME_TO_ULINT(GVL.sysTime - GVL.podveskas[a].bufferEntryTime) / 1000);
                    END_IF
                    IF (sourcePos = 3 OR sourcePos = 7 OR sourcePos = 12 OR sourcePos = 15) THEN
                        IF GVL.vanBusy[sourcePos + 1] THEN
                            prio := prio + 600;
                        END_IF
                    END_IF
                    IF (curStep < 0) AND (sourcePos = 0) THEN
                        prio := prio + 2600;
                    END_IF
                    IF (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
                        prio := prio + 2500;
                    END_IF
                    IF GVL.resetActive AND (curStep < 0) AND (sourcePos >= 1 AND sourcePos <= 17) THEN
                        prio := prio + 3000;
                    END_IF

                    gvl.testPrio[a] := prio;

                    IF prio > highestPrio THEN
                        highestPrio := prio;
                        candidateLocal := a;
                        aoJobSourceLocal := sourcePos;
                        aoJobDestLocal := targetVat;
                        evacuateLeader := evacuateCandidate;
                    END_IF;

                    // Восстанавливаем коллективный план
                    FOR i := 1 TO 17 DO
                        GVL.planVatFreeTime[i] := savedFree[i];
                        GVL.planVatBusyUntil[i] := savedBusy[i];
                    END_FOR;
                END_IF
            END_IF
        END_FOR

        // ----------------------------------------------------------
        // Запуск движения для выбранного кандидата
        // ----------------------------------------------------------
        IF candidateLocal > 0 THEN
            GVL.candidate := candidateLocal;
            GVL.aoJobSource := aoJobSourceLocal;
            GVL.aoJobDest := aoJobDestLocal;

            IF evacuateLeader THEN
                GVL.podveskas[GVL.candidate].currentStep := -1;
                GVL.podveskas[GVL.candidate].xLoaded := FALSE;
            END_IF;

            // Окончательная фиксация: копируем личные планы победителя в глобальные
            FOR i := 1 TO 17 DO
                //GVL.planVatFreeTime[i] := MAX(GVL.planVatFreeTime[i], GVL.podveskas[GVL.candidate].simFreeTime[i]);
                GVL.planVatBusyUntil[i] := MAX(GVL.planVatBusyUntil[i], GVL.podveskas[GVL.candidate].simBusyUntil[i]);
            END_FOR;

            GVL.aoBusy := TRUE;
        END_IF;
    END_IF;   // конец IF NOT GVL.aoBusy

METHOD EvaluatePodveska : BOOL
VAR_IN_OUT
    podveska : ST_Podveska;   // Модифицируем оригинал
END_VAR
VAR
    arrivalTime   : DT;
    prevVat       : INT;
    step          : INT;
    vat           : INT;
    techTime      : TIME;
    transitTime   : TIME;
    transitOut    : TIME;
    startTime     : DT;
    endTime       : DT;
    waitTime      : TIME;
    maxWait       : TIME := gvl.maxWait;
    nextVat       : INT;
    srcIdx        : INT;
END_VAR
 // Начальный момент времени
    IF (podveska.currentStep >= 0) AND (podveska.currentPos >= 1) AND (podveska.currentPos <= 17) THEN
        arrivalTime := MAX(GVL.sysTime, GVL.vanEndTime[podveska.currentPos]);
    ELSE
        arrivalTime := GVL.sysTime;
    END_IF;

    prevVat := podveska.currentPos;
    step := podveska.currentStep + 1;

    WHILE step <= 14 DO
        vat := GetVatFromRecipe(podveska.processType, step);
        IF vat = 0 THEN EXIT; END_IF;

        // Транспорт к ванне (добавляется только здесь)
        srcIdx := MapToTransitIndex(prevVat);
        transitTime := REAL_TO_TIME(PV.transitTimed[srcIdx, vat] * 1000.0);
        arrivalTime := arrivalTime + transitTime;

        // Технологическое время
        IF podveska.processType = E_RouteType.eTin THEN
            techTime := TargetVars.recipeTin[step].duration;
        ELSE
            techTime := TargetVars.recipeSilver[step].duration;
        END_IF;

        // Время выезда из текущей ванны к следующей (вычисляем заранее)
        IF step < 14 THEN
            nextVat := GetVatFromRecipe(podveska.processType, step + 1);
            IF nextVat <> 0 THEN
                transitOut := REAL_TO_TIME(PV.transitTimed[vat, nextVat] * 1000.0);
            ELSE
                transitOut := T#0s;
            END_IF;
        ELSE
            transitOut := T#0s;
        END_IF;

        // Стандартное время старта (ждём освобождения ванны)
        startTime := MAX(arrivalTime, GVL.planVatFreeTime[vat]);
        waitTime := startTime - arrivalTime;

        // Проверка лимита ожидания
        IF waitTime > maxWait THEN
            EvaluatePodveska := FALSE;
            RETURN;
        END_IF;

        // Проверка пересечения окон
        IF (GVL.planVatBusyUntil[vat] > DT#1970-01-01-00:00:00) THEN
            IF (startTime < GVL.planVatBusyUntil[vat]) AND 
               ((startTime + techTime + transitOut) > GVL.planVatBusyUntil[vat]) THEN
                EvaluatePodveska := FALSE;
                RETURN;
            END_IF;
        END_IF;

        endTime := startTime + techTime;

        // Сохраняем в структуру подвески
        podveska.simStartTime[vat] := startTime;
        //podveska.simFreeTime[vat] := endTime + transitOut;
        podveska.simBusyUntil[vat] := endTime + transitOut;

        // Готовим время для следующего шага (без удвоения transit)
        arrivalTime := endTime;

        prevVat := vat;
        step := step + 1;
    END_WHILE;

    EvaluatePodveska := TRUE;

METHOD OnMoveComplete
VAR
    src, dst : INT;
    podIdx   : INT;
    route    : E_RouteType;
    busyTime : TIME;
    maxSteps : INT;
	nextStep : INT;
END_VAR
    src := GVL.aoJobSource;
    dst := GVL.aoJobDest;

    FOR podIdx := 1 TO 4 DO
        IF GVL.podveskas[podIdx].currentPos = src THEN
            // ---------- Освобождение исходной позиции ----------
            IF src >= 1 AND src <= 17 THEN
                GVL.vanBusy[src] := FALSE;
                GVL.vanEndTime[src] := DT#1970-01-01-00:00:00;
                GVL.vanTransitOut[src] := T#0S;      // сброс времени выезда
				GVL.planVatFreeTime[src] := GVL.sysTime;   // сброс плана для освободившейся ванны
				GVL.planVatBusyUntil[src] := GVL.sysTime;  // <-- ДОБАВЛЕНО:
            ELSIF src = 0 THEN
                gvl.loadPosBusy := FALSE;
            ELSIF src >= 101 AND src <= 103 THEN
                Main.bufferHolds[src - 100] := 0;
            END_IF

            // ---------- Активная подвеска ----------
            IF GVL.podveskas[podIdx].currentStep >= 0 THEN
                GVL.podveskas[podIdx].currentPos := dst;
                route := GVL.podveskas[podIdx].processType;

                // Если подвеска уходит из позиции 0 в буфер – шаг не инкрементируем
                IF (src = 0) AND (dst >= 101) AND (dst <= 103) THEN
                    // currentStep остаётся без изменений (обычно 0)
                ELSE
                    GVL.podveskas[podIdx].currentStep := GVL.podveskas[podIdx].currentStep + 1;
                END_IF

                IF dst >= 1 AND dst <= 17 THEN
                    GVL.vanBusy[dst] := TRUE;
                    IF route = E_RouteType.eTin THEN
                        busyTime := TargetVars.recipeTin[GVL.podveskas[podIdx].currentStep].duration;
                    ELSE
                        busyTime := TargetVars.recipeSilver[GVL.podveskas[podIdx].currentStep].duration;
                    END_IF
                    GVL.vanEndTime[dst] := GVL.sysTime + busyTime;

                    // *** ВЫЧИСЛЕНИЕ ВРЕМЕНИ ВЫЕЗДА К СЛЕДУЮЩЕЙ ВАННЕ ***
                    nextStep := GVL.podveskas[podIdx].currentStep + 1;
                    IF nextStep <= 14 THEN
                        nextVat := GetVatFromRecipe(route, nextStep);
                        IF nextVat <> 0 THEN
                            GVL.vanTransitOut[dst] := REAL_TO_TIME(PV.transitTimed[dst, nextVat] * 1000.0);
                        ELSE
                            GVL.vanTransitOut[dst] := T#0s;
                        END_IF;
                    ELSE
                        GVL.vanTransitOut[dst] := T#0s;
                    END_IF;

                ELSIF dst = 0 THEN
                    gvl.loadPosBusy := TRUE;
                    GVL.podveskas[podIdx].currentStep := -1;   // разгрузка – завершение
                ELSIF dst >= 101 AND dst <= 103 THEN
                    Main.bufferHolds[dst - 100] := podIdx;
                    // Зафиксировать время въезда в буфер
                    GVL.podveskas[podIdx].bufferEntryTime := GVL.sysTime;
                END_IF

            ELSE
                // ---------- Завершённая подвеска (curStep = -1) ----------
                GVL.podveskas[podIdx].currentPos := dst;

                IF dst >= 1 AND dst <= 17 THEN
                    // Временное хранение в технологической ванне (например, ванна 1)
                    GVL.vanBusy[dst] := TRUE;
                    GVL.vanEndTime[dst] := GVL.sysTime + T#30s;   // бронь на 30 секунд
                    GVL.vanTransitOut[dst] := T#0s;                // время выезда не определено
                ELSIF dst = 0 THEN
                    // Возврат на позицию 0 – сброс флагов загрузки/разгрузки
                    GVL.podveskas[podIdx].xLoaded := FALSE;
                    GVL.podveskas[podIdx].xUnloaded := FALSE;
                    gvl.loadPosBusy := TRUE;
                ELSIF dst >= 101 AND dst <= 103 THEN
                    Main.bufferHolds[dst - 100] := podIdx;
                    // Зафиксировать время въезда в буфер
                    GVL.podveskas[podIdx].bufferEntryTime := GVL.sysTime;
                END_IF
            END_IF

            EXIT;
        END_IF
    END_FOR

    // Сброс задания после выполнения
    GVL.aoJobSource := -1;
    GVL.aoJobDest   := -1;
    GVL.candidate := 0;