/* ОБРАЗЫ ЦЕЛИ
Теория.
Образ цели, по данным когнитивной нейронауки, возникает как обобщённая «модель желаемого состояния», извлечённая из эпизодических воспоминаний о связках «ситуация → действие → последствия» и переорганизованная системами контроля (ПФК) в форму, пригодную для планирования.
При повторении опыта гиппокамп строит не только пространственные, но и предсказательные карты переходов, где закодированы вероятности и ожидаемые последствия разных действий.
Обзор по RL и эпизодической памяти показывает, что люди и животные могут «прокручивать» прошлые эпизоды и комбинировать их, чтобы делать выводы о новых ситуациях — это функциональный аналог модель‑бейсд RL на эпизодическом представлении.
Лобные отделы (особенно дорсолатеральная и орбитофронтальная кора) по данным фМРТ и моделирования выступают как система управления, которая запрашивает у гиппокампа те эпизоды, которые релевантны текущей потребности/задаче, и модулирует, как они будут перекодированы.
При построении воображаемых будущих эпизодов люди сначала активируют обобщённое личное знание (семантический уровень), а потом подставляют эпизодические детали — по сути, рекомбинируют прошлые сцены в возможные будущие сценарии.
Из множества эпизодов с похожим итогом выделяется инвариантный «шаблон последствий»: что общего у всех удачных исходов в данной сфере.
ПФК абстрагирует этот шаблон от конкретных деталей эпизодов, оставляя «вектор» направления (какой результат должен быть достигнут и какие ограничения удерживать), что и есть образ цели. Этот образ хранится уже не как отдельный эпизод, а как более устойчивое семантико‑аффективное представление, связанное с личной значимостью (mPFC) и оценкой ценности (орбитофронтальная кора).
Образы разных потенциальных целей конкурируют за поведенческий контроль через сеть ценности (вентральный стриатум, орбитофронтальная кора), которая интегрирует ожидаемую награду, усилие, риск и задержку.
Симулированный образ должен получить мотивационный заряд, чтобы стать целью, а не просто фантазией.
1. Накопление: Гиппокамп и базальные ганглии фиксируют эпизоды с надежной связкой Действие → Последствие.
2. Абстракция: Префронтальная кора обобщает эпизоды в причинно-следственную модель мира.
3. Симуляция: Нейронные сети памяти рекомбинируют прошлый опыт, создавая сенсорный образ желаемого будущего состояния (Эпизодическое будущее мышление).
4. Валидация: Система вознаграждения оценивает этот образ на основе прошлого опыта удовлетворения потребностей. Если ценность высока, образ становится Целью.
5. Реализация: Исполнительные системы используют цель как референсную точку для планирования и коррекции действий.
Данные показывают, что если поврежден гиппокамп (нет эпизодической памяти), человек теряет способность формировать конкретные, личные цели на будущее, хотя может понимать социальные нормы. Это подтверждает, что образ цели неразрывно связан с реконструкцией эпизодов памяти, а не является чисто логической конструкцией.
Способность создавать новые цели из старых элементов — ключевая функция гиппокампа и префронтальной коры.
Реализация.
Создается поддержка массива образов цели следующим образом.
В основе - базовая гомеостатическая цель, если другая не активирована - ID витала, который нужно вернуть в норму. Эта самая простая цель, которая может использовать семантические модели понимания.
Жизненный опыт (перекрывает базовую) - ID образа цели, который содержит условие - ID ветки дерева (что позволяет делать условие все более общими если цель не достигается при точном соотвествии), ID образа восприятия, ID совершенного действия и значение эффекта, число опытов для усреднения эффекта. Фактически правило из эпизодов памяти или даже цепочка правил, приводящая к позитиву. Такие правила, как и базовая цель, реализуются на втором уровне осознания.
Произвольные цели - это искуссвтенно выстроенные цепочки правил, которые, предположительно, способны привести к позитиву. Так же содержит величину значимости эффекта (предположительную) и счетчик реализаций для усреднения эффекта.
Перед активацией произвольной цели система должна прогнать цепочку правил через модель мира и оценить суммарную ошибку предсказания с применением семантической модели. Всякий раз при попытке достигнуть такой цели, эффект корректируется.
Произвольная цель не должна создаваться «с нуля». Она должна валидироваться через проверку каждого звена цепочки на наличие подтвержденных правил из Уровня 2 (Жизненный опыт). Если звено цепочки не имеет эмпирического подтверждения, значимость всей цепочки должна резко падать (коэффициент ненадежности).
Если отклонение витального параметра превышает порог (аллостатическую нагрузку), система должна временно блокировать произвольные цели целью становится восстановление гомеостата. 
В систоянии Норма, если не получается выбрать или произвольно сформировать цель, то осознание переходит в пассивный режим.

Связь со вторым уровнем осмысления:
- В функции осмысления при переходе ко второму уровню в целевом режиме (проблема с автоматизмом) ставится цель — вызывается функция определения цели в данной ситуации (например Goals_determineGoalInSituation(context)).
- Новые образы цели формируются на втором уровне осмысления, при работе с эпизодами памяти (перебор/анализ кадров ЭП, выделение правил с позитивным эффектом и т.п.), а не при автоматическом закрытии кадра ЭП.

=== ПРЕДЛОЖЕНИЯ ПО РЕАЛИЗАЦИИ ===

1. Три типа целей и структуры данных
--------------------------------------
• Базовая (гомеостатическая) цель:
  - Не хранится в массиве; вычисляется на лету.
  - activeBaseGoalVitalId: number | null — ID витала, который нужно вернуть в норму.
  - Источник: при BadNormWell !== 2 по максимальному BadVitalsValue[vitalId] (или по importance)
    выбрать один витал-«мишень». Если BadNormWell === 2 и нет активной цели из опыта/произвольной —
    базовая цель не активируется (активна «норма»).

• Образ цели жизненного опыта (Life Experience Goal):
  - Массив global.GoalImagesLife = [] (или отдельный ключ в BEAST.persistentModules).
  - Цель трактуется как правило «в таких условиях → действие → позитив»: агрегаты (effect, count)
    и явное хранение происхождения из ЭП.
  - Элемент: {
      id: number,
      perceptionNodeId: number,   // ID ветки (терминального узла) дерева восприятия
      perceptionBranchId: number, // то же, что perceptionNodeId (явное имя для «ветки восприятия»)
      situationId: number,        // ID образа ситуации (дерево ситуаций), 0 — не привязано к ситуации
      stimulusImageId: number,    // образ восприятия (FinalImage)
      actionImageId: number,     // образ действия (ОД)
      effect: number,            // усреднённый эффект (как в ЭП)
      count: number,             // число опытов для усреднения
      usefulness: number,        // усреднённая полезность (отдельно от effect)
      usefulnessCount: number,  // число усреднений полезности
      lastUsedAt?: number,       // опционально — для приоритета/актуальности
      episodicRule: object|null, // один снимок правила с кадра ЭП (см. Goals_episodicRuleFromFrame)
      episodicRuleChain: array  // упорядоченная цепочка таких снимков — подряд идущие кадры ЭП,
                                // усиливающие ту же цель (индекс эпизода хранится в snapshot.episodeIndex)
    }
  - Формирование: при закрытии кадра ЭП с effect > 0 — создать или обновить запись по
    (perceptionNodeId, stimulusImageId, actionImageId, situationId): добавить effect в усреднение, count++.
  - Выбор активной цели: при conscience_level_2 по текущей ветке (и при необходимости по
    родительским узлам) искать GoalImagesLife с лучшим effect и подходящим условием (точное
    совпадение ветки или обобщённое по parentId).

• Произвольная цель (Voluntary Goal):
  - Массив global.GoalImagesVoluntary = [].
  - Элемент: {
      id: number,
      chain: Array<{ perceptionNodeId?, stimulusImageId?, actionImageId? }>,  // цепочка правил
      assumedEffect: number,     // предполагаемая значимость (корректируется по опыту)
      count: number,             // число попыток реализации для усреднения
      validated: boolean         // все звенья цепочки имеют подтверждение из GoalImagesLife/ЭП
    }
  - Валидация: перед активацией проверять каждое звено chain на наличие подтверждённого правила
    (кадр ЭП с effect > 0 или запись в GoalImagesLife). Если хотя бы одно звено без подтверждения —
    применять коэффициент ненадёжности (например, assumedEffect *= 0.2 или не выбирать цель).
  - Оценка: перед активацией «прогнать» цепочку через модель мира (текущее состояние дерева
    восприятия, семантика) и пересчитать суммарную ошибку предсказания; при каждой попытке
    реализации обновлять assumedEffect по фактическому effect.

2. Точки интеграции в существующий код
--------------------------------------
• prepare.js (или отдельный goals.js):
  - Функция getActiveBaseGoalVitalId(): при BadNormWell === 1 (Плохо) вернуть vitalId с макс.
    BadVitalsValue; при BadNormWell === 3 (Хорошо) можно не задавать базовую цель «вернуть в норму»
    или трактовать по ТЗ. При BadNormWell === 2 вернуть null.

• episodic_memory.js:
  - Образы цели в ЭП не создаются при закрытии кадра (closeLastFrame). Второй уровень осмысления
    при работе с эпизодами памяти (перебор Episodes, анализ effect) сам формирует и обновляет
    записи в GoalImagesLife (и при необходимости цепочки для произвольных целей).

• conscience_level_2.js:
  - При входе во второй уровень в целевом режиме (проблема с автоматизмом) первым делом
    вызывается функция определения цели в данной ситуации: Goals_determineGoalInSituation(context).
    Она возвращает/устанавливает текущую цель (базовая, из жизненного опыта или произвольная).
  - В начале: проверить allostatic block (см. п. 4). Если порог превышен — активная цель только
    базовая (восстановление гомеостаза), выход.
  - Выбор цели по приоритету: 1) базовая (если activeBaseGoalVitalId); 2) жизненный опыт —
    поиск по текущей ветке/родителям в GoalImagesLife, выбор с лучшим effect; 3) произвольная —
    только если validated и assumedEffect после коэффициента выше порога.
  - Реализация выбранной цели: для базовой — передать управление в существующие механизмы
    (виталы/контексты); для жизненного опыта — запустить actionImageId как при автоматизме
    (через runActionImage) и ждать оценки в ЭП; для произвольной — пошаговый запуск цепочки
    с проверкой после каждого шага.
  - Формирование новых образов цели: выполняется на втором уровне при работе с эпизодами
    памяти (анализ Episodes, выделение правил с позитивным эффектом, обобщение по веткам
    дерева), а не в closeLastFrame ЭП.
  - Если цель не выбрана и не сформирована (Норма, нет подходящих целей) — установить
    thinkingMode = 'passive' у главного цикла ФО и выйти.

• InfoPicture / informing.js:
  - Добавить поле currentGoalType: 'base' | 'life' | 'voluntary' | null и currentGoalId (или
    currentGoalVitalId для базы). Обновлять при смене цели в conscience_level_2.

3. Обобщение условия для целей жизненного опыта
----------------------------------------------
• Если при точном совпадении perceptionNodeId подходящей цели нет — искать по parentId узла
  дерева (PerceptionTree_getNode(branchId).parentId) и так рекурсивно до корня. Первая
  найденная запись в GoalImagesLife с effect > 0 и подходящим actionImageId даёт «более общее»
  правило. Это соответствует тексту «условие все более общие если цель не достигается».

4. Аллостатическая нагрузка и блок произвольных целей
----------------------------------------------------
• Ввести порог ALLOSTATIC_THRESHOLD (например, сумма BadVitalsValue по всем виталам или макс.
  значение). Если порог превышен — в conscience_level_2 не рассматривать произвольные цели и
  цели жизненного опыта, не связанные с восстановлением витала; активной считать только
  базовую цель (activeBaseGoalVitalId). Порог можно хранить в goals.js и сделать настраиваемым.

5. Порядок реализации
---------------------
a) Реализовать getActiveBaseGoalVitalId() и использование базовой цели в conscience_level_2
   (выбор «вернуть витал в норму» как единственной цели при Плохо и при превышении порога).
b) Функция Goals_determineGoalInSituation(context): вызывается при входе в conscience_level_2;
   определяет и устанавливает цель в данной ситуации (базовая / жизненный опыт / произвольная).
c) Образы цели жизненного опыта: формируются на втором уровне при работе с эпизодами (анализ
   Episodes, выделение правил с effect > 0, запись в GoalImagesLife). Сохранение/загрузка в
   BEAST.persistentModules.
d) В conscience_level_2: после определения цели — выбор и реализация (базовая / жизненный опыт
   по текущей ветке и родителям / произвольная); обновление InfoPicture (currentGoalType/currentGoalId).
e) Структура GoalImagesVoluntary + валидация цепочки по ЭП/GoalImagesLife; блок при
   превышении ALLOSTATIC_THRESHOLD; переход в passive при отсутствии выбранной цели в Норма.

6. Дополнительно
----------------
• Уточнить по ТЗ: при Хорошо (BadNormWell === 3) базовая цель «вернуть в норму» не ставится —
  тогда getActiveBaseGoalVitalId() возвращает ненулевое значение только при BadNormWell === 1.
• Цели жизненного опыта можно ограничить по count (например, не предлагать к выбору при
  count < 2) или по порогу effect, чтобы не активировать слабые правила.

*/

(function (global) {
  'use strict';

  /** Умолчательная цель смысла: ключ (perceptionNodeId=0, situationId=0, actionImageId=-1). */
  var GOAL_KIND_MEANING_DEFAULT = 'meaning_default';
  var GOALS_DEFAULT_MEANING_LABEL = 'что для меня означает это?';

  /** Умолчательная цель «Плачь»: perceptionNodeId=0, situationId=0, ОД с BasicActions Id=1 (Плачет). */
  var GOAL_KIND_CRY_DEFAULT = 'cry_default';
  var GOALS_DEFAULT_CRY_LABEL = 'Плачь';

  if (!Array.isArray(global.GoalImagesLife)) {
    global.GoalImagesLife = [];
  }

  var nextId = 1;
  /** Индекс: perceptionNodeId -> массив образов цели для этой ветки (для быстрого поиска). */
  var byPerceptionNodeId = Object.create(null);
  /** Уникальность по (perceptionNodeId, stimulusImageId, actionImageId) -> объект цели. */
  var byKey = Object.create(null);
  var goalsDirty = false;
  var lastGoalsSavedSig = '';

  function lifeGoalKey(perceptionNodeId, stimulusImageId, actionImageId, situationId) {
    return (perceptionNodeId || 0) + '|' + (stimulusImageId || 0) + '|' + (actionImageId || 0) +
      '|' + (situationId || 0);
  }

  function normalizeLifeGoalRecord(g) {
    if (!g) return g;
    var pn = g.perceptionNodeId != null ? g.perceptionNodeId : (g.perceptionBranchId || 0);
    g.perceptionNodeId = pn;
    g.perceptionBranchId = pn;
    if (g.situationId == null) g.situationId = 0;
    if (g.usefulness == null) g.usefulness = 0;
    if (g.usefulnessCount == null) g.usefulnessCount = 0;
    if (g.episodicRule !== null && g.episodicRule !== undefined && typeof g.episodicRule !== 'object') {
      g.episodicRule = null;
    }
    if (!Array.isArray(g.episodicRuleChain)) g.episodicRuleChain = [];
    if (g.goalKind == null) g.goalKind = '';
    if (g.goalLabel == null) g.goalLabel = '';
    return g;
  }

  /**
   * Один «снимок правила» из кадра ЭП: условия + действие + исход кадра (+ ответ оператора при наличии).
   * Не включает служебные поля кадра (_goalsProcessed и т.д.).
   * @param {Object} frame
   * @param {number} [episodeIndex] — индекс в global.Episodes для сборки цепочки подряд идущих кадров
   * @returns {Object|null}
   */
  function episodicRuleFromFrame(frame, episodeIndex) {
    if (!frame) return null;
    var r = {
      perceptionNodeId: frame.perceptionNodeId != null ? frame.perceptionNodeId : 0,
      stimulusImageId: frame.stimulusImageId != null ? frame.stimulusImageId : 0,
      situationId: frame.situationId != null ? frame.situationId : 0,
      actionImageId: frame.actionImageId != null ? frame.actionImageId : 0,
      effect: typeof frame.effect === 'number' && !isNaN(frame.effect) ? frame.effect : null,
      responseStimulusImageId: frame.responseStimulusImageId != null ? frame.responseStimulusImageId : 0
    };
    if (episodeIndex != null && typeof episodeIndex === 'number' && !isNaN(episodeIndex)) {
      r.episodeIndex = episodeIndex;
    }
    return r;
  }

  function cloneEpisodicRule(rule) {
    if (!rule || typeof rule !== 'object') return null;
    try {
      return JSON.parse(JSON.stringify(rule));
    } catch (e) {
      return episodicRuleFromFrame(rule, rule.episodeIndex);
    }
  }

  /**
   * Обновить снимок правила и при возможности — цепочку: новое звено только если episodeIndex
   * на 1 больше индекса последнего звена (подряд идущие кадры в Episodes).
   * Пустая цепочка (старые сохранения): первое обновление добавляет текущий снимок как первое звено.
   */
  function mergeEpisodicRuleChainOnUpdate(goal, frame, meta) {
    if (!goal || !frame) return;
    var snap = episodicRuleFromFrame(frame, meta && typeof meta.episodeIndex === 'number' ? meta.episodeIndex : undefined);
    if (!snap) return;
    goal.episodicRule = snap;
    if (!Array.isArray(goal.episodicRuleChain)) goal.episodicRuleChain = [];
    if (goal.episodicRuleChain.length === 0) {
      goal.episodicRuleChain.push(cloneEpisodicRule(snap));
      return;
    }
    var idx = meta && typeof meta.episodeIndex === 'number' ? meta.episodeIndex : null;
    var last = goal.episodicRuleChain[goal.episodicRuleChain.length - 1];
    if (idx == null || typeof last !== 'object' || last == null || typeof last.episodeIndex !== 'number') {
      return;
    }
    if (idx === last.episodeIndex + 1) {
      goal.episodicRuleChain.push(cloneEpisodicRule(snap));
    }
  }

  /** Добавить образец полезности в скользящее среднее цели. */
  function mergeUsefulnessSample(goal, sample) {
    if (!goal || typeof sample !== 'number' || isNaN(sample)) return;
    var oc = goal.usefulnessCount || 0;
    var n = oc + 1;
    goal.usefulness = ((goal.usefulness || 0) * oc + sample) / n;
    goal.usefulnessCount = n;
  }

  function rebuildIndex() {
    byPerceptionNodeId = Object.create(null);
    byKey = Object.create(null);
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return;
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g || g.perceptionNodeId == null) continue;
      var key = String(g.perceptionNodeId);
      if (!byPerceptionNodeId[key]) byPerceptionNodeId[key] = [];
      byPerceptionNodeId[key].push(g);
      normalizeLifeGoalRecord(g);
      var k = lifeGoalKey(g.perceptionNodeId, g.stimulusImageId, g.actionImageId, g.situationId);
      byKey[k] = g;
    }
  }

  function getNextId() {
    var max = 0;
    var arr = global.GoalImagesLife;
    for (var i = 0; i < (arr && arr.length) || 0; i++) {
      if (arr[i] && arr[i].id > max) max = arr[i].id;
    }
    return max + 1;
  }

  /**
   * Добавить или обновить образ цели жизненного опыта по кадру эпизода (effect > 0).
   * Уникальность по (perceptionNodeId, stimulusImageId, actionImageId, situationId). Усреднение effect по count.
   * В цель записывается episodicRule (последний снимок кадра) и при нумерации эпизодов — наращивается episodicRuleChain.
   * @param {Object} frame — кадр ЭП
   * @param {Object} [meta] — { episodeIndex: number } индекс кадра в global.Episodes для цепочки подряд идущих правил
   * @returns {number} id образа цели (существующего или нового)
   */
  function addOrUpdateLifeGoalFromFrame(frame, meta) {
    if (!frame || typeof frame.effect !== 'number' || frame.effect <= 0) return 0;
    var perceptionNodeId = frame.perceptionNodeId != null ? frame.perceptionNodeId : 0;
    var stimulusImageId = frame.stimulusImageId != null ? frame.stimulusImageId : 0;
    var actionImageId = frame.actionImageId != null ? frame.actionImageId : 0;
    var situationId = frame.situationId != null ? frame.situationId : 0;
    if (!perceptionNodeId || !actionImageId) return 0;

    var k = lifeGoalKey(perceptionNodeId, stimulusImageId, actionImageId, situationId);
    var existing = byKey[k];
    if (existing) {
      var n = (existing.count || 0) + 1;
      existing.effect = ((existing.effect || 0) * (existing.count || 0) + frame.effect) / n;
      existing.count = n;
      mergeUsefulnessSample(existing, frame.usefulness);
      existing.lastUsedAt = Date.now();
      normalizeLifeGoalRecord(existing);
      mergeEpisodicRuleChainOnUpdate(existing, frame, meta || {});
      goalsDirty = true;
      return existing.id;
    }

    var id = getNextId();
    var snap = episodicRuleFromFrame(frame, meta && typeof meta.episodeIndex === 'number' ? meta.episodeIndex : undefined);
    var goal = {
      id: id,
      perceptionNodeId: perceptionNodeId,
      perceptionBranchId: perceptionNodeId,
      situationId: situationId,
      stimulusImageId: stimulusImageId,
      actionImageId: actionImageId,
      effect: frame.effect,
      count: 1,
      usefulness: 0,
      usefulnessCount: 0,
      lastUsedAt: Date.now(),
      episodicRule: snap,
      episodicRuleChain: snap ? [cloneEpisodicRule(snap)] : []
    };
    normalizeLifeGoalRecord(goal);
    mergeUsefulnessSample(goal, frame.usefulness);
    global.GoalImagesLife.push(goal);
    rebuildIndex();
    goalsDirty = true;
    return id;
  }

  /**
   * Явно добавить в цепочку правил образа цели ещё один снимок (порядок задаётся вызывающим кодом).
   * Обновляет episodicRule на этот снимок.
   * @param {number} goalId
   * @param {Object} frame — кадр ЭП или объект полей как у episodicRuleFromFrame
   * @param {number} [episodeIndex]
   * @returns {boolean}
   */
  function appendLifeGoalEpisodicRuleLink(goalId, frame, episodeIndex) {
    var g = getGoalById(goalId);
    if (!g || !frame) return false;
    var snap = episodicRuleFromFrame(frame, episodeIndex);
    if (!snap) return false;
    normalizeLifeGoalRecord(g);
    g.episodicRule = snap;
    g.episodicRuleChain.push(cloneEpisodicRule(snap));
    goalsDirty = true;
    return true;
  }

  /**
   * Усреднить полезность образа цели (GoalImagesLife) по одному образцу.
   * @param {number} goalId
   * @param {number} sample
   */
  function mergeUsefulnessSampleForGoalId(goalId, sample) {
    var g = getGoalById(goalId);
    if (!g || typeof sample !== 'number' || isNaN(sample)) return;
    mergeUsefulnessSample(g, sample);
    goalsDirty = true;
  }

  /**
   * Корректировка usefulness после закрытия кадра ожидания по effect эпизода (ответ оператора).
   * При count === 1 usefulness приравнивается к эффекту (без усреднения с прошлым).
   * При count &gt; 1 — mergeUsefulnessSample (накапливается среднее).
   * При таймауте ожидания без ответа функция не вызывается (пайплайн ур.2 не запускается).
   * @param {number} goalId
   * @param {number} episodeEffect
   */
  function applyWaitingPeriodEffectToGoalUsefulness(goalId, episodeEffect) {
    var g = getGoalById(goalId);
    if (!g || typeof episodeEffect !== 'number' || isNaN(episodeEffect)) return;
    if ((g.count || 0) === 1) {
      g.usefulness = episodeEffect;
      g.usefulnessCount = 1;
    } else {
      mergeUsefulnessSample(g, episodeEffect);
    }
    goalsDirty = true;
  }

  /**
   * Получить образ цели жизненного опыта по id.
   * @param {number} id
   * @returns {Object|null}
   */
  function getGoalById(id) {
    if (id == null || id === 0) return null;
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return null;
    for (var i = 0; i < arr.length; i++) {
      if (arr[i] && arr[i].id === id) return arr[i];
    }
    return null;
  }

  function lifeGoalMatchesSituationFilter(g, situationFilter) {
    if (situationFilter == null || situationFilter === 0) return true;
    return (g.situationId || 0) === situationFilter;
  }

  /**
   * Найти лучший образ цели жизненного опыта для ветки: сначала точное совпадение,
   * затем по цепочке родительских узлов. Возвращает цель с максимальным effect (count >= minCount).
   * @param {number} branchId — текущий терминальный узел дерева восприятия
   * @param {number} [minCount=1] — минимальное число опытов для учёта
   * @param {number} [situationId=0] — если 0, ситуация не фильтрует; иначе только цели с этим situationId
   * @returns {Object|null}
   */
  function getBestLifeGoalForBranch(branchId, minCount, situationId) {
    if (branchId == null || branchId === 0) return null;
    minCount = minCount != null ? minCount : 1;
    var situationFilter = situationId != null ? situationId : 0;
    var getNode = global.PerceptionTree_getNode;
    if (typeof getNode !== 'function') return null;

    var nodeId = branchId;
    var best = null;
    var bestEffect = -Infinity;

    while (nodeId) {
      var list = byPerceptionNodeId[String(nodeId)];
      if (Array.isArray(list)) {
        for (var i = 0; i < list.length; i++) {
          var g = list[i];
          if (!g || (g.count || 0) < minCount || typeof g.effect !== 'number') continue;
          if (!lifeGoalMatchesSituationFilter(g, situationFilter)) continue;
          if (g.effect > bestEffect) {
            bestEffect = g.effect;
            best = g;
          }
        }
        if (best) return best;
      }
      var node = getNode(nodeId);
      nodeId = node && typeof node.parentId === 'number' && node.parentId !== 0 ? node.parentId : null;
    }
    return null;
  }

  /**
   * Поиск образа цели жизненного опыта по ветке восприятия и (опционально) образу ситуации.
   * При situationId === 0 условие по ситуации не применяется (подходят любые situationId у записей).
   * При ненулевом situationId учитываются только цели с тем же situationId.
   * Из подходящих выбирается запись с максимальным effect (при равенстве — первая по порядку в массиве).
   * @param {number} perceptionNodeId
   * @param {number} situationId
   * @returns {Object|null}
   */
  function findLifeGoalByPerceptionAndSituation(perceptionNodeId, situationId) {
    perceptionNodeId = parseInt(perceptionNodeId, 10) || 0;
    situationId = parseInt(situationId, 10) || 0;
    if (!perceptionNodeId) return null;
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr) || !arr.length) return null;
    var best = null;
    var bestEffect = -Infinity;
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g) continue;
      var pn = g.perceptionNodeId != null ? g.perceptionNodeId : (g.perceptionBranchId || 0);
      if (pn !== perceptionNodeId) continue;
      if (situationId !== 0 && (g.situationId || 0) !== situationId) continue;
      if (typeof g.effect !== 'number') continue;
      if (g.effect > bestEffect) {
        bestEffect = g.effect;
        best = g;
      }
    }
    return best;
  }

  function goalUsefulnessPositive(g) {
    return g && typeof g.usefulness === 'number' && !isNaN(g.usefulness) && g.usefulness > 0;
  }

  /**
   * Образ цели жизненного опыта с положительной полезностью (режим выживания, ур.2).
   * 1) Сначала perceptionNodeId и situationId (если situationId === 0 — только цели с situationId 0).
   * 2) Иначе любая цель с тем же perceptionNodeId.
   * Из кандидатов — максимум usefulness (при равенстве — больший effect).
   */
  function findBestLifeGoalPositiveUsefulness(perceptionNodeId, situationId) {
    perceptionNodeId = parseInt(perceptionNodeId, 10) || 0;
    situationId = parseInt(situationId, 10) || 0;
    if (!perceptionNodeId) return null;
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr) || !arr.length) return null;
    function pickBest(filterSit) {
      var best = null;
      var bestU = -Infinity;
      for (var i = 0; i < arr.length; i++) {
        var g = arr[i];
        if (!g || !goalUsefulnessPositive(g)) continue;
        var pn = g.perceptionNodeId != null ? g.perceptionNodeId : (g.perceptionBranchId || 0);
        if (pn !== perceptionNodeId) continue;
        var gs = g.situationId != null ? g.situationId : 0;
        if (!filterSit(gs)) continue;
        var u = g.usefulness;
        var eff = typeof g.effect === 'number' ? g.effect : 0;
        var bestEff = best && typeof best.effect === 'number' ? best.effect : 0;
        if (u > bestU || (u === bestU && eff > bestEff)) {
          bestU = u;
          best = g;
        }
      }
      return best;
    }
    var tier1 = pickBest(function (gs) {
      return situationId === 0 ? gs === 0 : gs === situationId;
    });
    if (tier1) return tier1;
    return pickBest(function () { return true; });
  }

  /**
   * Базовая гомеостатическая цель: ID витала, который нужно вернуть в норму.
   * Только при BadNormWell === 1 (Плохо); при Норма/Хорошо возвращает 0.
   * @returns {number} vitalId с максимальным BadVitalsValue или 0
   */
  function getActiveBaseGoalVitalId() {
    if (global.BadNormWell !== 1) return 0;
    var badVitals = global.BadVitalsValue;
    if (!badVitals || typeof badVitals !== 'object') return 0;
    var vitalsArr = global.VitalsArr;
    if (!Array.isArray(vitalsArr) || !vitalsArr.length) return 0;
    var maxK = 0;
    var vitalId = 0;
    for (var v = 0; v < vitalsArr.length; v++) {
      var vid = vitalsArr[v].Id;
      var k = badVitals[vid] || badVitals[String(vid)] || 0;
      if (k > maxK) {
        maxK = k;
        vitalId = vid;
      }
    }
    return vitalId;
  }

  function contextSignificance(ctx) {
    if (!ctx) return 0;
    if (typeof ctx.significance === 'number' && !isNaN(ctx.significance)) return Math.abs(ctx.significance);
    var sc = ctx.sourceContext || (ctx.sourceCycle && ctx.sourceCycle.context);
    if (sc && typeof sc.significance === 'number' && !isNaN(sc.significance)) return Math.abs(sc.significance);
    return 0;
  }

  function actualStimulusIdFromContext(ctx) {
    if (!ctx) return 0;
    var a = ctx.actualId != null ? ctx.actualId : ctx.actual_stimul_ID;
    if (a != null) {
      var n = parseInt(a, 10);
      return isNaN(n) ? 0 : n;
    }
    var sc = ctx.sourceContext || (ctx.sourceCycle && ctx.sourceCycle.context);
    if (sc && sc.actual_stimul_ID != null) {
      var n2 = parseInt(sc.actual_stimul_ID, 10);
      return isNaN(n2) ? 0 : n2;
    }
    return 0;
  }

  /**
   * Запись умолчательной цели «что для меня означает это?» (если ещё нет).
   * Ключ: perceptionNodeId=0, situationId=0, actionImageId=-1, stimulusImageId=0.
   */
  function findDefaultMeaningGoal() {
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return null;
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g) continue;
      if (g.goalKind === GOAL_KIND_MEANING_DEFAULT) return normalizeLifeGoalRecord(g);
      var pn = g.perceptionNodeId != null ? g.perceptionNodeId : 0;
      var sn = g.situationId != null ? g.situationId : 0;
      var ai = g.actionImageId != null ? g.actionImageId : 0;
      if (pn === 0 && sn === 0 && ai === -1) return normalizeLifeGoalRecord(g);
    }
    return null;
  }

  function ensureDefaultMeaningGoal() {
    if (findDefaultMeaningGoal()) return;
    var id = getNextId();
    var goal = {
      id: id,
      perceptionNodeId: 0,
      perceptionBranchId: 0,
      situationId: 0,
      stimulusImageId: 0,
      actionImageId: -1,
      effect: 0,
      count: 1,
      usefulness: 0,
      usefulnessCount: 0,
      goalKind: GOAL_KIND_MEANING_DEFAULT,
      goalLabel: GOALS_DEFAULT_MEANING_LABEL,
      episodicRule: null,
      episodicRuleChain: []
    };
    normalizeLifeGoalRecord(goal);
    global.GoalImagesLife.push(goal);
    rebuildIndex();
    goalsDirty = true;
  }

  function findDefaultCryGoal() {
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return null;
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g) continue;
      if (g.goalKind === GOAL_KIND_CRY_DEFAULT) return normalizeLifeGoalRecord(g);
    }
    return null;
  }

  /**
   * Образ цели «Плачь» (если ещё нет): ОД для finalImageId=0 и actionIds=[1] (BasicActions «Плачет»).
   * Требует загруженный модуль actions_image.js (ActionImages_getOrCreateActionImageId).
   */
  function ensureDefaultCryGoal() {
    if (typeof global.ActionImages_getOrCreateActionImageId !== 'function') return;
    var cryOd = global.ActionImages_getOrCreateActionImageId(0, [1], '');
    if (!cryOd) return;
    var existing = findDefaultCryGoal();
    if (existing) {
      if (existing.actionImageId !== cryOd) {
        existing.actionImageId = cryOd;
        rebuildIndex();
        goalsDirty = true;
      }
      return;
    }
    var id = getNextId();
    var goal = {
      id: id,
      perceptionNodeId: 0,
      perceptionBranchId: 0,
      situationId: 0,
      stimulusImageId: 0,
      actionImageId: cryOd,
      effect: 0,
      count: 1,
      usefulness: 0,
      usefulnessCount: 0,
      goalKind: GOAL_KIND_CRY_DEFAULT,
      goalLabel: GOALS_DEFAULT_CRY_LABEL,
      episodicRule: null,
      episodicRuleChain: []
    };
    normalizeLifeGoalRecord(goal);
    global.GoalImagesLife.push(goal);
    rebuildIndex();
    goalsDirty = true;
  }

  function getDefaultCryGoalId() {
    var g = findDefaultCryGoal();
    return g && g.id ? g.id : 0;
  }

  function isDefaultCryGoalId(goalId) {
    if (goalId == null || goalId === 0) return false;
    var g = getGoalById(goalId);
    if (!g) return false;
    return g.goalKind === GOAL_KIND_CRY_DEFAULT;
  }

  function isDefaultMeaningGoalId(id) {
    if (id == null || id === 0) return false;
    var g = getGoalById(id);
    if (!g) return false;
    if (g.goalKind === GOAL_KIND_MEANING_DEFAULT) return true;
    var pn = g.perceptionNodeId != null ? g.perceptionNodeId : 0;
    var sn = g.situationId != null ? g.situationId : 0;
    return pn === 0 && sn === 0 && g.actionImageId === -1;
  }

  function getDefaultMeaningGoalId() {
    var g = findDefaultMeaningGoal();
    return g && g.id ? g.id : 0;
  }

  /**
   * Установить текущую цель в инфокартине (currentGoalType, currentGoalId, currentGoalVitalId).
   */
  function setCurrentGoalInInfoPicture(goalType, goalId, vitalId) {
    var info = global.InfoPicture;
    if (!info) return;
    info.currentGoalType = goalType || null;
    info.currentGoalId = goalId != null ? goalId : 0;
    info.currentGoalVitalId = vitalId != null ? vitalId : 0;
    if (typeof global.Info_updateView === 'function') {
      global.Info_updateView();
    }
  }

  /**
   * Определение цели в данной ситуации (первый шаг итерации второго уровня в целевом режиме).
   * Приоритет: 1) базовая (витал при Плохо); 2) образ из жизненного опыта по ветке/родителям;
   * 3) при значимости стимула &gt; 3 и наличии актуального стимула — умолчательная цель «значение» (meaning);
   * 4) иначе сброс.
   */
  function Goals_determineGoalInSituation(context) {
    if (!context) return;

    var baseVitalId = getActiveBaseGoalVitalId();
    if (baseVitalId) {
      setCurrentGoalInInfoPicture('base', 0, baseVitalId);
      return;
    }

    var branchId = context.branchId != null ? context.branchId : 0;
    var sitId = 0;
    if (global.InfoPicture && typeof global.InfoPicture.situationId === 'number') {
      sitId = global.InfoPicture.situationId || 0;
    }
    var lifeGoal = getBestLifeGoalForBranch(branchId, 1, sitId);
    if (lifeGoal && lifeGoal.id) {
      setCurrentGoalInInfoPicture('life', lifeGoal.id, 0);
      return;
    }

    ensureDefaultMeaningGoal();
    var stim = actualStimulusIdFromContext(context);
    var sig = contextSignificance(context);
    var meaningG = findDefaultMeaningGoal();
    if (meaningG && meaningG.id && stim > 0 && sig > 3) {
      setCurrentGoalInInfoPicture('meaning', meaningG.id, 0);
      return;
    }

    setCurrentGoalInInfoPicture(null, 0, 0);
  }

  /**
   * Поиск решения в эпизодической памяти: перебор кадров, вызов Goals_considerNewGoalFromEpisode.
   */
  function Goals_runEpisodicSearch(context) {
    if (!context) return;
    var episodes = global.Episodes;
    if (!Array.isArray(episodes) || !episodes.length) return;
    var limit = Math.min(50, episodes.length);
    for (var i = episodes.length - 1; i >= 0 && episodes.length - i <= limit; i--) {
      var frame = episodes[i];
      if (frame) Goals_considerNewGoalFromEpisode(frame, context, i);
    }
  }

  /**
   * Рассмотрение кадра эпизода: при effect > 0 и наличии полей — создать/обновить GoalImagesLife.
   * Каждый кадр учитывается один раз (по флагу _goalsProcessed), чтобы не сохранять цели каждый пульс.
   * @param {number} [episodeIndex] — индекс в Episodes[] для episodicRuleChain (подряд идущие кадры).
   */
  function Goals_considerNewGoalFromEpisode(frame, context, episodeIndex) {
    if (!frame) return;
    if (frame._goalsProcessed) return;
    addOrUpdateLifeGoalFromFrame(frame, episodeIndex != null && typeof episodeIndex === 'number'
      ? { episodeIndex: episodeIndex }
      : null);
    frame._goalsProcessed = true;
  }

  /**
   * Пассивный режим: семантическая обработка моделей понимания; новые образы ситуаций — в situatioms_tree.
   */
  function PassiveMode_processUnderstandingModels() {
    if (typeof global.Consciousness_getMainCycleThinkingMode !== 'function' ||
        global.Consciousness_getMainCycleThinkingMode() !== 'passive') return;
    if (typeof global.SituationsTree_considerNewSituationFromUnderstanding === 'function') {
      global.SituationsTree_considerNewSituationFromUnderstanding();
    }
  }

  /**
   * Вызвать при исчерпании сценариев пассивного режима: понижает значимость фоновых циклов ФО на 50%
   * (оперативная память о прошлых мышлениях).
   */
  function PassiveMode_notifyScenariosExhausted() {
    if (typeof global.Consciousness_decayBackgroundOperativeMemory === 'function') {
      global.Consciousness_decayBackgroundOperativeMemory(0.5);
    }
  }

  // --- Сериализация и восстановление ---
  function serializeState() {
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return { goals: [], nextId: 1 };
    var list = [];
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g) continue;
      var chain = [];
      if (Array.isArray(g.episodicRuleChain)) {
        for (var c = 0; c < g.episodicRuleChain.length; c++) {
          var link = cloneEpisodicRule(g.episodicRuleChain[c]);
          if (link) chain.push(link);
        }
      }
      list.push({
        id: g.id,
        perceptionNodeId: g.perceptionNodeId,
        perceptionBranchId: g.perceptionBranchId != null ? g.perceptionBranchId : (g.perceptionNodeId || 0),
        situationId: g.situationId != null ? g.situationId : 0,
        stimulusImageId: g.stimulusImageId,
        actionImageId: g.actionImageId,
        effect: g.effect != null ? g.effect : 0,
        count: g.count != null ? g.count : 0,
        usefulness: g.usefulness != null ? g.usefulness : 0,
        usefulnessCount: g.usefulnessCount != null ? g.usefulnessCount : 0,
        lastUsedAt: g.lastUsedAt || 0,
        goalKind: g.goalKind || '',
        goalLabel: g.goalLabel || '',
        episodicRule: g.episodicRule != null ? cloneEpisodicRule(g.episodicRule) : null,
        episodicRuleChain: chain
      });
    }
    return { goals: list, nextId: getNextId() };
  }

  /** Состояние без lastUsedAt — для сравнения подписи, чтобы не сохранять каждый пульс при обновлении только времени. */
  function serializeStateForSig() {
    var arr = global.GoalImagesLife;
    if (!Array.isArray(arr)) return { goals: [], nextId: 1 };
    var list = [];
    for (var i = 0; i < arr.length; i++) {
      var g = arr[i];
      if (!g) continue;
      var chainSig = [];
      if (Array.isArray(g.episodicRuleChain)) {
        for (var c2 = 0; c2 < g.episodicRuleChain.length; c2++) {
          var lk = cloneEpisodicRule(g.episodicRuleChain[c2]);
          if (lk) chainSig.push(lk);
        }
      }
      list.push({
        id: g.id,
        perceptionNodeId: g.perceptionNodeId,
        perceptionBranchId: g.perceptionBranchId != null ? g.perceptionBranchId : (g.perceptionNodeId || 0),
        situationId: g.situationId != null ? g.situationId : 0,
        stimulusImageId: g.stimulusImageId,
        actionImageId: g.actionImageId,
        effect: g.effect != null ? g.effect : 0,
        count: g.count != null ? g.count : 0,
        usefulness: g.usefulness != null ? g.usefulness : 0,
        usefulnessCount: g.usefulnessCount != null ? g.usefulnessCount : 0,
        goalKind: g.goalKind || '',
        goalLabel: g.goalLabel || '',
        episodicRule: g.episodicRule != null ? cloneEpisodicRule(g.episodicRule) : null,
        episodicRuleChain: chainSig
      });
    }
    return { goals: list, nextId: getNextId() };
  }

  function applyState(state) {
    if (!state || !Array.isArray(state.goals)) {
      global.GoalImagesLife = [];
      rebuildIndex();
      return;
    }
    global.GoalImagesLife = [];
    for (var i = 0; i < state.goals.length; i++) {
      var s = state.goals[i];
      if (!s || s.id == null) continue;
      var pn = s.perceptionNodeId != null ? s.perceptionNodeId : (s.perceptionBranchId != null ? s.perceptionBranchId : 0);
      var restChain = [];
      if (Array.isArray(s.episodicRuleChain)) {
        for (var rc = 0; rc < s.episodicRuleChain.length; rc++) {
          var rcl = cloneEpisodicRule(s.episodicRuleChain[rc]);
          if (rcl) restChain.push(rcl);
        }
      }
      var actId = 0;
      if (typeof s.actionImageId === 'number' && !isNaN(s.actionImageId)) actId = s.actionImageId;
      else if (s.actionImageId != null) {
        var ap = parseInt(s.actionImageId, 10);
        actId = isNaN(ap) ? 0 : ap;
      }
      global.GoalImagesLife.push(normalizeLifeGoalRecord({
        id: s.id,
        perceptionNodeId: pn,
        perceptionBranchId: s.perceptionBranchId != null ? s.perceptionBranchId : pn,
        situationId: s.situationId != null ? s.situationId : 0,
        stimulusImageId: s.stimulusImageId != null ? s.stimulusImageId : 0,
        actionImageId: actId,
        effect: typeof s.effect === 'number' ? s.effect : 0,
        count: (s.count != null && s.count >= 0) ? s.count : 0,
        usefulness: typeof s.usefulness === 'number' ? s.usefulness : 0,
        usefulnessCount: (s.usefulnessCount != null && s.usefulnessCount >= 0) ? s.usefulnessCount : 0,
        lastUsedAt: s.lastUsedAt || 0,
        goalKind: s.goalKind != null ? s.goalKind : '',
        goalLabel: s.goalLabel != null ? s.goalLabel : '',
        episodicRule: s.episodicRule != null ? cloneEpisodicRule(s.episodicRule) : null,
        episodicRuleChain: restChain
      }));
    }
    nextId = (typeof state.nextId === 'number' && state.nextId > 0) ? state.nextId : getNextId();
    rebuildIndex();
    ensureDefaultMeaningGoal();
    ensureDefaultCryGoal();
    lastGoalsSavedSig = JSON.stringify(serializeStateForSig());
    goalsDirty = false;
  }

  // IndexedDB (отдельная БД по аналогии с автоматизмами)
  var db = null;
  var DB_NAME = 'BEAST_Goals_DB';
  var DB_VERSION = 1;
  var STORE = 'goals_state';
  var KEY = 'goals_singleton';

  function openDb() {
    if (!global.indexedDB || !global.IndexedDBModule) return Promise.reject(new Error('IndexedDB не доступен'));
    if (db) return Promise.resolve(db);
    return global.IndexedDBModule.openDatabase({
      dbName: DB_NAME,
      version: DB_VERSION,
      stores: [{ name: STORE, options: { keyPath: 'id' } }]
    }).then(function (d) {
      db = d;
      return d;
    });
  }

  function saveToIndexedDB(state) {
    if (!global.IndexedDBModule) return;
    state = state || serializeState();
    function put(database) {
      return global.IndexedDBModule.put({
        db: database,
        storeName: STORE,
        value: { id: KEY, state: state, ts: Date.now() }
      }).catch(function () {});
    }
    if (db) put(db);
    else openDb().then(put).catch(function () {});
  }

  function loadFromIndexedDB() {
    if (!global.IndexedDBModule) return;
    openDb()
      .then(function (d) {
        return global.IndexedDBModule.get({ db: d, storeName: STORE, key: KEY });
      })
      .then(function (record) {
        if (record && record.state) {
          applyState(record.state);
          lastGoalsSavedSig = JSON.stringify(serializeStateForSig());
        }
      })
      .catch(function () {});
  }

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!goalsDirty) return;
      goalsDirty = false;
      var state = serializeState();
      var sig = JSON.stringify(serializeStateForSig());
      if (sig === lastGoalsSavedSig) return;
      lastGoalsSavedSig = sig;
      saveToIndexedDB(state);
    });
  }

  function clearAllLifeGoals() {
    global.GoalImagesLife = [];
    rebuildIndex();
    nextId = 1;
    goalsDirty = true;
    if (typeof global.TargetRuleStack_clear === 'function') global.TargetRuleStack_clear();
    var info = global.InfoPicture;
    if (info && (info.currentGoalType === 'life' || info.currentGoalType === 'meaning')) {
      info.currentGoalType = null;
      info.currentGoalId = 0;
    }
    if (info) info.usedRule = 0;
    if (typeof global.Info_updateView === 'function') global.Info_updateView();
    ensureDefaultMeaningGoal();
    ensureDefaultCryGoal();
  }

  rebuildIndex();
  ensureDefaultMeaningGoal();
  ensureDefaultCryGoal();
  loadFromIndexedDB();

  global.GoalImagesLife = global.GoalImagesLife;
  global.Goals_determineGoalInSituation = Goals_determineGoalInSituation;
  global.Goals_runEpisodicSearch = Goals_runEpisodicSearch;
  global.Goals_considerNewGoalFromEpisode = Goals_considerNewGoalFromEpisode;
  global.PassiveMode_processUnderstandingModels = PassiveMode_processUnderstandingModels;
  global.PassiveMode_notifyScenariosExhausted = PassiveMode_notifyScenariosExhausted;
  global.Goals_addOrUpdateLifeGoalFromFrame = addOrUpdateLifeGoalFromFrame;
  global.Goals_episodicRuleFromFrame = episodicRuleFromFrame;
  global.Goals_appendLifeGoalEpisodicRuleLink = appendLifeGoalEpisodicRuleLink;
  global.Goals_getGoalById = getGoalById;
  global.Goals_getBestLifeGoalForBranch = getBestLifeGoalForBranch;
  global.Goals_findLifeGoalByPerceptionAndSituation = findLifeGoalByPerceptionAndSituation;
  global.Goals_findBestLifeGoalPositiveUsefulness = findBestLifeGoalPositiveUsefulness;
  global.Goals_mergeUsefulnessSampleForGoalId = mergeUsefulnessSampleForGoalId;
  global.Goals_applyWaitingPeriodEffectToGoalUsefulness = applyWaitingPeriodEffectToGoalUsefulness;
  global.getActiveBaseGoalVitalId = getActiveBaseGoalVitalId;
  global.Goals_setCurrentGoalInInfoPicture = setCurrentGoalInInfoPicture;
  global.Goals_serializeState = serializeState;
  global.Goals_applyState = applyState;
  global.Goals_clearAllLifeGoals = clearAllLifeGoals;
  global.Goals_ensureDefaultMeaningGoal = ensureDefaultMeaningGoal;
  global.Goals_findDefaultMeaningGoal = findDefaultMeaningGoal;
  global.Goals_isDefaultMeaningGoalId = isDefaultMeaningGoalId;
  global.Goals_getDefaultMeaningGoalId = getDefaultMeaningGoalId;
  global.GOAL_KIND_MEANING_DEFAULT = GOAL_KIND_MEANING_DEFAULT;
  global.GOALS_DEFAULT_MEANING_LABEL = GOALS_DEFAULT_MEANING_LABEL;
  global.Goals_ensureDefaultCryGoal = ensureDefaultCryGoal;
  global.Goals_findDefaultCryGoal = findDefaultCryGoal;
  global.Goals_getDefaultCryGoalId = getDefaultCryGoalId;
  global.Goals_isDefaultCryGoalId = isDefaultCryGoalId;
  global.GOAL_KIND_CRY_DEFAULT = GOAL_KIND_CRY_DEFAULT;
  global.GOALS_DEFAULT_CRY_LABEL = GOALS_DEFAULT_CRY_LABEL;
})(typeof window !== 'undefined' ? window : globalThis);