Skip to content

hotfix 30.06.2026#1470

Merged
Rxup merged 8 commits into
masterfrom
starving
Jun 30, 2026
Merged

hotfix 30.06.2026#1470
Rxup merged 8 commits into
masterfrom
starving

Conversation

@Rxup

@Rxup Rxup commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Новые возможности
    • Медсканер и окно анализатора здоровья теперь показывают предупреждения о голоде и жажде, включая отдельную причину боли «Голодание».
    • В хирургии добавлен шаг «Купирование боли от голодания» — снимает боль без восстановления сытости.
    • В меню точильни появились “mining points” и кнопка подачи заявки.
    • R&D-консоль теперь нагляднее отображает требования/блокировки и автоматически раскладывает технологии.
  • Исправления
    • Улучшен fallback-логика лечения и точность отображения/снятия связанных состояний.
  • Документация
    • Обновлены локализации медицинских сообщений, хирургии и UI-подсказок.

@github-actions github-actions Bot added S: Untriaged size/L Changes: UI Can be reviewed or fixed by people who are knowledgeable with UI design Changes: Localization Can be reviewed or fixed by people who are knowledgeable with translation and removed size/L labels Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7757d66a-7da9-4885-82b6-83bd1311775c

📥 Commits

Reviewing files that changed from the base of the PR and between d1f38ba and 9d67de7.

📒 Files selected for processing (4)
  • Content.Client/Lathe/UI/LatheBoundUserInterface.cs
  • Content.Client/Lathe/UI/LatheMenu.xaml
  • Content.Client/Lathe/UI/LatheMenu.xaml.cs
  • Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting


Walkthrough

PR переводит голод в starving pain, добавляет хирургическое снятие этой боли, перерабатывает лечение fallback-сценариев, расширяет анализатор здоровья предупреждениями о голоде и жажде, обновляет research UI и прототипы, а также стабилизирует один тест NukeOps.

Changes

Медицинские механики и UI

Layer / File(s) Summary
Общие контракты и состояния
Content.Shared/Backmen/Nutrition/HungerPainTrackerComponent.cs, Content.Shared/Backmen/Surgery/Conditions/SurgeryStarvingPainConditionComponent.cs, Content.Shared/Backmen/Surgery/Effects/Step/SurgeryRelieveStarvingPainEffectComponent.cs, Content.Shared/Backmen/Surgery/Pain/PainSerializable.cs, Content.Shared/Nutrition/Components/HungerComponent.cs, Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs, Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs
Добавлены HungerPainTrackerComponent, SurgeryStarvingPainConditionComponent и SurgeryRelieveStarvingPainEffectComponent; PainType получил Starving; HungerComponent расширен настройками starving pain; HealthAnalyzerUiState и HealthAnalyzerScannedUserMessage получили HungerAlert и ThirstAlert; ThirstSystem.SetThirst обновляет порог и эффекты.
Hunger decay и starvation damage
Content.Shared/Nutrition/EntitySystems/HungerSystem.cs, Resources/Prototypes/Body/species_base.yml, Resources/Prototypes/_Backmen/Body/species_surgery_base.yml, Content.Server/Backmen/Shipwrecked/ShipwreckedRuleSystem.cs
Из DoContinuousHungerEffects убран starvation damage; добавлены ConfigureHungerDecay и ConfigureStarvingPain; прототипы species переведены на baseDecayRate; ShipwreckedRuleSystem использует ConfigureHungerDecay с clearStarvationDamage: true.
Starving pain system
Content.Server/Backmen/Nutrition/HungerPainSystem.cs, Content.Server/Backmen/Surgery/Pain/Systems/ServerPainSystem.cs
Добавлена серверная система, которая накапливает и снимает starving pain, применяет/убирает PainType.Starving и органную травму; в ServerPainSystem этот тип боли исключён из накопления woundPain.
Хирургия снятия starving pain
Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs, Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs, Content.Server/Backmen/Surgery/SurgerySystem.cs, Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml, Resources/Prototypes/_Backmen/Entities/Surgery/surgery_steps.yml, Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/surgery/surgeries.ftl, Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/surgery/surgery_steps.ftl
GetTools стал protected virtual; добавлена проверка хирургии по SurgeryStarvingPainConditionComponent; серверный SurgerySystem расширил GetTools и реализовал шаг снятия starving pain; добавлены прототипы операции/шага и русская локализация.
Healing fallback
Content.Shared/Medical/Healing/HealingSystem.cs, Resources/Locale/en-US/medical/components/healing-component.ftl, Resources/Locale/ru-RU/medical/components/healing-component.ftl, Resources/Locale/*/ss14-ru/prototypes/entities/objects/specific/medical/healing.ftl
HealingSystem получил fallback-резолв цели лечения, новые ветки сообщений и новые строки локализации; описания медицинских предметов обновлены.
Health analyzer satiation alerts
Content.Server/Medical/HealthAnalyzerSystem.cs, Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs, Content.Client/Backmen/Medical/UI/PainCausesDisplay.xaml.cs, Resources/Locale/*/medical/components/health-analyzer-component.ftl
Сервер вычисляет hunger/thirst alerts, клиент показывает их в секции analyzer-satiation, а PainCausesDisplay поддерживает "Starving".
Innate tools and med borg
Content.Server/Tools/Innate/InnateToolSystem.cs, Resources/Prototypes/_Backmen/Entities/Mobs/Player/Silicons/BPLA.yml, Resources/Prototypes/_Backmen/Entities/Mobs/Player/Silicons/BPLA_MED.yml, Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml, Resources/Prototypes/borg_types.yml
InnateToolSystem теперь гарантирует руки для innate-инструментов; DroneBPLAMED получает OmnimedToolINF; BorgModuleSurgery упрощён до OmnimedTool; medical borg получает extra module и surgery module; PlayerSiliconBase получает Sanitized.
Research UI and layout
Content.Shared/Backmen/Research/BkmResearchRequirements.cs, Content.Shared/Backmen/Research/BkmResearchTreeLayout.cs, Content.Shared/Research/Prototypes/TechnologyPrototype.cs, Content.Client/Backmen/Research/UI/FancyResearchConsoleMenu.xaml.cs, Content.Client/Backmen/Research/UI/FancyTechnologyInfoPanel.xaml.cs, Resources/Locale/*/_Goobstation/research/ui.ftl, Resources/Prototypes/Research/*, Resources/Prototypes/_Backmen/Research/*, Content.IntegrationTests/Tests/Backmen/TechnologyPrototypePositionTest.cs
Добавлены расчёт требований исследования и автолэйаут, TechnologyPrototype.Position стал nullable, UI консоли использует новые layout/requirements хелперы, а из research-прототипов убраны ручные position; обновлены строки локализации и тест на позиции технологий.
Интеграционные тесты
Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs, Content.IntegrationTests/Tests/Backmen/Surgery/StarvingPainSurgeryTest.cs, Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs, Content.IntegrationTests/Tests/Backmen/Medical/HealthAnalyzerSatiationTest.cs, Content.IntegrationTests/Tests/Backmen/Surgery/SiliconSurgeryToolsTest.cs
Добавлены проверки starving pain, хирургии снятия боли, fallback-лечения, alert-ов анализатора и поведения innate-инструментов меддрона.

NukeOps breathing stabilization

Layer / File(s) Summary
NukeOps damage reset test
Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
Тест добавляет подготовку состояния дыхания и принудительное снятие накопленного урона до проверки цикла сбоев.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Я прыгнул в код — и хрум, и хоп,
Боль голоданья стала flow of hope.
Скальпель поёт, research в ряд стоит,
А зайка в лукошке довольный сидит.
И если alert моргнёт в ночи —
Я хрумну морковку и всё починю!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive Заголовок слишком общий и не описывает конкретное изменение в этом наборе правок. Укажите основную суть изменений, например про голодную боль, UI health analyzer или хирургические правки, в одной короткой фразе.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch starving

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
Content.Shared/Nutrition/Components/HungerComponent.cs (1)

155-170: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Новые поля попадают под [Access(typeof(HungerSystem))].

Класс объявлен с [Access(typeof(HungerSystem))], поэтому прямое чтение этих полей из HungerPainSystem может нарушить access-анализатор. См. подробный комментарий и проверку в HungerPainSystem.cs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Nutrition/Components/HungerComponent.cs` around lines 155 -
170, The new starvation pain fields in HungerComponent are being read from
HungerPainSystem even though HungerComponent is marked with
[Access(typeof(HungerSystem))], so update the access pattern to satisfy the
analyzer. Use HungerSystem as the sole direct accessor for StarvingPainEnabled,
StarvingPainGrowthRate, StarvingPainMax, StarvingPainDecayRate, and
StarvingOrganTraumaThreshold, and have HungerPainSystem obtain the values
through HungerSystem or another approved boundary instead of reading the
component fields directly.
🧹 Nitpick comments (3)
Content.Shared/Medical/Healing/HealingSystem.cs (1)

383-414: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Дублирование логики разрешения цели/попапов.

Блок повторно вычисляет totalBleeds, stuffToHeal и три ветки fallback-попапов, которые по сути дублируют логику TryResolveHealTarget/GetTotalBleeds. Это расходящаяся копия (здесь stuffToHeal ключуется по damage.Key, а в TryResolveHealTarget — по damage.Key.Id). Рассмотрите вынос построения попап-сообщения в общий хелпер, чтобы пороги и условия не разъезжались между двумя местами.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Medical/Healing/HealingSystem.cs` around lines 383 - 414, This
block duplicates the healing target resolution and fallback popup logic, and the
duplicated `stuffToHeal` filter is inconsistent with `TryResolveHealTarget`
because it keys by `damage.Key` instead of `damage.Key.Id`. Refactor the popup
decision into a shared helper used by `HealingSystem` alongside
`TryResolveHealTarget`/`GetTotalBleeds`, and make both paths derive
`totalBleeds`, `stuffToHeal`, and the three fallback messages from the same
logic so thresholds and conditions cannot drift.
Content.Server/Backmen/Nutrition/HungerPainSystem.cs (1)

27-104: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Обновление модификатора боли каждый кадр может быть дорогим.

ApplyStarvingPain вызывает TryChangePainModifier на каждом тике для каждой голодающей сущности, что каждый раз триггерит UpdateNerveSystemPain (пересчёт + Dirty). При большом числе мобов это заметная нагрузка. Рассмотрите аккумулятор/интервал обновления (например, раз в N секунд или только при изменении CurrentStarvingPain сверх порога).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` around lines 27 - 104,
The `HungerPainSystem.Update` path is recalculating and dirtying nerve-system
pain every frame for each starving entity via `ApplyStarvingPain`, which is too
expensive. Update the logic so pain modifier changes are applied only when
`CurrentStarvingPain` changes meaningfully or on a fixed interval, and avoid
calling `TryChangePainModifier`/`UpdateNerveSystemPain` every tick unless the
value actually needs to change. Use the existing `HungerPainSystem`,
`ApplyStarvingPain`, and `ClearStarvingPain` flow to add a small accumulator or
threshold-based guard.
Content.Shared/Nutrition/EntitySystems/HungerSystem.cs (1)

252-258: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Зафиксируйте авторитетное значение голода до смены ActualDecayRate.

GetHunger вычисляет значение как LastAuthoritativeHungerValue - dt * ActualDecayRate. Здесь ActualDecayRate уже обновлён (строка 254) к моменту вызова SetAuthoritativeHungerValue(..., GetHunger(component)) (строка 258), поэтому накопленный за dt decay пересчитывается по новой ставке задним числом — при заметном dt это даёт скачок голода. Стоит сначала пересчитать авторитетное значение по старой ставке, затем менять BaseDecayRate/ActualDecayRate.

♻️ Предлагаемый порядок операций
         component.BaseDecayRate = baseDecayRate;
 
         if (clearStarvationDamage)
             component.StarvationDamage = null;
 
+        SetAuthoritativeHungerValue((uid, component), GetHunger(component));
+
         if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
         {
             component.ActualDecayRate = component.BaseDecayRate * modifier;
             DirtyField(uid, component, nameof(HungerComponent.ActualDecayRate));
         }
-
-        SetAuthoritativeHungerValue((uid, component), GetHunger(component));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Nutrition/EntitySystems/HungerSystem.cs` around lines 252 -
258, `HungerSystem` is recalculating the authoritative hunger value after
`ActualDecayRate` has already been updated, which retroactively applies the new
decay rate to the whole elapsed `dt`. In the `SetAuthoritativeHungerValue` flow
around `GetHunger(component)`, first compute and store the authoritative hunger
using the current rate, then update `BaseDecayRate`/`ActualDecayRate` and dirty
the field. Keep the fix localized to the `HungerSystem` logic so `GetHunger` and
`SetAuthoritativeHungerValue` continue to use the correct pre-change rate for
the elapsed interval.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs`:
- Around line 83-105: Add an explicit assertion in HealingFallbackTest around
the chest setup to verify the chest is actually in heavy bleeding state before
healing. Use the existing bodySys/woundSys setup after damage is applied, and
assert via the chest target obtained from TryGetWoundableTargetByType that the
piercing damage is classified as heavy bleeding so the test exercises the
intended branch instead of only validating Cold removal.

In `@Content.IntegrationTests/Tests/Backmen/Surgery/StarvingPainSurgeryTest.cs`:
- Around line 71-72: The StarvingPainSurgeryTest check is too weak because it
only asserts HungerComponent.CurrentThreshold, so it can miss changes to the
actual hunger value. In the test around GetComponent<HungerComponent>(patient),
capture the patient’s hunger value before the surgery step and compare it after
the operation to ensure the hunger itself remains unchanged while the pain is
removed.

In `@Content.Server/Tools/Innate/InnateToolSystem.cs`:
- Around line 71-94: In EnsureHandsForTools, the hand count is based on
InnateToolComponent.Tools.Count, which does not match the actual number of
spawned tools after EntitySpawnCollection.GetSpawns expands amount/prob entries.
Update the logic to count the real expanded spawn list used for spawning, and
use that value when adding hands so TrySpawnOneInHand never runs out of
available hands; keep the fix localized to EnsureHandsForTools and the spawn
flow that feeds it.

In `@Content.Shared/Medical/Healing/HealingSystem.cs`:
- Around line 420-477: TryResolveHealTarget in HealingSystem uses _random.Pick
for the fallback candidate selection, which is unsafe in the shared predicted
path and can desync later random calls; replace it with a deterministic
selection strategy or move the fallback to a server-only path. While updating
this logic, avoid calling TryCandidate twice for the final chosen woundable and
either remove usedFallback if it is truly unused or thread it into the caller if
it is meant to affect behavior.

---

Duplicate comments:
In `@Content.Shared/Nutrition/Components/HungerComponent.cs`:
- Around line 155-170: The new starvation pain fields in HungerComponent are
being read from HungerPainSystem even though HungerComponent is marked with
[Access(typeof(HungerSystem))], so update the access pattern to satisfy the
analyzer. Use HungerSystem as the sole direct accessor for StarvingPainEnabled,
StarvingPainGrowthRate, StarvingPainMax, StarvingPainDecayRate, and
StarvingOrganTraumaThreshold, and have HungerPainSystem obtain the values
through HungerSystem or another approved boundary instead of reading the
component fields directly.

---

Nitpick comments:
In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs`:
- Around line 27-104: The `HungerPainSystem.Update` path is recalculating and
dirtying nerve-system pain every frame for each starving entity via
`ApplyStarvingPain`, which is too expensive. Update the logic so pain modifier
changes are applied only when `CurrentStarvingPain` changes meaningfully or on a
fixed interval, and avoid calling
`TryChangePainModifier`/`UpdateNerveSystemPain` every tick unless the value
actually needs to change. Use the existing `HungerPainSystem`,
`ApplyStarvingPain`, and `ClearStarvingPain` flow to add a small accumulator or
threshold-based guard.

In `@Content.Shared/Medical/Healing/HealingSystem.cs`:
- Around line 383-414: This block duplicates the healing target resolution and
fallback popup logic, and the duplicated `stuffToHeal` filter is inconsistent
with `TryResolveHealTarget` because it keys by `damage.Key` instead of
`damage.Key.Id`. Refactor the popup decision into a shared helper used by
`HealingSystem` alongside `TryResolveHealTarget`/`GetTotalBleeds`, and make both
paths derive `totalBleeds`, `stuffToHeal`, and the three fallback messages from
the same logic so thresholds and conditions cannot drift.

In `@Content.Shared/Nutrition/EntitySystems/HungerSystem.cs`:
- Around line 252-258: `HungerSystem` is recalculating the authoritative hunger
value after `ActualDecayRate` has already been updated, which retroactively
applies the new decay rate to the whole elapsed `dt`. In the
`SetAuthoritativeHungerValue` flow around `GetHunger(component)`, first compute
and store the authoritative hunger using the current rate, then update
`BaseDecayRate`/`ActualDecayRate` and dirty the field. Keep the fix localized to
the `HungerSystem` logic so `GetHunger` and `SetAuthoritativeHungerValue`
continue to use the correct pre-change rate for the elapsed interval.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: df18a891-7540-4be3-80eb-7404b82ed62a

📥 Commits

Reviewing files that changed from the base of the PR and between de04b7f and fe6c380.

📒 Files selected for processing (40)
  • Content.Client/Backmen/Medical/UI/PainCausesDisplay.xaml.cs
  • Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
  • Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs
  • Content.IntegrationTests/Tests/Backmen/Medical/HealthAnalyzerSatiationTest.cs
  • Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs
  • Content.IntegrationTests/Tests/Backmen/Surgery/SiliconSurgeryToolsTest.cs
  • Content.IntegrationTests/Tests/Backmen/Surgery/StarvingPainSurgeryTest.cs
  • Content.Server/Backmen/Nutrition/HungerPainSystem.cs
  • Content.Server/Backmen/Shipwrecked/ShipwreckedRuleSystem.cs
  • Content.Server/Backmen/Surgery/Pain/Systems/ServerPainSystem.cs
  • Content.Server/Backmen/Surgery/SurgerySystem.cs
  • Content.Server/Medical/HealthAnalyzerSystem.cs
  • Content.Server/Tools/Innate/InnateToolSystem.cs
  • Content.Shared/Backmen/Nutrition/HungerPainTrackerComponent.cs
  • Content.Shared/Backmen/Surgery/Conditions/SurgeryStarvingPainConditionComponent.cs
  • Content.Shared/Backmen/Surgery/Effects/Step/SurgeryRelieveStarvingPainEffectComponent.cs
  • Content.Shared/Backmen/Surgery/Pain/PainSerializable.cs
  • Content.Shared/Backmen/Surgery/SharedSurgerySystem.Steps.cs
  • Content.Shared/Backmen/Surgery/SharedSurgerySystem.cs
  • Content.Shared/Medical/Healing/HealingSystem.cs
  • Content.Shared/MedicalScanner/HealthAnalyzerScannedUserMessage.cs
  • Content.Shared/Nutrition/Components/HungerComponent.cs
  • Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
  • Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs
  • Resources/Locale/en-US/medical/components/healing-component.ftl
  • Resources/Locale/en-US/medical/components/health-analyzer-component.ftl
  • Resources/Locale/en-US/ss14-ru/prototypes/entities/objects/specific/medical/healing.ftl
  • Resources/Locale/ru-RU/medical/components/healing-component.ftl
  • Resources/Locale/ru-RU/medical/components/health-analyzer-component.ftl
  • Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/surgery/surgeries.ftl
  • Resources/Locale/ru-RU/ss14-ru/prototypes/_backmen/entities/surgery/surgery_steps.ftl
  • Resources/Locale/ru-RU/ss14-ru/prototypes/entities/objects/specific/medical/healing.ftl
  • Resources/Prototypes/Body/species_base.yml
  • Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
  • Resources/Prototypes/_Backmen/Body/species_surgery_base.yml
  • Resources/Prototypes/_Backmen/Entities/Mobs/Player/Silicons/BPLA.yml
  • Resources/Prototypes/_Backmen/Entities/Mobs/Player/Silicons/BPLA_MED.yml
  • Resources/Prototypes/_Backmen/Entities/Surgery/surgeries.yml
  • Resources/Prototypes/_Backmen/Entities/Surgery/surgery_steps.yml
  • Resources/Prototypes/borg_types.yml

Comment on lines +83 to +105
var chestDamage = new DamageSpecifier
{
DamageDict =
{
["Cold"] = FixedPoint2.New(4),
["Piercing"] = FixedPoint2.New(30),
},
};
damageSys.ChangeDamage(patient, chestDamage, targetPart: TargetBodyPart.Chest);

Assert.That(bodySys.TryGetWoundableTargetByType(patient, BodyPartType.Chest, null, out var chest), Is.True);
Assert.That(woundSys.HasDamageOfType(chest, "Cold"), Is.True);

var targeting = Server.EntMan.EnsureComponent<TargetingComponent>(healer);
targeting.Target = TargetBodyPart.Chest;
});

await RaiseHealingDoAfter(patient, healer, ointment);

await Server.WaitAssertion(() =>
{
Assert.That(bodySys.TryGetWoundableTargetByType(patient, BodyPartType.Chest, null, out var chest), Is.True);
Assert.That(woundSys.HasDamageOfType(chest, "Cold"), Is.False);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Зафиксируйте в тесте, что на груди действительно есть сильное кровотечение.

Иначе кейс про heavy bleeding может зелёным пройти мимо нужной ветки: если 30 Piercing перестанет считаться тяжёлым кровотечением, тест всё равно останется зелёным и будет проверять только обычное снятие Cold.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs` around
lines 83 - 105, Add an explicit assertion in HealingFallbackTest around the
chest setup to verify the chest is actually in heavy bleeding state before
healing. Use the existing bodySys/woundSys setup after damage is applied, and
assert via the chest target obtained from TryGetWoundableTargetByType that the
piercing damage is classified as heavy bleeding so the test exercises the
intended branch instead of only validating Cold removal.

Comment on lines +71 to +72
var hunger = Server.EntMan.GetComponent<HungerComponent>(patient);
Assert.That(hunger.CurrentThreshold, Is.LessThanOrEqualTo(HungerThreshold.Starving));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Проверяйте неизменность самого значения голода, а не только порога.

Сейчас тест пройдёт даже если шаг операции изменит hunger внутри диапазона Starving. Для контракта “снимает боль, не сбрасывает голод” лучше сохранить значение до операции и сравнить его после.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.IntegrationTests/Tests/Backmen/Surgery/StarvingPainSurgeryTest.cs`
around lines 71 - 72, The StarvingPainSurgeryTest check is too weak because it
only asserts HungerComponent.CurrentThreshold, so it can miss changes to the
actual hunger value. In the test around GetComponent<HungerComponent>(patient),
capture the patient’s hunger value before the surgery step and compare it after
the operation to ensure the hunger itself remains unchanged while the pain is
removed.

Comment on lines +71 to +94
// start-backmen: innate-tool-auto-hands
private void EnsureHandsForTools(EntityUid uid, InnateToolComponent component)
{
if (!TryComp<HandsComponent>(uid, out var hands))
return;

var requiredHands = component.Tools.Count;
var handIndex = 3;
var handCount = 0;
foreach (var _ in _sharedHandsSystem.EnumerateHands((uid, hands)))
handCount++;

while (handCount < requiredHands)
{
string handId;
do
{
handId = $"innate-{handIndex++}";
} while (_sharedHandsSystem.TryGetHand((uid, hands), handId, out _));

_sharedHandsSystem.AddHand((uid, hands), handId, HandLocation.Middle);
handCount++;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Проверяем определение EntitySpawnEntry/GetSpawns и наличие amount/probability,
# а также порядок инициализации ToSpawn.
rg -nP --type=cs -C3 'class\s+EntitySpawnEntry'
rg -nP --type=cs -C3 'GetSpawns\s*\('
rg -nP --type=cs -C3 '\bToSpawn\b' Content.Server/Tools/Innate/InnateToolSystem.cs

Repository: Rxup/space-station-14

Length of output: 159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Files\n'
git ls-files 'Content.Server/Tools/Innate/*' 'Content.Shared/*Spawn*' 'Content.*/*Spawn*' | sed -n '1,200p'

printf '\n## Search for relevant symbols\n'
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' '\b(EntitySpawnCollection|EntitySpawnEntry|GetSpawns|ToSpawn|TryFillHands|TrySpawnOneInHand|EnsureHandsForTools)\b' .

printf '\n## Outline candidate files if found\n'
for f in \
  Content.Server/Tools/Innate/InnateToolSystem.cs \
  Content.Shared/ContentEntitySpawn.cs \
  Content.Shared/EntitySpawnCollection.cs \
  Content.Shared/EntitySpawnEntry.cs \
  ; do
  [ -f "$f" ] && { echo "### $f"; wc -l "$f"; ast-grep outline "$f" --view expanded || true; }
done

Repository: Rxup/space-station-14

Length of output: 28095


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Innate tool system\n'
wc -l Content.Server/Tools/Innate/InnateToolSystem.cs
sed -n '1,220p' Content.Server/Tools/Innate/InnateToolSystem.cs

printf '\n## Spawn-related symbols in repo\n'
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' '\bToSpawn\b|\bGetSpawns\b|\bEntitySpawnEntry\b|\bEntitySpawnCollection\b' Content.Shared Content.Server | sed -n '1,220p'

Repository: Rxup/space-station-14

Length of output: 23281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## EntitySpawnEntry / EntitySpawnCollection\n'
sed -n '1,340p' Content.Shared/Storage/EntitySpawnEntry.cs

printf '\n## Innate tool component\n'
sed -n '1,120p' Content.Server/Tools/Innate/InnateToolComponent.cs

printf '\n## Nearby body events / spawn ordering cues\n'
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' '\bInitialBodySpawnedEvent\b|\bMapInitEvent\b|\bInitialBodySystem\b' Content.Server Content.Shared | sed -n '1,220p'

Repository: Rxup/space-station-14

Length of output: 42250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## InitialBodySystem and event definitions\n'
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' '\bInitialBodySpawnedEvent\b|\bclass\s+InitialBodySystem\b|\bstruct\s+InitialBodySpawnedEvent\b|\brecord\s+InitialBodySpawnedEvent\b' Content.Server Content.Shared | sed -n '1,220p'

printf '\n## InnateToolSystem subscriptions / shutdown paths\n'
sed -n '1,180p' Content.Server/Tools/Innate/InnateToolSystem.cs

printf '\n## Search for cleanup of added hands in similar systems\n'
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' 'RemoveHand|DeleteHand|TryDrop\(|HandsComponent' Content.Server/Tools Content.Server/Body Content.Server | sed -n '1,220p'

Repository: Rxup/space-station-14

Length of output: 16523


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## InitialBodySystem\n'
sed -n '1,120p' Content.Shared/Body/InitialBodySystem.cs

printf '\n## InitialBodySpawnedEvent definition\n'
sed -n '1,80p' Content.Shared/Body/Events/InitialBodyEvents.cs

printf '\n## Any explicit ordering in InnateToolSystem registration\n'
sed -n '1,80p' Content.Server/Tools/Innate/InnateToolSystem.cs

Repository: Rxup/space-station-14

Length of output: 5237


Считать руки по фактическому числу спавнов, а не по Tools.Count.
EntitySpawnCollection.GetSpawns(...) разворачивает amount/prob, поэтому при Tools.Count часть инструментов может остаться без руки и быть удалена в TrySpawnOneInHand. ToSpawn.Count здесь тоже не универсален: в OnInitialBodySpawned список ещё пуст, так что нужен подсчёт из уже расширенного списка.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Tools/Innate/InnateToolSystem.cs` around lines 71 - 94, In
EnsureHandsForTools, the hand count is based on InnateToolComponent.Tools.Count,
which does not match the actual number of spawned tools after
EntitySpawnCollection.GetSpawns expands amount/prob entries. Update the logic to
count the real expanded spawn list used for spawning, and use that value when
adding hands so TrySpawnOneInHand never runs out of available hands; keep the
fix localized to EnsureHandsForTools and the spawn flow that feeds it.

Comment on lines +420 to +477
private bool TryResolveHealTarget(
EntityUid body,
EntityUid healer,
HealingComponent healing,
out EntityUid woundable,
out Dictionary<string, FixedPoint2> stuffToHeal,
out bool usedFallback)
{
woundable = EntityUid.Invalid;
stuffToHeal = new Dictionary<string, FixedPoint2>();
usedFallback = false;

if (!TryComp<TargetingComponent>(healer, out var targeting))
return false;

bool TryCandidate(EntityUid candidate, out Dictionary<string, FixedPoint2> healDict)
{
healDict = new Dictionary<string, FixedPoint2>();
if (!TryComp<WoundableComponent>(candidate, out var woundableComp))
return false;

healDict = healing.Damage.DamageDict
.Where(damage => _wounds.HasDamageOfType(candidate, damage.Key))
.ToDictionary(damage => damage.Key.Id, damage => damage.Value);

var bleeds = GetTotalBleeds(candidate, woundableComp);
if (bleeds > healing.UnableToHealBleedsThreshold
&& (healing.BloodlossModifier != 0 || healDict.Count == 0))
return false;

return healDict.Count > 0
|| healing.BloodlossModifier != 0
|| healing.ModifyBloodLevel > 0;
}

var (partType, symmetry) = _bodySystem.ConvertTargetBodyPart(targeting.Target);
if (_bodySystem.TryGetWoundableTargetByType(body, partType, symmetry, out var selected)
&& TryCandidate(selected, out stuffToHeal))
{
woundable = selected;
return true;
}

var candidates = new List<EntityUid>();
foreach (var candidate in _bodySystem.GetWoundableTargets(body))
{
if (TryCandidate(candidate, out _))
candidates.Add(candidate);
}

if (candidates.Count == 0)
return false;

usedFallback = true;
woundable = _random.Pick(candidates);
TryCandidate(woundable, out stuffToHeal);
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Подтвердить, что HealingSystem — общий/предсказываемый
rg -nP 'class\s+HealingSystem' --type=cs -C2
rg -nP 'HealingDoAfterEvent|UseInHandEvent|AfterInteractEvent' Content.Shared/Medical/Healing/HealingSystem.cs -n

Repository: Rxup/space-station-14

Length of output: 159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the system and inspect the relevant implementation.
git ls-files 'Content.Shared/Medical/Healing/*' 'Content.Shared/**/HealingSystem.cs' | sed 's#^`#FILE` #'
echo '---'
wc -l Content.Shared/Medical/Healing/HealingSystem.cs
echo '---'
sed -n '1,220p' Content.Shared/Medical/Healing/HealingSystem.cs
echo '---'
sed -n '220,520p' Content.Shared/Medical/Healing/HealingSystem.cs
echo '---'
# Check how random is provided and whether this system participates in prediction/event flows.
rg -n "IRobustRandom|_random|UseInHandEvent|AfterInteractEvent|DoAfterEvent|Predict|predicted|Prediction" Content.Shared/Medical/Healing Content.Shared -g '*.cs'

Repository: Rxup/space-station-14

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- TryHeal / end of HealingSystem.cs ---'
sed -n '520,597p' Content.Shared/Medical/Healing/HealingSystem.cs

echo '--- HealingDoAfterEvent definition ---'
rg -n "class\s+HealingDoAfterEvent|record\s+HealingDoAfterEvent|HealingDoAfterEvent" Content.Shared Content.Server -g '*.cs' -C 2

echo '--- prediction/timing guards in HealingSystem.cs ---'
rg -n "_timing|IsFirstTimePredicted|Predicted|Prediction|DoAfterArgs" Content.Shared/Medical/Healing/HealingSystem.cs -C 2

Repository: Rxup/space-station-14

Length of output: 6672


Не используйте _random.Pick в TryResolveHealTarget для предсказываемого shared-пути
HealingSystem обрабатывает UseInHandEvent/AfterInteractEvent и HealingDoAfterEvent в Content.Shared, поэтому этот fallback выполняется и на клиенте. _random.Pick(candidates) сдвигает RNG и может разъехать последующие предсказываемые случайные вызовы. Для fallback нужен детерминированный выбор или серверный путь; заодно TryCandidate для выбранного кандидата вызывается дважды, а usedFallback сейчас не используется.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Medical/Healing/HealingSystem.cs` around lines 420 - 477,
TryResolveHealTarget in HealingSystem uses _random.Pick for the fallback
candidate selection, which is unsafe in the shared predicted path and can desync
later random calls; replace it with a deterministic selection strategy or move
the fallback to a server-only path. While updating this logic, avoid calling
TryCandidate twice for the final chosen woundable and either remove usedFallback
if it is truly unused or thread it into the caller if it is meant to affect
behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs (1)

116-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Не обращайтесь к причине боли через индексатор словаря.

Здесь ?["Starving"] всё ещё бросит KeyNotFoundException, если запись ещё не появилась. Тогда тест упадёт исключением вместо нормальной проверки через Assert. Лучше читать значение через TryGetValue, как вы уже делаете ниже в этом же тесте.

Предлагаемое исправление
         await Server.WaitAssertion(() =>
         {
-            painBeforeEat = consciousnessSys.GetPainCauses(human)?["Starving"] ?? 0;
+            var causes = consciousnessSys.GetPainCauses(human);
+            painBeforeEat = causes != null && causes.TryGetValue("Starving", out var starving) ? starving : 0;
             Assert.That(painBeforeEat, Is.GreaterThan(0));
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs` around
lines 116 - 119, The HungerPainTest assertion still reads the “Starving” pain
cause with a dictionary indexer inside Server.WaitAssertion, so it can throw
before the Assert runs; update the pain lookup in this block to use TryGetValue
on the result of consciousnessSys.GetPainCauses(human) instead of ?["Starving"],
matching the safer pattern already used later in the same test, and keep the
assertion on the retrieved value.
Content.Server/Backmen/Nutrition/HungerPainSystem.cs (4)

97-100: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Не сбрасывайте флаг травмы без удаления модификатора.

Если CurrentStarvingPain уже обнулили извне, Line 99 теряет признак применённой "StarvingOrganTrauma", не вызывая TryRemoveOrganDamageModifier; модификатор может остаться навсегда.

Предлагаемое исправление
                 else
                 {
-                    tracker.StarvingOrganTraumaApplied = false;
+                    ClearStarvingPain(uid, tracker, nerveSys.Value, chest);
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` around lines 97 - 100,
In HungerPainSystem, the else branch in the starvation pain update clears
tracker.StarvingOrganTraumaApplied without removing the applied organ damage
modifier, which can leave StarvingOrganTrauma stuck on the tracker. Update the
logic around the CurrentStarvingPain/StarvingOrganTraumaApplied handling to call
TryRemoveOrganDamageModifier before resetting the flag, and keep the flag/state
transitions in sync wherever StarvingOrganTrauma is applied or cleared.

70-78: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Выставляйте StarvingOrganTraumaApplied только после успешного добавления травмы.

Сейчас флаг становится true, даже если TryAddOrganDamageModifier вернул false; это блокирует повторное применение и рассинхронизирует трекер с реальным trauma-модификатором.

Предлагаемое исправление
-                    tracker.StarvingOrganTraumaApplied = true;
-                    _trauma.TryAddOrganDamageModifier(
+                    if (_trauma.TryAddOrganDamageModifier(
                         chest,
                         FixedPoint2.New(5),
                         uid,
-                        "StarvingOrganTrauma");
+                        "StarvingOrganTrauma"))
+                    {
+                        tracker.StarvingOrganTraumaApplied = true;
+                    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` around lines 70 - 78,
The starvation trauma flag in HungerPainSystem is being set before confirming
that TryAddOrganDamageModifier succeeded, which can leave
StarvingOrganTraumaApplied out of sync with the actual modifier state. Update
the HungerPainSystem logic around the chest/StarvingOrganTrauma path so
tracker.StarvingOrganTraumaApplied is assigned only when
_trauma.TryAddOrganDamageModifier returns success, and leave it false on failure
so the attempt can be retried.

145-157: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Не блокируйте локальную очистку отсутствием nerve/chest.

Когда ClearStarvingPain вызывается из ветки отсутствующей нервной системы, Line 148 возвращает до сброса CurrentStarvingPain; аналогично Line 156–157 блокирует cleanup при отсутствующей груди. Разделите удаление внешних модификаторов и сброс локального трекера.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` around lines 145 - 157,
In ClearStarvingPain, separate the local cleanup of CurrentStarvingPain from the
optional nerve/chest lookup paths: if nerveSys or chest are missing, do not
return before clearing the hunger pain state. Keep the external modifier removal
conditional on resolving nerveUid/chestUid, but always reset the local tracker
first, using the existing ClearStarvingPain logic and the nerve/chest lookup
branches as the fix points.

107-113: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Синхронизируйте реальный pain-модификатор при ResetStarvingPain.

Метод меняет только трекер; активный PainType.Starving в PainSystem остаётся со старым значением до следующего Update, а при сбросе в 0 может не удалиться корректно.

Предлагаемое направление исправления
     public void ResetStarvingPain(EntityUid body, float remainingPain = 0)
     {
         if (!TryComp<HungerPainTrackerComponent>(body, out var tracker))
             return;
 
+        if (remainingPain <= 0)
+        {
+            ClearStarvingPain(body, tracker);
+            return;
+        }
+
         tracker.CurrentStarvingPain = remainingPain;
+
+        if (_consciousness.TryGetNerveSystem(body, out var nerveSys) &&
+            _body.TryGetWoundableTargetByType(body, BodyPartType.Chest, null, out var chest))
+        {
+            ApplyStarvingPain(nerveSys.Value, chest, tracker);
+        }
+
         Dirty(body, tracker);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` around lines 107 - 113,
`ResetStarvingPain` only updates `HungerPainTrackerComponent`, so the live
`PainSystem` modifier for `PainType.Starving` can stay stale until the next
update or fail to clear when resetting to zero. After setting
`tracker.CurrentStarvingPain`, also synchronize the corresponding starving pain
entry in `PainSystem` for the same `EntityUid` by updating/removing the active
modifier through the existing pain-management path used by `PainSystem` so the
runtime value matches the tracker immediately.
🧹 Nitpick comments (1)
Content.Server/Backmen/Nutrition/HungerPainSystem.cs (1)

103-103: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Вызывайте Dirty только при реальном изменении трекера.

Сейчас стабильные сущности помечаются dirty каждый тик, а после ClearStarvingPain возможен повторный Dirty в том же кадре. Лучше завести локальный dirty-флаг и выставлять его только при изменении полей трекера.

Also applies to: 169-170

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs` at line 103, The
HungerPainSystem is marking entities dirty unconditionally, which can trigger
redundant Dirty calls every tick and again after ClearStarvingPain in the same
frame. Update the logic around the tracker mutation in HungerPainSystem so a
local dirty flag is set only when tracker fields actually change, and call
Dirty(uid, tracker) only if that flag is true. Use the existing HungerPainSystem
methods and tracker updates to keep the dirtying behavior tied to real state
changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.Shared/Backmen/Research/BkmResearchRequirements.cs`:
- Around line 73-84: `HasUnmetRequirements` is treating a missing
`TechnologyDatabaseComponent` as if all requirements were satisfied, which
incorrectly makes `FancyTechnologyInfoPanel.InitializePrerequisites()` show the
no-requirements state for unknown tiers/discipline gates. Update
`BkmResearchRequirements.HasUnmetRequirements` so that a null `component` is
handled as “requirements cannot be verified” rather than returning false; keep
the prerequisite check using `SharedResearchSystem.IsTechnologyUnlocked`, and
make the method report unmet requirements whenever the database component needed
to evaluate tier/discipline gating is absent.

In `@Content.Shared/Backmen/Research/BkmResearchTreeLayout.cs`:
- Around line 70-100: The mixed auto-layout/manual Position path in
BuildDisciplineCells does not reserve space for overridden coordinates, so the
computed DisciplineCell Width/Height can be too small when
FancyResearchConsoleMenu later prefers proto.Position. Update
BuildDisciplineCells to include any manual Position overrides in the bounds
calculation, or explicitly reject mixed auto/manual placement, and make sure the
sizing logic in DisciplineCell matches the placement logic used by
FancyResearchConsoleMenu.

---

Outside diff comments:
In `@Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs`:
- Around line 116-119: The HungerPainTest assertion still reads the “Starving”
pain cause with a dictionary indexer inside Server.WaitAssertion, so it can
throw before the Assert runs; update the pain lookup in this block to use
TryGetValue on the result of consciousnessSys.GetPainCauses(human) instead of
?["Starving"], matching the safer pattern already used later in the same test,
and keep the assertion on the retrieved value.

In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs`:
- Around line 97-100: In HungerPainSystem, the else branch in the starvation
pain update clears tracker.StarvingOrganTraumaApplied without removing the
applied organ damage modifier, which can leave StarvingOrganTrauma stuck on the
tracker. Update the logic around the
CurrentStarvingPain/StarvingOrganTraumaApplied handling to call
TryRemoveOrganDamageModifier before resetting the flag, and keep the flag/state
transitions in sync wherever StarvingOrganTrauma is applied or cleared.
- Around line 70-78: The starvation trauma flag in HungerPainSystem is being set
before confirming that TryAddOrganDamageModifier succeeded, which can leave
StarvingOrganTraumaApplied out of sync with the actual modifier state. Update
the HungerPainSystem logic around the chest/StarvingOrganTrauma path so
tracker.StarvingOrganTraumaApplied is assigned only when
_trauma.TryAddOrganDamageModifier returns success, and leave it false on failure
so the attempt can be retried.
- Around line 145-157: In ClearStarvingPain, separate the local cleanup of
CurrentStarvingPain from the optional nerve/chest lookup paths: if nerveSys or
chest are missing, do not return before clearing the hunger pain state. Keep the
external modifier removal conditional on resolving nerveUid/chestUid, but always
reset the local tracker first, using the existing ClearStarvingPain logic and
the nerve/chest lookup branches as the fix points.
- Around line 107-113: `ResetStarvingPain` only updates
`HungerPainTrackerComponent`, so the live `PainSystem` modifier for
`PainType.Starving` can stay stale until the next update or fail to clear when
resetting to zero. After setting `tracker.CurrentStarvingPain`, also synchronize
the corresponding starving pain entry in `PainSystem` for the same `EntityUid`
by updating/removing the active modifier through the existing pain-management
path used by `PainSystem` so the runtime value matches the tracker immediately.

---

Nitpick comments:
In `@Content.Server/Backmen/Nutrition/HungerPainSystem.cs`:
- Line 103: The HungerPainSystem is marking entities dirty unconditionally,
which can trigger redundant Dirty calls every tick and again after
ClearStarvingPain in the same frame. Update the logic around the tracker
mutation in HungerPainSystem so a local dirty flag is set only when tracker
fields actually change, and call Dirty(uid, tracker) only if that flag is true.
Use the existing HungerPainSystem methods and tracker updates to keep the
dirtying behavior tied to real state changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 332b5cd9-7b7a-4f82-8eb2-d0bf83c06d73

📥 Commits

Reviewing files that changed from the base of the PR and between fe6c380 and b57e317.

📒 Files selected for processing (31)
  • Content.Client/Backmen/Research/UI/FancyResearchConsoleMenu.xaml.cs
  • Content.Client/Backmen/Research/UI/FancyTechnologyInfoPanel.xaml.cs
  • Content.IntegrationTests/Tests/Backmen/Nutrition/HungerPainTest.cs
  • Content.IntegrationTests/Tests/Backmen/TechnologyPrototypePositionTest.cs
  • Content.Server/Backmen/Nutrition/HungerPainSystem.cs
  • Content.Shared/Backmen/Research/BkmResearchRequirements.cs
  • Content.Shared/Backmen/Research/BkmResearchTreeLayout.cs
  • Content.Shared/Backmen/Research/SharedResearchSystemExtensions.cs
  • Content.Shared/Research/Prototypes/TechnologyPrototype.cs
  • Resources/Locale/en-US/_Goobstation/research/ui.ftl
  • Resources/Locale/ru-RU/_Goobstation/research/ui.ftl
  • Resources/Prototypes/Research/arsenal.yml
  • Resources/Prototypes/Research/civilianservices.yml
  • Resources/Prototypes/_Backmen/Research/arsenal.yml
  • Resources/Prototypes/_Backmen/Research/cargo.yml
  • Resources/Prototypes/_Backmen/Research/engineering.yml
  • Resources/Prototypes/_Backmen/Research/machines_arsenal.yml
  • Resources/Prototypes/_Backmen/Research/machines_engineering.yml
  • Resources/Prototypes/_Backmen/Research/machines_medical.yml
  • Resources/Prototypes/_Backmen/Research/machines_robotics.yml
  • Resources/Prototypes/_Backmen/Research/machines_scientific.yml
  • Resources/Prototypes/_Backmen/Research/machines_service.yml
  • Resources/Prototypes/_Backmen/Research/machines_shaft.yml
  • Resources/Prototypes/_Backmen/Research/machines_shuttle.yml
  • Resources/Prototypes/_Backmen/Research/medical.yml
  • Resources/Prototypes/_Backmen/Research/robotics.yml
  • Resources/Prototypes/_Backmen/Research/scientific.yml
  • Resources/Prototypes/_Backmen/Research/security.yml
  • Resources/Prototypes/_Backmen/Research/service.yml
  • Resources/Prototypes/_Backmen/Research/shaft.yml
  • Resources/Prototypes/_Backmen/Research/shuttle.yml
💤 Files with no reviewable changes (21)
  • Resources/Prototypes/_Backmen/Research/shuttle.yml
  • Content.Shared/Backmen/Research/SharedResearchSystemExtensions.cs
  • Resources/Prototypes/_Backmen/Research/machines_robotics.yml
  • Resources/Prototypes/_Backmen/Research/machines_arsenal.yml
  • Resources/Prototypes/_Backmen/Research/machines_shaft.yml
  • Resources/Prototypes/_Backmen/Research/cargo.yml
  • Resources/Prototypes/_Backmen/Research/security.yml
  • Resources/Prototypes/_Backmen/Research/machines_engineering.yml
  • Resources/Prototypes/_Backmen/Research/machines_medical.yml
  • Resources/Prototypes/_Backmen/Research/machines_shuttle.yml
  • Resources/Prototypes/_Backmen/Research/scientific.yml
  • Resources/Prototypes/_Backmen/Research/machines_service.yml
  • Resources/Prototypes/_Backmen/Research/service.yml
  • Resources/Prototypes/_Backmen/Research/engineering.yml
  • Resources/Prototypes/_Backmen/Research/shaft.yml
  • Resources/Prototypes/_Backmen/Research/arsenal.yml
  • Resources/Prototypes/_Backmen/Research/machines_scientific.yml
  • Resources/Prototypes/_Backmen/Research/robotics.yml
  • Resources/Prototypes/_Backmen/Research/medical.yml
  • Resources/Prototypes/Research/civilianservices.yml
  • Resources/Prototypes/Research/arsenal.yml

Comment on lines +73 to +84
public static bool HasUnmetRequirements(
SharedResearchSystem research,
TechnologyPrototype technology,
TechnologyDatabaseComponent? component,
IPrototypeManager prototypeManager)
{
if (technology.TechnologyPrerequisites.Any(prereq =>
component == null || !research.IsTechnologyUnlocked(EntityUid.Invalid, prereq, component)))
return true;

if (component == null)
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Не приравнивайте component == null к отсутствию требований.

false здесь означает «все требования выполнены», но без TechnologyDatabaseComponent метод просто не может проверить tier/discipline-гейты. В FancyTechnologyInfoPanel.InitializePrerequisites() это превращается в зелёное research-console-no-tech-requirements для технологий, у которых требования неизвестны, а не отсутствуют.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Backmen/Research/BkmResearchRequirements.cs` around lines 73 -
84, `HasUnmetRequirements` is treating a missing `TechnologyDatabaseComponent`
as if all requirements were satisfied, which incorrectly makes
`FancyTechnologyInfoPanel.InitializePrerequisites()` show the no-requirements
state for unknown tiers/discipline gates. Update
`BkmResearchRequirements.HasUnmetRequirements` so that a null `component` is
handled as “requirements cannot be verified” rather than returning false; keep
the prerequisite check using `SharedResearchSystem.IsTechnologyUnlocked`, and
make the method report unmet requirements whenever the database component needed
to evaluate tier/discipline gating is absent.

Comment on lines +70 to +100
private static Dictionary<string, DisciplineCell> BuildDisciplineCells(
List<TechnologyPrototype> visible,
List<string> disciplines)
{
var cells = new Dictionary<string, DisciplineCell>();

foreach (var discipline in disciplines)
{
var disciplineTechs = visible.Where(t => t.Discipline == discipline).ToList();
if (disciplineTechs.Count == 0)
continue;

var positions = new Dictionary<string, Vector2i>();
var maxWidth = 0;
var maxHeight = 0;

foreach (var tierGroup in disciplineTechs.GroupBy(t => t.Tier).OrderBy(g => g.Key))
{
var ordered = OrderWithinTier(tierGroup.ToList(), disciplineTechs);
var rowY = (tierGroup.Key - 1) * TierRowSpacing;
maxHeight = Math.Max(maxHeight, rowY + 1);

for (var i = 0; i < ordered.Count; i++)
{
positions[ordered[i].ID] = new Vector2i(i, rowY);
maxWidth = Math.Max(maxWidth, i + 1);
}
}

cells[discipline] = new DisciplineCell(positions, maxWidth, maxHeight);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Смешанный auto-layout и ручной Position сейчас не резервирует место под override-координаты.

BuildDisciplineCells() считает Width/Height только по авторасстановке, но меню потом всё равно предпочитает proto.Position (FancyResearchConsoleMenu, Lines 123-125 и 267-270). Любой ручной Position за пределами рассчитанной ячейки может наложиться на соседний macro-cell; либо учитывайте manual overrides при расчёте размеров, либо запретите смешанный режим явно.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.Shared/Backmen/Research/BkmResearchTreeLayout.cs` around lines 70 -
100, The mixed auto-layout/manual Position path in BuildDisciplineCells does not
reserve space for overridden coordinates, so the computed DisciplineCell
Width/Height can be too small when FancyResearchConsoleMenu later prefers
proto.Position. Update BuildDisciplineCells to include any manual Position
overrides in the bounds calculation, or explicitly reject mixed auto/manual
placement, and make sure the sizing logic in DisciplineCell matches the
placement logic used by FancyResearchConsoleMenu.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs (1)

92-99: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Тест не проверяет, что сильное кровотечение действительно достигнуто.

Цикл по GetWoundableWoundsWithComp<BleedInflicterComponent> молча выполнит ноль итераций, если у индуцированной Slash-раны нет BleedInflicterComponent, — тогда поля кровотечения не выставятся, а тест всё равно останется зелёным, проверяя лишь снятие Cold. Это та же проблема, что отмечалась ранее: нет явного утверждения, что грудь классифицирована как heavy bleeding.

Рекомендую добавить счётчик/Assert, что цикл задел хотя бы одну рану, и проверку фактического состояния сильного кровотечения после правок.

Скрипт для подтверждения порога и поля, определяющих heavy bleeding:

#!/bin/bash
# Найти определение порога/проверки сильного кровотечения
rg -nP 'HeavyBleeding|heavy.?bleed|4\.5' --type=cs -C3
# Проверить, какие поля BleedInflicterComponent участвуют в расчёте кровотечения
fd -i 'BleedInflicterComponent.cs' --exec cat -n {}
rg -nP '\bBleedingAmountRaw\b|\bIsBleeding\b|\bScaling\b' --type=cs -C2
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs` around
lines 92 - 99, The bleeding setup in the healing fallback test can silently do
nothing if no wound has BleedInflicterComponent, so add an explicit assertion in
HealingFallbackTest after the
GetWoundableWoundsWithComp<BleedInflicterComponent> loop that at least one wound
was updated. Also verify the chest is actually in the heavy bleeding state after
setting BleedingAmountRaw, IsBleeding, and Scaling, so the test checks the
intended condition rather than only the Cold removal path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs`:
- Around line 92-99: The bleeding setup in the healing fallback test can
silently do nothing if no wound has BleedInflicterComponent, so add an explicit
assertion in HealingFallbackTest after the
GetWoundableWoundsWithComp<BleedInflicterComponent> loop that at least one wound
was updated. Also verify the chest is actually in the heavy bleeding state after
setting BleedingAmountRaw, IsBleeding, and Scaling, so the test checks the
intended condition rather than only the Cold removal path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fae1c829-8c29-425b-b2f6-44704dcce8e4

📥 Commits

Reviewing files that changed from the base of the PR and between b57e317 and 29c6390.

📒 Files selected for processing (1)
  • Content.IntegrationTests/Tests/Backmen/Medical/HealingFallbackTest.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs`:
- Around line 253-263: В тесте NukeOpsTest в блоке с damageSys.TryChangeDamage и
финальным Assert.That(GetTotalDamage, Is.EqualTo(FixedPoint2.Zero)) не нужно
безусловно снимать весь накопленный урон, потому что это скрывает регрессии.
Вместо этого в этом участке кода сначала проверьте, что у player есть только
ожидаемый небольшой транзиентный урон (или что это именно асфиксический урон), и
лишь при выполнении этого условия выполняйте обнуление через TryChangeDamage;
если урон выше порога или другого типа, тест должен падать. Используйте
ориентиры по символам damageSys.GetTotalDamage, scratchDamage и TryChangeDamage,
чтобы локализовать место правки.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a45eca1d-1846-4bf1-b4a6-bb50cb880a34

📥 Commits

Reviewing files that changed from the base of the PR and between 29c6390 and d1f38ba.

📒 Files selected for processing (1)
  • Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs

Comment thread Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs Outdated
@Rxup Rxup merged commit 6c8688a into master Jun 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changes: Localization Can be reviewed or fixed by people who are knowledgeable with translation Changes: UI Can be reviewed or fixed by people who are knowledgeable with UI design S: Untriaged size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant