SAFETY ADVISORY #156: Doors are transition points between operational areas. Salvage Solutions Inc. reminds Associates that what lies beyond a door is subject to atmospheric variance, structural compromise, and unregistered occupants. The Company's scanner technology is optimized for asset detection, not hazard prediction. This is working as designed. Every door represents an opportunity for career advancement or career conclusion.
The Problem: Doors in Extraction Horror
When I started building Deep Haul's door system, I had one core design goal: every door should be a gamble. In extraction horror, doors aren't just level geometry - they're decision points that generate tension. Do you open the door to the room with a Data Core? Or do you play it safe and extract with what you have?
The trick is making that tension work in multiplayer. When four players are spread across a derelict station, doors need to:
- Maintain state across the network - If one player opens a door, everyone sees it open
- Create meaningful choices - Access restrictions, hazards, and risks
- Integrate with AI - Door sounds should alert enemies
- Feel responsive - No lag, no desync, no confusion about what's interactable
This post breaks down how Deep Haul's door system handles all of that using Unreal Engine 5's replication framework and Gameplay Ability System.
State Machine: The Foundation
Every door in Deep Haul exists in one of four states, defined in DungeonTypes.h:
enum class EDoorState : uint8
{
Closed, // Default - can be opened if access is granted
Open, // Passable, auto-closes after timer
Sealed, // Stuck - requires Crowbar to force
Destroyed // Forced open - cannot be closed
};
The state machine is simple but covers all the gameplay scenarios:
Closed doors are the default. If you have the right access (more on that below), you interact and it opens. No access? You see a "Keycard Required" prompt and the door stays shut.
Open doors have an auto-close timer. This was a deliberate design choice - in extraction horror, leaving doors open creates ambiguity. When you're backtracking through a station, seeing an open door raises the question: "Did I leave that open, or did something else come through?"
Sealed doors are jammed or damaged. They can't be opened normally, but a Crowbar (a consumable item) can force them into the Destroyed state. This creates resource tension - do you spend a Crowbar to access a locked room, or save it for later?
Destroyed doors stay open permanently. They're usually the result of forcing a Sealed door or deliberate player sabotage. A destroyed door removes ambiguity - it's clearly been breached.
Replication Strategy
The door state is replicated using a simple UPROPERTY(Replicated) variable on ADeepHaulDoor:
UPROPERTY(Replicated, BlueprintReadOnly)
EDoorState DoorState = EDoorState::Closed;
State changes happen only on the server via SetDoorState(), which then replicates to all clients. Clients never modify state directly - they send interaction requests through the Gameplay Ability System, which validates and executes on the server.
This keeps door state deterministic. There's no "I saw it open but you saw it closed" desync - the server's state is the source of truth.
Access Types: Making Doors Into Puzzles
Doors without restrictions are just animated walls. The real gameplay comes from access requirements that force players to explore, backtrack, and make choices.
Deep Haul's door system supports six access types via the EDoorAccessType enum:
None - Anyone can open it. Most common type.
Keycard - Requires a specific keycard item in inventory. The FDoorAccessConfig struct stores a RequiredKeycardTag that matches against items using Gameplay Tags. For example, a door might require Item.Keycard.Cargo, which only drops from certain enemy types or loot containers.
Biometric - Requires a severed hand or biometric bypass item. This is Deep Haul's take on the classic "sci-fi door needs a handprint" trope, but with a darker twist - the hands are lootable from corpses.
Keycode - Triggers a UI prompt where players enter a numeric code. The correct code might be found on a nearby terminal, written on a wall, or hinted at in environmental storytelling. Once entered correctly, the bKeycodeEntered flag persists for that session.
Hacked - Requires using a hacking tool or completing a minigame. This is a future system hook - right now, it's just a boolean flag, but it's designed to integrate with planned hacking mechanics.
Power - Door won't open until power is restored to the area. This ties into the ship's power grid system, creating multi-step puzzles (find generator, restore power, access locked rooms).
Interaction Prompt Integration
When a player looks at a door, their UInteractionComponent (from my AAA interaction prompt system - see the design doc in InteractionPrompt_Design_Doc.md) sends a trace to check if they're looking at an interactable object.
The door responds with contextual text based on access state:
| Access Type | Locked State | Unlocked State |
|---|---|---|
| None | - | "Open Door" |
| Keycard | "Keycard Required" | "Open Door (Keycard)" |
| Biometric | "Biometric Scan Required" | "Open Door (Biometric)" |
| Keycode | "Enter Keycode" | "Open Door" |
| Hacked | "Security Locked" | "Open Door (Bypassed)" |
| Power | "No Power" | "Open Door" |
This gives players immediate feedback about what they need without explicit tutorials. See a "Biometric Scan Required" prompt? You need a severed hand. See "No Power"? Go find the generator.
The Scanner Doesn't Show Hazards (And That's The Point)
Here's a critical design decision from DeepHaul_Door_System_Design.md:
Design Decision (Feb 2026): Scanner does NOT reveal door hazards. Every door is a gamble. The scanner is a "greed generator" - it reveals loot and enemies through walls so players know what's worth risking, but the door itself stays a surprise.
When I first prototyped the scanner, I had it showing everything: loot, enemies, AND hazards behind doors. Playtesters would scan a room, see it was safe, and confidently walk in. That's not extraction horror - that's a checklist simulator.
The current system creates a tension loop:
- Scan the room - "There's a Data Core in there. Worth 500 credits."
- Look at the door - "No obvious hazards, but I can't see what's behind it."
- Make the decision - "Is the loot worth the risk of what might be waiting?"
The scanner tells you what's worth risking, not whether it's safe. That distinction is what makes doors feel dangerous even after 20 hours of play.
Multiplayer Interaction Flow
When a player interacts with a door, here's what happens under the hood:
1. Client-Side Initiation
Player presses the interact button while looking at a door. The UInteractionComponent sends an input event to the Gameplay Ability System, which activates GA_Interact (the interaction gameplay ability).
2. Server-Side Validation
GA_Interact runs on the server (it's a server-authoritative ability). The ability:
- Checks if the door is in a valid state for interaction
- Validates the player has required access (keycard, biometric item, etc.)
- Confirms the player is within interaction range
- Verifies no other player is currently interacting with the door
3. Progress Feedback
For instant interactions (opening an unlocked door), the ability completes immediately. For hold interactions (forcing a Sealed door with a Crowbar), the ability:
- Updates a progress variable (0.0 to 1.0) every tick
- Broadcasts that progress to the HUD via
UInteractionPromptWidget - Shows a radial progress ring (the AAA interaction prompt)
4. Completion or Cancellation
If the ability completes (player held the button for the full duration), the server:
- Changes the door state
- Plays the appropriate animation and sound
- Replicates the new state to all clients
If cancelled (player let go of the button or moved away), the ability ends and nothing changes.
5. Client-Side Cosmetics
Clients receive the replicated door state change and update their visuals:
- Door mesh animates open/closed
- Collision updates (closed doors block movement)
- VFX and audio play at the door location
All clients see the same result at the same time because the state change originates from the server.
AI Integration: Doors as Alert Triggers
This is one of my favorite subtle features. When a door opens, it triggers an audio alert that nearby AI can hear.
From DeepHaul_Door_System_Design.md:
Door sounds alert nearby enemies
The ADeepHaulDoor actor has a UAudioComponent that plays a "door opening" sound. That sound is registered with Unreal's audio perception system, which means AI controllers (like ASentinelAIController) can "hear" it.
If an enemy is patrolling nearby and hears a door open, they transition from Idle to Investigate state. They path to the door's location and search the area. If they don't find a player, they return to patrol. If they DO find a player... well, that's why you need to be careful about opening doors in enemy-heavy zones.
This creates emergent "sound trap" scenarios. Smart players can deliberately open a door to lure an enemy into an ambush. Careless players open a door and alert every enemy in a 20-meter radius.
Technical Implementation Notes
File Structure
The door system is split across several files:
DungeonTypes.h- Enums for door states and access typesDeepHaulDoor.h/.cpp- The door actor itselfInteractionComponent.h/.cpp- Player-side interaction scanningInteractableComponent.h/.cpp- Object-side interaction interfaceGA_Interact.h/.cpp- Gameplay Ability for interaction logic
Replication Pattern
Doors use the standard "server authoritative, replicated state" pattern. No custom serialization, no OnRep functions - just a replicated property that drives cosmetic updates on clients.
This keeps the implementation simple and maintainable. As documented in DeepHaul_Replication_Patterns.md, I use more complex patterns (like deferred physics impulse replication) for physics-heavy systems, but doors don't need that complexity.
Performance Considerations
Doors are placed during level startup via Grid Flow markers (see DeepHaul_Door_System_Design.md for the full procedural generation notes - though that's been superseded by in-editor placement). Each level has 20-40 doors, and each door has:
- A static mesh component (the door itself)
- A collision box (for physics blocking)
- An interactable component (for player interaction)
- An audio component (for AI alerts)
No active ticking unless the door is in the process of opening/closing. This keeps the CPU cost minimal even with dozens of doors per level.
What's Next
The door system is fully functional for Early Access, but there are a few planned expansions:
Hacking Minigames - Right now, EDoorAccessType::Hacked is just a boolean flag. The plan is to add a simple circuit-tracing minigame (think BioShock's pipe puzzles) that appears when you interact with a hacked door.
Environmental Hazards - Some doors will have "hazard states" (on fire, electrified, etc.) that damage players who try to open them without proper equipment. This is already in the design doc; I just haven't implemented the visual effects yet.
Procedural Access Assignment - For future procedural dungeon generation, doors will assign access requirements based on room types. High-value cargo rooms might spawn locked with a keycard, forcing players to explore other areas first.
But for now, the system does exactly what it needs to: make every door a decision.
"This concludes the technical briefing on Door Interaction Protocols. Salvage Solutions Inc. reminds Associates that doors are provided as a convenience, not a guarantee of safety. If a door fails to protect you from atmospheric events, structural collapse, or involuntary biological processing by unregistered occupants, this is within expected operational parameters. Thank you for your compliance." - S.A.L.L.I.