Add cubing alg bound 3 validation profiles
This commit is contained in:
parent
2b401e136f
commit
07336e8268
3 changed files with 633 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue