From 3ed296b89398ae3b0333d4ed23184040e72f7902 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Wed, 1 Jul 2026 06:37:08 +0000 Subject: [PATCH] Land bounded OpenXR runtime ownership substrate --- UnrealHyperTwist/Config/DefaultInput.ini | 46 ++ .../HyperTwistTrainingPanelWidget.cpp | 239 ++++++++- .../HyperTwistXrRuntimeLibrary.cpp | 147 ++++++ .../HyperTwistXR/HyperTwistXrTrainingPawn.cpp | 486 ++++++++++++++++++ .../HyperTwistTrainingTypes.h | 90 ++++ .../HyperTwistXR/HyperTwistXrRuntimeLibrary.h | 234 +++++++++ .../HyperTwistXR/HyperTwistXrTrainingPawn.h | 119 +++++ .../HyperTwistBrowserBridgeObjectTest.cpp | 116 ++++- .../Tests/HyperTwistXrRuntimeContractTest.cpp | 158 ++++++ .../UnrealHyperTwist.Build.cs | 2 +- UnrealHyperTwist/UnrealHyperTwist.uproject | 8 + ...T_AND_PLUGIN_DECISION_PACKET_2026-06-24.md | 84 ++- ...UT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md | 70 ++- ...ME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md | 46 +- .../HyperTwist/DEVELOPMENT.md | 50 ++ .../HyperTwist/FEATURE_REGISTRY.md | 1 + docs/v6_5_deep_manual_pack/HyperTwist/PRD.md | 23 + website/deploy/README.md | 3 + 18 files changed, 1842 insertions(+), 80 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistXrRuntimeContractTest.cpp diff --git a/UnrealHyperTwist/Config/DefaultInput.ini b/UnrealHyperTwist/Config/DefaultInput.ini index a919105..cc0315f 100644 --- a/UnrealHyperTwist/Config/DefaultInput.ini +++ b/UnrealHyperTwist/Config/DefaultInput.ini @@ -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) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp index e1c6f46..284947e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingPanelWidget.cpp @@ -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 ); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp new file mode 100644 index 0000000..d21e5a7 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrRuntimeLibrary.cpp @@ -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; +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp new file mode 100644 index 0000000..a84b26b --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistXR/HyperTwistXrTrainingPawn.cpp @@ -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(TEXT("SceneRoot")); + SetRootComponent(SceneRoot); + + VROrigin = CreateDefaultSubobject(TEXT("VROrigin")); + VROrigin->SetupAttachment(SceneRoot); + + CameraComponent = CreateDefaultSubobject(TEXT("CameraComponent")); + CameraComponent->SetupAttachment(VROrigin); + + LeftController = CreateDefaultSubobject(TEXT("LeftController")); + LeftController->SetupAttachment(VROrigin); + LeftController->SetTrackingMotionSource(TEXT("Left")); + + RightController = CreateDefaultSubobject(TEXT("RightController")); + RightController->SetupAttachment(VROrigin); + RightController->SetTrackingMotionSource(TEXT("Right")); + + LeftPointerAnchor = CreateDefaultSubobject(TEXT("LeftPointerAnchor")); + LeftPointerAnchor->SetupAttachment(LeftController); + + RightPointerAnchor = CreateDefaultSubobject(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; + } +} diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 1b34481..28fe2ce 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -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; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h new file mode 100644 index 0000000..e214257 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrRuntimeLibrary.h @@ -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 PreferenceFieldTags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ComfortSurfaceTags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray 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 RequiredAxisNames; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ControllerFamilyIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray InteractionSurfaceTags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray 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 RequiredPackageIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray RequiredSurfaceTags; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray SupportedControllerFamilies; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray 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(); +}; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h new file mode 100644 index 0000000..124da96 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistXR/HyperTwistXrTrainingPawn.h @@ -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 SceneRoot = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr VROrigin = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr CameraComponent = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr LeftController = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr RightController = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr LeftPointerAnchor = nullptr; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "HyperTwist|XR|Runtime") + TObjectPtr 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; +}; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp index 655716b..bd3f6d1 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistBrowserBridgeObjectTest.cpp @@ -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."), diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistXrRuntimeContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistXrRuntimeContractTest.cpp new file mode 100644 index 0000000..59f9567 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistXrRuntimeContractTest.cpp @@ -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(); + 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 diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.Build.cs b/UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.Build.cs index 542f22e..e666c11 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.Build.cs +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/UnrealHyperTwist.Build.cs @@ -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" } diff --git a/UnrealHyperTwist/UnrealHyperTwist.uproject b/UnrealHyperTwist/UnrealHyperTwist.uproject index 20effe9..a56ff69 100644 --- a/UnrealHyperTwist/UnrealHyperTwist.uproject +++ b/UnrealHyperTwist/UnrealHyperTwist.uproject @@ -58,6 +58,14 @@ "Name": "WebBrowserWidget", "Enabled": true }, + { + "Name": "XRBase", + "Enabled": true + }, + { + "Name": "OpenXR", + "Enabled": true + }, { "Name": "MovieRenderPipeline", "Enabled": true, diff --git a/docs/ops/HYPERTWIST_NATIVE_XR_HOST_AND_PLUGIN_DECISION_PACKET_2026-06-24.md b/docs/ops/HYPERTWIST_NATIVE_XR_HOST_AND_PLUGIN_DECISION_PACKET_2026-06-24.md index 8b3504c..e6fd9fc 100644 --- a/docs/ops/HYPERTWIST_NATIVE_XR_HOST_AND_PLUGIN_DECISION_PACKET_2026-06-24.md +++ b/docs/ops/HYPERTWIST_NATIVE_XR_HOST_AND_PLUGIN_DECISION_PACKET_2026-06-24.md @@ -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. diff --git a/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md index 00a5562..e363151 100644 --- a/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md +++ b/docs/ops/HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md @@ -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 diff --git a/docs/ops/HYPERTWIST_WEBSITE_SAME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md b/docs/ops/HYPERTWIST_WEBSITE_SAME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md index 43afb47..116da87 100644 --- a/docs/ops/HYPERTWIST_WEBSITE_SAME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md +++ b/docs/ops/HYPERTWIST_WEBSITE_SAME_ORIGIN_DEPLOYMENT_HANDOFF_2026-06-22.md @@ -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 --identity-file ` + 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 diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md index 86bc69a..23192b1 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/DEVELOPMENT.md @@ -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 diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 40fcf81..3f3c707 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -145,6 +145,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Rewritten training analytics and report reference grounding | Implemented now | `apache/echarts` retained permissive lane + first-party current code | Current live `Training Analytics` reference side includes four rewritten first-party targets grounded in retained `apache/echarts`: session outcome and progress reporting, analytics data-view and export, timing-trend history and overview interaction, and the optional richer explainer or sidecar boundary. This does not displace the landed `Phase 3R-C` first-party analytics/reporting owner or elevate `ecomfe/echarts-gl` and `ecomfe/zrender` beyond support-only sidecars. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, and the current Phase 1 browser-runtime landing now includes a first-party authoritative `Content/Browser/index.html` shell, bundled-runtime upgrade path, committed clean-checkout plain-JS fallback runtime, embedded `UHyperTwistBrowserWidget` bridge, the `2026-06-19` runtime-ready queue hardening that retains outbound Unreal shell traffic until the browser runtime is ready, a same-day first-party `Browser Runtime Status` surface with shared boot-state ownership across bundled and fallback shells, a follow-on typed Unreal-side `browser-runtime-status` capture seam that retains the last valid runtime snapshot across unrelated later envelope traffic until explicit reset, a second follow-on typed Unreal-side `hypertwist-runtime-ready` capture seam that raises the original handshake payload out of raw JSON-only handling without changing queue flush behavior, a third same-day native/operator-facing status surface that consumes those typed seams inside Unreal while clearing retained runtime ownership on shell-authority change, a fourth same-day native training/operator diagnostics-panel continuation that wires that typed status ownership into `UHyperTwistTrainingPanelWidget` and the coach dashboard without making the dashboard inspect seam depend on stale rendered strings, a fifth same-day diagnostics-fidelity continuation that now carries bootstrap/runtime-ready timestamps, last command/shell-state receipt timestamps, and fallback reason through the same native operator/training seams, and a later `2026-06-23` control/input readiness continuation that projects shipped keyboard/input truth and unfinished XR/preferences truth through the same native training/operator surfaces. This still is not proof of unlimited browser-shell parity. | +| Bounded native OpenXR runtime-owner substrate | Implemented now | landed first-party `2026-07-01` bounded XR continuation | The desktop-hosted branch now enables `OpenXR` plus `XRBase`, ships first-party runtime-owner/profile ownership through `UHyperTwistXrRuntimeLibrary` and `AHyperTwistXrTrainingPawn`, and keeps the stable ids `xr/openxr-desktop-training-runtime-owner`, `xr-openxr-training-preferences/v1`, and `xr-openxr-motion-controllers/v1` visible through both native XR code and the operator/training inspect surfaces. The maintained reverse-SSH Windows lane revalidated the exact-source state on `C:\HyperTwist_worktrees\phase10validate` with final editor build `Result: Succeeded` at UnrealBuildTool `124.26 seconds`, focused XR automation report `XR-BoundedRuntimeOwner-20260701-Final` with `3/3` green, and full browser automation report `Browser-XrBoundedRuntime-20260701-Final` with all `26` `HyperTwist.Browser.*` tests green. Broader controller widening, polished rebinding/settings ownership, and packaged headset/controller proof remain explicitly unfinished. | | Native control/input readiness inspect surface | Implemented now | landed first-party `2026-06-23` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlInputReadinessInspectSurface`, including the shipped `classic-wca-keyboard/v1` mapping, exact classic-cube pointer, orbit, zoom, and action-shortcut truth, higher-dimensional dedicated-family runtime ownership truth, project-level `EnhancedInput` plus motion-controller groundwork facts, immersive-presence contract presence, and explicit unfinished XR/runtime plus preferences truth. The rendered detail line now also carries the fixed desktop-hosted `No-Go` decision on native OpenXR/controller widening instead of leaving that truth stranded in a non-rendered follow-up field. A later same-day structured-boundary hardening follow-up then promoted that same XR/controller boundary into stable inspect-surface fields as well, including decision id `desktop-hosted-openxr-controller-widening-no-go` plus explicit reopen requirements for dedicated runtime owners, user-facing settings or rebinding ownership, and Windows packaged controller validation, so native operator automation no longer depends only on rendered prose to verify the boundary. The recovered primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-23` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 1563.53 seconds`, and exported `Saved\AutomationReports\Browser-ControlInputReadiness-Verify\index.json` with all `16` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlInputReadinessInspectSurface` and `TrainingPanel.ControlInputReadinessInspectSurface`. A later same-day quality follow-up then moved the project-input-groundwork inspection behind that surface from raw text scanning into structured Unreal config reads, rebuilt the same maintained validation worktree again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 158.13 seconds`, and re-exported focused browser proof to `Saved\AutomationReports\Browser-ControlInputReadiness-StructuredConfig-Verify` with all `16` `HyperTwist.Browser.*` tests green again. The latest same-family truth-render follow-up on `2026-06-24` then rebuilt that same maintained validation root with `Result: Succeeded`, UnrealBuildTool `Total execution time: 172.52 seconds`, and exported `Saved\AutomationReports\Browser-XrNoGoRendered-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, explicitly covering both training-panel and coach-dashboard control-input detail-line visibility for the `No-Go` host decision. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control-input inspect surfaces alongside the sibling control/settings and control/profile seams. A later same-day `2026-06-25` parity follow-up then widened the same surface from general readiness truth into literal roster truth by rendering dedicated classic-cube pointer and action-shortcut lines, rebuilding the maintained validation root with `Result: Succeeded` at `5278.57 seconds` and exact-source rerun `70.76 seconds`, and exporting `Saved\AutomationReports\Browser-ControlRosterParity-Verify\index.json` with all `21` `HyperTwist.Browser.*` tests green, including both training-panel and coach-dashboard control-input surfaces plus the structured dashboard artifact seam. The latest same-family `2026-06-28` ownership-truth follow-up then tightened higher-dimensional readiness again by resolving the shipped `Magic120Cell` and `MagicCube5D` dedicated-family host, view-context, session, and interactive-scene surfaces explicitly by activation profile id instead of treating broad catalog validity as equivalent proof. That exact-source-state follow-up rebuilt maintained validation root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 109.14 seconds`, then passed all three focused browser filters `TrainingPanel.ControlInputReadinessInspectSurface`, `CoachDashboard.ControlInputReadinessInspectSurface`, and `CoachDashboard.ControlSurfaceStructuredTextArtifacts`, so both the inspect surfaces and the rendered structured dashboard row now keep concrete family ownership ids such as `phase6c/magic120cell/runtime-host-surface` and `phase6c/magiccube5d/interactive-scene-surface` visible to operators. A later same-day bounded preferences-continuity follow-up then tightened the non-`XR` seam again without reopening the controller lane: the preferences line now resolves the first valid viewer camera-export artifact instead of trusting only the first listed id, keeps `artifact/camera-export-json` plus `immersive-training-session-recall-boundary` visible with recall-scope and preference-field counts, and preserves the honest boundary that broader polished rebinding and control-settings UI are still not shipped. That exact-source continuity follow-up then rebuilt maintained validation root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3372.27 seconds`, and exported `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.TrainingPanel.ControlInputReadinessInspectSurface`, `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.CoachDashboard.ControlInputReadinessInspectSurface`, and `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.CoachDashboard.ControlSurfaceStructuredTextArtifacts`, each with `index.json` `State=Success`, while the accepted unattended `Failed to create the web browser window.` dialog remained a non-blocking Windows editor lane artifact. | | Native control/settings ownership inspect surface | Implemented now | landed first-party `2026-06-24` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlSettingsOwnershipInspectSurface`, including the shipped viewer camera-settings owner `tool/camera-settings`, the immersive-presence control contract, dedicated-family `magic120cell-focus-view-profile` and `magiccube5d-projection-view-profile` view ownership, higher-dimensional selector ownership, persisted generated-mode selector recall when a structurally valid launch request is present, and explicit unfinished XR/controller rebinding truth. A later same-day structured-boundary hardening follow-up then carried the same `desktop-hosted-openxr-controller-widening-no-go` decision id plus the explicit reopen requirements for dedicated runtime owners, polished user-facing settings or rebinding ownership, and Windows packaged controller validation into this surface’s structured state as well, so control-settings diagnostics no longer have to prove the boundary only by substring matching their rendered detail line. The same primary reverse-SSH `localhost:22022` lane rebuilt maintained validation worktree `C:\HyperTwist_worktrees\phase10validate` on `2026-06-24` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4331.11 seconds`, and then exported `Saved\AutomationReports\Browser-ControlSettingsOwnership-Verify\index.json` with all `18` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlSettingsOwnershipInspectSurface`, `TrainingPanel.ControlSettingsOwnershipInspectSurface`, and the previously landed runtime plus control/input inspect seams. A later same-day selector-recall hardening follow-up then rebuilt that same maintained validation root again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4084.77 seconds`, and exported `Saved\AutomationReports\Browser-SelectorRecall-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing, keeping the current control/settings proof aligned with the latest repository-backed selector-recall behavior. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control/settings inspect surfaces alongside the sibling control/input and control/profile seams. The latest same-family `2026-06-28` ownership-proof follow-up then tightened this settings seam again by resolving the active higher-dimensional session surface, active interactive-scene surface, dedicated-family session surface, dedicated-family interactive-scene surface, persistence-boundary id, and state-semantics id explicitly for both `Magic120Cell` and `MagicCube5D`, while also carrying concrete projection, symmetry, stereo, visibility, and focus tag counts plus persistence-readiness booleans in structured state instead of leaving that ownership implied by broad view-profile validity. That exact-source proof-hardening follow-up synced the tightened browser test source into the already-current maintained validation root, rebuilt it with `Result: Succeeded` and UnrealBuildTool `Total execution time: 168.35 seconds`, then passed focused browser proof for `HyperTwist.Browser.TrainingPanel.ControlSettingsOwnershipInspectSurface`, `HyperTwist.Browser.CoachDashboard.ControlSettingsOwnershipInspectSurface`, and `HyperTwist.Browser.CoachDashboard.ControlSurfaceStructuredTextArtifacts`, so both the inspect surfaces and the rendered structured dashboard row now keep exact family-owned ids such as `phase6c/magic120cell/dedicated-training-session-surface`, `phase6c/magic120cell/interactive-scene-surface`, `magic120cell-persistence-boundary`, `phase6c/magiccube5d/dedicated-training-session-surface`, and `phase6c/magiccube5d/family-owned-scene-state` visible to operators. A later same-day bounded preferences-continuity follow-up then tightened the same settings seam again without reopening native controller widening: the structured state now carries camera workflow, preview-state, export-artifact, immersive presence-surface, recall-scope, preference-field, and reset-surface counts together with explicit camera-continuity and immersive-session-recall readiness booleans; the rendered status text now distinguishes `missing`, `partial`, and `ready` ownership truth instead of collapsing those states; and the shipped `artifact/camera-export-json` plus `immersive-training-session-recall-boundary` ids now stay visible in both the training-panel and coach-dashboard settings detail lines. That exact-source continuity follow-up then rebuilt maintained validation root `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3372.27 seconds`, and exported `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.TrainingPanel.ControlSettingsOwnershipInspectSurface`, `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.CoachDashboard.ControlSettingsOwnershipInspectSurface`, and `Browser-PreferencesContinuity-20260628-HyperTwist.Browser.CoachDashboard.ControlSurfaceStructuredTextArtifacts`, each with `index.json` `State=Success`, while the same accepted unattended `Failed to create the web browser window.` dialog remained non-blocking on the Windows editor lane. A later same-family `2026-06-30` degraded-state hardening follow-up then pulled that same readiness doctrine fully into the shared formatter layer itself, so absent higher-dimensional family ownership now renders as `missing` instead of being overstated as generic `partial`, one-sided view-versus-persistence lanes still render as `partial`, and both the dedicated family lines and the selector-ownership line now mask stale ids/counts while preserving the side of the lane that is actually ready. | | Native control/profile roster inspect surface | Implemented now | landed first-party `2026-06-24` browser/native operator continuation | `UHyperTwistTrainingPanelWidget` and `UHyperTwistCoachDashboardWidget` now expose `FHyperTwistTrainingControlProfileRosterInspectSurface`, keeping the actually shipped selectable native roster visible: classic keyboard profile `classic-wca-keyboard/v1` with `18` bindings plus the exact classic move roster, immersive-presence contract `immersive-training-presence-control-contract` with `3` intensity plus `3` reduced-distraction presets, dedicated-family `Magic120Cell` runtime/view ids `magic120cell-runtime-profile` and `magic120cell-focus-view-profile`, dedicated-family `MagicCube5D` runtime/view ids `magiccube5d-runtime-profile` and `magiccube5d-projection-view-profile`, the current `4` selectors in each dedicated-family roster, persisted generated-mode selector recall when a structurally valid launch request is present, and explicit unfinished controller rebinding truth. The rendered roster boundary now also states the fixed desktop-hosted `No-Go` decision on native OpenXR/controller widening instead of sounding like a merely pending generic settings packet. A later same-day structured-boundary hardening follow-up then carried the same `desktop-hosted-openxr-controller-widening-no-go` decision id plus its explicit reopen requirements into the roster surface’s structured state too, so native operator automation can prove the shipped roster boundary without depending only on rendered text. After hash-syncing the touched type/training/dashboard/test files into maintained validation worktree `C:\HyperTwist_worktrees\phase10validate`, the same primary reverse-SSH `localhost:22022` lane rebuilt the widened slice on `2026-06-24` with `Result: Succeeded`, UnrealBuildTool `Total execution time: 3606.23 seconds`, then exported `Saved\AutomationReports\Browser-ControlProfileRoster-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing, including `CoachDashboard.ControlProfileRosterInspectSurface` and `TrainingPanel.ControlProfileRosterInspectSurface`. A later same-day selector-recall hardening follow-up then rebuilt that same maintained validation root again with `Result: Succeeded`, UnrealBuildTool `Total execution time: 4084.77 seconds`, and exported `Saved\AutomationReports\Browser-SelectorRecall-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests passing again, now explicitly covering imported-request fallback plus active-deck precedence for repository-backed selector recall inside the training-panel and coach-dashboard roster surface. The latest same-family truth-render follow-up on `2026-06-24` then rebuilt that same maintained validation root with `Result: Succeeded`, UnrealBuildTool `Total execution time: 172.52 seconds`, and exported `Saved\AutomationReports\Browser-XrNoGoRendered-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, explicitly covering the roster detail-line visibility of the same fixed `No-Go` boundary. A later same-day post-build structured-boundary follow-up then rebuilt that same maintained validation root once more with `Result: Succeeded`, UnrealBuildTool `Total execution time: 2323.18 seconds`, and exported `Saved\AutomationReports\Browser-XrBoundaryStructured-PostBuild-Verify\index.json` with all `20` `HyperTwist.Browser.*` tests green again, now explicitly covering the stable decision-id and reopen-requirement fields on both control/profile inspect surfaces alongside the sibling control/input and control/settings seams. A later same-day `2026-06-25` parity follow-up then added the literal shipped classic move-roster line to that surface, mirrored it into dedicated coach-dashboard structured rows, rebuilt the maintained validation root with `Result: Succeeded` at `5278.57 seconds` and exact-source rerun `70.76 seconds`, and exported `Saved\AutomationReports\Browser-ControlRosterParity-Verify\index.json` with all `21` `HyperTwist.Browser.*` tests green, including both training-panel and coach-dashboard control/profile surfaces plus the structured dashboard artifact seam. The later same-family `2026-06-28` continuity follow-up then widened that roster seam again by carrying the active higher-dimensional scene id alongside the active activation, view-context, and session ids, while also resolving the first valid viewer camera-export artifact and keeping `artifact/camera-export-json` plus `immersive-training-session-recall-boundary` visible with recall-scope and preference-field counts through both the native detail line and a dedicated coach-dashboard structured row. The authoritative short-root recovery lane on `C:\HTpp` then re-synced the exact touched Unreal files, rebuilt the exact-source state with `Result: Succeeded` and UnrealBuildTool `Total execution time: 2374.44 seconds`, and exported both `Browser-ControlProfileContinuityParity-20260628-HyperTwist.Browser.TrainingPanel.ControlProfileContinuityParity` and `Browser-ControlProfileContinuityParity-20260628-HyperTwist.Browser.CoachDashboard.ControlProfileContinuityArtifacts` with `Result={Success}`, while the repeated `Failed to create the web browser window.` dialog remained a non-blocking unattended editor artifact. A later exact-source short-root recovery proof on `2026-06-29` then rebuilt the same touched state again with `Result: Succeeded` and UnrealBuildTool `Total execution time: 78.51 seconds`, re-exported `Saved\AutomationReports\ControlProfileContinuity-Rerun` with `HyperTwist.Browser.CoachDashboard.ControlProfileContinuityArtifacts`, `HyperTwist.Browser.TrainingPanel.ControlProfileContinuityParity`, and `HyperTwist.Browser.ControlProfileContinuityStateFormatting` all `Result={Success}`, and also re-exported `Saved\AutomationReports\ContinuityStateFormatting-Rerun` with the wider shared continuity-formatting trio green, so the roster-parity and shared continuity-helper seams are both grounded in current exact-source Windows proof rather than only the earlier broader maintained-root evidence. A final maintained-root exact-source hardening follow-up later that same day then tightened the shared continuity formatter so absent readiness masks stale ids and counts instead of leaking carried-over values, rebuilt `C:\HyperTwist_worktrees\phase10validate` with `Result: Succeeded` and UnrealBuildTool `Total execution time: 3325.07 seconds`, and exported `Continuity-CountMasking-20260629-*` with `HyperTwist.Browser.ControlProfileContinuityStateFormatting`, `HyperTwist.Browser.TrainingPanel.ControlProfileContinuityParity`, and `HyperTwist.Browser.CoachDashboard.ControlProfileContinuityArtifacts` all `Result={Success}`, keeping the shipped roster-continuity truth grounded in current maintained-root degraded-state proof as well. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md index 0a0e927..4aac24b 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md @@ -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` diff --git a/website/deploy/README.md b/website/deploy/README.md index 8da7a21..589a2f2 100644 --- a/website/deploy/README.md +++ b/website/deploy/README.md @@ -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`