Implement Bound 4 publication entitlement bundle

This commit is contained in:
axiomlogicnexus 2026-05-19 23:31:20 +02:00
parent 7128e26fd0
commit c9583b82da
8 changed files with 1142 additions and 0 deletions

View file

@ -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;
}

View file

@ -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))

View file

@ -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();

View file

@ -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
);
};

View file

@ -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);

View file

@ -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;

View file

@ -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
{

View file

@ -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