diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 93d693e418fdde59a75556ec96d65e03313e3293..e63ad67079c433a824a63e292b308b483ed38c0d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,30 @@
 # Virtual Reality & Immersive Visualisation Group.
 #-------------------------------------------------------------------------------
 
+spec:
+  inputs:
+    unreal_version:
+      description: The Unreal Engine version in the form of "major.minor"
+      type: string
+      regex: \d+\.\d+
+      default: "5.4"
+    build_type:
+      description: The game build type.
+      type: string
+      options: ["DebugGame", "Shipping"]
+      default: "DebugGame"
+    number_of_old_versions:
+      description: How many successful pipeline builds of this branch should be stored on the cluster.
+      type: number
+      default: 3
+    custom_ndisplay_config:
+      description: Custom nDisplay config stored on the cluster filesystem.
+      type: string
+      default: ""
+
+---
+
+
 # The include file can be change to either be removed or reference a specific commit.
 
 include:
@@ -41,8 +65,9 @@ include:
 # Use the CUSTOM_NDISPLAY_CONFIG variable in case you need a custom ndisplay config. These are always located in /home/vrdemo/configs/ndisplay.
 
 variables:
-    UNREAL_VERSION: "5.4"
-    CUSTOM_NDISPLAY_CONFIG: "aixcave_5_4.ndisplay"
+    UNREAL_VERSION: $[[ inputs.unreal_version ]]
+    NUMBER_OF_OLD_VERSIONS: $[[ inputs.number_of_old_versions ]]
+    CUSTOM_NDISPLAY_CONFIG: $[[ inputs.custom_ndisplay_config ]]
 
 stages:
   - analyze
@@ -113,7 +138,7 @@ Build_Windows:
         GIT_STRATEGY: none   
         GIT_CHECKOUT: "false"
         # CLIENT_CONFIG: "Shipping"
-        CLIENT_CONFIG: "DebugGame"
+        CLIENT_CONFIG: $[[ inputs.build_type ]]
     needs:
         - job: "Generate_Project"
           artifacts: true
@@ -134,7 +159,7 @@ Build_Linux:
         GIT_STRATEGY: none   
         GIT_CHECKOUT: "false"
         # CLIENT_CONFIG: "Shipping"
-        CLIENT_CONFIG: "DebugGame"
+        CLIENT_CONFIG: $[[ inputs.build_type ]]
     needs:
         - job: "Generate_Project"
           artifacts: true
@@ -146,16 +171,7 @@ Build_Linux_Without_Cluster:
     needs:
         - job: "Generate_Project_Without_Cluster"
           artifacts: true
-
-# Deploys to vrdev
-.Deploy_Windows:
-    rules: 
-      - if: $CI_PIPELINE_SOURCE == "web"
-      - if: $CI_PIPELINE_SOURCE == "schedule"
-    extends: .Deploy_VRDev_
-    needs:
-        - job: "Build_Windows"
-          artifacts: true          
+             
 
 # Deploys to vrdemo instead of av006de. Use extends: .Deploy_CAVE_ to deploy to legacy av006de
 Deploy_CAVE:
diff --git a/Content/Pawn/BP_RWTHVRPawn_Default.uasset b/Content/Pawn/BP_RWTHVRPawn_Default.uasset
index 145ddf5f938d0a3612c7a609aafa75a30d82063f..98bd1d8298820e85ce83fa1eaf0cee4f8d04c159 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 cba6c2e1875f45603f51e234dbb9b066017654f9..435be5fe5966d3796891186bfc38247df833f47b 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/ClientTransformReplication.cpp b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp
index 0bce9302ae30860f0211918a620be91e9666ea21..011c83377dbcf497f2df887a00c4ec6250838275 100644
--- a/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp
+++ b/Source/RWTHVRToolkit/Private/Core/ClientTransformReplication.cpp
@@ -26,8 +26,8 @@ void UClientTransformReplication::UpdateState(float DeltaTime)
 		// Only do this if we actually replicate the actor
 		if (GetIsReplicated())
 		{
-			const FVector Loc = OwningActor->GetActorLocation();
-			const FRotator Rot = OwningActor->GetActorRotation();
+			const FVector Loc = OwningActor->GetRootComponent()->GetRelativeLocation();
+			const FRotator Rot = OwningActor->GetRootComponent()->GetRelativeRotation();
 
 			// Only update state if the local state changed
 			if (!Loc.Equals(ReplicatedTransform.Position) || !Rot.Equals(ReplicatedTransform.Rotation))
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
index 18e515817875cb60c35090b4dbfb4300051b8dac..fac35ab8270c772dcd94b2b67d1808eb4f8b5bd5 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
@@ -70,6 +70,13 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick
 	// Call hover end events on all components that were previously in range, but not anymore
 	for (UInteractableComponent* PrevInteractableComp : PreviousInteractableComponentsInRange)
 	{
+		// It can happen that a previous component was destroyed
+		if (!PrevInteractableComp || !PrevInteractableComp->IsValidLowLevel())
+		{
+			ComponentsToRemove.Add(PrevInteractableComp); // might have to use indices here
+			continue;
+		}
+
 		if (!CurrentInteractableCompsInRange.Contains(PrevInteractableComp))
 		{
 			ComponentsToRemove.AddUnique(PrevInteractableComp);
diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
index dc9c9022a86fc6e57b1dd6e19927519bca283161..a0b82558ca19ade0331653e513f27486249dd4f4 100644
--- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
+++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp
@@ -18,6 +18,10 @@
 #include "Utility/RWTHVRUtilities.h"
 
 #if PLATFORM_SUPPORTS_CLUSTER
+#include "DisplayClusterRootActor.h"
+#include "ScalableConfigInterface.h"
+#include "IDisplayCluster.h"
+#include "Game/IDisplayClusterGameManager.h"
 #include "Components/DisplayClusterSceneComponentSyncParent.h"
 #endif
 
@@ -56,11 +60,7 @@ ARWTHVRPawn::ARWTHVRPawn(const FObjectInitializer& ObjectInitializer) : Super(Ob
 		});
 }
 
-void ARWTHVRPawn::BeginPlay()
-{
-	Super::BeginPlay();
-	InitialWorldToMeters = GetWorldSettings()->WorldToMeters;
-}
+void ARWTHVRPawn::BeginPlay() { Super::BeginPlay(); }
 
 void ARWTHVRPawn::Tick(float DeltaSeconds)
 {
@@ -75,17 +75,37 @@ void ARWTHVRPawn::Tick(float DeltaSeconds)
 }
 
 /*
- *	Scales the Pawn while also adjusting the WorldToMeters ratio to adjust for pupillary distance.
- *	Only supports uniform scaling.
+ *	Scales the Pawn. Only supports uniform scaling.
  */
 void ARWTHVRPawn::SetScale(float NewScale)
 {
-	FVector OldScale = GetActorScale();
 	UniformScale = NewScale;
 	FVector NewScaleVector = FVector(UniformScale, UniformScale, UniformScale);
-	GetWorldSettings()->WorldToMeters = InitialWorldToMeters * UniformScale;
 	SetActorRelativeScale3D(NewScaleVector);
-	OnScaleChanged.Broadcast(OldScale, NewScale);
+
+#if PLATFORM_SUPPORTS_CLUSTER
+	const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>();
+	if (URWTHVRUtilities::IsRoomMountedMode() && State && State->GetCorrespondingClusterActor())
+	{
+		if (const auto GameMgr = IDisplayCluster::Get().GetGameMgr())
+		{
+			if (const auto ClusterRootActor = GameMgr->GetRootActor())
+			{
+				if (ClusterRootActor->Implements<UScalableConfigInterface>())
+				{
+					IScalableConfigInterface::Execute_OnScaleChanged(ClusterRootActor, NewScale);
+				}
+				else
+				{
+					UE_LOGFMT(Toolkit, Warning,
+							  "The ClusterRootActor {0} does not implement the ScalableConfigInterface. Scaling the "
+							  "Pawn on the cluster will lead to unintended behavior.",
+							  ClusterRootActor->GetName());
+				}
+			}
+		}
+	}
+#endif
 }
 
 float ARWTHVRPawn::GetScale() { return UniformScale; }
@@ -118,6 +138,12 @@ void ARWTHVRPawn::NotifyControllerChanged()
 				AttachClustertoPawn();
 			}
 		}
+		else
+		{
+			UE_LOGFMT(Toolkit, Warning,
+					  "ARWTHVRPawn: PlayerState is not a subclass of ARWTHVRPlayerState. Cluster attachment only works "
+					  "with correct PlayerStates!");
+		}
 	}
 }
 
@@ -150,14 +176,18 @@ void ARWTHVRPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponen
 	if (ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>())
 	{
 		// Might not be properly synced yet?
-		const EPlayerType Type = State->GetPlayerType();
+		EPlayerType Type = State->GetPlayerType();
 
 		// Don't do anything with the type if it's been set to clustertype or anything.
 		// This is already being done when connecting to the server.
 		const bool bClusterType = Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary;
 
-		if (!bClusterType && URWTHVRUtilities::IsHeadMountedMode())
+		if (!bClusterType)
 		{
+			if (URWTHVRUtilities::IsHeadMountedMode())
+				Type = EPlayerType::HMD;
+
+			UE_LOGFMT(Toolkit, Display, "Pawn: Requesting Player Type {T}...", StaticCast<int8>(Type));
 			// Could be too early to call this RPC...
 			State->RequestSetPlayerType(Type);
 		}
diff --git a/Source/RWTHVRToolkit/Private/Utility/RWTHVRUtilities.cpp b/Source/RWTHVRToolkit/Private/Utility/RWTHVRUtilities.cpp
index 0ac047f0c52247b45ec6d78170bf482b88eb6f05..af8fc1089e86f0bdaf6dd63dae95be908bf3fd3c 100644
--- a/Source/RWTHVRToolkit/Private/Utility/RWTHVRUtilities.cpp
+++ b/Source/RWTHVRToolkit/Private/Utility/RWTHVRUtilities.cpp
@@ -1,6 +1,7 @@
 #include "Utility/RWTHVRUtilities.h"
 
 #include "AudioDevice.h"
+#include "HeadMountedDisplayFunctionLibrary.h"
 #include "IHeadMountedDisplay.h"
 #include "IXRTrackingSystem.h"
 #include "Engine/Engine.h"
@@ -16,12 +17,7 @@ DEFINE_LOG_CATEGORY(Toolkit);
 
 bool URWTHVRUtilities::IsDesktopMode() { return !IsRoomMountedMode() && !IsHeadMountedMode(); }
 
-bool URWTHVRUtilities::IsHeadMountedMode()
-{
-	// In editor builds: checks for EdEngine->IsVRPreviewActive()
-	// In packaged builds: checks for `-vr` in commandline or bStartInVR in UGeneralProjectSettings
-	return FAudioDevice::CanUseVRAudioDevice();
-}
+bool URWTHVRUtilities::IsHeadMountedMode() { return UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled(); }
 
 bool URWTHVRUtilities::IsRoomMountedMode()
 {
@@ -38,7 +34,7 @@ bool URWTHVRUtilities::IsPrimaryNode()
 	return URWTHVRClusterUtilities::IsPrimaryNode();
 #else
 	return false;
-#endif	
+#endif
 }
 
 float URWTHVRUtilities::GetEyeDistance()
diff --git a/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h
index ac364067efcca3c0c21a6ae940b25ece78914647..c31ed9bd3093670ab88e5c26e38f56a68b86c2f3 100644
--- a/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h
+++ b/Source/RWTHVRToolkit/Public/Core/ClientTransformReplication.h
@@ -56,7 +56,10 @@ protected:
 		// For now, directly apply the transforms:
 		auto* OwningActor = GetOwner();
 		if (OwningActor && OwningActor->HasValidRootComponent())
-			OwningActor->SetActorLocationAndRotation(ReplicatedTransform.Position, ReplicatedTransform.Rotation);
+		{
+			OwningActor->SetActorRelativeLocation(ReplicatedTransform.Position);
+			OwningActor->SetActorRelativeRotation(ReplicatedTransform.Rotation);
+		}
 	}
 
 	// Unreliable Server RPC that sends the transform from owning client to the server
diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
index 40eb174f5f82b11a9c30b3130fca1f702a687409..dcd506b1456848cba48f946da63d930b5c651ea1 100644
--- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
+++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h
@@ -122,6 +122,5 @@ protected:
 
 private:
 	UInputComponent* ActivePlayerInputComponent;
-	float InitialWorldToMeters;
 	float UniformScale;
 };
diff --git a/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs b/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs
index a949553aaa90bd704c53e9ace2f749073a5441bf..3ca3534e7eb530fa1f3847c8cfc8d4fb18051875 100644
--- a/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs
+++ b/Source/RWTHVRToolkit/RWTHVRToolkit.Build.cs
@@ -38,7 +38,7 @@ public class RWTHVRToolkit : ModuleRules
 		PrivateDependencyModuleNames.AddRange(
 			new string[]
 			{
-				"NetCore"
+				"NetCore", "XRBase"
 			}
 		);
 		if (Target.bBuildEditor == true)