Skip to content

[Tweak] The Great Medbay Reformation#101

Open
UmbiMax wants to merge 198 commits into
ArcaneSS14:masterfrom
OSAS-corp:NewMed
Open

[Tweak] The Great Medbay Reformation#101
UmbiMax wants to merge 198 commits into
ArcaneSS14:masterfrom
OSAS-corp:NewMed

Conversation

@UmbiMax

@UmbiMax UmbiMax commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Описание PR

Всё ближе и ближе к TG13.
На Орионе не успел - продолжаем тут.

Медиа

Coming Soon

Чек-лист

  • Orion > Arcane
  • Добавить звуки при переливании.
  • Доработать Синтеплоть.
  • Локализация.
  • Доработка просчёта охлаждения в системке криокапсулы. (+ Фикс устойчивости у меха вульп/таяров в крио.)
  • Добавить раздатчику химикатов эффективность заряда от серво. Зависимость заряда от батарейки в новом PR со СМЭС-ами и ЛКП.
  • Рецепты новых химикатов.
  • Обновить медицину в аплинках.

Тип PR

  • Feature
  • Fix
  • Tweak
  • Balance
  • Refactor
  • Port
  • Translate
  • Resprite

Изменения
🆑

  • add: Добавлено веселье.

  • remove: Убрано веселье.

  • tweak: Изменено веселье.

  • fix: Исправлено веселье.

  • ChemMaster

    • Буфер больше не бесконечный: базовый лимит в прототипе — 1000u вместо 1000000u.
    • Реальная вместимость считается по первым двум мензуркам, вставленным при постройке (в machine_parts): их объём суммируется и умножается на x10. Без мензурок — fallback 1000u.
    • При сборке содержимое этих мензурок переливается в буфер один раз; при разборке — возвращается обратно, остаток выливается на пол.
    • Интерфейс показывает "текущий/максимум" объём буфера.
    • Добавлен визуал заполненности буфера на спрайте станка (полоса + смена цвета).
    • Перенос реагентов в буфер и обратно корректно ограничивается наличием реагента и свободным объёмом.
    • Лимит дозировки пилюль: 20u → 50u (и для переносного ChemMaster тоже).
  • Тара для растворов (кувшины, мензурки)

    • Кувшины и все мензурки (обычная, большая, криостазис, блюспейс) теперь открываются/закрываются крышкой — с анимацией и звуком.
    • Уровни заполнения кувшина: 6 → 11 (плавнее показывает объём).
    • Добавлены две промежуточные мензурки между обычной и блюспейс:
      • Сверхбольшая: 150u, крафт на лабораторном принтере — 400 стекла + 500 пластика. Изучается в ветке медицинского оборудования.
      • Метаматериальная: 200u, крафт — 400 стекла + 500 пластика + 200 золота + 100 серебра. Изучается в ветке химического синтеза.
    • Блюспейс-мензурка: 1000u → 300u. Блюспейс-шейкер: 1000u → 250u.
    • Таблетка как контейнер раствора: 20u → 50u.
    • Другое (РНД, по мелочи): в ветке AppliedBluespace/AnomalyShells переставлены разблокировки блюспейс-предметов (беспосадочная перетасовка технологий, не влияет на баланс мензурок выше).
  • Аптечки

    • Экстренная аптечка (2x1): нить, инжектор адреналина, шприц трамадола, таблетка херигниса, таблетка угля. Спавнится в аварийном шкафчике вместо старой кислородной (шанс той почти убран — раньше была там основным лутом).
    • Стандартная боевая (3x2): нить, продвинутая регенеративная сетка, три автоинжектора (от удушья/стимулятор/от кровотечения-ожогов), два продвинутых аварийных медипена, два патча (бикаридин/дермалин). Аплинк — 30 ТК.
    • Продвинутая боевая (5x2, крупные предметы влезают): всё из стандартной + два аварийных баллона (нитриум ~1.5–2 мин, хилиум ~1.3 мин), маска, патчи от удушья/сальбутамола, доп. автоинжекторы. Аплинк — 60 ТК.
    • Старая аптечка с таблетками в аплинке подешевела: 25 ТК → 15 ТК.
    • Пересобраны составы всех старых аптечек (обычная, ожоговая, брутальная, токсиновая, кислородная, радиационная, боевая) — обновлены инжекторы/патчи/таблетки внутри.
    • Синди-набор в вещмешке: медицинский набор заменён на новую стандартную+продвинутую боевую аптечку, добавлены ночник и два анализатора здоровья, урезаны автоинжекторы/турникеты.
  • Новые химикаты-предметы

    • Новые капсулы: херкури, херигнис, пробитал, пентеновая кислота, мультивер, сиринивер, генецид, аммонизированная ртуть, комбо анти-брут (бикаридин+ибупрофен) и анти-физ (кетолин+дермалин+пиразин+тегифин).
    • Новые предзаполненные шприцы: галоперидол, конвермол.
    • Новые предзаполненные патчи: проценил лазид, эбифин, сальбутамол, сиринивер, гироналин, формальдегид.
    • Картридж физраствора для инжекторов: было 7u физраствора + 3u дексалин плюс → теперь 5u физраствора + 3u дексалин плюс + 2u артиплатин.
  • Регенеративные материалы

    • Мазь, брутпак, повязка, медицинская нить — многоступенчатая визуализация по мере расхода стака.
    • Регенеративные сетки теперь нужно сначала открыть/распаковать (как на НовоТГ13) — с полки напрямую не лечат.
    • Добавлена промежуточная пара: обычная нить + обычная регенеративная сетка, слабее продвинутых версий.
    • У продвинутой регенеративной сетки поменялся только спрайт, лечебные характеристики не тронуты.
    • Максимум стака медицинской нити: 15 → 10.
    • Лечащие предметы с приоритетом кровотечения теперь лечат кровоточащие конечности первыми.
    • Штраф за самолечение: x2 → x5.
    • При завершении лечения — отдельный звук окончания.
  • Баланс химикатов (реагент: тип урона/лечения: было → стало)

    • Диловен: Яд: -1.5 → -1.
    • Дифенгидрамин: снятие гистамина: -3 → -4.
    • Криоксадон: рабочий температурный порог 213K → 273.15K, скейл лечения 1.6 → 3, Брут: -3 → -6, Ожог: -3 → -4 (Удушье без изменений, -3). В критическом состоянии на холоде добавлен отдельный эффект: Удушье -6, Брут -3, Ожог -2, плюс небольшой побочный урон ядом -1.5.
    • Дермалин: Тепло/Шок/Холод: -2 → -1.5 (все три типа).
    • Эпинефрин: порог гистамина для снятия: 45 → 30.
    • Физраствор: восстановление крови: 12 → 6.
    • Сидерлак: Кислота: -5 → -6.
    • Синаптизин: добавлен шанс срабатывания побочки 30%; при низком галоперидоле дополнительно снимает сонливость.
    • Трикордразин: работает при суммарном уроне ≤100 → ≤60.
    • Сигинат: Кислота: -3 → -4.
    • Пиразин: Тепло: -1 → -1.5.
    • Инсузин: Шок: -4 → -6.
    • Некрозол (крио-режим): Брут: -5 → -20, Ожог: -5 → -15, Токсин: -5 → -10 (Удушье не менялось).
    • Пробитал: Брут: -1.5 → -5, но добавлен побочный урон по стамине +4.
    • Тиримол: было точечное лечение удушья -6 → теперь группа Airloss -8; временная остановка кровотечения отключена.
    • Мультивер: условия срабатывания антидот-эффекта немного расширены (доп. порог по накопленному количеству яда).
    • Сульфадиазин серебра: Тепло: -1.5 → -1, Холод: -1.5 → -1.25, Кислота: -1 → -0.75 (Шок не менялся); добавлено побочное охлаждение при контакте без защиты от давления.
    • Стиптик (кровоостанавливающий порошок): Тупой: -2 → -1.5, Колющий: -1 → -0.75, Режущий: -2 → -1.5; добавлен побочный нагрев при контакте без защиты от давления.
    • Синтплоть: добавлен побочный урон ядом +0.2 при контакте.
    • Синтоклетки: реактивный контактный эффект резко ослаблен (Яд: -2 → -0.2, Радиация: -1 → -0.4); при приёме внутрь лечение переписано с общей группы токсинов на точечные типы (Яд -0.25, Радиация -0.5).
    • Оксандролон: Кислота: -3 → -1.
    • Херкури/Херигнис: добавлено лечение теплового/холодового урона при сильных ожогах/обморожениях (порог 50 урона).
    • Артиплатин: восстановление крови: 8 → 12.
  • Яды

    • Нестабильный мутаген: Радиация: 3 → 2.
    • Heartbreaker Toxin: Удушье: 5 → 12; рецепт переведён с дексалин плюс на обычный дексалин.
    • Лексорин: Удушье (группа): 10 → 16.
    • Гистамин: добавлена собственная скорость метаболизма; Брут: 2 → 6 (и в пороговом эффекте тоже 2 → 6 по Брут/Удушью); порог срабатывания: 45 → 30.
    • Аматоксин: скорость метаболизма 0.2 → 0.3; Яд: 3 → 5.
    • Липолицид: Яд: 2 → 4.
    • Pendetide: Радиация: 2 → 3.5.
    • Хлор: вместо яда (2) теперь наносит тупой урон (3).
    • Медь и железо: восстановление крови: 0.4 → 1.2 (у обоих).
  • Рецепты

    • Рецепт дексалин плюс полностью удалён (сам дексалин не тронут).
    • Рецепт мензурки в химлаборатории: серебро заменено на уран + добавлен бискристалл.
    • Рецепт тиримола: вместо полиакрилата натрия — азот; серная кислота стала катализатором.
    • Рецепт синтоклеток: основного продукта меньше, побочных (натрий/углерод) больше.
  • Раздатчик химикатов

    • Для платы дополнительно нужен конденсатор.
    • Стоимость большинства базовых реагентов снижена (например, алюминий 8 → 3, хлор 9 → 5, медь 5 → 3, ртуть 12 → 8, фосфор 14 → 10), сера подорожала (5 → 7) — остальное по мелочи в том же духе.
    • Скорость зарядки раздатчика теперь зависит от установленных деталей, включая конденсатор.
  • Давление, удушье, урон по телу

    • Максимальный урон от баротравмы: 200 → 350 (на практике добить тело до этого предела одним давлением почти нереально — оторвать голову проще).
    • Порог урона от тупого оружия для разрушения тела: 400 → 450.
    • Базовый урон от низкого давления: 4 → 8.
    • Урон от долгого нахождения в опасно низком давлении теперь нарастает со временем, а не бьёт сразу на полную.
    • Удушье в вакууме/при очень низком давлении: урон x2.
  • Криокапсулы

    • Активная криокапсула теперь реально охлаждает пациента, а не только воздух внутри.
    • Без полноценной защиты (одновременно от температуры и давления) охлаждение эффективнее.
  • Хирургия и осмотр

    • Остановка внутреннего кровотечения и восстановление тканей от брута/ожогов — дольше по времени, но заметно эффективнее за раз.
    • Осмотр своего тела теперь корректно показывает свои раны/кровотечения отдельными формулировками от осмотра других (раньше был кривой/неполный).
  • Спрайты

    • Новые/обновлённые: аптечки (экстренная, боевая стандартная, боевая продвинутая), многоступенчатая визуализация расходников (мазь, брутпак, повязка, нить, обе сетки), новые медипены, крышки для мензурок/кувшинов, новые мензурки.
    • Миксер (портирован спрайт с НовоТГ13): добавлен визуал "включён/работает".
  • Звуки

    • Новый звук переливания жидкостей между ёмкостями.
    • Звуки поднятия/падения для мензурок и кувшинов.
    • Новые звуки для нити и регенеративных сеток (начало/процесс/завершение лечения, разрывание упаковки).
    • Звук завершения лечения при полном заживлении раны.
  • Аранесп и дофамин

    • Аранесп теперь работает как на НовоТГ13: восстанавливает 18 стамина-урона в секунду мгновенно, не влияя на скорость передвижения и выход из станкрита.
    • Старая версия эффекта аранеспа осталась в сборке под названием "дофамин" — синтезируется отдельно из нового аранеспа, сальбутамола и дифенгидрамина.
  • Новые химикаты (эффект)

    • Конвермол: быстро переводит урон от удушья в яд (5 к 1 при обычном приёме, 1 к 1 при передозировке); скорость лечения растёт от количества в крови; простые противоядия (Dylovene/Tricordrazine) ускоренно его выводят.
    • Сальбутамол: медленный, но мощный химикат от удушья.
  • Для разработчиков

    • ChemMasterWindow.xaml.cs: форматирование объёма буфера вынесено в хелпер — только читаемость, поведения не меняет.
    • ChangeConstructionNodeBehavior: при переходе ноды в machineFrame заранее поднимается MachineDeconstructedEvent — фиксит порядок событий при разборке машин.
    • EnergyReagentDispenserSystem: возврат энергии при очистке контейнера для неизвестного реагента больше не улетает в float.MaxValue, теперь честный 0.
    • TryAllReactionsTest.cs: порядок TryAddReagent/SetTemperature/UpdateChemicals переписан на единый проход — раньше промежуточные вызовы могли триггерить не ту реакцию раньше времени.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ArcaneSS14/arcane-old"
pr="101"

echo "=== PR checks ==="
gh pr checks "$pr" --repo "$repo" || true

echo "=== latest failed PR workflow ==="
run="$(
  gh run list --repo "$repo" --limit 30 \
    --json databaseId,event,headBranch,status,conclusion,createdAt \
  | jq -r '
      [.[] | select(.event == "pull_request" and .conclusion == "failure")]
      | sort_by(.createdAt) | last | .databaseId // empty
    '
)"
test -n "$run"
echo "run=$run"

log="$(mktemp)"
gh run view "$run" --repo "$repo" --log-failed > "$log" 2>&1

echo "=== test occurrence and surrounding context ==="
match_lines="$(grep -n 'SpawnAndDeleteAllEntitiesInTheSameSpot' "$log" | cut -d: -f1 || true)"
if [ -z "$match_lines" ]; then
  echo "Test name absent from --log-failed; searching complete run log."
  gh run view "$run" --repo "$repo" --log > "$log" 2>&1
  match_lines="$(grep -n 'SpawnAndDeleteAllEntitiesInTheSameSpot' "$log" | cut -d: -f1 || true)"
fi

for line in $match_lines; do
  start=$(( line > 100 ? line - 100 : 1 ))
  end=$(( line + 180 ))
  echo "--- lines $start-$end ---"
  sed -n "${start},${end}p" "$log"
done

echo "=== all likely primary failures from this run ==="
grep -nEi \
  'EntityCreationException|Exception inside InitializeAndStartEntity|StorageFill|Insufficient|Недостаточ|does not exist in RSI|Layer with key|Prototype .* threw|Unhandled|ERROR.*system\.' \
  "$log" | head -250 || true

Length of output: 264003


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== MobState enum ==="
cat -n Content.Shared/Mobs/MobState.cs

echo "=== throwing state-entry handler ==="
sed -n '180,240p' Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs

echo "=== all explicit NotImplementedException throws related to MobState ==="
rg -n -C4 'NotImplementedException|MobState\.(Critical|SoftCritical|HardCritical|Dead)' \
  Content.Shared/Mobs Content.Server Content.Client \
  -g '*.cs'

echo "=== all switches over MobState, including expressions ==="
rg -n -U -C3 'switch\s*(\([^\n]*MobState|[^\n]*CurrentState|[^\n]*state\b)|MobState\.[A-Za-z]+\s*=>' \
  Content.Shared Content.Server Content.Client \
  -g '*.cs'

echo "=== PR diff for enum and subscriber handler versus master ==="
git diff master...HEAD -- \
  Content.Shared/Mobs/MobState.cs \
  Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs

echo "=== master version of enum and state-entry handler ==="
git show master:Content.Shared/Mobs/MobState.cs
git show master:Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs | sed -n '180,240p'

Length of output: 178969


@UmbiMax, нашёл. Это регрессия от изменения MobState.

MobState.Critical раньше был alias SoftCritical (Critical = SoftCritical). В PR он стал отдельным значением 2, а остальные значения сдвинулись. При попадании моба в Critical MobStateSystem.OnStateEnteredSubscribers() не находит для него case и на Line 220 кидает NotImplementedException.

Проблема системная: Critical не обработан также в выходе из состояния, проверках сознания, действий, AllowedStates и прочих местах. Добавлять case MobState.Critical во все такие ветки — рискованная неполная миграция и слом сетевой/сериализуемой совместимости.

Нужно откатить превращение Critical в отдельное состояние:

--- a/Content.Shared/Mobs/MobState.cs
+++ b/Content.Shared/Mobs/MobState.cs
@@
 public enum MobState : byte
 {
     Invalid = 0,
     Alive = 1,
-    Critical = 2,
-    SoftCritical = 3,
-    HardCritical = 4,
-    Dead = 5,
+    SoftCritical = 2,
+    Critical = SoftCritical,
+    HardCritical = 3,
+    Dead = 4,
 }

Также в Content.Shared/Mobs/Components/MobThresholdsComponent.cs добавленная запись избыточна и её надо убрать: при alias ключ MobState.Critical равен MobState.SoftCritical, поэтому два одинаковых ключа в StateAlertDict недопустимы/бессмысленны.

     public Dictionary<MobState, ProtoId<AlertPrototype>> StateAlertDict = new()
     {
         {MobState.Alive, "HumanHealth"},
-        {MobState.Critical, "HumanSoftCrit"},
         {MobState.SoftCritical, "HumanSoftCrit"},
         {MobState.HardCritical, "HumanHardCrit"},
         {MobState.Dead, "HumanDead"},
     };

Итог: SpawnAndDeleteAllEntitiesInTheSameSpot падает не из-за новых предметов, а из-за этого enum-изменения. После отката Critical снова будет корректно обрабатываться как soft crit без массовой правки всех систем.

@UmbiMax

UmbiMax commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai но получим проблему, из-за которой химикаты для критического состояния, по типу инапровалина вновь будут работать лишь в софткрите

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Inaprovaline/Ivacetamide effect + condition on MobState ==="
rg -n -C5 'Inaprovaline' Resources/Prototypes/Reagents/medicine.yml 2>/dev/null | head -60

echo "=== MobStateCondition current (post-PR) ==="
cat -n Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs

echo "=== usages of MobStateCondition with Mobstate: Critical ==="
rg -n -B3 -A3 'Mobstate:\s*Critical' Resources/Prototypes 2>/dev/null

echo "=== check other systems keying off MobState.Critical directly (non-alias-safe) ==="
rg -n 'MobState\.Critical\b' Content.Shared Content.Server Content.Client -g '*.cs'

Length of output: 2980


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Inaprovaline full reagent block ==="
sed -n '709,760p' Resources/Prototypes/Reagents/medicine.yml

echo "=== any other reagent condition on Mobstate Critical (case-insensitive) ==="
rg -ni 'mobstate' Resources/Prototypes/Reagents Resources/Prototypes/_Goobstation/Reagents Resources/Prototypes/_Arcane/Reagents Resources/Prototypes/_EinsteinEngines/Reagents 2>/dev/null

Length of output: 4112


@UmbiMax, не будет — это уже учтено отдельным кодом, независимым от значений enum.

В Content.Shared/EntityEffects/EffectConditions/MobStateCondition.cs (Line 22-26) есть явный спец-кейс:

if (Mobstate == MobState.Critical)
    return mobState.CurrentState == MobState.SoftCritical
        || mobState.CurrentState == MobState.HardCritical;

Это сравнение работает по значению поля Mobstate из прототипа, а не по численному равенству enum-значений. Даже после отката Critical = SoftCritical (alias), эта проверка всё равно вернёт true для обоих под-состояний — SoftCritical и HardCritical.

Подтвердил на данных: Inaprovaline (Resources/Prototypes/Reagents/medicine.yml, Line 724-725) и остальные реагенты с mobstate: Critical (десублимация в _Goobstation/Reagents/medicine.yml и др.) используют именно MobStateCondition, а не прямое сравнение MobState.Critical в C#-коде систем. Прямых MobState.Critical в .cs-файлах (кроме самого enum и Condition) не нашёл — значит откат enum затрагивает только MobStateSystem.Subscribers (там и был краш), а химия через MobStateCondition продолжит работать в обоих критах как задумано.

Так что откат enum безопасен для инапровалина и прочих критовых химикатов — фиксить нужно только сам MobState.cs и дубль-ключ в MobThresholdsComponent.cs, как я писал.

(・_・)b

@UmbiMax

UmbiMax commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Хахахахах, кажется, что ты не полностью просчитал весь тест интегралок. Я вот думаю, чтобы уже отложить добавление реакции охлаждения/нагрева воды/льда, не забывая про весь код для поддержки, включая тесты с доработкой систем реакции.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@UmbiMax, разумно. Тест уже показал, что цепочка реакций затрагивает больше систем, чем кажется на первый взгляд (Vodka, cascade-порядок, теперь ещё и MobState-регрессия рядом всплыла отдельно).

Что нужно откатить для полного отложения фичи:

  • Resources/Prototypes/_Arcane/Recipes/Reactions/drinks.ymlWaterFreezing/IceMelting.
  • Связанные AdjustTemperature в Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml (IcedCoffee/IcedTea/Ice) и Resources/Prototypes/Reagents/Consumable/Food/condiments.yml (Coldsauce) — они завязаны именно на эту фичу охлаждения.
  • Тестовый патч TryAllReactionsTest.cs (порядок TryAddReagent/SetTemperature/UpdateChemicals) можно оставить — это независимый фикс инфраструктуры теста, полезен сам по себе, регрессий не даёт.
  • MobState.cs/MobThresholdsComponent.cs откат — отдельная тема, не связана с водой/льдом, но тоже блокирует SpawnAndDeleteAllEntitiesInTheSameSpot.

Когда будешь возвращаться к фиче — добавь отдельный интеграционный тест на цепочку Water → Ice → Water до мержа в основную ветку, чтобы не выявлять такие вещи постфактум через TryAllTest.

(・_・)


✏️ Learnings added
Learnt from: UmbiMax
URL: https://github.com/ArcaneSS14/arcane-old/pull/101

Timestamp: 2026-07-11T13:20:20.036Z
Learning: Для PR `#101` (ArcaneSS14/arcane-old): решено отложить добавление реакций WaterFreezing/IceMelting (охлаждение/нагрев воды и льда через AdjustTemperature) до полноценной доработки поддерживающего кода в системе химических реакций, включая интеграционные тесты. Затронутые файлы для отката: Resources/Prototypes/_Arcane/Recipes/Reactions/drinks.yml (WaterFreezing/IceMelting), Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml (AdjustTemperature у IcedCoffee/IcedTea/Ice), Resources/Prototypes/Reagents/Consumable/Food/condiments.yml (AdjustTemperature у Coldsauce). Причина: цепочка реакций спровоцировала неочевидные регрессии в Content.IntegrationTests/Tests/Chemistry/TryAllReactionsTest.cs (например Vodka), потребовавшие правок порядка вызовов TryAddReagent/SetTemperature/UpdateChemicals в тесте.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@UmbiMax

UmbiMax commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Хмм... Названия химикатов оставь на русском, но и то без ошибок по типу гироналина, а не хироналина.
Постарайся больше использовать не "уменьшена/увеличена эффективность химиката", а прямо указывать изменения чисел там, где это удобно и возможно. Как в прошлой методичке
Диловен:
Яды: -1.5 > -1

Люди сразу поймут в данных списках чёткие изменения, да и то они краткие.

Можешь прочитать мои прошлые требования для CL. В основном проблема только в данном указании. У всего остального относительно всё в норме.
Не забудь проверить, чего ещё не хватает в CL

Можешь снова прописывать CL

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== check ru names for reagents mentioned in CL ==="
rg -n "reagent-name-hyronalin|reagent-name-dylovene|reagent-name-cryoxadone|reagent-name-necrosol|reagent-name-dermaline|reagent-name-epinephrine|reagent-name-saline|reagent-name-synaptizine|reagent-name-tricordrazine|reagent-name-probital|reagent-name-tirimol|reagent-name-multiver|reagent-name-oxandrolone|reagent-name-hercuri|reagent-name-herignis|reagent-name-synthflesh|reagent-name-synthcells|reagent-name-unstablemutagen|reagent-name-heartbreakertoxin|reagent-name-lexorin|reagent-name-histamine|reagent-name-amatoxin|reagent-name-lipolicide|reagent-name-pendetide|reagent-name-chlorine|reagent-name-copper|reagent-name-iron|reagent-name-dexalinplus|reagent-name-dexalin|reagent-name-convermol|reagent-name-salbutamol|reagent-name-aranesp|reagent-name-dopamine|reagent-name-diphenhydramine|reagent-name-formaldehyde|reagent-name-siderlac|reagent-name-pyrazine|reagent-name-insuzine|reagent-name-sigynate" Resources/Locale/ru-RU -g '*.ftl' 2>/dev/null

Length of output: 4688


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== diff medicine.yml (main) ==="
git diff master...HEAD -- Resources/Prototypes/Reagents/medicine.yml
echo "=== diff narcotics.yml (main) ==="
git diff master...HEAD -- Resources/Prototypes/Reagents/narcotics.yml
echo "=== diff toxins.yml (main) ==="
git diff master...HEAD -- Resources/Prototypes/Reagents/toxins.yml
echo "=== diff elements.yml ==="
git diff master...HEAD -- Resources/Prototypes/Reagents/elements.yml
echo "=== diff Goobstation medicine.yml ==="
git diff master...HEAD -- Resources/Prototypes/_Goobstation/Reagents/medicine.yml
echo "=== diff Goobstation toxins.yml ==="
git diff master...HEAD -- Resources/Prototypes/_Goobstation/Reagents/toxins.yml
echo "=== diff EinsteinEngines medicine.yml ==="
git diff master...HEAD -- Resources/Prototypes/_EinsteinEngines/Reagents/medicine.yml
echo "=== diff Goobstation dispensers.yml ==="
git diff master...HEAD -- Resources/Prototypes/_Goobstation/Entities/Structures/Machines/dispensers.yml
echo "=== diff base.yml mobs ==="
git diff master...HEAD -- Resources/Prototypes/Entities/Mobs/base.yml
echo "=== diff Atmospherics.cs ==="
git diff master...HEAD -- Content.Shared/Atmos/Atmospherics.cs
echo "=== diff BarotraumaComponent.cs ==="
git diff master...HEAD -- Content.Server/Atmos/Components/BarotraumaComponent.cs

Length of output: 25571


🏁 Script executed:

#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import re
path = "Resources/Prototypes/Reagents/medicine.yml"
text = open(path, encoding="utf-8").read()
blocks = re.split(r'\n(?=- type: reagent)', text)
targets = ["Caustic: -4","Heat: -1.5","Shock: -6","Sigynate","Pyrazine","Insuzine","Necrosol","Dylovene","Cryoxadone","Dermaline","Epinephrine","Saline","Siderlac","Synaptizine","Tricordrazine","Diphenhydramine"]
for b in blocks:
    m = re.search(r'id:\s*(\S+)', b)
    if not m: continue
    rid = m.group(1)
    if rid in ["Dylovene","Diphenhydramine","Cryoxadone","Dermaline","Epinephrine","Saline","Siderlac","Synaptizine","Tricordrazine","Sigynate","Pyrazine","Insuzine","Necrosol"]:
        print(f"===== {rid} =====")
        print(b[:1500])
        print()
PY

Length of output: 15686


@UmbiMax, поправил названия и формат чисел, проверил дифф напрямую (не на глаз). Раздел про напитки/лёд убрал — фича отложена по прошлому решению.

- ChemMaster
  - Буфер больше не бесконечный: базовый лимит в прототипе — 1000u вместо 1000000u.
  - Реальная вместимость считается по первым двум мензуркам, вставленным при постройке (в machine_parts): их объём суммируется и умножается на x10. Без мензурок — fallback 1000u.
  - При сборке содержимое этих мензурок переливается в буфер один раз; при разборке — возвращается обратно, остаток выливается на пол.
  - Интерфейс показывает "текущий/максимум" объём буфера.
  - Добавлен визуал заполненности буфера на спрайте станка (полоса + смена цвета).
  - Перенос реагентов в буфер и обратно корректно ограничивается наличием реагента и свободным объёмом.
  - Лимит дозировки пилюль: 20u → 50u (и для переносного ChemMaster тоже).

- Тара для растворов (кувшины, мензурки)
  - Кувшины и все мензурки (обычная, большая, криостазис, блюспейс) теперь открываются/закрываются крышкой — с анимацией и звуком.
  - Уровни заполнения кувшина: 6 → 11 (плавнее показывает объём).
  - Добавлены две промежуточные мензурки между обычной и блюспейс:
    - Сверхбольшая: 150u, крафт на лабораторном принтере — 400 стекла + 500 пластика. Изучается в ветке медицинского оборудования.
    - Метаматериальная: 200u, крафт — 400 стекла + 500 пластика + 200 золота + 100 серебра. Изучается в ветке химического синтеза.
  - Блюспейс-мензурка: 1000u → 300u. Блюспейс-шейкер: 1000u → 250u.
  - Таблетка как контейнер раствора: 20u → 50u.
  - Другое (РНД, по мелочи): в ветке AppliedBluespace/AnomalyShells переставлены разблокировки блюспейс-предметов (беспосадочная перетасовка технологий, не влияет на баланс мензурок выше).

- Аптечки
  - Экстренная аптечка (2x1): нить, инжектор адреналина, шприц трамадола, таблетка херигниса, таблетка угля. Спавнится в аварийном шкафчике вместо старой кислородной (шанс той почти убран — раньше была там основным лутом).
  - Стандартная боевая (3x2): нить, продвинутая регенеративная сетка, три автоинжектора (от удушья/стимулятор/от кровотечения-ожогов), два продвинутых аварийных медипена, два патча (бикаридин/дермалин). Аплинк — 30 ТК.
  - Продвинутая боевая (5x2, крупные предметы влезают): всё из стандартной + два аварийных баллона (нитриум ~1.5–2 мин, хилиум ~1.3 мин), маска, патчи от удушья/сальбутамола, доп. автоинжекторы. Аплинк — 60 ТК.
  - Старая аптечка с таблетками в аплинке подешевела: 25 ТК → 15 ТК.
  - Пересобраны составы всех старых аптечек (обычная, ожоговая, брутальная, токсиновая, кислородная, радиационная, боевая) — обновлены инжекторы/патчи/таблетки внутри.
  - Синди-набор в вещмешке: медицинский набор заменён на новую стандартную+продвинутую боевую аптечку, добавлены ночник и два анализатора здоровья, урезаны автоинжекторы/турникеты.

- Новые химикаты-предметы
  - Новые капсулы: херкури, херигнис, пробитал, пентеновая кислота, мультивер, сиринивер, генецид, аммонизированная ртуть, комбо анти-брут (бикаридин+ибупрофен) и анти-физ (кетолин+дермалин+пиразин+тегифин).
  - Новые предзаполненные шприцы: галоперидол, конвермол.
  - Новые предзаполненные патчи: проценил лазид, эбифин, сальбутамол, сиринивер, гироналин, формальдегид.
  - Картридж физраствора для инжекторов: было 7u физраствора + 3u дексалин плюс → теперь 5u физраствора + 3u дексалин плюс + 2u артиплатин.

- Регенеративные материалы
  - Мазь, брутпак, повязка, медицинская нить — многоступенчатая визуализация по мере расхода стака.
  - Регенеративные сетки теперь нужно сначала открыть/распаковать (как на НовоТГ13) — с полки напрямую не лечат.
  - Добавлена промежуточная пара: обычная нить + обычная регенеративная сетка, слабее продвинутых версий.
  - У продвинутой регенеративной сетки поменялся только спрайт, лечебные характеристики не тронуты.
  - Максимум стака медицинской нити: 15 → 10.
  - Лечащие предметы с приоритетом кровотечения теперь лечат кровоточащие конечности первыми.
  - Штраф за самолечение: x2 → x5.
  - При завершении лечения — отдельный звук окончания.

- Баланс химикатов (реагент: тип урона/лечения: было → стало)
  - Диловен: Яд: -1.5 → -1.
  - Дифенгидрамин: снятие гистамина: -3 → -4.
  - Криоксадон: рабочий температурный порог 213K → 273.15K, скейл лечения 1.6 → 3, Брут: -3 → -6, Ожог: -3 → -4 (Удушье без изменений, -3). В критическом состоянии на холоде добавлен отдельный эффект: Удушье -6, Брут -3, Ожог -2, плюс небольшой побочный урон ядом -1.5.
  - Дермалин: Тепло/Шок/Холод: -2 → -1.5 (все три типа).
  - Эпинефрин: порог гистамина для снятия: 45 → 30.
  - Физраствор: восстановление крови: 12 → 6.
  - Сидерлак: Кислота: -5 → -6.
  - Синаптизин: добавлен шанс срабатывания побочки 30%; при низком галоперидоле дополнительно снимает сонливость.
  - Трикордразин: работает при суммарном уроне ≤100 → ≤60.
  - Сигинат: Кислота: -3 → -4.
  - Пиразин: Тепло: -1 → -1.5.
  - Инсузин: Шок: -4 → -6.
  - Некрозол (крио-режим): Брут: -5 → -20, Ожог: -5 → -15, Токсин: -5 → -10 (Удушье не менялось).
  - Пробитал: Брут: -1.5 → -5, но добавлен побочный урон по стамине +4.
  - Тиримол: было точечное лечение удушья -6 → теперь группа Airloss -8; временная остановка кровотечения отключена.
  - Мультивер: условия срабатывания антидот-эффекта немного расширены (доп. порог по накопленному количеству яда).
  - Сульфадиазин серебра: Тепло: -1.5 → -1, Холод: -1.5 → -1.25, Кислота: -1 → -0.75 (Шок не менялся); добавлено побочное охлаждение при контакте без защиты от давления.
  - Стиптик (кровоостанавливающий порошок): Тупой: -2 → -1.5, Колющий: -1 → -0.75, Режущий: -2 → -1.5; добавлен побочный нагрев при контакте без защиты от давления.
  - Синтплоть: добавлен побочный урон ядом +0.2 при контакте.
  - Синтоклетки: реактивный контактный эффект резко ослаблен (Яд: -2 → -0.2, Радиация: -1 → -0.4); при приёме внутрь лечение переписано с общей группы токсинов на точечные типы (Яд -0.25, Радиация -0.5).
  - Оксандролон: Кислота: -3 → -1.
  - Херкури/Херигнис: добавлено лечение теплового/холодового урона при сильных ожогах/обморожениях (порог 50 урона).
  - Артиплатин: восстановление крови: 8 → 12.

- Яды
  - Нестабильный мутаген: Радиация: 3 → 2.
  - Heartbreaker Toxin: Удушье: 5 → 12; рецепт переведён с дексалин плюс на обычный дексалин.
  - Лексорин: Удушье (группа): 10 → 16.
  - Гистамин: добавлена собственная скорость метаболизма; Брут: 2 → 6 (и в пороговом эффекте тоже 2 → 6 по Брут/Удушью); порог срабатывания: 45 → 30.
  - Аматоксин: скорость метаболизма 0.2 → 0.3; Яд: 3 → 5.
  - Липолицид: Яд: 2 → 4.
  - Pendetide: Радиация: 2 → 3.5.
  - Хлор: вместо яда (2) теперь наносит тупой урон (3).
  - Медь и железо: восстановление крови: 0.4 → 1.2 (у обоих).

- Рецепты
  - Рецепт дексалин плюс полностью удалён (сам дексалин не тронут).
  - Рецепт мензурки в химлаборатории: серебро заменено на уран + добавлен бискристалл.
  - Рецепт тиримола: вместо полиакрилата натрия — азот; серная кислота стала катализатором.
  - Рецепт синтоклеток: основного продукта меньше, побочных (натрий/углерод) больше.

- Раздатчик химикатов
  - Для платы дополнительно нужен конденсатор.
  - Стоимость большинства базовых реагентов снижена (например, алюминий 8 → 3, хлор 9 → 5, медь 5 → 3, ртуть 12 → 8, фосфор 14 → 10), сера подорожала (5 → 7) — остальное по мелочи в том же духе.
  - Скорость зарядки раздатчика теперь зависит от установленных деталей, включая конденсатор.

- Давление, удушье, урон по телу
  - Максимальный урон от баротравмы: 200 → 350 (на практике добить тело до этого предела одним давлением почти нереально — оторвать голову проще).
  - Порог урона от тупого оружия для разрушения тела: 400 → 450.
  - Базовый урон от низкого давления: 4 → 8.
  - Урон от долгого нахождения в опасно низком давлении теперь нарастает со временем, а не бьёт сразу на полную.
  - Удушье в вакууме/при очень низком давлении: урон x2.

- Криокапсулы
  - Активная криокапсула теперь реально охлаждает пациента, а не только воздух внутри.
  - Без полноценной защиты (одновременно от температуры и давления) охлаждение эффективнее.

- Хирургия и осмотр
  - Остановка внутреннего кровотечения и восстановление тканей от брута/ожогов — дольше по времени, но заметно эффективнее за раз.
  - Осмотр своего тела теперь корректно показывает свои раны/кровотечения отдельными формулировками от осмотра других (раньше был кривой/неполный).

- Спрайты
  - Новые/обновлённые: аптечки (экстренная, боевая стандартная, боевая продвинутая), многоступенчатая визуализация расходников (мазь, брутпак, повязка, нить, обе сетки), новые медипены, крышки для мензурок/кувшинов, новые мензурки.
  - Миксер (портирован спрайт с НовоТГ13): добавлен визуал "включён/работает".

- Звуки
  - Новый звук переливания жидкостей между ёмкостями.
  - Звуки поднятия/падения для мензурок и кувшинов.
  - Новые звуки для нити и регенеративных сеток (начало/процесс/завершение лечения, разрывание упаковки).
  - Звук завершения лечения при полном заживлении раны.

- Аранесп и дофамин
  - Аранесп теперь работает как на НовоТГ13: восстанавливает 18 стамина-урона в секунду мгновенно, не влияя на скорость передвижения и выход из станкрита.
  - Старая версия эффекта аранеспа осталась в сборке под названием "дофамин" — синтезируется отдельно из нового аранеспа, сальбутамола и дифенгидрамина.

- Новые химикаты (эффект)
  - Конвермол: быстро переводит урон от удушья в яд (5 к 1 при обычном приёме, 1 к 1 при передозировке); скорость лечения растёт от количества в крови; простые противоядия (Dylovene/Tricordrazine) ускоренно его выводят.
  - Сальбутамол: медленный, но мощный химикат от удушья.

- Для разработчиков
  - `ChemMasterWindow.xaml.cs`: форматирование объёма буфера вынесено в хелпер — только читаемость, поведения не меняет.
  - `ChangeConstructionNodeBehavior`: при переходе ноды в `machineFrame` заранее поднимается `MachineDeconstructedEvent` — фиксит порядок событий при разборке машин.
  - `EnergyReagentDispenserSystem`: возврат энергии при очистке контейнера для неизвестного реагента больше не улетает в `float.MaxValue`, теперь честный `0`.
  - `TryAllReactionsTest.cs`: порядок `TryAddReagent`/`SetTemperature`/`UpdateChemicals` переписан на единый проход — раньше промежуточные вызовы могли триггерить не ту реакцию раньше времени.

Если что-то из формулировок ещё режет глаз — говори конкретный пункт, поправлю точечно.

@UmbiMax

UmbiMax commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Глянь на последние коммиты и на тесты. Где-то я просчитался

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ArcaneSS14/arcane-old"
pr="101"

echo "=== latest commits on PR ==="
gh pr view "$pr" --repo "$repo" --json commits --jq '.commits[-10:] | .[] | [.oid[0:8], .messageHeadline] | `@tsv`'

echo "=== PR checks ==="
gh pr checks "$pr" --repo "$repo" || true

echo "=== latest failed run ==="
run="$(
  gh run list --repo "$repo" --limit 30 \
    --json databaseId,event,headBranch,status,conclusion,createdAt \
  | jq -r '
      [.[] | select(.event == "pull_request" and .conclusion == "failure")]
      | sort_by(.createdAt) | last | .databaseId // empty
    '
)"
echo "run=$run"
if [ -n "$run" ]; then
  gh run view "$run" --repo "$repo" --log-failed > /tmp/failed_log.txt 2>&1 || true
  wc -l /tmp/failed_log.txt
  grep -n -iE "Failed |Error Message|Assert\.|Exception|does not exist|threw an exception" /tmp/failed_log.txt | head -200
fi

Length of output: 2763


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n /tmp/failed_log.txt

Length of output: 18099


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== search for ItemSlots component default spawn definitions with startingItem across recently touched dirs ==="
rg -n "startingItem:" Resources/Prototypes/_Arcane Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Medical Resources/Prototypes/Entities/Objects/Specific/Medical Resources/Prototypes/Entities/Structures/Machines Resources/Prototypes/_Goobstation/Entities/Structures/Machines 2>/dev/null

echo "=== search for EntityTableContainerFillComponent / StorageFill referencing gas tanks / medkits recursively ==="
rg -n "MedkitEmergencyFilled|MedkitCombatStandardFilled|MedkitCombatAdvancedFilled|EmergencyNitriumTankFilled|EmergencyHealiumTankFilled" Resources/Prototypes -g '*.yml'

echo "=== full firstaidkits.yml _Arcane content ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Fills/Items/firstaidkits.yml

echo "=== full gas_tanks.yml _Arcane fills content ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Fills/Items/gas_tanks.yml

Length of output: 1411


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "=== search for ItemSlots component default spawn definitions with startingItem across recently touched dirs ==="
rg -n "startingItem:" Resources/Prototypes/_Arcane Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Medical Resources/Prototypes/Entities/Objects/Specific/Medical Resources/Prototypes/Entities/Structures/Machines Resources/Prototypes/_Goobstation/Entities/Structures/Machines 2>/dev/null || echo "none"

echo "=== search for EntityTableContainerFillComponent / StorageFill referencing gas tanks / medkits recursively ==="
rg -n "MedkitEmergencyFilled|MedkitCombatStandardFilled|MedkitCombatAdvancedFilled|EmergencyNitriumTankFilled|EmergencyHealiumTankFilled" Resources/Prototypes -g '*.yml' || echo "none"

echo "=== full firstaidkits.yml _Arcane content ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Fills/Items/firstaidkits.yml || echo "missing"

echo "=== full gas_tanks.yml _Arcane fills content ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Fills/Items/gas_tanks.yml || echo "missing"

echo "=== duffelbag.yml relevant section ==="
rg -n "ClothingBackpackDuffelSyndicateMedicalBundleFilled" -A30 Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml || echo "none"

echo "=== misc.yml FillLockerEmergencyStandard ==="
rg -n "FillLockerEmergencyStandard" -A20 Resources/Prototypes/Catalog/Fills/Lockers/misc.yml || echo "none"

Length of output: 9073


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "=== search startingItem repo-wide referencing our new/changed entities ==="
rg -n "startingItem:\s*(MedkitEmergencyFilled|MedkitCombatStandardFilled|MedkitCombatAdvancedFilled|EmergencyNitriumTankFilled|EmergencyHealiumTankFilled|EmergencyNitriumTank|EmergencyHealiumTank|AdvancedEmergencyMedipen|SalbutamolAutoInjector|StimulatorAutoInjector|Suture|RegenerativeMesh|AdvancedRegenerativeMesh|XLargeBeaker|MetamaterialBeaker)" Resources/Prototypes -g '*.yml' || echo none

echo "=== search ItemSlots with entityTable / NestedSelector fills that spawn into item slots ==="
rg -n "type: ItemSlots" -A15 Resources/Prototypes/Entities/Structures/Machines/chem_master.yml Resources/Prototypes/_Goobstation/Entities/Structures/Machines/dispensers.yml 2>/dev/null

echo "=== search for EntityTableContainerFill component pointing to gas tank belt slot or medkit itemslot chain ==="
rg -n "type: EntityTableContainerFill" -A10 Resources/Prototypes/Entities/Structures/Storage/Closets/*.yml 2>/dev/null | head -100

echo "=== check ClosetEmergency def ==="
rg -n "id: ClosetEmergency\b" -A20 Resources/Prototypes/Entities/Structures/Storage/Closets/*.yml 2>/dev/null

Length of output: 5315


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "=== all startingItem occurrences repo-wide ==="
rg -n "startingItem:" Resources/Prototypes -g '*.yml' | head -80

echo "=== check chem_master.yml full content for portable machine + startingItem/EntityTableContainerFill ==="
rg -n "EntityTableContainerFill|startingItem" Resources/Prototypes/Entities/Structures/Machines/chem_master.yml Resources/Prototypes/_Goobstation/Entities/Objects/Specific/Medical/portable_chem_master.yml 2>/dev/null

echo "=== check EnergyChemDispenser ItemSlots / startingItem for beaker slot ==="
rg -n "type: ItemSlots" -A20 Resources/Prototypes/_Goobstation/Entities/Structures/Machines/dispensers.yml 2>/dev/null

Length of output: 10721


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "=== _Arcane crates medical.yml ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Fills/Crates/medical.yml 2>/dev/null || echo missing

echo "=== _Arcane cargo_medical.yml ==="
cat -n Resources/Prototypes/_Arcane/Catalog/Cargo/cargo_medical.yml 2>/dev/null || echo missing

echo "=== search all entityTable definitions with self-referencing tableId or nested chains among _Arcane files ==="
rg -n "type: entityTable" -A15 Resources/Prototypes/_Arcane -g '*.yml' 2>/dev/null

echo "=== search EntityTableContainerFill anywhere in _Arcane ==="
rg -n "EntityTableContainerFill" -A10 Resources/Prototypes/_Arcane -g '*.yml' 2>/dev/null

echo "=== does MedkitCombatStandard/Advanced base entity (non-filled) have EntityTableContainerFill or ItemSlots? ==="
cat -n Resources/Prototypes/_Arcane/Entities/Objects/Specific/Medical/medkits.yml

Length of output: 4233


🏁 Script executed:

#!/bin/bash
set -uo pipefail
fd -a "ItemSlotsSystem.cs" 2>/dev/null
find / -iname "ItemSlotsSystem.cs" 2>/dev/null | head -5

Length of output: 697


🏁 Script executed:

#!/bin/bash
set -uo pipefail
rg -n "OnMapInit" -B3 -A30 Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs

Length of output: 3108


🏁 Script executed:

#!/bin/bash
set -uo pipefail
fd -a "AllEntitiesTest\.cs|SpawnEntitiesTest" 2>/dev/null
rg -rln "SpawnAndDeleteAllEntitiesInTheSameSpot" Content.IntegrationTests

Length of output: 375


🏁 Script executed:

#!/bin/bash
set -uo pipefail
rg -n "SpawnAndDeleteAllEntitiesInTheSameSpot" Content.IntegrationTests -g '*.cs' || echo none
echo "---"
fd -a "EntityTest.cs"
sed -n '1,220p' Content.IntegrationTests/Tests/EntityTest.cs

Length of output: 13008


🏁 Script executed:

#!/bin/bash
set -uo pipefail
sed -n '218,280p' Content.IntegrationTests/Tests/EntityTest.cs

Length of output: 3187


🏁 Script executed:

#!/bin/bash
set -uo pipefail
python3 - <<'PY'
import re, pathlib, sys, json
from collections import defaultdict

root = pathlib.Path("Resources/Prototypes")
paths = list(root.rglob("*.yml"))

entity_table_fill = {}   # entity_id -> tableId (from EntityTableContainerFill)
table_children = defaultdict(list)  # tableId -> list of ('entity', id) or ('table', id)

cur_type = None
cur_id = None
cur_indent_entity = None
in_etcf = False
in_table_children = False
table_id_stack = []

def parse_file(path):
    text = path.read_text(encoding="utf-8", errors="ignore").splitlines()
    i = 0
    n = len(text)
    cur_type = None
    cur_id = None
    while i < n:
        line = text[i]
        m = re.match(r'^- type:\s*(\S+)\s*$', line)
        if m:
            cur_type = m.group(1)
            cur_id = None
            # look ahead a few lines for id:
            j = i+1
            while j < n and not re.match(r'^-\s*type:', text[j]):
                idm = re.match(r'^\s*id:\s*(\S+)\s*$', text[j])
                if idm:
                    cur_id = idm.group(1)
                    break
                j += 1
        # EntityTableContainerFill within an entity block
        if cur_type == 'entity' and cur_id:
            m2 = re.match(r'^\s*- type: EntityTableContainerFill\s*$', line)
            if m2:
                # scan forward for tableId: under containers: <name>: !type:...  \n tableId: X
                j = i+1
                while j < n and not re.match(r'^-\s*type:', text[j]) and not re.match(r'^\s*- type: \w', text[j]):
                    tidm = re.match(r'^\s*tableId:\s*(\S+)\s*$', text[j])
                    if tidm:
                        entity_table_fill[cur_id] = tidm.group(1)
                        break
                    j += 1
        # entityTable definitions: capture all "id:" lines within table (as entity refs) at any nesting
        if cur_type == 'entityTable' and cur_id:
            m3 = re.match(r'^\s*-?\s*id:\s*(\S+)\s*$', line)
            if m3:
                table_children[cur_id].append(m3.group(1))
            m4 = re.match(r'^\s*tableId:\s*(\S+)\s*$', line)
            if m4:
                table_children[cur_id].append("`@TABLE`:"+m4.group(1))
        i += 1

for p in paths:
    try:
        parse_file(p)
    except Exception as e:
        pass

print("entity_table_fill entries:", len(entity_table_fill))
print("table_children tables:", len(table_children))

# Build cycle detection: entity -> table -> entities (recursively) -> possibly back to entity
def resolve_table(tid, seen_tables=None):
    if seen_tables is None:
        seen_tables = set()
    if tid in seen_tables:
        return set()
    seen_tables.add(tid)
    result = set()
    for child in table_children.get(tid, []):
        if child.startswith("`@TABLE`:"):
            sub = child[7:]
            result |= resolve_table(sub, seen_tables)
        else:
            result.add(child)
    return result

cycles = []
for ent, tid in entity_table_fill.items():
    reachable_entities = resolve_table(tid)
    if ent in reachable_entities:
        cycles.append((ent, tid))

print("DIRECT/INDIRECT CYCLES (entity fills a table that eventually contains itself):")
for c in cycles:
    print(c)
PY

Length of output: 3691


🏁 Script executed:

#!/bin/bash
set -uo pipefail
repo="ArcaneSS14/arcane-old"
echo "=== commit 19 OPTIMIZATIONS FOR SYSTEMS diff stat ==="
git show --stat 9fee338e | head -60
echo "=== files changed ==="
git show --name-only 9fee338e | tail -40

Length of output: 1110


🏁 Script executed:

#!/bin/bash
set -uo pipefail
git show 9fee338e -- Content.Server/Chemistry/EntitySystems/ChemMasterSystem.cs Content.Server/_Orion/Chemistry/EntitySystems/ChemMasterBeakerCapacitySystem.cs

Length of output: 2931


🏁 Script executed:

#!/bin/bash
set -uo pipefail
fd -a "ContainerFillSystem.cs"
rg -n "OnTableMapInit|MaxDepth|depth|Recursion|Cycle" -B3 -A40 Content.Shared/Containers/ContainerFillSystem.cs 2>/dev/null | head -150

Length of output: 3841


@UmbiMax, гляну и по коммитам, и по тесту — вот что нашёл.

Коммиты

Последние 10: 16.9.7…16.9.9.3, 18 NEW SOUNDS FOR BEAKERS, 4.7, 19 OPTIMIZATIONS FOR SYSTEMS, 19.1 Fix inspect wound. Из них 19 OPTIMIZATIONS FOR SYSTEMS трогает только ChemMasterSystem.cs/ChemMasterBeakerCapacitySystem.cs (убрал лишнюю DI-зависимость, переименовал метод) — с текущим крашем не связан.

Тест — SpawnAndDeleteAllEntitiesInTheSameSpot

CI: Passed! Failed: 0, Passed: 180, Skipped: 1, Total: 181 → и сразу Test host process crashed: Stack overflow на 181-м (последнем) тесте. Это именно он — спавнит все non-abstract прототипы одним батчем.

Механизм подтверждён кодом:

  • Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs, Line 159-176: OnMapInit спавнит slot.StartingItem, если он задан.
  • Content.Shared/Containers/ContainerFillSystem.cs, Line 129-161: OnTableMapInit для каждого proto из _entityTable.GetSpawns(table) вызывает Spawn(...) без какой-либо защиты от рекурсии/циклов — если спавн триггерит MapInit сущности, у которой снова есть EntityTableContainerFill/ItemSlots.StartingItem, ведущий по цепочке назад — это гарантированный StackOverflowException, который в .NET нельзя перехватить (поэтому в логе нет имени виновной сущности, просто голый дамп стека).

Что проверил и не нашёл:

  • Grep startingItem: по всему репо — ни один хит не указывает на новые/изменённые Arcane-сущности (медкиты, баллоны, мензурки, медипены, нить/сетки).
  • Написал Python-скрипт, который резолвит все EntityTableContainerFill → tableId и рекурсивно разворачивает entityTable/NestedSelector/GroupSelector, ищет случаи «сущность заполняет таблицу, которая рано или поздно содержит саму эту сущность». Нашёл только DresserBlueshieldOfficerFilled, DresserNanorepFilled, ClosetMaintenanceFilledRandom → MaintenanceLockerLoot — все три не относятся к медбей-PR (не химия/медицина), и это либо предсуществующий баг, либо ложное срабатывание грубого regex-парсера.
  • Наши медкиты (MedkitEmergencyFilled и т.д.) используют StorageFill со статическим списком id — не entityTable, замкнуться сами на себя не могут. Баллоны (EmergencyNitriumTankFilled/EmergencyHealiumTankFilled) — просто GasTank, без ItemSlots/EntityTableContainerFill.

Вывод

Причина точно системная (нет recursion-guard в движке), но статически указать точную сущность-виновника не могу — вероятностные GroupSelector внутри entityTable могут делать поведение теста невоспроизводимым между прогонами, а сам краш нельзя перехватить, чтобы залогировать конкретный ID.

Что нужно от тебя для точной локализации:

  1. Bisect локально: временно откати изменения в FillLockerEmergencyStandard (misc.yml, где ты поднял MedkitEmergencyFilled до prob: 0.3 и убрал MedkitOxygenFilled до 0.005), в duffelbag.yml (синди-набор), и в _Arcane/Catalog/Fills/Items/*.yml — по одному, прогоняя тест после каждого отката.
  2. Либо временно добавь defensive-guard прямо в движок (раз баг общий для всех модов, можно завести отдельный тикет на апстрим/RobustToolbox): счётчик глубины рекурсии в ContainerFillSystem.OnTableMapInit, который логирует ошибку и обрывает цепочку вместо краша всего процесса — это не исправит логическую причину, но не даст тесту (и продакшену) падать намертво.

Если хочешь, могу оформить это как отдельный issue на recursion-guard в ContainerFillSystem/ItemSlotsSystem — независимо от того, найдёшь ли ты конкретную сущность-виновника.

(・_・;)

@UmbiMax

UmbiMax commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai
Может из-за того, что указал в Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml именно delay: 0.5 # Arcane? Хотя должно поддерживать десятичные и более значения.

Данный провал пошёл после коммита bso locker update, хотя возможно был допущен и в прошлых 6 коммитах. Понять бы в чём дело да исправить

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
repo="ArcaneSS14/arcane-old"

echo "=== show commit bso locker update ==="
git show --stat e10f58ee83e997fe841dec5094824f7ef3e2ccbd | head -60
echo "=== full diff of that commit ==="
git show e10f58ee83e997fe841dec5094824f7ef3e2ccbd

echo "=== check delay field type in surgery step / doAfter or wherever used ==="
rg -n "delay:" Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml

Length of output: 1788


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== LockerBlueshieldOfficerFilled definition ==="
rg -rn "id: LockerBlueshieldOfficerFilled\b" Resources/Prototypes -g '*.yml'
rg -n "LockerBlueshieldOfficerFilled" -B5 -A25 Resources/Prototypes/_Goobstation/Entities/Structures/Storage/Closets/*.yml 2>/dev/null

echo "=== find entity definition of LockerBlueshieldOfficerFilled and its parent chain ==="
rg -rn "id: LockerBlueshieldOfficer\b" -A30 Resources/Prototypes -g '*.yml'

echo "=== full heads.yml context around the edit ==="
sed -n '1,80p' Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml

echo "=== what locker uses this fill (the one with children list containing LockerBlueshieldOfficerFilled) ==="
rg -n "type: entity" -B2 -A5 Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml | head -80

Length of output: 1352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== search LockerBlueshieldOfficerFilled (plain) ==="
grep -rn "LockerBlueshieldOfficerFilled" Resources/Prototypes --include="*.yml"

echo "=== search LockerBlueshieldOfficer entity def (not Filled) ==="
grep -rn "id: LockerBlueshieldOfficer$" Resources/Prototypes --include="*.yml"

echo "=== find file defining LockerBlueshieldOfficerFilled entity/fill, print context ==="
for f in $(grep -rl "LockerBlueshieldOfficerFilled" Resources/Prototypes --include="*.yml"); do
  echo "--- $f ---"
  grep -n "LockerBlueshieldOfficerFilled" -B5 -A20 "$f"
done

Length of output: 3807


@UmbiMax, нашёл. delay: 0.5 не при делах — YAML/RobustToolbox float спокойно жрёт десятичные, это не баг.

Причина — коммит bso locker update

Единственное изменение в этом коммите:

- - id: MedkitBSOFilled
+ - id: LockerBlueshieldOfficerFilled # Arcane-Edit: MedBay Rework

в Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml, внутри entityTable с id: LockerFillBlueshieldOfficer.

А LockerBlueshieldOfficerFilled сам объявлен так:

- type: entity
  id: LockerBlueshieldOfficerFilled
  parent: LockerBlueshieldOfficer
  components:
  - type: EntityTableContainerFill
    containers:
      entity_storage: !type:NestedSelector
        tableId: LockerFillBlueshieldOfficer

Получился прямой замкнутый цикл: спавн LockerBlueshieldOfficerFilled → заполняет свой entity_storage из таблицы LockerFillBlueshieldOfficer → эта таблица содержит ещё один LockerBlueshieldOfficerFilled → у него снова EntityTableContainerFill на ту же таблицу → и так до StackOverflowException (неперехватываемый, поэтому в логе теста не было имени виновника — я предполагал это в прошлом ответе, но отфутболил как "несвязанное с медбей PR", ошибся).

ContainerFillSystem.OnTableMapInit не имеет recursion-guard — я об этом писал раньше, тут это и сработало.

Фикс

Откатить строку на MedkitBSOFilled, либо, если цель была реально добавить сейф/локер БСО внутрь шкафчика — указывать не LockerBlueshieldOfficerFilled (который сам себя же и заполняет), а либо пустой/другой вариант локера без EntityTableContainerFill на ту же таблицу, либо развести на два разных tableId, чтобы вложенный локер не тянул родительскую таблицу.

--- a/Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml
+++ b/Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml
@@
-    - id: LockerBlueshieldOfficerFilled # Arcane-Edit: MedBay Rework
+    - id: MedkitBSOFilled

Если цель была осознанная (вложить локер в локер) — заведи отдельный LockerFillBlueshieldOfficerNested-таблицу без самореференции, не переиспользуй LockerFillBlueshieldOfficer.

(・_・;)


✏️ Learnings added
Learnt from: UmbiMax
URL: https://github.com/ArcaneSS14/arcane-old/pull/101

Timestamp: 2026-07-13T14:21:38.595Z
Learning: Для PR `#101` (ArcaneSS14/arcane-old): причина краша StackOverflowException в тесте SpawnAndDeleteAllEntitiesInTheSameSpot — коммит "bso locker update" (e10f58ee83e997fe841dec5094824f7ef3e2ccbd) заменил `MedkitBSOFilled` на `LockerBlueshieldOfficerFilled` в children списке entityTable `LockerFillBlueshieldOfficer` (Resources/Prototypes/_Goobstation/Catalog/Fills/Lockers/heads.yml). Сущность `LockerBlueshieldOfficerFilled` сама заполняет свой container `entity_storage` через EntityTableContainerFill с tableId той же `LockerFillBlueshieldOfficer` — получился прямой самореференцирующийся цикл спавна, вызывающий бесконечную рекурсию через ContainerFillSystem.OnTableMapInit (в движке нет recursion-guard). Не путать с полем `delay:` (декомпозиция float работает нормально с десятичными значениями, это не источник проблемы).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@UmbiMax

UmbiMax commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Хахахахах. Ебать. Как это возможно

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant