/**
 * Ориентировочный рефлекс (ОР) — конкуренция среди активных стимулов.
 *
 * Условие запуска ОР: ID конечного узла активной ветки дерева восприятия
 * (дерево условий восприятия, а не только базовое состояние + эмоция).
 *
 * Алгоритм:
 * - Актуальность = новизна × значимость стимула.
 * - Если нет значимости в семантической модели для данных условий:
 *   значимость = (VitalsArr.importance)/20, новизна = 1.
 * - Новизна при наличии значимости: Н = (СЗ * samplesCount) / 10.
 * - Множители новизны при отсутствии узлов уровня: нет 6-го → Н*=2, нет 5-го → Н*=3, 4-го → Н*=4 и т.д.
 *
 * Действия ОР:
 * 1. Гистерезис: понижение актуальности остальных стимулов (вычитание константы).
 *    Стимулы из связной цепочки кадров ЭП (суффикс с реакциями на каждом кадре) гистерезису не подвергаются
 *    и получают повышение актуальности — см. EpisodicMemory_getChainPriorityStimulusSet, about_episodic_memory_rules.md.
 * 2. Блокировка генетического/условного рефлекса на 2 пульса (задержка решения).
 * 3. Гиппокамп: удержание в памяти стимула (active_stimulsArr_ID, actual_stimul_ID).
 * 4. Осознание: вызов stimuls_consciousness(actual_stimul_ID).
 * 5. Эпизодическая память: вызов save_episodic_memory().
 */
(function (global) {
  'use strict';

  /** Постоянная гистерезиса ОР: вычитается из актуальности остальных стимулов (подбор при оптимизации). */
  var OR_HYSTERESIS = 0.5;

  /** Блокировка рефлекса на столько пульсов после ОР. */
  var OR_BLOCK_PULSES = 2;

  /** Порог «хорошо известного» опыта: при samplesCount < OR_well_known стимул не вызывает ОР и не входит в ЭП. Подбор при оптимизации. */
  var OR_well_known = 10;
  global.OR_well_known = OR_well_known;

  /** Хранилище блокировок: { key: string (finalImageId или stimId), untilPulse: number, actionId?: number } */
  var reflexBlockedUntil = [];

  /**
   * Текущий путь дерева восприятия: [baseID, emotionID, 0, 0, 0, 0].
   * baseID = BadNormWell, emotionID = Emotions_getOrCreateEmotionId(BasicContextsActived).
   */
  function getCurrentTreePath() {
    var baseID = global.BadNormWell || 0;
    var emotionID = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionID = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    return [baseID, emotionID, 0, 0, 0, 0];
  }

  /**
   * Получить объект семантической значимости для (well, emotionId, finalImageId).
   * @returns {{ meanImportance: number, samplesCount: number } | null}
   */
  function getSemanticForConditions(finalImageId, well, emotionId) {
    if (!finalImageId || !well || !global.SemanticMemoryIndex) return null;
    var wKey = String(well);
    var eKey = String(emotionId);
    var bucket = global.SemanticMemoryIndex[wKey] && global.SemanticMemoryIndex[wKey][eKey];
    if (!bucket) return null;
    var obj = bucket[String(finalImageId)];
    if (!obj) return null;
    return {
      meanImportance: obj.meanImportance || 0,
      samplesCount: obj.samplesCount || 0
    };
  }

  /**
   * Уровень готовности по опыту (порог OR_well_known) для конкретного FinalImageId.
   * Возвращает число оставшихся «активаций» до выполнения порога (max(0, wellKnown - samplesCount)).
   * В состоянии Плохо фактический старт ОР порог не блокирует (см. runOROnStimulusActivation), но для UI
   * остаток считается по семантике — как в Норме/Хорошо.
   *
   * @param {number} finalImageId
   * @returns {{remainingActivations:number, samplesCount:number, wellKnown:number, well:number, emotionId:number}}
   */
  function getOrReadinessForFinalImageId(finalImageId) {
    var well = global.BadNormWell || 0;
    var wellKnown = (typeof global.OR_well_known === 'number' && global.OR_well_known >= 0) ? global.OR_well_known : OR_well_known;

    // Если базовое состояние ещё не рассчитано (BadNormWell=0) — показатель зрелости вычислить нельзя.
    if (well !== 1 && well !== 2 && well !== 3) {
      return {
        remainingActivations: null,
        samplesCount: null,
        wellKnown: wellKnown,
        well: well,
        emotionId: null
      };
    }

    // В Плохо порог OR_well_known для фактического старта ОР не применяется (см. runOROnStimulusActivation),
    // но индикатор «готовности по опыту» для UI считаем так же, как в Норме/Хорошо — иначе при well===1
    // у всех стимулов remaining=0 и все кнопки подсвечиваются как готовые к ОР.

    var emotionId = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionId = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    if (!emotionId) {
      return {
        remainingActivations: null,
        samplesCount: null,
        wellKnown: wellKnown,
        well: well,
        emotionId: 0
      };
    }

    var samplesCount = 0;
    if (finalImageId && well && emotionId) {
      var sem = getSemanticForConditions(finalImageId, well, emotionId);
      samplesCount = (sem && typeof sem.samplesCount === 'number') ? sem.samplesCount : 0;
    }

    var remaining = Math.max(0, wellKnown - samplesCount);
    return {
      remainingActivations: remaining,
      samplesCount: samplesCount,
      wellKnown: wellKnown,
      well: well,
      emotionId: emotionId
    };
  }

  /** Средняя важность виталов для значимости при отсутствии семантической записи (importance/20). */
  function getDefaultSignificanceFromVitals() {
    if (!Array.isArray(global.VitalsArr) || !global.VitalsArr.length) return 0.05;
    var sum = 0;
    for (var i = 0; i < global.VitalsArr.length; i++) {
      sum += (global.VitalsArr[i].importance || 0);
    }
    return (sum / global.VitalsArr.length) / 20;
  }

  /**
   * Множитель новизны по отсутствующим уровням узла дерева (pathArr — путь из 6 элементов).
   * Нет 6-го уровня → *2, нет 5-го → *3, 4-го → *4, 3-го → *5, 2-го → *6.
   */
  function noveltyLevelMultiplier(pathArr) {
    if (!Array.isArray(pathArr) || pathArr.length < 6) return 1;
    var m = 1;
    if (!pathArr[5]) m *= 2; // phraseID
    if (!pathArr[4]) m *= 3; // simbolID
    if (!pathArr[3]) m *= 4; // toneMoodID
    if (!pathArr[2]) m *= 5; // activityID
    if (!pathArr[1]) m *= 6; // emotionID
    return m;
  }

  /**
   * Вычислить новизну стимула для данных условий.
   * Если нет значимости в СЗ: новизна = 1.
   * Иначе Н = (СЗ * samplesCount) / 10, затем Н *= множитель по отсутствующим уровням.
   */
  function computeNovelty(finalImageId, well, emotionId, pathArr) {
    var sem = getSemanticForConditions(finalImageId, well, emotionId);
    var n;
    if (!sem || sem.samplesCount <= 0) {
      n = 1;
    } else {
      n = (sem.meanImportance * sem.samplesCount) / 10;
      if (n <= 0) n = 0.1;
    }
    n *= noveltyLevelMultiplier(pathArr || getCurrentTreePath());
    return n;
  }

  /**
   * Вычислить значимость стимула для данных условий.
   * Если нет записи в СЗ: (VitalsArr.importance)/20.
   */
  function computeSignificance(finalImageId, well, emotionId) {
    var sem = getSemanticForConditions(finalImageId, well, emotionId);
    if (!sem || sem.samplesCount <= 0) return getDefaultSignificanceFromVitals();
    return sem.meanImportance;
  }

  /**
   * Актуальность = новизна × значимость.
   */
  function computeActuality(finalImageId, well, emotionId, pathArr) {
    var sig = computeSignificance(finalImageId, well, emotionId);
    var nov = computeNovelty(finalImageId, well, emotionId, pathArr);
    return nov * sig;
  }

  /**
   * Среди кандидатов (finalImageId[]) выявить наиболее актуальный.
   * lastWinnerId — ID прошлого победителя; для остальных из актуальности вычитается гистерезис.
   * При ImportantProblem гистерезис усилен (понижается значимость остальных стимулов в конкуренции).
   * Стимулы из связной цепочки кадров ЭП (реакция на каждом кадре суффикса) гистерезису не подвергаются и
   * усиливаются по актуальности — преимущество перед «обрывающими» цепочку стимулами без такой связи.
   * @returns {{ winnerId: number, actuality: number } | null}
   */
  function findMostActualStimulus(candidateFinalImageIds, lastWinnerId) {
    if (!candidateFinalImageIds || !candidateFinalImageIds.length) return null;
    var well = global.BadNormWell || 0;
    var emotionId = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionId = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    var pathArr = getCurrentTreePath();
    var importantProblem = !!(global.InfoPicture && global.InfoPicture.ImportantProblem);
    var hysteresis = importantProblem ? OR_HYSTERESIS * 2 : OR_HYSTERESIS;
    var chainSet = typeof global.EpisodicMemory_getChainPriorityStimulusSet === 'function'
      ? global.EpisodicMemory_getChainPriorityStimulusSet() : null;

    var bestId = null;
    var bestAct = -Infinity;
    for (var i = 0; i < candidateFinalImageIds.length; i++) {
      var fid = candidateFinalImageIds[i];
      if (fid == null || fid <= 0) continue;
      var inChain = !!(chainSet && chainSet[String(fid)]);
      var act = computeActuality(fid, well, emotionId, pathArr);
      if (inChain) {
        act = act * 2 + 1;
      } else if (lastWinnerId && lastWinnerId !== fid) {
        act -= hysteresis;
      }
      if (act > bestAct) {
        bestAct = act;
        bestId = fid;
      }
    }
    return bestId != null ? { winnerId: bestId, actuality: bestAct } : null;
  }

  /** Последний победитель конкуренции (для гистерезиса). */
  var lastORWinnerId = 0;

  /**
   * Заблокировать рефлекс по данному образу/стимулу на OR_BLOCK_PULSES пульсов.
   */
  function setBlockedReflex(finalImageIdOrStimId, actionId) {
    var pulse = typeof global.Cur_puls_val === 'number' ? global.Cur_puls_val : 0;
    reflexBlockedUntil.push({
      key: String(finalImageIdOrStimId),
      actionId: actionId,
      untilPulse: pulse + OR_BLOCK_PULSES
    });
  }

  /**
   * Проверить, заблокирован ли рефлекс по данному образу/стимулу и действию (ОР задерживает на 2 пульса).
   */
  function isReflexBlockedByOR(finalImageIdOrStimId, actionId) {
    var pulse = typeof global.Cur_puls_val === 'number' ? global.Cur_puls_val : 0;
    for (var i = reflexBlockedUntil.length - 1; i >= 0; i--) {
      var b = reflexBlockedUntil[i];
      if (pulse >= b.untilPulse) {
        reflexBlockedUntil.splice(i, 1);
        continue;
      }
      if (b.key === String(finalImageIdOrStimId) && (actionId == null || b.actionId === actionId)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Детализация стимула по FinalImageId. stimulIds в FinalImages хранятся как глобальные id (type*1000+localId),
   * расшифровка через StimulusGlobalId_getName устраняет коллизии имён между типами стимулов.
   */
  /**
   * Активны стимулы «Еда» / «Вода» (глобальные id 1001, 1002): для них обходим порог OR_well_known,
   * чтобы при слабой семантике всё равно запускались ОР, ФО и генетические рефлексы «Ест»/«Пьет».
   */
  function activeIdsIncludeFoodOrWaterRelief(activeIds) {
    if (!activeIds || !activeIds.length) return false;
    for (var i = 0; i < activeIds.length; i++) {
      var g = activeIds[i];
      if (g === 1001 || g === 1002) return true;
      if (typeof global.StimulusGlobalId_fromGlobal === 'function') {
        var p = global.StimulusGlobalId_fromGlobal(g);
        if (p && p.type === 1 && (p.localId === 1 || p.localId === 2)) return true;
      }
    }
    return false;
  }

  function getStimulusDetailForAlert(finalImageId) {
    if (!finalImageId) return '';
    var stimulIds = null;
    if (Array.isArray(global.FinalImages)) {
      for (var i = 0; i < global.FinalImages.length; i++) {
        if (global.FinalImages[i] && global.FinalImages[i].id === finalImageId) {
          stimulIds = global.FinalImages[i].stimulIds;
          break;
        }
      }
    }
    if (!stimulIds || !stimulIds.length) return 'ID=' + finalImageId;
    var names = [];
    var getName = typeof global.StimulusGlobalId_getName === 'function' ? global.StimulusGlobalId_getName : null;
    for (var g = 0; g < stimulIds.length; g++) {
      var sid = stimulIds[g];
      var n = getName ? getName(sid) : '';
      if (n) names.push(n); else names.push('Id ' + sid);
    }
    return names.length ? names.join(', ') : 'ID=' + finalImageId;
  }

  /**
   * Запуск конкуренции и ОР при активации стимула (например, при щелчке).
   * Вызывать после того как активные стимулы и FinalImage обновлены.
   * Кандидаты: FinalImageId полного набора активных стимулов + по одному FinalImageId на каждый активный стимул.
   */
  function runOROnStimulusActivation() {
    if (typeof global.FinalImages_getOrCreateFinalImageId !== 'function' ||
        typeof global.PerceptionTree_getOrCreateTerminalNode !== 'function') {
      if (global.EpisodicMemory_testMode && typeof global.show_alert === 'function') {
        global.show_alert('<b>Почему не начат процесс ЭП:</b> недоступны функции дерева восприятия или образов. Ориентировочный рефлекс не вызван. Кадр ЭП не создается. Прогресс-бар не запускается.', 0);
      }
      return false;
    }

    // Глобальные id стимулов (type*1000+localId) — устраняют коллизии имён между массивами (см. stimulus_global_id.js).
    var activeIds = typeof global.StimulusGlobalId_getActiveGlobalIds === 'function'
      ? global.StimulusGlobalId_getActiveGlobalIds()
      : [];

    // Деактивация стимулов (все сняты) — нормальное действие; кадр ЭП, уже начатый при активации, не отменяется.
    if (!activeIds.length) {
      // При отсутствии активных стимулов сбрасываем детектор стимула в инфокартине.
      if (typeof global.Info_updateFromStimulus === 'function') {
        global.Info_updateFromStimulus(0, 'external');
      }
      if (typeof global.InfoDet_updateConsoleStimulus === 'function') {
        global.InfoDet_updateConsoleStimulus(0);
      }
      return false;
    }

    var fullId = global.FinalImages_getOrCreateFinalImageId(activeIds);
    var pathArr = getCurrentTreePath();
    // Терминальный узел — тот же, что и в processGeneticReflexes (getActiveTerminalNodeId), иначе branchId не совпадёт при аннулировании отложенного рефлекса.
    var terminalNodeId = typeof global.PerceptionTree_getActiveTerminalNodeId === 'function'
      ? global.PerceptionTree_getActiveTerminalNodeId()
      : global.PerceptionTree_getOrCreateTerminalNode(pathArr);
    if (!terminalNodeId) {
      // Формирование ЭП не должно мешать учёту семантической значимости — регистрируем экспозицию и при раннем выходе.
      var wellEarly = global.BadNormWell || 0;
      var emotionIdEarly = 0;
      if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
        emotionIdEarly = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
      }
      if (wellEarly && emotionIdEarly && fullId && typeof global.registerSemanticSample === 'function' && typeof global.getDiffImportance === 'function') {
        var zEarly = global.getDiffImportance();
        if (typeof zEarly === 'number' && !isNaN(zEarly)) global.registerSemanticSample(fullId, emotionIdEarly, zEarly);
      }
      if (global.EpisodicMemory_testMode && typeof global.show_alert === 'function') {
        var det = getStimulusDetailForAlert(fullId);
        global.show_alert('<b>Стимул:</b> ' + (det || 'ID=' + fullId) + '.<br><b>Почему не начат процесс ЭП:</b> не найдена или не создана ветка дерева восприятия. Ориентировочный рефлекс не вызван. Кадр ЭП не создается. Прогресс-бар периода ожидания не запускается.', 0);
      }
      return true;
    }

    // Обновляем информационную картину: текущая ветка и стимул.
    if (typeof global.Info_updateFromPerceptionTree === 'function') {
      global.Info_updateFromPerceptionTree();
    }
    if (typeof global.Info_updateFromStimulus === 'function') {
      global.Info_updateFromStimulus(fullId, 'external');
    }
    if (typeof global.Info_thinkingFlash === 'function') {
      global.Info_thinkingFlash();
    }

    var candidates = [];
    if (fullId) candidates.push(fullId);
    for (var j = 0; j < activeIds.length; j++) {
      var oneGlobalId = activeIds[j];
      var oneId = global.FinalImages_getOrCreateFinalImageId([oneGlobalId]);
      if (oneId && candidates.indexOf(oneId) === -1) candidates.push(oneId);
    }

    var result = findMostActualStimulus(candidates, lastORWinnerId);
    if (!result) {
      var wellNoRes = global.BadNormWell || 0;
      var emotionIdNoRes = 0;
      if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
        emotionIdNoRes = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
      }
      if (wellNoRes && emotionIdNoRes && fullId && typeof global.registerSemanticSample === 'function' && typeof global.getDiffImportance === 'function') {
        var zNoRes = global.getDiffImportance();
        if (typeof zNoRes === 'number' && !isNaN(zNoRes)) global.registerSemanticSample(fullId, emotionIdNoRes, zNoRes);
      }
      if (global.EpisodicMemory_testMode && typeof global.show_alert === 'function') {
        var det2 = getStimulusDetailForAlert(fullId);
        global.show_alert('<b>Стимул:</b> ' + (det2 || 'ID=' + fullId) + '.<br><b>Почему не начат процесс ЭП:</b> не выявлен наиболее актуальный стимул среди кандидатов. Ориентировочный рефлекс не вызван. Кадр ЭП не создается. Прогресс-бар периода ожидания не запускается.', 0);
      }
      return false;
    }

    var winnerId = result.winnerId;
    if (activeIds.length === 1 && fullId) {
      winnerId = fullId;
    }

    var well = global.BadNormWell || 0;
    var emotionId = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionId = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    var semWinner = getSemanticForConditions(winnerId, well, emotionId);
    var samplesCount = (semWinner && typeof semWinner.samplesCount === 'number') ? semWinner.samplesCount : 0;
    var wellKnown = (typeof global.OR_well_known === 'number' && global.OR_well_known >= 0) ? global.OR_well_known : OR_well_known;

    // Регистрируем экспозицию стимула при каждой активации, иначе samplesCount растёт только при срабатывании рефлекса
    // (напр. «Пение» без рефлекса никогда не увеличивал бы счётчик).
    if (well && emotionId && typeof global.registerSemanticSample === 'function' && typeof global.getDiffImportance === 'function') {
      var z = global.getDiffImportance();
      if (typeof z === 'number' && !isNaN(z)) {
        global.registerSemanticSample(winnerId, emotionId, z);
      }
    }
    // Обновляем счётчик после регистрации (он мог быть 0 до первого вызова registerSemanticSample).
    semWinner = getSemanticForConditions(winnerId, well, emotionId);
    samplesCount = (semWinner && typeof semWinner.samplesCount === 'number') ? semWinner.samplesCount : 0;

    // Второй проход ОР: после закрытия кадра по ответному стимулу — полный ориентировочный рефлекс и новый цикл ФО (без укороченной ветки ожидания).
    var resumeFullOr = !!global.OrientationReflex_resumeFullOrAfterWaitingResponse;
    if (resumeFullOr) {
      global.OrientationReflex_resumeFullOrAfterWaitingResponse = false;
      if (typeof global.Consciousness_getMainCycle === 'function' && typeof global.Consciousness_cycleAppendLog === 'function') {
        var orMain = global.Consciousness_getMainCycle();
        if (orMain && orMain.isMainCycle) {
          global.Consciousness_cycleAppendLog(orMain, 'ОР: второй полный проход после ответа оператора в периоде ожидания ЭП.', typeof global.cpuls === 'number' ? global.cpuls : 0);
        }
      }
    }

    // Сначала проверяем период ожидания: ответный стимул оператора не требует порога известности (OR_well_known).
    if (!resumeFullOr && typeof global.EpisodicMemory_isInWaitingPeriod === 'function' && global.EpisodicMemory_isInWaitingPeriod()) {
      if (typeof global.EpisodicMemory_closeLastFrameWithResponse === 'function') {
        global.EpisodicMemory_closeLastFrameWithResponse(winnerId);
      }
      // Ответ оператора получен — режим "игнорирование" снимается и детектор ответа обновляется.
      if (typeof global.InfoDet_updateOperatorIgnoring === 'function') {
        global.InfoDet_updateOperatorIgnoring(false);
      }
      if (typeof global.InfoDet_updateOperatorAnswer === 'function') {
        global.InfoDet_updateOperatorAnswer(true);
      }
      global.EpisodicMemory_skipConditionalReflexFormation = true;
      global.actual_stimul_ID = winnerId;
      var branchIdResponse = (typeof global.PerceptionTree_getActiveTerminalNodeId === 'function')
        ? global.PerceptionTree_getActiveTerminalNodeId() : 0;
      // Цепочка кадров ЭП: открываем новый кадр по ответному стимулу, затем безусловный второй проход ОР —
      // полный путь (hippocampus, orContext, stimuls_consciousness, save_episodic), новый цикл осмысления.
      if (typeof global.save_episodic_memory === 'function' && branchIdResponse) {
        global.save_episodic_memory(branchIdResponse, winnerId);
      }
      global.OrientationReflex_resumeFullOrAfterWaitingResponse = true;
      if (typeof global.Consciousness_getMainCycle === 'function' && typeof global.Consciousness_cycleAppendLog === 'function') {
        var orM2 = global.Consciousness_getMainCycle();
        if (orM2 && orM2.isMainCycle) {
          global.Consciousness_cycleAppendLog(orM2, 'ОР: ответ в ожидании — открыт кадр ЭП, запланирован повторный полный ОР.', typeof global.cpuls === 'number' ? global.cpuls : 0);
        }
      }
      return runOROnStimulusActivation();
    }

    // Порог известности только для начала нового процесса ОР/ЭП (не для ответного стимула в периоде ожидания).
    // В состоянии Плохо (well === 1) порог не применяем: всегда запускаем ОР и ФО, иначе при резком
    // ухудшении (например, кнопка «Наказание») новый цикл не создаётся из-за малого samplesCount для (Плохо, эмоция, стимул).
    // Второй проход после ответа в ожидании (resumeFullOr) порог также обходим — нужен полный ОР и новый ФО.
    var skipWellKnownInBadState = (well === 1);
    var skipWellKnownFoodWater = activeIdsIncludeFoodOrWaterRelief(activeIds);
    if (!resumeFullOr && !skipWellKnownInBadState && !skipWellKnownFoodWater && samplesCount < wellKnown) {
      if (global.EpisodicMemory_testMode && typeof global.show_alert === 'function') {
        var detWeak = (activeIds.length === 1 && typeof global.StimulusGlobalId_getName === 'function')
          ? (global.StimulusGlobalId_getName(activeIds[0]) || getStimulusDetailForAlert(winnerId))
          : getStimulusDetailForAlert(winnerId);
        global.show_alert('<b>Стимул:</b> ' + (detWeak || 'ID=' + winnerId) + '.<br><b>Почему не начат процесс ЭП:</b> опыт семантической значимости слабый (samplesCount=' + samplesCount + ' < ' + wellKnown + '). Ориентировочный рефлекс не вызван. Кадр ЭП не создается. Прогресс-бар периода ожидания не запускается.', 0);
      }
      return false;
    }

    lastORWinnerId = winnerId;
    global.actual_stimul_ID = winnerId;

    var hasSignificance = semWinner != null;

    if (typeof global.Hippocampus_holdStimulusFromOR === 'function') {
      global.Hippocampus_holdStimulusFromOR(winnerId);
    }

    setBlockedReflex(winnerId, undefined);
    if (typeof global.addBlockedReflexToQueue === 'function') {
      global.addBlockedReflexToQueue(terminalNodeId);
    }

    var activityID = (typeof global.FinalImages_getOrCreateFinalImageId === 'function' && activeIds && activeIds.length)
      ? (global.FinalImages_getOrCreateFinalImageId(activeIds) || 0) : 0;
    var pathArr = [well, emotionId, activityID, 0, 0, 0];
    var novelty = computeNovelty(winnerId, well, emotionId, pathArr);
    var significance = computeSignificance(winnerId, well, emotionId);
    var orContext = {
      actual_stimul_ID: winnerId,
      branchId: terminalNodeId,
      novelty: novelty,
      significance: significance,
      samplesCount: samplesCount,
      pathArr: pathArr,
      well: well,
      emotionId: emotionId,
      activityID: activityID,
      isResponseStimulus: false,
      /** Победитель уже выбран конкуренцией ОР — канал ФО не должен отменять запуск порогом «тишины мысли». */
      orientationReflexWinner: true
    };
    if (typeof global.stimuls_consciousness === 'function') {
      global.stimuls_consciousness(orContext);
    }
    // Кадр ЭП и прогресс-бар запускаем всегда при срабатывании ОР (если начался процесс сборки кадра).
    // Тестовый режим влияет только на пошаговые сообщения, не на наличие прогресс-бара.
    if (typeof global.save_episodic_memory === 'function') {
      global.save_episodic_memory(terminalNodeId, winnerId);
      if (global.EpisodicMemory_testMode && typeof global.show_alert === 'function') {
        var detStart = getStimulusDetailForAlert(winnerId);
        global.show_alert('<b>Стимул:</b> ' + (detStart || 'ID=' + winnerId) + '.<br><b>Результат:</b> начат процесс формирования кадра ЭП. Ожидание ответа или реакции. Прогресс-бар периода ожидания запущен.', 0);
      }
    }
    if (typeof global.PerceptionTree_updateActiveBranchView === 'function') {
      global.PerceptionTree_updateActiveBranchView();
    }
    return true;
  }

  global.OrientationReflex_runOnStimulusActivation = runOROnStimulusActivation;
  global.OrientationReflex_isReflexBlockedByOR = isReflexBlockedByOR;
  global.OrientationReflex_setBlockedReflex = setBlockedReflex;
  global.OrientationReflex_findMostActualStimulus = findMostActualStimulus;
  global.OrientationReflex_OR_HYSTERESIS = OR_HYSTERESIS;
  global.OrientationReflex_getOrReadinessForFinalImageId = getOrReadinessForFinalImageId;

  /**
   * Счётчик кликов по кнопке «Тест»: каждый следующий вызов передаёт бо́льшую значимость,
   * чтобы новый клик гарантированно прерывал текущий цикл (порог: currentThinkingImportance + 2).
   */
  var runTestNegativeInterruptCounter = 0;

  /**
   * Тестовый запуск прерывания цикла осмысления. Создаёт цикл с небольшой значимостью 5,
   * при каждом следующем клике значимость увеличивается (10, 15, 20…), чтобы каждый новый
   * клик давал новое прерывание (текущий главный уходит в фон, создаётся новый цикл).
   * Это не стимул, а жёсткий вызов осмысления для проверки диспетчера и стека фоновых.
   */
  function runTestNegativeInterrupt() {
    runTestNegativeInterruptCounter += 1;
    var significance = 5 + (runTestNegativeInterruptCounter - 1) * 5;
    var branchId = (typeof global.PerceptionTree_getActiveTerminalNodeId === 'function')
      ? global.PerceptionTree_getActiveTerminalNodeId()
      : 0;
    var ctx = {
      actual_stimul_ID: 0,
      branchId: branchId,
      well: 1,              // Плохо
      emotionId: 0,
      activityID: 0,
      significance: significance
    };
    if (typeof global.stimuls_consciousness === 'function') {
      global.stimuls_consciousness(ctx);
    }
  }

  global.runTestNegativeInterrupt = runTestNegativeInterrupt;
})(window);
