Add cubing alg bound 3 validation profiles

This commit is contained in:
axiomlogicnexus 2026-05-15 18:27:29 +02:00
parent 2b401e136f
commit 07336e8268
3 changed files with 633 additions and 0 deletions

View file

@ -6,6 +6,7 @@
#include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmValidation.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
namespace HyperTwistAlgorithmAutomationTestInternal
@ -73,6 +74,45 @@ namespace HyperTwistAlgorithmAutomationTestInternal
{
return UHyperTwistAlgorithmSerializer::SerializeAlgorithm(Sequence);
}
bool ValidationResultsMatch(
const FHyperTwistAlgorithmValidationResult& A,
const FHyperTwistAlgorithmValidationResult& B)
{
if (A.bIsValid != B.bIsValid
|| A.ProfileName != B.ProfileName
|| A.Issues.Num() != B.Issues.Num())
{
return false;
}
for (int32 Index = 0; Index < A.Issues.Num(); ++Index)
{
if (A.Issues[Index].IssueCode != B.Issues[Index].IssueCode
|| A.Issues[Index].NodePath != B.Issues[Index].NodePath
|| A.Issues[Index].Message != B.Issues[Index].Message)
{
return false;
}
}
return true;
}
bool ResultContainsIssue(
const FHyperTwistAlgorithmValidationResult& Result,
const EHyperTwistAlgorithmValidationIssueCode IssueCode)
{
for (const FHyperTwistAlgorithmValidationIssue& Issue : Result.Issues)
{
if (Issue.IssueCode == IssueCode)
{
return true;
}
}
return false;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
@ -110,6 +150,21 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST(
"HyperTwist.Algorithm.Bound2.StructuredJsonRebaseline",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistAlgorithmValidationFlatOnlyAutomationTest,
"HyperTwist.Algorithm.Bound3.FlatOnly",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistAlgorithmValidationClassicWcaAutomationTest,
"HyperTwist.Algorithm.Bound3.ClassicWca",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistAlgorithmValidationJsonParityAutomationTest,
"HyperTwist.Algorithm.Bound3.JsonProfileParity",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FHyperTwistAlgorithmCanonicalizationAutomationTest::RunTest(const FString& Parameters)
{
(void)Parameters;
@ -489,4 +544,169 @@ bool FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest::RunTest(const FS
return true;
}
bool FHyperTwistAlgorithmValidationFlatOnlyAutomationTest::RunTest(const FString& Parameters)
{
(void)Parameters;
using namespace HyperTwistAlgorithmAutomationTestInternal;
FHyperTwistAlgorithmSequence FlatSequence;
if (!TryParseSequence(*this, TEXT("R . // note\nU"), FlatSequence))
{
return false;
}
const FHyperTwistAlgorithmValidationResult FlatResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(FlatSequence);
TestTrue(TEXT("Flat-only profile accepts atomic moves and annotations"), FlatResult.bIsValid);
TestEqual(TEXT("Flat-only valid case has no issues"), FlatResult.Issues.Num(), 0);
FHyperTwistAlgorithmSequence GroupSequence;
if (!TryParseSequence(*this, TEXT("(R U)'"), GroupSequence))
{
return false;
}
const FHyperTwistAlgorithmValidationResult GroupResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(GroupSequence);
TestFalse(TEXT("Flat-only profile rejects group containers"), GroupResult.bIsValid);
TestTrue(
TEXT("Flat-only profile returns typed container failure"),
ResultContainsIssue(GroupResult, EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed));
FHyperTwistAlgorithmSequence CommutatorSequence;
if (!TryParseSequence(*this, TEXT("[R, U]"), CommutatorSequence))
{
return false;
}
const FHyperTwistAlgorithmValidationResult CommutatorResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(CommutatorSequence);
TestFalse(TEXT("Flat-only profile rejects commutators"), CommutatorResult.bIsValid);
TestTrue(
TEXT("Flat-only commutator rejection uses typed container failure"),
ResultContainsIssue(CommutatorResult, EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed));
return true;
}
bool FHyperTwistAlgorithmValidationClassicWcaAutomationTest::RunTest(const FString& Parameters)
{
(void)Parameters;
using namespace HyperTwistAlgorithmAutomationTestInternal;
const TArray<FString> ValidCases = {
TEXT("R U R' U'"),
TEXT("3Rw"),
TEXT("2-3Uw"),
TEXT("x y z"),
TEXT("M E S"),
TEXT("u")
};
for (const FString& Input : ValidCases)
{
FHyperTwistAlgorithmSequence Sequence;
if (!TryParseSequence(*this, Input, Sequence))
{
return false;
}
const FHyperTwistAlgorithmValidationResult Result =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(Sequence);
TestTrue(FString::Printf(TEXT("classic-wca accepts '%s'"), *Input), Result.bIsValid);
if (!Result.bIsValid)
{
AddError(FString::Printf(
TEXT("classic-wca rejected '%s' with %d issues"),
*Input,
Result.Issues.Num()));
}
}
struct FInvalidCase
{
const TCHAR* Input;
EHyperTwistAlgorithmValidationIssueCode ExpectedCode;
};
const TArray<FInvalidCase> InvalidCases = {
{ TEXT("P"), EHyperTwistAlgorithmValidationIssueCode::UnknownMoveFamily },
{ TEXT("0R"), EHyperTwistAlgorithmValidationIssueCode::LayerIndexMustBePositive },
{ TEXT("3-2Uw"), EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustAscend },
{ TEXT("2x"), EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily },
{ TEXT("2M"), EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily }
};
for (const FInvalidCase& Case : InvalidCases)
{
FHyperTwistAlgorithmSequence Sequence;
if (!TryParseSequence(*this, FString(Case.Input), Sequence))
{
return false;
}
const FHyperTwistAlgorithmValidationResult Result =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(Sequence);
TestFalse(FString::Printf(TEXT("classic-wca rejects '%s'"), Case.Input), Result.bIsValid);
TestTrue(
FString::Printf(TEXT("classic-wca reports typed issue for '%s'"), Case.Input),
ResultContainsIssue(Result, Case.ExpectedCode));
}
return true;
}
bool FHyperTwistAlgorithmValidationJsonParityAutomationTest::RunTest(const FString& Parameters)
{
(void)Parameters;
using namespace HyperTwistAlgorithmAutomationTestInternal;
FHyperTwistAlgorithmSequence InvalidTextSequence;
if (!TryParseSequence(*this, TEXT("(R U)'"), InvalidTextSequence))
{
return false;
}
FHyperTwistAlgorithmSequence InvalidJsonSequence;
if (!TryRoundTripJson(*this, InvalidTextSequence, InvalidJsonSequence, TEXT("flat-only invalid sequence")))
{
return false;
}
const FHyperTwistAlgorithmValidationResult InvalidTextResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(InvalidTextSequence);
const FHyperTwistAlgorithmValidationResult InvalidJsonResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(InvalidJsonSequence);
TestTrue(
TEXT("Flat-only validation matches between text-ingested and JSON-ingested sequences"),
ValidationResultsMatch(InvalidTextResult, InvalidJsonResult));
FHyperTwistAlgorithmSequence ValidTextSequence;
if (!TryParseSequence(*this, TEXT("2-3Uw R x"), ValidTextSequence))
{
return false;
}
FHyperTwistAlgorithmSequence ValidJsonSequence;
if (!TryRoundTripJson(*this, ValidTextSequence, ValidJsonSequence, TEXT("classic-wca valid sequence")))
{
return false;
}
const FHyperTwistAlgorithmValidationResult ValidTextResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(ValidTextSequence);
const FHyperTwistAlgorithmValidationResult ValidJsonResult =
UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(ValidJsonSequence);
TestTrue(TEXT("classic-wca text-ingested sequence validates"), ValidTextResult.bIsValid);
TestTrue(TEXT("classic-wca JSON-ingested sequence validates"), ValidJsonResult.bIsValid);
TestTrue(
TEXT("classic-wca validation matches between text-ingested and JSON-ingested sequences"),
ValidationResultsMatch(ValidTextResult, ValidJsonResult));
return true;
}
#endif

View file

@ -0,0 +1,334 @@
// Clean-room implementation for HyperTwist algorithm-language subsystem
// Implemented from governance docs only (Phase 0R-D, Phase 1R, Phase 5R-A)
// Source: Behavioral contracts from cubing/alg.js Model A handoff (GPL-3.0-or-later lane)
// No source code inspection. Clean-room workflow only.
// Date: 2026-05-15
// Bound 3 - validation profiles and typed validation failures
#include "HyperTwistAlgorithm/HyperTwistAlgorithmValidation.h"
namespace HyperTwistAlgorithmValidationInternal
{
struct FResolvedValidationProfile
{
FString CanonicalName = TEXT("permissive");
bool bRejectContainers = false;
bool bClassicCubeLegality = false;
bool bSupported = true;
};
FString NormalizeProfileKey(const FString& InProfile)
{
FString Normalized = InProfile.TrimStartAndEnd();
Normalized.ToLowerInline();
return Normalized;
}
FResolvedValidationProfile ResolveValidationProfile(const FString& InProfile)
{
const FString ProfileKey = NormalizeProfileKey(InProfile);
FResolvedValidationProfile Result;
if (ProfileKey.IsEmpty()
|| ProfileKey == TEXT("permissive")
|| ProfileKey == TEXT("unknown-family-ok"))
{
return Result;
}
if (ProfileKey == TEXT("flat-only"))
{
Result.CanonicalName = TEXT("flat-only");
Result.bRejectContainers = true;
return Result;
}
if (ProfileKey == TEXT("classic-wca")
|| ProfileKey == TEXT("classic-cube"))
{
Result.CanonicalName = TEXT("classic-wca");
Result.bClassicCubeLegality = true;
return Result;
}
if (ProfileKey == TEXT("classic-wca-flat")
|| ProfileKey == TEXT("classic-wca-flat-only")
|| ProfileKey == TEXT("flat-only+classic-wca")
|| ProfileKey == TEXT("classic-cube-flat"))
{
Result.CanonicalName = TEXT("classic-wca-flat");
Result.bRejectContainers = true;
Result.bClassicCubeLegality = true;
return Result;
}
Result.CanonicalName = ProfileKey;
Result.bSupported = false;
return Result;
}
void AddIssue(
FHyperTwistAlgorithmValidationResult& OutResult,
const EHyperTwistAlgorithmValidationIssueCode IssueCode,
const FString& NodePath,
const FString& Message)
{
OutResult.bIsValid = false;
FHyperTwistAlgorithmValidationIssue Issue;
Issue.IssueCode = IssueCode;
Issue.NodePath = NodePath;
Issue.Message = Message;
OutResult.Issues.Add(MoveTemp(Issue));
}
bool IsSingleCharacterInSet(const FString& Family, const TCHAR* AllowedCharacters)
{
return Family.Len() == 1 && FCString::Strchr(AllowedCharacters, Family[0]) != nullptr;
}
bool IsClassicFaceFamily(const FString& Family)
{
return IsSingleCharacterInSet(Family, TEXT("ULFRBD"));
}
bool IsClassicWideLowercaseFamily(const FString& Family)
{
return IsSingleCharacterInSet(Family, TEXT("ulfrbd"));
}
bool IsClassicRotationFamily(const FString& Family)
{
return IsSingleCharacterInSet(Family, TEXT("xyzXYZ"));
}
bool IsClassicSliceFamily(const FString& Family)
{
return IsSingleCharacterInSet(Family, TEXT("MESmes"));
}
bool IsKnownClassicFamily(const FHyperTwistAlgorithmBlockMove& Move)
{
return IsClassicFaceFamily(Move.Family)
|| IsClassicWideLowercaseFamily(Move.Family)
|| IsClassicRotationFamily(Move.Family)
|| IsClassicSliceFamily(Move.Family);
}
FString NodeTypeToContainerLabel(const EHyperTwistAlgorithmNodeType NodeType)
{
switch (NodeType)
{
case EHyperTwistAlgorithmNodeType::Group:
return TEXT("group");
case EHyperTwistAlgorithmNodeType::Commutator:
return TEXT("commutator");
case EHyperTwistAlgorithmNodeType::Conjugate:
return TEXT("conjugate");
default:
return TEXT("container");
}
}
void ValidateBlockMove(
const FHyperTwistAlgorithmBlockMove& Move,
const FString& NodePath,
const FResolvedValidationProfile& Profile,
FHyperTwistAlgorithmValidationResult& OutResult)
{
if (!Profile.bClassicCubeLegality)
{
return;
}
if (!IsKnownClassicFamily(Move))
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::UnknownMoveFamily,
NodePath,
FString::Printf(
TEXT("Move family '%s' is not allowed by profile '%s'."),
*Move.Family,
*Profile.CanonicalName));
return;
}
const bool bHasSingleLayerPrefix = Move.GetCanonicalMoveType() == EHyperTwistAlgorithmMoveType::InnerSlice;
const bool bHasRangePrefix = Move.GetCanonicalMoveType() == EHyperTwistAlgorithmMoveType::RangedSlice;
if (bHasSingleLayerPrefix && Move.GetCanonicalInnerLayer() <= 0)
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::LayerIndexMustBePositive,
NodePath,
FString::Printf(
TEXT("Move '%s' uses layer index %d, but layer indices must be positive."),
*Move.Family,
Move.GetCanonicalInnerLayer()));
}
if (bHasRangePrefix)
{
if (Move.GetCanonicalInnerLayer() <= 0 || Move.GetCanonicalOuterLayer() <= 0)
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustBePositive,
NodePath,
FString::Printf(
TEXT("Move '%s' uses range %d-%d, but both range endpoints must be positive."),
*Move.Family,
Move.GetCanonicalInnerLayer(),
Move.GetCanonicalOuterLayer()));
}
if (Move.GetCanonicalInnerLayer() >= Move.GetCanonicalOuterLayer())
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::LayerRangeMustAscend,
NodePath,
FString::Printf(
TEXT("Move '%s' uses range %d-%d, but the range must ascend with start < end."),
*Move.Family,
Move.GetCanonicalInnerLayer(),
Move.GetCanonicalOuterLayer()));
}
}
if ((bHasSingleLayerPrefix || bHasRangePrefix)
&& (IsClassicRotationFamily(Move.Family) || IsClassicSliceFamily(Move.Family)))
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::LayerPrefixNotAllowedForFamily,
NodePath,
FString::Printf(
TEXT("Move family '%s' does not allow layer indices or ranges in profile '%s'."),
*Move.Family,
*Profile.CanonicalName));
}
}
void ValidateNodeList(
const TArray<FHyperTwistAlgorithmNode>& Nodes,
const FString& PathPrefix,
const FResolvedValidationProfile& Profile,
FHyperTwistAlgorithmValidationResult& OutResult);
void ValidateNode(
const FHyperTwistAlgorithmNode& Node,
const FString& NodePath,
const FResolvedValidationProfile& Profile,
FHyperTwistAlgorithmValidationResult& OutResult)
{
switch (Node.NodeType)
{
case EHyperTwistAlgorithmNodeType::BlockMove:
ValidateBlockMove(Node.BlockMove, NodePath, Profile, OutResult);
break;
case EHyperTwistAlgorithmNodeType::Group:
if (Profile.bRejectContainers)
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
NodePath,
FString::Printf(
TEXT("Profile '%s' does not allow group containers."),
*Profile.CanonicalName));
}
ValidateNodeList(Node.Group.Inner, NodePath + TEXT(".items"), Profile, OutResult);
break;
case EHyperTwistAlgorithmNodeType::Commutator:
if (Profile.bRejectContainers)
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
NodePath,
FString::Printf(
TEXT("Profile '%s' does not allow commutator containers."),
*Profile.CanonicalName));
}
ValidateNodeList(Node.Commutator.A, NodePath + TEXT(".left"), Profile, OutResult);
ValidateNodeList(Node.Commutator.B, NodePath + TEXT(".right"), Profile, OutResult);
break;
case EHyperTwistAlgorithmNodeType::Conjugate:
if (Profile.bRejectContainers)
{
AddIssue(
OutResult,
EHyperTwistAlgorithmValidationIssueCode::ContainerNotAllowed,
NodePath,
FString::Printf(
TEXT("Profile '%s' does not allow conjugate containers."),
*Profile.CanonicalName));
}
ValidateNodeList(Node.Conjugate.A, NodePath + TEXT(".left"), Profile, OutResult);
ValidateNodeList(Node.Conjugate.B, NodePath + TEXT(".right"), Profile, OutResult);
break;
case EHyperTwistAlgorithmNodeType::Pause:
case EHyperTwistAlgorithmNodeType::Newline:
case EHyperTwistAlgorithmNodeType::Comment:
case EHyperTwistAlgorithmNodeType::Sequence:
default:
break;
}
}
void ValidateNodeList(
const TArray<FHyperTwistAlgorithmNode>& Nodes,
const FString& PathPrefix,
const FResolvedValidationProfile& Profile,
FHyperTwistAlgorithmValidationResult& OutResult)
{
for (int32 Index = 0; Index < Nodes.Num(); ++Index)
{
const FString NodePath = FString::Printf(TEXT("%s[%d]"), *PathPrefix, Index);
ValidateNode(Nodes[Index], NodePath, Profile, OutResult);
}
}
}
FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceForProfile(
const FHyperTwistAlgorithmSequence& Sequence,
const FString& NotationProfile)
{
using namespace HyperTwistAlgorithmValidationInternal;
FHyperTwistAlgorithmValidationResult Result;
const FResolvedValidationProfile Profile = ResolveValidationProfile(NotationProfile);
Result.ProfileName = Profile.CanonicalName;
if (!Profile.bSupported)
{
AddIssue(
Result,
EHyperTwistAlgorithmValidationIssueCode::UnsupportedProfile,
TEXT("profile"),
FString::Printf(TEXT("Validation profile '%s' is not supported."), *NotationProfile));
return Result;
}
ValidateNodeList(Sequence.Nodes, TEXT("root"), Profile, Result);
return Result;
}
FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceFlatOnly(
const FHyperTwistAlgorithmSequence& Sequence)
{
return ValidateSequenceForProfile(Sequence, TEXT("flat-only"));
}
FHyperTwistAlgorithmValidationResult UHyperTwistAlgorithmValidationLibrary::ValidateSequenceClassicWca(
const FHyperTwistAlgorithmSequence& Sequence)
{
return ValidateSequenceForProfile(Sequence, TEXT("classic-wca"));
}

View file

@ -0,0 +1,79 @@
#pragma once
// Clean-room implementation for HyperTwist algorithm-language subsystem
// Implemented from governance docs only (Phase 0R-D, Phase 1R, Phase 5R-A)
// Source: Behavioral contracts from cubing/alg.js Model A handoff (GPL-3.0-or-later lane)
// No source code inspection. Clean-room workflow only.
// Date: 2026-05-15
// Bound 3 - validation profiles and typed validation failures
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmTypes.h"
#include "HyperTwistAlgorithmValidation.generated.h"
UENUM(BlueprintType)
enum class EHyperTwistAlgorithmValidationIssueCode : uint8
{
UnsupportedProfile UMETA(DisplayName = "UnsupportedProfile"),
ContainerNotAllowed UMETA(DisplayName = "ContainerNotAllowed"),
UnknownMoveFamily UMETA(DisplayName = "UnknownMoveFamily"),
LayerIndexMustBePositive UMETA(DisplayName = "LayerIndexMustBePositive"),
LayerRangeMustBePositive UMETA(DisplayName = "LayerRangeMustBePositive"),
LayerRangeMustAscend UMETA(DisplayName = "LayerRangeMustAscend"),
LayerPrefixNotAllowedForFamily UMETA(DisplayName = "LayerPrefixNotAllowedForFamily")
};
USTRUCT(BlueprintType)
struct FHyperTwistAlgorithmValidationIssue
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
EHyperTwistAlgorithmValidationIssueCode IssueCode =
EHyperTwistAlgorithmValidationIssueCode::UnsupportedProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
FString NodePath;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
FString Message;
};
USTRUCT(BlueprintType)
struct FHyperTwistAlgorithmValidationResult
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
bool bIsValid = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
FString ProfileName = TEXT("permissive");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Algorithm|Validation")
TArray<FHyperTwistAlgorithmValidationIssue> Issues;
};
UCLASS()
class UNREALHYPERTWIST_API UHyperTwistAlgorithmValidationLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Validation")
static FHyperTwistAlgorithmValidationResult ValidateSequenceForProfile(
const FHyperTwistAlgorithmSequence& Sequence,
const FString& NotationProfile
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Validation")
static FHyperTwistAlgorithmValidationResult ValidateSequenceFlatOnly(
const FHyperTwistAlgorithmSequence& Sequence
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Validation")
static FHyperTwistAlgorithmValidationResult ValidateSequenceClassicWca(
const FHyperTwistAlgorithmSequence& Sequence
);
};