Skip to content

[Tweak] D.O.O.R 2.0 pt. 1#9

Open
UmbiMax wants to merge 27 commits into
ArcaneSS14:arcane-devfrom
UmbiMax:Airlocks
Open

[Tweak] D.O.O.R 2.0 pt. 1#9
UmbiMax wants to merge 27 commits into
ArcaneSS14:arcane-devfrom
UmbiMax:Airlocks

Conversation

@UmbiMax

@UmbiMax UmbiMax commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Описание PR

Порт с визденов + кучка багфиксов да дополнений

Медиа

Тип PR

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

Лицензионное соглашение

  • Я согласен с лицензионным соглашением Contributor License Agreement, для Arcane14 Organization.

Изменения

🆑

  • add: Добавлено веселье.
  • remove: Убрано веселье.
  • tweak: Изменено веселье.
  • fix: Исправлено веселье.

Summary by CodeRabbit

  • New Features

    • Added a “View maintenance panel” option for airlocks that have an open maintenance panel, and expanded wires UI access via an alternate verb.
    • Added separate forced-open and forced-close sounds for airlocks (including Orion-specific variants).
  • Bug Fixes

    • Improved door animation consistency across open/close/emag/deny, with better reliability during client prediction and remote control.
    • Fixed access-reader desynchronization on emagged behavior and improved maintenance-panel visuals/positioning.
  • Style

    • Refreshed airlock/door animation pacing and updated related door audio assets.

* Update ``DoorComponent`` to use TimeSpans and fix comments (#40420)

Cleanup

* Fix emag sparking animation on doors (#40350)

* Fix

* Update

* Comment

* fix: allow opening doors with open wire panels (#38733)

* fix: allow opening doors with open wire panels

* fix: set wire panel sprite state on airlock close

* fix: make airlock wire panels animate better

* removed unused dependency

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* Port door animation fix from ephemeral space (#43613)

* port ES animation fix

* close sound too :)

* pump up the jam

* worlds most minor sloptimization

* silly

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>

* time animations

* sounds

* another time for WINDOORs from TG13

* 1.1

* 1.2 BUGFIX fix door desynchronization

* Minor door animation tweak (#43708)

is this bad

* Cherry-pick additions

* 2 NEW sound for blastdoor

* 3 Best firelock upgrade

* 3.1

* 3.2

* 4. Sound for FORCE OPEN

* 4,1 fix

* 4,2 fix

* 5. Sound for FIRELOCKs

* 5.1

* BUNCH OF FIXES: START

* FIX

* Update SharedDoorSystem.cs

* FIX

* FIX DOUBLE DOORS

* Fix

* DOOR FIX

* Final sprite UPGRADE

* Final firelock sound UPGRADE

* FIX JITTERING. Removed small part of wizden code for optimization

* AA

* Revert "AA"

This reverts commit 25976e100e8b35393a0552e9eee37b679f3c54f8.

* Reapply "AA"

This reverts commit 7c24bfbddbeeba3052064eafd9191ebadd8b6c83.

---------

Co-authored-by: Winkarst-cpu <74284083+Winkarst-cpu@users.noreply.github.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: Jessica M <jessica@jessicamaybe.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Door and airlock systems now use separate animation keys, final-state synchronization, force-open/close audio overrides, alternative wires-panel verbs, updated prototypes, access-reader synchronization, and revised door animation metadata.

Changes

Door and airlock behavior

Layer / File(s) Summary
Door animation and visual state flow
Content.Client/Doors/*, Content.Shared/Doors/Components/DoorComponent.cs
Door animations use separate open, close, deny, and emag keys, apply final sprite states on completion, and use updated timing and layer mappings.
Door actions and airlock force sounds
Content.Shared/Doors/Components/AirlockComponent.cs, Content.Shared/Doors/Systems/SharedDoorSystem.cs, Content.Shared/Access/Systems/AccessReaderSystem.cs
Airlocks support force-open and force-close sounds, opening and closing accept sound overrides, partial-open timing uses OpenTimeTwo, and emagged access readers dirty the resolved reader entity.
Wires panel interaction and UI flow
Content.Shared/Wires/*, Content.Server/Wires/WiresSystem.cs, Content.Server/Doors/Systems/AirlockSystem.cs, Resources/Locale/en-US/wires/components/*
Wires panels expose a localized alternative verb, shared UI-opening overloads, and static server UI operations; direct wires activation handling was removed from the airlock system.
Door prototypes and audio assets
Resources/Prototypes/Entities/Structures/Doors/*, Resources/Audio/*/attributions.yml
Door prototypes received updated audio, pry timing, construction, destructibility, force-open/close configuration, and attribution records.
Airlock animation state metadata
Resources/Textures/Structures/Doors/Airlocks/*, Resources/Textures/_Starlight/Structures/Doors/Airlocks/*
Airlock opening, closing, unlit, and panel animation delays were revised, with additional glass door states and panel-closing metadata.
Material and hazard door animation metadata
Resources/Textures/Structures/Doors/MineralDoors/*, Resources/Textures/Structures/Doors/edge_door_hazard.rsi/meta.json
Bananium and paper door animation metadata and attribution were updated, and hazard-door timing values changed from 0.2 to 0.1.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested labels: S: Needs Review, Changes: Sprites

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is generic and does not describe the door, wires, or airlock changes in the patch. Use a concise title that names the main change, such as door/airlock/wires system updates or prediction fixes.
✅ 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 unit tests (beta)
  • Create PR with unit tests

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.

@UmbiMax

UmbiMax commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Это порт моих наработок со старого билда на .NET 9, а не 10. Из-за вмешательства разработчиков Старлайта в данные системы, появилась огромная кучка ошибок, некоторые из которых пофиксил, но остальная часть всё равно осталась. Отправлю сюда.
Это при билде: Exception has occurred: CLR/System.ArgumentException
An unhandled exception of type 'System.ArgumentException' occurred in Robust.Shared.dll: 'An item with the same key has already been added. Key: ComponentRegistration(Door: Content.Shared.Doors.Components.DoorComponent)'

И куда-то пропал доафтер.
The type or namespace name 'DoAfter' does not exist in the namespace 'Content.Shared' (are you missing an assembly reference?)
The type or namespace name 'ActiveDoAfterComponent' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'DoAfterComponent' could not be found (are you missing a using directive or an assembly reference?)
И то всё в DoAfterOverlay.cs
Но у AirlockSystem
The type or namespace name 'Doors'(+ ошибка с 'Power') does not exist in the namespace 'Content.Shared' (are you missing an assembly reference?) Куда они это дели - неизвестно, включая The type or namespace name 'SharedAirlockSystem' could not be found (are you missing a using directive or an assembly reference?)
The name 'SubscribeLocalEvent' does not exist in the current context
The type or namespace name 'AirlockComponent' could not be found (are you missing a using directive or an assembly reference?)
The name 'TryComp' does not exist in the current context
The type or namespace name 'DoorComponent' could not be found (are you missing a using directive or an assembly reference?)
The name 'DoorVisualLayers' does not exist in the current context
The type or namespace name 'AirlockComponent' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'DoorState' could not be found (are you missing a using directive or an assembly reference?)
The name 'DoorVisuals'(+ DoorState + PowerDeviceVisuals) does not exist in the current context.

Кучка данных ошибок и в Door/Airlock/WiresSystem.
Я не могу понять, как так можно было проебаться? Начинай с анализа

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
echo "=== Searching for DoorComponent definitions ==="
rg -n "class DoorComponent" --type=cs -C2

echo "=== Searching for [RegisterComponent] near DoorComponent ==="
rg -n "RegisterComponent" -B2 -A1 --type=cs | rg -n "Door" -B3

echo "=== Searching for DoAfter namespace/types ==="
fd -i "doafter" --type f

echo "=== Searching for ActiveDoAfterComponent / DoAfterComponent ==="
rg -n "class ActiveDoAfterComponent|class DoAfterComponent" --type=cs

echo "=== Searching for SharedAirlockSystem ==="
rg -n "class SharedAirlockSystem" --type=cs -C2

echo "=== AirlockSystem.cs location and usings ==="
fd -i "airlocksystem.cs"

Length of output: 1000


🏁 Script executed:

#!/bin/bash
echo "=== Repo root structure ==="
ls
echo "=== Find Content.Shared / Content.Server dirs ==="
fd -t d "Content" -d 2

echo "=== Search DoorComponent (any case) ==="
rg -n "DoorComponent" --type=cs | head -50

echo "=== Search RegisterComponentAttribute usage near Door ==="
rg -n "\[RegisterComponent\]" --type=cs -A2 | rg -n "Door" -B2

echo "=== DoAfter related files ==="
fd -i "doafter"

echo "=== SharedAirlockSystem ==="
rg -n "SharedAirlockSystem" --type=cs | head -30

echo "=== AirlockSystem.cs files ==="
fd -i "airlock" -e cs

echo "=== check git log / branch ==="
git log --oneline -5
git status

Length of output: 2152


🏁 Script executed:

#!/bin/bash
echo "=== fd for Doors directories ==="
fd -t d -i "doors"

echo "=== fd for files named *Door*.cs ==="
fd -i "door" -e cs | head -80

echo "=== rg case-insensitive DoorComponent no type filter ==="
rg -ni "class DoorComponent" | head -50

echo "=== rg RegisterComponent with Door context (no type filter) ==="
rg -n "RegisterComponent" -A2 | grep -i "door" -B2 | head -80

echo "=== PR diff files touched ==="
git diff --name-only origin/main...HEAD 2>/dev/null | head -100 || git diff --name-only HEAD~20 2>/dev/null | head -100

echo "=== current branch ==="
git branch -a | head -20
git log --oneline -10

Length of output: 3348


🏁 Script executed:

#!/bin/bash
echo "=== DoorComponent.cs content ==="
cat -n Content.Shared/Doors/Components/DoorComponent.cs | head -60

echo "=== grep RegisterComponent Door Content.Shared ==="
grep -rn "RegisterComponent" Content.Shared/Doors/ 2>/dev/null

echo "=== Search for duplicate Door component registrations across repo (any file with class named ...DoorComponent registered as 'Door') ==="
grep -rn "\[RegisterComponent\]" -A3 Content.Shared Content.Server Content.Client 2>/dev/null | grep -B3 "class DoorComponent"

echo "=== Find any _Starlight Door component files ==="
fd -i "doorcomponent" -e cs

echo "=== DoAfter directories ==="
fd -t d -i "doafter"
fd -i "doafter" -e cs | head -50

echo "=== Airlock related files ==="
fd -i "airlock" -e cs
fd -i "airlockcomponent" -e cs

Length of output: 7844


🏁 Script executed:

#!/bin/bash
echo "=== git info ==="
git log --oneline -5
git branch -a
git remote -v

echo "=== search RegisterClass / RegisterClassFunction for Door ==="
rg -n "RegisterClass" -A2 | rg -i "door" -B2

echo "=== check for duplicate csproj includes that might double-compile a file ==="
fd -e csproj | xargs grep -l "DoorComponent" 2>/dev/null

echo "=== search any [ComponentProtoName(\"Door\")] usage ==="
rg -n "ComponentProtoName" 

echo "=== search for any other class with 'Door' component name explicitly set ==="
rg -n "\"Door\"" --type=cs | rg -i "component"

Length of output: 1385


🏁 Script executed:

#!/bin/bash
echo "=== Content.Server/Doors/Systems/AirlockSystem.cs ==="
cat -n Content.Server/Doors/Systems/AirlockSystem.cs | head -80

echo "=== Content.Shared/Doors/Systems/SharedAirlockSystem.cs head ==="
cat -n Content.Shared/Doors/Systems/SharedAirlockSystem.cs | head -60

echo "=== WiresSystem files related to Door ==="
fd -i "wiressystem" -e cs

Length of output: 6094


🏁 Script executed:

#!/bin/bash
echo "=== global.json ==="
cat global.json

echo "=== RobustToolbox submodule info ==="
git submodule status 2>/dev/null
cat .gitmodules 2>/dev/null

echo "=== check for stray/backup duplicate files ==="
fd -i "door" | rg -iv "\.(cs|yml|png|ftl|rsi)$"

echo "=== .gitignore for obj/bin ==="
grep -n "obj/\|bin/" .gitignore 2>/dev/null

Length of output: 4372


@UmbiMax, разобрался с текущим состоянием кода в PR (коммит 62b3874b40, ветка arcane-dev). Вот что нашёл:

1. В самом дереве исходников всё в порядке

  • Content.Shared/Doors/Components/DoorComponent.cs — единственное определение DoorComponent, дублей [RegisterComponent] с именем Door больше нигде нет (проверил по всему Content.Shared, Content.Server, Content.Client).
  • Content.Shared/DoAfter/DoAfter.cs, ActiveDoAfterComponent.cs, DoAfterComponent.cs, SharedDoAfterSystem.cs и т.д. — все на месте, ничего не потеряно.
  • Content.Server/Doors/Systems/AirlockSystem.cs наследуется от SharedAirlockSystem, использует корректные using-директивы (Content.Shared.Doors.Components, Content.Shared.Doors.Systems), TryComp, SubscribeLocalEvent, DoorState и т.п. приходят из базового EntitySystem — это стандартный, компилируемый код, ничего "утеряно".
  • Content.Shared/Doors/Systems/SharedAirlockSystem.cs тоже целый, с валидными зависимостями (SharedDoorSystem, SharedWiresSystem, SharedAppearanceSystem и т.д.).

2. Что нашёл подозрительное

  • global.json пинит sdk.version: 10.0.100 — то есть репозиторий сейчас настроен именно на .NET 10 SDK.
  • Сабмодуль RobustToolbox указывает на форк ss14Starlight/RobustToolbox.git, закреплённый на коммите 7bf1873afbee872cd6d97f4d37f32cc1867c9959.

Поскольку вы упомянули, что портировали наработки со старого билда на .NET 9, а не 10 — вероятная причина всех этих ошибок (ArgumentException про дублирующийся Door key, "потерявшийся" DoAfter, "потерявшийся" SharedAirlockSystem/AirlockComponent) в том, что локально у вас не синхронизирован сабмодуль RobustToolbox с тем, что ожидает Content-слой в этом PR, либо остались стейл obj/bin от старой сборки под .NET 9/старую версию RobustToolbox.

Дублирующаяся регистрация компонента Door почти всегда возникает, когда:

  • в памяти оказываются две сборки Content (например, старая закешированная в bin/obj + новая), либо
  • RobustToolbox (сабмодуль) не обновлён и там ещё остался старый DoorComponent, который конфликтует с новым из Content.Shared.

А "потерявшиеся" типы DoAfter, SharedAirlockSystem, AirlockComponent, DoorVisualLayers и т.п. — это может значить, что ваш локальный сабмодуль RobustToolbox новее/старее, чем зафиксированный 7bf1873afbee872cd6d97f4d37f32cc1867c9959, и в нём эти типы либо переехали в другое место, либо ещё не появились.

Что стоит проверить перед дальнейшим фиксом кода:

  1. git submodule update --init --recursive — обновить RobustToolbox до нужного коммита.
  2. Убедиться, что установлен именно .NET 10 SDK (dotnet --version), а не 9-й — из-за rollForward: latestFeature сборка может тихо съезжать на другой SDK.
  3. Полностью очистить bin/, obj/ во всех проектах (Content.Shared, Content.Server, Content.Client, RobustToolbox/*) и пересобрать с нуля — дубликат ComponentRegistration очень похож на конфликт стейл-сборок.
  4. Если после этого ошибка с дублирующимся Door всё равно возникает — пришлите, пожалуйста, полный стектрейс исключения (там будет указано, из какой сборки/файла берётся вторая регистрация) — в текущем исходном дереве я второй DoorComponent не нашёл, значит проблема, скорее всего, окружения, а не кода в этом PR.

Если после синхронизации сабмодуля и чистой пересборки ошибки останутся именно в коде (а не в окружении) — присылайте конкретные файлы/строки, разберу подробнее.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml (1)

209-230: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Merge duplicate Door component definitions.

The BananiumDoor prototype defines - type: Door twice in the same components sequence. RobustToolbox prohibits duplicate component types within the same entity definition, which will lead to a prototype load error. The new timing configurations should be merged into the existing Door definition.

🐛 Proposed fix
   - type: Door
+  # Orion-Start
+    closeTimeOne: 0.1
+    closeTimeTwo: 0.2
+    openTimeOne: 0.2
+    openTimeTwo: 0.1
+    openingAnimationTime: 0.6
+    closingAnimationTime: 0.6
+  # Orion-End
     openSound: &BikeHornSound
       collection: BikeHorn
       params:
         variation: 0.125
     closeSound: *BikeHornSound
   - type: Construction
     graph: DoorGraph
     node: bananiumDoor
-  - type: Door
-  # Orion-Start
-    closeTimeOne: 0.1
-    closeTimeTwo: 0.2
-    openTimeOne: 0.2
-    openTimeTwo: 0.1
-    openingAnimationTime: 0.6
-    closingAnimationTime: 0.6
-  # Orion-End
-    openSound:
-      path: /Audio/Items/bikehorn.ogg
-    closeSound:
-      path: /Audio/Items/bikehorn.ogg
🤖 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
`@Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml`
around lines 209 - 230, Merge the duplicate Door component definitions in the
BananiumDoor prototype into one component, preserving the existing BikeHorn
sound settings and incorporating the Orion timing fields (closeTimeOne,
closeTimeTwo, openTimeOne, openTimeTwo, openingAnimationTime, and
closingAnimationTime). Remove the second Door declaration while keeping the
Construction component unchanged.
Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json (1)

58-99: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Resolve Git merge conflict markers.

The file contains unresolved Git merge conflict markers, which invalidates the JSON structure and will cause the asset loader to fail.

🐛 Proposed fix to remove merge conflict markers
-<<<<<<< HEAD
-
-=======
       "name": "closing-fill",

(and remove the trailing >>>>>>> 08b658b6e49 ([Tweak] D.O.O.R (#427)) marker at the end of the conflicted block)

🤖 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 `@Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json`
around lines 58 - 99, Remove the unresolved Git conflict markers surrounding the
animation entries in the glass.rsi metadata, including the HEAD separator and
trailing commit marker, while preserving the valid closing-fill, closing-glass,
and panel_closing JSON entries and restoring valid JSON syntax.
🧹 Nitpick comments (1)
Content.Shared/Wires/SharedWiresSystem.cs (1)

104-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid capturing the Entity<T> struct in the lambda.

Capturing ent (an Entity<WiresPanelComponent>) inside the Act lambda captures the entire struct, which includes the component reference. While verbs are short-lived, it's a good practice to capture only the EntityUid to avoid unintentionally keeping component references alive in closures.

♻️ Proposed refactor
     private void OnGetVerbs(Entity<WiresPanelComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
     {
         if (!IsPanelOpen(ent.Owner))
             return;
 
+        var uid = ent.Owner;
         var actor = args.User;
         var verb = new AlternativeVerb
         {
             Text = Loc.GetString("wires-panel-verb-view-panel"),
             Icon = new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/VerbIcons/screwdriver.png")),
-            Act = () => OpenUserInterface(ent, actor),
+            Act = () => OpenUserInterface(uid, actor),
         };
 
         args.Verbs.Add(verb);
     }
🤖 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/Wires/SharedWiresSystem.cs` around lines 104 - 118, Update
OnGetVerbs to capture only ent.Owner’s EntityUid before constructing the
AlternativeVerb, and have the Act lambda use that UID when calling
OpenUserInterface instead of capturing the entire Entity<WiresPanelComponent>
ent struct.
🤖 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.Client/Doors/AirlockSystem.cs`:
- Around line 33-40: Update the OpenSpriteStates and ClosedSpriteStates tuple
definitions in DoorComponent to carry an AutoAnimated flag, then update the
airlock registrations in AirlockSystem so the BaseUnlit opening and closing
end-states explicitly disable auto-animation. Ensure DoorSystem uses this flag
when calling LayerSetAutoAnimated, while preserving existing behavior for other
door states.

In `@Content.Client/Doors/DoorSystem.cs`:
- Around line 177-192: Update the DoorState.Opening and DoorState.Closing
branches in the door animation logic to stop the opposite animation key before
calling _animationSystem.Play: stop DoorComponent.CloseKey when opening and
DoorComponent.OpenKey when closing. Preserve the existing zero-duration and
same-key running-animation checks.

In
`@Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml`:
- Around line 96-99: Audio prototype paths use the incorrect _Orion directory;
replace it with _Arcane for both forceOpenSound and forceCloseSound in
Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
lines 96-99, Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml
lines 84-87,
Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml lines
102-104, and
Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml lines
28-30.

In
`@Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml`:
- Around line 106-119: Remove the duplicated Construction component and
paper-door-specific configuration from the MetalDoor prototype. In the MetalDoor
entity’s Door component, replace the paperdoor_openclose.ogg sound paths with
the appropriate metal-door sounds, while preserving the Orion timing overrides.

In `@Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json`:
- Around line 23-31: Remove the unresolved Git conflict markers in the glass.rsi
metadata and retain the valid closed-fill and closed-glass entries within the
JSON array. Ensure the resulting meta.json is valid JSON and preserves the
surrounding metadata structure.

---

Outside diff comments:
In
`@Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml`:
- Around line 209-230: Merge the duplicate Door component definitions in the
BananiumDoor prototype into one component, preserving the existing BikeHorn
sound settings and incorporating the Orion timing fields (closeTimeOne,
closeTimeTwo, openTimeOne, openTimeTwo, openingAnimationTime, and
closingAnimationTime). Remove the second Door declaration while keeping the
Construction component unchanged.

In `@Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json`:
- Around line 58-99: Remove the unresolved Git conflict markers surrounding the
animation entries in the glass.rsi metadata, including the HEAD separator and
trailing commit marker, while preserving the valid closing-fill, closing-glass,
and panel_closing JSON entries and restoring valid JSON syntax.

---

Nitpick comments:
In `@Content.Shared/Wires/SharedWiresSystem.cs`:
- Around line 104-118: Update OnGetVerbs to capture only ent.Owner’s EntityUid
before constructing the AlternativeVerb, and have the Act lambda use that UID
when calling OpenUserInterface instead of capturing the entire
Entity<WiresPanelComponent> ent struct.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 855ab2e6-847a-44e1-84d0-d3c418f29257

📥 Commits

Reviewing files that changed from the base of the PR and between 82e4b02 and 62b3874.

⛔ Files ignored due to path filters (6)
  • Resources/Audio/Machines/boltsdown.ogg is excluded by !**/*.ogg
  • Resources/Audio/Machines/boltsup.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Arcane/Machines/airlock_close_force.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Arcane/Machines/airlock_open_force.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Arcane/Machines/blastdoornew.ogg is excluded by !**/*.ogg
  • Resources/Audio/_Arcane/Machines/firedoor_openclose.ogg is excluded by !**/*.ogg
📒 Files selected for processing (61)
  • Content.Client/Doors/AirlockSystem.cs
  • Content.Client/Doors/DoorSystem.cs
  • Content.Server/Doors/Systems/AirlockSystem.cs
  • Content.Server/Wires/WiresSystem.cs
  • Content.Shared/Access/Systems/AccessReaderSystem.cs
  • Content.Shared/Doors/Components/AirlockComponent.cs
  • Content.Shared/Doors/Components/DoorComponent.cs
  • Content.Shared/Doors/Systems/SharedDoorSystem.cs
  • Content.Shared/Wires/SharedWiresSystem.cs
  • Resources/Audio/Machines/attributions.yml
  • Resources/Audio/_Arcane/Machines/attributions.yml
  • Resources/Locale/en-US/wires/components/wires-panel-component.ftl
  • Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml
  • Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml
  • Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml
  • Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml
  • Resources/Prototypes/Entities/Structures/Doors/Shutter/blast_door.yml
  • Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml
  • Resources/Textures/Structures/Doors/Airlocks/Glass/atmospherics.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/basic.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/cargo.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/centcomm.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/chemistry.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/clockwork_pinion.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/command.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/engineering.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/hydroponics.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/maint.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/medical.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/salvage.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/science.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/security.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/syndicate.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Glass/virology.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/basic.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/cargo.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/centcomm.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/chemistry.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/clockwork_pinion.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/command.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/engineering.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/freezer.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/hatch.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/hatch_maint.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/hydroponics.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/maint.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/medical.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/salvage.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/science.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/security.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/syndicate.rsi/meta.json
  • Resources/Textures/Structures/Doors/Airlocks/Standard/virology.rsi/meta.json
  • Resources/Textures/Structures/Doors/MineralDoors/bananium_door.rsi/meta.json
  • Resources/Textures/Structures/Doors/MineralDoors/paper_door.rsi/meta.json
  • Resources/Textures/Structures/Doors/edge_door_hazard.rsi/meta.json
  • Resources/Textures/_Starlight/Structures/Doors/Airlocks/Glass/brigmedic.rsi/meta.json
  • Resources/Textures/_Starlight/Structures/Doors/Airlocks/Glass/eva.rsi/meta.json
  • Resources/Textures/_Starlight/Structures/Doors/Airlocks/Glass/mining_cargo.rsi/meta.json
  • Resources/Textures/_Starlight/Structures/Doors/Airlocks/Glass/paramedic.rsi/meta.json
💤 Files with no reviewable changes (1)
  • Content.Server/Doors/Systems/AirlockSystem.cs

Comment thread Content.Client/Doors/AirlockSystem.cs
Comment thread Content.Client/Doors/DoorSystem.cs
Comment thread Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml Outdated
Comment thread Resources/Textures/Structures/Doors/Airlocks/Glass/glass.rsi/meta.json Outdated
@UmbiMax UmbiMax changed the title [Tweak] D.O.O.R (#427) [Tweak] D.O.O.R 2.0 Jul 14, 2026
@UmbiMax
UmbiMax marked this pull request as ready for review July 14, 2026 15:11
@UmbiMax

UmbiMax commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Тест

@UmbiMax

UmbiMax commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Можешь отправить список того, из-за чего провал нескольких тестов?

@coderabbitai

coderabbitai Bot commented Jul 15, 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 "=== PR checks status ==="
gh pr checks 9 --repo ArcaneSS14/arcane || true
echo "=== PR details ==="
gh pr view 9 --repo ArcaneSS14/arcane --json title,state,statusCheckRollup -q '.statusCheckRollup[] | {name: .name, status: .status, conclusion: .conclusion, detailsUrl: .detailsUrl}' || true

Length of output: 7808


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Shard 4 failed logs ==="
gh run view --repo ArcaneSS14/arcane --job 87316855732 --log-failed 2>/dev/null | tail -200
echo "=== Shard 6 failed logs ==="
gh run view --repo ArcaneSS14/arcane --job 87316855718 --log-failed 2>/dev/null | tail -200

Length of output: 62843


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Shard 6 full log search for failed tests ==="
gh run view --repo ArcaneSS14/arcane --job 87316855718 --log 2>/dev/null | grep -iE "❌|Failed |FAIL|Error Message|Assert\." | head -100
echo "=== Shard 4 full log search for failures/hangs before crash ==="
gh run view --repo ArcaneSS14/arcane --job 87316855732 --log 2>/dev/null | grep -iE "❌|Failed |hangdump|Blame|inactivity|crashed" | head -100

Length of output: 13575


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== Find test file ==="
fd -i "airlock" -e cs Content.IntegrationTests 2>/dev/null
echo "=== grep test names ==="
rg -n "PoweredOpenAirlock_Pry_DoesNotClose|UnpoweredOpenAirlock_Pry_Closes" --type=cs -C5

Length of output: 704


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cat -n Content.IntegrationTests/Tests/Doors/AirlockPryingTest.cs

Length of output: 4253


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== OnAfterPry and StartOpening/StartClosing ==="
rg -n "OnAfterPry|StartOpening|StartClosing" -A30 Content.Shared/Doors/Systems/SharedDoorSystem.cs | head -250

Length of output: 10417


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh run view --repo ArcaneSS14/arcane --job 87316855718 --log 2>/dev/null | grep -n "Failed PoweredOpenAirlock_Pry_DoesNotClose\|Failed UnpoweredOpenAirlock_Pry_Closes" 
echo "---"
gh run view --repo ArcaneSS14/arcane --job 87316855718 --log 2>/dev/null | sed -n '1,99999p' | awk '/Failed PoweredOpenAirlock_Pry_DoesNotClose|Failed UnpoweredOpenAirlock_Pry_Closes/{print NR": "$0; c=NR+40; next} c && NR<=c {print NR": "$0}'

Length of output: 40070


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== DoorSystem.cs lines 100-170 ==="
sed -n '90,170p' Content.Client/Doors/DoorSystem.cs
echo "=== DoorComponent.cs OpenSpriteStates/ClosedSpriteStates ==="
rg -n "OpenSpriteStates|ClosedSpriteStates" -A3 -B3 Content.Shared/Doors/Components/DoorComponent.cs
echo "=== AirlockComponent.cs sprite states ==="
rg -n "SpriteState|OpenPanelSpriteState" Content.Shared/Doors/Components/AirlockComponent.cs
echo "=== basic.rsi states list ==="
python3 -c "
import json
d = json.load(open('Resources/Textures/Structures/Doors/Airlocks/Standard/basic.rsi/meta.json'))
for s in d['states']:
    print(s['name'])
"

Length of output: 5960


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== AirlockSystem.cs OnComponentStartup ==="
rg -n "OnComponentStartup" -A60 Content.Client/Doors/AirlockSystem.cs | head -100

Length of output: 3531


@UmbiMax, разобрался с деталями провалившихся джобов (прогон 29404055577).

Shard 6 — 2 упавших теста, причина найдена точно

  • PoweredOpenAirlock_Pry_DoesNotClose и UnpoweredOpenAirlock_Pry_Closes (Content.IntegrationTests/Tests/Doors/AirlockPryingTest.cs, строки 29 и 67) падают не на ассерте, а на teardown-проверке RobustToolbox TestPair.ReportErrorLogs(), которая превращает любой ERROR-лог клиента/сервера во время теста в провал.

Ошибка в обоих случаях одна и та же:

CLIENT: [ERRO] system.sprite: ...'s state 'open_unlit' does not exist in RSI /Textures/Structures/Doors/Airlocks/Standard/basic.rsi
   at Content.Client.Doors.DoorSystem.UpdateAppearanceForDoorState(...) in Content.Client/Doors/DoorSystem.cs:line 152
   at Content.Client.Doors.DoorSystem.OnAppearanceChange(...) in Content.Client/Doors/DoorSystem.cs:line 128

Причина цепочки:

  1. В Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml в этом PR добавлено openUnlitVisible: true для базового Airlock.
  2. В Content.Client/Doors/AirlockSystem.cs, OnComponentStartup (строки 28‑32):
    if (comp.OpenUnlitVisible)
    {
        door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
        door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));
    }
    AirlockComponent.OpenSpriteState по умолчанию равен "open_unlit".
  3. Но Resources/Textures/Structures/Doors/Airlocks/Standard/basic.rsi/meta.json не содержит состояния open_unlit — там есть только closed_unlit, opening_unlit, closing_unlit, open, closed и т.д.
  4. Когда дверь переходит в DoorState.Open, DoorSystem.UpdateAppearanceForDoorState (Content.Client/Doors/DoorSystem.cs, строка ~152) пытается выставить слою BaseUnlit несуществующий RSI-стейт open_unlit, движок логирует ERROR, и тест падает на teardown.

Это регрессия, введённая включением openUnlitVisible: true без соответствующего добавления состояния open_unlit в RSI (или без адаптации логики AirlockSystem.OnComponentStartup под RSI, где такого состояния нет). Проверьте, какие именно RSI airlock’ов содержат open_unlit, и либо добавьте это состояние везде, где включён openUnlitVisible, либо не включайте этот флаг для RSI без него, либо в OnComponentStartup делайте TryGetState/проверку наличия стейта перед добавлением в OpenSpriteStates.

Shard 4 — не провал теста, а креш тестхоста

Здесь не было упавших ассертов — процесс dotnet test два раза подряд "завис" (3 минуты неактивности → hangdump → Test host process crashed), после чего джоба провалилась по коду выхода 124 (timeout). Судя по логу, зависание случилось сразу после XenoArtifactRemoveNodeTest/XenoArtifactGenerateSegmentsTest, но точную причину без анализа дампа (blame-dump-shard-4 артефакт, ~8 ГБ) не установить — она может быть как флакой CI-инфраструктуры, так и детерминированным дедлоком. Здесь нет прямой связи с изменениями в Doors/Airlock/Wires, судя по видимым логам, но нельзя исключать побочный эффект (например, если DoorSystem.OnAnimationCompleted/новые SubscribeLocalEvent<AnimationCompletedEvent> где-то создают цикл событий). Если хотите, можно перезапустить только shard 4, чтобы проверить, воспроизводится ли креш детерминированно — если да, стоит скачать crash-дамп и проанализировать стек через dotnet-dump.

Итог: для гарантированного фикса CI нужно решить проблему с open_unlit в шаблоне 1 (это точная, воспроизводимая причина 2 упавших тестов); shard 4 требует отдельного расследования (повторный прогон/дамп).


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

Timestamp: 2026-07-15T10:58:44.048Z
Learning: In this repo (arcane, Content.Client/Doors/AirlockSystem.cs), `AirlockComponent.OpenUnlitVisible = true` causes `OnComponentStartup` to add `(DoorVisualLayers.BaseUnlit, comp.OpenSpriteState)` (default "open_unlit") to `DoorComponent.OpenSpriteStates`. Any airlock RSI used with `openUnlitVisible: true` must define an `open_unlit` state, otherwise `DoorSystem.UpdateAppearanceForDoorState` (Content.Client/Doors/DoorSystem.cs) throws a client sprite ERROR when the door opens, which fails integration tests like `Content.IntegrationTests/Tests/Doors/AirlockPryingTest.cs` via RobustToolbox's `TestPair.ReportErrorLogs()` teardown check.

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 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Можешь написать способы решения и поиска проблем.

@coderabbitai

coderabbitai Bot commented Jul 15, 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
python3 - <<'PY'
import json, glob, os, re

# Собираем все airlock-прототипы с openUnlitVisible: true и их sprite (RSI) путь
import subprocess
proto_files = glob.glob("Resources/Prototypes/Entities/Structures/Doors/Airlocks/**/*.yml", recursive=True)
for pf in proto_files:
    text = open(pf, encoding="utf-8").read()
    if "openUnlitVisible: true" not in text:
        continue
    print(f"--- {pf} has openUnlitVisible: true ---")
PY

# Для каждого airlock RSI проверяем наличие state "open_unlit" и "closed_unlit"
fd -e json -p "meta.json" Resources/Textures/Structures/Doors/Airlocks | while read -r f; do
  python3 - "$f" <<'PY'
import json, sys
f = sys.argv[1]
d = json.load(open(f))
names = {s["name"] for s in d.get("states", [])}
missing = [s for s in ("open_unlit", "closed_unlit") if s not in names]
if missing:
    print(f"{f}: missing {missing}")
PY
done

Length of output: 6469


🏁 Script executed:

rg -n "does not exist in RSI"

Length of output: 338


@UmbiMax, результаты скрипта подтверждают, что проблема гораздо шире, чем один basic.rsi — это системная, а не точечная нехватка ассета.

Ключевой вывод из скрипта

  • openUnlitVisible: true включён всего в 2 местах: base_structureairlocks.yml (базовый Airlock, от которого наследуются почти все двери) и highsec.yml.
  • Состояние open_unlit отсутствует абсолютно во всех ~49 airlock RSI в репозитории (и Glass/*, и Standard/*, включая basic.rsi, security.rsi, command.rsi и т.д.) — без единого исключения.
  • При этом closed_unlit нигде не отмечен как отсутствующий — то есть он есть везде.

Что это значит

Это не "забыли добавить спрайт в один файл" — это говорит о том, что open_unlit как состояние никогда не существовало в конвенции этих RSI. В классической SS14-логике "unlit"-слой обозначает светящийся контур запитанной/закрытой двери, и когда дверь открыта, этот слой обычно скрывается/не отображается вовсе, а не переключается на отдельный "открытый светящийся" спрайт.

Отсюда следует, что правильный фикс — не Вариант A (добавлять open_unlit в 49 файлов текстур, что явно не соответствует художественной конвенции игры), а именно доработка кода:

Рекомендуемый фикс (обновлённый, с учётом новых данных)

В Content.Client/Doors/AirlockSystem.cs, OnComponentStartup, для BaseUnlit-слоя при открытии двери нужно либо:

  1. Не регистрировать sprite-state вообще, а скрывать/выключать видимость слоя BaseUnlit, когда дверь открыта (раз в открытом состоянии unlit-glow не предусмотрен художественно), либо
  2. Если задумывался какой-то "открытый" визуальный эффект света — переиспользовать существующий стейт (например, тот же closed_unlit не подходит по смыслу, но можно проверить, не должен ли открытый airlock просто не иметь этого слоя вовсе, судя по тому, что ни в одном RSI не предусмотрен отдельный "открытый unlit" арт).

Практически: замените безусловное

if (comp.OpenUnlitVisible)
{
    door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
    door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));
}

на добавление только ClosedSpriteStates (стейт closed_unlit, который реально существует везде), и явно скрывать/не задавать состояние для BaseUnlit, когда дверь открыта — например, через sprite.LayerSetVisible(BaseUnlitLayerIndex, false) в момент перехода в Open, а не через попытку выставить несуществующий RSI-стейт.

Как перепроверить после фикса

Тот же python-скрипт можно превратить в CI-проверку (например, юнит/интеграционный тест или pre-commit хук), который валит билд, если какой-либо DoorComponent.OpenSpriteStates/ClosedSpriteStates, собранный во время OnComponentStartup, ссылается на состояние, отсутствующее в связанном RSI. Это заранее отловит подобные регрессии, не дожидаясь провала интеграционных тестов в CI.

Что касается rg -n "does not exist in RSI" — это ожидаемо не нашло совпадений в самом репозитории, так как это runtime-сообщение движка RobustToolbox (из SpriteSystem), а не строка в исходниках Content; используйте этот паттерн только при грепе логов CI-джобов (gh run view --job <id> --log), а не кода репозитория.

(=`ω´=) похоже, нашлась настоящая корневая причина — дело не в одном файле, а в самой логике


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

Timestamp: 2026-07-15T11:19:22.659Z
Learning: In this repo (arcane), `openUnlitVisible: true` is set only on the base `Airlock` entity (`Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml`) and on `highsec.yml`. A repo-wide audit of every airlock RSI meta.json under `Resources/Textures/Structures/Doors/Airlocks/{Glass,Standard}/*.rsi` (~49 files) shows `open_unlit` is missing from 100% of them, while `closed_unlit` exists in all of them. This confirms `open_unlit` is not a real art convention in this codebase — `Content.Client/Doors/AirlockSystem.cs`'s `OnComponentStartup` should not add `comp.OpenSpriteState` (default "open_unlit") to `DoorComponent.OpenSpriteStates` for the `BaseUnlit` layer; instead the BaseUnlit layer should likely be hidden/not set when the door is open, and only `closed_unlit` should be used for `ClosedSpriteStates`.

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 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Напиши замену данного кода, пока не за ПК.

@coderabbitai

coderabbitai Bot commented Jul 15, 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
sed -n '1,80p' Content.Client/Doors/AirlockSystem.cs

Length of output: 3709


@UmbiMax, вот замена для Content.Client/Doors/AirlockSystem.cs, метод OnComponentStartup — конкретно блок if (comp.OpenUnlitVisible). Логика: перед регистрацией comp.OpenSpriteState для слоя BaseUnlit проверяем, что такой стейт реально существует в RSI спрайта; если нет — просто не регистрируем открытый unlit-стейт (слой останется без изменений/скрытым), избегая runtime ERROR state 'open_unlit' does not exist in RSI, который валит AirlockPryingTest.

private void OnComponentStartup(EntityUid uid, AirlockComponent comp, ComponentStartup args)
{
    // Has to be on component startup because we don't know what order components initialize in and running this before DoorComponent inits _will_ crash.
    if (!TryComp<DoorComponent>(uid, out var door))
        return;

    if (comp.OpenUnlitVisible) // Otherwise there are flashes of the fallback sprite between clicking on the door and the door closing animation starting.
    {
        // Arcane-Fix: comp.OpenSpriteState (default "open_unlit") is not defined in most airlock RSIs.
        // Registering a non-existent RSI state here causes a client-side sprite ERROR when the door opens
        // (Content.Client.Doors.DoorSystem.UpdateAppearanceForDoorState), which fails integration tests
        // like AirlockPryingTest via RobustToolbox's ReportErrorLogs() teardown check.
        // Only add the open state if it actually exists in this entity's RSI; otherwise skip it so the
        // BaseUnlit layer simply keeps its last valid state instead of crashing/erroring on transition.
        if (TryComp<SpriteComponent>(uid, out var sprite)
            && sprite.LayerMapTryGet(DoorVisualLayers.BaseUnlit, out var baseUnlitLayer)
            && sprite[baseUnlitLayer].Rsi is { } rsi
            && rsi.TryGetState(comp.OpenSpriteState, out _))
        {
            door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
        }

        door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));
    }
    // Arcane-Start
    else
    {
        // Arcane: Register BaseUnlit end-states so DoorSystem.OnAnimationCompleted restores them. Allows removing LayerSetAnimationTime(0) in OnAppearanceChange that caused jitter.
        door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosingSpriteState));
        door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpeningSpriteState));
    }
    // Arcane-End

    // ... остальной код метода без изменений (OpeningAnimation, ClosingAnimation, DenyingAnimation, panel logic)
}

Пояснения:

  • sprite[baseUnlitLayer].Rsi — берём RSI, реально привязанный к слою BaseUnlit (обычно это RSI самой сущности, если не переопределён в YAML отдельно для слоя).
  • rsi.TryGetState(...) — стандартный метод RSI в Robust.Client.Graphics, безопасно возвращает false, если стейта нет, вместо логирования ERROR при попытке выставить его позже.
  • closed_unlit регистрируется всегда безусловно, так как по вашему аудиту он есть во всех airlock RSI — там проблем не будет.
  • Это чисто защитный фикс (не даёт крашить тесты/лог ERROR), но не решает художественный вопрос "должен ли открытый airlock иметь unlit-слой вообще" — если хотите визуальный эффект для открытого стейта, тогда стейт open_unlit нужно нарисовать и добавить в нужные .rsi/meta.json.

Когда будете за ПК — дайте знать, помогу применить и, если нужно, пройтись по всем 49 RSI, чтобы решить, где стоит добавить арт, а где оставить как есть.

(=`ω´=)゙


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

Timestamp: 2026-07-15T15:35:04.360Z
Learning: In Content.Client/Doors/AirlockSystem.cs, OnComponentStartup's `if (comp.OpenUnlitVisible)` branch previously unconditionally added `(DoorVisualLayers.BaseUnlit, comp.OpenSpriteState)` (default "open_unlit") to `door.OpenSpriteStates`, which crashes/errors at runtime because no airlock RSI in the repo defines an "open_unlit" state. The recommended fix is to guard this addition with a check via `SpriteComponent`/`RSI.TryGetState` before adding the state, falling back to not registering it (leaving BaseUnlit layer state unchanged) instead of registering a non-existent RSI state.

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 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Но вот на && sprite.LayerMapTryGet(DoorVisualLayers.BaseUnlit, out var baseUnlitLayer)
падает 'SpriteComponent.LayerMapTryGet(object, out int, bool)' is obsolete: 'Use SpriteSystem.LayerMapTryGet() instead.' предупреждние. Пробовал && _sprite.LayerMapTryGet(sprite, DoorVisualLayers.BaseUnlit, out var baseUnlitLayer) но жалуется на аргументы. Через AppearanceSystem в том числе не вариант. Проблемно. Какие ещё имеются варианты без лишней нагрузки? Данных шлюзов на станции сотни.

@UmbiMax

UmbiMax commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Ранее вмешивались в
if (TryComp(uid, out var sprite)
&& _sprite.LayerMapTryGet((uid, sprite), DoorVisualLayers.BaseUnlit, out var baseUnlitLayer, false)
&& sprite[baseUnlitLayer] is Layer layer
&& layer.ActualRsi is { } rsi
&& rsi.TryGetState(comp.OpenSpriteState, out _))
Имеет ли смысл заменять данную проверку изменением в последнем коммите "replace"?

@coderabbitai

coderabbitai Bot commented Jul 17, 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 "=== Latest commit ==="
git show -s --format='%H%n%s%n%b' HEAD

echo "=== Files changed by latest commit ==="
git show --name-status --format='' HEAD

echo "=== Relevant diff (doors, airlocks, sprites, RSI metadata) ==="
git show --format= HEAD -- \
  Content.Client/Doors/AirlockSystem.cs \
  Content.Client/Doors/DoorSystem.cs \
  Content.Shared/Doors/Components/AirlockComponent.cs \
  Content.Shared/Doors/Components/DoorComponent.cs \
  Resources/Prototypes/Entities/Structures/Doors \
  Resources/Textures/Structures/Doors \
  Resources/Textures/_Starlight/Structures/Doors

echo "=== Locate occurrences of 'replace' in latest commit and current relevant code ==="
git show --format= HEAD | rg -n -i -C4 '\breplace\b' || true
rg -n -i -C4 '\breplace\b|OpenUnlitVisible|OpenSpriteState|BaseUnlit' \
  Content.Client/Doors/AirlockSystem.cs \
  Content.Client/Doors/DoorSystem.cs \
  Content.Shared/Doors/Components/AirlockComponent.cs \
  Content.Shared/Doors/Components/DoorComponent.cs || true

Length of output: 15526


@UmbiMax, нет: изменение из коммита replace не заменяет эту проверку.

Оно меняет только видимость слоя BaseUnlit в OnAppearanceChange:

(state == DoorState.Closed && comp.OpenUnlitVisible)

Но причина падения была в другом месте: DoorSystem при DoorState.Open перебирает door.OpenSpriteStates и вызывает LayerSetRsiState. Если туда снова без проверки добавить:

(DoorVisualLayers.BaseUnlit, comp.OpenSpriteState)

то будет попытка выставить отсутствующий open_unlit, а значит вернётся прежний client ERROR и падение тестов.

Более того, в текущем блоке нужно обязательно добавить фигурные скобки: сейчас ClosedSpriteStates.Add(...) тоже выполняется лишь если найден open_unlit, хотя closed_unlit существует и должен регистрироваться независимо.

Оставьте replace, но приведите блок к такому виду:

if (comp.OpenUnlitVisible)
{
    if (TryComp<SpriteComponent>(uid, out var sprite)
        && _sprite.LayerMapTryGet((uid, sprite), DoorVisualLayers.BaseUnlit, out var baseUnlitLayer, false)
        && sprite[baseUnlitLayer] is SpriteComponent.Layer layer
        && layer.ActualRsi is { } rsi
        && rsi.TryGetState(comp.OpenSpriteState, out _))
    {
        door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
    }

    door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));
}

То есть:

  • replace отвечает за когда слой показывается;
  • эта проверка отвечает за можно ли назначить RSI-состояние;
  • это две независимые части логики, поэтому одну другой заменять нельзя.

Есть важный визуальный нюанс: после replace слой становится видимым и при DoorState.Open, но при отсутствии open_unlit отдельное конечное состояние ему не назначается. Это безопасно для CI, но для корректного визуала лучше отдельно определить существующий RSI-стейт, который должен быть финальным для открытой двери, либо скрывать BaseUnlit в Open.

@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.Client/Doors/AirlockSystem.cs`:
- Around line 31-37: Add curly braces around the conditional body in the
OpenUnlitVisible handling so both door.OpenSpriteStates.Add and
door.ClosedSpriteStates.Add execute only when all TryComp and sprite-state
checks succeed.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b97b60d-0cb4-4686-bcbb-38f8d42d9125

📥 Commits

Reviewing files that changed from the base of the PR and between 7e0e1e8 and 672edef.

📒 Files selected for processing (3)
  • Content.Client/Doors/AirlockSystem.cs
  • Content.Shared/Remotes/EntitySystems/SharedDoorRemoteSystem.cs
  • Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml

Comment thread Content.Client/Doors/AirlockSystem.cs Outdated
@UmbiMax

UmbiMax commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Имеется лишний код/хардкод в PR?

@coderabbitai

coderabbitai Bot commented Jul 18, 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 "=== PR branch and latest commits ==="
git log --oneline -12

echo "=== Changed files (merge-base to HEAD) ==="
base="$(git merge-base HEAD origin/arcane-dev 2>/dev/null || git merge-base HEAD origin/main 2>/dev/null || true)"
if [ -n "$base" ]; then
  git diff --name-status "$base"...HEAD
else
  git diff --name-status HEAD~1..HEAD
fi

echo "=== Code diff: door, airlock, wires, remotes ==="
if [ -n "$base" ]; then
  git diff --unified=40 "$base"...HEAD -- \
    Content.Client/Doors/AirlockSystem.cs \
    Content.Client/Doors/DoorSystem.cs \
    Content.Server/Doors/Systems/AirlockSystem.cs \
    Content.Server/Wires/WiresSystem.cs \
    Content.Shared/Doors/Components/AirlockComponent.cs \
    Content.Shared/Doors/Components/DoorComponent.cs \
    Content.Shared/Doors/Systems/SharedDoorSystem.cs \
    Content.Shared/Wires/SharedWiresSystem.cs \
    Content.Shared/Remotes/EntitySystems/SharedDoorRemoteSystem.cs \
    Resources/Prototypes/Entities/Structures/Doors
else
  git show --unified=40 --format= HEAD -- \
    Content.Client/Doors/AirlockSystem.cs \
    Content.Client/Doors/DoorSystem.cs \
    Content.Server/Doors/Systems/AirlockSystem.cs \
    Content.Server/Wires/WiresSystem.cs \
    Content.Shared/Doors/Components/AirlockComponent.cs \
    Content.Shared/Doors/Components/DoorComponent.cs \
    Content.Shared/Doors/Systems/SharedDoorSystem.cs \
    Content.Shared/Wires/SharedWiresSystem.cs \
    Content.Shared/Remotes/EntitySystems/SharedDoorRemoteSystem.cs \
    Resources/Prototypes/Entities/Structures/Doors
fi

echo "=== Marker comments and literal resource paths in changed code/prototypes ==="
rg -n -C2 'Arcane-|Orion-|_Arcane|_Orion|openUnlitVisible|pryTime:|TimeSpan\.FromSeconds|AnimationKey|LayerSetAnimationTime|OpenPanelSpriteState' \
  Content.Client/Doors \
  Content.Server/Doors \
  Content.Shared/Doors \
  Resources/Prototypes/Entities/Structures/Doors 2>/dev/null || true

echo "=== Map public methods and class members in changed C# files ==="
ast-grep outline Content.Client/Doors/AirlockSystem.cs --items all
ast-grep outline Content.Client/Doors/DoorSystem.cs --items all
ast-grep outline Content.Shared/Doors/Systems/SharedDoorSystem.cs --items all

Length of output: 144356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Changes introduced by replace commits ==="
for commit in 672edef980 2148e3e41c; do
  echo "--- $commit ---"
  git show --stat --oneline "$commit"
  git show --unified=25 --format= "$commit" -- \
    Content.Client/Doors/AirlockSystem.cs \
    Content.Client/Doors/DoorSystem.cs \
    Content.Shared/Doors/Components/AirlockComponent.cs \
    Content.Shared/Doors/Components/DoorComponent.cs
done

echo "=== Final-state registration and all call sites ==="
rg -n -C4 'OpenSpriteStates\.Add|ClosedSpriteStates\.Add|OpenPanelSpriteState|OpeningSpriteState' \
  Content.Client Content.Shared --type=cs

echo "=== Representative RSI states for base airlocks and windoors ==="
python3 - <<'PY'
import json
paths = [
    "Resources/Textures/Structures/Doors/Airlocks/Standard/basic.rsi/meta.json",
    "Resources/Textures/Structures/Doors/Windoors/plasma.rsi/meta.json",
    "Resources/Textures/Structures/Doors/MineralDoors/bananium_door.rsi/meta.json",
]
for path in paths:
    print(f"--- {path} ---")
    with open(path, encoding="utf-8-sig") as f:
        data = json.load(f)
    for state in data.get("states", []):
        if state.get("name") in {
            "open_unlit", "closed_unlit", "opening_unlit", "closing_unlit",
            "panel_open", "panel_opening", "panel_closing",
            "open", "closed", "opening", "closing",
        }:
            print({k: state[k] for k in ("name", "directions", "delays") if k in state})
PY

echo "=== Check Airlock prototype inheritance / duplicated force-sound configuration ==="
rg -n -C3 'id: (Airlock|HighSecDoor)|parent: .*Airlock|forceOpenSound|forceCloseSound|openUnlitVisible' \
  Resources/Prototypes/Entities/Structures/Doors/Airlocks --type=yaml

echo "=== All commented-out executable blocks in affected C# files ==="
rg -n -U -C2 '/\*.*\n(?:.|\n)*?\*/|// Arcane-(Start|End|Edit)' \
  Content.Client/Doors/AirlockSystem.cs \
  Content.Client/Doors/DoorSystem.cs \
  Content.Shared/Doors/Components/AirlockComponent.cs \
  Content.Shared/Doors/Components/DoorComponent.cs \
  Content.Shared/Doors/Systems/SharedDoorSystem.cs

Length of output: 207616


@UmbiMax, да. В PR есть как минимум несколько лишних/временных частей, а ещё пара мест, где «хардкод» на самом деле является нормальной конфигурацией.

Точно стоит убрать

1. Закомментированный исполняемый код

Content.Client/Doors/AirlockSystem.cs, блок в конце OnAppearanceChange:

/* Arcane-Edit-Start: LayerSetAnimationTime(0) here caused one-frame jitter when DoorState.Open arrived animation.
switch (state)
{
    ...
}
*/ // Arcane-Edit-End

Это уже не выполняется и хранит старую реализацию прямо в рабочем файле. Если она не нужна для скорого сравнения — лучше удалить полностью: историю всегда можно посмотреть через Git.

2. Служебные маркеры Arcane-Start, Arcane-End, Arcane-Edit

Они разбросаны по:

  • Content.Client/Doors/AirlockSystem.cs
  • Content.Client/Doors/DoorSystem.cs
  • Content.Shared/Doors/Components/AirlockComponent.cs
  • Content.Shared/Doors/Components/DoorComponent.cs
  • Content.Shared/Doors/Systems/SharedDoorSystem.cs
  • YAML-прототипам дверей.

После завершения переноса это лишний шум. Полезные комментарии должны объяснять почему, а не отмечать автора или границы внесённого изменения. Например, вот это можно оставить, но без маркера:

// Stop the opposite animation so a rapid direction change does not leave both tracks active.

А комментарии вида // Arcane-Edit, // Arcane: Fix, // Arcane-Start ценности в будущем не дают.


Выглядит как ошибочный/лишний workaround

3. OpeningSpriteState как конечное закрытое состояние

В Content.Client/Doors/AirlockSystem.cs сейчас есть:

else
    door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpeningSpriteState));

Это сомнительно. OpeningSpriteState — это анимация перехода (opening_unlit), а ClosedSpriteStates содержит финальные состояния. Для обычных RSI существует отдельное корректное состояние:

"closed_unlit"

То есть, если для ветки !comp.OpenUnlitVisible вообще требуется регистрировать финальный BaseUnlit-слой, должно быть:

door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));

а не comp.OpeningSpriteState.

Иначе после завершения анимации DoorSystem.OnAnimationCompleted включает auto-animation и назначает состоянию закрытой двери opening_unlit. Это привязка к тому, что «первый кадр opening выглядит как closed», а не явное описание нужного состояния.

Вывод: этот else — главный кандидат на замену/удаление. Я бы сначала попробовал заменить на comp.ClosedSpriteState.


Не лишнее, но требует решения на уровне данных

4. Проверка существования open_unlit

Этот блок:

if (TryComp<SpriteComponent>(uid, out var sprite)
    && _sprite.LayerMapTryGet((uid, sprite), DoorVisualLayers.BaseUnlit, out var baseUnlitLayer, false)
    && sprite[baseUnlitLayer] is SpriteComponent.Layer layer
    && layer.ActualRsi is { } rsi
    && rsi.TryGetState(comp.OpenSpriteState, out _))
{
    door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpenSpriteState));
}

не является лишней нагрузкой: он выполняется один раз при старте компонента, а не каждый тик. Он нужен как защита от отсутствующего RSI-state и предотвращает CI-ошибку.

Но это защитный workaround, потому что в Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml включено:

openUnlitVisible: true

А базовые Standard/Glass RSI, наследуемые множеством шлюзов, не содержат open_unlit.

Нормальные варианты:

  • оставить проверку как защиту и убрать openUnlitVisible: true из базового Airlock;
  • либо создать и поддерживать open_unlit во всех RSI, где этот флаг включён;
  • либо переработать визуальную логику так, чтобы BaseUnlit скрывался у открытой двери.

Сейчас проверка нужна, но конфигурация openUnlitVisible: true на самом базовом шлюзе остаётся слишком широкой.


Не является проблемным хардкодом

5. Звуковые пути в YAML

Например:

path: /Audio/_Arcane/Machines/airlock_open_force.ogg

Это корректная data-driven настройка прототипа, а не хардкод в C#.

Повторение force-звуков в base_structureairlocks.yml и highsec.yml допустимо: HighSecDoor не наследуется от базового Airlock, поэтому автоматически получить эти поля не может.

6. pryTime, времена дверей и звуки material/windoor/firelock

Такие значения:

pryTime: 0.25
closeTimeOne: 0.4

тоже находятся там, где им положено быть: в YAML-прототипах. Это настройка баланса, а не технический хардкод. Единственный вопрос — геймдизайн: изменения дефолтов в DoorComponent затрагивают все прототипы, которые не переопределяют значения.

7. Отдельные OpenKey, CloseKey, DenyKey, EmagKey

Это не лишний хардкод. Константы нужны, чтобы AnimationPlayerSystem различал одновременно/быстро сменяющиеся анимации. Это более надёжно, чем один общий AnimationKey.

8. OpenPanelSpriteState для обоих состояний

На первый взгляд это выглядит странно:

door.OpenSpriteStates.Add((WiresVisualLayers.MaintenancePanel, comp.OpenPanelSpriteState));
door.ClosedSpriteStates.Add((WiresVisualLayers.MaintenancePanel, comp.OpenPanelSpriteState));

Но у обычных airlock RSI есть panel_open, а отдельного panel_closed нет. panel_open — статичное состояние; поэтому здесь одинаковое значение, вероятно, намеренно восстанавливает слой после завершения переходной анимации.

Код лучше оставить, но поправить комментарий и XML-документацию OpenPanelSpriteState: текущая формулировка про «first frame for closed» не соответствует тому, что реально делает код.

Коротко

Удалить: закомментированный switch, все маркеры Arcane-*.
Исправить/проверить: OpeningSpriteState в ClosedSpriteStates — заменить на ClosedSpriteState либо удалить после визуальной проверки.
Оставить: RSI-проверку, разные animation keys, звуки и тайминги в YAML, панельный panel_open для обоих финальных состояний.

@UmbiMax UmbiMax changed the title [Tweak] D.O.O.R 2.0 [Tweak] D.O.O.R 2.0 pt. 1 Jul 18, 2026
@UmbiMax

UmbiMax commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai сообщи все причины провалов тестов

@coderabbitai

coderabbitai Bot commented Jul 21, 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"
pr="9"

echo "=== PR head and checks ==="
gh pr view "$pr" --repo "$repo" --json headRefOid,statusCheckRollup \
  --jq '{head: .headRefOid, checks: [.statusCheckRollup[] | {name, status, conclusion, detailsUrl}]}'

echo
echo "=== Failed check job IDs ==="
gh pr checks "$pr" --repo "$repo" --json name,bucket,link,workflowName \
  --jq '.[] | select(.bucket == "fail") | {name, workflowName, link}'

echo
echo "=== Recent failed workflow runs for this PR ==="
gh run list --repo "$repo" --branch "$(gh pr view "$pr" --repo "$repo" --json headRefName --jq .headRefName)" \
  --limit 10 --json databaseId,name,status,conclusion,headSha \
  --jq '.[] | select(.conclusion == "failure") | {databaseId, name, status, conclusion, headSha}'

Length of output: 5795


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ArcaneSS14/arcane"
job="88555872515"

echo "=== Failed-step log (tail) ==="
gh run view --repo "$repo" --job "$job" --log-failed 2>&1 | tail -n 500

echo
echo "=== Failure and diagnostic markers in full shard log ==="
gh run view --repo "$repo" --job "$job" --log 2>&1 |
  rg -n -i -C4 \
  '(^|\s)(failed|error|fatal|exception|assert|test host|crash|hang|hangdump|inactivity|timeout|timed out|exit code|blame)(\s|:|$)' \
  | tail -n 400

echo
echo "=== Job metadata and failed steps ==="
gh api "repos/${repo}/actions/jobs/${job}" \
  --jq '{name, conclusion, started_at, completed_at, steps: [.steps[] | select(.conclusion != "success") | {number, name, status, conclusion}]}'

Length of output: 127939


@UmbiMax, по текущему запуску на коммите e1234eb26af82aff3e92bde0a6e2d57b4067121b есть одна самостоятельная причина провала CI.

1. Integration Tests (shard 4): зависание тестхоста → аварийное завершение

Шаг Run Content.IntegrationTests падает потому, что тестовый процесс два раза подряд не проявил активности в течение 3 минут:

Data collector 'Blame' message: The specified inactivity time of 3 minutes has elapsed.
Collecting hang dumps from testhost and its child processes.
The active test run was aborted. Reason: Test host process crashed

После второго повтора CI завершает шаг с:

Final attempt failed. Child_process exited with error code 124

То есть это не упавший assert и не ошибка компиляции, а повторяемый hang / crash test host.

Тест, который выполнялся в момент остановки

В первом запуске runner указывает:

Content.IntegrationTests.Tests.EntityTest.SpawnAndDirtyAllEntities

Но сам runner отдельно предупреждает:

This test may, or may not be the source of the crash.

Поэтому SpawnAndDirtyAllEntities — главный кандидат для проверки, но пока нельзя утверждать, что именно он создаёт зависание.

Что происходило непосредственно раньше

Перед зависанием в логе были тесты xenoarchaeology и предупреждение из:

Content.Shared.Xenoarchaeology.Artifact.SharedXenoArtifactSystem.RemoveNode(...)

о невозможности прикрепить TestArtifactNode к карте/гридy. Это подозрительное соседнее событие, но оно записано как WARN, не как exception/fail, поэтому не доказано как причина зависания.

Что нужно для точного диагноза

CI сохранил дамп зависшего процесса размером около 8 ГБ:

blame-dump-shard-4.zip

В нём будут стеки всех потоков на момент зависания. Только анализ этого дампа позволит отличить:

  • дедлок в SpawnAndDirtyAllEntities;
  • зависание после SharedXenoArtifactSystem.RemoveNode;
  • проблему тестовой инфраструктуры / раннера;
  • иной фоновой поток или блокировку.

2. Debug CI Required

Это не отдельная причина. Джоб падает, потому что требует успешного завершения всех обязательных проверок, а shard 4 завершился с ошибкой.

Что больше не падает

  • Все остальные integration shards (0–3, 5–7) — успешны.
  • Shard 6 теперь успешен: прежняя проблема с отсутствующим RSI-состоянием open_unlit и тестами PoweredOpenAirlock_Pry_DoesNotClose / UnpoweredOpenAirlock_Pry_Closes в актуальном прогоне устранена.
  • Сборки, YAML/RSI-валидации и упаковка — успешны.

Предупреждения, но не причины текущего провала

В shard 4 также есть предупреждения о дублирующихся FTL-ключах, отсутствующих ru-RU message ID и EF SQLite-миграциях. Они повторяются при запуске тестов, но CI не считает их ошибками и лог не связывает их с падением test host.

Итог: сейчас блокирует PR только повторяющийся hang/crash shard 4; точная первопричина пока не подтверждена без дампа.

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@UmbiMax

UmbiMax commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Пробегись по коду PR. Имеется что-то лишнее?
Возможно найдёшь причины провалов тестов.

@coderabbitai

coderabbitai Bot commented Jul 21, 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"
pr="9"

echo "=== PR head/base and failed checks ==="
gh pr view "$pr" --repo "$repo" --json headRefOid,baseRefName,statusCheckRollup \
  --jq '{head: .headRefOid, base: .baseRefName, checks: [.statusCheckRollup[] | select(.conclusion != "SUCCESS" and .conclusion != null) | {name,status,conclusion,detailsUrl}]}'

echo
echo "=== Merge base and changed files ==="
base="$(git merge-base HEAD "origin/$(gh pr view "$pr" --repo "$repo" --json baseRefName --jq .baseRefName)")"
echo "BASE=$base"
git diff --stat "$base"...HEAD
git diff --name-status "$base"...HEAD

echo
echo "=== Current relevant code diff ==="
git diff --unified=18 "$base"...HEAD -- \
  Content.Client/Doors/AirlockSystem.cs \
  Content.Client/Doors/DoorSystem.cs \
  Content.Server/Doors/Systems/AirlockSystem.cs \
  Content.Server/Wires/WiresSystem.cs \
  Content.Shared/Doors/Components/AirlockComponent.cs \
  Content.Shared/Doors/Components/DoorComponent.cs \
  Content.Shared/Doors/Systems/SharedDoorSystem.cs \
  Content.Shared/Wires/SharedWiresSystem.cs \
  Content.Shared/Remotes/EntitySystems/SharedDoorRemoteSystem.cs \
  Resources/Prototypes/Entities/Structures/Doors

echo
echo "=== Structure of modified door/wires systems ==="
ast-grep outline Content.Client/Doors/AirlockSystem.cs --items all
ast-grep outline Content.Client/Doors/DoorSystem.cs --items all
ast-grep outline Content.Shared/Doors/Systems/SharedDoorSystem.cs --items all
ast-grep outline Content.Shared/Wires/SharedWiresSystem.cs --items all
ast-grep outline Content.Server/Wires/WiresSystem.cs --items all

echo
echo "=== Temporary markers / commented executable code ==="
rg -n -U -C2 '(Arcane|Orion)-(Start|End|Edit|Fix)|/\*[\s\S]*?\*/|^\s*//\s*(public|private|protected|if|else|return|_wiresSystem)' \
  Content.Client/Doors \
  Content.Server/Doors \
  Content.Server/Wires \
  Content.Shared/Doors \
  Content.Shared/Wires || true

echo
echo "=== Latest failed run/job diagnostics ==="
head_ref="$(gh pr view "$pr" --repo "$repo" --json headRefName --jq .headRefName)"
gh run list --repo "$repo" --branch "$head_ref" --limit 12 \
  --json databaseId,headSha,conclusion,name \
  --jq '.[] | select(.conclusion == "failure") | {databaseId,headSha,name}' | head -5

Length of output: 112269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ArcaneSS14/arcane"

echo "=== Current door/airlock implementation with line numbers ==="
cat -n Content.Client/Doors/AirlockSystem.cs
echo "--- DoorSystem relevant methods ---"
sed -n '1,255p' Content.Client/Doors/DoorSystem.cs
echo "--- SharedWiresSystem relevant methods ---"
sed -n '1,155p' Content.Shared/Wires/SharedWiresSystem.cs
echo "--- Server airlock activation ---"
sed -n '1,105p' Content.Server/Doors/Systems/AirlockSystem.cs

echo
echo "=== All UI-opening call sites and AlternativeVerb handlers ==="
rg -n -C4 '\bOpenUserInterface\s*\(|new AlternativeVerb|GetVerbsEvent<AlternativeVerb>|WiresUiKey\.Key' \
  Content.Shared Content.Server Content.Client Content.IntegrationTests --type=cs

echo
echo "=== Door animation-completion subscriptions and animation keys ==="
rg -n -C4 'AnimationCompletedEvent|OpenKey|CloseKey|DenyKey|EmagKey|AnimationKey' \
  Content.Client Content.Shared Content.Server --type=cs

echo
echo "=== Door-related integration tests and assertions ==="
fd -t f -e cs . Content.IntegrationTests | while read -r f; do
  if rg -q 'Door|Airlock|Wires|Windoor|Pry' "$f"; then
    echo "--- $f ---"
    rg -n -C3 'Fact|Theory|Door|Airlock|Wires|Windoor|Pry|Assert\.' "$f"
  fi
done

echo
echo "=== `openUnlitVisible` configurations and RSI sprite states ==="
rg -n -C3 'openUnlitVisible|sprite:.*[Dd]oor|sprite:.*[Aa]irlock' \
  Resources/Prototypes/Entities/Structures/Doors --type=yaml
python3 - <<'PY'
import json
from pathlib import Path
for path in Path("Resources/Textures").rglob("*.rsi/meta.json"):
    if "Doors" not in str(path):
        continue
    with path.open(encoding="utf-8-sig") as f:
        states = {state["name"] for state in json.load(f).get("states", [])}
    if "closed_unlit" in states or "opening_unlit" in states:
        missing = [name for name in ("open_unlit", "closed_unlit", "opening_unlit", "closing_unlit", "panel_open") if name not in states]
        print(f"{path}: missing={','.join(missing) or '-'}")
PY

echo
echo "=== Last completed failed integration job for PR `#9` ==="
gh pr view 9 --repo "$repo" --json commits \
  --jq '.commits[].oid' | while read -r sha; do
  gh run list --repo "$repo" --commit "$sha" --limit 10 \
    --json databaseId,conclusion,name,headSha \
    --jq '.[] | select(.conclusion == "failure" and (.name | test("Integration|CI"; "i"))) | {databaseId,name,headSha}'
done | head -10

echo
echo "=== Known previous shard-4 failure markers ==="
gh run view --repo "$repo" --job 88555872515 --log 2>&1 | \
  rg -n -i -C5 'SpawnAndDirtyAllEntities|hangdump|inactivity|test host|crash|timeout|failed|exception|error' | tail -260 || true

Length of output: 203202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="ArcaneSS14/arcane"
pr="9"

echo "=== Current CI status for HEAD ==="
gh pr view "$pr" --repo "$repo" --json headRefOid,statusCheckRollup \
  --jq '{head: .headRefOid, checks: [.statusCheckRollup[] | {name,status,conclusion,detailsUrl}]}'

echo
echo "=== Every WiresAccessible guard and every wires UI opening path ==="
rg -n -C5 'WiresAccessible|OpenUi\(.*WiresUiKey|OpenUserInterface\s*\(' \
  Content.Shared Content.Server Content.Client --type=cs

echo
echo "=== AlternativeVerb security and interaction conventions ==="
rg -n -U -C8 \
  'GetVerbsEvent<AlternativeVerb>[\s\S]{0,1400}(CanInteract|CanAccess|WiresAccessible|InRange|IsPanelOpen|Act\s*=)' \
  Content.Shared Content.Server Content.Client --type=cs

echo
echo "=== Shared UI APIs and UI-open authorization checks ==="
rg -n -C6 'class SharedUserInterfaceSystem|void OpenUi|OpenUi\(' \
  RobustToolbox/Robust.Shared RobustToolbox/Robust.Client RobustToolbox/Robust.Server \
  Content.Shared/UserInterface Content.Server/UserInterface Content.Client/UserInterface 2>/dev/null || true

echo
echo "=== Airlock components which explicitly disable OpenUnlitVisible ==="
rg -n -C4 'type:\s*Airlock|openUnlitVisible:\s*false|openingSpriteState:|closedSpriteState:' \
  Resources/Prototypes/Entities --type=yaml | head -500

echo
echo "=== Relevant current line ranges ==="
nl -ba Content.Shared/Wires/SharedWiresSystem.cs | sed -n '96,132p'
nl -ba Content.Client/Doors/AirlockSystem.cs | sed -n '20,90p'
nl -ba Content.Server/Doors/Systems/AirlockSystem.cs | sed -n '52,82p'

Length of output: 202259


@UmbiMax, пробежался по актуальному HEAD 4eb0020. Есть один подтверждённый функциональный риск, несколько участков на чистку и один сомнительный fallback. Причину hang-теста по коду PR подтвердить не удалось.

1. Возможен обход защиты проводной панели

В Content.Shared/Wires/SharedWiresSystem.cs, Line 105 новый OnGetVerbs показывает и открывает UI, если панель открыта:

if (!IsPanelOpen(ent.Owner))
    return;

// ...
Act = () => OpenUserInterface(ent, actor),

Но он не проверяет:

  • args.CanAccess;
  • args.CanInteract;
  • WiresPanelSecurityComponent.WiresAccessible.

Это важно, потому что отключённая логика в Content.Server/Doors/Systems/AirlockSystem.cs, Line 61 ранее явно запрещала открыть UI при:

TryComp<WiresPanelSecurityComponent>(uid, out var wiresPanelSecurity) &&
!wiresPanelSecurity.WiresAccessible

А OpenUserInterface() сейчас просто вызывает UI.OpenUi, без видимой дополнительной авторизации.

Что стоит добавить в начало OnGetVerbs:

if (!args.CanAccess || !args.CanInteract || !IsPanelOpen(ent.Owner))
    return;

if (TryComp<WiresPanelSecurityComponent>(ent.Owner, out var security)
    && !security.WiresAccessible)
{
    return;
}

Это также соответствует конвенции остальных GetVerbsEvent<AlternativeVerb> в проекте: почти все такие обработчики сначала проверяют CanAccess и CanInteract.

Рекомендую добавить отдельный integration-тест: открытая панель + WiresAccessible: false не должна давать verb и не должна открыть WiresUiKey.


2. Лишний закомментированный исполняемый код

Его лучше удалить, а не оставлять в исходниках:

  • Content.Client/Doors/AirlockSystem.cs, Lines 140–153 — старый switch, который вручную назначал RSI-state для BaseUnlit;
  • Content.Client/Doors/DoorSystem.cs, Lines 128–131 — отключённая остановка старой анимации;
  • Content.Server/Doors/Systems/AirlockSystem.cs, Lines 61–74 — отключённый старый путь открытия wires UI;
  • Content.Server/Wires/WiresSystem.cs, Lines 585–590 — отключённый OpenUserInterface;
  • закомментированные зависимости _wiresSystem и _uiSystem.

История уже хранится в Git, поэтому эти блоки не помогают отладке, но осложняют дальнейший перенос и создают риск, что кто-то случайно вернёт старый путь вместе с новым.

Также можно убрать все служебные маркеры Arcane-Start, Arcane-End, Arcane-Edit. Полезные комментарии про причину поведения лучше оставить, но без маркеров происхождения изменения.


3. Сомнительное финальное состояние OpeningSpriteState

В Content.Client/Doors/AirlockSystem.cs, Lines 43–45:

else
    door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.OpeningSpriteState));

ClosedSpriteStates используются как конечные состояния после AnimationCompletedEvent, но OpeningSpriteState — переходная анимация (opening_unlit), а не явно закрытый стейт.

Для такой fallback-ветки логичнее:

door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, comp.ClosedSpriteState));

Либо ветку можно удалить, если она более не требуется. Сейчас для основных airlock’ов и windoor’ов openUnlitVisible: true, поэтому ветка почти не участвует в обычном сценарии, но она останется ловушкой для будущего прототипа с AirlockComponent и дефолтным openUnlitVisible: false.


4. Что не считаю лишним

  • Проверка существования open_unlit через LayerMapTryGet + ActualRsi.TryGetState в Content.Client/Doors/AirlockSystem.cs — оправданная защита. В текущем наборе RSI часть файлов действительно имеет open_unlit, часть — нет; значит без проверки снова возможны client ERROR и падение тестов.
  • Раздельные ключи анимаций OpenKey, CloseKey, DenyKey, EmagKey — не выглядят лишними: они позволяют корректно отменять только противоположную анимацию.
  • Звуки и pryTime в YAML — это нормальная data-driven конфигурация, не технический хардкод.

По причинам падения тестов

Ранее исправленная причина

Падение двух airlock-prying тестов из-за несуществующего open_unlit сейчас не подтверждается как актуальное: защитная проверка уже добавлена.

Текущий известный провал — shard 4

Предыдущий shard 4 завершился не assertion failure, а зависанием testhost:

  • XenoArtifactRemoveNodeTest прошёл;
  • затем не было активности более 3 минут;
  • createdump записывал dump размером около 19.4 GB примерно 7 минут;
  • job завершился с Child_process exited with error code 124.

Перед этим был WARN из SharedXenoArtifactSystem.RemoveNode() о невозможности прикрепить test artifact node к карте/grid, но сам тест прошёл. По одному логу нельзя доказать, что это источник зависания.

Связи с изменениями дверей/wires в PR не видно: в конце лога нет door/wires exception, нет упавшего теста этих систем и нет error-лога спрайтов. Точную причину даст только стек из crash/hang dump либо повторное воспроизведение на том же shard.

🐇🔎

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant