Harden validation, tooling, and public product surfaces
This commit is contained in:
parent
9c93020c69
commit
a1d40d1232
29 changed files with 7020 additions and 5015 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,6 +13,8 @@ website/dist/
|
||||||
website/node_modules/
|
website/node_modules/
|
||||||
website/server/node_modules/
|
website/server/node_modules/
|
||||||
website/server/data/
|
website/server/data/
|
||||||
|
tools/sentrux/bin/*
|
||||||
|
!tools/sentrux/bin/.gitkeep
|
||||||
|
|
||||||
# Sensitive or reference-only material.
|
# Sensitive or reference-only material.
|
||||||
docs/refs/
|
docs/refs/
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,968 @@
|
||||||
|
#include "HyperTwistRecognition/HyperTwistRecognitionTypes.h"
|
||||||
|
|
||||||
|
namespace HyperTwistRecognitionTypesInternal
|
||||||
|
{
|
||||||
|
bool AreAllStringsPopulated(const TArray<FString>& Values)
|
||||||
|
{
|
||||||
|
for (const FString& Value : Values)
|
||||||
|
{
|
||||||
|
if (Value.IsEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ItemType>
|
||||||
|
bool AreAllItemsStructurallyValid(const TArray<ItemType>& Items)
|
||||||
|
{
|
||||||
|
for (const ItemType& Item : Items)
|
||||||
|
{
|
||||||
|
if (!Item.IsStructurallyValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteShellProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !NativeCaptureRouteShellProfileId.IsEmpty()
|
||||||
|
&& !ShellKind.IsEmpty()
|
||||||
|
&& !OwnershipSummarySurfaceMode.IsEmpty()
|
||||||
|
&& !PreparationSurfaceMode.IsEmpty()
|
||||||
|
&& !SessionReopenSurfaceMode.IsEmpty()
|
||||||
|
&& !RouteInspectActionId.IsEmpty()
|
||||||
|
&& !PreparationRetryActionId.IsEmpty()
|
||||||
|
&& !SessionReopenActionId.IsEmpty()
|
||||||
|
&& !PermissionDependencyActionId.IsEmpty()
|
||||||
|
&& Panels.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (NativeCaptureRouteShellProfileId.IsEmpty()
|
||||||
|
|| NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| WorkflowStateId.IsEmpty()
|
||||||
|
|| CaptureRouteStateId.IsEmpty()
|
||||||
|
|| PermissionStateId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActiveIssueCount != Issues.Num())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechUsageCostDashboardShellProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !DashboardShellProfileId.IsEmpty()
|
||||||
|
&& !ShellKind.IsEmpty()
|
||||||
|
&& !UsageSurfaceMode.IsEmpty()
|
||||||
|
&& !CostSurfaceMode.IsEmpty()
|
||||||
|
&& !EscalationBannerMode.IsEmpty()
|
||||||
|
&& !RefreshActionId.IsEmpty()
|
||||||
|
&& !RouteInspectActionId.IsEmpty()
|
||||||
|
&& !BudgetInspectActionId.IsEmpty()
|
||||||
|
&& Panels.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechUsageCostDashboardShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (DashboardShellProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| ActiveRouteStateId.IsEmpty()
|
||||||
|
|| UsageAggregationWindowId.IsEmpty()
|
||||||
|
|| CostAggregationWindowId.IsEmpty()
|
||||||
|
|| UsageMeterKind.IsEmpty()
|
||||||
|
|| CurrencyCode.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| LatestUsageEventId.IsEmpty()
|
||||||
|
|| LatestCostEventId.IsEmpty()
|
||||||
|
|| LatestQuotaStateId.IsEmpty()
|
||||||
|
|| LatestRouteDecisionId.IsEmpty()
|
||||||
|
|| DisplayedUsageQuantity < 0.0f
|
||||||
|
|| DisplayedEstimatedCostUsd < 0.0f
|
||||||
|
|| DisplayedReportedCostUsd < 0.0f
|
||||||
|
|| RemainingEstimatedSpendUsd < 0.0f
|
||||||
|
|| RemainingRequestCount < 0
|
||||||
|
|| RemainingAudioSeconds < 0
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ActiveIssueCount != Issues.Num())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVisionShellProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !ShellProfileId.IsEmpty()
|
||||||
|
&& !ShellKind.IsEmpty()
|
||||||
|
&& !DefaultLocaleCode.IsEmpty()
|
||||||
|
&& !FontReviewProfileId.IsEmpty()
|
||||||
|
&& FontReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
&& SupportedFontReviews.Num() > 0
|
||||||
|
&& SupportedLocales.Num() > 0
|
||||||
|
&& Panels.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedLocales)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedFontReviews)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVisionSolveExplanationProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !ExplanationProfileId.IsEmpty()
|
||||||
|
&& !PuzzleId.IsEmpty()
|
||||||
|
&& !RecommendationMode.IsEmpty()
|
||||||
|
&& !LocaleGuidanceMode.IsEmpty()
|
||||||
|
&& !DefaultLocaleCode.IsEmpty()
|
||||||
|
&& !StartingOrientationHint.IsEmpty()
|
||||||
|
&& !FontReviewProfileId.IsEmpty()
|
||||||
|
&& FontReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
&& SupportedFontReviews.Num() > 0
|
||||||
|
&& SupportedLocales.Num() > 0
|
||||||
|
&& Steps.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedLocales)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedFontReviews)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Steps)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVisionCorrectionState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (CorrectionProfileId.IsEmpty()
|
||||||
|
|| MissingFaceCount < 0
|
||||||
|
|| ContradictionCount < 0
|
||||||
|
|| ContradictionCount != Contradictions.Num()
|
||||||
|
|| ResolvedCorrectionCount < 0
|
||||||
|
|| ResolvedCorrectionCount != ResolutionLedger.Num())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bCorrectionRequired
|
||||||
|
&& (ActiveTargetFaceId.IsEmpty()
|
||||||
|
|| !ActiveTarget.IsStructurallyValid()
|
||||||
|
|| ActiveTargetFaceId != ActiveTarget.FaceId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(PendingTargets)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Contradictions)
|
||||||
|
&& (LastResolution.ResolutionId.IsEmpty() || LastResolution.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ResolutionLedger);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechProviderRoutingPolicy::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !ProviderRoutingPolicyId.IsEmpty()
|
||||||
|
&& !PolicyKind.IsEmpty()
|
||||||
|
&& !RouteSelectionMode.IsEmpty()
|
||||||
|
&& !FallbackMode.IsEmpty()
|
||||||
|
&& !WorkflowPolicyState.IsEmpty()
|
||||||
|
&& !FailureEscalationMode.IsEmpty()
|
||||||
|
&& !UserOverridePosture.IsEmpty()
|
||||||
|
&& EligibleProviderClasses.Num() > 0
|
||||||
|
&& EligibleEndpointClasses.Num() > 0
|
||||||
|
&& RequiredCapabilityFlags.Num() > 0
|
||||||
|
&& SupportedTaskKinds.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(EligibleProviderClasses)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(EligibleEndpointClasses)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(RequiredCapabilityFlags)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedTaskKinds);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace HyperTwistRecognitionTypeValidation
|
||||||
|
{
|
||||||
|
template <typename TStruct>
|
||||||
|
bool AreStructurallyValidEntries(const TArray<TStruct>& Values)
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreNonEmptyStrings(const TArray<FString>& Values)
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreSpeechModelPayloadsValid(const TArray<FHyperTwistSpeechModelPayloadDescriptor>& Payloads)
|
||||||
|
{
|
||||||
|
return AreStructurallyValidEntries(Payloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidSpeechSessionCore(const FHyperTwistSpeechSessionConfig& Session)
|
||||||
|
{
|
||||||
|
return !Session.SessionId.IsEmpty()
|
||||||
|
&& !Session.ListeningContractId.IsEmpty()
|
||||||
|
&& !Session.InputRouteId.IsEmpty()
|
||||||
|
&& !Session.AudioEncoding.IsEmpty()
|
||||||
|
&& Session.SampleRateHz > 0
|
||||||
|
&& Session.ChannelCount > 0
|
||||||
|
&& !Session.TaskKind.IsEmpty()
|
||||||
|
&& Session.RequiredModelPayloads.Num() > 0
|
||||||
|
&& Session.VadPolicy.IsStructurallyValid()
|
||||||
|
&& Session.OrchestrationProfile.IsStructurallyValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidSpeechSessionProfiles(const FHyperTwistSpeechSessionConfig& Session)
|
||||||
|
{
|
||||||
|
return !Session.MicrophoneShellProfileId.IsEmpty()
|
||||||
|
&& Session.MicrophoneShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.DevicePermissionWorkflowProfileId.IsEmpty()
|
||||||
|
&& Session.DevicePermissionWorkflowProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||||
|
&& Session.NativeCaptureRouteWorkflowProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.NativeCaptureRouteShellProfileId.IsEmpty()
|
||||||
|
&& Session.NativeCaptureRouteShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ExternalDictationShellProfileId.IsEmpty()
|
||||||
|
&& Session.ExternalDictationShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ProviderProfileId.IsEmpty()
|
||||||
|
&& Session.ProviderProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ByokCustodyProfileId.IsEmpty()
|
||||||
|
&& Session.ByokCustodyProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ProviderRoutingPolicyId.IsEmpty()
|
||||||
|
&& Session.ProviderRoutingPolicyDefinition.IsStructurallyValid()
|
||||||
|
&& Session.ProviderRouteDecision.IsStructurallyValid()
|
||||||
|
&& !Session.UsageCostAccountingProfileId.IsEmpty()
|
||||||
|
&& Session.UsageCostAccountingProfileDefinition.IsStructurallyValid()
|
||||||
|
&& Session.UsageEventTemplate.IsStructurallyValid()
|
||||||
|
&& Session.CostEventTemplate.IsStructurallyValid()
|
||||||
|
&& !Session.UsageCostDashboardShellProfileId.IsEmpty()
|
||||||
|
&& Session.UsageCostDashboardShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.UsageCostHistoryExportShellProfileId.IsEmpty()
|
||||||
|
&& Session.UsageCostHistoryExportShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ProviderReceiptReviewShellProfileId.IsEmpty()
|
||||||
|
&& Session.ProviderReceiptReviewShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ProviderBillingSettlementShellProfileId.IsEmpty()
|
||||||
|
&& Session.ProviderBillingSettlementShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.ProviderSettlementExceptionShellProfileId.IsEmpty()
|
||||||
|
&& Session.ProviderSettlementExceptionShellProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Session.PayloadCustodyProfileId.IsEmpty()
|
||||||
|
&& Session.PayloadCustodyProfileDefinition.IsStructurallyValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidTranscriptCore(const FHyperTwistSpeechTranscriptResult& Transcript)
|
||||||
|
{
|
||||||
|
return !Transcript.SessionId.IsEmpty()
|
||||||
|
&& !Transcript.UtteranceId.IsEmpty()
|
||||||
|
&& !Transcript.TaskKind.IsEmpty()
|
||||||
|
&& (!Transcript.TranscriptText.IsEmpty() || Transcript.Segments.Num() > 0)
|
||||||
|
&& !Transcript.OrchestrationProfileId.IsEmpty()
|
||||||
|
&& !Transcript.ServiceLaneId.IsEmpty()
|
||||||
|
&& Transcript.RequestedBatchSize > 0
|
||||||
|
&& Transcript.ProcessedClipCount > 0
|
||||||
|
&& Transcript.AppliedBatchCollectionWindowMs > 0
|
||||||
|
&& Transcript.LanguageProbability >= 0.0f
|
||||||
|
&& !Transcript.AppliedPromptRoutingModeId.IsEmpty()
|
||||||
|
&& !Transcript.AppliedRetrievalContextLaneId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AreTranscriptSegmentsValid(const TArray<FHyperTwistSpeechTranscriptSegment>& Segments)
|
||||||
|
{
|
||||||
|
return AreStructurallyValidEntries(Segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidSpeechServiceHealthCore(const FHyperTwistSpeechServiceHealth& Health)
|
||||||
|
{
|
||||||
|
return !Health.ProviderLabel.IsEmpty()
|
||||||
|
&& !Health.ServiceVersion.IsEmpty()
|
||||||
|
&& !Health.ProviderProfileId.IsEmpty()
|
||||||
|
&& Health.ProviderProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Health.ByokCustodyProfileId.IsEmpty()
|
||||||
|
&& Health.ByokCustodyProfileDefinition.IsStructurallyValid()
|
||||||
|
&& !Health.ProviderRoutingPolicyId.IsEmpty()
|
||||||
|
&& Health.ProviderRoutingPolicyDefinition.IsStructurallyValid()
|
||||||
|
&& Health.ProviderRouteDecision.IsStructurallyValid()
|
||||||
|
&& !Health.UsageCostAccountingProfileId.IsEmpty()
|
||||||
|
&& Health.UsageCostAccountingProfileDefinition.IsStructurallyValid()
|
||||||
|
&& Health.LatestUsageEvent.IsStructurallyValid()
|
||||||
|
&& Health.LatestCostEvent.IsStructurallyValid()
|
||||||
|
&& Health.QuotaRateLimitState.IsStructurallyValid()
|
||||||
|
&& !Health.PayloadCustodyProfileId.IsEmpty()
|
||||||
|
&& Health.PayloadCustodyProfileDefinition.IsStructurallyValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidExternalDictationShellProfileCore(
|
||||||
|
const FHyperTwistSpeechExternalDictationShellProfile& Profile)
|
||||||
|
{
|
||||||
|
return !Profile.ExternalDictationShellProfileId.IsEmpty()
|
||||||
|
&& !Profile.ShellKind.IsEmpty()
|
||||||
|
&& !Profile.TranscriptHistorySurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.OutputRoutingSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.PostProcessOverlaySurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.GlobalHotkeySurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.InputDeviceSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.OutputDeviceSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.MuteSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.MicrophoneModeSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.LocalModelCatalogSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.ModelIntegritySurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.ModelUnloadSurfaceMode.IsEmpty()
|
||||||
|
&& !Profile.StartCaptureActionId.IsEmpty()
|
||||||
|
&& !Profile.CancelCaptureActionId.IsEmpty()
|
||||||
|
&& !Profile.CycleOutputRouteActionId.IsEmpty()
|
||||||
|
&& !Profile.CopyTranscriptActionId.IsEmpty()
|
||||||
|
&& !Profile.PasteTranscriptActionId.IsEmpty()
|
||||||
|
&& !Profile.ScriptDispatchActionId.IsEmpty()
|
||||||
|
&& !Profile.TogglePostProcessOverlayActionId.IsEmpty()
|
||||||
|
&& !Profile.ReopenSpeechSessionActionId.IsEmpty()
|
||||||
|
&& !Profile.RouteInspectActionId.IsEmpty()
|
||||||
|
&& !Profile.InspectGlobalHotkeyActionId.IsEmpty()
|
||||||
|
&& !Profile.CycleInputDeviceActionId.IsEmpty()
|
||||||
|
&& !Profile.CycleOutputDeviceActionId.IsEmpty()
|
||||||
|
&& !Profile.ToggleMuteWhileRecordingActionId.IsEmpty()
|
||||||
|
&& !Profile.ToggleMicrophoneModeActionId.IsEmpty()
|
||||||
|
&& !Profile.InspectLocalModelCatalogActionId.IsEmpty()
|
||||||
|
&& !Profile.ReviewLocalModelIntegrityActionId.IsEmpty()
|
||||||
|
&& !Profile.ReviewLocalModelUnloadPolicyActionId.IsEmpty()
|
||||||
|
&& !Profile.PrimaryHotkeyBindingLabel.IsEmpty()
|
||||||
|
&& !Profile.PostProcessHotkeyBindingLabel.IsEmpty()
|
||||||
|
&& Profile.RetainedHistoryEntryLimit > 0
|
||||||
|
&& Profile.Panels.Num() > 0
|
||||||
|
&& Profile.ActionBindings.Num() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasValidExternalDictationShellStateCore(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||||
|
{
|
||||||
|
return !State.ExternalDictationShellProfileId.IsEmpty()
|
||||||
|
&& !State.ActiveProviderProfileId.IsEmpty()
|
||||||
|
&& !State.ActiveServiceLaneId.IsEmpty()
|
||||||
|
&& !State.SelectedOutputRouteId.IsEmpty()
|
||||||
|
&& !State.TranscriptHistorySurfaceModeId.IsEmpty()
|
||||||
|
&& !State.OutputRoutingSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.PostProcessOverlaySurfaceModeId.IsEmpty()
|
||||||
|
&& !State.GlobalHotkeySurfaceModeId.IsEmpty()
|
||||||
|
&& !State.InputDeviceSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.OutputDeviceSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.MuteSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.MicrophoneModeSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.LocalModelCatalogSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.ModelIntegritySurfaceModeId.IsEmpty()
|
||||||
|
&& !State.ModelUnloadSurfaceModeId.IsEmpty()
|
||||||
|
&& !State.LatestSourceKind.IsEmpty()
|
||||||
|
&& !State.StatusLine.IsEmpty()
|
||||||
|
&& !State.DetailLine.IsEmpty()
|
||||||
|
&& !State.PrimaryHotkeyBindingLabel.IsEmpty()
|
||||||
|
&& !State.PostProcessHotkeyBindingLabel.IsEmpty()
|
||||||
|
&& !State.SelectedInputDeviceId.IsEmpty()
|
||||||
|
&& !State.SelectedInputDeviceLabel.IsEmpty()
|
||||||
|
&& !State.SelectedOutputDeviceId.IsEmpty()
|
||||||
|
&& !State.SelectedOutputDeviceLabel.IsEmpty()
|
||||||
|
&& !State.MicrophoneModeId.IsEmpty()
|
||||||
|
&& !State.PayloadCustodyProfileId.IsEmpty()
|
||||||
|
&& !State.SelectedPrimaryPayloadId.IsEmpty()
|
||||||
|
&& State.HistoryEntryCount >= 0
|
||||||
|
&& State.PostProcessedEntryCount >= 0
|
||||||
|
&& State.SavedEntryCount >= 0
|
||||||
|
&& State.ModelCatalogEntryCount >= 0
|
||||||
|
&& State.OptionalModelCatalogEntryCount >= 0
|
||||||
|
&& State.DownloadDeferredModelEntryCount >= 0
|
||||||
|
&& State.IntegrityReviewRequiredEntryCount >= 0
|
||||||
|
&& State.ActiveIssueCount >= 0
|
||||||
|
&& State.OutputRouteOptions.Num() > 0
|
||||||
|
&& State.InputDeviceOptions.Num() > 0
|
||||||
|
&& State.OutputDeviceOptions.Num() > 0
|
||||||
|
&& State.ModelCatalogEntries.Num() > 0
|
||||||
|
&& State.AvailableActionIds.Num() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasConsistentExternalDictationShellStateCounts(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||||
|
{
|
||||||
|
return State.HistoryEntryCount == State.HistoryEntries.Num()
|
||||||
|
&& State.PostProcessedEntryCount <= State.HistoryEntryCount
|
||||||
|
&& State.SavedEntryCount <= State.HistoryEntryCount
|
||||||
|
&& State.ModelCatalogEntryCount == State.ModelCatalogEntries.Num()
|
||||||
|
&& State.OptionalModelCatalogEntryCount <= State.ModelCatalogEntryCount
|
||||||
|
&& State.DownloadDeferredModelEntryCount <= State.ModelCatalogEntryCount
|
||||||
|
&& State.IntegrityReviewRequiredEntryCount <= State.ModelCatalogEntryCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasConsistentExternalDictationShellStateLatestEntry(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||||
|
{
|
||||||
|
return State.HistoryEntryCount <= 0
|
||||||
|
|| (!State.LatestHistoryEntryId.IsEmpty() && !State.LatestTranscriptText.IsEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasConsistentExternalDictationShellStateActiveIssue(const FHyperTwistSpeechExternalDictationShellState& State)
|
||||||
|
{
|
||||||
|
return State.ActiveIssueCount == State.Issues.Num()
|
||||||
|
&& (State.ActiveIssueCount <= 0 || State.ActiveIssue.IsStructurallyValid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechUsageCostHistoryExportShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (HistoryExportShellProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| SelectedHistoryWindowId.IsEmpty()
|
||||||
|
|| ExportPreviewFormatId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| HistoryEntryCount < 0
|
||||||
|
|| EstimateOnlyEntryCount < 0
|
||||||
|
|| ProviderReceiptEntryCount < 0
|
||||||
|
|| TotalUsageQuantity < 0.0f
|
||||||
|
|| TotalEstimatedCostUsd < 0.0f
|
||||||
|
|| TotalReportedCostUsd < 0.0f
|
||||||
|
|| LowestRemainingEstimatedSpendUsd < 0.0f
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HistoryEntryCount == HistoryEntries.Num()
|
||||||
|
&& (HistoryEntryCount <= 0 || !LatestHistoryEntryId.IsEmpty())
|
||||||
|
&& (!bExportPreviewReady || !ExportPreviewText.IsEmpty())
|
||||||
|
&& ActiveIssueCount == Issues.Num()
|
||||||
|
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechProviderReceiptReviewShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (ReceiptReviewShellProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| SelectedChargeWindowId.IsEmpty()
|
||||||
|
|| PostedChargeInspectionModeId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| ReceiptEntryCount < 0
|
||||||
|
|| ProviderPostedChargeCount < 0
|
||||||
|
|| EstimateOnlyEntryCount < 0
|
||||||
|
|| VarianceReviewEntryCount < 0
|
||||||
|
|| TotalEstimatedCostUsd < 0.0f
|
||||||
|
|| TotalReportedCostUsd < 0.0f
|
||||||
|
|| TotalAbsoluteVarianceUsd < 0.0f
|
||||||
|
|| HighestAbsoluteVarianceUsd < 0.0f
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReceiptEntryCount == ReceiptEntries.Num()
|
||||||
|
&& ProviderPostedChargeCount <= ReceiptEntryCount
|
||||||
|
&& EstimateOnlyEntryCount <= ReceiptEntryCount
|
||||||
|
&& VarianceReviewEntryCount <= ReceiptEntryCount
|
||||||
|
&& (ReceiptEntryCount <= 0 || !LatestReceiptEntryId.IsEmpty())
|
||||||
|
&& (!bReceiptSummaryReady || !ReceiptSummaryText.IsEmpty())
|
||||||
|
&& ActiveIssueCount == Issues.Num()
|
||||||
|
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ReceiptEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechProviderBillingSettlementShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (BillingSettlementShellProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| SelectedSettlementWindowId.IsEmpty()
|
||||||
|
|| SettlementSurfaceModeId.IsEmpty()
|
||||||
|
|| InvoiceReconciliationModeId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| SettlementEntryCount < 0
|
||||||
|
|| ProviderPostedChargeCount < 0
|
||||||
|
|| ReconciliationReadyEntryCount < 0
|
||||||
|
|| EstimateOnlyEntryCount < 0
|
||||||
|
|| ManualReviewEntryCount < 0
|
||||||
|
|| BlockedSettlementEntryCount < 0
|
||||||
|
|| TotalEstimatedCostUsd < 0.0f
|
||||||
|
|| TotalReportedCostUsd < 0.0f
|
||||||
|
|| TotalSettlementDeltaUsd < 0.0f
|
||||||
|
|| HighestSettlementDeltaUsd < 0.0f
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettlementEntryCount == SettlementEntries.Num()
|
||||||
|
&& ProviderPostedChargeCount <= SettlementEntryCount
|
||||||
|
&& ReconciliationReadyEntryCount <= SettlementEntryCount
|
||||||
|
&& EstimateOnlyEntryCount <= SettlementEntryCount
|
||||||
|
&& ManualReviewEntryCount <= SettlementEntryCount
|
||||||
|
&& BlockedSettlementEntryCount <= SettlementEntryCount
|
||||||
|
&& (SettlementEntryCount <= 0 || !LatestSettlementEntryId.IsEmpty())
|
||||||
|
&& (!bSettlementSummaryReady || !SettlementSummaryText.IsEmpty())
|
||||||
|
&& ActiveIssueCount == Issues.Num()
|
||||||
|
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(SettlementEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechProviderSettlementExceptionShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (SettlementExceptionShellProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveProviderDisplayLabel.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| SelectedSettlementWindowId.IsEmpty()
|
||||||
|
|| ExceptionSurfaceModeId.IsEmpty()
|
||||||
|
|| ExternalPortalHandoffModeId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| ExceptionEntryCount < 0
|
||||||
|
|| ExternalPortalHandoffReadyEntryCount < 0
|
||||||
|
|| PendingChargeEntryCount < 0
|
||||||
|
|| ManualReviewEntryCount < 0
|
||||||
|
|| RouteDegradedEntryCount < 0
|
||||||
|
|| BlockedSettlementEntryCount < 0
|
||||||
|
|| TotalEstimatedCostUsd < 0.0f
|
||||||
|
|| TotalReportedCostUsd < 0.0f
|
||||||
|
|| TotalSettlementDeltaUsd < 0.0f
|
||||||
|
|| HighestSettlementDeltaUsd < 0.0f
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExceptionEntryCount == ExceptionEntries.Num()
|
||||||
|
&& ExternalPortalHandoffReadyEntryCount <= ExceptionEntryCount
|
||||||
|
&& PendingChargeEntryCount <= ExceptionEntryCount
|
||||||
|
&& ManualReviewEntryCount <= ExceptionEntryCount
|
||||||
|
&& RouteDegradedEntryCount <= ExceptionEntryCount
|
||||||
|
&& BlockedSettlementEntryCount <= ExceptionEntryCount
|
||||||
|
&& (ExceptionEntryCount <= 0 || !LatestExceptionEntryId.IsEmpty())
|
||||||
|
&& (!bExceptionSummaryReady || !ExceptionSummaryText.IsEmpty())
|
||||||
|
&& ActiveIssueCount == Issues.Num()
|
||||||
|
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ExceptionEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechExternalDictationShellProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypeValidation::HasValidExternalDictationShellProfileCore(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Panels)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechExternalDictationShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypeValidation::HasValidExternalDictationShellStateCore(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateCounts(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateLatestEntry(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::HasConsistentExternalDictationShellStateActiveIssue(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(Issues)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(HistoryEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputRouteOptions)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(InputDeviceOptions)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(OutputDeviceOptions)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreStructurallyValidEntries(ModelCatalogEntries)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechSessionConfig::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypeValidation::HasValidSpeechSessionCore(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::HasValidSpeechSessionProfiles(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(RequiredModelPayloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechTranscriptResult::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (!HyperTwistRecognitionTypeValidation::HasValidTranscriptCore(*this)
|
||||||
|
|| RetrievedHintCount < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bUsedRetrievedHintAugmentation && RetrievedHintCount <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RetrievedHintCount == AppliedRetrievedHints.Num()
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreTranscriptSegmentsValid(Segments)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(AppliedRetrievedHints);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechServiceHealth::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return HyperTwistRecognitionTypeValidation::HasValidSpeechServiceHealthCore(*this)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreNonEmptyStrings(SupportedModelPayloadIds)
|
||||||
|
&& HyperTwistRecognitionTypeValidation::AreSpeechModelPayloadsValid(SupportedModelPayloads);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceAssetDescriptor::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !VoiceAssetId.IsEmpty()
|
||||||
|
&& !VoiceProfileId.IsEmpty()
|
||||||
|
&& !ArtifactName.IsEmpty()
|
||||||
|
&& !RelativePathHint.IsEmpty()
|
||||||
|
&& !SourceDocumentPath.IsEmpty()
|
||||||
|
&& !ReviewDocumentPath.IsEmpty()
|
||||||
|
&& !AcquisitionMode.IsEmpty()
|
||||||
|
&& !ReviewPosture.IsEmpty()
|
||||||
|
&& ApproximateSizeMiB > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceAssetReviewProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !VoiceAssetReviewProfileId.IsEmpty()
|
||||||
|
&& !VoiceAssetReviewPosture.IsEmpty()
|
||||||
|
&& !ShippingPosture.IsEmpty()
|
||||||
|
&& !DownloadWorkflowPosture.IsEmpty()
|
||||||
|
&& !ProvisioningPosture.IsEmpty()
|
||||||
|
&& !CodeLicenseBoundary.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceModelReviewDescriptor::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !VoiceModelReviewId.IsEmpty()
|
||||||
|
&& !ModelBindingId.IsEmpty()
|
||||||
|
&& !VocoderBindingId.IsEmpty()
|
||||||
|
&& !VoiceProfileId.IsEmpty()
|
||||||
|
&& !RegistryReferencePath.IsEmpty()
|
||||||
|
&& !ReviewDocumentPath.IsEmpty()
|
||||||
|
&& !ModelLicensePosture.IsEmpty()
|
||||||
|
&& !PayloadLicensePosture.IsEmpty()
|
||||||
|
&& !AcquisitionMode.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceModelReviewProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !VoiceModelReviewProfileId.IsEmpty()
|
||||||
|
&& !ReviewPosture.IsEmpty()
|
||||||
|
&& !ShippingPosture.IsEmpty()
|
||||||
|
&& !DownloadWorkflowPosture.IsEmpty()
|
||||||
|
&& !CodeLicenseBoundary.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceProfileSummary::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !VoiceProfileId.IsEmpty()
|
||||||
|
&& !VoiceName.IsEmpty()
|
||||||
|
&& !LanguageCode.IsEmpty()
|
||||||
|
&& !LanguageFamily.IsEmpty()
|
||||||
|
&& !RegionCode.IsEmpty()
|
||||||
|
&& !LanguageNameEnglish.IsEmpty()
|
||||||
|
&& !Quality.IsEmpty()
|
||||||
|
&& NumSpeakers > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceProfileCatalog::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return Profiles.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistNarrationOrchestrationProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !OrchestrationProfileId.IsEmpty()
|
||||||
|
&& !ServiceLaneId.IsEmpty()
|
||||||
|
&& !ModelBindingId.IsEmpty()
|
||||||
|
&& !VocoderBindingId.IsEmpty()
|
||||||
|
&& !DefaultSpeakerProfileId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistNarrationSynthesisRequest::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !RequestId.IsEmpty()
|
||||||
|
&& !NarrationContractId.IsEmpty()
|
||||||
|
&& !ServiceLaneId.IsEmpty()
|
||||||
|
&& !OutputRouteId.IsEmpty()
|
||||||
|
&& !VoiceProfileId.IsEmpty()
|
||||||
|
&& !LanguageCode.IsEmpty()
|
||||||
|
&& OrchestrationProfile.IsStructurallyValid()
|
||||||
|
&& ServiceLaneId.Equals(OrchestrationProfile.ServiceLaneId, ESearchCase::CaseSensitive)
|
||||||
|
&& !ScriptText.IsEmpty()
|
||||||
|
&& !AudioEncodingHint.IsEmpty()
|
||||||
|
&& LengthScale > 0.0f
|
||||||
|
&& NoiseScale >= 0.0f
|
||||||
|
&& NoiseW >= 0.0f
|
||||||
|
&& SentenceSilenceSeconds >= 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistNarrationSynthesisResult::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !RequestId.IsEmpty()
|
||||||
|
&& !NarrationContractId.IsEmpty()
|
||||||
|
&& !ServiceLaneId.IsEmpty()
|
||||||
|
&& !OutputRouteId.IsEmpty()
|
||||||
|
&& !VoiceProfileId.IsEmpty()
|
||||||
|
&& !LanguageCode.IsEmpty()
|
||||||
|
&& !OrchestrationProfileId.IsEmpty()
|
||||||
|
&& !ModelBindingId.IsEmpty()
|
||||||
|
&& !VocoderBindingId.IsEmpty()
|
||||||
|
&& !SubtitleText.IsEmpty()
|
||||||
|
&& !AudioEncoding.IsEmpty()
|
||||||
|
&& SampleRateHz > 0
|
||||||
|
&& ChannelCount > 0
|
||||||
|
&& DurationMs >= 0
|
||||||
|
&& AudioBytes.Num() > 0
|
||||||
|
&& AppliedLengthScale > 0.0f
|
||||||
|
&& AppliedNoiseScale >= 0.0f
|
||||||
|
&& AppliedNoiseW >= 0.0f
|
||||||
|
&& AppliedSentenceSilenceSeconds >= 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistVoiceServiceHealth::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (ProviderLabel.IsEmpty() || ServiceVersion.IsEmpty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!VoiceAssetReviewProfileId.IsEmpty()
|
||||||
|
|| VoiceAssetReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
|| SupportedVoiceAssetIds.Num() > 0
|
||||||
|
|| SupportedVoiceAssets.Num() > 0)
|
||||||
|
&& (VoiceAssetReviewProfileId.IsEmpty()
|
||||||
|
|| !VoiceAssetReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
|| SupportedVoiceAssetIds.Num() != SupportedVoiceAssets.Num()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!VoiceModelReviewProfileId.IsEmpty()
|
||||||
|
|| VoiceModelReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
|| SupportedVoiceModelReviewIds.Num() > 0
|
||||||
|
|| SupportedVoiceModelReviews.Num() > 0)
|
||||||
|
&& (VoiceModelReviewProfileId.IsEmpty()
|
||||||
|
|| !VoiceModelReviewProfileDefinition.IsStructurallyValid()
|
||||||
|
|| SupportedVoiceModelReviewIds.Num() != SupportedVoiceModelReviews.Num()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedVoiceAssetIds)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedVoiceAssets)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(SupportedVoiceModelReviewIds)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(SupportedVoiceModelReviews);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechMicrophoneShellState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (MicrophoneShellProfileId.IsEmpty()
|
||||||
|
|| CaptureMode.IsEmpty()
|
||||||
|
|| ListeningContractId.IsEmpty()
|
||||||
|
|| InputRouteId.IsEmpty()
|
||||||
|
|| PermissionStateId.IsEmpty()
|
||||||
|
|| CaptureRouteStateId.IsEmpty()
|
||||||
|
|| LastLanguageCode.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| StepWindowMs <= 0
|
||||||
|
|| CaptureWindowMs < StepWindowMs
|
||||||
|
|| KeepWindowMs < 0
|
||||||
|
|| KeepWindowMs > CaptureWindowMs
|
||||||
|
|| SubmittedUtteranceCount < 0
|
||||||
|
|| FinalTranscriptCount < 0
|
||||||
|
|| DetectedSpeechStartMs < 0
|
||||||
|
|| DetectedSpeechEndMs < DetectedSpeechStartMs
|
||||||
|
|| LastSilenceGapMs < 0
|
||||||
|
|| VadThreshold <= 0.0f
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ActiveIssueCount == Issues.Num()
|
||||||
|
&& (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechDevicePermissionWorkflowProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !DevicePermissionWorkflowProfileId.IsEmpty()
|
||||||
|
&& !WorkflowKind.IsEmpty()
|
||||||
|
&& !PermissionContractId.IsEmpty()
|
||||||
|
&& !SettingsHandoffContractId.IsEmpty()
|
||||||
|
&& !PermissionRecheckContractId.IsEmpty()
|
||||||
|
&& !PermissionRequestActionId.IsEmpty()
|
||||||
|
&& !OpenSettingsActionId.IsEmpty()
|
||||||
|
&& !PermissionRecheckActionId.IsEmpty()
|
||||||
|
&& Panels.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechDevicePermissionWorkflowEntry::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !EntryId.IsEmpty()
|
||||||
|
&& !SourceKind.IsEmpty()
|
||||||
|
&& !WorkflowStateId.IsEmpty()
|
||||||
|
&& !StatusLine.IsEmpty()
|
||||||
|
&& !DetailLine.IsEmpty()
|
||||||
|
&& !RecommendedActionId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechDevicePermissionWorkflowIssue::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !IssueId.IsEmpty()
|
||||||
|
&& !IssueKind.IsEmpty()
|
||||||
|
&& !StatusLine.IsEmpty()
|
||||||
|
&& !DetailLine.IsEmpty()
|
||||||
|
&& !RecommendedActionId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechDevicePermissionWorkflowState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (DevicePermissionWorkflowProfileId.IsEmpty()
|
||||||
|
|| WorkflowStateId.IsEmpty()
|
||||||
|
|| PermissionStateId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| WorkflowEntryCount < 0
|
||||||
|
|| PermissionRequestEntryCount < 0
|
||||||
|
|| SettingsHandoffEntryCount < 0
|
||||||
|
|| PermissionRecheckEntryCount < 0
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WorkflowEntryCount != Entries.Num()
|
||||||
|
|| ActiveIssueCount != Issues.Num()
|
||||||
|
|| PermissionRequestEntryCount > WorkflowEntryCount
|
||||||
|
|| SettingsHandoffEntryCount > WorkflowEntryCount
|
||||||
|
|| PermissionRecheckEntryCount > WorkflowEntryCount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteWorkflowProfile::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||||
|
&& !WorkflowKind.IsEmpty()
|
||||||
|
&& !OwnershipContractId.IsEmpty()
|
||||||
|
&& !PreparationContractId.IsEmpty()
|
||||||
|
&& !SessionReopenContractId.IsEmpty()
|
||||||
|
&& !RouteInspectActionId.IsEmpty()
|
||||||
|
&& !PreparationRetryActionId.IsEmpty()
|
||||||
|
&& !SessionReopenActionId.IsEmpty()
|
||||||
|
&& !PermissionDependencyActionId.IsEmpty()
|
||||||
|
&& Panels.Num() > 0
|
||||||
|
&& ActionBindings.Num() > 0
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Panels)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(ActionBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteWorkflowEntry::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !EntryId.IsEmpty()
|
||||||
|
&& !SourceKind.IsEmpty()
|
||||||
|
&& !WorkflowStateId.IsEmpty()
|
||||||
|
&& !CaptureRouteStateId.IsEmpty()
|
||||||
|
&& !StatusLine.IsEmpty()
|
||||||
|
&& !DetailLine.IsEmpty()
|
||||||
|
&& !RecommendedActionId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteWorkflowIssue::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
return !IssueId.IsEmpty()
|
||||||
|
&& !IssueKind.IsEmpty()
|
||||||
|
&& !StatusLine.IsEmpty()
|
||||||
|
&& !DetailLine.IsEmpty()
|
||||||
|
&& !RecommendedActionId.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FHyperTwistSpeechNativeCaptureRouteWorkflowState::IsStructurallyValid() const
|
||||||
|
{
|
||||||
|
if (NativeCaptureRouteWorkflowProfileId.IsEmpty()
|
||||||
|
|| WorkflowStateId.IsEmpty()
|
||||||
|
|| CaptureRouteStateId.IsEmpty()
|
||||||
|
|| PermissionStateId.IsEmpty()
|
||||||
|
|| ActiveProviderProfileId.IsEmpty()
|
||||||
|
|| ActiveServiceLaneId.IsEmpty()
|
||||||
|
|| LatestSourceKind.IsEmpty()
|
||||||
|
|| StatusLine.IsEmpty()
|
||||||
|
|| DetailLine.IsEmpty()
|
||||||
|
|| WorkflowEntryCount < 0
|
||||||
|
|| PreparationEntryCount < 0
|
||||||
|
|| RouteRetryEntryCount < 0
|
||||||
|
|| SessionReopenEntryCount < 0
|
||||||
|
|| PermissionDependencyEntryCount < 0
|
||||||
|
|| ActiveIssueCount < 0
|
||||||
|
|| AvailableActionIds.Num() <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WorkflowEntryCount != Entries.Num()
|
||||||
|
|| ActiveIssueCount != Issues.Num()
|
||||||
|
|| PreparationEntryCount > WorkflowEntryCount
|
||||||
|
|| RouteRetryEntryCount > WorkflowEntryCount
|
||||||
|
|| SessionReopenEntryCount > WorkflowEntryCount
|
||||||
|
|| PermissionDependencyEntryCount > WorkflowEntryCount)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ActiveIssueCount <= 0 || ActiveIssue.IsStructurallyValid())
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Entries)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllItemsStructurallyValid(Issues)
|
||||||
|
&& HyperTwistRecognitionTypesInternal::AreAllStringsPopulated(AvailableActionIds);
|
||||||
|
}
|
||||||
|
|
@ -3647,6 +3647,11 @@ UHyperTwistSkillCoreLibrary::DeriveSkillContinuityResumeState(
|
||||||
}
|
}
|
||||||
else if (SkillId == TEXT("skill/capture-note"))
|
else if (SkillId == TEXT("skill/capture-note"))
|
||||||
{
|
{
|
||||||
|
const bool bCanCaptureNotesNow =
|
||||||
|
KnowledgeNotesState.bUserNotesLaneAvailable
|
||||||
|
&& !KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation
|
||||||
|
&& Entry->Status == EHyperTwistSkillStatus::ImplementedNow;
|
||||||
|
|
||||||
SkillState.InputStateKinds = {
|
SkillState.InputStateKinds = {
|
||||||
TEXT("state/memory-knowledge-notes"),
|
TEXT("state/memory-knowledge-notes"),
|
||||||
TEXT("state/memory-user-notes-posture")
|
TEXT("state/memory-user-notes-posture")
|
||||||
|
|
@ -3660,14 +3665,13 @@ UHyperTwistSkillCoreLibrary::DeriveSkillContinuityResumeState(
|
||||||
KnowledgeNotesState.UserNotesPosture.SummaryLine
|
KnowledgeNotesState.UserNotesPosture.SummaryLine
|
||||||
);
|
);
|
||||||
|
|
||||||
SkillState.AvailableItemCount =
|
SkillState.AvailableItemCount = bCanCaptureNotesNow ? 1 : 0;
|
||||||
KnowledgeNotesState.bUserNotesLaneAvailable ? 1 : 0;
|
|
||||||
SkillState.BlockingItemCount =
|
SkillState.BlockingItemCount =
|
||||||
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation ? 1 : 0;
|
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation ? 1 : 0;
|
||||||
SkillState.bLiveSkill = false;
|
SkillState.bLiveSkill = false;
|
||||||
SkillState.bReadsAuthoritativeStores =
|
SkillState.bReadsAuthoritativeStores =
|
||||||
KnowledgeNotesState.UserNotesPosture.bOwnershipJustified;
|
KnowledgeNotesState.UserNotesPosture.bOwnershipJustified;
|
||||||
SkillState.bAvailableNow = KnowledgeNotesState.bUserNotesLaneAvailable;
|
SkillState.bAvailableNow = bCanCaptureNotesNow;
|
||||||
SkillState.bRequiresOwnerActivation =
|
SkillState.bRequiresOwnerActivation =
|
||||||
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation;
|
KnowledgeNotesState.UserNotesPosture.bRequiresSeparateOwnerActivation;
|
||||||
SkillState.Summary =
|
SkillState.Summary =
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -20,6 +20,7 @@ VectorShell.
|
||||||
## Landed HyperTwist-owned entry points
|
## Landed HyperTwist-owned entry points
|
||||||
|
|
||||||
- `.sentrux/rules.toml`
|
- `.sentrux/rules.toml`
|
||||||
|
- `scripts/bootstrap-hypertwist-sentrux.sh`
|
||||||
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
- `scripts/run-hypertwist-sentrux-source-only.sh`
|
||||||
- `scripts/run-hypertwist-gitnexus-analyze.sh`
|
- `scripts/run-hypertwist-gitnexus-analyze.sh`
|
||||||
- `scripts/run-hypertwist-gitnexus-status.sh`
|
- `scripts/run-hypertwist-gitnexus-status.sh`
|
||||||
|
|
@ -56,9 +57,16 @@ Resolution order for the analyzer binary is now HyperTwist-owned first:
|
||||||
|
|
||||||
- `HYPERTWIST_SENTRUX_BINARY` if explicitly provided
|
- `HYPERTWIST_SENTRUX_BINARY` if explicitly provided
|
||||||
- repo-local `./sentrux` or `./sentrux.exe` if present
|
- repo-local `./sentrux` or `./sentrux.exe` if present
|
||||||
|
- repo-local `tools/sentrux/bin/sentrux` or `tools/sentrux/bin/sentrux.exe`
|
||||||
- `sentrux` on `PATH`
|
- `sentrux` on `PATH`
|
||||||
- the retained local fallback under `/home/dev/src/VectorShell/sentrux`
|
- the retained local fallback under `/home/dev/src/VectorShell/sentrux`
|
||||||
|
|
||||||
|
If the repo-local tools path is empty, materialize it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/bootstrap-hypertwist-sentrux.sh
|
||||||
|
```
|
||||||
|
|
||||||
Current rules enforce:
|
Current rules enforce:
|
||||||
|
|
||||||
- no cycles
|
- no cycles
|
||||||
|
|
@ -237,6 +245,129 @@ Important nuance from the `2026-06-22` follow-up:
|
||||||
family extraction or multi-header ownership separation for the remaining
|
family extraction or multi-header ownership separation for the remaining
|
||||||
validator clusters rather than more in-place header-local helperization
|
validator clusters rather than more in-place header-local helperization
|
||||||
|
|
||||||
|
## `2026-06-23` continuation refresh
|
||||||
|
|
||||||
|
Additional same-lane follow-up on `2026-06-23`:
|
||||||
|
|
||||||
|
- the remaining inline validator clusters in
|
||||||
|
`HyperTwistSkillTypes.h` and `HyperTwistRecognitionTypes.h` were then moved
|
||||||
|
further out of the public headers into dedicated private translation units:
|
||||||
|
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistSkills/HyperTwistSkillTypes.cpp`
|
||||||
|
- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`
|
||||||
|
- a public-website/manual continuation also landed in the same pass so the
|
||||||
|
docs/resources/support surfaces now explicitly project current keyboard,
|
||||||
|
higher-dimensional control, and unfinished XR/controller truth instead of
|
||||||
|
only the browser-versus-desktop topology boundary
|
||||||
|
- focused website coverage for
|
||||||
|
`src/__tests__/public-marketing-pages.test.tsx` and
|
||||||
|
`src/__tests__/protected-app-pages.test.tsx` passed after that public-manual
|
||||||
|
continuation
|
||||||
|
- a fresh full website validation pass then stayed green:
|
||||||
|
- `npm --prefix website test -- --run`
|
||||||
|
- `37` test files passed
|
||||||
|
- `137` tests passed
|
||||||
|
- the same widened public-manual packet also kept the production website build
|
||||||
|
green under `npm --prefix website run build`
|
||||||
|
|
||||||
|
Current highest-signal structural truth after that `2026-06-23` continuation:
|
||||||
|
|
||||||
|
- `scripts/run-hypertwist-sentrux-source-only.sh` now reports `Quality: 6070`
|
||||||
|
- the only remaining reported `sentrux` debt is still:
|
||||||
|
- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)`
|
||||||
|
- that remaining hit is now more likely a parser/grouping artifact than a new
|
||||||
|
broad website or browser regression, because the website/manual widening
|
||||||
|
stayed structurally clean and the real validator ownership was moved out of
|
||||||
|
the header family rather than expanded inside it
|
||||||
|
|
||||||
|
Additional same-lane follow-up later on `2026-06-23`:
|
||||||
|
|
||||||
|
- `scripts/run-hypertwist-gitnexus-analyze.sh` re-indexed the bounded
|
||||||
|
source-only mirror successfully at `16,042` nodes, `37,412` edges,
|
||||||
|
`643` clusters, and `300` flows
|
||||||
|
- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded
|
||||||
|
mirror `Status: up-to-date`
|
||||||
|
- a fresh full website validation rerun stayed green again:
|
||||||
|
- `npm --prefix website test -- --run`
|
||||||
|
- `37` test files passed
|
||||||
|
- `137` tests passed
|
||||||
|
- duration `4.63s`
|
||||||
|
- `npm --prefix website run build`
|
||||||
|
- Vite production build succeeded
|
||||||
|
- `scripts/run-hypertwist-sentrux-source-only.sh` improved again to
|
||||||
|
`Quality: 6076`
|
||||||
|
- one more out-of-header migration moved
|
||||||
|
`FHyperTwistSpeechExternalDictationShellProfile::IsStructurallyValid()`
|
||||||
|
into `Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`
|
||||||
|
- the same remaining reported `sentrux` hit still stayed fixed at
|
||||||
|
`HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)` even after
|
||||||
|
that migration, which further supports the current interpretation that this
|
||||||
|
residual is a repeated-name/grouping artifact inside the analyzer rather
|
||||||
|
than one newly expanded high-risk validator body
|
||||||
|
- the public-website lane was then refactored into bounded per-page modules so
|
||||||
|
the website manual/distribution surface is no longer concentrated in one
|
||||||
|
large `public-pages.tsx` owner
|
||||||
|
- the public route tree then stopped lazy-loading the old barrel and now loads
|
||||||
|
`public-pages-marketing` and `public-pages-commerce` directly, which restored
|
||||||
|
real route-level bundle separation instead of keeping one coarse public-pages
|
||||||
|
chunk
|
||||||
|
- HyperTwist also gained a repo-local `scripts/bootstrap-hypertwist-sentrux.sh`
|
||||||
|
helper plus `tools/sentrux/bin/` landing zone so the analyzer path can be
|
||||||
|
materialized under HyperTwist authority instead of depending only on
|
||||||
|
cross-repo memory
|
||||||
|
- after that same refactor/tooling continuation, the repo-local materialized
|
||||||
|
analyzer reran successfully through
|
||||||
|
`scripts/run-hypertwist-sentrux-source-only.sh` at `Quality: 6032`
|
||||||
|
- the same single residual violation remained:
|
||||||
|
`HyperTwistRecognitionTypes.h:IsStructurallyValid (308 lines)`
|
||||||
|
- that lower numeric score did not reopen cycle debt or website/page-module
|
||||||
|
god-file debt; it still reported the same one residual recognition-header hit
|
||||||
|
and no new browser/website structural regression
|
||||||
|
|
||||||
|
Latest same-lane follow-up later on `2026-06-23`:
|
||||||
|
|
||||||
|
- the remaining repeated-name recognition validator hotspot was then reduced
|
||||||
|
again by moving these larger header-local validators into
|
||||||
|
`Private/HyperTwistRecognition/HyperTwistRecognitionTypes.cpp`:
|
||||||
|
- `FHyperTwistVisionShellProfile::IsStructurallyValid()`
|
||||||
|
- `FHyperTwistVisionSolveExplanationProfile::IsStructurallyValid()`
|
||||||
|
- `FHyperTwistVisionCorrectionState::IsStructurallyValid()`
|
||||||
|
- `FHyperTwistSpeechProviderRoutingPolicy::IsStructurallyValid()`
|
||||||
|
- after that out-of-header continuation,
|
||||||
|
`scripts/run-hypertwist-sentrux-source-only.sh` reached `Quality: 6112`
|
||||||
|
and all `7` rules passed with no remaining violations
|
||||||
|
- `scripts/run-hypertwist-gitnexus-analyze.sh` then re-indexed the bounded
|
||||||
|
source-only mirror successfully at `16,055` nodes, `37,472` edges,
|
||||||
|
`645` clusters, and `300` flows
|
||||||
|
- `scripts/run-hypertwist-gitnexus-status.sh` again reported the bounded
|
||||||
|
mirror `Status: up-to-date`
|
||||||
|
- the tightened public/manual route split remained green under focused website
|
||||||
|
validation:
|
||||||
|
- `npm test -- --run src/__tests__/public-marketing-pages.test.tsx src/__tests__/protected-app-pages.test.tsx`
|
||||||
|
- `2` test files passed
|
||||||
|
- `11` tests passed
|
||||||
|
- the production website build remained green under `npm run build` in
|
||||||
|
`website/`
|
||||||
|
- the embedded browser runtime verification and production build also remained
|
||||||
|
green under:
|
||||||
|
- `npm run verify:shell` in `Content/Browser/`
|
||||||
|
- `npm run build` in `Content/Browser/`
|
||||||
|
- the same lane also preserved remote validation truth for the repaired skill
|
||||||
|
fixture packet:
|
||||||
|
- all `9` Windows Unreal automation reports under
|
||||||
|
`Skill-S3-Full-PostFixture-*` were present
|
||||||
|
- every report recorded `Succeeded: true`
|
||||||
|
- the green set covered `S3A`, `S3B`, and `S3C` continuity, authoritative,
|
||||||
|
and serialization filters
|
||||||
|
|
||||||
|
Current highest-signal structural truth after this latest continuation:
|
||||||
|
|
||||||
|
- HyperTwist now has repo-owned `sentrux` and `GitNexus` entry points that are
|
||||||
|
both revalidated and green on the bounded source-only mirror
|
||||||
|
- the public/manual/browser lane stayed validation-clean while the Unreal
|
||||||
|
validator ownership was pushed farther out of public headers
|
||||||
|
- the repaired skill-memory fixture lane is now fully green on the real remote
|
||||||
|
Windows Unreal path across all `9` focused `S3` reports
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
This note does not:
|
This note does not:
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,15 @@ Fallback connect-back shape when `22022` is occupied or stale:
|
||||||
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p 22023 -l 'DESKTOP-KS3VGHU\\anthracite ace' localhost
|
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 -p 22023 -l 'DESKTOP-KS3VGHU\\anthracite ace' localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Verified helper shape for short Windows-side PowerShell diagnostics on the same
|
||||||
|
lane:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||||
|
scripts/run-hypertwist-remote-windows-powershell.sh \
|
||||||
|
--command 'Write-Output ("ComputerName=" + $env:COMPUTERNAME)'
|
||||||
|
```
|
||||||
|
|
||||||
## Current Linux-side pickup sequence
|
## Current Linux-side pickup sequence
|
||||||
|
|
||||||
For a HyperTwist AI session that starts on Linux and needs to pick up the live
|
For a HyperTwist AI session that starts on Linux and needs to pick up the live
|
||||||
|
|
@ -148,6 +157,27 @@ Live follow-up on `2026-06-04` established these current facts:
|
||||||
- `22023` and `22024` were not visible in the latest probe and should be
|
- `22023` and `22024` were not visible in the latest probe and should be
|
||||||
treated as unavailable until re-established
|
treated as unavailable until re-established
|
||||||
- live `whoami` over `22022` again returned `desktop-ks3vghu\anthracite ace`
|
- live `whoami` over `22022` again returned `desktop-ks3vghu\anthracite ace`
|
||||||
|
- the repo now also carries
|
||||||
|
`scripts/run-hypertwist-remote-windows-powershell.sh` as a bounded encoded-
|
||||||
|
PowerShell wrapper for the reverse-SSH lane so future sessions can run short
|
||||||
|
diagnostics or automation commands through `localhost:22022` without brittle
|
||||||
|
nested shell quoting
|
||||||
|
- the repo now also carries
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation.sh` as a thin first-party
|
||||||
|
helper that builds canonical `UnrealEditor-Cmd` automation invocations on top
|
||||||
|
of that encoded-PowerShell wrapper for the maintained
|
||||||
|
`C:\HyperTwist_worktrees\phase10validate` lane
|
||||||
|
- the repo now also carries
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh` as a thin
|
||||||
|
first-party fail-fast helper for short ordered batches of focused automation
|
||||||
|
reruns on that same maintained validation lane
|
||||||
|
- the repo now also carries
|
||||||
|
`scripts/run-hypertwist-remote-unreal-build.sh` as a thin first-party helper
|
||||||
|
that builds canonical `Build.bat` invocations on top of that same encoded-
|
||||||
|
PowerShell wrapper for the maintained
|
||||||
|
`C:\HyperTwist_worktrees\phase10validate` lane, with the currently verified
|
||||||
|
reverse-lane low-memory posture `-NoUBA -MaxParallelActions=2` as its
|
||||||
|
default build shape
|
||||||
|
|
||||||
The sensitive runbook now carries the exact current Windows-side tunnel
|
The sensitive runbook now carries the exact current Windows-side tunnel
|
||||||
commands, known-hosts scratch file, listener verification command, reverse-sync
|
commands, known-hosts scratch file, listener verification command, reverse-sync
|
||||||
|
|
@ -564,6 +594,41 @@ Operational rules reinforced by this proof:
|
||||||
- when the export helper reports success, do one lightweight media sanity check
|
- when the export helper reports success, do one lightweight media sanity check
|
||||||
on the canonical MP4 rather than trusting file existence alone; current proof
|
on the canonical MP4 rather than trusting file existence alone; current proof
|
||||||
used `ffprobe` to confirm codec, resolution, frame count, and duration
|
used `ffprobe` to confirm codec, resolution, frame count, and duration
|
||||||
|
- when the refreshed reverse-SSH Windows lane only has about `1.2 GB` of free
|
||||||
|
physical memory and the host is still carrying large resident
|
||||||
|
`chrome.exe` / `Code.exe` workloads, expect UnrealBuildTool to fall back to
|
||||||
|
one active compiler worker; that is a slow-but-healthy memory-pressure
|
||||||
|
condition, not by itself proof of tunnel corruption or a looping build
|
||||||
|
- when a future session needs a short bounded PowerShell diagnostic over the
|
||||||
|
reverse-SSH lane, prefer
|
||||||
|
`scripts/run-hypertwist-remote-windows-powershell.sh` over ad hoc nested
|
||||||
|
shell quoting; the wrapper was re-proved on `2026-06-23` against the live
|
||||||
|
`22022` lane with `ComputerName=DESKTOP-KS3VGHU`
|
||||||
|
- when a future session needs a focused `UnrealEditor-Cmd` automation rerun on
|
||||||
|
the maintained Windows lane, prefer
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation.sh`; its generated command
|
||||||
|
shape was dry-run-validated on `2026-06-23` for
|
||||||
|
`HyperTwist.FirstParty.Skill` before the next live post-build invocation
|
||||||
|
- when that future session instead needs a short ordered batch of those focused
|
||||||
|
automation reruns, prefer
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh`; its generated
|
||||||
|
command shape was dry-run-validated on `2026-06-23`
|
||||||
|
|
||||||
|
Additional `2026-06-23` lane truth:
|
||||||
|
|
||||||
|
- the primary tunnel on `localhost:22022` remained healthy for a same-lane
|
||||||
|
skill-memory repair continuation
|
||||||
|
- the isolated Windows build root again stayed
|
||||||
|
`C:\HyperTwist_worktrees\phase10validate`
|
||||||
|
- a safe narrow-sync route was re-proved from the Windows side by using the
|
||||||
|
remote host's own `scp.exe` plus the retained VPS key to pull changed files
|
||||||
|
directly from `root@212.227.13.220` into that isolated worktree
|
||||||
|
- that route is now suitable when the active lane needs only a few touched
|
||||||
|
Unreal files refreshed in the maintained validation tree and a broad
|
||||||
|
reverse-sync would be wasteful or riskier than necessary
|
||||||
|
- after that narrow sync, the authoritative incremental Unreal editor rebuild
|
||||||
|
succeeded and the widened `S3` skill-memory automation packet finished with
|
||||||
|
all `9` expected `Skill-S3-Full-PostFixture-*` reports present and green
|
||||||
|
|
||||||
## Addendum - 2026-06-03 (stale-listener recovery)
|
## Addendum - 2026-06-03 (stale-listener recovery)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,6 +267,55 @@ Operational reading:
|
||||||
it as stale and shift to `localhost:22023`
|
it as stale and shift to `localhost:22023`
|
||||||
- if `22023` is not visibly listening, do not assume fallback availability;
|
- if `22023` is not visibly listening, do not assume fallback availability;
|
||||||
re-establish it first or report that only the primary lane is currently live
|
re-establish it first or report that only the primary lane is currently live
|
||||||
|
- when a short bounded Windows-side PowerShell diagnostic or automation command
|
||||||
|
is needed from Linux over the tunnel, prefer
|
||||||
|
`scripts/run-hypertwist-remote-windows-powershell.sh` over ad hoc nested
|
||||||
|
shell quoting; the wrapper encodes the payload for `powershell -EncodedCommand`
|
||||||
|
and was re-proved against the live `22022` lane on `2026-06-23`
|
||||||
|
- when the next step is a focused `UnrealEditor-Cmd` automation rerun on the
|
||||||
|
maintained Windows worktree, prefer
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation.sh`; it composes canonical
|
||||||
|
`-NullRHI`, `-ReportExportPath`, `-AbsLog`, and single-filter
|
||||||
|
`Automation RunTests ...; Quit` invocations on top of that encoded-
|
||||||
|
PowerShell wrapper, and its generated command shape was dry-run-validated on
|
||||||
|
`2026-06-23`
|
||||||
|
- when the next step is a short ordered batch of focused automation reruns on
|
||||||
|
that same maintained Windows worktree, prefer
|
||||||
|
`scripts/run-hypertwist-remote-unreal-automation-sequence.sh`; it composes
|
||||||
|
repeated fail-fast invocations of the single-filter wrapper in the exact
|
||||||
|
order provided, and its generated command shape was dry-run-validated on
|
||||||
|
`2026-06-23`
|
||||||
|
- when the next step is the authoritative remote Unreal editor build itself on
|
||||||
|
the maintained Windows worktree, prefer
|
||||||
|
`scripts/run-hypertwist-remote-unreal-build.sh`; it composes the verified
|
||||||
|
`Build.bat` invocation on top of that same encoded-PowerShell wrapper,
|
||||||
|
defaults the reverse-SSH lane to the maintained
|
||||||
|
`C:\HyperTwist_worktrees\phase10validate` root, and bakes in the
|
||||||
|
currently-verified low-memory recovery posture `-NoUBA -MaxParallelActions=2`
|
||||||
|
unless explicitly overridden. Its generated command shape was dry-run-
|
||||||
|
validated on `2026-06-23`
|
||||||
|
|
||||||
|
Canonical helper shape for the next focused post-build reruns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||||
|
scripts/run-hypertwist-remote-unreal-automation.sh \
|
||||||
|
--filter HyperTwist.FirstParty.Skill \
|
||||||
|
--report-name Skill-Verify
|
||||||
|
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||||
|
scripts/run-hypertwist-remote-unreal-automation.sh \
|
||||||
|
--filter HyperTwist.Permissive.WhisperCpp \
|
||||||
|
--report-name WhisperCpp-Verify
|
||||||
|
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD='...' \
|
||||||
|
scripts/run-hypertwist-remote-unreal-automation-sequence.sh \
|
||||||
|
--report-prefix Skill-S3 \
|
||||||
|
--filter HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage \
|
||||||
|
--filter HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead \
|
||||||
|
--filter HyperTwist.FirstParty.Skill.PhaseS3A.SerializationRoundTrip
|
||||||
|
```
|
||||||
|
|
||||||
- when recovering from a stale listener, run a split sequence:
|
- when recovering from a stale listener, run a split sequence:
|
||||||
1. smoke login/identity check
|
1. smoke login/identity check
|
||||||
2. canonical Unreal build command with explicit result markers
|
2. canonical Unreal build command with explicit result markers
|
||||||
|
|
@ -305,11 +354,52 @@ Operational reading:
|
||||||
before building; preserving old mtimes can leave stale
|
before building; preserving old mtimes can leave stale
|
||||||
`Intermediate\Build\Win64\UnrealEditor\Inc\UnrealHyperTwist` outputs in place
|
`Intermediate\Build\Win64\UnrealEditor\Inc\UnrealHyperTwist` outputs in place
|
||||||
and surface misleading reflected-type or generated-header failures
|
and surface misleading reflected-type or generated-header failures
|
||||||
|
- when the reverse-SSH Windows lane only has about `1.2 GB` of free physical
|
||||||
|
memory and the host still has large resident `chrome.exe` / `Code.exe`
|
||||||
|
workloads, expect UnrealBuildTool to serialize down to one active compiler
|
||||||
|
worker; treat that as a slow-but-healthy memory-pressure condition rather than
|
||||||
|
as tunnel corruption or a looping build unless the compile output itself
|
||||||
|
stalls or errors
|
||||||
- when a logical slice authors new `.umap` or `.uasset` authority surfaces in an
|
- when a logical slice authors new `.umap` or `.uasset` authority surfaces in an
|
||||||
isolated Windows worktree, pull those assets back into the tracked repo before
|
isolated Windows worktree, pull those assets back into the tracked repo before
|
||||||
calling the slice landed; remote-only authored assets are validation evidence,
|
calling the slice landed; remote-only authored assets are validation evidence,
|
||||||
not final repo truth
|
not final repo truth
|
||||||
|
|
||||||
|
## Addendum - 2026-06-23 (skill-memory fixture repair and full `S3` proof)
|
||||||
|
|
||||||
|
Live follow-up on `2026-06-23` established these additional facts:
|
||||||
|
|
||||||
|
- the maintained validation root again stayed
|
||||||
|
`C:\HyperTwist_worktrees\phase10validate`
|
||||||
|
- Windows-side `scp.exe` running on the remote host was re-proved as a safe
|
||||||
|
narrow-sync route from the VPS into that isolated worktree for touched
|
||||||
|
source files, avoiding a broad reverse-sync just to refresh a few Unreal
|
||||||
|
translation units
|
||||||
|
- after the touched skill-memory source files were hash-matched into that
|
||||||
|
worktree, the authoritative incremental editor rebuild succeeded there with
|
||||||
|
`Result: Succeeded` and UnrealBuildTool `Total execution time: 104.88
|
||||||
|
seconds`
|
||||||
|
- a fresh focused `S3A` rerun then passed on that rebuilt binary
|
||||||
|
- the widened same-lane follow-up then produced all `9` expected
|
||||||
|
`Skill-S3-Full-PostFixture-*` automation report folders under
|
||||||
|
`Saved\AutomationReports`
|
||||||
|
- each of those `9` report `index.json` files recorded a green result with
|
||||||
|
`Succeeded: true`, `SucceededCount: 1`, and `FailedCount: 0`
|
||||||
|
- that green set covered:
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3A.SerializationRoundTrip`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3B.RecallCompactViewCoverage`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3B.AuthoritativeCompactViewLinkage`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3B.SerializationRoundTrip`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3C.WorkflowMemoryCaptureCoverage`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3C.AuthoritativeWorkflowCaptureBoundaries`
|
||||||
|
- `HyperTwist.FirstParty.Skill.PhaseS3C.SerializationRoundTrip`
|
||||||
|
|
||||||
|
This extends the doctrine from classic-cube, package, higher-dimensional, and
|
||||||
|
media-export proof into the real remote skill-memory validation lane on the
|
||||||
|
same maintained reverse-SSH path.
|
||||||
|
|
||||||
## Closeout wording requirement
|
## Closeout wording requirement
|
||||||
|
|
||||||
Every Unreal C++ closeout should say one of these explicitly:
|
Every Unreal C++ closeout should say one of these explicitly:
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,18 @@ Current truthful product wording should say:
|
||||||
- full VR/controller/settings polish lane: not yet complete enough to market as
|
- full VR/controller/settings polish lane: not yet complete enough to market as
|
||||||
finished
|
finished
|
||||||
|
|
||||||
|
## Public-surface consequence
|
||||||
|
|
||||||
|
The same truth should remain visible on the public website and public operator
|
||||||
|
manual:
|
||||||
|
|
||||||
|
- docs/resources/support surfaces should explicitly distinguish current keyboard
|
||||||
|
and higher-dimensional control ownership from unfinished VR/controller claims
|
||||||
|
- public copy should not imply that `EnhancedInput` plus motion-controller axis
|
||||||
|
groundwork already equals a finished OpenXR/runtime or rebinding lane
|
||||||
|
- browser/distribution pages may describe the simulator input posture, but they
|
||||||
|
must not market browser control parity with the native runtime
|
||||||
|
|
||||||
## Next clean implementation packet
|
## Next clean implementation packet
|
||||||
|
|
||||||
If the project wants to raise this lane from “groundwork exists” to “shipping
|
If the project wants to raise this lane from “groundwork exists” to “shipping
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
82
scripts/bootstrap-hypertwist-sentrux.sh
Normal file
82
scripts/bootstrap-hypertwist-sentrux.sh
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
dest_dir="$repo_root/tools/sentrux/bin"
|
||||||
|
dest_linux="$dest_dir/sentrux"
|
||||||
|
dest_windows="$dest_dir/sentrux.exe"
|
||||||
|
|
||||||
|
vector_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux"
|
||||||
|
vector_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml"
|
||||||
|
scriptorium_sentrux_windows="/home/dev/src/ScriptoriumAI/sentrux.exe"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
scripts/bootstrap-hypertwist-sentrux.sh [--if-missing]
|
||||||
|
|
||||||
|
Materializes a repo-local sentrux binary under tools/sentrux/bin/ using the
|
||||||
|
best available retained source on this machine.
|
||||||
|
|
||||||
|
Resolution order:
|
||||||
|
1. existing VectorShell Linux build artifact
|
||||||
|
2. local VectorShell source build via cargo
|
||||||
|
3. local ScriptoriumAI Windows binary
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_binary() {
|
||||||
|
local source_path="$1"
|
||||||
|
local destination_path="$2"
|
||||||
|
|
||||||
|
mkdir -p "$dest_dir"
|
||||||
|
cp "$source_path" "$destination_path"
|
||||||
|
chmod +x "$destination_path" 2>/dev/null || true
|
||||||
|
printf 'Materialized sentrux at %s from %s\n' "$destination_path" "$source_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--if-missing" ]]; then
|
||||||
|
if [[ -x "$dest_linux" || -x "$dest_windows" ]]; then
|
||||||
|
printf 'Repo-local sentrux already present under %s\n' "$dest_dir"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
elif [[ $# -gt 0 ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$vector_sentrux_binary" ]]; then
|
||||||
|
copy_binary "$vector_sentrux_binary" "$dest_linux"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v cargo >/dev/null 2>&1 && [[ -f "$vector_sentrux_manifest" ]]; then
|
||||||
|
cargo build --quiet --release --manifest-path "$vector_sentrux_manifest" --bin sentrux
|
||||||
|
if [[ -x "$vector_sentrux_binary" ]]; then
|
||||||
|
copy_binary "$vector_sentrux_binary" "$dest_linux"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$scriptorium_sentrux_windows" ]]; then
|
||||||
|
copy_binary "$scriptorium_sentrux_windows" "$dest_windows"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Unable to materialize a repo-local sentrux binary.
|
||||||
|
|
||||||
|
Checked:
|
||||||
|
- $vector_sentrux_binary
|
||||||
|
- $vector_sentrux_manifest
|
||||||
|
- $scriptorium_sentrux_windows
|
||||||
|
|
||||||
|
Provide HYPERTWIST_SENTRUX_BINARY directly or place a compatible sentrux binary
|
||||||
|
under tools/sentrux/bin/.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
102
scripts/run-hypertwist-remote-unreal-automation-sequence.sh
Normal file
102
scripts/run-hypertwist-remote-unreal-automation-sequence.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-unreal-automation-sequence.sh \
|
||||||
|
--filter HyperTwist.FirstParty.Skill.PhaseS3A.ContinuityResumeCoverage \
|
||||||
|
--filter HyperTwist.FirstParty.Skill.PhaseS3A.AuthoritativeStoreRead
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--filter <automation-filter>
|
||||||
|
Required. May be repeated. Filters are run sequentially in the order given.
|
||||||
|
|
||||||
|
--report-prefix <name>
|
||||||
|
Optional prefix for per-filter report names.
|
||||||
|
|
||||||
|
--extra-arg <value>
|
||||||
|
Append an extra UnrealEditor-Cmd.exe argument to every filter run.
|
||||||
|
|
||||||
|
--dry-run
|
||||||
|
Print the underlying wrapper invocations instead of executing them.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
filters=()
|
||||||
|
report_prefix=""
|
||||||
|
dry_run="false"
|
||||||
|
extra_args=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--filter)
|
||||||
|
filters+=("${2:-}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--report-prefix)
|
||||||
|
report_prefix="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--extra-arg)
|
||||||
|
extra_args+=("${2:-}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#filters[@]} -eq 0 ]]; then
|
||||||
|
echo "Provide at least one --filter." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
single_wrapper="${script_dir}/run-hypertwist-remote-unreal-automation.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "$single_wrapper" ]]; then
|
||||||
|
echo "Expected executable wrapper at $single_wrapper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sanitize_report_name() {
|
||||||
|
printf '%s' "$1" | sed 's/[^[:alnum:]._+-]/_/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
for filter in "${filters[@]}"; do
|
||||||
|
report_name="$(sanitize_report_name "$filter")"
|
||||||
|
if [[ -n "$report_prefix" ]]; then
|
||||||
|
report_name="${report_prefix}-$(sanitize_report_name "$filter")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
command=(
|
||||||
|
"$single_wrapper"
|
||||||
|
--filter "$filter"
|
||||||
|
--report-name "$report_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
for extra_arg in "${extra_args[@]}"; do
|
||||||
|
command+=(--extra-arg "$extra_arg")
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$dry_run" == "true" ]]; then
|
||||||
|
printf '%q ' "${command[@]}"
|
||||||
|
printf '\n'
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running remote Unreal automation filter: $filter"
|
||||||
|
"${command[@]}"
|
||||||
|
done
|
||||||
161
scripts/run-hypertwist-remote-unreal-automation.sh
Normal file
161
scripts/run-hypertwist-remote-unreal-automation.sh
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-unreal-automation.sh --filter HyperTwist.FirstParty.Skill
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--filter <automation-filter>
|
||||||
|
Required Unreal automation filter.
|
||||||
|
|
||||||
|
--report-name <name>
|
||||||
|
Optional report/log folder suffix. Defaults to a sanitized filter name.
|
||||||
|
|
||||||
|
--extra-arg <value>
|
||||||
|
Append an extra UnrealEditor-Cmd.exe argument. May be repeated.
|
||||||
|
|
||||||
|
--dry-run
|
||||||
|
Print the generated remote PowerShell payload instead of executing it.
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required by the underlying tunnel wrapper.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT Optional, defaults to C:\HyperTwist_worktrees\phase10validate
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH Optional, defaults beneath the worktree root.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_UNREAL_EDITOR_CMD
|
||||||
|
Optional, defaults to the UE 5.7 UnrealEditor-Cmd.exe path.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_AUTOMATION_REPORT_ROOT
|
||||||
|
Optional, defaults beneath Saved\AutomationReports.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_LOG_ROOT Optional, defaults beneath Saved\Logs.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
filter=""
|
||||||
|
report_name=""
|
||||||
|
dry_run="false"
|
||||||
|
extra_args=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--filter)
|
||||||
|
filter="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--report-name)
|
||||||
|
report_name="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--extra-arg)
|
||||||
|
extra_args+=("${2:-}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$filter" ]]; then
|
||||||
|
echo "Provide --filter." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
remote_ps_wrapper="${script_dir}/run-hypertwist-remote-windows-powershell.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "$remote_ps_wrapper" ]]; then
|
||||||
|
echo "Expected executable wrapper at $remote_ps_wrapper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
worktree_root="${HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT:-C:\\HyperTwist_worktrees\\phase10validate}"
|
||||||
|
project_path="${HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH:-${worktree_root}\\UnrealHyperTwist\\UnrealHyperTwist.uproject}"
|
||||||
|
editor_cmd_path="${HYPERTWIST_REMOTE_WINDOWS_UNREAL_EDITOR_CMD:-C:\\Program Files\\Epic Games\\UE_5.7\\Engine\\Binaries\\Win64\\UnrealEditor-Cmd.exe}"
|
||||||
|
report_root="${HYPERTWIST_REMOTE_WINDOWS_AUTOMATION_REPORT_ROOT:-${worktree_root}\\UnrealHyperTwist\\Saved\\AutomationReports}"
|
||||||
|
log_root="${HYPERTWIST_REMOTE_WINDOWS_LOG_ROOT:-${worktree_root}\\UnrealHyperTwist\\Saved\\Logs}"
|
||||||
|
|
||||||
|
if [[ -z "$report_name" ]]; then
|
||||||
|
report_name="$(printf '%s' "$filter" | sed 's/[^[:alnum:]._+-]/_/g')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
report_export_path="${report_root}\\${report_name}"
|
||||||
|
abs_log_path="${log_root}\\${report_name}.log"
|
||||||
|
|
||||||
|
powershell_payload="$(
|
||||||
|
python3 - <<'PY' \
|
||||||
|
"$editor_cmd_path" \
|
||||||
|
"$project_path" \
|
||||||
|
"$report_export_path" \
|
||||||
|
"$abs_log_path" \
|
||||||
|
"$filter" \
|
||||||
|
"${extra_args[@]}"
|
||||||
|
import sys
|
||||||
|
|
||||||
|
editor_cmd_path, project_path, report_export_path, abs_log_path, filter_name, *extra_args = sys.argv[1:]
|
||||||
|
|
||||||
|
def ps_single_quote(value: str) -> str:
|
||||||
|
return "'" + value.replace("'", "''") + "'"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"$EditorCmdPath = {ps_single_quote(editor_cmd_path)}",
|
||||||
|
f"$ProjectPath = {ps_single_quote(project_path)}",
|
||||||
|
f"$ReportExportPath = {ps_single_quote(report_export_path)}",
|
||||||
|
f"$AbsLogPath = {ps_single_quote(abs_log_path)}",
|
||||||
|
f"$Filter = {ps_single_quote(filter_name)}",
|
||||||
|
"",
|
||||||
|
"New-Item -ItemType Directory -Force -Path $ReportExportPath | Out-Null",
|
||||||
|
"New-Item -ItemType Directory -Force -Path (Split-Path -Parent $AbsLogPath) | Out-Null",
|
||||||
|
"",
|
||||||
|
"if (-not (Test-Path -LiteralPath $EditorCmdPath)) {",
|
||||||
|
" throw \"UnrealEditor-Cmd.exe was not found at '$EditorCmdPath'.\"",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"if (-not (Test-Path -LiteralPath $ProjectPath)) {",
|
||||||
|
" throw \"Project file was not found at '$ProjectPath'.\"",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"& $EditorCmdPath `",
|
||||||
|
" $ProjectPath `",
|
||||||
|
" -unattended `",
|
||||||
|
" -nop4 `",
|
||||||
|
" -nosplash `",
|
||||||
|
" -NullRHI `",
|
||||||
|
" -log `",
|
||||||
|
" -stdout `",
|
||||||
|
" -FullStdOutLogOutput `",
|
||||||
|
" \"-AbsLog=$AbsLogPath\" `",
|
||||||
|
" \"-ReportExportPath=$ReportExportPath\" `",
|
||||||
|
" \"-ExecCmds=Automation RunTests $Filter\" `",
|
||||||
|
]
|
||||||
|
|
||||||
|
if extra_args:
|
||||||
|
for extra_arg in extra_args:
|
||||||
|
lines.append(f" {ps_single_quote(extra_arg)} `")
|
||||||
|
|
||||||
|
lines.extend([
|
||||||
|
" \"-TestExit=Automation Test Queue Empty\"",
|
||||||
|
"",
|
||||||
|
"exit $LASTEXITCODE",
|
||||||
|
])
|
||||||
|
|
||||||
|
print("\n".join(lines))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$dry_run" == "true" ]]; then
|
||||||
|
printf '%s\n' "$powershell_payload"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$remote_ps_wrapper" --command "$powershell_payload"
|
||||||
213
scripts/run-hypertwist-remote-unreal-build.sh
Normal file
213
scripts/run-hypertwist-remote-unreal-build.sh
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-unreal-build.sh
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--target <name>
|
||||||
|
Optional Unreal build target. Defaults to UnrealHyperTwistEditor.
|
||||||
|
|
||||||
|
--platform <name>
|
||||||
|
Optional Unreal platform. Defaults to Win64.
|
||||||
|
|
||||||
|
--configuration <name>
|
||||||
|
Optional Unreal configuration. Defaults to Development.
|
||||||
|
|
||||||
|
--worktree-root <path>
|
||||||
|
Optional Windows worktree root. Defaults to C:\HyperTwist_worktrees\phase10validate.
|
||||||
|
|
||||||
|
--project-path <path>
|
||||||
|
Optional Windows .uproject path. Defaults beneath the worktree root.
|
||||||
|
|
||||||
|
--build-batch-path <path>
|
||||||
|
Optional Build.bat path. Defaults to the UE 5.7 installed-engine path.
|
||||||
|
|
||||||
|
--max-parallel-actions <count>
|
||||||
|
Optional MaxParallelActions override. Defaults to 2 for the remote lane.
|
||||||
|
|
||||||
|
--allow-uba
|
||||||
|
Allow UBA instead of forcing the verified remote-lane -NoUBA posture.
|
||||||
|
|
||||||
|
--extra-arg <value>
|
||||||
|
Append an extra Build.bat argument. May be repeated.
|
||||||
|
|
||||||
|
--dry-run
|
||||||
|
Print the generated remote PowerShell payload instead of executing it.
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required by the underlying tunnel wrapper.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT Optional default for --worktree-root.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH Optional default for --project-path.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_BUILD_BAT Optional default for --build-batch-path.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_MAX_PARALLEL_ACTIONS
|
||||||
|
Optional default for --max-parallel-actions.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
target_name="UnrealHyperTwistEditor"
|
||||||
|
platform_name="Win64"
|
||||||
|
configuration_name="Development"
|
||||||
|
worktree_root="${HYPERTWIST_REMOTE_WINDOWS_WORKTREE_ROOT:-C:\\HyperTwist_worktrees\\phase10validate}"
|
||||||
|
project_path=""
|
||||||
|
build_batch_path="${HYPERTWIST_REMOTE_WINDOWS_BUILD_BAT:-C:\\Program Files\\Epic Games\\UE_5.7\\Engine\\Build\\BatchFiles\\Build.bat}"
|
||||||
|
max_parallel_actions="${HYPERTWIST_REMOTE_WINDOWS_MAX_PARALLEL_ACTIONS:-2}"
|
||||||
|
allow_uba="false"
|
||||||
|
dry_run="false"
|
||||||
|
extra_args=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--target)
|
||||||
|
target_name="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--platform)
|
||||||
|
platform_name="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--configuration)
|
||||||
|
configuration_name="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--worktree-root)
|
||||||
|
worktree_root="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--project-path)
|
||||||
|
project_path="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--build-batch-path)
|
||||||
|
build_batch_path="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--max-parallel-actions)
|
||||||
|
max_parallel_actions="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--allow-uba)
|
||||||
|
allow_uba="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--extra-arg)
|
||||||
|
extra_args+=("${2:-}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$project_path" ]]; then
|
||||||
|
project_path="${HYPERTWIST_REMOTE_WINDOWS_PROJECT_PATH:-${worktree_root}\\UnrealHyperTwist\\UnrealHyperTwist.uproject}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$target_name" || -z "$platform_name" || -z "$configuration_name" || -z "$project_path" || -z "$build_batch_path" ]]; then
|
||||||
|
echo "Target, platform, configuration, project path, and Build.bat path must all be populated." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$max_parallel_actions" && ! "$max_parallel_actions" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "--max-parallel-actions must be numeric." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
remote_ps_wrapper="${script_dir}/run-hypertwist-remote-windows-powershell.sh"
|
||||||
|
|
||||||
|
if [[ ! -x "$remote_ps_wrapper" ]]; then
|
||||||
|
echo "Expected executable wrapper at $remote_ps_wrapper" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
powershell_payload="$(
|
||||||
|
python3 - <<'PY' \
|
||||||
|
"$build_batch_path" \
|
||||||
|
"$target_name" \
|
||||||
|
"$platform_name" \
|
||||||
|
"$configuration_name" \
|
||||||
|
"$project_path" \
|
||||||
|
"$max_parallel_actions" \
|
||||||
|
"$allow_uba" \
|
||||||
|
"${extra_args[@]}"
|
||||||
|
import sys
|
||||||
|
|
||||||
|
build_batch_path, target_name, platform_name, configuration_name, project_path, max_parallel_actions, allow_uba, *extra_args = sys.argv[1:]
|
||||||
|
|
||||||
|
def ps_single_quote(value: str) -> str:
|
||||||
|
return "'" + value.replace("'", "''") + "'"
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"$BuildBatchPath = {ps_single_quote(build_batch_path)}",
|
||||||
|
f"$TargetName = {ps_single_quote(target_name)}",
|
||||||
|
f"$PlatformName = {ps_single_quote(platform_name)}",
|
||||||
|
f"$ConfigurationName = {ps_single_quote(configuration_name)}",
|
||||||
|
f"$ProjectPath = {ps_single_quote(project_path)}",
|
||||||
|
f"$MaxParallelActions = {max_parallel_actions}",
|
||||||
|
f"$AllowUba = ${'true' if allow_uba == 'true' else 'false'}",
|
||||||
|
"$ExtraArgs = @(",
|
||||||
|
]
|
||||||
|
|
||||||
|
for value in extra_args:
|
||||||
|
lines.append(f" {ps_single_quote(value)}")
|
||||||
|
|
||||||
|
lines.extend([
|
||||||
|
")",
|
||||||
|
"",
|
||||||
|
"if (-not (Test-Path -LiteralPath $BuildBatchPath)) {",
|
||||||
|
" throw \"Build.bat was not found at '$BuildBatchPath'.\"",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"if (-not (Test-Path -LiteralPath $ProjectPath)) {",
|
||||||
|
" throw \"Project file was not found at '$ProjectPath'.\"",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"$BuildArgs = @(",
|
||||||
|
" $TargetName",
|
||||||
|
" $PlatformName",
|
||||||
|
" $ConfigurationName",
|
||||||
|
" $ProjectPath",
|
||||||
|
" '-WaitMutex'",
|
||||||
|
" '-NoHotReloadFromIDE'",
|
||||||
|
")",
|
||||||
|
"",
|
||||||
|
"if (-not $AllowUba) {",
|
||||||
|
" $BuildArgs += '-NoUBA'",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"if ($MaxParallelActions -gt 0) {",
|
||||||
|
" $BuildArgs += \"-MaxParallelActions=$MaxParallelActions\"",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"if ($ExtraArgs.Count -gt 0) {",
|
||||||
|
" $BuildArgs += $ExtraArgs",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"& $BuildBatchPath @BuildArgs",
|
||||||
|
"exit $LASTEXITCODE",
|
||||||
|
])
|
||||||
|
|
||||||
|
print("\n".join(lines))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ "$dry_run" == "true" ]]; then
|
||||||
|
printf '%s\n' "$powershell_payload"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$remote_ps_wrapper" --command "$powershell_payload"
|
||||||
142
scripts/run-hypertwist-remote-windows-file-sync.sh
Normal file
142
scripts/run-hypertwist-remote-windows-file-sync.sh
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-windows-file-sync.sh \
|
||||||
|
--file UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--file <path>
|
||||||
|
Relative repo path to copy into the remote Windows worktree. May be repeated.
|
||||||
|
|
||||||
|
--remote-root <path>
|
||||||
|
Optional Windows worktree root. Defaults to C:\HyperTwist_worktrees\phase10validate.
|
||||||
|
|
||||||
|
--dry-run
|
||||||
|
Print the copy plan without transferring files.
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required password for the reverse-SSH Windows user.
|
||||||
|
HYPERTWIST_REMOTE_TUNNEL_PORT Optional, defaults to 22022.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_USER Optional, defaults to "anthracite ace".
|
||||||
|
HYPERTWIST_REMOTE_TUNNEL_HOST Optional, defaults to "localhost".
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v sshpass >/dev/null 2>&1; then
|
||||||
|
echo "sshpass is required but was not found on PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
echo "sha256sum is required but was not found on PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD:-}" ]]; then
|
||||||
|
echo "Set HYPERTWIST_REMOTE_WINDOWS_PASSWORD before using this wrapper." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_root='C:\HyperTwist_worktrees\phase10validate'
|
||||||
|
chunk_size_bytes=65536
|
||||||
|
dry_run="false"
|
||||||
|
files=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--file)
|
||||||
|
files+=("${2:-}")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--remote-root)
|
||||||
|
remote_root="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
dry_run="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#files[@]} -eq 0 ]]; then
|
||||||
|
echo "Provide at least one --file." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_port="${HYPERTWIST_REMOTE_TUNNEL_PORT:-22022}"
|
||||||
|
remote_user="${HYPERTWIST_REMOTE_WINDOWS_USER:-anthracite ace}"
|
||||||
|
remote_host="${HYPERTWIST_REMOTE_TUNNEL_HOST:-localhost}"
|
||||||
|
|
||||||
|
for relative_path in "${files[@]}"; do
|
||||||
|
if [[ "$relative_path" = /* ]]; then
|
||||||
|
echo "Use repo-relative paths only: $relative_path" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$relative_path" ]]; then
|
||||||
|
echo "File not found: $relative_path" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
windows_relative_path="${relative_path//\//\\}"
|
||||||
|
windows_dest_path="${remote_root}\\${windows_relative_path}"
|
||||||
|
local_hash="$(sha256sum "$relative_path" | awk '{print tolower($1)}')"
|
||||||
|
|
||||||
|
if [[ "$dry_run" == "true" ]]; then
|
||||||
|
printf '%s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssh_base=(
|
||||||
|
sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}"
|
||||||
|
ssh
|
||||||
|
-o StrictHostKeyChecking=no
|
||||||
|
-o PreferredAuthentications=password
|
||||||
|
-o PubkeyAuthentication=no
|
||||||
|
-p "${remote_port}"
|
||||||
|
-l "${remote_user}"
|
||||||
|
"${remote_host}"
|
||||||
|
)
|
||||||
|
|
||||||
|
"${ssh_base[@]}" \
|
||||||
|
"powershell -NoProfile -Command \"\$dest='${windows_dest_path}'; \$destDir = Split-Path -Parent \$dest; New-Item -ItemType Directory -Force -Path \$destDir | Out-Null; \$outputStream = [IO.File]::Open(\$dest, [IO.FileMode]::Create, [IO.FileAccess]::Write, [IO.FileShare]::None); \$outputStream.Dispose()\""
|
||||||
|
|
||||||
|
tmp_chunk_dir="$(mktemp -d)"
|
||||||
|
split -b "${chunk_size_bytes}" --numeric-suffixes=1 --suffix-length=4 \
|
||||||
|
"$relative_path" "${tmp_chunk_dir}/chunk-"
|
||||||
|
|
||||||
|
for chunk_path in "${tmp_chunk_dir}"/chunk-*; do
|
||||||
|
cat "$chunk_path" | \
|
||||||
|
"${ssh_base[@]}" \
|
||||||
|
"powershell -NoProfile -Command \"\$dest='${windows_dest_path}'; \$outputStream = [IO.File]::Open(\$dest, [IO.FileMode]::Append, [IO.FileAccess]::Write, [IO.FileShare]::None); try { [Console]::OpenStandardInput().CopyTo(\$outputStream) } finally { \$outputStream.Dispose() }\""
|
||||||
|
done
|
||||||
|
|
||||||
|
remote_hash="$(
|
||||||
|
"${ssh_base[@]}" \
|
||||||
|
"powershell -NoProfile -Command \"[Console]::Out.Write((Get-FileHash -LiteralPath '${windows_dest_path}' -Algorithm SHA256).Hash.ToLowerInvariant())\""
|
||||||
|
)"
|
||||||
|
|
||||||
|
rm -rf "$tmp_chunk_dir"
|
||||||
|
|
||||||
|
if [[ "$remote_hash" != "$local_hash" ]]; then
|
||||||
|
echo "Hash mismatch for $relative_path" >&2
|
||||||
|
echo " local : $local_hash" >&2
|
||||||
|
echo " remote: $remote_hash" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'Synced %s -> %s (%s)\n' "$relative_path" "$windows_dest_path" "$local_hash"
|
||||||
|
done
|
||||||
90
scripts/run-hypertwist-remote-windows-powershell.sh
Normal file
90
scripts/run-hypertwist-remote-windows-powershell.sh
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-windows-powershell.sh --command "Write-Output 'hello'"
|
||||||
|
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD=... \
|
||||||
|
scripts/run-hypertwist-remote-windows-powershell.sh --script-file ./script.ps1
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--command <powershell>
|
||||||
|
Run the provided PowerShell source text.
|
||||||
|
|
||||||
|
--script-file <path>
|
||||||
|
Read PowerShell source from the given file.
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_PASSWORD Required password for the reverse-SSH Windows user.
|
||||||
|
HYPERTWIST_REMOTE_TUNNEL_PORT Optional, defaults to 22022.
|
||||||
|
HYPERTWIST_REMOTE_WINDOWS_USER Optional, defaults to "anthracite ace".
|
||||||
|
HYPERTWIST_REMOTE_TUNNEL_HOST Optional, defaults to "localhost".
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v sshpass >/dev/null 2>&1; then
|
||||||
|
echo "sshpass is required but was not found on PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD:-}" ]]; then
|
||||||
|
echo "Set HYPERTWIST_REMOTE_WINDOWS_PASSWORD before using this wrapper." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_text=""
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
--command)
|
||||||
|
script_text="$2"
|
||||||
|
;;
|
||||||
|
--script-file)
|
||||||
|
if [[ ! -f "$2" ]]; then
|
||||||
|
echo "Script file not found: $2" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
script_text="$(<"$2")"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
remote_port="${HYPERTWIST_REMOTE_TUNNEL_PORT:-22022}"
|
||||||
|
remote_user="${HYPERTWIST_REMOTE_WINDOWS_USER:-anthracite ace}"
|
||||||
|
remote_host="${HYPERTWIST_REMOTE_TUNNEL_HOST:-localhost}"
|
||||||
|
script_preamble=$'$ProgressPreference = \'SilentlyContinue\'\n$ErrorActionPreference = \'Stop\'\n'
|
||||||
|
script_payload="${script_preamble}${script_text}"
|
||||||
|
|
||||||
|
encoded_command="$(
|
||||||
|
python3 - <<'PY' "$script_payload"
|
||||||
|
import base64
|
||||||
|
import sys
|
||||||
|
|
||||||
|
script = sys.argv[1]
|
||||||
|
print(base64.b64encode(script.encode("utf-16le")).decode("ascii"))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
exec sshpass -p "${HYPERTWIST_REMOTE_WINDOWS_PASSWORD}" \
|
||||||
|
ssh \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o PreferredAuthentications=password \
|
||||||
|
-o PubkeyAuthentication=no \
|
||||||
|
-p "${remote_port}" \
|
||||||
|
-l "${remote_user}" \
|
||||||
|
"${remote_host}" \
|
||||||
|
"powershell -NoProfile -EncodedCommand ${encoded_command}"
|
||||||
|
|
@ -5,6 +5,8 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
temp_root="${TMPDIR:-/tmp}/hypertwist-sentrux-source-only"
|
temp_root="${TMPDIR:-/tmp}/hypertwist-sentrux-source-only"
|
||||||
repo_local_sentrux_binary="$repo_root/sentrux"
|
repo_local_sentrux_binary="$repo_root/sentrux"
|
||||||
repo_local_sentrux_windows_binary="$repo_root/sentrux.exe"
|
repo_local_sentrux_windows_binary="$repo_root/sentrux.exe"
|
||||||
|
repo_tools_sentrux_binary="$repo_root/tools/sentrux/bin/sentrux"
|
||||||
|
repo_tools_sentrux_windows_binary="$repo_root/tools/sentrux/bin/sentrux.exe"
|
||||||
local_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux"
|
local_sentrux_binary="/home/dev/src/VectorShell/sentrux/target/release/sentrux"
|
||||||
local_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml"
|
local_sentrux_manifest="/home/dev/src/VectorShell/sentrux/Cargo.toml"
|
||||||
|
|
||||||
|
|
@ -24,6 +26,16 @@ resolve_sentrux_command() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$repo_tools_sentrux_binary" ]]; then
|
||||||
|
printf '%s\n' "$repo_tools_sentrux_binary"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$repo_tools_sentrux_windows_binary" ]]; then
|
||||||
|
printf '%s\n' "$repo_tools_sentrux_windows_binary"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
if command -v sentrux >/dev/null 2>&1; then
|
if command -v sentrux >/dev/null 2>&1; then
|
||||||
printf 'sentrux\n'
|
printf 'sentrux\n'
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -43,7 +55,7 @@ resolve_sentrux_command() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sentrux_command="$(resolve_sentrux_command)" || {
|
sentrux_command="$(resolve_sentrux_command)" || {
|
||||||
echo "Unable to locate sentrux. Provide HYPERTWIST_SENTRUX_BINARY, add sentrux to PATH, place a repo-local sentrux binary at $repo_root, or keep /home/dev/src/VectorShell/sentrux available." >&2
|
echo "Unable to locate sentrux. Provide HYPERTWIST_SENTRUX_BINARY, run scripts/bootstrap-hypertwist-sentrux.sh, add sentrux to PATH, place a repo-local sentrux binary at $repo_root, or keep /home/dev/src/VectorShell/sentrux available." >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
tools/sentrux/README.md
Normal file
15
tools/sentrux/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# HyperTwist-local sentrux landing zone
|
||||||
|
|
||||||
|
This directory is the HyperTwist-owned location for a repo-local `sentrux`
|
||||||
|
binary.
|
||||||
|
|
||||||
|
Do not commit platform binaries here.
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/bootstrap-hypertwist-sentrux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That helper materializes a local binary under `tools/sentrux/bin/` from the
|
||||||
|
best retained source available on this machine.
|
||||||
1
tools/sentrux/bin/.gitkeep
Normal file
1
tools/sentrux/bin/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { cleanup, render, screen } from '@testing-library/react'
|
import { cleanup, render, screen } from '@testing-library/react'
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { MemoryRouter } from 'react-router-dom'
|
import { MemoryRouter } from 'react-router-dom'
|
||||||
|
import { ROUTER_FUTURE_FLAGS } from '../router/router-future'
|
||||||
|
|
||||||
const mockUsePlatformAuth = vi.fn()
|
const mockUsePlatformAuth = vi.fn()
|
||||||
const mockGetAuthHealth = vi.fn()
|
const mockGetAuthHealth = vi.fn()
|
||||||
|
|
@ -31,7 +32,7 @@ function renderPage(page: React.ReactNode, initialEntry: string) {
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<MemoryRouter initialEntries={[initialEntry]}>
|
<MemoryRouter initialEntries={[initialEntry]} future={ROUTER_FUTURE_FLAGS}>
|
||||||
{page}
|
{page}
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
|
|
|
||||||
|
|
@ -547,6 +547,8 @@ describe('public marketing pages', () => {
|
||||||
expect(screen.getByText('Selected help lane')).toBeTruthy()
|
expect(screen.getByText('Selected help lane')).toBeTruthy()
|
||||||
expect(screen.getByText('Launch readiness')).toBeTruthy()
|
expect(screen.getByText('Launch readiness')).toBeTruthy()
|
||||||
expect(screen.getByText(/turning the preview lane into a public launch/i)).toBeTruthy()
|
expect(screen.getByText(/turning the preview lane into a public launch/i)).toBeTruthy()
|
||||||
|
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy()
|
||||||
expect(screen.getByText('Support lanes')).toBeTruthy()
|
expect(screen.getByText('Support lanes')).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -650,6 +652,10 @@ describe('public marketing pages', () => {
|
||||||
expect(screen.getAllByText(/MagicCube5D dedicated-family training map: passed/i).length).toBeGreaterThan(0)
|
expect(screen.getAllByText(/MagicCube5D dedicated-family training map: passed/i).length).toBeGreaterThan(0)
|
||||||
expect(screen.getByText('Operator playbooks')).toBeTruthy()
|
expect(screen.getByText('Operator playbooks')).toBeTruthy()
|
||||||
expect(screen.getByText('Simulator use today')).toBeTruthy()
|
expect(screen.getByText('Simulator use today')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Higher-dimensional runtime guide')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Current control and device posture')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Deployment readiness snapshot')).toBeTruthy()
|
||||||
|
expect(screen.getByText('XR groundwork exists, but the full VR lane is not finished')).toBeTruthy()
|
||||||
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -715,6 +721,10 @@ describe('public marketing pages', () => {
|
||||||
expect(screen.getByText('Operator manual')).toBeTruthy()
|
expect(screen.getByText('Operator manual')).toBeTruthy()
|
||||||
expect(screen.getByText('1. Start in the browser shell')).toBeTruthy()
|
expect(screen.getByText('1. Start in the browser shell')).toBeTruthy()
|
||||||
expect(screen.getByText('Simulator manual')).toBeTruthy()
|
expect(screen.getByText('Simulator manual')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Higher-dimensional family guide')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Deployment readiness manual')).toBeTruthy()
|
||||||
|
expect(screen.getByText('Input and device posture')).toBeTruthy()
|
||||||
|
expect(screen.getAllByText('Keyboard and mouse ship today').length).toBeGreaterThan(0)
|
||||||
expect(screen.getByText('Feature-registry-backed wording only')).toBeTruthy()
|
expect(screen.getByText('Feature-registry-backed wording only')).toBeTruthy()
|
||||||
expect(await screen.findByRole('link', { name: /open public docs portal/i })).toBeTruthy()
|
expect(await screen.findByRole('link', { name: /open public docs portal/i })).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
88
website/src/pages/public-page-helpers.tsx
Normal file
88
website/src/pages/public-page-helpers.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
brandConfig,
|
||||||
|
downloadTargets,
|
||||||
|
mplSourceUrl,
|
||||||
|
openSourceRepoUrl,
|
||||||
|
planCatalog,
|
||||||
|
publicDocsUrl,
|
||||||
|
releaseNotesUrl,
|
||||||
|
} from '../site-config'
|
||||||
|
import { isExternalHref } from '../site-routes'
|
||||||
|
|
||||||
|
export const supportTopicGuidance: Record<string, { title: string; description: string }> = {
|
||||||
|
'launch-readiness': {
|
||||||
|
title: 'Launch readiness',
|
||||||
|
description:
|
||||||
|
'Need help turning the preview lane into a public launch? We can walk through checkout wiring, download release targets, notices, and corresponding-source publication.',
|
||||||
|
},
|
||||||
|
'operator-access': {
|
||||||
|
title: 'Operator access',
|
||||||
|
description:
|
||||||
|
'Use this lane when you need operator checkout, entitlement enablement, or help reaching the protected desktop-download surface.',
|
||||||
|
},
|
||||||
|
'studio-rollout': {
|
||||||
|
title: 'Studio rollout',
|
||||||
|
description:
|
||||||
|
'Use this lane for higher-dimensional rollout planning, deployment coordination, or production-lane package and notice readiness.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const explorerFallbackPlan = planCatalog.find((plan) => plan.key === 'explorer') ?? planCatalog[0]
|
||||||
|
export const operatorFallbackPlan = planCatalog.find((plan) => plan.key === 'operator') ?? planCatalog[1]
|
||||||
|
export const studioFallbackPlan = planCatalog.find((plan) => plan.key === 'studio') ?? planCatalog[2]
|
||||||
|
|
||||||
|
export const releaseCommerceFallback = {
|
||||||
|
operatorCheckoutUrl: isExternalHref(operatorFallbackPlan.ctaHref) ? operatorFallbackPlan.ctaHref : '',
|
||||||
|
studioCheckoutUrl: isExternalHref(studioFallbackPlan.ctaHref) ? studioFallbackPlan.ctaHref : '',
|
||||||
|
planPriceOperator: operatorFallbackPlan.price,
|
||||||
|
planPriceStudio: studioFallbackPlan.price,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPublicReleaseManifestFallback(supportEmail = brandConfig.contact.email) {
|
||||||
|
return {
|
||||||
|
downloadTargets,
|
||||||
|
publicDocsUrl,
|
||||||
|
releaseNotesUrl,
|
||||||
|
correspondingSourceUrl: mplSourceUrl,
|
||||||
|
openSourceRepoUrl,
|
||||||
|
supportEmail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Section({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
children: ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="page-section">
|
||||||
|
<div className="section-heading">
|
||||||
|
<h2>{title}</h2>
|
||||||
|
{description ? <p>{description}</p> : null}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PlanActionLink({ href, label }: { href: string; label: string }) {
|
||||||
|
if (isExternalHref(href)) {
|
||||||
|
return (
|
||||||
|
<a className="button button--primary button--full" href={href} target="_blank" rel="noreferrer">
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link className="button button--primary button--full" to={href}>
|
||||||
|
{label}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
536
website/src/pages/public-pages-commerce.tsx
Normal file
536
website/src/pages/public-pages-commerce.tsx
Normal file
|
|
@ -0,0 +1,536 @@
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { getReleaseManifest } from '../auth/auth-api'
|
||||||
|
import { MarketingShell } from '../components/layout/MarketingShell'
|
||||||
|
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||||
|
import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus'
|
||||||
|
import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary'
|
||||||
|
import { paddleReadyDescription } from '../site-config'
|
||||||
|
import {
|
||||||
|
buildReleaseMetadataItems,
|
||||||
|
resolveReleaseCommerceView,
|
||||||
|
resolveReleaseManifestView,
|
||||||
|
} from '../release-manifest'
|
||||||
|
import { buildProtectedDownloadPath, buildSupportPath } from '../site-routes'
|
||||||
|
import {
|
||||||
|
deliverySurfaceCards,
|
||||||
|
desktopDownloadSteps,
|
||||||
|
desktopReleaseSignals,
|
||||||
|
digitalDeliveryCards,
|
||||||
|
distributionDoctrineCards,
|
||||||
|
openSourceNotices,
|
||||||
|
privacyBoundaryCards,
|
||||||
|
termsBoundaryCards,
|
||||||
|
} from '../site-data'
|
||||||
|
import {
|
||||||
|
buildPublicReleaseManifestFallback,
|
||||||
|
explorerFallbackPlan,
|
||||||
|
operatorFallbackPlan,
|
||||||
|
PlanActionLink,
|
||||||
|
releaseCommerceFallback,
|
||||||
|
Section,
|
||||||
|
studioFallbackPlan,
|
||||||
|
} from './public-page-helpers'
|
||||||
|
|
||||||
|
export function PricingPage() {
|
||||||
|
const releaseManifestQuery = useQuery({
|
||||||
|
queryKey: ['release-manifest', 'public'],
|
||||||
|
queryFn: getReleaseManifest,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const releaseCommerce = useMemo(
|
||||||
|
() => resolveReleaseCommerceView(releaseManifestQuery.data?.manifest, releaseCommerceFallback),
|
||||||
|
[releaseManifestQuery.data?.manifest],
|
||||||
|
)
|
||||||
|
const runtimePlanCatalog = useMemo(() => ([
|
||||||
|
explorerFallbackPlan,
|
||||||
|
{
|
||||||
|
...operatorFallbackPlan,
|
||||||
|
price: releaseCommerce.plan_price_operator,
|
||||||
|
ctaLabel: releaseCommerce.operator_checkout_url ? 'Open Paddle checkout' : 'Request operator access',
|
||||||
|
ctaHref: releaseCommerce.operator_checkout_url || buildSupportPath('operator-access'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...studioFallbackPlan,
|
||||||
|
price: releaseCommerce.plan_price_studio,
|
||||||
|
ctaLabel: releaseCommerce.studio_checkout_url ? 'Open Paddle checkout' : 'Talk to HyperTwist',
|
||||||
|
ctaHref: releaseCommerce.studio_checkout_url || buildSupportPath('studio-rollout'),
|
||||||
|
},
|
||||||
|
]), [releaseCommerce])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist pricing"
|
||||||
|
description="View HyperTwist browser-access, operator, and studio pricing with Paddle-ready checkout posture and launch-honest download/legal guidance."
|
||||||
|
canonicalPath="/pricing"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Paddle-ready plans"
|
||||||
|
title="Pricing that matches the actual delivery model."
|
||||||
|
lede="The browser shell handles account, release, and billing access. The simulator remains desktop-first. Prices and checkouts can be switched live through the same Paddle-ready structure used across the broader product family."
|
||||||
|
>
|
||||||
|
<Section title="Plan lineup" description={paddleReadyDescription}>
|
||||||
|
<div className="card-grid card-grid--pricing">
|
||||||
|
{runtimePlanCatalog.map((plan) => (
|
||||||
|
<article key={plan.key} className="card card--pricing">
|
||||||
|
<p className="status-pill">{plan.name}</p>
|
||||||
|
<h3>{plan.price}</h3>
|
||||||
|
<p>{plan.notes}</p>
|
||||||
|
<ul className="list">
|
||||||
|
{plan.features.map((feature) => (
|
||||||
|
<li key={feature}>{feature}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<PlanActionLink href={plan.ctaHref} label={plan.ctaLabel} />
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Launch posture">
|
||||||
|
<PublicLaunchStatus />
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Why plans live in the browser while training stays native"
|
||||||
|
description="Commercial access, entitlement, and launch-readiness posture belong to the browser shell so the simulator can stay focused on training quality."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deliverySurfaceCards.slice(0, 3).map((surface) => (
|
||||||
|
<article key={surface.title} className="card">
|
||||||
|
<h3>{surface.title}</h3>
|
||||||
|
<p>{surface.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Important launch note">
|
||||||
|
<article className="callout">
|
||||||
|
<p>
|
||||||
|
Public pricing, checkout, and download pages are distribution surfaces. Before external launch,
|
||||||
|
keep their legal footer and open-source notices link live and ensure the corresponding-source URL is configured for any downloadable build containing MPL-covered material.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DownloadPage() {
|
||||||
|
const releaseManifestQuery = useQuery({
|
||||||
|
queryKey: ['release-manifest', 'public'],
|
||||||
|
queryFn: getReleaseManifest,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const releaseManifest = useMemo(
|
||||||
|
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||||
|
[releaseManifestQuery.data?.manifest],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="Download HyperTwist desktop"
|
||||||
|
description="Download the HyperTwist desktop build, preserve your target platform into the protected release surface, and pair the installed app with your browser account."
|
||||||
|
canonicalPath="/download"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Desktop distribution"
|
||||||
|
title="Download the desktop build and pair it with your browser account."
|
||||||
|
lede="The website provides account, release, and legal surfaces. The actual simulator ships through the desktop lane, with package validation and release discipline carried over from the HyperTwist roadmap."
|
||||||
|
>
|
||||||
|
{releaseManifestQuery.isLoading ? (
|
||||||
|
<Section title="Release manifest status">
|
||||||
|
<article className="callout">
|
||||||
|
<p>Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.</p>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{releaseManifestQuery.isError ? (
|
||||||
|
<Section title="Release manifest status">
|
||||||
|
<article className="callout">
|
||||||
|
<p>
|
||||||
|
The live release manifest could not be loaded from the auth server right now.
|
||||||
|
This page is showing bounded fallback site metadata instead of current runtime release authority.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Section title="Available targets">
|
||||||
|
<div className="card-grid">
|
||||||
|
{releaseManifest.platforms.map((platform) => {
|
||||||
|
const metadataItems = buildReleaseMetadataItems(platform)
|
||||||
|
return (
|
||||||
|
<article key={platform.platform_key} className="card">
|
||||||
|
<h3>{platform.platform}</h3>
|
||||||
|
<p className="status-pill">{platform.subtitle}</p>
|
||||||
|
<p>{platform.details}</p>
|
||||||
|
{metadataItems.length > 0 ? (
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{metadataItems.map((item) => (
|
||||||
|
<li key={`${platform.platform_key}-${item.label}`}>
|
||||||
|
{item.label}: {item.value}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
<ReleaseValidationSummary platform={platform} />
|
||||||
|
{platform.configured ? (
|
||||||
|
<Link className="button button--primary button--full" to={buildProtectedDownloadPath(platform.platform_key)}>
|
||||||
|
Sign in for {platform.platform} access
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className="button button--ghost button--full is-disabled" aria-disabled="true">
|
||||||
|
Release URL not configured yet
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="How the release lane works">
|
||||||
|
<div className="card-grid">
|
||||||
|
{desktopDownloadSteps.map((step, index) => (
|
||||||
|
<article key={step} className="card card--compact">
|
||||||
|
<p className="status-pill">{index + 1}</p>
|
||||||
|
<p>{step}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Release posture">
|
||||||
|
<PublicLaunchStatus title="Desktop release access stays launch-honest" />
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Browser and desktop responsibilities"
|
||||||
|
description="The release lane is easier to trust when the site explains why some actions stay public, some stay protected, and the simulator itself stays native."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deliverySurfaceCards.map((surface) => (
|
||||||
|
<article key={surface.title} className="card">
|
||||||
|
<h3>{surface.title}</h3>
|
||||||
|
<p>{surface.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Release integrity and documentation">
|
||||||
|
<div className="card-grid">
|
||||||
|
{desktopReleaseSignals.map((signal) => (
|
||||||
|
<article key={signal.title} className="card">
|
||||||
|
<h3>{signal.title}</h3>
|
||||||
|
<p>{signal.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
<article className="card">
|
||||||
|
<h3>Operator rollout references</h3>
|
||||||
|
<ul className="list">
|
||||||
|
{releaseManifest.public_docs_url ? (
|
||||||
|
<li>
|
||||||
|
Public docs:{' '}
|
||||||
|
<a href={releaseManifest.public_docs_url} target="_blank" rel="noreferrer">
|
||||||
|
{releaseManifest.public_docs_url}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>Public docs URL not configured yet.</li>
|
||||||
|
)}
|
||||||
|
{releaseManifest.release_notes_url ? (
|
||||||
|
<li>
|
||||||
|
Release notes:{' '}
|
||||||
|
<a href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
|
||||||
|
{releaseManifest.release_notes_url}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>Release notes URL not configured yet.</li>
|
||||||
|
)}
|
||||||
|
<li>
|
||||||
|
Notices and source posture:{' '}
|
||||||
|
{releaseManifest.corresponding_source_url ? (
|
||||||
|
<a href={releaseManifest.corresponding_source_url} target="_blank" rel="noreferrer">
|
||||||
|
Corresponding source
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
'configure the corresponding-source URL before external launch'
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Why this page does not expose raw download URLs">
|
||||||
|
<article className="callout">
|
||||||
|
<p>
|
||||||
|
HyperTwist treats desktop distribution as an account-gated release surface.
|
||||||
|
Public pages can describe supported targets and release posture, but the actual
|
||||||
|
download links live behind the protected dashboard where plan and entitlement
|
||||||
|
state are resolved.
|
||||||
|
</p>
|
||||||
|
<Link className="button button--ghost" to="/login">
|
||||||
|
Sign in to check access
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Pair the desktop app with your browser account">
|
||||||
|
<article className="callout">
|
||||||
|
<p>
|
||||||
|
After sign-in, open the operator dashboard to generate a desktop-link token.
|
||||||
|
That token is designed to hand browser identity and plan posture over to the local desktop app without exposing your password.
|
||||||
|
</p>
|
||||||
|
<Link className="button button--ghost" to="/app">
|
||||||
|
Open dashboard
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OpenSourceNoticesPage() {
|
||||||
|
const releaseManifestQuery = useQuery({
|
||||||
|
queryKey: ['release-manifest', 'public'],
|
||||||
|
queryFn: getReleaseManifest,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const releaseManifest = useMemo(
|
||||||
|
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||||
|
[releaseManifestQuery.data?.manifest],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist open-source notices"
|
||||||
|
description="Review HyperTwist public open-source notices and corresponding-source guidance for downloadable distribution surfaces."
|
||||||
|
canonicalPath="/open-source-notices"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Legal and notices"
|
||||||
|
title="Open-source notices for public distribution surfaces."
|
||||||
|
lede="HyperTwist pricing, checkout, release, and download pages must make legal and corresponding-source guidance visible whenever shipped builds include MPL-covered material."
|
||||||
|
>
|
||||||
|
<Section title="Key components">
|
||||||
|
<div className="card-grid">
|
||||||
|
{openSourceNotices.map((item) => (
|
||||||
|
<article className="card" key={item.component}>
|
||||||
|
<h3>{item.component}</h3>
|
||||||
|
<p className="status-pill">{item.license}</p>
|
||||||
|
<p>{item.whyItMatters}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Corresponding source">
|
||||||
|
<div className="card">
|
||||||
|
<p>
|
||||||
|
MPL-covered shipped builds need a stable corresponding-source location for the exact distributed material.
|
||||||
|
</p>
|
||||||
|
<ul className="list">
|
||||||
|
<li>
|
||||||
|
Public corresponding-source URL:{' '}
|
||||||
|
{releaseManifest.corresponding_source_url ? (
|
||||||
|
<a href={releaseManifest.corresponding_source_url} target="_blank" rel="noreferrer">{releaseManifest.corresponding_source_url}</a>
|
||||||
|
) : (
|
||||||
|
'configure the public corresponding-source URL before external launch'
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Public repository / notices reference:{' '}
|
||||||
|
{releaseManifest.open_source_repo_url ? (
|
||||||
|
<a href={releaseManifest.open_source_repo_url} target="_blank" rel="noreferrer">{releaseManifest.open_source_repo_url}</a>
|
||||||
|
) : (
|
||||||
|
'configure the public repository/notices URL before external launch'
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Official MPL 2.0 license text:{' '}
|
||||||
|
<a href="https://www.mozilla.org/en-US/MPL/2.0/" target="_blank" rel="noreferrer">
|
||||||
|
https://www.mozilla.org/en-US/MPL/2.0/
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Distribution readiness">
|
||||||
|
<PublicLaunchStatus title="Notices and corresponding-source readiness" />
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Distribution doctrine"
|
||||||
|
description="These public legal surfaces should explain why the website is part of the release story without pretending it has replaced the native simulator."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{distributionDoctrineCards.map((card) => (
|
||||||
|
<article className="card" key={card.title}>
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist privacy"
|
||||||
|
description="Understand the HyperTwist privacy posture for browser account access, desktop-link issuance, and the desktop-first simulator boundary."
|
||||||
|
canonicalPath="/privacy"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Privacy"
|
||||||
|
title="Privacy posture"
|
||||||
|
lede="HyperTwist keeps the browser shell narrow and the simulator desktop-first. Privacy descriptions must reflect that separation clearly."
|
||||||
|
>
|
||||||
|
<Section title="Core points">
|
||||||
|
<article className="card">
|
||||||
|
<ul className="list">
|
||||||
|
<li>The public website stores account/session data needed for authentication, plan access, and desktop-link issuance.</li>
|
||||||
|
<li>The browser shell does not claim ownership over the full simulator runtime state unless a future browser-client packet is explicitly opened.</li>
|
||||||
|
<li>Support, billing, and release operations should collect only the data required to deliver digital access and maintain legal compliance.</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Practical privacy boundary"
|
||||||
|
description="Privacy wording should follow the actual product split instead of flattening the browser shell and native simulator into one vague surface."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{privacyBoundaryCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TermsPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist terms"
|
||||||
|
description="Review the HyperTwist terms of access for browser account routes, desktop delivery, and open-source notice obligations."
|
||||||
|
canonicalPath="/terms"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Terms"
|
||||||
|
title="Terms of access"
|
||||||
|
lede="HyperTwist access is digital-first and plan-gated. Terms should match the actual delivery and account model."
|
||||||
|
>
|
||||||
|
<Section title="Service posture">
|
||||||
|
<article className="card">
|
||||||
|
<ul className="list">
|
||||||
|
<li>Browser access covers public pages, account, release, download, and operator/dashboard surfaces.</li>
|
||||||
|
<li>The simulator itself is delivered through the desktop lane unless a later browser-client branch is explicitly opened.</li>
|
||||||
|
<li>Downloaded builds and their public distribution pages remain subject to open-source notice and corresponding-source disclosure rules where applicable.</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Terms in practice"
|
||||||
|
description="These terms-oriented boundaries keep the public site, protected dashboard, and desktop simulator aligned with the actual delivery model."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{termsBoundaryCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShippingPaymentPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist shipping and payment"
|
||||||
|
description="HyperTwist is delivered digitally through account access, release pages, and desktop downloads with Paddle-ready billing posture."
|
||||||
|
canonicalPath="/shipping-payment"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Shipping & payment"
|
||||||
|
title="Digital delivery only"
|
||||||
|
lede="HyperTwist is not a physical-goods storefront. Delivery happens through authenticated account access, release pages, and desktop downloads."
|
||||||
|
>
|
||||||
|
<Section title="Delivery model">
|
||||||
|
<article className="card">
|
||||||
|
<p>{paddleReadyDescription}</p>
|
||||||
|
<ul className="list">
|
||||||
|
<li>No physical goods ship through this site.</li>
|
||||||
|
<li>Pricing and checkout are structured for Paddle-backed digital plans.</li>
|
||||||
|
<li>Desktop downloads must remain paired with public notices and legal links when required by shipped-code obligations.</li>
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Digital delivery workflow"
|
||||||
|
description="A professional digital-delivery lane does more than expose a buy button. It keeps release posture, entitlement, and the desktop handoff coherent."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{digitalDeliveryCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
733
website/src/pages/public-pages-marketing.tsx
Normal file
733
website/src/pages/public-pages-marketing.tsx
Normal file
|
|
@ -0,0 +1,733 @@
|
||||||
|
import { useDeferredValue, useMemo, useState } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { ArrowRight, BookOpenText, Boxes, Download, ExternalLink, Landmark, MonitorCog, Sparkles } from 'lucide-react'
|
||||||
|
import { Link, useSearchParams } from 'react-router-dom'
|
||||||
|
import { getReleaseManifest } from '../auth/auth-api'
|
||||||
|
import { MarketingShell } from '../components/layout/MarketingShell'
|
||||||
|
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||||
|
import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus'
|
||||||
|
import { brandConfig } from '../site-config'
|
||||||
|
import { formatReleasePublishedAt, resolveReleaseManifestView } from '../release-manifest'
|
||||||
|
import { getReleasePlatformValidationSummary } from '../shared/package-validation'
|
||||||
|
import {
|
||||||
|
capabilityPillars,
|
||||||
|
changelogEntries,
|
||||||
|
companyNarrative,
|
||||||
|
deliverySurfaceCards,
|
||||||
|
deploymentReadinessTracks,
|
||||||
|
heroMetrics,
|
||||||
|
higherDimensionalRuntimeGuideCards,
|
||||||
|
inputAndDevicePostureCards,
|
||||||
|
operatorManualTracks,
|
||||||
|
operatorPlaybooks,
|
||||||
|
publicDocumentationPrinciples,
|
||||||
|
releaseStoryCards,
|
||||||
|
resourceCollections,
|
||||||
|
roadmapHonestyCards,
|
||||||
|
shippingNowCards,
|
||||||
|
simulatorManualCards,
|
||||||
|
supportFaqs,
|
||||||
|
} from '../site-data'
|
||||||
|
import { buildPublicReleaseManifestFallback, Section, supportTopicGuidance } from './public-page-helpers'
|
||||||
|
|
||||||
|
export function HomeLanding() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist"
|
||||||
|
description="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||||
|
canonicalPath="/"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Desktop-first training. Browser-first operator access."
|
||||||
|
title="HyperTwist turns cube practice into a real operator-grade training stack."
|
||||||
|
lede="Recognition, replay, coaching, analytics, higher-dimensional runtime ownership, public distribution, and browser-based account access all live under one honest product story."
|
||||||
|
>
|
||||||
|
<section className="hero-panel">
|
||||||
|
<div className="hero-grid">
|
||||||
|
<div className="hero-copy">
|
||||||
|
<p>
|
||||||
|
HyperTwist is a native training environment for classic cube, higher-dimensional
|
||||||
|
families, browser-assisted recognition, replay explanation, and desktop packaging.
|
||||||
|
The public site is intentionally honest: the desktop runtime is real, the browser
|
||||||
|
account/dashboard is real, and the optional full-browser simulator path remains spec-only.
|
||||||
|
</p>
|
||||||
|
<div className="button-row">
|
||||||
|
<Link className="button button--primary" to="/download">
|
||||||
|
Download desktop app
|
||||||
|
</Link>
|
||||||
|
<Link className="button button--ghost" to="/app">
|
||||||
|
Open operator dashboard
|
||||||
|
</Link>
|
||||||
|
<Link className="button button--ghost" to="/pricing">
|
||||||
|
View pricing
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="hero-visual-card">
|
||||||
|
<img
|
||||||
|
src="/branding/hypertwist-3d-symbol.png"
|
||||||
|
alt="HyperTwist symbol"
|
||||||
|
className="hero-visual-card__image"
|
||||||
|
/>
|
||||||
|
<p className="hero-visual-card__caption">
|
||||||
|
Browser account shell outside the simulator. Native Unreal runtime inside the simulator.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="metric-grid">
|
||||||
|
{heroMetrics.map((metric) => (
|
||||||
|
<article key={metric.label} className="metric-card">
|
||||||
|
<strong>{metric.value}</strong>
|
||||||
|
<span>{metric.label}</span>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Current public site status"
|
||||||
|
description="The homepage now exposes the same bounded preview-versus-launch truth that governs the release and pricing lanes."
|
||||||
|
>
|
||||||
|
<PublicLaunchStatus title="Public website and release posture" />
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="What ships now"
|
||||||
|
description="The public site only describes current product truth or explicitly marked retained/spec-only branches."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{shippingNowCards.map((item) => (
|
||||||
|
<article key={item} className="card card--compact">
|
||||||
|
<Sparkles size={18} />
|
||||||
|
<p>{item}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Capability pillars"
|
||||||
|
description="The page structure borrows the FamiliarOS and ScriptoriumAI public-shell discipline, then rebrands the actual product around HyperTwist runtime authority."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{capabilityPillars.map((pillar) => (
|
||||||
|
<article key={pillar.title} className="card">
|
||||||
|
<h3>{pillar.title}</h3>
|
||||||
|
<p>{pillar.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Roadmap-honest posture"
|
||||||
|
description="Important boundaries stay visible instead of being blurred into vague marketing claims."
|
||||||
|
>
|
||||||
|
<div className="split-grid">
|
||||||
|
{roadmapHonestyCards.map((item) => (
|
||||||
|
<article key={item} className="callout">
|
||||||
|
<p>{item}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Delivery surfaces"
|
||||||
|
description="Use the website for account, release, pricing, and notices. Use the desktop runtime for the core simulator."
|
||||||
|
>
|
||||||
|
<div className="feature-band">
|
||||||
|
<article className="feature-band__card">
|
||||||
|
<MonitorCog size={22} />
|
||||||
|
<h3>Browser account and operator shell</h3>
|
||||||
|
<p>Authenticated browser access for release posture, desktop pairing, notices, and operator state.</p>
|
||||||
|
<Link to="/app" className="inline-link">
|
||||||
|
Open dashboard <ArrowRight size={15} />
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
<article className="feature-band__card">
|
||||||
|
<Download size={22} />
|
||||||
|
<h3>Desktop download and package lane</h3>
|
||||||
|
<p>Public download posture for the native Unreal build, with legal linkage already wired in.</p>
|
||||||
|
<Link to="/download" className="inline-link">
|
||||||
|
Open download center <ArrowRight size={15} />
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
<article className="feature-band__card">
|
||||||
|
<Landmark size={22} />
|
||||||
|
<h3>Checkout and notices discipline</h3>
|
||||||
|
<p>Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.</p>
|
||||||
|
<Link to="/open-source-notices" className="inline-link">
|
||||||
|
Review notices <ArrowRight size={15} />
|
||||||
|
</Link>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Why both browser and desktop stay"
|
||||||
|
description="The public site owns operator and distribution work the simulator should not dilute, while the simulator stays native for the runtime-heavy training job."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deliverySurfaceCards.map((surface) => (
|
||||||
|
<article key={surface.title} className="card">
|
||||||
|
<h3>{surface.title}</h3>
|
||||||
|
<p>{surface.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{surface.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AboutPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="About HyperTwist"
|
||||||
|
description="Learn why HyperTwist exists, how it treats higher-dimensional training as first-class work, and why the public site stays honest about desktop-first simulator truth."
|
||||||
|
canonicalPath="/about"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Why HyperTwist exists"
|
||||||
|
title="A training stack serious enough for higher-dimensional cubing."
|
||||||
|
lede="HyperTwist exists because cubers deserve one coherent system for physical recognition, explanation, practice, replay, analytics, and hyper puzzle ownership."
|
||||||
|
>
|
||||||
|
<Section title="Mission">
|
||||||
|
<div className="card">
|
||||||
|
<p>{companyNarrative.mission}</p>
|
||||||
|
<p>{companyNarrative.posture}</p>
|
||||||
|
<p>{companyNarrative.distribution}</p>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Why the web surface remains necessary"
|
||||||
|
description="Keeping the website does not weaken the desktop-first thesis. It keeps public distribution, release, and operator-governance work outside the simulator proper."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deliverySurfaceCards.map((surface) => (
|
||||||
|
<article key={surface.title} className="card">
|
||||||
|
<h3>{surface.title}</h3>
|
||||||
|
<p>{surface.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="What makes the product different">
|
||||||
|
<div className="card-grid">
|
||||||
|
<article className="card">
|
||||||
|
<Boxes size={22} />
|
||||||
|
<h3>It treats higher-dimensional puzzles as first-class work</h3>
|
||||||
|
<p>120-cell and 5D runtime ownership are not hand-wavy aspirations. They are part of the current product truth.</p>
|
||||||
|
</article>
|
||||||
|
<article className="card">
|
||||||
|
<BookOpenText size={22} />
|
||||||
|
<h3>It stays roadmap-honest</h3>
|
||||||
|
<p>Shipped, retained, and spec-only surfaces remain clearly separated so public copy matches actual authority.</p>
|
||||||
|
</article>
|
||||||
|
<article className="card">
|
||||||
|
<MonitorCog size={22} />
|
||||||
|
<h3>It separates browser shell from simulator truth</h3>
|
||||||
|
<p>The public web surface helps operators access the product without pretending the browser already replaces the desktop runtime.</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResourcesPage() {
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
const deferredQuery = useDeferredValue(query)
|
||||||
|
const windowsValidationSummary = getReleasePlatformValidationSummary('windows')
|
||||||
|
|
||||||
|
const filteredCollections = useMemo(() => {
|
||||||
|
const normalized = deferredQuery.trim().toLowerCase()
|
||||||
|
if (!normalized) return resourceCollections
|
||||||
|
return resourceCollections
|
||||||
|
.map((collection) => ({
|
||||||
|
...collection,
|
||||||
|
items: collection.items.filter((item) => item.toLowerCase().includes(normalized) || collection.title.toLowerCase().includes(normalized)),
|
||||||
|
}))
|
||||||
|
.filter((collection) => collection.items.length > 0)
|
||||||
|
}, [deferredQuery])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist resources"
|
||||||
|
description="Browse product-safe HyperTwist resources for rollout, release notes, documentation, operator onboarding, and higher-dimensional product truth."
|
||||||
|
canonicalPath="/resources"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Public resources"
|
||||||
|
title="Resources that explain the product without leaking operator-only internals."
|
||||||
|
lede="This surface is shaped after the FamiliarOS and ScriptoriumAI public resource pages, but constrained to safe HyperTwist product-facing material."
|
||||||
|
>
|
||||||
|
<Section title="Resource finder" description="Search the public resource categories that matter to operators and buyers.">
|
||||||
|
<label className="input-label" htmlFor="resource-query">Filter resources</label>
|
||||||
|
<input
|
||||||
|
id="resource-query"
|
||||||
|
className="input"
|
||||||
|
value={query}
|
||||||
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
|
placeholder="Search training, rollout, notices..."
|
||||||
|
/>
|
||||||
|
<div className="card-grid top-gap">
|
||||||
|
{filteredCollections.map((collection) => (
|
||||||
|
<article key={collection.title} className="card">
|
||||||
|
<h3>{collection.title}</h3>
|
||||||
|
<ul className="list">
|
||||||
|
{collection.items.map((item) => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Direct routes">
|
||||||
|
<div className="feature-band">
|
||||||
|
<Link to="/docs" className="feature-band__card feature-band__card--link">
|
||||||
|
<BookOpenText size={22} />
|
||||||
|
<h3>Docs landing</h3>
|
||||||
|
<p>Product-facing documentation, boundaries, and rollout guidance.</p>
|
||||||
|
</Link>
|
||||||
|
<Link to="/support" className="feature-band__card feature-band__card--link">
|
||||||
|
<ExternalLink size={22} />
|
||||||
|
<h3>Support</h3>
|
||||||
|
<p>Contact, rollout questions, and account/download help.</p>
|
||||||
|
</Link>
|
||||||
|
<Link to="/changelog" className="feature-band__card feature-band__card--link">
|
||||||
|
<Sparkles size={22} />
|
||||||
|
<h3>Release notes</h3>
|
||||||
|
<p>Recent public-facing packets and posture updates.</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Operator playbooks"
|
||||||
|
description="These are the public-safe working patterns that matter once a team moves from curiosity into real rollout."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{operatorPlaybooks.map((playbook) => (
|
||||||
|
<article key={playbook.title} className="card">
|
||||||
|
<h3>{playbook.title}</h3>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{playbook.steps.map((step) => (
|
||||||
|
<li key={step}>{step}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Simulator use today"
|
||||||
|
description="This summary is deliberately practical: what you actually do in the desktop runtime once browser identity and release posture are already resolved."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{simulatorManualCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Higher-dimensional runtime guide"
|
||||||
|
description="These public-safe cards explain which higher-dimensional family lanes are already real and what runtime posture each one actually uses."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{higherDimensionalRuntimeGuideCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Current control and device posture"
|
||||||
|
description="This keeps public resources honest about what input/runtime ownership is already strong and what still remains a later native completion packet."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{inputAndDevicePostureCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Deployment readiness snapshot"
|
||||||
|
description="The public site can be detailed without becoming misleading when rollout guidance stays separated into identity, package, and legal lanes."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deploymentReadinessTracks.map((track) => (
|
||||||
|
<article key={track.title} className="card">
|
||||||
|
<h3>{track.title}</h3>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{track.steps.map((step) => (
|
||||||
|
<li key={step}>{step}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{windowsValidationSummary ? (
|
||||||
|
<Section title="Current packaged desktop proof">
|
||||||
|
<article className="card">
|
||||||
|
<h3>{windowsValidationSummary.lane}</h3>
|
||||||
|
<p>
|
||||||
|
Latest public-safe package evidence was generated{' '}
|
||||||
|
{formatReleasePublishedAt(windowsValidationSummary.generated_at) || windowsValidationSummary.generated_at}
|
||||||
|
{' '}in {windowsValidationSummary.configuration} mode and passed across{' '}
|
||||||
|
{windowsValidationSummary.smoke_map_count} dedicated-family higher-dimensional training map{windowsValidationSummary.smoke_map_count === 1 ? '' : 's'}.
|
||||||
|
</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{windowsValidationSummary.smoke_maps.map((map) => (
|
||||||
|
<li key={map.map_url}>
|
||||||
|
{map.label}: {map.result}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="button-row top-gap">
|
||||||
|
<Link className="button button--ghost" to="/download">
|
||||||
|
Open download center
|
||||||
|
</Link>
|
||||||
|
<Link className="button button--ghost" to="/app">
|
||||||
|
Open operator dashboard
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DocsPage() {
|
||||||
|
const releaseManifestQuery = useQuery({
|
||||||
|
queryKey: ['release-manifest', 'public'],
|
||||||
|
queryFn: getReleaseManifest,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const releaseManifest = useMemo(
|
||||||
|
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||||
|
[releaseManifestQuery.data?.manifest],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist documentation"
|
||||||
|
description="Open the HyperTwist public documentation lane for feature-registry-backed product truth, roadmap honesty, and distribution guidance."
|
||||||
|
canonicalPath="/docs"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Documentation posture"
|
||||||
|
title="HyperTwist documentation stays capability-accurate."
|
||||||
|
lede="The public docs surface points users to product-safe truth: feature registry discipline, roadmap honesty, release notes, and distribution/legal guidance."
|
||||||
|
>
|
||||||
|
<Section title="Public documentation lanes">
|
||||||
|
<div className="card-grid">
|
||||||
|
{publicDocumentationPrinciples.map((principle) => (
|
||||||
|
<article key={principle.title} className="card">
|
||||||
|
<h3>{principle.title}</h3>
|
||||||
|
<p>{principle.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Operator manual"
|
||||||
|
description="This is the public-facing manual for how HyperTwist is actually used today: browser first for identity and release posture, desktop first for the simulator."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{operatorManualTracks.map((track) => (
|
||||||
|
<article key={track.title} className="card">
|
||||||
|
<h3>{track.title}</h3>
|
||||||
|
<p>{track.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{track.steps.map((step) => (
|
||||||
|
<li key={step}>{step}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Browser versus simulator boundary"
|
||||||
|
description="The docs stay professional by being explicit about what each surface does better."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deliverySurfaceCards.map((surface) => (
|
||||||
|
<article key={surface.title} className="card">
|
||||||
|
<h3>{surface.title}</h3>
|
||||||
|
<p>{surface.description}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Simulator manual"
|
||||||
|
description="These are the current product-safe usage tracks for the native runtime itself."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{simulatorManualCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Higher-dimensional family guide"
|
||||||
|
description="These are the currently represented higher-dimensional lanes and the truthful host/runtime posture for each."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{higherDimensionalRuntimeGuideCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Deployment readiness manual"
|
||||||
|
description="This is the public-safe checklist for moving from preview posture to operator-grade rollout."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{deploymentReadinessTracks.map((track) => (
|
||||||
|
<article key={track.title} className="card">
|
||||||
|
<h3>{track.title}</h3>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{track.steps.map((step) => (
|
||||||
|
<li key={step}>{step}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Input and device posture"
|
||||||
|
description="Public documentation should be explicit about the current control/runtime truth instead of blurring groundwork and finished VR claims together."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{inputAndDevicePostureCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{releaseManifest.public_docs_url ? (
|
||||||
|
<Section title="External docs portal">
|
||||||
|
<a className="button button--primary" href={releaseManifest.public_docs_url} target="_blank" rel="noreferrer">
|
||||||
|
Open public docs portal
|
||||||
|
</a>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SupportPage() {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const supportTopic = searchParams.get('topic')
|
||||||
|
const selectedSupportTopic = supportTopic ? supportTopicGuidance[supportTopic] : null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist support"
|
||||||
|
description="Get help with HyperTwist rollout, desktop downloads, pricing, legal readiness, and browser-to-desktop operator access."
|
||||||
|
canonicalPath="/support"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Operator help"
|
||||||
|
title="Support for rollout, downloads, pricing, and browser-to-desktop access."
|
||||||
|
lede="Support is focused on helping operators understand what the browser shell does, what the desktop build does, and how the two connect."
|
||||||
|
>
|
||||||
|
{selectedSupportTopic ? (
|
||||||
|
<Section title="Selected help lane">
|
||||||
|
<article className="callout">
|
||||||
|
<p className="status-pill status-pill--info">{selectedSupportTopic.title}</p>
|
||||||
|
<p>{selectedSupportTopic.description}</p>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Section title="Contact">
|
||||||
|
<article className="card">
|
||||||
|
<p>
|
||||||
|
Email <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> for account activation,
|
||||||
|
release enablement, pricing configuration, or deployment help.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section title="Frequently asked questions">
|
||||||
|
<div className="card-grid">
|
||||||
|
{supportFaqs.map((faq) => (
|
||||||
|
<article className="card" key={faq.question}>
|
||||||
|
<h3>{faq.question}</h3>
|
||||||
|
<p>{faq.answer}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="Support lanes"
|
||||||
|
description="Support works best when operators know whether they need public guidance, protected browser access, or native desktop follow-through."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{operatorPlaybooks.map((playbook) => (
|
||||||
|
<article key={playbook.title} className="card">
|
||||||
|
<h3>{playbook.title}</h3>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{playbook.steps.map((step) => (
|
||||||
|
<li key={step}>{step}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChangelogPage() {
|
||||||
|
const releaseManifestQuery = useQuery({
|
||||||
|
queryKey: ['release-manifest', 'public'],
|
||||||
|
queryFn: getReleaseManifest,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const releaseManifest = useMemo(
|
||||||
|
() => resolveReleaseManifestView(releaseManifestQuery.data?.manifest, buildPublicReleaseManifestFallback()),
|
||||||
|
[releaseManifestQuery.data?.manifest],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SiteMetadata
|
||||||
|
title="HyperTwist release notes"
|
||||||
|
description="Review recent public-facing HyperTwist changes across browser operator access, package hardening, diagnostics, and distribution readiness."
|
||||||
|
canonicalPath="/changelog"
|
||||||
|
/>
|
||||||
|
<MarketingShell
|
||||||
|
eyebrow="Release notes"
|
||||||
|
title="Recent public-facing HyperTwist changes."
|
||||||
|
lede="This page focuses on user-visible posture changes: browser operator access, package hardening, diagnostics, and distribution readiness."
|
||||||
|
>
|
||||||
|
<Section title="Latest changes">
|
||||||
|
<div className="timeline">
|
||||||
|
{changelogEntries.map((entry) => (
|
||||||
|
<article key={`${entry.date}-${entry.title}`} className="timeline-entry">
|
||||||
|
<p className="timeline-entry__date">{entry.date}</p>
|
||||||
|
<h3>{entry.title}</h3>
|
||||||
|
<p>{entry.details}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section
|
||||||
|
title="How to read the release feed"
|
||||||
|
description="HyperTwist release notes stay useful when they distinguish simulator/runtime work, browser-operator work, and distribution/legal hardening instead of collapsing them together."
|
||||||
|
>
|
||||||
|
<div className="card-grid">
|
||||||
|
{releaseStoryCards.map((card) => (
|
||||||
|
<article key={card.title} className="card">
|
||||||
|
<h3>{card.title}</h3>
|
||||||
|
<p>{card.description}</p>
|
||||||
|
<ul className="list top-gap">
|
||||||
|
{card.bullets.map((bullet) => (
|
||||||
|
<li key={bullet}>{bullet}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{releaseManifest.release_notes_url ? (
|
||||||
|
<Section title="External release notes">
|
||||||
|
<a className="button button--ghost" href={releaseManifest.release_notes_url} target="_blank" rel="noreferrer">
|
||||||
|
Open release feed
|
||||||
|
</a>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
</MarketingShell>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,18 +2,18 @@ import { Suspense, lazy, type ReactNode } from 'react'
|
||||||
import { Route } from 'react-router-dom'
|
import { Route } from 'react-router-dom'
|
||||||
import { GeneralPageLoader } from '../components/ui/Skeletons'
|
import { GeneralPageLoader } from '../components/ui/Skeletons'
|
||||||
|
|
||||||
const HomeLanding = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.HomeLanding })))
|
const HomeLanding = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.HomeLanding })))
|
||||||
const AboutPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.AboutPage })))
|
const AboutPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.AboutPage })))
|
||||||
const ResourcesPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ResourcesPage })))
|
const ResourcesPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ResourcesPage })))
|
||||||
const DocsPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.DocsPage })))
|
const DocsPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.DocsPage })))
|
||||||
const SupportPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.SupportPage })))
|
const SupportPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.SupportPage })))
|
||||||
const ChangelogPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ChangelogPage })))
|
const ChangelogPage = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.ChangelogPage })))
|
||||||
const PricingPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.PricingPage })))
|
const PricingPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.PricingPage })))
|
||||||
const DownloadPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.DownloadPage })))
|
const DownloadPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.DownloadPage })))
|
||||||
const OpenSourceNoticesPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.OpenSourceNoticesPage })))
|
const OpenSourceNoticesPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.OpenSourceNoticesPage })))
|
||||||
const PrivacyPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.PrivacyPage })))
|
const PrivacyPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.PrivacyPage })))
|
||||||
const TermsPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.TermsPage })))
|
const TermsPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.TermsPage })))
|
||||||
const ShippingPaymentPage = lazy(() => import('../pages/public-pages').then((m) => ({ default: m.ShippingPaymentPage })))
|
const ShippingPaymentPage = lazy(() => import('../pages/public-pages-commerce').then((m) => ({ default: m.ShippingPaymentPage })))
|
||||||
const LoginPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.LoginPage })))
|
const LoginPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.LoginPage })))
|
||||||
const RegisterPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.RegisterPage })))
|
const RegisterPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.RegisterPage })))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,93 @@ export const simulatorManualCards = [
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
export const higherDimensionalRuntimeGuideCards = [
|
||||||
|
{
|
||||||
|
title: 'Magic120Cell packaged runtime',
|
||||||
|
description: 'Use the packaged native training lane when you need real 120-cell runtime-state, projection ownership, and persistence-aware family behavior.',
|
||||||
|
bullets: [
|
||||||
|
'Launch the dedicated Magic120Cell training map from the desktop runtime.',
|
||||||
|
'Use the current symmetry, focus, and logical-visibility posture as the authoritative first-party runtime owner.',
|
||||||
|
'Treat the public website as documentation and rollout support, not as the runtime that executes this family.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'MagicCube5D packaged runtime',
|
||||||
|
description: 'Use the packaged native training lane when you need real 5D projection, stereo, focus, and face-visibility ownership.',
|
||||||
|
bullets: [
|
||||||
|
'Launch the dedicated MagicCube5D training map from the desktop runtime.',
|
||||||
|
'Use the current projection-distance, stereo, and visibility defaults as the bounded first-party runtime posture.',
|
||||||
|
'Keep this lane described honestly as packaged native behavior rather than browser simulator parity.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'MagicTile embedded-browser runtime',
|
||||||
|
description: 'Use the embedded browser lane for the current non-Euclidean tiling host posture while native renderer widening remains intentionally gated.',
|
||||||
|
bullets: [
|
||||||
|
'Treat the embedded browser/CEF shell as the current shipped host for the live MagicTile interaction lane.',
|
||||||
|
'Keep shared scramble normalization, timer transport, and state-bridge ownership attached to the first-party native shell and bridge.',
|
||||||
|
'Do not describe a separate native renderer port as live while the renderer-widening gate stays explicit No-Go.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const inputAndDevicePostureCards = [
|
||||||
|
{
|
||||||
|
title: 'Keyboard and mouse ship today',
|
||||||
|
description: 'Classic-cube play and bounded keyboard-driven higher-dimensional interaction are already real current product lanes.',
|
||||||
|
bullets: [
|
||||||
|
'Classic-cube runtime input already includes click, touch, orbit, zoom, and bounded keyboard move intent.',
|
||||||
|
'The current classic keyboard profile is the shipped `classic-wca-keyboard/v1` mapping.',
|
||||||
|
'Treat this as real simulator input ownership, not as a browser-side placeholder.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Higher-dimensional view controls are already owned',
|
||||||
|
description: 'Magic120Cell and MagicCube5D already carry first-party projection, focus, symmetry or stereo, and visibility defaults in the packaged runtime.',
|
||||||
|
bullets: [
|
||||||
|
'Magic120Cell keeps symmetry, logical-visibility depth, and center-cell focus posture explicit.',
|
||||||
|
'MagicCube5D keeps projection-distance, stereo, face-visibility, and focus posture explicit.',
|
||||||
|
'These controls belong to the dedicated-family desktop runtime lanes, not to the public website.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'XR groundwork exists, but the full VR lane is not finished',
|
||||||
|
description: 'EnhancedInput posture and motion-controller groundwork exist, but HyperTwist does not yet market a fully finished OpenXR/controller/rebinding runtime lane.',
|
||||||
|
bullets: [
|
||||||
|
'Project config already carries EnhancedInput plus Vive, Oculus Touch, Mixed Reality, and Valve Index axis groundwork.',
|
||||||
|
'Current product truth should not overclaim headset-specific runtime ownership or polished user-facing input rebinding.',
|
||||||
|
'A later native XR completion packet still needs dedicated runtime owners, user-facing settings, and Windows package validation with controller truth.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const deploymentReadinessTracks = [
|
||||||
|
{
|
||||||
|
title: '1. Identity and access posture',
|
||||||
|
steps: [
|
||||||
|
'Confirm shared browser auth is live before inviting operators into the protected release lane.',
|
||||||
|
'Use the protected dashboard to resolve plan, entitlement, and desktop-link token posture.',
|
||||||
|
'Keep browser-to-desktop handoff explicit so the installed app never depends on password reuse.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '2. Release and package posture',
|
||||||
|
steps: [
|
||||||
|
'Publish release-manifest truth for the active desktop targets before widening any public launch language.',
|
||||||
|
'Keep package validation proof visible beside download posture so rollout remains evidence-backed.',
|
||||||
|
'Differentiate browser-shell changes from simulator/package changes in release notes and support guidance.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3. Legal and source posture',
|
||||||
|
steps: [
|
||||||
|
'Keep notices and corresponding-source links visible anywhere pricing, checkout, or downloads are exposed.',
|
||||||
|
'Treat public pricing/download surfaces as part of the distributed product, not detached brochure pages.',
|
||||||
|
'Do not let launch copy outrun the actual checkout, release, or source-availability configuration.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
export const publicDocumentationPrinciples = [
|
export const publicDocumentationPrinciples = [
|
||||||
{
|
{
|
||||||
title: 'Feature-registry-backed wording only',
|
title: 'Feature-registry-backed wording only',
|
||||||
|
|
@ -388,6 +475,15 @@ export const resourceCollections = [
|
||||||
'120-cell and 5D dedicated-family runtime ownership overview',
|
'120-cell and 5D dedicated-family runtime ownership overview',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Deployment readiness',
|
||||||
|
items: [
|
||||||
|
'Shared auth and browser-to-desktop pairing posture',
|
||||||
|
'Package validation proof and release-manifest interpretation',
|
||||||
|
'Pricing, checkout, notices, and corresponding-source coordination',
|
||||||
|
'Launch-readiness distinction between preview, protected, and public lanes',
|
||||||
|
],
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const changelogEntries = [
|
export const changelogEntries = [
|
||||||
|
|
@ -454,6 +550,18 @@ export const supportFaqs = [
|
||||||
question: 'Is the simulator fully in the browser?',
|
question: 'Is the simulator fully in the browser?',
|
||||||
answer: 'No. The current shipping lane is desktop-first and Unreal-backed. The public website offers account, operator, support, and download access, while the optional full-browser simulator path remains spec-only.',
|
answer: 'No. The current shipping lane is desktop-first and Unreal-backed. The public website offers account, operator, support, and download access, while the optional full-browser simulator path remains spec-only.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
question: 'If the desktop app is primary, why keep the web version?',
|
||||||
|
answer: 'Because the browser shell owns the parts that should stay outside the simulator: public positioning, account access, billing, release posture, download gating, notices, and browser-to-desktop pairing. Keeping that work on the web makes the native runtime easier to trust and easier to operate.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Is VR/controller support already fully finished?',
|
||||||
|
answer: 'Not yet. HyperTwist already has real EnhancedInput posture and motion-controller groundwork, but it does not yet claim a fully finished OpenXR/controller/runtime or polished rebinding lane.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: 'Can I already customize controls and higher-dimensional view posture?',
|
||||||
|
answer: 'Partly. The current desktop runtime already owns the classic keyboard profile plus higher-dimensional projection, focus, and visibility defaults, but a broader polished user-facing preferences and rebinding layer is still a later native packet.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
question: 'Can I download a build immediately after sign-in?',
|
question: 'Can I download a build immediately after sign-in?',
|
||||||
answer: 'Yes, once a release URL is configured for your plan. The dashboard also exposes a desktop-link token so the browser account can pair with the desktop app safely.',
|
answer: 'Yes, once a release URL is configured for your plan. The dashboard also exposes a desktop-link token so the browser account can pair with the desktop app safely.',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue