| name | subnautica-ii-deep-synergy-coop-mod |
| description | BepInEx multiplayer mod for Subnautica 2 with synchronized co-op gameplay, adaptive scaling, and shared world state |
| triggers | ["how do I set up the Subnautica 2 co-op mod","configure Deep Synergy multiplayer session","install BepInEx mod for Subnautica 2","troubleshoot Subnautica 2 multiplayer connection","create custom session profile for subnautica coop","sync inventory state in Deep Synergy mod","integrate OpenAI narrator with subnautica mod","fix session desync in subnautica multiplayer"] |
Subnautica II Deep Synergy Co-op Mod
Skill by ara.so — Devtools Skills collection.
Overview
The Deep Synergy Multiplayer Mod transforms Subnautica 2 into a synchronized cooperative experience using BepInEx. It implements deterministic session synchronization, adaptive difficulty scaling based on player count, and peer-to-peer networking via WebRTC. The mod provides shared inventory management, cross-platform multiplayer, and optional AI-powered narrative generation.
Architecture:
- BepInEx 6.0+ plugin architecture (IL2CPP hooks)
- Peer-to-peer WebRTC data channels (no central server)
- Merkle tree-based inventory verification
- NAT punch-through for cross-platform connectivity
- Optional OpenAI/Claude API integration for dynamic narration
Installation
Prerequisites
- Subnautica 2 installed via Steam or GOG
- BepInEx 6.0.x for Unity IL2CPP games
Steps
BepInEx/
plugins/
DeepSynergy/
DeepSynergy.dll
WebRTC.Native.dll
SyncEngine.dll
mkdir -p BepInEx/config/DeepSynergy
Verify Installation
Configuration
Basic Session Profile
Create BepInEx/config/DeepSynergy/synergy_profile.json:
{
"session_name": "My Co-op Session",
"max_players": 4,
"difficulty_scale": "adaptive",
"resource_multiplier": 1.0,
"oxygen_consumption": 1.0,
"creature_spawn_divider": 1,
"enable_pvp": false,
"friendly_fire": false,
"shared_blueprints": true,
"ping_locations_shared": true,
"time_of_day_sync": "all",
"voice_chat_integration": "none",
"api_integration": {
"openai":
Network Configuration
Create BepInEx/config/DeepSynergy/network.json:
{
"nat_traversal": "auto",
"stun_servers": [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302"
],
"max_latency_ms": 150,
"sync_rate_hz": 20,
"compression": "zstd",
"encryption": true
}
AI Integration (Optional)
Create .env in game root directory:
OPENAI_API_KEY=your_openai_key_here
CLAUDE_API_KEY=your_claude_key_here
Enable in synergy_profile.json:
{
"api_integration": {
"openai": {
"enabled": true,
"role": "narrator",
"model": "gpt-4",
"max_tokens": 150,
"temperature": 0.7
},
"claude": {
"enabled": true,
"role": "lore_engine",
"model": "claude-3-opus-20240229",
"max_tokens": 300
}
}
}
Console Commands
Access BepInEx console with F12 (default) in-game:
Session Management
/start_server
/join_session 9B2A-4C7D-E8F1
/leave_session
/synergy_status
Difficulty Scaling
/synergy_scale 1.5
/synergy_scale reset
/synergy_info
Debugging
/sync_check
/force_sync inventory
/peer_info
/seed_override 12345
AI Narration
/api_narrate "discovering ancient alien ruins"
/api_lore_gen "ghost leviathan"
/api_hint current_biome
Programming Guide
Creating Custom Session Profiles Programmatically
using DeepSynergy.API;
using System.Collections.Generic;
public class CustomSessionManager
{
public static SessionProfile CreateHardcoreProfile()
{
return new SessionProfile
{
SessionName = "Hardcore Duo",
MaxPlayers = 2,
DifficultyScale = ScaleMode.Static,
ResourceMultiplier = 0.7f,
OxygenConsumption = 1.3f,
CreatureSpawnDivider = 0.8f,
EnablePvP = false,
FriendlyFire = true,
SharedBlueprints = false,
TimeOfDaySync = SyncMode.Host
};
}
public static void ApplyProfile(SessionProfile profile)
{
var manager = SynergyCore.GetSessionManager();
manager.LoadProfile(profile);
manager.BroadcastConfiguration();
}
}
Hooking into Sync Events
using DeepSynergy.Events;
using BepInEx;
using BepInEx.IL2CPP;
[BepInPlugin("com.mymod.synergyextension", "Synergy Extension", "1.0.0")]
public class SynergyExtension : BasePlugin
{
public override void Load()
{
SyncEvents.OnInventorySync += HandleInventorySync;
SyncEvents.OnPlayerJoined += HandlePlayerJoined;
SyncEvents.OnPlayerLeft += HandlePlayerLeft;
SyncEvents.OnStructurePlaced += HandleStructurePlaced;
}
private void HandleInventorySync(InventorySyncData data)
{
Log.LogInfo($"Inventory synced: {data.ItemCount} items, hash {data.MerkleRoot}");
}
private void HandlePlayerJoined(PlayerJoinData data)
{
Log.LogInfo($"Player joined: {data.PlayerName} (ID: {data.PlayerId})");
}
private void HandlePlayerLeft(PlayerLeaveData data)
{
Log.LogInfo($"Player left: {data.PlayerId}");
}
private void HandleStructurePlaced()
{
Log.LogInfo();
}
}
Custom Difficulty Scaler
using DeepSynergy.Scaling;
public class CustomScaler : IAdaptiveScaler
{
public float CalculateResourceMultiplier(int playerCount)
{
return 1.0f + (playerCount - 1) * 0.25f;
}
public float CalculateCreatureSpawnRate(int playerCount)
{
return 1.0f + Mathf.Log10(playerCount);
}
public float CalculateOxygenConsumption(int playerCount)
{
return Mathf.Max(0.5f, 1.0f - (playerCount - 1) * 0.1f);
}
}
SynergyCore.GetSessionManager().RegisterScaler(new CustomScaler());
Accessing Synchronized State
using DeepSynergy.State;
public class BaseMonitor
{
public void CheckSharedBaseStatus()
{
var stateManager = SynergyCore.GetStateManager();
var bases = stateManager.GetSyncedStructures();
foreach (var structure in bases)
{
Debug.Log($"Structure: {structure.Id}");
Debug.Log($" Type: {structure.Type}");
Debug.Log($" Owner: {structure.PlacedByPlayerId}");
Debug.Log($" Position: {structure.Position}");
Debug.Log($" Integrity: {structure.Integrity}%");
}
var inventory = stateManager.GetSharedInventory();
Debug.Log($"Total items in network: {inventory.TotalItemCount}");
Debug.Log($"Merkle root: {inventory.MerkleRoot}");
}
}
WebRTC Connection Management
using DeepSynergy.Networking;
public class ConnectionManager
{
public async Task<string> CreateSession()
{
var network = SynergyCore.GetNetworkManager();
var sessionCode = await network.CreateHostSession();
Debug.Log($"Session created: {sessionCode}");
network.OnPeerConnected += (peerId) => {
Debug.Log($"Peer connected: {peerId}");
};
network.OnPeerDisconnected += (peerId) => {
Debug.Log($"Peer disconnected: {peerId}");
};
return sessionCode;
}
public async Task<bool> JoinSession(string sessionCode)
{
var network = SynergyCore.GetNetworkManager();
try
{
await network.JoinSession(sessionCode);
Debug.Log("Successfully joined session");
return true;
}
catch (NetworkException ex)
{
Debug.LogError($"Failed to join: {ex.Message}");
return false;
}
}
}
Common Patterns
Graceful Session Migration
When the host disconnects, the mod automatically migrates to another peer:
using DeepSynergy.Migration;
SyncEvents.OnHostMigration += (newHostId) => {
if (newHostId == SynergyCore.GetLocalPlayerId())
{
Debug.Log("Became session host");
var state = SynergyCore.GetStateManager();
state.BroadcastFullState();
}
else
{
Debug.Log($"New host: {newHostId}");
}
};
Conditional AI Narration
using DeepSynergy.AI;
public class NarrativeController
{
private AIIntegration aiIntegration;
public void Initialize()
{
aiIntegration = SynergyCore.GetAIIntegration();
if (!aiIntegration.IsEnabled())
{
Debug.Log("AI integration disabled in profile");
return;
}
}
public async Task NarrateDiscovery(string biome, string discovery)
{
if (!aiIntegration.IsEnabled()) return;
var prompt = $"The team discovered {discovery} in the {biome}. " +
$"Write a brief journal entry (50 words max).";
var narration = await aiIntegration.GenerateNarration(prompt);
SynergyCore.GetChatManager().SendSystemMessage(narration);
}
}
Profile Validation
using DeepSynergy.Validation;
public static class ProfileValidator
{
public static bool ValidateProfile(SessionProfile profile, out string error)
{
error = null;
if (profile.MaxPlayers < 2 || profile.MaxPlayers > 8)
{
error = "MaxPlayers must be between 2 and 8";
return false;
}
if (profile.ResourceMultiplier < 0.1f || profile.ResourceMultiplier > 5.0f)
{
error = "ResourceMultiplier must be between 0.1 and 5.0";
return false;
}
if (profile.OxygenConsumption < 0.1f)
{
error = "OxygenConsumption cannot be less than 0.1";
return false;
}
return true;
}
}
Troubleshooting
Session Code Not Generating
Problem: /start_server returns empty session code
Solutions:
cat BepInEx/config/DeepSynergy/network.json
ping stun.l.google.com
/synergy_debug enable
Inventory Desync
Problem: Players see different inventory states
Solutions:
/force_sync all
/sync_check
/debug merkle_verify
In code:
var stateManager = SynergyCore.GetStateManager();
stateManager.RequestFullSync();
var conflicts = stateManager.GetSyncConflicts();
foreach (var conflict in conflicts)
{
Debug.LogWarning($"Sync conflict: {conflict.Type} at {conflict.Timestamp}");
}
High Latency Between Peers
Problem: Laggy interactions, delayed inventory updates
Solutions:
{
"sync_rate_hz": 10,
"max_latency_ms": 200,
"compression": "lz4"
}
/peer_info
AI Integration Not Responding
Problem: /api_narrate commands fail silently
Solutions:
echo $OPENAI_API_KEY
echo $CLAUDE_API_KEY
grep "AI Integration" BepInEx/LogOutput.log
/api_test openai
/api_test claude
Structure Placement Conflicts
Problem: Two players place structures at same location
Solutions:
The mod uses timestamp-based conflict resolution:
SyncEvents.OnStructureConflict += (conflict) => {
Debug.Log($"Structure conflict resolved: {conflict.WinningPlayerId} " +
$"placed {conflict.StructureType} at {conflict.Position}");
};
Session Migration Failures
Problem: All players disconnect when host leaves
Solutions:
{
"enable_host_migration": true,
"migration_timeout_seconds": 10
}
SyncEvents.OnMigrationStarted += () => {
Debug.Log("Host migration in progress...");
};
SyncEvents.OnMigrationFailed += (reason) => {
Debug.LogError($"Migration failed: {reason}");
SynergyCore.GetStateManager().SaveLocalBackup();
};
Blueprint Sync Issues
Problem: Unlocked blueprints not appearing for all players
Solutions:
/synergy_info | grep blueprints
/force_sync blueprints
var blueprintManager = SynergyCore.GetBlueprintManager();
blueprintManager.SyncAllBlueprints();
var syncedBlueprints = blueprintManager.GetSyncedBlueprints();
Debug.Log($"Synced blueprints: {syncedBlueprints.Count}");
Best Practices
- Always validate profiles before applying to avoid runtime errors
- Use environment variables for API keys, never hardcode
- Enable compression for sessions with 3+ players
- Monitor sync conflicts in production sessions
- Implement graceful degradation when AI APIs are unavailable
- Test host migration scenarios before public sessions
- Use adaptive scaling for variable player counts
Resources