Implement Bound 4 publication entitlement bundle
This commit is contained in:
parent
7128e26fd0
commit
c9583b82da
8 changed files with 1142 additions and 0 deletions
|
|
@ -0,0 +1,443 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingPublicationLibrary.h"
|
||||
|
||||
namespace HyperTwistTrainingPublicationLibraryInternal
|
||||
{
|
||||
const FString PublicationFeatureId = TEXT("feature/training/competitive-publication");
|
||||
const FString PublicationGateId = TEXT("gate/training/competitive-publication");
|
||||
|
||||
bool ArrayContainsCaseInsensitive(const TArray<FString>& Values, const FString& Target)
|
||||
{
|
||||
for (const FString& Value : Values)
|
||||
{
|
||||
if (Value.Equals(Target, ESearchCase::IgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArrayContainsSubstringCaseInsensitive(const TArray<FString>& Values, const FString& Fragment)
|
||||
{
|
||||
for (const FString& Value : Values)
|
||||
{
|
||||
if (Value.Contains(Fragment, ESearchCase::IgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AppendUniqueTag(TArray<FString>& Tags, const FString& Tag)
|
||||
{
|
||||
if (Tag.IsEmpty() || Tags.Contains(Tag))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Tags.Add(Tag);
|
||||
}
|
||||
|
||||
FString ResolveUserId(
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary
|
||||
)
|
||||
{
|
||||
if (!LearnerProfile.UserId.IsEmpty())
|
||||
{
|
||||
return LearnerProfile.UserId;
|
||||
}
|
||||
if (!DeckHistorySummary.UserId.IsEmpty())
|
||||
{
|
||||
return DeckHistorySummary.UserId;
|
||||
}
|
||||
return PersonalBestSummary.UserId;
|
||||
}
|
||||
|
||||
FString ResolveDeckId(
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary
|
||||
)
|
||||
{
|
||||
if (!Deck.DeckId.IsEmpty())
|
||||
{
|
||||
return Deck.DeckId;
|
||||
}
|
||||
if (!DeckHistorySummary.DeckId.IsEmpty())
|
||||
{
|
||||
return DeckHistorySummary.DeckId;
|
||||
}
|
||||
return PersonalBestSummary.DeckId;
|
||||
}
|
||||
|
||||
FString ResolveDeckPuzzleId(const FHyperTwistTrainingDeck& Deck)
|
||||
{
|
||||
for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
|
||||
{
|
||||
if (TrainingCase.IsStructurallyValid())
|
||||
{
|
||||
return TrainingCase.PuzzleId;
|
||||
}
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString ResolveLifecycleStageId(const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract)
|
||||
{
|
||||
static const TArray<FString> PreferredStageOrder =
|
||||
{
|
||||
TEXT("general-availability"),
|
||||
TEXT("beta"),
|
||||
TEXT("alpha"),
|
||||
TEXT("concept"),
|
||||
TEXT("draft"),
|
||||
TEXT("archived")
|
||||
};
|
||||
|
||||
for (const FString& StageId : PreferredStageOrder)
|
||||
{
|
||||
if (ArrayContainsCaseInsensitive(EarlyAccessContract.ActiveStages, StageId))
|
||||
{
|
||||
return StageId;
|
||||
}
|
||||
}
|
||||
|
||||
for (const FString& StageId : PreferredStageOrder)
|
||||
{
|
||||
if (ArrayContainsCaseInsensitive(EarlyAccessContract.LifecycleStages, StageId))
|
||||
{
|
||||
return StageId;
|
||||
}
|
||||
}
|
||||
|
||||
return EarlyAccessContract.ActiveStages.Num() > 0
|
||||
? EarlyAccessContract.ActiveStages[0]
|
||||
: (EarlyAccessContract.LifecycleStages.Num() > 0 ? EarlyAccessContract.LifecycleStages[0] : FString());
|
||||
}
|
||||
|
||||
bool IsVisibleLifecycleStage(const FString& LifecycleStageId)
|
||||
{
|
||||
return LifecycleStageId.Equals(TEXT("alpha"), ESearchCase::IgnoreCase)
|
||||
|| LifecycleStageId.Equals(TEXT("beta"), ESearchCase::IgnoreCase)
|
||||
|| LifecycleStageId.Equals(TEXT("general-availability"), ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingEntitlementGateState BuildEntitlementGateState(
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
const FHyperTwistTrainingFeatureGovernanceContract& FeatureGovernanceContract,
|
||||
const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingEntitlementGateState Gate;
|
||||
Gate.GateId = PublicationGateId;
|
||||
Gate.FeatureId = PublicationFeatureId;
|
||||
Gate.Title = TEXT("Competitive publication and leaderboard access");
|
||||
Gate.FeatureGovernanceContractId = FeatureGovernanceContract.ContractId;
|
||||
Gate.EarlyAccessContractId = EarlyAccessContract.ContractId;
|
||||
Gate.LifecycleStageId = ResolveLifecycleStageId(EarlyAccessContract);
|
||||
Gate.EnrollmentModeLabel =
|
||||
ArrayContainsSubstringCaseInsensitive(EarlyAccessContract.EnrollmentRules, TEXT("opt-in"))
|
||||
? TEXT("opt-in")
|
||||
: TEXT("automatic");
|
||||
Gate.ApprovedWorkflowTags = FeatureGovernanceContract.ApprovedWorkflowTags;
|
||||
Gate.bRequiresOptIn = Gate.EnrollmentModeLabel.Equals(TEXT("opt-in"), ESearchCase::CaseSensitive);
|
||||
|
||||
const bool bHasGovernanceSupport =
|
||||
ArrayContainsCaseInsensitive(FeatureGovernanceContract.ApprovedWorkflowTags, TEXT("feature-governance"));
|
||||
const bool bHasVisibleLifecycleStage = IsVisibleLifecycleStage(Gate.LifecycleStageId);
|
||||
const bool bHasActivity = LearnerProfile.TotalSessionCount > 0 || DeckHistorySummary.SessionCount > 0;
|
||||
const bool bHasEarnedPublication =
|
||||
LearnerProfile.CompletedSessionCount > 0
|
||||
|| DeckHistorySummary.CompletedSessionCount > 0
|
||||
|| PersonalBestSummary.bHasTimedPersonalBest;
|
||||
|
||||
Gate.bVisible = bHasGovernanceSupport && bHasVisibleLifecycleStage && bHasActivity;
|
||||
Gate.bEntitled = Gate.bVisible && bHasEarnedPublication;
|
||||
Gate.VisibilityLabel = Gate.bEntitled
|
||||
? TEXT("published")
|
||||
: (Gate.bVisible ? TEXT("preview") : TEXT("hidden"));
|
||||
return Gate;
|
||||
}
|
||||
|
||||
EHyperTwistTrainingPublicationVisibility ResolvePublicationVisibility(
|
||||
const FHyperTwistTrainingEntitlementGateState& GateState
|
||||
)
|
||||
{
|
||||
if (GateState.bEntitled)
|
||||
{
|
||||
return EHyperTwistTrainingPublicationVisibility::Published;
|
||||
}
|
||||
if (GateState.bVisible)
|
||||
{
|
||||
return EHyperTwistTrainingPublicationVisibility::Preview;
|
||||
}
|
||||
return EHyperTwistTrainingPublicationVisibility::Hidden;
|
||||
}
|
||||
|
||||
void ResolveHighlightValues(
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
FString& OutPrimaryValue,
|
||||
FString& OutSecondaryValue
|
||||
)
|
||||
{
|
||||
OutPrimaryValue.Reset();
|
||||
OutSecondaryValue.Reset();
|
||||
if (PersonalBestSummary.bHasTimedPersonalBest && PersonalBestSummary.BestSingleTotalTimeMs > 0)
|
||||
{
|
||||
const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
|
||||
UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
|
||||
PersonalBestSummary.BestSingleTotalTimeMs
|
||||
);
|
||||
OutPrimaryValue = FormattedValue.PrimaryLabel;
|
||||
OutSecondaryValue = FormattedValue.SecondaryLabel;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingProfilePublication BuildProfilePublication(
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FHyperTwistTrainingEntitlementGateState& GateState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingProfilePublication Publication;
|
||||
Publication.UserId = ResolveUserId(LearnerProfile, DeckHistorySummary, PersonalBestSummary);
|
||||
Publication.DeckId = ResolveDeckId(Deck, DeckHistorySummary, PersonalBestSummary);
|
||||
Publication.DeckTitle = Deck.Title;
|
||||
Publication.PublicationId = FString::Printf(TEXT("publication/%s/%s"), *Publication.UserId, *Publication.DeckId);
|
||||
Publication.DisplayName = Publication.UserId;
|
||||
Publication.Visibility = ResolvePublicationVisibility(GateState);
|
||||
Publication.Headline = Publication.Visibility == EHyperTwistTrainingPublicationVisibility::Published
|
||||
? FString::Printf(
|
||||
TEXT("%d completed sessions, %d day streak"),
|
||||
LearnerProfile.CompletedSessionCount,
|
||||
LearnerProfile.ActiveTrainingDayStreak
|
||||
)
|
||||
: (Publication.Visibility == EHyperTwistTrainingPublicationVisibility::Preview
|
||||
? TEXT("Preview publication is ready for local review")
|
||||
: TEXT("Complete training to unlock publication"));
|
||||
Publication.SummaryLine = FString::Printf(
|
||||
TEXT("%d deck sessions, %d attempts, %.0f%% success rate"),
|
||||
DeckHistorySummary.SessionCount,
|
||||
DeckHistorySummary.TotalAttemptCount,
|
||||
LearnerProfile.SuccessRate
|
||||
);
|
||||
Publication.HighlightLabel = PersonalBestSummary.bHasTimedPersonalBest ? TEXT("Best single") : TEXT("Activity");
|
||||
ResolveHighlightValues(
|
||||
PersonalBestSummary,
|
||||
Publication.HighlightPrimaryValue,
|
||||
Publication.HighlightSecondaryValue
|
||||
);
|
||||
if (Publication.HighlightPrimaryValue.IsEmpty())
|
||||
{
|
||||
Publication.HighlightPrimaryValue = FString::Printf(
|
||||
TEXT("%d sessions"),
|
||||
LearnerProfile.TotalSessionCount
|
||||
);
|
||||
}
|
||||
Publication.LastPublishedAtUtc = !PersonalBestSummary.LastPersonalBestAtUtc.IsEmpty()
|
||||
? PersonalBestSummary.LastPersonalBestAtUtc
|
||||
: LearnerProfile.LastTrainingAtUtc;
|
||||
for (const FString& Tag : Deck.Tags)
|
||||
{
|
||||
AppendUniqueTag(Publication.Tags, Tag);
|
||||
}
|
||||
AppendUniqueTag(Publication.Tags, GateState.LifecycleStageId);
|
||||
AppendUniqueTag(Publication.Tags, TEXT("competitive-publication"));
|
||||
return Publication;
|
||||
}
|
||||
|
||||
bool TryBuildEntryMetric(
|
||||
const FHyperTwistTrainingLeaderboardEventReference& EventReference,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
FString& OutPrimaryValue,
|
||||
FString& OutSecondaryValue
|
||||
)
|
||||
{
|
||||
OutPrimaryValue.Reset();
|
||||
OutSecondaryValue.Reset();
|
||||
|
||||
if (EventReference.FormatId.Equals(TEXT("single"), ESearchCase::IgnoreCase)
|
||||
&& PersonalBestSummary.bHasTimedPersonalBest
|
||||
&& PersonalBestSummary.BestSingleTotalTimeMs > 0)
|
||||
{
|
||||
const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
|
||||
UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
|
||||
PersonalBestSummary.BestSingleTotalTimeMs
|
||||
);
|
||||
OutPrimaryValue = FormattedValue.PrimaryLabel;
|
||||
OutSecondaryValue = FormattedValue.SecondaryLabel;
|
||||
return !OutPrimaryValue.IsEmpty();
|
||||
}
|
||||
|
||||
if ((EventReference.FormatId.Contains(TEXT("ao"), ESearchCase::IgnoreCase)
|
||||
|| EventReference.FormatId.Contains(TEXT("mean"), ESearchCase::IgnoreCase))
|
||||
&& PersonalBestSummary.BestSessionAverageTotalTimeMs > 0)
|
||||
{
|
||||
const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
|
||||
UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
|
||||
PersonalBestSummary.BestSessionAverageTotalTimeMs
|
||||
);
|
||||
OutPrimaryValue = FormattedValue.PrimaryLabel;
|
||||
OutSecondaryValue = FormattedValue.SecondaryLabel;
|
||||
return !OutPrimaryValue.IsEmpty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLeaderboardBundle BuildLeaderboardBundle(
|
||||
const FHyperTwistTrainingProfilePublication& Publication,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FHyperTwistTrainingKnowledgeReferenceBundle& KnowledgeBundle,
|
||||
const FHyperTwistTrainingEntitlementGateState& GateState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingLeaderboardBundle Leaderboard;
|
||||
Leaderboard.BundleId = FString::Printf(TEXT("leaderboard/%s/%s"), *Publication.UserId, *Publication.DeckId);
|
||||
Leaderboard.DeckId = Publication.DeckId;
|
||||
Leaderboard.DeckTitle = Publication.DeckTitle;
|
||||
Leaderboard.ColumnOrder = KnowledgeBundle.LeaderboardDisplayPolicy.ColumnOrder;
|
||||
Leaderboard.TimeFormattingRule = KnowledgeBundle.LeaderboardDisplayPolicy.TimeFormattingRule;
|
||||
Leaderboard.RelativeDateRule = KnowledgeBundle.LeaderboardDisplayPolicy.RelativeDateRule;
|
||||
Leaderboard.SolverProfileRule = KnowledgeBundle.LeaderboardDisplayPolicy.SolverProfileRule;
|
||||
Leaderboard.ProgramBadgeRule = KnowledgeBundle.LeaderboardDisplayPolicy.ProgramBadgeRule;
|
||||
Leaderboard.bEntitlementVisible = GateState.bVisible;
|
||||
|
||||
const FString DeckPuzzleId = ResolveDeckPuzzleId(Deck);
|
||||
const FString RecordedAtUtc = !PersonalBestSummary.LastPersonalBestAtUtc.IsEmpty()
|
||||
? PersonalBestSummary.LastPersonalBestAtUtc
|
||||
: Publication.LastPublishedAtUtc;
|
||||
|
||||
for (const FHyperTwistTrainingLeaderboardEventReference& EventReference : KnowledgeBundle.LeaderboardEvents)
|
||||
{
|
||||
if (!EventReference.PuzzleId.Equals(DeckPuzzleId, ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
++Leaderboard.MatchedEventCount;
|
||||
if (Leaderboard.PrimaryEventId.IsEmpty())
|
||||
{
|
||||
Leaderboard.PrimaryEventId = EventReference.EventId;
|
||||
Leaderboard.PrimaryEventTitle = EventReference.Title;
|
||||
}
|
||||
|
||||
FString MetricPrimaryValue;
|
||||
FString MetricSecondaryValue;
|
||||
if (!TryBuildEntryMetric(
|
||||
EventReference,
|
||||
PersonalBestSummary,
|
||||
MetricPrimaryValue,
|
||||
MetricSecondaryValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLeaderboardEntry Entry;
|
||||
Entry.EntryId = FString::Printf(TEXT("leaderboard-entry/%s/%s"), *EventReference.EventId, *Publication.UserId);
|
||||
Entry.EventId = EventReference.EventId;
|
||||
Entry.EventTitle = EventReference.Title;
|
||||
Entry.MetricLabel = EventReference.MetricLabel;
|
||||
Entry.SolverDisplayName = Publication.DisplayName;
|
||||
Entry.ProgramLabel = EventReference.PreferredProgramIds.Num() > 0
|
||||
? EventReference.PreferredProgramIds[0]
|
||||
: TEXT("Local");
|
||||
Entry.RecordedAtUtc = RecordedAtUtc;
|
||||
Entry.RelativeDateLabel = !RecordedAtUtc.IsEmpty()
|
||||
? UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardRelativeDateLabel(RecordedAtUtc)
|
||||
: TEXT("n/a");
|
||||
Entry.RankLabel = GateState.bEntitled ? TEXT("#1 local") : TEXT("preview");
|
||||
Entry.MetricPrimaryValue = MetricPrimaryValue;
|
||||
Entry.MetricSecondaryValue = MetricSecondaryValue;
|
||||
Entry.DeckId = Publication.DeckId;
|
||||
Entry.bEligible = GateState.bVisible;
|
||||
Entry.bPublished = GateState.bEntitled;
|
||||
Leaderboard.Entries.Add(Entry);
|
||||
}
|
||||
|
||||
for (const FHyperTwistTrainingLeaderboardEntry& Entry : Leaderboard.Entries)
|
||||
{
|
||||
if (Entry.bPublished)
|
||||
{
|
||||
Leaderboard.bHasPublishedEntries = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Leaderboard;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingPublicationBundle UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FHyperTwistTrainingKnowledgeReferenceBundle& KnowledgeBundle,
|
||||
const FHyperTwistTrainingFeatureGovernanceContract& FeatureGovernanceContract,
|
||||
const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingPublicationBundle Bundle;
|
||||
Bundle.UserId = HyperTwistTrainingPublicationLibraryInternal::ResolveUserId(
|
||||
LearnerProfile,
|
||||
DeckHistorySummary,
|
||||
PersonalBestSummary
|
||||
);
|
||||
Bundle.DeckId = HyperTwistTrainingPublicationLibraryInternal::ResolveDeckId(
|
||||
Deck,
|
||||
DeckHistorySummary,
|
||||
PersonalBestSummary
|
||||
);
|
||||
Bundle.DeckTitle = Deck.Title;
|
||||
Bundle.LearnerProfile = LearnerProfile;
|
||||
Bundle.DeckHistorySummary = DeckHistorySummary;
|
||||
Bundle.PersonalBestSummary = PersonalBestSummary;
|
||||
|
||||
if (Bundle.UserId.IsEmpty()
|
||||
|| Bundle.DeckId.IsEmpty()
|
||||
|| !Deck.IsStructurallyValid()
|
||||
|| !KnowledgeBundle.LeaderboardDisplayPolicy.IsStructurallyValid()
|
||||
|| !FeatureGovernanceContract.IsStructurallyValid()
|
||||
|| !EarlyAccessContract.IsStructurallyValid())
|
||||
{
|
||||
return Bundle;
|
||||
}
|
||||
|
||||
Bundle.EntitlementGate = HyperTwistTrainingPublicationLibraryInternal::BuildEntitlementGateState(
|
||||
LearnerProfile,
|
||||
DeckHistorySummary,
|
||||
PersonalBestSummary,
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
);
|
||||
Bundle.ProfilePublication = HyperTwistTrainingPublicationLibraryInternal::BuildProfilePublication(
|
||||
LearnerProfile,
|
||||
DeckHistorySummary,
|
||||
PersonalBestSummary,
|
||||
Deck,
|
||||
Bundle.EntitlementGate
|
||||
);
|
||||
Bundle.Leaderboard = HyperTwistTrainingPublicationLibraryInternal::BuildLeaderboardBundle(
|
||||
Bundle.ProfilePublication,
|
||||
PersonalBestSummary,
|
||||
Deck,
|
||||
KnowledgeBundle,
|
||||
Bundle.EntitlementGate
|
||||
);
|
||||
Bundle.bHasMatchedLeaderboardEvents = Bundle.Leaderboard.MatchedEventCount > 0;
|
||||
Bundle.ProfilePublication.bLeaderboardVisible =
|
||||
Bundle.EntitlementGate.bVisible && Bundle.Leaderboard.Entries.Num() > 0;
|
||||
return Bundle;
|
||||
}
|
||||
|
|
@ -1422,6 +1422,18 @@ FHyperTwistTrainingLearnerProfile UHyperTwistTrainingRuntimeLibrary::GetActiveTr
|
|||
return FHyperTwistTrainingLearnerProfile();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingPublicationBundle UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingPublicationBundle(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActivePublicationBundle();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingPublicationBundle();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingCoachLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPublicationLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
|
|
@ -3063,6 +3064,11 @@ FHyperTwistTrainingLearnerProfile UHyperTwistTrainingSubsystem::GetActiveLearner
|
|||
return ActiveLearnerProfile;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingPublicationBundle UHyperTwistTrainingSubsystem::GetActivePublicationBundle() const
|
||||
{
|
||||
return ActivePublicationBundle;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCoachMemorySnapshot UHyperTwistTrainingSubsystem::GetActiveCoachMemorySnapshot() const
|
||||
{
|
||||
return ActiveCoachMemorySnapshot;
|
||||
|
|
@ -6940,6 +6946,7 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
|
||||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActivePublicationBundle = FHyperTwistTrainingPublicationBundle();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveReplayReviewAnalytics = FHyperTwistReplayReviewAnalytics();
|
||||
ActiveRecognitionReplaySummary = FHyperTwistRecognitionReplaySummary();
|
||||
|
|
@ -7153,6 +7160,38 @@ void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
|||
5
|
||||
)
|
||||
: TArray<FHyperTwistTrainingCaseRecommendation>();
|
||||
{
|
||||
const FHyperTwistTrainingKnowledgeReferenceBundle KnowledgeBundle =
|
||||
UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle();
|
||||
const FHyperTwistTrainingControlPlaneReferenceBundle ControlPlaneBundle =
|
||||
UHyperTwistTrainingControlPlaneLibrary::BuildBundledControlPlaneReferenceBundle();
|
||||
FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
|
||||
FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
|
||||
const bool bHasFeatureGovernanceContract =
|
||||
UHyperTwistTrainingControlPlaneLibrary::TryGetFeatureGovernanceContractById(
|
||||
ControlPlaneBundle,
|
||||
ControlPlaneBundle.PrimaryFeatureGovernanceContractId,
|
||||
FeatureGovernanceContract
|
||||
);
|
||||
const bool bHasEarlyAccessContract =
|
||||
UHyperTwistTrainingControlPlaneLibrary::TryGetEarlyAccessRolloutContractById(
|
||||
ControlPlaneBundle,
|
||||
ControlPlaneBundle.PrimaryEarlyAccessRolloutContractId,
|
||||
EarlyAccessContract
|
||||
);
|
||||
ActivePublicationBundle =
|
||||
RepositoryFocusDeck.IsStructurallyValid() && bHasFeatureGovernanceContract && bHasEarlyAccessContract
|
||||
? UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
|
||||
ActiveLearnerProfile,
|
||||
ActiveDeckHistorySummary,
|
||||
ActivePersonalBestSummary,
|
||||
RepositoryFocusDeck,
|
||||
KnowledgeBundle,
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
)
|
||||
: FHyperTwistTrainingPublicationBundle();
|
||||
}
|
||||
ActiveReviewFlowStatus = ActiveReviewPlanState.IsStructurallyValid()
|
||||
? UHyperTwistTrainingRepositoryLibrary::DeriveReviewFlowStatus(ActiveReviewPlanState)
|
||||
: FHyperTwistTrainingReviewFlowStatus();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingControlPlaneLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingKnowledgeLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingTypes.h"
|
||||
#include "HyperTwistTrainingPublicationLibrary.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UNREALHYPERTWIST_API UHyperTwistTrainingPublicationLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Publication")
|
||||
static FHyperTwistTrainingPublicationBundle BuildPublicationBundle(
|
||||
const FHyperTwistTrainingLearnerProfile& LearnerProfile,
|
||||
const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
|
||||
const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
|
||||
const FHyperTwistTrainingDeck& Deck,
|
||||
const FHyperTwistTrainingKnowledgeReferenceBundle& KnowledgeBundle,
|
||||
const FHyperTwistTrainingFeatureGovernanceContract& FeatureGovernanceContract,
|
||||
const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract
|
||||
);
|
||||
};
|
||||
|
|
@ -565,6 +565,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingLearnerProfile GetActiveTrainingLearnerProfile(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Publication", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingPublicationBundle GetActiveTrainingPublicationBundle(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingCoachMemorySnapshot GetActiveTrainingCoachMemorySnapshot(UObject* WorldContextObject);
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingLearnerProfile GetActiveLearnerProfile() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Publication")
|
||||
FHyperTwistTrainingPublicationBundle GetActivePublicationBundle() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingCoachMemorySnapshot GetActiveCoachMemorySnapshot() const;
|
||||
|
||||
|
|
@ -750,6 +753,9 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistTrainingLearnerProfile ActiveLearnerProfile;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingPublicationBundle ActivePublicationBundle;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingCoachMemorySnapshot ActiveCoachMemorySnapshot;
|
||||
|
||||
|
|
|
|||
|
|
@ -3256,6 +3256,265 @@ struct FHyperTwistTrainingLearnerProfile
|
|||
int32 OpenReviewPlanCount = 0;
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistTrainingPublicationVisibility : uint8
|
||||
{
|
||||
Hidden UMETA(DisplayName = "Hidden"),
|
||||
Preview UMETA(DisplayName = "Preview"),
|
||||
Published UMETA(DisplayName = "Published")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingEntitlementGateState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString GateId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FeatureId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Title;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString FeatureGovernanceContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EarlyAccessContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LifecycleStageId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString VisibilityLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EnrollmentModeLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> ApprovedWorkflowTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bRequiresOptIn = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bVisible = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bEntitled = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !GateId.IsEmpty() && !FeatureId.IsEmpty() && !Title.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingProfilePublication
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PublicationId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString Headline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SummaryLine;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString HighlightLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString HighlightPrimaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString HighlightSecondaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString LastPublishedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> Tags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
EHyperTwistTrainingPublicationVisibility Visibility = EHyperTwistTrainingPublicationVisibility::Hidden;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bLeaderboardVisible = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !PublicationId.IsEmpty() && !UserId.IsEmpty() && !DeckId.IsEmpty() && !DisplayName.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingLeaderboardEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EntryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString EventTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SolverDisplayName;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ProgramLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RecordedAtUtc;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RelativeDateLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RankLabel;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricPrimaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString MetricSecondaryValue;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bEligible = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bPublished = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !EntryId.IsEmpty() && !EventId.IsEmpty() && !MetricPrimaryValue.IsEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingLeaderboardBundle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString BundleId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryEventId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString PrimaryEventTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> ColumnOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString TimeFormattingRule;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString RelativeDateRule;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString SolverProfileRule;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString ProgramBadgeRule;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingLeaderboardEntry> Entries;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 MatchedEventCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bEntitlementVisible = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasPublishedEntries = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !BundleId.IsEmpty() && !DeckId.IsEmpty() && ColumnOrder.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingPublicationBundle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingLearnerProfile LearnerProfile;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingDeckHistorySummary DeckHistorySummary;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingPersonalBestSummary PersonalBestSummary;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingEntitlementGateState EntitlementGate;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingProfilePublication ProfilePublication;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FHyperTwistTrainingLeaderboardBundle Leaderboard;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
bool bHasMatchedLeaderboardEvents = false;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return !UserId.IsEmpty()
|
||||
&& !DeckId.IsEmpty()
|
||||
&& EntitlementGate.IsStructurallyValid()
|
||||
&& ProfilePublication.IsStructurallyValid()
|
||||
&& Leaderboard.IsStructurallyValid();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCoachMemorySnapshot
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,354 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "HyperTwistTraining/HyperTwistTrainingControlPlaneLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingKnowledgeLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingPublicationLibrary.h"
|
||||
|
||||
#if WITH_AUTOMATION_TESTS
|
||||
|
||||
namespace HyperTwistCubeDeskBound4PublicationEntitlementTestInternal
|
||||
{
|
||||
FHyperTwistTrainingDeck MakeHypercubeDeck(const FString& DeckId)
|
||||
{
|
||||
FHyperTwistTrainingDeck Deck;
|
||||
Deck.DeckId = DeckId;
|
||||
Deck.Title = TEXT("3x3x3x3 Fundamentals");
|
||||
Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
|
||||
Deck.Tags = {TEXT("4d"), TEXT("hypercube"), TEXT("competitive")};
|
||||
|
||||
FHyperTwistTrainingCase TrainingCase;
|
||||
TrainingCase.CaseId = TEXT("bound4-case-1");
|
||||
TrainingCase.PuzzleId = TEXT("3x3x3x3");
|
||||
TrainingCase.PromptLabel = TEXT("Bound4 Hypercube Case");
|
||||
TrainingCase.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
|
||||
Deck.Cases.Add(TrainingCase);
|
||||
return Deck;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingLearnerProfile MakeLearnerProfile(
|
||||
const FString& UserId,
|
||||
const int32 TotalSessionCount,
|
||||
const int32 CompletedSessionCount
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingLearnerProfile Profile;
|
||||
Profile.UserId = UserId;
|
||||
Profile.ReferenceUtc = TEXT("2026-05-19T12:00:00Z");
|
||||
Profile.TotalSessionCount = TotalSessionCount;
|
||||
Profile.CompletedSessionCount = CompletedSessionCount;
|
||||
Profile.TotalAttemptCount = 24;
|
||||
Profile.TotalSuccessCount = 18;
|
||||
Profile.TotalFailureCount = 6;
|
||||
Profile.SuccessRate = 75.0f;
|
||||
Profile.ActiveTrainingDayStreak = 5;
|
||||
Profile.LongestObservedTrainingDayStreak = 8;
|
||||
Profile.PrimaryDeliveryMode = EHyperTwistTrainingDeliveryMode::Timer;
|
||||
Profile.FavoriteDeckId = TEXT("deck/hypercube-3333");
|
||||
Profile.FavoriteDeckSessionShare = 0.85f;
|
||||
Profile.LastTrainingAtUtc = TEXT("2026-05-19T11:58:00Z");
|
||||
return Profile;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary MakeDeckHistorySummary(const FString& UserId, const FString& DeckId)
|
||||
{
|
||||
FHyperTwistTrainingDeckHistorySummary Summary;
|
||||
Summary.UserId = UserId;
|
||||
Summary.DeckId = DeckId;
|
||||
Summary.SessionCount = 4;
|
||||
Summary.CompletedSessionCount = 3;
|
||||
Summary.TotalAttemptCount = 24;
|
||||
Summary.SuccessCount = 18;
|
||||
Summary.FailureCount = 4;
|
||||
Summary.TimeoutCount = 2;
|
||||
Summary.BestTotalTimeMs = 1890;
|
||||
Summary.BestRawSolveTimeMs = 1710;
|
||||
Summary.AverageTotalTimeMs = 2330;
|
||||
Summary.AverageRawSolveTimeMs = 2140;
|
||||
Summary.LastSessionId = TEXT("bound4-session");
|
||||
Summary.LastCompletedAtUtc = TEXT("2026-05-19T11:58:00Z");
|
||||
return Summary;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingPersonalBestSummary MakePersonalBestSummary(const FString& UserId, const FString& DeckId)
|
||||
{
|
||||
FHyperTwistTrainingPersonalBestSummary Summary;
|
||||
Summary.UserId = UserId;
|
||||
Summary.DeckId = DeckId;
|
||||
Summary.ReferenceUtc = TEXT("2026-05-19T11:58:00Z");
|
||||
Summary.BestSingleSessionId = TEXT("bound4-session");
|
||||
Summary.BestSingleTotalTimeMs = 1890;
|
||||
Summary.BestSingleRawSolveTimeMs = 1710;
|
||||
Summary.BestAverageSessionId = TEXT("bound4-session");
|
||||
Summary.BestSessionAverageTotalTimeMs = 2050;
|
||||
Summary.BestSessionAverageRawSolveTimeMs = 1930;
|
||||
Summary.LastPersonalBestAtUtc = TEXT("2026-05-19T11:58:00Z");
|
||||
Summary.bHasTimedPersonalBest = true;
|
||||
return Summary;
|
||||
}
|
||||
|
||||
bool ResolveControlPlaneContracts(
|
||||
FHyperTwistTrainingFeatureGovernanceContract& OutFeatureGovernanceContract,
|
||||
FHyperTwistTrainingEarlyAccessRolloutContract& OutEarlyAccessContract
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingControlPlaneReferenceBundle ControlPlaneBundle =
|
||||
UHyperTwistTrainingControlPlaneLibrary::BuildBundledControlPlaneReferenceBundle();
|
||||
return UHyperTwistTrainingControlPlaneLibrary::TryGetFeatureGovernanceContractById(
|
||||
ControlPlaneBundle,
|
||||
ControlPlaneBundle.PrimaryFeatureGovernanceContractId,
|
||||
OutFeatureGovernanceContract
|
||||
)
|
||||
&& UHyperTwistTrainingControlPlaneLibrary::TryGetEarlyAccessRolloutContractById(
|
||||
ControlPlaneBundle,
|
||||
ControlPlaneBundle.PrimaryEarlyAccessRolloutContractId,
|
||||
OutEarlyAccessContract
|
||||
);
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingLeaderboardEntry* FindEntryByEventId(
|
||||
const FHyperTwistTrainingLeaderboardBundle& LeaderboardBundle,
|
||||
const FString& EventId
|
||||
)
|
||||
{
|
||||
for (const FHyperTwistTrainingLeaderboardEntry& Entry : LeaderboardBundle.Entries)
|
||||
{
|
||||
if (Entry.EventId == EventId)
|
||||
{
|
||||
return &Entry;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound4PublicationBundleTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound4.PublicationBundle",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound4PublicationBundleTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
|
||||
FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
|
||||
TestTrue(
|
||||
TEXT("Control-plane publication contracts must resolve from the bundled PostHog packet."),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
)
|
||||
);
|
||||
|
||||
const FHyperTwistTrainingDeck Deck =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
|
||||
TEXT("deck/hypercube-3333")
|
||||
);
|
||||
const FHyperTwistTrainingKnowledgeReferenceBundle KnowledgeBundle =
|
||||
UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle();
|
||||
const FHyperTwistTrainingPublicationBundle Bundle =
|
||||
UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
|
||||
TEXT("bound4-user"),
|
||||
4,
|
||||
3
|
||||
),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
|
||||
TEXT("bound4-user"),
|
||||
Deck.DeckId
|
||||
),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
|
||||
TEXT("bound4-user"),
|
||||
Deck.DeckId
|
||||
),
|
||||
Deck,
|
||||
KnowledgeBundle,
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
);
|
||||
|
||||
TestTrue(TEXT("The publication bundle must resolve as structurally valid."), Bundle.IsStructurallyValid());
|
||||
TestTrue(TEXT("The entitlement gate must be visible for active learners."), Bundle.EntitlementGate.bVisible);
|
||||
TestTrue(TEXT("The entitlement gate must resolve to entitled when local publication exists."), Bundle.EntitlementGate.bEntitled);
|
||||
TestEqual(
|
||||
TEXT("The publication bundle must promote to the published visibility state."),
|
||||
Bundle.ProfilePublication.Visibility,
|
||||
EHyperTwistTrainingPublicationVisibility::Published
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("The publication bundle must retain the active deck title."),
|
||||
Bundle.ProfilePublication.DeckTitle,
|
||||
Deck.Title
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("The publication bundle must retain the active user as its display name."),
|
||||
Bundle.ProfilePublication.DisplayName,
|
||||
TEXT("bound4-user")
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("The publication bundle must expose a formatted highlight value."),
|
||||
!Bundle.ProfilePublication.HighlightPrimaryValue.IsEmpty()
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("The publication bundle must report matched leaderboard events for the hypercube deck."),
|
||||
Bundle.bHasMatchedLeaderboardEvents
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound4EntitlementGateTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound4.EntitlementGate",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound4EntitlementGateTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
|
||||
FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
|
||||
TestTrue(
|
||||
TEXT("Control-plane publication contracts must resolve for entitlement gating."),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
)
|
||||
);
|
||||
|
||||
const FHyperTwistTrainingDeck Deck =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
|
||||
TEXT("deck/hypercube-preview")
|
||||
);
|
||||
FHyperTwistTrainingDeckHistorySummary DeckHistorySummary =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
|
||||
TEXT("bound4-preview-user"),
|
||||
Deck.DeckId
|
||||
);
|
||||
DeckHistorySummary.CompletedSessionCount = 0;
|
||||
FHyperTwistTrainingPersonalBestSummary PersonalBestSummary =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
|
||||
TEXT("bound4-preview-user"),
|
||||
Deck.DeckId
|
||||
);
|
||||
PersonalBestSummary.bHasTimedPersonalBest = false;
|
||||
PersonalBestSummary.BestSingleTotalTimeMs = 0;
|
||||
PersonalBestSummary.BestSessionAverageTotalTimeMs = 0;
|
||||
|
||||
const FHyperTwistTrainingPublicationBundle Bundle =
|
||||
UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
|
||||
TEXT("bound4-preview-user"),
|
||||
2,
|
||||
0
|
||||
),
|
||||
DeckHistorySummary,
|
||||
PersonalBestSummary,
|
||||
Deck,
|
||||
UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle(),
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
);
|
||||
|
||||
TestTrue(TEXT("The preview publication bundle must still be structurally valid."), Bundle.IsStructurallyValid());
|
||||
TestTrue(TEXT("Preview publication must still be visible when the lifecycle stage is active."), Bundle.EntitlementGate.bVisible);
|
||||
TestFalse(TEXT("Preview publication must not be entitled without earned local publication data."), Bundle.EntitlementGate.bEntitled);
|
||||
TestEqual(
|
||||
TEXT("Preview publication must remain in the preview visibility state."),
|
||||
Bundle.ProfilePublication.Visibility,
|
||||
EHyperTwistTrainingPublicationVisibility::Preview
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("The entitlement visibility label must describe preview posture explicitly."),
|
||||
Bundle.EntitlementGate.VisibilityLabel,
|
||||
TEXT("preview")
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistCubeDeskBound4LeaderboardProjectionTest,
|
||||
"HyperTwist.CleanRoom.CubeDesk.Bound4.LeaderboardProjection",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistCubeDeskBound4LeaderboardProjectionTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
|
||||
FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
|
||||
TestTrue(
|
||||
TEXT("Control-plane publication contracts must resolve for leaderboard projection."),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
)
|
||||
);
|
||||
|
||||
const FHyperTwistTrainingDeck Deck =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
|
||||
TEXT("deck/hypercube-leaderboard")
|
||||
);
|
||||
const FHyperTwistTrainingPublicationBundle Bundle =
|
||||
UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
|
||||
TEXT("bound4-leaderboard-user"),
|
||||
5,
|
||||
4
|
||||
),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
|
||||
TEXT("bound4-leaderboard-user"),
|
||||
Deck.DeckId
|
||||
),
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
|
||||
TEXT("bound4-leaderboard-user"),
|
||||
Deck.DeckId
|
||||
),
|
||||
Deck,
|
||||
UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle(),
|
||||
FeatureGovernanceContract,
|
||||
EarlyAccessContract
|
||||
);
|
||||
|
||||
TestTrue(TEXT("The leaderboard bundle must resolve as structurally valid."), Bundle.Leaderboard.IsStructurallyValid());
|
||||
TestEqual(
|
||||
TEXT("The hypercube deck must match exactly two bundled leaderboard events."),
|
||||
Bundle.Leaderboard.MatchedEventCount,
|
||||
2
|
||||
);
|
||||
TestEqual(
|
||||
TEXT("The leaderboard projection must materialize a single and an Ao5 row."),
|
||||
Bundle.Leaderboard.Entries.Num(),
|
||||
2
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("The leaderboard projection must retain the default rank column."),
|
||||
Bundle.Leaderboard.ColumnOrder.Contains(TEXT("rank"))
|
||||
);
|
||||
const FHyperTwistTrainingLeaderboardEntry* SingleEntry =
|
||||
HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::FindEntryByEventId(
|
||||
Bundle.Leaderboard,
|
||||
TEXT("event/3x3x3x3/single")
|
||||
);
|
||||
TestNotNull(TEXT("The single-event leaderboard row must exist."), SingleEntry);
|
||||
if (SingleEntry == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TestEqual(
|
||||
TEXT("The single-event leaderboard row must retain the preferred program badge."),
|
||||
SingleEntry->ProgramLabel,
|
||||
TEXT("HSC")
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("The single-event leaderboard row must expose a formatted metric label."),
|
||||
!SingleEntry->MetricPrimaryValue.IsEmpty()
|
||||
);
|
||||
TestTrue(
|
||||
TEXT("Published leaderboard projections must expose published entries."),
|
||||
Bundle.Leaderboard.bHasPublishedEntries
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Reference in a new issue