diff --git a/Content/Pawn/BP_RWTHVRPawn_Default.uasset b/Content/Pawn/BP_RWTHVRPawn_Default.uasset
index cb05e67f79561281c52e04b21e9bd67726871dbb..145ddf5f938d0a3612c7a609aafa75a30d82063f 100644
Binary files a/Content/Pawn/BP_RWTHVRPawn_Default.uasset and b/Content/Pawn/BP_RWTHVRPawn_Default.uasset differ
diff --git a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset
index 3616819e69aa1ac0f21047b036be9244b1597c76..cba6c2e1875f45603f51e234dbb9b066017654f9 100644
Binary files a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset and b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset differ
diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp
index 4d9b4b6e4dcdb1447e2d9ff98fbfc5400f000589..e2a1511392d96f9890bb52d309346d2465413593 100644
--- a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp
+++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp
@@ -7,9 +7,15 @@
 #include "GameFramework/SpectatorPawn.h"
 #include "Kismet/GameplayStatics.h"
 #include "Logging/StructuredLog.h"
+#include "Pawn/ClusterRepresentationActor.h"
 #include "Utility/RWTHVRUtilities.h"
 
 
+ARWTHVRGameModeBase::ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer)
+{
+	PlayerStateClass = ARWTHVRPlayerState::StaticClass();
+}
+
 FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId,
 										   const FString& Options, const FString& Portal)
 {
@@ -17,6 +23,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle
 	// but I don't really want to introduce a hard dependency here.
 	const FString NodeNameKey = "node";
 	const FString PrimaryNodeIdKey = "PrimaryNodeId";
+	const FString ClusterIdKey = "ClusterId";
 
 	// Check if we're using our custom PlayerState so that we can save the player type there.
 	// If not, just ingore all related args.
@@ -24,6 +31,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle
 
 	if (State != nullptr)
 	{
+		int32 ClusterId = -1;
 		if (UGameplayStatics::HasOption(Options, PrimaryNodeIdKey))
 		{
 			const FString PrimaryNodeId = UGameplayStatics::ParseOption(Options, PrimaryNodeIdKey);
@@ -34,10 +42,22 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle
 				? UGameplayStatics::ParseOption(Options, NodeNameKey)
 				: PrimaryNodeId;
 
+			ClusterId = UGameplayStatics::HasOption(Options, ClusterIdKey)
+				? FCString::Atoi(*UGameplayStatics::ParseOption(Options, ClusterIdKey))
+				: -1;
+
 			const EPlayerType Type =
 				NodeName == PrimaryNodeId ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary;
 			State->RequestSetPlayerType(Type);
 		}
+		else if (GetNetMode() == NM_Standalone && URWTHVRUtilities::IsRoomMountedMode())
+		{
+			ClusterId = 0;
+			const EPlayerType Type =
+				URWTHVRUtilities::IsPrimaryNode() ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary;
+			State->RequestSetPlayerType(Type);
+		}
+		State->SetCorrespondingClusterId(ClusterId);
 	}
 
 	return Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal);
@@ -45,8 +65,47 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle
 
 void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer)
 {
-	if (const ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr)
+	if (ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr)
 	{
+		// If we're in none-standalone netmode, this is only executed on the server, as the GM only exists there.
+		// On standalone, this is executed on every node.
+		int32 ClusterId = State->GetCorrespondingClusterId();
+		if (ClusterId >= 0) // we're either standalone (0) or in an acutal cluster
+		{
+			AClusterRepresentationActor** ClusterRepresentationPtr = ConnectedClusters.Find(ClusterId);
+			AClusterRepresentationActor* ClusterRepresentation;
+			if (!ClusterRepresentationPtr)
+			{
+				// No actor there yet, spawn it
+				FActorSpawnParameters SpawnParameters;
+				SpawnParameters.Name = FName(*FString::Printf(TEXT("ClusterRepresentation_%d"), ClusterId));
+				SpawnParameters.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested;
+				ClusterRepresentation = GetWorld()->SpawnActor<AClusterRepresentationActor>(SpawnParameters);
+				UE_LOGFMT(Toolkit, Display,
+						  "ARWTHVRGameModeBase: Spawned ClusterRepresentationActor {Name} for Cluster {Id}",
+						  ClusterRepresentation->GetName(), ClusterId);
+				ConnectedClusters.Add(ClusterId, ClusterRepresentation);
+			}
+			else
+			{
+				ClusterRepresentation = *ClusterRepresentationPtr;
+			}
+
+			UE_LOGFMT(Toolkit, Display, "ARWTHVRGameModeBase: Using ClusterRepresentationActor {Name} for Cluster {Id}",
+					  *ClusterRepresentation->GetName(), ClusterId);
+
+			// Double check for sanity
+			check(ClusterRepresentation != nullptr);
+
+			State->SetCorrespondingClusterActor(ClusterRepresentation);
+
+			if (State->GetPlayerType() == EPlayerType::nDisplayPrimary)
+			{
+				// We're the owner of the actor!
+				ClusterRepresentation->SetOwner(NewPlayer);
+			}
+		}
+
 		// Do we already have an auto-possessing pawn possessed?
 		if (NewPlayer->GetPawn() && NewPlayer->GetPawn()->IsValidLowLevelFast())
 		{
@@ -56,10 +115,11 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer)
 			return;
 		}
 
+		// When we're not in standalone:
 		// If the new player is a secondary nDisplay node, spawn it only as a Spectator
 		// Potentially we can use MustSpectate instead.
 		UClass* PawnClass;
-		if (State->GetPlayerType() == EPlayerType::nDisplaySecondary)
+		if (GetNetMode() != NM_Standalone && State->GetPlayerType() == EPlayerType::nDisplaySecondary)
 		{
 			// For now, simply use the BP approach of spawning the pawn here. Can do this in a better way potentially.
 			PawnClass = SpectatorClass;
@@ -88,6 +148,5 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer)
 		NewPlayer->Possess(ResultPawn);
 	}
 
-
 	Super::PostLogin(NewPlayer);
 }
diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp
index 0ad60f0c44a7853df65507cd23b9943c6cd8530b..4ae4795c85058331aeea4a2ff08df643b3cccd05 100644
--- a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp
+++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp
@@ -3,8 +3,10 @@
 
 #include "Core/RWTHVRPlayerState.h"
 
+#include "Logging/StructuredLog.h"
 #include "Net/UnrealNetwork.h"
 #include "Net/Core/PushModel/PushModel.h"
+#include "Utility/RWTHVRUtilities.h"
 
 // Boilerplate, copies properties to new state
 void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState)
@@ -17,6 +19,8 @@ void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState)
 		if (IsValid(RWTHVRPlayerState))
 		{
 			RWTHVRPlayerState->SetPlayerType(GetPlayerType());
+			RWTHVRPlayerState->SetCorrespondingClusterId(CorrespondingClusterId);
+			RWTHVRPlayerState->SetCorrespondingClusterActor(CorrespondingClusterActor);
 		}
 	}
 }
@@ -32,6 +36,8 @@ void ARWTHVRPlayerState::OverrideWith(class APlayerState* PlayerState)
 		if (IsValid(RWTHVRPlayerState))
 		{
 			SetPlayerType(RWTHVRPlayerState->GetPlayerType());
+			SetCorrespondingClusterId(RWTHVRPlayerState->CorrespondingClusterId);
+			SetCorrespondingClusterActor(RWTHVRPlayerState->CorrespondingClusterActor);
 		}
 	}
 }
@@ -45,6 +51,8 @@ void ARWTHVRPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& O
 	SharedParams.bIsPushBased = true;
 
 	DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, PlayerType, SharedParams);
+	DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterId, SharedParams);
+	DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterActor, SharedParams);
 }
 
 void ARWTHVRPlayerState::ServerSetPlayerTypeRpc_Implementation(const EPlayerType NewPlayerType)
@@ -57,6 +65,28 @@ void ARWTHVRPlayerState::SetPlayerType(const EPlayerType NewPlayerType)
 	MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, PlayerType, this);
 	PlayerType = NewPlayerType;
 }
+void ARWTHVRPlayerState::SetCorrespondingClusterId(int32 NewCorrespondingClusterId)
+{
+	if (!HasAuthority())
+	{
+		UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster Id on non-authority!");
+		return;
+	}
+
+	MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterId, this);
+	CorrespondingClusterId = NewCorrespondingClusterId;
+}
+void ARWTHVRPlayerState::SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor)
+{
+	if (!HasAuthority())
+	{
+		UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster actor ref on non-authority!");
+		return;
+	}
+
+	MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterActor, this);
+	CorrespondingClusterActor = NewCorrespondingClusterActor;
+}
 
 void ARWTHVRPlayerState::RequestSetPlayerType(const EPlayerType NewPlayerType)
 {
diff --git a/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a280d911113902d4918555b0a6b425d31e97ff8e
--- /dev/null
+++ b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp
@@ -0,0 +1,170 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Pawn/ClusterRepresentationActor.h"
+#include "Core/RWTHVRPlayerState.h"
+#include "Kismet/GameplayStatics.h"
+#include "Logging/StructuredLog.h"
+#include "Utility/RWTHVRUtilities.h"
+
+#if PLATFORM_SUPPORTS_CLUSTER
+#include "DisplayClusterRootActor.h"
+#include "IDisplayCluster.h"
+#include "Config/IDisplayClusterConfigManager.h"
+#include "Game/IDisplayClusterGameManager.h"
+#endif
+
+// Sets default values
+AClusterRepresentationActor::AClusterRepresentationActor()
+{
+	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
+	PrimaryActorTick.bCanEverTick = false;
+	bReplicates = true;
+	SetActorEnableCollision(false);
+
+	SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Origin")));
+}
+
+void AClusterRepresentationActor::BeginPlay()
+{
+	Super::BeginPlay();
+	// will fail if we're in replicated mode and PlayerState has not yet replicated fully
+	// Therefore we also execute this
+	AttachDCRAIfRequired();
+}
+
+void AClusterRepresentationActor::AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState)
+{
+	// We need to identify the correct ClusterRepresentationActor to do the attachment.
+	// 1. Either we are the local net owner -> Primary Node Pawn
+	// This is hard to do as things might not be synchronized yet.
+	// In this case I think this is save to do because either:
+	// - We are in standalone mode, then attach it for all nodes
+	// - If we are a client, this actor has been spawned on the server only. Therefore I assume that if we
+	//   have replicated this actor to our client, we're good to go.
+
+#if PLATFORM_SUPPORTS_CLUSTER
+
+	if (!URWTHVRUtilities::IsRoomMountedMode())
+		return;
+
+	if (bIsAttached)
+	{
+		UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Already attached, skipping repeated attachment.",
+				  GetName());
+		return;
+	}
+
+	UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Starting DCRA Attachment process.", GetName());
+
+	// This should give us the first local player controller
+	const auto* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
+
+	// Only run this for the local controller - redundant, but double check
+	if (!PlayerController || !PlayerController->IsLocalController())
+	{
+		UE_LOGFMT(Toolkit, Warning,
+				  "{Name} AttachDCRAIfRequired: PlayerController not valid or not locally controlled.", GetName());
+		return;
+	}
+	const auto* PlayerState =
+		OptionalPlayerState != nullptr ? OptionalPlayerState : PlayerController->GetPlayerState<ARWTHVRPlayerState>();
+	if (!PlayerState)
+	{
+		UE_LOGFMT(Toolkit, Error,
+				  "{Name} AttachDCRAIfRequired: PlayerState is not valid or not of type "
+				  "ARWTHVRPlayerState.",
+				  GetName());
+		return;
+	}
+
+	const auto CCA = PlayerState->GetCorrespondingClusterActor();
+
+	if (CCA == nullptr) // this can happen often if property isn't replicated yet, this is okay.
+		return;
+
+	UE_LOGFMT(Toolkit, Display,
+			  "{Name} AttachDCRAIfRequired: Player State is {PlayerState}, PlayerState->CCA is {CCA}.", GetName(),
+			  PlayerState->GetName(), CCA->GetName());
+
+	// The local player this is executed on corresponds to this actor
+	if (CCA == this)
+	{
+		UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Attaching DCRA to {Name}.", GetName(),
+				  CCA->GetName());
+
+		bIsAttached = AttachDCRA();
+	}
+#endif
+}
+
+#if PLATFORM_SUPPORTS_CLUSTER
+bool AClusterRepresentationActor::AttachDCRA()
+{
+	if (URWTHVRUtilities::IsRoomMountedMode())
+	{
+		UE_LOGFMT(Toolkit, Display, "{Name}: Trying to attach DCRA", GetName());
+		auto DCRA = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
+
+		if (!IsValid(DCRA))
+		{
+			UE_LOGFMT(Toolkit, Display, "No Valid DCRA in BeginPlay. Spawning manually.");
+
+			DCRA = SpawnDCRA();
+			if (!IsValid(DCRA))
+			{
+				UE_LOGFMT(Toolkit, Error, "Failed to spawn correct DCRA, cannot attach it to ClusterRepresentation.");
+				return false;
+			}
+		}
+		else // if we just spawned the DCRA, it is not yet the primary one and this check makes no sense
+		{
+			if (!DCRA->IsPrimaryRootActor())
+			{
+				UE_LOGFMT(Toolkit, Error, "Found DCRA {Name} is not the primary DCRA of Cluster with Id!",
+						  DCRA->GetName());
+				return false;
+			}
+		}
+
+		bool bAttached = DCRA->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
+		UE_LOGFMT(Toolkit, Display, "Attaching DCRA {DCRA} to {this} returned {Res}", DCRA->GetName(), GetName(),
+				  bAttached);
+
+		DCRA->SetActorEnableCollision(false);
+	}
+	return true;
+}
+
+ADisplayClusterRootActor* AClusterRepresentationActor::SpawnDCRA()
+{
+	UDisplayClusterConfigurationData* ConfigData = IDisplayCluster::Get().GetConfigMgr()->GetConfig();
+
+
+	// Function similar to the one from DisplayClusterGameManager.
+	ADisplayClusterRootActor* RootActor = nullptr;
+	// We need to use generated class as it's the only one available in packaged buidls
+	const FString AssetPath =
+		(ConfigData->Info.AssetPath.EndsWith(TEXT("_C")) ? ConfigData->Info.AssetPath
+														 : ConfigData->Info.AssetPath + TEXT("_C"));
+
+	if (UClass* ActorClass = Cast<UClass>(StaticLoadObject(UClass::StaticClass(), nullptr, *AssetPath)))
+	{
+		// Spawn the actor
+		if (AActor* NewActor = GetWorld()->SpawnActor<AActor>(ActorClass))
+		{
+			RootActor = Cast<ADisplayClusterRootActor>(NewActor);
+
+			if (RootActor)
+			{
+				UE_LOG(Toolkit, Log, TEXT("Instantiated DCRA from asset '%s'"), *ConfigData->Info.AssetPath);
+
+				// Override actor settings in case the config file contains some updates
+				RootActor->OverrideFromConfig(ConfigData);
+			}
+		}
+	}
+	return RootActor;
+}
+
+#endif
diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index d50578531749b295d79961ac45eab7ebfd097c7e..810a6205268b8ebdec7ba7922d1ba697cbe96f88 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -8,8 +8,8 @@
 #include "ILiveLinkClient.h"
 #include "InputMappingContext.h"
 #include "Core/RWTHVRPlayerState.h"
-#include "Kismet/GameplayStatics.h"
 #include "Logging/StructuredLog.h"
+#include "Pawn/ClusterRepresentationActor.h"
 #include "Pawn/InputExtensionInterface.h"
 #include "Pawn/Navigation/CollisionHandlingMovement.h"
 #include "Pawn/ReplicatedCameraComponent.h"
@@ -61,7 +61,7 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
  * as connections now send their playertype over.
  */
 // This pawn's controller has changed! This is called on both server and owning client. If we are the owning client
-// and the master, request that the DCRA is attached to us.
+// and the master, request that the Cluster is attached to us.
 void ARWTHVRPawn::NotifyControllerChanged()
 {
 	Super::NotifyControllerChanged();
@@ -71,17 +71,17 @@ void ARWTHVRPawn::NotifyControllerChanged()
 	if (HasAuthority())
 	{
 		UE_LOG(Toolkit, Display,
-			   TEXT("ARWTHVRPawn: Player Controller has changed, trying to change DCRA attachment if possible..."));
+			   TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible..."));
 		if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
 		{
 			const EPlayerType Type = State->GetPlayerType();
 
 			// Only cluster types are valid here as they are set on connection.
 			// For all other player types this is a race condition
-			if (Type == EPlayerType::nDisplayPrimary || GetNetMode() == NM_Standalone)
+			if (Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary)
 			{
-				UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching DCRA to Pawn {Pawn}.", GetName());
-				AttachDCRAtoPawn();
+				UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching Cluster to Pawn {Pawn}.", GetName());
+				AttachClustertoPawn();
 			}
 		}
 	}
@@ -259,35 +259,35 @@ void ARWTHVRPawn::MulticastAddDCSyncComponent_Implementation()
 #endif
 }
 
-// Executed on the server only: Finds and attaches the CaveSetup Actor, which contains the DCRA to the Pawn.
+// Executed on the server only: Attaches the ClusterRepresentation Actor, which contains the DCRA to the Pawn.
 // It is only executed on the server because attachments are synced to all clients, but not from client to server.
-void ARWTHVRPawn::AttachDCRAtoPawn()
+void ARWTHVRPawn::AttachClustertoPawn()
 {
-	if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast())
+	if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
 	{
-		UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!");
-		return;
-	}
-
-	// Find our CaveSetup actor
-	TArray<AActor*> FoundActors;
-	UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors);
-
-	if (!FoundActors.IsEmpty())
-	{
-		const auto CaveSetupActor = FoundActors[0];
-		FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale;
-		CaveSetupActor->AttachToActor(this, AttachmentRules);
-		UE_LOGFMT(Toolkit, Display, "VirtualRealityPawn: Attaching CaveSetup to our pawn!");
+		const auto ClusterActor = State->GetCorrespondingClusterActor();
+		if (!ClusterActor)
+		{
+			UE_LOGFMT(
+				Toolkit, Error,
+				"ARWTHVRPawn::AttachClustertoPawn: GetCorrespondingClusterActor returned null! This won't work on "
+				"the Cave.");
+			return;
+		}
+		const FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale;
+		bool bAttached = ClusterActor->AttachToComponent(GetRootComponent(), AttachmentRules);
+		// State->GetCorrespondingClusterActor()->OnAttached();
+		UE_LOGFMT(Toolkit, Display,
+				  "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached);
 	}
 	else
 	{
-		UE_LOGFMT(Toolkit, Warning,
-				  "No CaveSetup Actor found which can be attached to the Pawn! This won't work on the Cave.");
+		UE_LOGFMT(Toolkit, Error,
+				  "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave.");
 	}
 
-	// if (HasAuthority()) // Should always be the case here, but double check
-	//	MulticastAddDCSyncComponent();
+	if (HasAuthority()) // Should always be the case here, but double check
+		MulticastAddDCSyncComponent();
 }
 
 void ARWTHVRPawn::SetupMotionControllerSources()
diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h
index 1469ac36caa9b571b765622766a01ed103b26252..be8e65d9da82cf7acc215bc8154ec9772fc7537c 100644
--- a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h
+++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h
@@ -4,6 +4,7 @@
 
 #include "CoreMinimal.h"
 #include "GameFramework/GameModeBase.h"
+#include "Pawn/ClusterRepresentationActor.h"
 #include "RWTHVRGameModeBase.generated.h"
 
 /**
@@ -15,6 +16,8 @@ UCLASS()
 class RWTHVRTOOLKIT_API ARWTHVRGameModeBase : public AGameModeBase
 {
 	GENERATED_BODY()
+public:
+	ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer);
 
 protected:
 	/**
@@ -29,4 +32,8 @@ protected:
 	 * possess. If not, spawn a DefaultPawnClass Pawn and Possess it (Should be BP_VirtualRealityPawn to make sense).
 	 */
 	virtual void PostLogin(APlayerController* NewPlayer) override;
+
+private:
+	UPROPERTY()
+	TMap<int32, AClusterRepresentationActor*> ConnectedClusters;
 };
diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h
index 4c709e204428e4cbcd20d782823676c5b8e12c7c..040c251a6db94e6564b83d120bfd62d7bb6d57e0 100644
--- a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h
+++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h
@@ -5,8 +5,13 @@
 #include "CoreMinimal.h"
 #include "PlayerType.h"
 #include "GameFramework/PlayerState.h"
+#include "Logging/StructuredLog.h"
+#include "Pawn/ClusterRepresentationActor.h"
+#include "Utility/RWTHVRUtilities.h"
 #include "RWTHVRPlayerState.generated.h"
 
+
+class AClusterRepresentationActor;
 enum class EPlayerType : uint8;
 /**
  * Extension of the PlayerState that additionally holds information about what type the player is.
@@ -22,6 +27,26 @@ private:
 	UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetPlayerType, meta = (AllowPrivateAccess))
 	EPlayerType PlayerType = EPlayerType::Desktop;
 
+	/** Replicated cluster ID for this player. Is -1 in case player is not a cluster*/
+	UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetCorrespondingClusterId,
+			  meta = (AllowPrivateAccess))
+	int32 CorrespondingClusterId = -1;
+
+	/** Replicated cluster actor for this player. Is nullptr in case player is not a cluster.
+	 * As this is not guaranteed to be valid on BeginPlay, we need to do a callback to the CorrespondingClusterActor
+	 * here...
+	 */
+	UPROPERTY(ReplicatedUsing = OnRep_CorrespondingClusterActor)
+	TObjectPtr<AClusterRepresentationActor> CorrespondingClusterActor;
+
+	UFUNCTION()
+	virtual void OnRep_CorrespondingClusterActor()
+	{
+		// Only execute this on the local owning player
+		if (HasLocalNetOwner())
+			CorrespondingClusterActor->AttachDCRAIfRequired(this);
+	}
+
 	UFUNCTION(Reliable, Server)
 	void ServerSetPlayerTypeRpc(EPlayerType NewPlayerType);
 
@@ -31,6 +56,15 @@ public:
 	UFUNCTION(BlueprintGetter)
 	EPlayerType GetPlayerType() const { return PlayerType; }
 
+	UFUNCTION(BlueprintGetter)
+	int32 GetCorrespondingClusterId() const { return CorrespondingClusterId; }
+
+	UFUNCTION(BlueprintGetter)
+	AClusterRepresentationActor* GetCorrespondingClusterActor() const { return CorrespondingClusterActor; }
+
+	void SetCorrespondingClusterId(int32 NewCorrespondingClusterId);
+	void SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor);
+
 	UFUNCTION(BlueprintCallable)
 	void RequestSetPlayerType(EPlayerType NewPlayerType);
 
diff --git a/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h
new file mode 100644
index 0000000000000000000000000000000000000000..7826cb001ff5b5ea035f3e6b96920c3edb215138
--- /dev/null
+++ b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h
@@ -0,0 +1,36 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+
+#include "ClusterRepresentationActor.generated.h"
+
+
+class ARWTHVRPlayerState;
+#if PLATFORM_SUPPORTS_CLUSTER
+class ADisplayClusterRootActor;
+#endif
+
+UCLASS()
+class RWTHVRTOOLKIT_API AClusterRepresentationActor : public AActor
+{
+	GENERATED_BODY()
+
+public:
+	// Sets default values for this actor's properties
+	AClusterRepresentationActor();
+
+	virtual void BeginPlay() override;
+
+	void AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState = nullptr);
+
+private:
+	bool bIsAttached = false;
+
+#if PLATFORM_SUPPORTS_CLUSTER
+	bool AttachDCRA();
+	ADisplayClusterRootActor* SpawnDCRA();
+
+#endif
+};
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index 260cdc0408e5f2d1ef9d64c7f56ba4325a8ff123..cd81e497181dcfef757b067ccae8c862c022fa34 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -79,10 +79,6 @@ public:
 	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink")
 	bool bWorldTransform = false;
 
-	/* The class which to search for DCRA attachment. TODO: Make this better it's ugly */
-	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink")
-	TSubclassOf<AActor> CaveSetupActorClass;
-
 protected:
 	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
 	void AddInputMappingContext(const APlayerController* PC, const UInputMappingContext* Context) const;
@@ -107,8 +103,8 @@ protected:
 	UFUNCTION(Reliable, NetMulticast)
 	void MulticastAddDCSyncComponent();
 
-	/* Attaches the DCRA to the pawn */
-	void AttachDCRAtoPawn();
+	/* Attaches the Cluster representation to the pawn */
+	void AttachClustertoPawn();
 
 	/* Set device specific motion controller sources (None, L/R, Livelink) */
 	void SetupMotionControllerSources();