Harden bounded OpenXR runtime ownership lane

This commit is contained in:
axiomlogicnexus 2026-07-01 10:28:11 +00:00
parent 3ed296b893
commit d74b35bb06
13 changed files with 332 additions and 23 deletions

View file

@ -95,6 +95,10 @@ DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.Defaul
+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_VerticalAdjust",Key=PageUp,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_VerticalAdjust",Key=PageDown,Scale=-1.000000)
+AxisMappings=(AxisName="HyperTwistXr_VerticalAdjust",Key=Gamepad_RightShoulder,Scale=1.000000)
+AxisMappings=(AxisName="HyperTwistXr_VerticalAdjust",Key=Gamepad_LeftShoulder,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)

View file

@ -23,6 +23,7 @@ namespace HyperTwistTrainingPanelWidgetInternal
bool bEnhancedInputProjectGroundworkPresent = false;
bool bMotionControllerGroundworkPresent = false;
bool bOpenXrProjectPluginEnabled = false;
bool bXrVerticalAdjustmentAxisPresent = false;
int32 MotionControllerFamilyCount = 0;
TArray<FString> MotionControllerFamilies;
};
@ -434,6 +435,17 @@ namespace HyperTwistTrainingPanelWidgetInternal
AxisConfigEntries,
GInputIni
);
TArray<FString> AxisMappingEntries;
GConfig->GetArray(
InputSettingsSection,
TEXT("AxisMappings"),
AxisMappingEntries,
GInputIni
);
Facts.bXrVerticalAdjustmentAxisPresent = AxisConfigEntriesContainToken(
AxisMappingEntries,
TEXT("AxisName=\"HyperTwistXr_VerticalAdjust\"")
);
const struct
{
@ -645,7 +657,7 @@ namespace HyperTwistTrainingPanelWidgetInternal
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."),
TEXT("XR preference ownership: %s | profile %s | posture %s | dominant hand %s | snap turn %.0f | vertical adjust %.0f | pointer %.0f | world scale %.0f | preference fields %d."),
*DescribeReadyState(Facts.bPreferenceProfileReady),
*DescribeIdIfReady(
Facts.bPreferenceProfileReady,
@ -653,6 +665,8 @@ namespace HyperTwistTrainingPanelWidgetInternal
*DescribeXrPostureMode(Facts.PreferenceProfile.PostureMode),
*DescribeXrDominantHand(Facts.PreferenceProfile.DominantHand),
Facts.PreferenceProfile.SnapTurnDegrees,
Facts.PreferenceProfile.VerticalAdjustmentUnitsPerSecond,
Facts.PreferenceProfile.PointerLengthUnits,
Facts.PreferenceProfile.WorldToMetersScale,
DescribeCountIfReady(
Facts.bPreferenceProfileReady,
@ -1419,6 +1433,8 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
Surface.MotionControllerFamilyCount = InputGroundworkFacts.MotionControllerFamilyCount;
Surface.bOpenXrProjectPluginEnabled =
InputGroundworkFacts.bOpenXrProjectPluginEnabled;
Surface.bXrVerticalAdjustmentAxisPresent =
InputGroundworkFacts.bXrVerticalAdjustmentAxisPresent;
FHyperTwistTrainingImmersivePresenceControlContract PresenceControlContract;
Surface.bImmersivePresenceContractReady =
@ -1512,7 +1528,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 | bounded runtime owner %s (%s) | controller binding %s (%s) | finished XR runtime not yet shipped."),
TEXT("XR groundwork: EnhancedInput config %s | motion controls %s | controller families %d (%s) | OpenXR project plugin %s | vertical adjust axis %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(
@ -1521,6 +1537,8 @@ UHyperTwistTrainingPanelWidget::GetDisplayedControlInputReadinessInspectSurface(
*MotionControllerFamilyLabels,
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bOpenXrProjectPluginEnabled),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bXrVerticalAdjustmentAxisPresent),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(
Surface.bImmersivePresenceContractReady),
*HyperTwistTrainingPanelWidgetInternal::DescribeBool(

View file

@ -11,8 +11,26 @@ namespace HyperTwistXrTrainingPawnInternal
{
constexpr float SeatedHeightOffset = -42.0f;
constexpr float HybridHeightOffset = -20.0f;
constexpr float ManualVerticalAdjustmentMin = -160.0f;
constexpr float ManualVerticalAdjustmentMax = 160.0f;
constexpr float DesktopFallbackPitchMin = -65.0f;
constexpr float DesktopFallbackPitchMax = 65.0f;
constexpr float MoveSpeedMin = 40.0f;
constexpr float MoveSpeedMax = 600.0f;
constexpr float VerticalAdjustmentSpeedMin = 40.0f;
constexpr float VerticalAdjustmentSpeedMax = 320.0f;
constexpr float SnapTurnDegreesMin = 5.0f;
constexpr float SnapTurnDegreesMax = 90.0f;
constexpr float SnapTurnAxisThresholdMin = 0.2f;
constexpr float SnapTurnAxisThresholdMax = 0.95f;
constexpr float SmoothTurnDegreesPerSecondMin = 45.0f;
constexpr float SmoothTurnDegreesPerSecondMax = 240.0f;
constexpr float PointerLengthMin = 32.0f;
constexpr float PointerLengthMax = 320.0f;
constexpr float WorldToMetersScaleMin = 25.0f;
constexpr float WorldToMetersScaleMax = 400.0f;
constexpr float ControllerDeadZoneMin = 0.0f;
constexpr float ControllerDeadZoneMax = 0.95f;
FVector BuildPlanarDirection(const FVector& RawDirection)
{
@ -45,6 +63,87 @@ namespace HyperTwistXrTrainingPawnInternal
return TEXT("standing");
}
}
FHyperTwistXrPreferenceProfile SanitizePreferenceProfile(
const FHyperTwistXrPreferenceProfile& PreferenceProfile
)
{
const FHyperTwistXrPreferenceProfile DefaultProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
if (!PreferenceProfile.IsStructurallyValid())
{
return DefaultProfile;
}
FHyperTwistXrPreferenceProfile Sanitized = PreferenceProfile;
Sanitized.MoveSpeedUnitsPerSecond = FMath::Clamp(
Sanitized.MoveSpeedUnitsPerSecond,
MoveSpeedMin,
MoveSpeedMax
);
Sanitized.VerticalAdjustmentUnitsPerSecond = FMath::Clamp(
Sanitized.VerticalAdjustmentUnitsPerSecond,
VerticalAdjustmentSpeedMin,
VerticalAdjustmentSpeedMax
);
Sanitized.SnapTurnDegrees = FMath::Clamp(
Sanitized.SnapTurnDegrees,
SnapTurnDegreesMin,
SnapTurnDegreesMax
);
Sanitized.SnapTurnAxisThreshold = FMath::Clamp(
Sanitized.SnapTurnAxisThreshold,
SnapTurnAxisThresholdMin,
SnapTurnAxisThresholdMax
);
const float SnapTurnResetThresholdMax = FMath::Max(
0.0f,
Sanitized.SnapTurnAxisThreshold - 0.05f
);
Sanitized.SnapTurnResetThreshold = FMath::Clamp(
Sanitized.SnapTurnResetThreshold,
0.0f,
SnapTurnResetThresholdMax
);
Sanitized.SmoothTurnDegreesPerSecond = FMath::Clamp(
Sanitized.SmoothTurnDegreesPerSecond,
SmoothTurnDegreesPerSecondMin,
SmoothTurnDegreesPerSecondMax
);
Sanitized.PointerLengthUnits = FMath::Clamp(
Sanitized.PointerLengthUnits,
PointerLengthMin,
PointerLengthMax
);
Sanitized.WorldToMetersScale = FMath::Clamp(
Sanitized.WorldToMetersScale,
WorldToMetersScaleMin,
WorldToMetersScaleMax
);
Sanitized.ComfortVignetteStrength = FMath::Clamp(
Sanitized.ComfortVignetteStrength,
0.0f,
1.0f
);
Sanitized.ControllerDeadZone = FMath::Clamp(
Sanitized.ControllerDeadZone,
ControllerDeadZoneMin,
ControllerDeadZoneMax
);
if (Sanitized.PreferenceFieldTags.Num() <= 0)
{
Sanitized.PreferenceFieldTags = DefaultProfile.PreferenceFieldTags;
}
if (Sanitized.ComfortSurfaceTags.Num() <= 0)
{
Sanitized.ComfortSurfaceTags = DefaultProfile.ComfortSurfaceTags;
}
if (Sanitized.ExplicitExclusions.Num() <= 0)
{
Sanitized.ExplicitExclusions = DefaultProfile.ExplicitExclusions;
}
return Sanitized;
}
}
AHyperTwistXrTrainingPawn::AHyperTwistXrTrainingPawn()
@ -97,6 +196,7 @@ void AHyperTwistXrTrainingPawn::Tick(const float DeltaSeconds)
RefreshHeadMountedDisplayState();
UpdateSmoothLocomotion(DeltaSeconds);
UpdateVerticalAdjustment(DeltaSeconds);
UpdateTurn(DeltaSeconds);
UpdateDesktopFallbackPitch(DeltaSeconds);
}
@ -121,6 +221,11 @@ void AHyperTwistXrTrainingPawn::SetupPlayerInputComponent(
this,
&AHyperTwistXrTrainingPawn::HandleMoveRight
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_VerticalAdjust"),
this,
&AHyperTwistXrTrainingPawn::HandleVerticalAdjustAxis
);
PlayerInputComponent->BindAxis(
TEXT("HyperTwistXr_Turn"),
this,
@ -175,12 +280,10 @@ void AHyperTwistXrTrainingPawn::ApplyPreferenceProfile(
const FHyperTwistXrPreferenceProfile& PreferenceProfile
)
{
if (!PreferenceProfile.IsStructurallyValid())
{
return;
}
ActivePreferenceProfile = PreferenceProfile;
ActivePreferenceProfile =
HyperTwistXrTrainingPawnInternal::SanitizePreferenceProfile(
PreferenceProfile
);
ApplyPostureOffset();
ApplyPointerAnchors();
@ -218,6 +321,7 @@ void AHyperTwistXrTrainingPawn::SetSnapTurnEnabled(const bool bEnabled)
void AHyperTwistXrTrainingPawn::ResetXrOrigin()
{
ManualVerticalAdjustmentUnits = 0.0f;
DesktopFallbackPitchDegrees = 0.0f;
ApplyDesktopFallbackRotation();
ApplyPostureOffset();
@ -235,7 +339,7 @@ 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"),
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 | vertical offset %.0f | desktop pitch %.0f | grip %.2f/%.2f | trigger %.2f/%.2f"),
*RuntimeSurface.RuntimeOwnerId,
*ActivePreferenceProfile.ProfileId,
*ActiveControllerBindingProfile.ProfileId,
@ -247,6 +351,8 @@ FString AHyperTwistXrTrainingPawn::BuildRuntimeStatusLine() const
ActivePreferenceProfile.DominantHand),
ActivePreferenceProfile.bSnapTurnEnabled ? TEXT("on") : TEXT("off"),
ActivePreferenceProfile.bSmoothLocomotionEnabled ? TEXT("on") : TEXT("off"),
ManualVerticalAdjustmentUnits,
DesktopFallbackPitchDegrees,
LeftGripValue,
RightGripValue,
LeftTriggerValue,
@ -274,7 +380,9 @@ void AHyperTwistXrTrainingPawn::ApplyPostureOffset()
break;
}
VROrigin->SetRelativeLocation(FVector(0.0f, 0.0f, HeightOffset));
VROrigin->SetRelativeLocation(
FVector(0.0f, 0.0f, HeightOffset + ManualVerticalAdjustmentUnits)
);
}
void AHyperTwistXrTrainingPawn::ApplyPointerAnchors()
@ -313,10 +421,25 @@ void AHyperTwistXrTrainingPawn::ApplyDesktopFallbackRotation()
void AHyperTwistXrTrainingPawn::RefreshHeadMountedDisplayState()
{
const bool bWasHeadMountedDisplayEnabled = bHeadMountedDisplayEnabled;
bHeadMountedDisplayConnected =
UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayConnected();
bHeadMountedDisplayEnabled =
UHeadMountedDisplayFunctionLibrary::IsHeadMountedDisplayEnabled();
if (bHeadMountedDisplayEnabled && !bWasHeadMountedDisplayEnabled)
{
DesktopFallbackPitchDegrees = 0.0f;
if (VROrigin != nullptr)
{
VROrigin->SetRelativeRotation(FRotator::ZeroRotator);
}
return;
}
if (!bHeadMountedDisplayEnabled && bWasHeadMountedDisplayEnabled)
{
ApplyDesktopFallbackRotation();
}
}
void AHyperTwistXrTrainingPawn::UpdateSmoothLocomotion(const float DeltaSeconds)
@ -352,6 +475,33 @@ void AHyperTwistXrTrainingPawn::UpdateSmoothLocomotion(const float DeltaSeconds)
AddActorWorldOffset(Delta, true);
}
void AHyperTwistXrTrainingPawn::UpdateVerticalAdjustment(const float DeltaSeconds)
{
if (!ActivePreferenceProfile.bAllowVerticalAdjustment || VROrigin == nullptr)
{
return;
}
const float VerticalAdjustValue = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
PendingVerticalAdjust,
ActivePreferenceProfile.ControllerDeadZone
);
if (FMath::IsNearlyZero(VerticalAdjustValue))
{
return;
}
ManualVerticalAdjustmentUnits = FMath::Clamp(
ManualVerticalAdjustmentUnits
+ VerticalAdjustValue
* ActivePreferenceProfile.VerticalAdjustmentUnitsPerSecond
* DeltaSeconds,
HyperTwistXrTrainingPawnInternal::ManualVerticalAdjustmentMin,
HyperTwistXrTrainingPawnInternal::ManualVerticalAdjustmentMax
);
ApplyPostureOffset();
}
void AHyperTwistXrTrainingPawn::UpdateTurn(const float DeltaSeconds)
{
const float TurnValue = HyperTwistXrTrainingPawnInternal::ApplyDeadZone(
@ -430,6 +580,11 @@ void AHyperTwistXrTrainingPawn::HandleMoveRight(const float Value)
PendingMoveRight = Value;
}
void AHyperTwistXrTrainingPawn::HandleVerticalAdjustAxis(const float Value)
{
PendingVerticalAdjust = Value;
}
void AHyperTwistXrTrainingPawn::HandleTurnAxis(const float Value)
{
PendingTurnAxis = Value;
@ -442,22 +597,22 @@ void AHyperTwistXrTrainingPawn::HandleLookPitchAxis(const float Value)
void AHyperTwistXrTrainingPawn::HandleLeftGripAxis(const float Value)
{
LeftGripValue = Value;
LeftGripValue = FMath::Clamp(Value, 0.0f, 1.0f);
}
void AHyperTwistXrTrainingPawn::HandleRightGripAxis(const float Value)
{
RightGripValue = Value;
RightGripValue = FMath::Clamp(Value, 0.0f, 1.0f);
}
void AHyperTwistXrTrainingPawn::HandleLeftTriggerAxis(const float Value)
{
LeftTriggerValue = Value;
LeftTriggerValue = FMath::Clamp(Value, 0.0f, 1.0f);
}
void AHyperTwistXrTrainingPawn::HandleRightTriggerAxis(const float Value)
{
RightTriggerValue = Value;
RightTriggerValue = FMath::Clamp(Value, 0.0f, 1.0f);
}
void AHyperTwistXrTrainingPawn::HandleToggleSnapTurn()

View file

@ -10057,6 +10057,9 @@ struct FHyperTwistTrainingControlInputReadinessInspectSurface
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bOpenXrProjectPluginEnabled = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bXrVerticalAdjustmentAxisPresent = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bClassicCubeMouseInputReady = false;

View file

@ -96,11 +96,13 @@ protected:
void ApplyDesktopFallbackRotation();
void RefreshHeadMountedDisplayState();
void UpdateSmoothLocomotion(float DeltaSeconds);
void UpdateVerticalAdjustment(float DeltaSeconds);
void UpdateTurn(float DeltaSeconds);
void UpdateDesktopFallbackPitch(float DeltaSeconds);
void HandleMoveForward(float Value);
void HandleMoveRight(float Value);
void HandleVerticalAdjustAxis(float Value);
void HandleTurnAxis(float Value);
void HandleLookPitchAxis(float Value);
void HandleLeftGripAxis(float Value);
@ -112,8 +114,10 @@ protected:
float PendingMoveForward = 0.0f;
float PendingMoveRight = 0.0f;
float PendingVerticalAdjust = 0.0f;
float PendingTurnAxis = 0.0f;
float PendingLookPitchAxis = 0.0f;
float ManualVerticalAdjustmentUnits = 0.0f;
float DesktopFallbackPitchDegrees = 0.0f;
bool bSnapTurnLatchOpen = true;
};

View file

@ -1150,6 +1150,7 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The control/input readiness surface must keep the current project OpenXR plugin posture explicit."),
Surface.bOpenXrProjectPluginEnabled
&& Surface.bXrVerticalAdjustmentAxisPresent
);
TestFalse(
TEXT("The control/input readiness surface must keep unfinished XR/runtime and preferences truth explicit."),
@ -1190,9 +1191,15 @@ bool FHyperTwistTrainingPanelControlInputReadinessInspectSurfaceTest::RunTest(
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 yes"))
&& Surface.XrStatusLine.Contains(TEXT("vertical adjust axis 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 now spell out the bounded vertical-adjust and pointer preference ownership."),
Surface.XrPreferenceOwnerLine.Contains(TEXT("vertical adjust 120"))
&& Surface.XrPreferenceOwnerLine.Contains(TEXT("pointer 180"))
);
TestTrue(
TEXT("The control/input readiness surface must retain the fixed No-Go host decision and the later settings/rebinding follow-through."),
Surface.NextPacketLine.Contains(TEXT("No-Go"))
@ -1257,6 +1264,7 @@ bool FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest::RunTest(
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 yes"))
&& Surface.DetailLine.Contains(TEXT("vertical adjust axis yes"))
&& Surface.DetailLine.Contains(TEXT("xr/openxr-desktop-training-runtime-owner"))
);
TestTrue(
@ -1267,6 +1275,12 @@ bool FHyperTwistCoachDashboardControlInputReadinessInspectSurfaceTest::RunTest(
TestTrue(
TEXT("The coach dashboard control/input readiness surface must preserve the explicit enabled OpenXR project plugin truth."),
Surface.bOpenXrProjectPluginEnabled
&& Surface.bXrVerticalAdjustmentAxisPresent
);
TestTrue(
TEXT("The coach dashboard control/input readiness surface must retain the bounded vertical-adjust and pointer preference ownership."),
Surface.DetailLine.Contains(TEXT("vertical adjust 120"))
&& Surface.DetailLine.Contains(TEXT("pointer 180"))
);
TestTrue(
TEXT("The coach dashboard control/input readiness surface must expose the bounded XR runtime owner, preference profile, and controller binding profile."),

View file

@ -2,6 +2,9 @@
#include "Misc/AutomationTest.h"
#include "Components/SceneComponent.h"
#include "Math/UnrealMathUtility.h"
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "HyperTwistXR/HyperTwistXrTrainingPawn.h"
@ -151,6 +154,92 @@ bool FHyperTwistXrTrainingPawnDefaultsTest::RunTest(const FString& Parameters)
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"))
&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("vertical offset 0"))
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistXrTrainingPawnPreferenceHardeningTest,
"HyperTwist.FirstParty.XR.TrainingPawnPreferenceHardening",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistXrTrainingPawnPreferenceHardeningTest::RunTest(
const FString& Parameters
)
{
AHyperTwistXrTrainingPawn* Pawn =
NewObject<AHyperTwistXrTrainingPawn>(GetTransientPackage());
TestNotNull(TEXT("The XR training pawn must be constructible for hardening checks."), Pawn);
if (Pawn == nullptr)
{
return false;
}
FHyperTwistXrPreferenceProfile PreferenceProfile =
UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
PreferenceProfile.MoveSpeedUnitsPerSecond = -25.0f;
PreferenceProfile.VerticalAdjustmentUnitsPerSecond = -5.0f;
PreferenceProfile.SnapTurnDegrees = 0.0f;
PreferenceProfile.SnapTurnAxisThreshold = 2.0f;
PreferenceProfile.SnapTurnResetThreshold = 2.0f;
PreferenceProfile.SmoothTurnDegreesPerSecond = 12.0f;
PreferenceProfile.PointerLengthUnits = 0.0f;
PreferenceProfile.WorldToMetersScale = 0.0f;
PreferenceProfile.ComfortVignetteStrength = 9.0f;
PreferenceProfile.ControllerDeadZone = -0.2f;
PreferenceProfile.PreferenceFieldTags.Reset();
PreferenceProfile.ComfortSurfaceTags.Reset();
PreferenceProfile.ExplicitExclusions.Reset();
PreferenceProfile.PostureMode = EHyperTwistXrPostureMode::Hybrid;
Pawn->ApplyPreferenceProfile(PreferenceProfile);
TestTrue(
TEXT("The XR training pawn must clamp invalid movement and vertical-adjust speeds."),
Pawn->ActivePreferenceProfile.MoveSpeedUnitsPerSecond > 0.0f
&& Pawn->ActivePreferenceProfile.VerticalAdjustmentUnitsPerSecond > 0.0f
&& Pawn->ActivePreferenceProfile.MoveSpeedUnitsPerSecond
!= PreferenceProfile.MoveSpeedUnitsPerSecond
&& Pawn->ActivePreferenceProfile.VerticalAdjustmentUnitsPerSecond
!= PreferenceProfile.VerticalAdjustmentUnitsPerSecond
);
TestTrue(
TEXT("The XR training pawn must clamp turning, pointer, world-scale, comfort, and dead-zone bounds."),
Pawn->ActivePreferenceProfile.SnapTurnDegrees >= 5.0f
&& Pawn->ActivePreferenceProfile.SnapTurnAxisThreshold <= 0.95f
&& Pawn->ActivePreferenceProfile.SnapTurnResetThreshold
< Pawn->ActivePreferenceProfile.SnapTurnAxisThreshold
&& Pawn->ActivePreferenceProfile.SmoothTurnDegreesPerSecond >= 45.0f
&& Pawn->ActivePreferenceProfile.PointerLengthUnits > 0.0f
&& Pawn->ActivePreferenceProfile.WorldToMetersScale > 0.0f
&& Pawn->ActivePreferenceProfile.ComfortVignetteStrength <= 1.0f
&& Pawn->ActivePreferenceProfile.ControllerDeadZone >= 0.0f
);
TestTrue(
TEXT("The XR training pawn must restore default metadata arrays when a custom profile clears them."),
Pawn->ActivePreferenceProfile.PreferenceFieldTags.Num() > 0
&& Pawn->ActivePreferenceProfile.ComfortSurfaceTags.Num() > 0
&& Pawn->ActivePreferenceProfile.ExplicitExclusions.Num() > 0
);
TestTrue(
TEXT("The XR training pawn must apply the sanitized pointer anchor length and hybrid posture offset."),
Pawn->LeftPointerAnchor != nullptr
&& Pawn->RightPointerAnchor != nullptr
&& Pawn->VROrigin != nullptr
&& FMath::IsNearlyEqual(
Pawn->LeftPointerAnchor->GetRelativeLocation().X,
Pawn->ActivePreferenceProfile.PointerLengthUnits
)
&& FMath::IsNearlyEqual(
Pawn->RightPointerAnchor->GetRelativeLocation().X,
Pawn->ActivePreferenceProfile.PointerLengthUnits
)
&& FMath::IsNearlyEqual(
Pawn->VROrigin->GetRelativeLocation().Z,
-20.0f
)
);
return true;
}

View file

@ -46,7 +46,10 @@ Current authoritative host posture:
- `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
roster instead of only broad motion-controller groundwork, and the current
same-family hardening follow-up also adds a bounded dedicated vertical-adjust
axis for desktop-hosted XR posture instead of leaving that owned preference
field inert
- `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
@ -57,7 +60,9 @@ Current authoritative host posture:
- `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
posture switching, desktop fallback pitch handling, recentering, bounded
manual vertical-adjust ownership, sanitized preference application, stale
desktop-fallback rotation clearing on headset activation, and status
reporting
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingImmersiveEnvironmentLibrary.cpp`
keeps the immersive lane explicitly bounded to scenic training ownership
@ -110,6 +115,11 @@ Current authoritative host posture:
with `3` focused XR tests succeeded
- `Saved\AutomationReports\Browser-XrBoundedRuntime-20260701-Final\index.json`
with all `26` `HyperTwist.Browser.*` tests succeeded
- the current same-family hardening continuation then also keeps the dedicated
vertical-adjust axis visible in native control/input inspect truth and keeps
XR preference ownership explicit about vertical-adjust plus pointer-length
control instead of relying on those facts to stay implicit in the preference
struct alone
## Interpretation

View file

@ -336,7 +336,9 @@ truthfully claim:
runtime module dependencies
- `UnrealHyperTwist/Config/DefaultInput.ini` now owns explicit OpenXR-ready
controller axes and actions across the currently bounded controller-family
roster
roster, and the current hardening follow-up also adds a bounded dedicated
vertical-adjust axis for desktop-hosted XR posture instead of leaving the
owned vertical-adjust preference field inert
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h`
and `.../Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp` now define
the first-party runtime-owner, preference-profile, and controller-binding
@ -348,12 +350,19 @@ truthfully claim:
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
posture switching, recentering, bounded manual vertical-adjust ownership,
sanitized preference application, stale-fallback-rotation clearing on HMD
activation, 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 same native control/input inspect surface now also keeps the dedicated
vertical-adjust axis presence explicit in structured state and rendered
operator text, so the current XR preference/profile packet no longer says it
owns vertical adjustment while the corresponding project input seam remains
silent
- the maintained reverse-SSH Windows lane then revalidated that exact-source
state on `C:\HyperTwist_worktrees\phase10validate`:
- final exact-source editor build:

File diff suppressed because one or more lines are too long

View file

@ -165,7 +165,10 @@ statement forward too without overclaiming launch-tier completion:
- `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
anchors, posture switching, recentering, bounded manual vertical-adjust
ownership, sanitized preference application, and desktop fallback pitch
handling that now clears stale fallback rotation when a headset becomes
active
- the maintained Windows validation lane then re-proved the exact-source state
with:
- editor build `Result: Succeeded`, UnrealBuildTool `Total execution time:

View file

@ -1440,7 +1440,7 @@ describe('public marketing pages', () => {
expect(screen.getAllByText(/classic-wca-keyboard\/v1/i).length).toBeGreaterThan(0)
expect(screen.getByText('4. Open the higher-dimensional lanes and read the control boundary honestly')).toBeTruthy()
expect(screen.getByText(/I\/K = R\/R', J\/F = U\/U', H\/G = F\/F'/i)).toBeTruthy()
expect(screen.getByText(/OpenXR plugin disabled/i)).toBeTruthy()
expect(screen.getByText(/bounded OpenXR plugin stack and first-party runtime-owner substrate/i)).toBeTruthy()
expect(screen.getByText(/R scramble, H hint, Enter submit, F mode, V hold-to-talk, C cycle voice/i)).toBeTruthy()
expect(screen.getAllByText(/latest persisted generated-mode selector posture is available for recall/i).length).toBeGreaterThan(0)
expect(screen.getByText('Feature-registry-backed wording only')).toBeTruthy()

View file

@ -604,7 +604,7 @@ export const inputAndDevicePostureCards = [
description: 'EnhancedInput posture and motion-controller groundwork exist, but HyperTwist does not yet market a fully finished OpenXR/controller/rebinding runtime lane.',
bullets: [
'Project config already carries EnhancedInput plus Vive, Oculus Touch, Mixed Reality, and Valve Index axis groundwork.',
'The current shipping branch also keeps the project OpenXR plugin disabled, which is one reason the native XR/controller lane remains an explicit desktop-hosted No-Go today.',
'The current shipping branch now enables the bounded OpenXR plugin stack and first-party runtime-owner substrate, but that still does not collapse the broader native XR/controller lane out of its explicit desktop-hosted No-Go posture.',
'Current product truth should not overclaim headset-specific runtime ownership or polished user-facing input rebinding.',
'The current host decision remains desktop-hosted No-Go on native OpenXR/controller widening; any future reopen would still need dedicated runtime owners, user-facing settings and controller-rebinding ownership, and Windows packaged controller validation with controller truth.',
],