Land bounded OpenXR runtime ownership substrate

This commit is contained in:
axiomlogicnexus 2026-07-01 06:37:08 +00:00
parent 951c927ffb
commit 3ed296b893
18 changed files with 1842 additions and 80 deletions

View file

@ -81,4 +81,50 @@ DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent
DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks
-ConsoleKeys=Tilde
+ConsoleKeys=Tilde
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=Gamepad_LeftY,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=Vive_Left_Trackpad_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=MixedReality_Left_Thumbstick_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=MixedReality_Left_Trackpad_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=OculusTouch_Left_Thumbstick_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=ValveIndex_Left_Thumbstick_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveForward",Key=ValveIndex_Left_Trackpad_Y,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=Gamepad_LeftX,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=Vive_Left_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=MixedReality_Left_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=MixedReality_Left_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=OculusTouch_Left_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=ValveIndex_Left_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_MoveRight",Key=ValveIndex_Left_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=Gamepad_RightX,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=Vive_Right_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=MixedReality_Right_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=MixedReality_Right_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=OculusTouch_Right_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=ValveIndex_Right_Thumbstick_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_Turn",Key=ValveIndex_Right_Trackpad_X,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=Gamepad_RightY,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=Vive_Right_Trackpad_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=MixedReality_Right_Thumbstick_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=MixedReality_Right_Trackpad_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=OculusTouch_Right_Thumbstick_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=ValveIndex_Right_Thumbstick_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LookPitch",Key=ValveIndex_Right_Trackpad_Y,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftGrip",Key=Gamepad_LeftTriggerAxis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftGrip",Key=OculusTouch_Left_Grip_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftGrip",Key=ValveIndex_Left_Grip_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightGrip",Key=Gamepad_RightTriggerAxis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightGrip",Key=OculusTouch_Right_Grip_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightGrip",Key=ValveIndex_Right_Grip_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftTrigger",Key=Vive_Left_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftTrigger",Key=MixedReality_Left_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftTrigger",Key=OculusTouch_Left_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_LeftTrigger",Key=ValveIndex_Left_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightTrigger",Key=Vive_Right_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightTrigger",Key=MixedReality_Right_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightTrigger",Key=OculusTouch_Right_Trigger_Axis,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_RightTrigger",Key=ValveIndex_Right_Trigger_Axis,Scale=1.000000)
+ActionMappings=(ActionName="HyperTwistXr_Recenter",Key=Home,bShift=False,bCtrl=False,bAlt=False,bCmd=False)
+ActionMappings=(ActionName="HyperTwistXr_Recenter",Key=Gamepad_Special_Left,bShift=False,bCtrl=False,bAlt=False,bCmd=False)
+ActionMappings=(ActionName="HyperTwistXr_ToggleSnapTurn",Key=Insert,bShift=False,bCtrl=False,bAlt=False,bCmd=False)
+ActionMappings=(ActionName="HyperTwistXr_TogglePostureMode",Key=End,bShift=False,bCtrl=False,bAlt=False,bCmd=False)

View file

@ -12,6 +12,7 @@
#include "HyperTwistTraining/HyperTwistTrainingMagicCube5DLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingRuntimeLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingViewerLibrary.h"
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/ConfigCacheIni.h"
@ -78,6 +79,19 @@ namespace HyperTwistTrainingPanelWidgetInternal
bool bReady = false;
};
struct FXrRuntimeOwnershipFacts
{
FHyperTwistXrRuntimeOwnershipSurface RuntimeSurface;
FHyperTwistXrPreferenceProfile PreferenceProfile;
FHyperTwistXrControllerBindingProfile ControllerBindingProfile;
bool bRuntimeOwnerReady = false;
bool bPreferenceProfileReady = false;
bool bControllerBindingProfileReady = false;
int32 PreferenceFieldCount = 0;
int32 ControllerBindingAxisCount = 0;
int32 SupportedControllerFamilyCount = 0;
};
FString DescribeBool(const bool bValue)
{
return HyperTwistTrainingControlSurfaceFormatting::DescribeBool(bValue);
@ -113,6 +127,28 @@ namespace HyperTwistTrainingPanelWidgetInternal
return HyperTwistTrainingControlSurfaceFormatting::DescribeIdOrFallback(Value);
}
FString DescribeIdIfReady(
const bool bReady,
const FString& Value
)
{
return HyperTwistTrainingControlSurfaceFormatting::DescribeIdIfReady(
bReady,
Value
);
}
int32 DescribeCountIfReady(
const bool bReady,
const int32 Count
)
{
return HyperTwistTrainingControlSurfaceFormatting::DescribeCountIfReady(
bReady,
Count
);
}
FString DescribeKeyForRoster(const FKey& Key)
{
return Key.IsValid() ? Key.GetDisplayName().ToString() : TEXT("n/a");
@ -545,6 +581,102 @@ namespace HyperTwistTrainingPanelWidgetInternal
);
}
FXrRuntimeOwnershipFacts BuildXrRuntimeOwnershipFacts()
{
FXrRuntimeOwnershipFacts Facts;
Facts.RuntimeSurface =
UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface();
Facts.PreferenceProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
Facts.ControllerBindingProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile();
Facts.bRuntimeOwnerReady = Facts.RuntimeSurface.IsStructurallyValid();
Facts.bPreferenceProfileReady = Facts.PreferenceProfile.IsStructurallyValid();
Facts.bControllerBindingProfileReady =
Facts.ControllerBindingProfile.IsStructurallyValid();
Facts.PreferenceFieldCount = Facts.PreferenceProfile.PreferenceFieldTags.Num();
Facts.ControllerBindingAxisCount =
Facts.ControllerBindingProfile.RequiredAxisNames.Num();
Facts.SupportedControllerFamilyCount =
Facts.RuntimeSurface.SupportedControllerFamilies.Num();
return Facts;
}
FString DescribeXrDominantHand(const EHyperTwistXrDominantHand DominantHand)
{
return DominantHand == EHyperTwistXrDominantHand::Left
? TEXT("left")
: TEXT("right");
}
FString DescribeXrPostureMode(const EHyperTwistXrPostureMode PostureMode)
{
switch (PostureMode)
{
case EHyperTwistXrPostureMode::Seated:
return TEXT("seated");
case EHyperTwistXrPostureMode::Hybrid:
return TEXT("hybrid");
default:
return TEXT("standing");
}
}
FString BuildXrRuntimeOwnerLine(const FXrRuntimeOwnershipFacts& Facts)
{
return FString::Printf(
TEXT("Bounded XR runtime owner: %s | runtime %s | preferences %s | bindings %s | motion sources %s/%s | controller families %d."),
*DescribeReadyState(Facts.bRuntimeOwnerReady),
*DescribeIdIfReady(Facts.bRuntimeOwnerReady, Facts.RuntimeSurface.RuntimeOwnerId),
*DescribeIdIfReady(
Facts.bPreferenceProfileReady,
Facts.PreferenceProfile.ProfileId),
*DescribeIdIfReady(
Facts.bControllerBindingProfileReady,
Facts.ControllerBindingProfile.ProfileId),
*DescribeIdIfReady(Facts.bRuntimeOwnerReady, Facts.RuntimeSurface.LeftMotionSourceId),
*DescribeIdIfReady(Facts.bRuntimeOwnerReady, Facts.RuntimeSurface.RightMotionSourceId),
DescribeCountIfReady(
Facts.bRuntimeOwnerReady,
Facts.SupportedControllerFamilyCount)
);
}
FString BuildXrPreferenceOwnerLine(const FXrRuntimeOwnershipFacts& Facts)
{
return FString::Printf(
TEXT("XR preference ownership: %s | profile %s | posture %s | dominant hand %s | snap turn %.0f | world scale %.0f | preference fields %d."),
*DescribeReadyState(Facts.bPreferenceProfileReady),
*DescribeIdIfReady(
Facts.bPreferenceProfileReady,
Facts.PreferenceProfile.ProfileId),
*DescribeXrPostureMode(Facts.PreferenceProfile.PostureMode),
*DescribeXrDominantHand(Facts.PreferenceProfile.DominantHand),
Facts.PreferenceProfile.SnapTurnDegrees,
Facts.PreferenceProfile.WorldToMetersScale,
DescribeCountIfReady(
Facts.bPreferenceProfileReady,
Facts.PreferenceFieldCount)
);
}
FString BuildXrControllerBindingLine(const FXrRuntimeOwnershipFacts& Facts)
{
return FString::Printf(
TEXT("XR controller bindings: %s | profile %s | required axes %d | families %d."),
*DescribeReadyState(Facts.bControllerBindingProfileReady),
*DescribeIdIfReady(
Facts.bControllerBindingProfileReady,
Facts.ControllerBindingProfile.ProfileId),
DescribeCountIfReady(
Facts.bControllerBindingProfileReady,
Facts.ControllerBindingAxisCount),
DescribeCountIfReady(
Facts.bControllerBindingProfileReady,
Facts.SupportedControllerFamilyCount)
);
}
const FXrControllerBoundaryFacts& GetXrControllerBoundaryFacts()
{
static const FXrControllerBoundaryFacts CachedFacts = []()
@ -558,13 +690,13 @@ namespace HyperTwistTrainingPanelWidgetInternal
FString BuildControlInputXrBoundaryLine()
{
return TEXT("Current XR host/plugin decision: desktop-hosted No-Go on native OpenXR/controller widening; any future reopen packet would need dedicated headset/controller runtime owners, user-facing settings/rebinding, and Windows package validation.");
return TEXT("Current XR host/plugin decision: bounded OpenXR runtime ownership is now first-party-owned inside the desktop simulator, but full native controller widening remains desktop-hosted No-Go until polished settings/rebinding follow-through and Windows packaged controller validation are complete.");
}
FString BuildControlSettingsXrBoundaryLine(const bool bOpenXrProjectPluginEnabled)
{
return FString::Printf(
TEXT("XR/controller completion: the host decision is already fixed to desktop-hosted No-Go; project OpenXR plugin %s; dedicated runtime owners, polished settings/rebinding, and packaged controller proof are not yet shipped."),
TEXT("XR/controller completion: the broader host decision remains desktop-hosted No-Go; project OpenXR plugin %s; bounded runtime owners now exist, but polished settings/rebinding follow-through and packaged controller proof are not yet shipped."),
bOpenXrProjectPluginEnabled ? TEXT("enabled") : TEXT("not enabled")
);
}
@ -572,7 +704,7 @@ namespace HyperTwistTrainingPanelWidgetInternal
FString BuildControlProfileRosterXrBoundaryLine(const bool bOpenXrProjectPluginEnabled)
{
return FString::Printf(
TEXT("Controller rebinding: not yet shipped; the current native roster ends at keyboard, scenic presence presets, and dedicated family selector/view profiles, the project OpenXR plugin is %s, and the XR host decision remains desktop-hosted No-Go on native OpenXR/controller widening."),
TEXT("Controller rebinding: not yet shipped; the current native roster now includes a bounded OpenXR runtime plus controller binding profile, but the project OpenXR plugin is %s and the broader XR host decision remains desktop-hosted No-Go until packaged proof and richer settings/rebinding are complete."),
bOpenXrProjectPluginEnabled ? TEXT("enabled") : TEXT("not enabled")
);
}
@ -1305,8 +1437,24 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
HyperTwistTrainingPanelWidgetInternal::BuildImmersiveSessionRecallFacts();
const bool bImmersiveSessionRecallReady =
ImmersiveSessionRecallFacts.bReady;
const bool bBoundedPreferencesContinuityReady =
bCameraSettingsContinuityReady && bImmersiveSessionRecallReady;
const HyperTwistTrainingPanelWidgetInternal::FXrRuntimeOwnershipFacts
XrRuntimeOwnershipFacts =
HyperTwistTrainingPanelWidgetInternal::BuildXrRuntimeOwnershipFacts();
Surface.XrRuntimeOwnerId = XrRuntimeOwnershipFacts.RuntimeSurface.RuntimeOwnerId;
Surface.XrPreferenceProfileId = XrRuntimeOwnershipFacts.PreferenceProfile.ProfileId;
Surface.XrControllerBindingProfileId =
XrRuntimeOwnershipFacts.ControllerBindingProfile.ProfileId;
Surface.XrPreferenceFieldCount = XrRuntimeOwnershipFacts.PreferenceFieldCount;
Surface.XrControllerBindingAxisCount =
XrRuntimeOwnershipFacts.ControllerBindingAxisCount;
Surface.XrSupportedControllerFamilyCount =
XrRuntimeOwnershipFacts.SupportedControllerFamilyCount;
Surface.bBoundedXrRuntimeOwnerReady =
XrRuntimeOwnershipFacts.bRuntimeOwnerReady;
Surface.bXrPreferenceProfileReady =
XrRuntimeOwnershipFacts.bPreferenceProfileReady;
Surface.bXrControllerBindingProfileReady =
XrRuntimeOwnershipFacts.bControllerBindingProfileReady;
Surface.bFinishedXrRuntimeReady = false;
Surface.bFinishedPreferencesReady = false;
@ -1323,7 +1471,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
XrBoundaryFacts.bRequiresWindowsPackagedControllerValidationForReopen;
Surface.SummaryLine = FString::Printf(
TEXT("Keyboard and mouse lanes are ready today; XR/controller groundwork exists, the project OpenXR plugin is %s, and finished XR runtime remains intentionally unshipped."),
TEXT("Keyboard and mouse lanes are ready today; bounded OpenXR runtime ownership, controller bindings, and preference profiles now exist, the project OpenXR plugin is %s, and finished packaged XR proof remains intentionally unshipped."),
Surface.bOpenXrProjectPluginEnabled ? TEXT("enabled") : TEXT("not enabled")
);
Surface.ClassicCubeStatusLine = FString::Printf(
@ -1364,7 +1512,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
? FString::Join(InputGroundworkFacts.MotionControllerFamilies, TEXT(", "))
: FString(TEXT("none"));
Surface.XrStatusLine = FString::Printf(
TEXT("XR groundwork: EnhancedInput config %s | motion controls %s | controller families %d (%s) | OpenXR project plugin %s | immersive presence contract %s | finished XR runtime not yet shipped."),
TEXT("XR groundwork: EnhancedInput config %s | motion controls %s | controller families %d (%s) | OpenXR project plugin %s | immersive presence contract %s | bounded runtime owner %s (%s) | controller binding %s (%s) | finished XR runtime not yet shipped."),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bEnhancedInputProjectGroundworkPresent),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
@ -1374,7 +1522,15 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bOpenXrProjectPluginEnabled),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bImmersivePresenceContractReady)
Surface.bImmersivePresenceContractReady),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bBoundedXrRuntimeOwnerReady),
*HyperTwistTrainingPanelWidgetInternal::DescribeIdOrFallback(
Surface.XrRuntimeOwnerId),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bXrControllerBindingProfileReady),
*HyperTwistTrainingPanelWidgetInternal::DescribeIdOrFallback(
Surface.XrControllerBindingProfileId)
);
Surface.PreferencesStatusLine =
HyperTwistTrainingControlSurfaceFormatting::BuildControlInputPreferencesStatusLine(
@ -1385,6 +1541,18 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
ImmersiveSessionRecallFacts.Boundary.RecallScopeIds.Num(),
ImmersiveSessionRecallFacts.Boundary.PreferenceFieldTags.Num()
);
Surface.XrRuntimeOwnerLine =
HyperTwistTrainingPanelWidgetInternal::BuildXrRuntimeOwnerLine(
XrRuntimeOwnershipFacts
);
Surface.XrPreferenceOwnerLine =
HyperTwistTrainingPanelWidgetInternal::BuildXrPreferenceOwnerLine(
XrRuntimeOwnershipFacts
);
Surface.XrControllerBindingLine =
HyperTwistTrainingPanelWidgetInternal::BuildXrControllerBindingLine(
XrRuntimeOwnershipFacts
);
Surface.NextPacketLine =
HyperTwistTrainingPanelWidgetInternal::BuildControlInputXrBoundaryLine();
Surface.StatusLine = FString::Printf(
@ -1405,7 +1573,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
: TEXT("limited")
);
Surface.DetailLine = FString::Printf(
TEXT("%s | %s | %s | %s | %s | %s | %s | %s"),
TEXT("%s | %s | %s | %s | %s | %s | %s | %s | %s | %s | %s"),
*Surface.ClassicCubeStatusLine,
*Surface.ClassicCubePointerLine,
*Surface.ClassicCubeActionShortcutLine,
@ -1413,6 +1581,9 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
*Surface.HigherDimensionalStatusLine,
*Surface.XrStatusLine,
*Surface.PreferencesStatusLine,
*Surface.XrRuntimeOwnerLine,
*Surface.XrPreferenceOwnerLine,
*Surface.XrControllerBindingLine,
*Surface.NextPacketLine
);
return Surface;
@ -1605,6 +1776,22 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa
);
Surface.bRepositoryBackedSelectorRecallReady = SelectorRecallFacts.bReady;
Surface.RecalledHigherDimensionalSelectorCount = SelectorRecallFacts.SelectorCount;
const HyperTwistTrainingPanelWidgetInternal::FXrRuntimeOwnershipFacts
XrRuntimeOwnershipFacts =
HyperTwistTrainingPanelWidgetInternal::BuildXrRuntimeOwnershipFacts();
Surface.XrRuntimeOwnerId = XrRuntimeOwnershipFacts.RuntimeSurface.RuntimeOwnerId;
Surface.XrPreferenceProfileId = XrRuntimeOwnershipFacts.PreferenceProfile.ProfileId;
Surface.XrControllerBindingProfileId =
XrRuntimeOwnershipFacts.ControllerBindingProfile.ProfileId;
Surface.XrPreferenceFieldCount = XrRuntimeOwnershipFacts.PreferenceFieldCount;
Surface.XrControllerBindingAxisCount =
XrRuntimeOwnershipFacts.ControllerBindingAxisCount;
Surface.XrSupportedControllerFamilyCount =
XrRuntimeOwnershipFacts.SupportedControllerFamilyCount;
Surface.bXrPreferenceProfileReady =
XrRuntimeOwnershipFacts.bPreferenceProfileReady;
Surface.bXrControllerBindingProfileReady =
XrRuntimeOwnershipFacts.bControllerBindingProfileReady;
Surface.bHigherDimensionalSelectorSettingsReady =
Surface.bMagic120CellViewSettingsReady
@ -1635,7 +1822,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa
&& Surface.MagicCube5DSelectorCount > 0;
Surface.SummaryLine =
TEXT("Camera, immersive, and dedicated-family projection, selector, continuity, and persistence settings are already first-party owned, while XR/controller rebinding still remains unfinished.");
TEXT("Camera, immersive, dedicated-family, and bounded XR preference settings are already first-party owned, while packaged controller proof and broader rebinding polish still remain unfinished.");
Surface.CameraSettingsLine =
HyperTwistTrainingControlSurfaceFormatting::BuildControlSettingsCameraSettingsLine(
Surface.bCameraSettingsReady,
@ -1711,6 +1898,10 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa
HyperTwistTrainingPanelWidgetInternal::BuildSelectorRecallLine(
SelectorRecallFacts
);
Surface.XrSettingsOwnershipLine =
HyperTwistTrainingPanelWidgetInternal::BuildXrPreferenceOwnerLine(
XrRuntimeOwnershipFacts
);
Surface.XrGapLine =
HyperTwistTrainingPanelWidgetInternal::BuildControlSettingsXrBoundaryLine(
Surface.bOpenXrProjectPluginEnabled
@ -1730,13 +1921,14 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlSettingsOwnershipInspectSurfa
Surface.bRepositoryBackedSelectorRecallReady
);
Surface.DetailLine = FString::Printf(
TEXT("%s | %s | %s | %s | %s | %s | %s"),
TEXT("%s | %s | %s | %s | %s | %s | %s | %s"),
*Surface.CameraSettingsLine,
*Surface.ImmersiveSettingsLine,
*Surface.Magic120CellSettingsLine,
*Surface.MagicCube5DSettingsLine,
*Surface.SelectorSettingsLine,
*Surface.SelectorRecallLine,
*Surface.XrSettingsOwnershipLine,
*Surface.XrGapLine
);
@ -1891,6 +2083,22 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlProfileRosterInspectSurface()
);
Surface.bRepositoryBackedSelectorRecallReady = SelectorRecallFacts.bReady;
Surface.RecalledHigherDimensionalSelectorCount = SelectorRecallFacts.SelectorCount;
const HyperTwistTrainingPanelWidgetInternal::FXrRuntimeOwnershipFacts
XrRuntimeOwnershipFacts =
HyperTwistTrainingPanelWidgetInternal::BuildXrRuntimeOwnershipFacts();
Surface.XrRuntimeOwnerId = XrRuntimeOwnershipFacts.RuntimeSurface.RuntimeOwnerId;
Surface.XrPreferenceProfileId = XrRuntimeOwnershipFacts.PreferenceProfile.ProfileId;
Surface.XrControllerBindingProfileId =
XrRuntimeOwnershipFacts.ControllerBindingProfile.ProfileId;
Surface.XrPreferenceFieldCount = XrRuntimeOwnershipFacts.PreferenceFieldCount;
Surface.XrControllerBindingAxisCount =
XrRuntimeOwnershipFacts.ControllerBindingAxisCount;
Surface.XrSupportedControllerFamilyCount =
XrRuntimeOwnershipFacts.SupportedControllerFamilyCount;
Surface.bXrPreferenceProfileReady =
XrRuntimeOwnershipFacts.bPreferenceProfileReady;
Surface.bXrControllerBindingProfileReady =
XrRuntimeOwnershipFacts.bControllerBindingProfileReady;
Surface.bFinishedControllerRebindingReady = false;
Surface.bOpenXrProjectPluginEnabled =
@ -1949,7 +2157,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlProfileRosterInspectSurface()
? Surface.ActiveHigherDimensionalInteractiveSceneSurfaceId
: TEXT("none");
Surface.SummaryLine =
TEXT("The shipped selectable roster currently covers the classic keyboard mapping, scenic immersive presets, bounded camera or immersive continuity, and dedicated 120-cell/5D view-selector families, while controller rebinding still remains unfinished.");
TEXT("The shipped selectable roster currently covers the classic keyboard mapping, scenic immersive presets, bounded XR controller profiles, bounded camera or immersive continuity, and dedicated 120-cell/5D view-selector families, while controller rebinding still remains unfinished.");
Surface.ClassicKeyboardProfileLine = FString::Printf(
TEXT("Classic keyboard profile: %s | bindings %d | ctrl suppressed %s | alt suppressed %s | meta suppressed %s | repeat suppressed %s."),
*ClassicKeyboardProfileName,
@ -2015,6 +2223,10 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlProfileRosterInspectSurface()
Surface.ImmersiveSessionRecallScopeCount,
Surface.ImmersiveSessionRecallPreferenceFieldTagCount
);
Surface.XrControllerProfileLine =
HyperTwistTrainingPanelWidgetInternal::BuildXrControllerBindingLine(
XrRuntimeOwnershipFacts
);
Surface.PreferencesGapLine =
HyperTwistTrainingPanelWidgetInternal::BuildControlProfileRosterXrBoundaryLine(
Surface.bOpenXrProjectPluginEnabled
@ -2031,7 +2243,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlProfileRosterInspectSurface()
Surface.bImmersiveSessionRecallReady
);
Surface.DetailLine = FString::Printf(
TEXT("%s | %s | %s | %s | %s | %s | %s | %s | %s"),
TEXT("%s | %s | %s | %s | %s | %s | %s | %s | %s | %s"),
*Surface.ClassicKeyboardProfileLine,
*Surface.ClassicKeyboardMoveRosterLine,
*Surface.ImmersivePresenceProfileLine,
@ -2040,6 +2252,7 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlProfileRosterInspectSurface()
*Surface.ActiveHigherDimensionalProfileLine,
*Surface.SelectorRecallLine,
*Surface.PreferencesContinuityLine,
*Surface.XrControllerProfileLine,
*Surface.PreferencesGapLine
);

View file

@ -0,0 +1,147 @@
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
namespace HyperTwistXrRuntimeLibraryInternal
{
FHyperTwistTrainingSourceAttribution MakeFirstPartyAttribution(
const FString& TrackId,
const FString& Notes
)
{
FHyperTwistTrainingSourceAttribution Attribution;
Attribution.SourceMode = EHyperTwistTrainingSourceMode::FirstParty;
Attribution.SourceTrackId = TrackId;
Attribution.SourceNotes = Notes;
return Attribution;
}
}
FHyperTwistXrPreferenceProfile UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile()
{
using namespace HyperTwistXrRuntimeLibraryInternal;
FHyperTwistXrPreferenceProfile Profile;
Profile.ProfileId = TEXT("xr-openxr-training-preferences/v1");
Profile.Title = TEXT("OpenXR desktop-training preferences");
Profile.Summary = TEXT("First-party desktop-hosted OpenXR preference bundle for locomotion, comfort, posture, world scale, and dominant-hand defaults beneath the still-bounded XR runtime lane.");
Profile.PreferenceFieldTags =
{
TEXT("movement speed"),
TEXT("vertical adjustment"),
TEXT("snap turn"),
TEXT("smooth turn fallback"),
TEXT("pointer length"),
TEXT("world scale"),
TEXT("dominant hand"),
TEXT("posture mode")
};
Profile.ComfortSurfaceTags =
{
TEXT("comfort vignette strength"),
TEXT("seated height offset"),
TEXT("snap-turn comfort"),
TEXT("world-scale recenter")
};
Profile.ExplicitExclusions =
{
TEXT("platform-global guardian ownership"),
TEXT("cross-title cloud remap storage"),
TEXT("generic social VR avatar shell")
};
Profile.SourceAttribution = MakeFirstPartyAttribution(
TEXT("first-party/xr-openxr-training-preferences"),
TEXT("Owns first-party XR training preferences for HyperTwist without promoting the remaining packaged-controller validation gate to finished product status.")
);
return Profile;
}
FHyperTwistXrControllerBindingProfile
UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile()
{
using namespace HyperTwistXrRuntimeLibraryInternal;
FHyperTwistXrControllerBindingProfile Profile;
Profile.ProfileId = TEXT("xr-openxr-motion-controllers/v1");
Profile.Title = TEXT("OpenXR motion-controller binding profile");
Profile.Summary = TEXT("First-party motion-controller axis profile for locomotion, turning, grip, and trigger ownership inside the bounded HyperTwist XR training lane.");
Profile.RequiredAxisNames =
{
TEXT("HyperTwistXr_MoveForward"),
TEXT("HyperTwistXr_MoveRight"),
TEXT("HyperTwistXr_Turn"),
TEXT("HyperTwistXr_LookPitch"),
TEXT("HyperTwistXr_LeftGrip"),
TEXT("HyperTwistXr_RightGrip"),
TEXT("HyperTwistXr_LeftTrigger"),
TEXT("HyperTwistXr_RightTrigger")
};
Profile.ControllerFamilyIds =
{
TEXT("controller/vive"),
TEXT("controller/mixed-reality"),
TEXT("controller/oculus-touch"),
TEXT("controller/valve-index")
};
Profile.InteractionSurfaceTags =
{
TEXT("camera-aligned locomotion vector"),
TEXT("snap-turn ownership"),
TEXT("trigger and grip value capture"),
TEXT("bounded desktop-hosted XR interaction")
};
Profile.ExplicitExclusions =
{
TEXT("platform-global controller remap UI"),
TEXT("vendor-specific hand-tracking ownership"),
TEXT("full room-scale locomotion sandbox")
};
Profile.SourceAttribution = MakeFirstPartyAttribution(
TEXT("first-party/xr-openxr-motion-controller-bindings"),
TEXT("Owns the HyperTwist OpenXR motion-controller binding substrate for the desktop-hosted simulator without claiming finished cross-device rebinding polish.")
);
return Profile;
}
FHyperTwistXrRuntimeOwnershipSurface
UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface()
{
using namespace HyperTwistXrRuntimeLibraryInternal;
const FHyperTwistXrPreferenceProfile PreferenceProfile = GetDefaultXrPreferenceProfile();
const FHyperTwistXrControllerBindingProfile BindingProfile =
GetDefaultXrControllerBindingProfile();
FHyperTwistXrRuntimeOwnershipSurface Surface;
Surface.RuntimeOwnerId = TEXT("xr/openxr-desktop-training-runtime-owner");
Surface.Title = TEXT("Desktop-hosted OpenXR training runtime owner");
Surface.Summary = TEXT("Bounded first-party OpenXR runtime owner for motion-controller anchors, camera-aligned locomotion, comfort-aware turning, and preference-driven desktop XR training posture beneath the still-open packaged-controller proof gate.");
Surface.PreferenceProfileId = PreferenceProfile.ProfileId;
Surface.ControllerBindingProfileId = BindingProfile.ProfileId;
Surface.WorldAnchorContractId = TEXT("immersive-training-world-anchor-contract");
Surface.LeftMotionSourceId = TEXT("Left");
Surface.RightMotionSourceId = TEXT("Right");
Surface.RequiredPackageIds =
{
TEXT("package/hypertwist-immersive-environment-shell"),
TEXT("package/hypertwist-immersive-world-anchors"),
TEXT("package/hypertwist-immersive-focus-presence-controls")
};
Surface.RequiredSurfaceTags =
{
TEXT("motion-controller pose anchors"),
TEXT("camera-aligned locomotion vector"),
TEXT("training-world recenter"),
TEXT("bounded trigger and grip interaction")
};
Surface.SupportedControllerFamilies = BindingProfile.ControllerFamilyIds;
Surface.ExplicitExclusions =
{
TEXT("platform-global guardian ownership"),
TEXT("generic social VR hub ownership"),
TEXT("launch-tier packaged controller certification ownership")
};
Surface.SourceAttribution = MakeFirstPartyAttribution(
TEXT("first-party/xr-openxr-runtime-owner"),
TEXT("Owns the bounded OpenXR runtime-owner seam for HyperTwist desktop XR training while keeping finished controller validation and cross-device launch certification outside the current packet.")
);
return Surface;
}

View file

@ -0,0 +1,486 @@
#include "HyperTwistXR/HyperTwistXrTrainingPawn.h"
#include "Camera/CameraComponent.h"
#include "Components/SceneComponent.h"
#include "GameFramework/PlayerController.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "InputCoreTypes.h"
#include "MotionControllerComponent.h"
namespace HyperTwistXrTrainingPawnInternal
{
constexpr float SeatedHeightOffset = -42.0f;
constexpr float HybridHeightOffset = -20.0f;
constexpr float DesktopFallbackPitchMin = -65.0f;
constexpr float DesktopFallbackPitchMax = 65.0f;
FVector BuildPlanarDirection(const FVector& RawDirection)
{
FVector Result = FVector::VectorPlaneProject(RawDirection, FVector::UpVector);
Result.Normalize();
return Result;
}
float ApplyDeadZone(const float Value, const float DeadZone)
{
return FMath::Abs(Value) >= DeadZone ? Value : 0.0f;
}
const TCHAR* DescribeDominantHand(const EHyperTwistXrDominantHand DominantHand)
{
return DominantHand == EHyperTwistXrDominantHand::Left
? TEXT("left")
: TEXT("right");
}
const TCHAR* DescribePostureMode(const EHyperTwistXrPostureMode PostureMode)
{
switch (PostureMode)
{
case EHyperTwistXrPostureMode::Seated:
return TEXT("seated");
case EHyperTwistXrPostureMode::Hybrid:
return TEXT("hybrid");
default:
return TEXT("standing");
}
}
}
AHyperTwistXrTrainingPawn::AHyperTwistXrTrainingPawn()
{
PrimaryActorTick.bCanEverTick = true;
AutoPossessPlayer = EAutoReceiveInput::Player0;
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
SetRootComponent(SceneRoot);
VROrigin = CreateDefaultSubobject<USceneComponent>(TEXT("VROrigin"));
VROrigin->SetupAttachment(SceneRoot);
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
CameraComponent->SetupAttachment(VROrigin);
LeftController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("LeftController"));
LeftController->SetupAttachment(VROrigin);
LeftController->SetTrackingMotionSource(TEXT("Left"));
RightController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("RightController"));
RightController->SetupAttachment(VROrigin);
RightController->SetTrackingMotionSource(TEXT("Right"));
LeftPointerAnchor = CreateDefaultSubobject<USceneComponent>(TEXT("LeftPointerAnchor"));
LeftPointerAnchor->SetupAttachment(LeftController);
RightPointerAnchor = CreateDefaultSubobject<USceneComponent>(TEXT("RightPointerAnchor"));
RightPointerAnchor->SetupAttachment(RightController);
ActivePreferenceProfile = UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
ActiveControllerBindingProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile();
}
void AHyperTwistXrTrainingPawn::BeginPlay()
{
Super::BeginPlay();
ApplyPreferenceProfile(ActivePreferenceProfile);
if (bAutoResetOrientationOnBeginPlay)
{
ResetXrOrigin();
}
}
void AHyperTwistXrTrainingPawn::Tick(const float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
RefreshHeadMountedDisplayState();
UpdateSmoothLocomotion(DeltaSeconds);
UpdateTurn(DeltaSeconds);
UpdateDesktopFallbackPitch(DeltaSeconds);
}
void AHyperTwistXrTrainingPawn::SetupPlayerInputComponent(
UInputComponent* PlayerInputComponent
)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (PlayerInputComponent == nullptr)
{
return;
}
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_MoveForward"),
this,
&AHyperTwistXrTrainingPawn::HandleMoveForward
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_MoveRight"),
this,
&AHyperTwistXrTrainingPawn::HandleMoveRight
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_Turn"),
this,
&AHyperTwistXrTrainingPawn::HandleTurnAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_LookPitch"),
this,
&AHyperTwistXrTrainingPawn::HandleLookPitchAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_LeftGrip"),
this,
&AHyperTwistXrTrainingPawn::HandleLeftGripAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_RightGrip"),
this,
&AHyperTwistXrTrainingPawn::HandleRightGripAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_LeftTrigger"),
this,
&AHyperTwistXrTrainingPawn::HandleLeftTriggerAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_RightTrigger"),
this,
&AHyperTwistXrTrainingPawn::HandleRightTriggerAxis
);
PlayerInputComponent->BindAction(
TEXT("HyperTwistXr_Recenter"),
IE_Pressed,
this,
&AHyperTwistXrTrainingPawn::ResetXrOrigin
);
PlayerInputComponent->BindAction(
TEXT("HyperTwistXr_ToggleSnapTurn"),
IE_Pressed,
this,
&AHyperTwistXrTrainingPawn::HandleToggleSnapTurn
);
PlayerInputComponent->BindAction(
TEXT("HyperTwistXr_TogglePostureMode"),
IE_Pressed,
this,
&AHyperTwistXrTrainingPawn::HandleTogglePostureMode
);
}
void AHyperTwistXrTrainingPawn::ApplyPreferenceProfile(
const FHyperTwistXrPreferenceProfile& PreferenceProfile
)
{
if (!PreferenceProfile.IsStructurallyValid())
{
return;
}
ActivePreferenceProfile = PreferenceProfile;
ApplyPostureOffset();
ApplyPointerAnchors();
if (GetWorld() != nullptr)
{
UHeadMountedDisplayFunctionLibrary::SetWorldToMetersScale(
this,
ActivePreferenceProfile.WorldToMetersScale
);
}
}
void AHyperTwistXrTrainingPawn::SetDominantHand(
const EHyperTwistXrDominantHand DominantHand
)
{
ActivePreferenceProfile.DominantHand = DominantHand;
ApplyPointerAnchors();
}
void AHyperTwistXrTrainingPawn::SetPostureMode(
const EHyperTwistXrPostureMode PostureMode
)
{
ActivePreferenceProfile.PostureMode = PostureMode;
ApplyPostureOffset();
}
void AHyperTwistXrTrainingPawn::SetSnapTurnEnabled(const bool bEnabled)
{
ActivePreferenceProfile.bSnapTurnEnabled = bEnabled;
ActivePreferenceProfile.bSmoothTurnFallbackEnabled = !bEnabled;
bSnapTurnLatchOpen = true;
}
void AHyperTwistXrTrainingPawn::ResetXrOrigin()
{
DesktopFallbackPitchDegrees = 0.0f;
ApplyDesktopFallbackRotation();
ApplyPostureOffset();
UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}
FHyperTwistXrRuntimeOwnershipSurface
AHyperTwistXrTrainingPawn::GetBoundedRuntimeOwnershipSurface() const
{
return UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface();
}
FString AHyperTwistXrTrainingPawn::BuildRuntimeStatusLine() const
{
const FHyperTwistXrRuntimeOwnershipSurface RuntimeSurface =
GetBoundedRuntimeOwnershipSurface();
return FString::Printf(
TEXT("OpenXR runtime owner %s | preferences %s | bindings %s | HMD connected %s | HMD enabled %s | posture %s | dominant hand %s | snap turn %s | smooth locomotion %s | grip %.2f/%.2f | trigger %.2f/%.2f"),
*RuntimeSurface.RuntimeOwnerId,
*ActivePreferenceProfile.ProfileId,
*ActiveControllerBindingProfile.ProfileId,
bHeadMountedDisplayConnected ? TEXT("yes") : TEXT("no"),
bHeadMountedDisplayEnabled ? TEXT("yes") : TEXT("no"),
HyperTwistXrTrainingPawnInternal::DescribePostureMode(
ActivePreferenceProfile.PostureMode),
HyperTwistXrTrainingPawnInternal::DescribeDominantHand(
ActivePreferenceProfile.DominantHand),
ActivePreferenceProfile.bSnapTurnEnabled ? TEXT("on") : TEXT("off"),
ActivePreferenceProfile.bSmoothLocomotionEnabled ? TEXT("on") : TEXT("off"),
LeftGripValue,
RightGripValue,
LeftTriggerValue,
RightTriggerValue
);
}
void AHyperTwistXrTrainingPawn::ApplyPostureOffset()
{
if (VROrigin == nullptr)
{
return;
}
float HeightOffset = 0.0f;
switch (ActivePreferenceProfile.PostureMode)
{
case EHyperTwistXrPostureMode::Seated:
HeightOffset = HyperTwistXrTrainingPawnInternal::SeatedHeightOffset;
break;
case EHyperTwistXrPostureMode::Hybrid:
HeightOffset = HyperTwistXrTrainingPawnInternal::HybridHeightOffset;
break;
default:
break;
}
VROrigin->SetRelativeLocation(FVector(0.0f, 0.0f, HeightOffset));
}
void AHyperTwistXrTrainingPawn::ApplyPointerAnchors()
{
if (LeftPointerAnchor != nullptr)
{
LeftPointerAnchor->SetRelativeLocation(
FVector(ActivePreferenceProfile.PointerLengthUnits, 0.0f, 0.0f)
);
}
if (RightPointerAnchor != nullptr)
{
RightPointerAnchor->SetRelativeLocation(
FVector(ActivePreferenceProfile.PointerLengthUnits, 0.0f, 0.0f)
);
}
}
void AHyperTwistXrTrainingPawn::ApplyDesktopFallbackRotation()
{
if (VROrigin == nullptr || bHeadMountedDisplayEnabled)
{
return;
}
const FRotator CurrentRotation = VROrigin->GetRelativeRotation();
VROrigin->SetRelativeRotation(
FRotator(
DesktopFallbackPitchDegrees,
CurrentRotation.Yaw,
CurrentRotation.Roll
)
);
}
void AHyperTwistXrTrainingPawn::RefreshHeadMountedDisplayState()
{
bHeadMountedDisplayConnected =
UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayConnected();
bHeadMountedDisplayEnabled =
UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled();
}
void AHyperTwistXrTrainingPawn::UpdateSmoothLocomotion(const float DeltaSeconds)
{
if (!ActivePreferenceProfile.bSmoothLocomotionEnabled)
{
return;
}
const float MoveForward = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
PendingMoveForward,
ActivePreferenceProfile.ControllerDeadZone
);
const float MoveRight = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
PendingMoveRight,
ActivePreferenceProfile.ControllerDeadZone
);
if (FMath::IsNearlyZero(MoveForward) && FMath::IsNearlyZero(MoveRight))
{
return;
}
const FVector ForwardDirection = HyperTwistXrTrainingPawnInternal::BuildPlanarDirection(
CameraComponent != nullptr ? CameraComponent->GetForwardVector() : GetActorForwardVector()
);
const FVector RightDirection = HyperTwistXrTrainingPawnInternal::BuildPlanarDirection(
CameraComponent != nullptr ? CameraComponent->GetRightVector() : GetActorRightVector()
);
const FVector Delta =
(ForwardDirection * MoveForward + RightDirection * MoveRight)
* ActivePreferenceProfile.MoveSpeedUnitsPerSecond
* DeltaSeconds;
AddActorWorldOffset(Delta, true);
}
void AHyperTwistXrTrainingPawn::UpdateTurn(const float DeltaSeconds)
{
const float TurnValue = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
PendingTurnAxis,
ActivePreferenceProfile.ControllerDeadZone
);
if (FMath::IsNearlyZero(TurnValue))
{
bSnapTurnLatchOpen = true;
return;
}
if (ActivePreferenceProfile.bSnapTurnEnabled)
{
if (bSnapTurnLatchOpen
&& FMath::Abs(TurnValue) >= ActivePreferenceProfile.SnapTurnAxisThreshold)
{
const float Direction = TurnValue > 0.0f ? 1.0f : -1.0f;
AddActorWorldRotation(
FRotator(0.0f, Direction * ActivePreferenceProfile.SnapTurnDegrees, 0.0f)
);
bSnapTurnLatchOpen = false;
}
if (FMath::Abs(TurnValue) <= ActivePreferenceProfile.SnapTurnResetThreshold)
{
bSnapTurnLatchOpen = true;
}
return;
}
if (ActivePreferenceProfile.bSmoothTurnFallbackEnabled)
{
AddActorWorldRotation(
FRotator(
0.0f,
TurnValue * ActivePreferenceProfile.SmoothTurnDegreesPerSecond * DeltaSeconds,
0.0f
)
);
}
}
void AHyperTwistXrTrainingPawn::UpdateDesktopFallbackPitch(const float DeltaSeconds)
{
if (bHeadMountedDisplayEnabled || VROrigin == nullptr)
{
return;
}
const float PitchValue = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
PendingLookPitchAxis,
ActivePreferenceProfile.ControllerDeadZone
);
if (FMath::IsNearlyZero(PitchValue))
{
return;
}
DesktopFallbackPitchDegrees = FMath::Clamp(
DesktopFallbackPitchDegrees
+ PitchValue * ActivePreferenceProfile.SmoothTurnDegreesPerSecond * DeltaSeconds,
HyperTwistXrTrainingPawnInternal::DesktopFallbackPitchMin,
HyperTwistXrTrainingPawnInternal::DesktopFallbackPitchMax
);
ApplyDesktopFallbackRotation();
}
void AHyperTwistXrTrainingPawn::HandleMoveForward(const float Value)
{
PendingMoveForward = Value;
}
void AHyperTwistXrTrainingPawn::HandleMoveRight(const float Value)
{
PendingMoveRight = Value;
}
void AHyperTwistXrTrainingPawn::HandleTurnAxis(const float Value)
{
PendingTurnAxis = Value;
}
void AHyperTwistXrTrainingPawn::HandleLookPitchAxis(const float Value)
{
PendingLookPitchAxis = Value;
}
void AHyperTwistXrTrainingPawn::HandleLeftGripAxis(const float Value)
{
LeftGripValue = Value;
}
void AHyperTwistXrTrainingPawn::HandleRightGripAxis(const float Value)
{
RightGripValue = Value;
}
void AHyperTwistXrTrainingPawn::HandleLeftTriggerAxis(const float Value)
{
LeftTriggerValue = Value;
}
void AHyperTwistXrTrainingPawn::HandleRightTriggerAxis(const float Value)
{
RightTriggerValue = Value;
}
void AHyperTwistXrTrainingPawn::HandleToggleSnapTurn()
{
SetSnapTurnEnabled(!ActivePreferenceProfile.bSnapTurnEnabled);
if (!ActivePreferenceProfile.bSnapTurnEnabled)
{
ActivePreferenceProfile.bSmoothTurnFallbackEnabled = true;
}
}
void AHyperTwistXrTrainingPawn::HandleTogglePostureMode()
{
switch (ActivePreferenceProfile.PostureMode)
{
case EHyperTwistXrPostureMode::Standing:
SetPostureMode(EHyperTwistXrPostureMode::Seated);
break;
case EHyperTwistXrPostureMode::Seated:
SetPostureMode(EHyperTwistXrPostureMode::Hybrid);
break;
default:
SetPostureMode(EHyperTwistXrPostureMode::Standing);
break;
}
}

View file

@ -10015,6 +10015,15 @@ struct FHyperTwistTrainingControlInputReadinessInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferencesStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrRuntimeOwnerLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrPreferenceOwnerLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrControllerBindingLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextPacketLine;
@ -10024,9 +10033,27 @@ struct FHyperTwistTrainingControlInputReadinessInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString KeyboardProfileName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrRuntimeOwnerId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrPreferenceProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrControllerBindingProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 MotionControllerFamilyCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrPreferenceFieldCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrControllerBindingAxisCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrSupportedControllerFamilyCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bOpenXrProjectPluginEnabled = false;
@ -10057,6 +10084,15 @@ struct FHyperTwistTrainingControlInputReadinessInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bImmersivePresenceContractReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBoundedXrRuntimeOwnerReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrPreferenceProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrControllerBindingProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bFinishedXrRuntimeReady = false;
@ -10128,6 +10164,9 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SelectorRecallLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrSettingsOwnershipLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrGapLine;
@ -10146,6 +10185,15 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ImmersiveSessionRecallBoundaryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrRuntimeOwnerId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrPreferenceProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrControllerBindingProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ActiveHigherDimensionalViewContextId;
@ -10242,6 +10290,15 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ImmersiveSessionRecallResetSurfaceTagCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrPreferenceFieldCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrControllerBindingAxisCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrSupportedControllerFamilyCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCameraSettingsReady = false;
@ -10275,6 +10332,12 @@ struct FHyperTwistTrainingControlSettingsOwnershipInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRepositoryBackedSelectorRecallReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrPreferenceProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrControllerBindingProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bFinishedControllerRebindingReady = false;
@ -10356,6 +10419,9 @@ struct FHyperTwistTrainingControlProfileRosterInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferencesContinuityLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrControllerProfileLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferencesGapLine;
@ -10374,6 +10440,15 @@ struct FHyperTwistTrainingControlProfileRosterInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ImmersiveSessionRecallBoundaryId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrRuntimeOwnerId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrPreferenceProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString XrControllerBindingProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Magic120CellRuntimeProfileContractId;
@ -10425,6 +10500,15 @@ struct FHyperTwistTrainingControlProfileRosterInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ImmersiveSessionRecallPreferenceFieldTagCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrPreferenceFieldCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrControllerBindingAxisCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 XrSupportedControllerFamilyCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClassicKeyboardProfileReady = false;
@ -10449,6 +10533,12 @@ struct FHyperTwistTrainingControlProfileRosterInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bRepositoryBackedSelectorRecallReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrPreferenceProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrControllerBindingProfileReady = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bFinishedControllerRebindingReady = false;

View file

@ -0,0 +1,234 @@
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
#include "HyperTwistXrRuntimeLibrary.generated.h"
UENUM(BlueprintType)
enum class EHyperTwistXrDominantHand : uint8
{
Right UMETA(DisplayName = "Right"),
Left UMETA(DisplayName = "Left")
};
UENUM(BlueprintType)
enum class EHyperTwistXrPostureMode : uint8
{
Standing UMETA(DisplayName = "Standing"),
Seated UMETA(DisplayName = "Seated"),
Hybrid UMETA(DisplayName = "Hybrid")
};
USTRUCT(BlueprintType)
struct FHyperTwistXrPreferenceProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Title;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Summary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistXrDominantHand DominantHand = EHyperTwistXrDominantHand::Right;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistXrPostureMode PostureMode = EHyperTwistXrPostureMode::Standing;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float MoveSpeedUnitsPerSecond = 220.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float VerticalAdjustmentUnitsPerSecond = 120.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SnapTurnDegrees = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SnapTurnAxisThreshold = 0.65f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SnapTurnResetThreshold = 0.25f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float SmoothTurnDegreesPerSecond = 90.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float PointerLengthUnits = 180.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float WorldToMetersScale = 100.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float ComfortVignetteStrength = 0.35f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float ControllerDeadZone = 0.15f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSmoothLocomotionEnabled = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSnapTurnEnabled = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSmoothTurnFallbackEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAllowVerticalAdjustment = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMirrorSpectatorView = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bHapticsEnabled = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> PreferenceFieldTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> ComfortSurfaceTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> ExplicitExclusions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingSourceAttribution SourceAttribution;
bool IsStructurallyValid() const
{
return !ProfileId.IsEmpty()
&& !Title.IsEmpty()
&& SourceAttribution.IsStructurallyValid();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistXrControllerBindingProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Title;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Summary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> RequiredAxisNames;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> ControllerFamilyIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> InteractionSurfaceTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> ExplicitExclusions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingSourceAttribution SourceAttribution;
bool IsStructurallyValid() const
{
return !ProfileId.IsEmpty()
&& !Title.IsEmpty()
&& RequiredAxisNames.Num() > 0
&& SourceAttribution.IsStructurallyValid();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistXrRuntimeOwnershipSurface
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RuntimeOwnerId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Title;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Summary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PreferenceProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ControllerBindingProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString WorldAnchorContractId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LeftMotionSourceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RightMotionSourceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> RequiredPackageIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> RequiredSurfaceTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> SupportedControllerFamilies;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> ExplicitExclusions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingSourceAttribution SourceAttribution;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bOpenXrPluginRequired = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMotionControllerComponentsRequired = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bDesktopFallbackAllowed = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bComfortProfileReady = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bControllerBindingsReady = true;
bool IsStructurallyValid() const
{
return !RuntimeOwnerId.IsEmpty()
&& !Title.IsEmpty()
&& !PreferenceProfileId.IsEmpty()
&& !ControllerBindingProfileId.IsEmpty()
&& !LeftMotionSourceId.IsEmpty()
&& !RightMotionSourceId.IsEmpty()
&& SourceAttribution.IsStructurallyValid();
}
};
UCLASS(BlueprintType)
class UNREALHYPERTWIST_API UHyperTwistXrRuntimeLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|XR")
static FHyperTwistXrPreferenceProfile GetDefaultXrPreferenceProfile();
UFUNCTION(BlueprintPure, Category = "HyperTwist|XR")
static FHyperTwistXrControllerBindingProfile GetDefaultXrControllerBindingProfile();
UFUNCTION(BlueprintPure, Category = "HyperTwist|XR")
static FHyperTwistXrRuntimeOwnershipSurface BuildBundledXrRuntimeOwnershipSurface();
};

View file

@ -0,0 +1,119 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "HyperTwistXrTrainingPawn.generated.h"
class UCameraComponent;
class UMotionControllerComponent;
class USceneComponent;
UCLASS(BlueprintType, Blueprintable)
class UNREALHYPERTWIST_API AHyperTwistXrTrainingPawn : public APawn
{
GENERATED_BODY()
public:
AHyperTwistXrTrainingPawn();
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|XR|Preferences")
FHyperTwistXrPreferenceProfile ActivePreferenceProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|XR|Bindings")
FHyperTwistXrControllerBindingProfile ActiveControllerBindingProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|XR|Runtime")
bool bAutoResetOrientationOnBeginPlay = true;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<USceneComponent> SceneRoot = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<USceneComponent> VROrigin = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<UCameraComponent> CameraComponent = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<UMotionControllerComponent> LeftController = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<UMotionControllerComponent> RightController = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<USceneComponent> LeftPointerAnchor = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
TObjectPtr<USceneComponent> RightPointerAnchor = nullptr;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
bool bHeadMountedDisplayConnected = false;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
bool bHeadMountedDisplayEnabled = false;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
float LeftGripValue = 0.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
float RightGripValue = 0.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
float LeftTriggerValue = 0.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime")
float RightTriggerValue = 0.0f;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|XR|Preferences")
void ApplyPreferenceProfile(const FHyperTwistXrPreferenceProfile& PreferenceProfile);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|XR|Preferences")
void SetDominantHand(EHyperTwistXrDominantHand DominantHand);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|XR|Preferences")
void SetPostureMode(EHyperTwistXrPostureMode PostureMode);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|XR|Preferences")
void SetSnapTurnEnabled(bool bEnabled);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|XR|Preferences")
void ResetXrOrigin();
UFUNCTION(BlueprintPure, Category = "HyperTwist|XR|Runtime")
FHyperTwistXrRuntimeOwnershipSurface GetBoundedRuntimeOwnershipSurface() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|XR|Runtime")
FString BuildRuntimeStatusLine() const;
protected:
void ApplyPostureOffset();
void ApplyPointerAnchors();
void ApplyDesktopFallbackRotation();
void RefreshHeadMountedDisplayState();
void UpdateSmoothLocomotion(float DeltaSeconds);
void UpdateTurn(float DeltaSeconds);
void UpdateDesktopFallbackPitch(float DeltaSeconds);
void HandleMoveForward(float Value);
void HandleMoveRight(float Value);
void HandleTurnAxis(float Value);
void HandleLookPitchAxis(float Value);
void HandleLeftGripAxis(float Value);
void HandleRightGripAxis(float Value);
void HandleLeftTriggerAxis(float Value);
void HandleRightTriggerAxis(float Value);
void HandleToggleSnapTurn();
void HandleTogglePostureMode();
float PendingMoveForward = 0.0f;
float PendingMoveRight = 0.0f;
float PendingTurnAxis = 0.0f;
float PendingLookPitchAxis = 0.0f;
float DesktopFallbackPitchDegrees = 0.0f;
bool bSnapTurnLatchOpen = true;
};

View file

@ -1147,7 +1147,7 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
&& Surface.bMotionControllerGroundworkPresent
&& Surface.bImmersivePresenceContractReady
);
TestFalse(
TestTrue(
TEXT("The control/input readiness surface must keep the current project OpenXR plugin posture explicit."),
Surface.bOpenXrProjectPluginEnabled
);
@ -1155,6 +1155,18 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
TEXT("The control/input readiness surface must keep unfinished XR/runtime and preferences truth explicit."),
Surface.bFinishedXrRuntimeReady || Surface.bFinishedPreferencesReady
);
TestTrue(
TEXT("The control/input readiness surface must expose the bounded XR runtime owner, preference profile, and controller binding profile."),
Surface.bBoundedXrRuntimeOwnerReady
&& Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestTrue(
TEXT("The control/input readiness surface must now expose bounded non-XR preferences continuity for camera export and immersive session recall."),
Surface.PreferencesStatusLine.Contains(TEXT("artifact/camera-export-json"))
@ -1177,7 +1189,9 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The control/input readiness surface must say plainly that finished XR runtime is not yet shipped."),
Surface.XrStatusLine.Contains(TEXT("not yet shipped"))
&& Surface.XrStatusLine.Contains(TEXT("OpenXR project plugin no"))
&& Surface.XrStatusLine.Contains(TEXT("OpenXR project plugin yes"))
&& Surface.XrStatusLine.Contains(TEXT("xr/openxr-desktop-training-runtime-owner"))
&& Surface.XrStatusLine.Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
TestTrue(
TEXT("The control/input readiness surface must retain the fixed No-Go host decision and the later settings/rebinding follow-through."),
@ -1187,6 +1201,8 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The control/input readiness detail line must surface the fixed No-Go host decision operators actually need to see."),
Surface.DetailLine.Contains(TEXT("No-Go"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-training-preferences/v1"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
return true;
}
@ -1240,17 +1256,30 @@ bool FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The coach dashboard control/input readiness surface must retain the unfinished XR/runtime truth in its detail line."),
Surface.DetailLine.Contains(TEXT("not yet shipped"))
&& Surface.DetailLine.Contains(TEXT("OpenXR project plugin no"))
&& Surface.DetailLine.Contains(TEXT("OpenXR project plugin yes"))
&& Surface.DetailLine.Contains(TEXT("xr/openxr-desktop-training-runtime-owner"))
);
TestTrue(
TEXT("The coach dashboard control/input readiness surface must retain the bounded non-XR preferences continuity ids in its detail line."),
Surface.DetailLine.Contains(TEXT("artifact/camera-export-json"))
&& Surface.DetailLine.Contains(TEXT("immersive-training-session-recall-boundary"))
);
TestFalse(
TEXT("The coach dashboard control/input readiness surface must preserve the explicit disabled OpenXR project plugin truth."),
TestTrue(
TEXT("The coach dashboard control/input readiness surface must preserve the explicit enabled OpenXR project plugin truth."),
Surface.bOpenXrProjectPluginEnabled
);
TestTrue(
TEXT("The coach dashboard control/input readiness surface must expose the bounded XR runtime owner, preference profile, and controller binding profile."),
Surface.bBoundedXrRuntimeOwnerReady
&& Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestEqual(
TEXT("The coach dashboard control/input readiness surface must preserve the structured XR host decision id."),
Surface.XrHostDecisionId,
@ -1267,6 +1296,7 @@ bool FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest::RunTest(
TEXT("The coach dashboard control/input readiness surface must keep the fixed No-Go host decision visible in its rendered detail line."),
Surface.DetailLine.Contains(TEXT("No-Go"))
&& Surface.DetailLine.Contains(TEXT("settings/rebinding"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
return true;
}
@ -1311,8 +1341,8 @@ bool FHyperTwistTrainingPanelControlSettingsOwnershipInspectSurfaceTest::RunTest
TEXT("The control settings ownership surface must keep unfinished XR/controller rebinding truth explicit."),
Surface.bFinishedControllerRebindingReady
);
TestFalse(
TEXT("The control settings ownership surface must keep the current disabled OpenXR project plugin posture explicit."),
TestTrue(
TEXT("The control settings ownership surface must keep the current enabled OpenXR project plugin posture explicit."),
Surface.bOpenXrProjectPluginEnabled
);
TestEqual(
@ -1352,6 +1382,17 @@ bool FHyperTwistTrainingPanelControlSettingsOwnershipInspectSurfaceTest::RunTest
&& Surface.ImmersiveSessionRecallPreferenceFieldTagCount == 5
&& Surface.ImmersiveSessionRecallResetSurfaceTagCount == 3
);
TestTrue(
TEXT("The control settings ownership surface must expose the bounded XR preference and controller-profile ownership packet."),
Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestEqual(
TEXT("The control settings ownership surface must keep the default active higher-dimensional view context explicit when no live session is attached."),
Surface.ActiveHigherDimensionalViewContextId,
@ -1454,7 +1495,8 @@ bool FHyperTwistTrainingPanelControlSettingsOwnershipInspectSurfaceTest::RunTest
TestTrue(
TEXT("The control settings ownership surface must say plainly that XR/controller rebinding is not yet shipped."),
Surface.DetailLine.Contains(TEXT("not yet shipped"))
&& Surface.DetailLine.Contains(TEXT("project OpenXR plugin not enabled"))
&& Surface.DetailLine.Contains(TEXT("project OpenXR plugin enabled"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-training-preferences/v1"))
);
TestTrue(
TEXT("The control settings ownership surface must keep the fixed desktop-hosted No-Go posture visible."),
@ -1573,8 +1615,9 @@ bool FHyperTwistCoachDashboardControlSettingsOwnershipInspectSurfaceTest::RunTes
&& Surface.DetailLine.Contains(TEXT("phase6c/magiccube5d/family-owned-scene-state"))
);
TestTrue(
TEXT("The coach dashboard control settings ownership surface must keep the rendered disabled OpenXR project plugin truth explicit."),
Surface.DetailLine.Contains(TEXT("project OpenXR plugin not enabled"))
TEXT("The coach dashboard control settings ownership surface must keep the rendered enabled OpenXR project plugin truth explicit."),
Surface.DetailLine.Contains(TEXT("project OpenXR plugin enabled"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-training-preferences/v1"))
);
TestEqual(
TEXT("The coach dashboard control settings ownership surface must preserve the structured XR host decision id."),
@ -1609,10 +1652,21 @@ bool FHyperTwistCoachDashboardControlSettingsOwnershipInspectSurfaceTest::RunTes
Surface.MagicCube5DFocusSurfaceTagCount,
5
);
TestFalse(
TEXT("The coach dashboard control settings ownership surface must preserve the explicit disabled OpenXR project plugin posture."),
TestTrue(
TEXT("The coach dashboard control settings ownership surface must preserve the explicit enabled OpenXR project plugin posture."),
Surface.bOpenXrProjectPluginEnabled
);
TestTrue(
TEXT("The coach dashboard control settings ownership surface must expose the bounded XR preference and controller binding packet."),
Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestTrue(
TEXT("The coach dashboard control settings ownership surface must preserve the structured No-Go decision and reopen requirements."),
Surface.bNativeOpenXrControllerWideningNoGo
@ -1700,10 +1754,21 @@ bool FHyperTwistTrainingPanelControlProfileRosterInspectSurfaceTest::RunTest(
TEXT("The control/profile roster surface must keep unfinished controller rebinding truth explicit."),
Surface.bFinishedControllerRebindingReady
);
TestFalse(
TEXT("The control/profile roster surface must keep the current disabled OpenXR project plugin posture explicit."),
TestTrue(
TEXT("The control/profile roster surface must keep the current enabled OpenXR project plugin posture explicit."),
Surface.bOpenXrProjectPluginEnabled
);
TestTrue(
TEXT("The control/profile roster surface must expose the bounded XR controller profile packet."),
Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestEqual(
TEXT("The control/profile roster surface must expose a stable structured XR host decision id."),
Surface.XrHostDecisionId,
@ -1719,7 +1784,8 @@ bool FHyperTwistTrainingPanelControlProfileRosterInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The control/profile roster surface must say plainly that controller rebinding is not yet shipped."),
Surface.DetailLine.Contains(TEXT("not yet shipped"))
&& Surface.DetailLine.Contains(TEXT("project OpenXR plugin is not enabled"))
&& Surface.DetailLine.Contains(TEXT("project OpenXR plugin is enabled"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
TestTrue(
TEXT("The control/profile roster surface must keep the fixed No-Go host decision visible alongside the unfinished controller rebinding truth."),
@ -1822,10 +1888,21 @@ bool FHyperTwistCoachDashboardControlProfileRosterInspectSurfaceTest::RunTest(
Surface.XrHostDecisionId,
TEXT("desktop-hosted-openxr-controller-widening-no-go")
);
TestFalse(
TEXT("The coach dashboard control/profile roster surface must preserve the explicit disabled OpenXR project plugin posture."),
TestTrue(
TEXT("The coach dashboard control/profile roster surface must preserve the explicit enabled OpenXR project plugin posture."),
Surface.bOpenXrProjectPluginEnabled
);
TestTrue(
TEXT("The coach dashboard control/profile roster surface must expose the bounded XR controller profile packet."),
Surface.bXrPreferenceProfileReady
&& Surface.bXrControllerBindingProfileReady
&& Surface.XrRuntimeOwnerId == TEXT("xr/openxr-desktop-training-runtime-owner")
&& Surface.XrPreferenceProfileId == TEXT("xr-openxr-training-preferences/v1")
&& Surface.XrControllerBindingProfileId == TEXT("xr-openxr-motion-controllers/v1")
&& Surface.XrPreferenceFieldCount == 8
&& Surface.XrControllerBindingAxisCount == 8
&& Surface.XrSupportedControllerFamilyCount == 4
);
TestTrue(
TEXT("The coach dashboard control/profile roster surface must preserve the structured No-Go decision and reopen requirements."),
Surface.bNativeOpenXrControllerWideningNoGo
@ -1834,8 +1911,9 @@ bool FHyperTwistCoachDashboardControlProfileRosterInspectSurfaceTest::RunTest(
&& Surface.bRequiresWindowsPackagedControllerValidationForReopen
);
TestTrue(
TEXT("The coach dashboard control/profile roster surface must keep the rendered disabled OpenXR project plugin truth explicit."),
Surface.DetailLine.Contains(TEXT("project OpenXR plugin is not enabled"))
TEXT("The coach dashboard control/profile roster surface must keep the rendered enabled OpenXR project plugin truth explicit."),
Surface.DetailLine.Contains(TEXT("project OpenXR plugin is enabled"))
&& Surface.DetailLine.Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
TestTrue(
TEXT("The coach dashboard control/profile roster surface must keep the fixed No-Go host decision visible in its rendered detail line."),

View file

@ -0,0 +1,158 @@
// Copyright HyperTwist, Inc. All Rights Reserved.
#include "Misc/AutomationTest.h"
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "HyperTwistXR/HyperTwistXrTrainingPawn.h"
#if WITH_AUTOMATION_TESTS
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistXrRuntimeOwnershipSurfaceTest,
"HyperTwist.FirstParty.XR.RuntimeOwnershipSurface",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistXrRuntimeOwnershipSurfaceTest::RunTest(const FString& Parameters)
{
const FHyperTwistXrRuntimeOwnershipSurface Surface =
UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface();
TestTrue(
TEXT("The bounded XR runtime ownership surface must be structurally valid."),
Surface.IsStructurallyValid()
);
TestEqual(
TEXT("The bounded XR runtime ownership id must stay stable."),
Surface.RuntimeOwnerId,
FString(TEXT("xr/openxr-desktop-training-runtime-owner"))
);
TestEqual(
TEXT("The bounded XR runtime ownership surface must keep the left motion source stable."),
Surface.LeftMotionSourceId,
FString(TEXT("Left"))
);
TestEqual(
TEXT("The bounded XR runtime ownership surface must keep the right motion source stable."),
Surface.RightMotionSourceId,
FString(TEXT("Right"))
);
TestTrue(
TEXT("The bounded XR runtime ownership surface must preserve the first-party interaction tags."),
Surface.RequiredSurfaceTags.Contains(TEXT("motion-controller pose anchors"))
&& Surface.RequiredSurfaceTags.Contains(TEXT("camera-aligned locomotion vector"))
&& Surface.RequiredSurfaceTags.Contains(TEXT("training-world recenter"))
);
TestTrue(
TEXT("The bounded XR runtime ownership surface must preserve the staged exclusions."),
Surface.ExplicitExclusions.Contains(TEXT("platform-global guardian ownership"))
&& Surface.ExplicitExclusions.Contains(TEXT("launch-tier packaged controller certification ownership"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistXrPreferenceAndBindingProfileTest,
"HyperTwist.FirstParty.XR.PreferenceAndBindingProfiles",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistXrPreferenceAndBindingProfileTest::RunTest(const FString& Parameters)
{
const FHyperTwistXrPreferenceProfile PreferenceProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
TestTrue(
TEXT("The XR preference profile must be structurally valid."),
PreferenceProfile.IsStructurallyValid()
);
TestEqual(
TEXT("The XR preference profile id must stay stable."),
PreferenceProfile.ProfileId,
FString(TEXT("xr-openxr-training-preferences/v1"))
);
TestTrue(
TEXT("The XR preference profile must preserve the owned preference-field roster."),
PreferenceProfile.PreferenceFieldTags.Contains(TEXT("movement speed"))
&& PreferenceProfile.PreferenceFieldTags.Contains(TEXT("snap turn"))
&& PreferenceProfile.PreferenceFieldTags.Contains(TEXT("world scale"))
);
TestTrue(
TEXT("The XR preference profile must preserve comfort-surface tags."),
PreferenceProfile.ComfortSurfaceTags.Contains(TEXT("comfort vignette strength"))
&& PreferenceProfile.ComfortSurfaceTags.Contains(TEXT("seated height offset"))
);
const FHyperTwistXrControllerBindingProfile BindingProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile();
TestTrue(
TEXT("The XR controller binding profile must be structurally valid."),
BindingProfile.IsStructurallyValid()
);
TestEqual(
TEXT("The XR controller binding profile id must stay stable."),
BindingProfile.ProfileId,
FString(TEXT("xr-openxr-motion-controllers/v1"))
);
TestTrue(
TEXT("The XR controller binding profile must preserve the owned axis roster."),
BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_MoveForward"))
&& BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_Turn"))
&& BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_RightTrigger"))
);
TestTrue(
TEXT("The XR controller binding profile must preserve the supported controller families."),
BindingProfile.ControllerFamilyIds.Contains(TEXT("controller/oculus-touch"))
&& BindingProfile.ControllerFamilyIds.Contains(TEXT("controller/valve-index"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistXrTrainingPawnDefaultsTest,
"HyperTwist.FirstParty.XR.TrainingPawnDefaults",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistXrTrainingPawnDefaultsTest::RunTest(const FString& Parameters)
{
const AHyperTwistXrTrainingPawn* PawnDefaults = GetDefault<AHyperTwistXrTrainingPawn>();
TestNotNull(TEXT("The XR training pawn defaults must exist."), PawnDefaults);
if (PawnDefaults == nullptr)
{
return false;
}
TestNotNull(
TEXT("The XR training pawn must allocate a scene root."),
PawnDefaults->SceneRoot.Get()
);
TestNotNull(
TEXT("The XR training pawn must allocate a VR origin."),
PawnDefaults->VROrigin.Get()
);
TestNotNull(
TEXT("The XR training pawn must allocate a camera."),
PawnDefaults->CameraComponent.Get()
);
TestNotNull(
TEXT("The XR training pawn must allocate a left controller."),
PawnDefaults->LeftController.Get()
);
TestNotNull(
TEXT("The XR training pawn must allocate a right controller."),
PawnDefaults->RightController.Get()
);
TestTrue(
TEXT("The XR training pawn must start with a valid owned preference profile and binding profile."),
PawnDefaults->ActivePreferenceProfile.IsStructurallyValid()
&& PawnDefaults->ActiveControllerBindingProfile.IsStructurallyValid()
);
TestTrue(
TEXT("The XR training pawn must preserve the bounded runtime ownership line."),
PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr/openxr-desktop-training-runtime-owner"))
&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr-openxr-training-preferences/v1"))
&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr-openxr-motion-controllers/v1"))
);
return true;
}
#endif

View file

@ -9,7 +9,7 @@ public class UnrealHyperTwist : ModuleRules
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "Json", "JsonUtilities", "UMG", "Voice", "ProceduralMeshComponent" });
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "Json", "JsonUtilities", "UMG", "Voice", "ProceduralMeshComponent", "HeadMountedDisplay", "XRBase" });
PrivateDependencyModuleNames.AddRange(
new string[] { "Slate", "SlateCore", "HTTP", "WebBrowser", "WebBrowserWidget", "Projects" }

View file

@ -58,6 +58,14 @@
"Name": "WebBrowserWidget",
"Enabled": true
},
{
"Name": "XRBase",
"Enabled": true
},
{
"Name": "OpenXR",
"Enabled": true
},
{
"Name": "MovieRenderPipeline",
"Enabled": true,

View file

@ -9,38 +9,56 @@ leaving “maybe XR is already basically there” ambiguity in the Unreal lane.
## Current decision
Current decision: `No-Go` on native OpenXR/controller runtime widening in the
shipping HyperTwist lane.
Current decision: `No-Go` on broader native OpenXR/controller runtime widening
beyond the newly landed bounded desktop-training owner seam in the shipping
HyperTwist lane.
Current authoritative host posture:
- the shipping immersive lane remains a desktop-hosted Unreal training runtime
with scenic presence, comfort, posture, and session-local settings ownership
- the project does **not** currently enable an `OpenXR` plugin stack in
`UnrealHyperTwist.uproject`
- the project does **not** currently prove a first-party headset/controller
runtime owner such as a dedicated VR pawn plus
`MotionControllerComponent`-driven interaction surface
- the newly landed native control/input and control/settings inspect surfaces
must be treated as truthful bounded ownership around the desktop simulator,
not as proof of a finished XR runtime
- the project now **does** enable a deliberately bounded `OpenXR` + `XRBase`
plugin stack in `UnrealHyperTwist.uproject`
- the project now **does** prove a first-party bounded runtime owner through a
dedicated XR training pawn plus `MotionControllerComponent`-driven anchors,
controller-binding ownership, and preference-profile ownership
- that bounded runtime-owner substrate must still be treated as truthful
desktop-simulator ownership, not as proof of a finished launch-tier
XR/controller product
- polished user-facing rebinding/settings ownership and packaged
headset/controller proof remain unfinished and continue to hold the wider
lane closed
## Evidence checked
### Project plugin posture
- `UnrealHyperTwist/UnrealHyperTwist.uproject` currently enables:
- `OpenXR`
- `XRBase`
- `RemoteControl`
- `ProceduralMeshComponent`
- `WebBrowserWidget`
- editor-only tooling such as `MovieRenderPipeline`,
`PythonScriptPlugin`, and `ModelingToolsEditorMode`
- that file does **not** currently declare an enabled `OpenXR` plugin
### Runtime ownership posture
- `UnrealHyperTwist/Config/DefaultInput.ini` still proves real
`EnhancedInput` and broad motion-controller axis groundwork
- `UnrealHyperTwist/Config/DefaultInput.ini` now proves real bounded
controller-axis and action ownership across the current controller-family
roster instead of only broad motion-controller groundwork
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h`
and `.../Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp` now define the
first-party bounded runtime-owner, preference-profile, and controller-binding
contracts:
- `xr/openxr-desktop-training-runtime-owner`
- `xr-openxr-training-preferences/v1`
- `xr-openxr-motion-controllers/v1`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h`
and `.../Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp` now provide a
bounded first-party XR training pawn with camera, controller anchors,
posture switching, desktop fallback pitch handling, recentering, and status
reporting
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingImmersiveEnvironmentLibrary.cpp`
keeps the immersive lane explicitly bounded to scenic training ownership
- the same immersive library explicitly excludes:
@ -80,6 +98,18 @@ Current authoritative host posture:
99.38 seconds`, and exported
`Saved\AutomationReports\Browser-XrPluginProjectPosture-Verify\index.json`
with all `20` `HyperTwist.Browser.*` tests green
- a later same-family bounded-runtime-owner follow-up on `2026-07-01` then
carried the real first-party runtime-owner packet into those same native
inspect surfaces as stable structured ids plus counts, rebuilt the exact
source state on maintained validation root
`C:\HyperTwist_worktrees\phase10validate` with:
- `Result: Succeeded`
- UnrealBuildTool `Total execution time: 124.26 seconds`
then exported:
- `Saved\AutomationReports\XR-BoundedRuntimeOwner-20260701-Final\index.json`
with `3` focused XR tests succeeded
- `Saved\AutomationReports\Browser-XrBoundedRuntime-20260701-Final\index.json`
with all `26` `HyperTwist.Browser.*` tests succeeded
## Interpretation
@ -89,37 +119,39 @@ HyperTwist already owns:
- scenic immersive training presentation
- session-local presence and comfort posture
- current camera and dedicated-family view settings ownership
- a bounded OpenXR runtime-owner substrate for desktop training, including
first-party runtime owner, controller bindings, preference ownership, and XR
training pawn scaffolding
HyperTwist does **not** yet own:
- headset-runtime plugin posture
- controller interaction runtime ownership
- polished controller rebinding/preferences ownership
- packaged XR/controller proof
- packaged headset/controller proof
- broader launch-tier cross-device XR completion
Therefore the current truthful host decision is:
- keep the immersive lane desktop-hosted
- keep `OpenXR` disabled by default in the shipping branch
- do not market VR/controller support as finished
- do not let browser-side retained XR grounding imply native XR completion
- keep the broader controller-widening branch closed until the remaining
settings/rebinding and packaged-proof requirements are actually met
## Reopen criteria
Future native XR widening may reopen only when all of the following are true:
1. the exact plugin stack is chosen explicitly and checked in
2. first-party runtime owners for headset/controller interaction are present
3. controller bindings and rebinding/preferences ownership are first-party and
user-visible
4. Windows build plus focused automation plus packaged validation prove that
lane
5. public/product copy is updated only after those proofs exist
1. polished controller bindings plus rebinding/preferences ownership are
first-party and user-visible
2. Windows build plus focused automation plus packaged validation prove actual
headset/controller behavior on the shipping lane
3. public/product copy is updated only after those proofs exist
## Immediate consequence
No native XR implementation packet is currently open by default.
If the project later reopens this lane, the first implementation packet should
be a bounded native plugin/runtime-owner slice, not public marketing widening
and not speculative controller polish in the absence of a chosen runtime host.
be finished user-facing controller settings/rebinding ownership plus packaged
controller proof above the now-landed bounded runtime-owner substrate, not
public marketing widening.

View file

@ -324,22 +324,70 @@ truthfully claim:
- `TrainingPanel.ControlProfileRosterInspectSurface`
- `TrainingPanel.ControlSettingsOwnershipInspectSurface`
### Finished XR runtime ownership is not yet proven
### Bounded OpenXR runtime ownership now exists on the desktop-hosted lane
- `UnrealHyperTwist/UnrealHyperTwist.uproject` currently enables
`WebBrowserWidget`, `ProceduralMeshComponent`, `RemoteControl`, and editor
tooling plugins, but this audit did not find an enabled `OpenXR` plugin
declaration or a headset-specific runtime plugin stack in the project file
- a focused source search did not surface first-party runtime classes such as a
dedicated VR pawn or `MotionControllerComponent`-driven interaction owner
that would justify stronger shipping claims
- a later same-family `2026-07-01` bounded-runtime-owner follow-up then moved
the native XR lane from “plugin posture and grounded boundary only” into a
real first-party bounded runtime-owner substrate without reopening the
broader desktop-hosted `No-Go` posture:
- `UnrealHyperTwist/UnrealHyperTwist.uproject` now enables both `OpenXR`
and `XRBase`
- `UnrealHyperTwist/UnrealHyperTwist.Build.cs` now carries the matching
runtime module dependencies
- `UnrealHyperTwist/Config/DefaultInput.ini` now owns explicit OpenXR-ready
controller axes and actions across the currently bounded controller-family
roster
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h`
and `.../Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp` now define
the first-party runtime-owner, preference-profile, and controller-binding
contracts:
- `xr/openxr-desktop-training-runtime-owner`
- `xr-openxr-training-preferences/v1`
- `xr-openxr-motion-controllers/v1`
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h`
and `.../Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp` now provide a
bounded first-party training pawn with `CameraComponent`,
`MotionControllerComponent` anchors, desktop fallback pitch handling,
posture switching, recentering, and owned runtime status reporting
- the native control/input, control/settings, and control/profile inspect
surfaces now also expose that bounded runtime-owner packet in structured
fields and rendered operator lines, including the stable ids, preference
field count `8`, required binding-axis count `8`, and supported controller
family count `4`
- the maintained reverse-SSH Windows lane then revalidated that exact-source
state on `C:\HyperTwist_worktrees\phase10validate`:
- final exact-source editor build:
- `Result: Succeeded`
- UnrealBuildTool `Total execution time: 124.26 seconds`
- focused XR automation export:
- `Saved\AutomationReports\XR-BoundedRuntimeOwner-20260701-Final\index.json`
- `3` tests performed
- `3` succeeded
- full browser automation export:
- `Saved\AutomationReports\Browser-XrBoundedRuntime-20260701-Final\index.json`
- `26` `HyperTwist.Browser.*` tests succeeded
- that same follow-up matters for this audit because native Unreal now owns a
real bounded OpenXR runtime-owner seam instead of only negative boundary
wording, while still keeping the broader widening doctrine honest
### Finished launch-tier XR/controller ownership is still not proven
- the earlier `2026-06-22` finding that the shipping branch did not yet enable
`OpenXR` is now historically superseded by the bounded-runtime-owner packet
above; current source truth is that the desktop-hosted branch now carries a
deliberately bounded `OpenXR` + `XRBase` stack plus first-party runtime-owner
classes
- that later exact-source state still does **not** yet prove:
- packaged headset/controller behavior on the Windows lane
- polished user-facing controller rebinding/settings ownership
- broader launch-tier cross-device XR completion
- `HyperTwistTrainingImmersiveEnvironmentLibrary.cpp` clearly establishes a
first-party immersive/scenic environment direction, but it also explicitly
keeps scope bounded away from platform-global XR settings or generic VR shell
ownership
- the current explicit decision packet on `2026-06-24` therefore fixes the
host/plugin posture to desktop-hosted scenic immersive training with a
current `No-Go` on native OpenXR/controller runtime widening
- the current explicit decision packet therefore still fixes the broader
desktop-hosted `No-Go` on native OpenXR/controller widening beyond the newly
landed bounded runtime-owner seam
## Product-language consequence

View file

@ -8,6 +8,37 @@ This note records the concrete cutover path for replacing the current
placeholder `hypertwist.app` page with the first-party HyperTwist website plus
auth-server lane already shipped in the repo.
## Latest live-origin truth on `2026-07-01`
The historical placeholder-era cutover notes below remain useful as deployment
history, but they are no longer the current live reading.
Current direct live checks now show:
- `https://hypertwist.app/` returns the first-party site shell, including meta
marker `hypertwist-site-shell=first-party-website-v1`
- `https://hypertwist.app/api/auth/health` returns JSON with:
- `ok: true`
- `service: hypertwist-auth-server`
- `runtime.public_origin_ready: true`
- `launch.posture: preview`
- `https://hypertwist.app/api/releases/manifest` returns the first-party
release manifest JSON and currently keeps the Windows lane at preview
posture with packaged-validation summary attached but no public download URL
Current truthful interpretation:
- the placeholder rollout page is gone
- the first-party same-origin website/auth-server deployment is live
- the live public origin is still a preview deployment rather than full public
launch because the current auth/launch JSON still reports missing Windows
download, checkout, billing-map, and webhook-secret launch requirements
- a plain repo push is not itself a production deploy; live website changes are
only published after
`npm run run:vps-same-origin-live-deploy -- --manifest <file> --identity-file <key>`
or an equivalent CI job that invokes the same helper and revalidates the
public origin
This is the next operator step after:
- the public website implementation packet
@ -323,16 +354,11 @@ curl --silent --show-error https://hypertwist.app/api/releases/manifest
curl --silent --show-error https://hypertwist.app/ | rg "hypertwist-site-shell|id=\"root\""
```
As of the latest repo-side proof on `2026-06-22`, those live checks still
returned the placeholder rollout page, so the rendered bundle is ready but the
public-host cutover has not yet been applied.
That remaining cutover gap is now narrower than before: an isolated VPS-local
staging proof on the same host succeeded on `3011` for `/health`,
`/api/auth/health`, `/api/releases/manifest`, and the first-party shell marker,
and the new first-party staging-proof helper now reproduces that flow
programmatically, so the remaining work is root-owned enablement and vhost
replacement rather than unresolved repo-side runtime behavior.
That `2026-06-22` placeholder-era gap is now historically closed: the live
origin currently serves the first-party shell and JSON health/manifest routes.
The remaining gap is no longer host cutover; it is launch-tier value
completion and keeping future site changes paired with the real live-deploy
helper instead of assuming a repo push alone is production publication.
### 7. Run the first-party readiness verifier

View file

@ -1116,6 +1116,56 @@ Current audit note:
- `scripts/run-hypertwist-gitnexus-status.sh`
- bounded mirror `Status: up-to-date`
## Latest bounded OpenXR runtime-owner follow-up (`2026-07-01`)
- the next same-family native continuation then finally moved the XR lane from
“explicit plugin posture and fixed `No-Go` wording” into a real first-party
bounded runtime-owner substrate without pretending the wider branch had
reopened:
- `UnrealHyperTwist.uproject` now enables `OpenXR` plus `XRBase`
- `UnrealHyperTwist.Build.cs` now carries the matching runtime module
dependencies
- `DefaultInput.ini` now owns explicit OpenXR-ready controller axes/actions
across the bounded controller-family roster
- new first-party runtime-owner/profile code now lives under
`Source/UnrealHyperTwist/Public/HyperTwistXR/` and
`.../Private/HyperTwistXR/`
- the landed stable ids are:
- `xr/openxr-desktop-training-runtime-owner`
- `xr-openxr-training-preferences/v1`
- `xr-openxr-motion-controllers/v1`
- the native control/input, control/settings, and control/profile inspect
surfaces now all carry those ids plus stable counts in structured state and
rendered lines
- one adjacent quality follow-up in the same packet then tightened the bounded
pawn itself:
- desktop recenter now clears fallback pitch instead of leaving a carried
non-HMD look angle behind
- the runtime status line now keeps the owned runtime, preference, and
controller-profile ids visible together
- the fallback-rotation path now lives in one helper instead of duplicating
inline rotation writes
- the maintained reverse-SSH Windows lane then revalidated the exact-source
state on `C:\HyperTwist_worktrees\phase10validate`:
- final editor build:
- `Result: Succeeded`
- UnrealBuildTool `Total execution time: 124.26 seconds`
- focused XR automation:
- `Saved\AutomationReports\XR-BoundedRuntimeOwner-20260701-Final\index.json`
- `3` tests succeeded
- full browser automation:
- `Saved\AutomationReports\Browser-XrBoundedRuntime-20260701-Final\index.json`
- all `26` `HyperTwist.Browser.*` tests succeeded
- current truthful reading after this continuation:
- HyperTwist now owns a real bounded desktop-training OpenXR runtime-owner
seam
- the broader desktop-hosted XR/controller widening branch still remains
closed until polished rebinding/settings ownership and packaged
headset/controller proof exist
- this is the right intermediate state to document publicly because it is
stronger than the older negative-only wording and still honest about the
remaining gap
## Latest public/manual follow-up (`2026-06-25`)
- the public website now carries one shared release-reference bundle across the

File diff suppressed because one or more lines are too long

View file

@ -156,6 +156,29 @@ scene ownership into the native control-profile roster seam, so the selectable
roster truth now stays aligned with the sibling control-settings and active
runtime surfaces instead of leaving those facts implied.
The next same-family `2026-07-01` follow-up then moved the XR side of that
statement forward too without overclaiming launch-tier completion:
- the desktop-hosted branch now enables `OpenXR` plus `XRBase`
- first-party bounded runtime-owner/profile ownership now exists through:
- `xr/openxr-desktop-training-runtime-owner`
- `xr-openxr-training-preferences/v1`
- `xr-openxr-motion-controllers/v1`
- a dedicated `AHyperTwistXrTrainingPawn` now owns bounded camera/controller
anchors, posture switching, recentering, and desktop fallback pitch handling
- the maintained Windows validation lane then re-proved the exact-source state
with:
- editor build `Result: Succeeded`, UnrealBuildTool `Total execution time:
124.26 seconds`
- focused XR automation `XR-BoundedRuntimeOwner-20260701-Final`: `3/3`
green
- full browser automation `Browser-XrBoundedRuntime-20260701-Final`:
`26/26` `HyperTwist.Browser.*` green
That is real bounded runtime ownership, but it is still not the same thing as
finished launch-tier controller rebinding, packaged headset proof, or generic
marketed VR completion.
Canonical audit note:
- `C:\HyperTwist\docs\ops\HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md`

View file

@ -46,6 +46,9 @@ Before using these templates:
to stage the committed checkout, upload the rendered bundle, install env,
build the site, replace the live `systemd` plus `nginx` files, and validate
the public origin through the same readiness lane
- a plain git push does **not** publish `https://hypertwist.app` by itself;
production changes are only live after this helper, or a CI job that invokes
the same helper, has completed and the public origin has been revalidated
- set `publicOrigin` to the exact live origin that should own both the website
and auth cookies; for the canonical production lane that value is
`https://hypertwist.app`