From d76b0cf9b77934ce49040e1bf657b0656702f325 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Fri, 15 May 2026 16:19:07 +0200 Subject: [PATCH] Implement cubing alg bound 2 traversal helpers --- .../HyperTwistAlgorithmAutomationTests.cpp | 312 +++++++++++++++ .../HyperTwistAlgorithmTraversal.cpp | 374 +++++++++--------- .../HyperTwistAlgorithmTraversal.h | 42 +- 3 files changed, 538 insertions(+), 190 deletions(-) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmAutomationTests.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmAutomationTests.cpp index a012282..0585786 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmAutomationTests.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmAutomationTests.cpp @@ -5,8 +5,76 @@ #include "HyperTwistAlgorithm/HyperTwistAlgorithmLibrary.h" #include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h" #include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h" +#include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h" #include "HyperTwistCore/HyperTwistCoreLibrary.h" +namespace HyperTwistAlgorithmAutomationTestInternal +{ + bool TryParseSequence( + FAutomationTestBase& Test, + const FString& Input, + FHyperTwistAlgorithmSequence& OutSequence) + { + const FHyperTwistAlgorithmParseResult ParseResult = + UHyperTwistAlgorithmParser::ParseAlgorithm(Input); + + Test.TestTrue(FString::Printf(TEXT("Parse succeeded for '%s'"), *Input), ParseResult.bSuccess); + if (!ParseResult.bSuccess) + { + Test.AddError(FString::Printf( + TEXT("Parse failed for '%s' at %d: %s"), + *Input, + ParseResult.ErrorPosition, + *ParseResult.ErrorMessage)); + OutSequence = FHyperTwistAlgorithmSequence(); + return false; + } + + OutSequence = ParseResult.Sequence; + return true; + } + + bool TryRoundTripJson( + FAutomationTestBase& Test, + const FHyperTwistAlgorithmSequence& InputSequence, + FHyperTwistAlgorithmSequence& OutSequence, + const FString& ContextLabel) + { + FString JsonText; + const bool bSerializeSucceeded = + UHyperTwistAlgorithmSerializer::TrySerializeToJson(InputSequence, JsonText); + Test.TestTrue( + FString::Printf(TEXT("Structured JSON serialization succeeded for %s"), *ContextLabel), + bSerializeSucceeded); + if (!bSerializeSucceeded) + { + return false; + } + + FString ParseError; + const bool bParseSucceeded = + UHyperTwistAlgorithmParser::TryParseFromJson(JsonText, OutSequence, ParseError); + Test.TestTrue( + FString::Printf(TEXT("Structured JSON parse succeeded for %s"), *ContextLabel), + bParseSucceeded); + if (!bParseSucceeded) + { + Test.AddError(FString::Printf( + TEXT("Structured JSON parse failed for %s: %s"), + *ContextLabel, + *ParseError)); + return false; + } + + return true; + } + + FString SerializeSequence(const FHyperTwistAlgorithmSequence& Sequence) + { + return UHyperTwistAlgorithmSerializer::SerializeAlgorithm(Sequence); + } +} + IMPLEMENT_SIMPLE_AUTOMATION_TEST( FHyperTwistAlgorithmCanonicalizationAutomationTest, "HyperTwist.Algorithm.Bound1.Canonicalization", @@ -22,6 +90,26 @@ IMPLEMENT_SIMPLE_AUTOMATION_TEST( "HyperTwist.Algorithm.Bound1.StructuredJson", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistAlgorithmTraversalInvertAutomationTest, + "HyperTwist.Algorithm.Bound2.Invert", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistAlgorithmTraversalExpandAutomationTest, + "HyperTwist.Algorithm.Bound2.Expand", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistAlgorithmTraversalCoalesceAutomationTest, + "HyperTwist.Algorithm.Bound2.Coalesce", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest, + "HyperTwist.Algorithm.Bound2.StructuredJsonRebaseline", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) + bool FHyperTwistAlgorithmCanonicalizationAutomationTest::RunTest(const FString& Parameters) { (void)Parameters; @@ -177,4 +265,228 @@ bool FHyperTwistAlgorithmStructuredJsonAutomationTest::RunTest(const FString& Pa return true; } +bool FHyperTwistAlgorithmTraversalInvertAutomationTest::RunTest(const FString& Parameters) +{ + (void)Parameters; + + using namespace HyperTwistAlgorithmAutomationTestInternal; + + struct FInvertCase + { + const TCHAR* Input; + const TCHAR* Expected; + }; + + const TArray Cases = { + { TEXT("R U R' U'"), TEXT("U R U' R'") }, + { TEXT("[R, U]2"), TEXT("[U, R]2") }, + { TEXT("[R: U]"), TEXT("[R: U']") }, + { TEXT("R . U"), TEXT("U' . R'") }, + { TEXT("R\nU"), TEXT("U'\nR'") } + }; + + for (const FInvertCase& Case : Cases) + { + FHyperTwistAlgorithmSequence InputSequence; + if (!TryParseSequence(*this, FString(Case.Input), InputSequence)) + { + continue; + } + + const FHyperTwistAlgorithmSequence InvertedSequence = + UHyperTwistAlgorithmTraversal::InvertSequence(InputSequence); + const FString ActualText = SerializeSequence(InvertedSequence); + TestEqual( + FString::Printf(TEXT("Inverted canonical text for '%s'"), Case.Input), + ActualText, + FString(Case.Expected)); + + FHyperTwistAlgorithmSequence ExpectedSequence; + if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence)) + { + TestTrue( + FString::Printf(TEXT("Inverted structure matches expected for '%s'"), Case.Input), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(InvertedSequence, ExpectedSequence)); + } + } + + return true; +} + +bool FHyperTwistAlgorithmTraversalExpandAutomationTest::RunTest(const FString& Parameters) +{ + (void)Parameters; + + using namespace HyperTwistAlgorithmAutomationTestInternal; + + struct FExpandCase + { + const TCHAR* Input; + const TCHAR* Expected; + }; + + const TArray Cases = { + { TEXT("(R U)'"), TEXT("U' R'") }, + { TEXT("[R, U]2"), TEXT("R U R' U' R U R' U'") }, + { TEXT("[R: U]"), TEXT("R U R'") }, + { TEXT("R . [U, F]"), TEXT("R . U F U' F'") } + }; + + for (const FExpandCase& Case : Cases) + { + FHyperTwistAlgorithmSequence InputSequence; + if (!TryParseSequence(*this, FString(Case.Input), InputSequence)) + { + continue; + } + + const FHyperTwistAlgorithmSequence ExpandedSequence = + UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(InputSequence); + const FString ActualText = SerializeSequence(ExpandedSequence); + TestEqual( + FString::Printf(TEXT("Expanded canonical text for '%s'"), Case.Input), + ActualText, + FString(Case.Expected)); + + FHyperTwistAlgorithmSequence ExpectedSequence; + if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence)) + { + TestTrue( + FString::Printf(TEXT("Expanded structure matches expected for '%s'"), Case.Input), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedSequence, ExpectedSequence)); + } + } + + return true; +} + +bool FHyperTwistAlgorithmTraversalCoalesceAutomationTest::RunTest(const FString& Parameters) +{ + (void)Parameters; + + using namespace HyperTwistAlgorithmAutomationTestInternal; + + struct FCoalesceCase + { + const TCHAR* Input; + const TCHAR* Expected; + }; + + const TArray Cases = { + { TEXT("R R"), TEXT("R2") }, + { TEXT("R2 R2"), TEXT("R4") }, + { TEXT("R R'"), TEXT("") }, + { TEXT("R . R"), TEXT("R . R") }, + { TEXT("R (R R') U"), TEXT("R U") } + }; + + for (const FCoalesceCase& Case : Cases) + { + FHyperTwistAlgorithmSequence InputSequence; + if (!TryParseSequence(*this, FString(Case.Input), InputSequence)) + { + continue; + } + + const FHyperTwistAlgorithmSequence CoalescedSequence = + UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(InputSequence); + const FString ActualText = SerializeSequence(CoalescedSequence); + TestEqual( + FString::Printf(TEXT("Expanded/coalesced canonical text for '%s'"), Case.Input), + ActualText, + FString(Case.Expected)); + + FHyperTwistAlgorithmSequence ExpectedSequence; + if (TryParseSequence(*this, FString(Case.Expected), ExpectedSequence)) + { + TestTrue( + FString::Printf(TEXT("Expanded/coalesced structure matches expected for '%s'"), Case.Input), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedSequence, ExpectedSequence)); + } + } + + FHyperTwistAlgorithmSequence IntegerSumSequence; + if (TryParseSequence(*this, TEXT("R2 R2"), IntegerSumSequence)) + { + const TArray Moves = + UHyperTwistAlgorithmTraversal::ExpandAndSimplify(IntegerSumSequence); + TestEqual(TEXT("Integer-sum coalescing retains one move"), Moves.Num(), 1); + if (Moves.Num() == 1) + { + TestEqual(TEXT("Integer-sum coalescing preserves amount 4"), Moves[0].Amount, 4); + } + } + + return true; +} + +bool FHyperTwistAlgorithmTraversalStructuredJsonAutomationTest::RunTest(const FString& Parameters) +{ + (void)Parameters; + + using namespace HyperTwistAlgorithmAutomationTestInternal; + + FHyperTwistAlgorithmSequence TextSequence; + if (!TryParseSequence(*this, TEXT("R R . [U, F]"), TextSequence)) + { + return false; + } + + FHyperTwistAlgorithmSequence JsonSequence; + if (!TryRoundTripJson(*this, TextSequence, JsonSequence, TEXT("input sequence"))) + { + return false; + } + + const FHyperTwistAlgorithmSequence ExpandedFromText = + UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(TextSequence); + const FHyperTwistAlgorithmSequence ExpandedFromJson = + UHyperTwistAlgorithmTraversal::ExpandToFlatSequence(JsonSequence); + TestTrue( + TEXT("Expanded text-ingested and JSON-ingested sequences match structurally"), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(ExpandedFromText, ExpandedFromJson)); + TestEqual( + TEXT("Expanded text-ingested and JSON-ingested sequences share canonical text"), + SerializeSequence(ExpandedFromText), + SerializeSequence(ExpandedFromJson)); + + const FHyperTwistAlgorithmSequence CoalescedFromText = + UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(TextSequence); + const FHyperTwistAlgorithmSequence CoalescedFromJson = + UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence(JsonSequence); + TestTrue( + TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences match structurally"), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual(CoalescedFromText, CoalescedFromJson)); + TestEqual( + TEXT("Expanded/coalesced text-ingested and JSON-ingested sequences share canonical text"), + SerializeSequence(CoalescedFromText), + SerializeSequence(CoalescedFromJson)); + TestEqual( + TEXT("Expanded/coalesced canonical text matches expected output"), + SerializeSequence(CoalescedFromText), + FString(TEXT("R2 . U F U' F'"))); + + FHyperTwistAlgorithmSequence JsonRoundTrippedTransformedSequence; + if (!TryRoundTripJson( + *this, + CoalescedFromText, + JsonRoundTrippedTransformedSequence, + TEXT("expanded/coalesced sequence"))) + { + return false; + } + + TestTrue( + TEXT("Structured JSON preserves transformed sequence structure"), + UHyperTwistAlgorithmLibrary::AreAlgorithmsStructurallyEqual( + CoalescedFromText, + JsonRoundTrippedTransformedSequence)); + TestEqual( + TEXT("Structured JSON preserves transformed sequence canonical text"), + SerializeSequence(CoalescedFromText), + SerializeSequence(JsonRoundTrippedTransformedSequence)); + + return true; +} + #endif diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.cpp index beab94c..8e20ba0 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.cpp @@ -3,42 +3,30 @@ // 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 2 — Traversal and transformation helpers +// Bound 2 - Traversal and transformation helpers #include "HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h" -// --------------------------------------------------------------------------- -// Internal implementation helpers -// --------------------------------------------------------------------------- - namespace HyperTwistAlgorithmTraversalInternal { - // ----------------------------------------------------------------------- - // Block-move inversion - // Negates Amount: 1→-1, -1→1, 2→-2, -2→2, 3→-3, etc. - // All other fields (Family, MoveType, InnerLayer, OuterLayer) are unchanged. - // ----------------------------------------------------------------------- - static FHyperTwistAlgorithmBlockMove InvertBlockMove( - const FHyperTwistAlgorithmBlockMove& Move) + static FHyperTwistAlgorithmBlockMove InvertBlockMove(const FHyperTwistAlgorithmBlockMove& Move) { FHyperTwistAlgorithmBlockMove Result = Move; Result.Amount = -Move.Amount; return Result; } - // Forward-declare so InvertNode can call InvertNodeList. - static TArray InvertNodeList( - const TArray& Nodes); + static FHyperTwistAlgorithmNode MakeBlockMoveNode(const FHyperTwistAlgorithmBlockMove& Move) + { + FHyperTwistAlgorithmNode Result; + Result.NodeType = EHyperTwistAlgorithmNodeType::BlockMove; + Result.BlockMove = Move; + return Result; + } + + static TArray InvertNodeList(const TArray& Nodes); + static TArray CoalesceNodeList(const TArray& Nodes); - // ----------------------------------------------------------------------- - // Node inversion - // - // BlockMove : negate Amount - // Group : InvertNodeList(Inner), Amount unchanged - // Commutator : [A, B]' = [B, A] — swap operands - // Conjugate : [A: B]' = [A: B'] — invert B; A unchanged - // Pause / Newline / Comment : pass through - // ----------------------------------------------------------------------- static FHyperTwistAlgorithmNode InvertNode(const FHyperTwistAlgorithmNode& Node) { FHyperTwistAlgorithmNode Result = Node; @@ -51,18 +39,14 @@ namespace HyperTwistAlgorithmTraversalInternal case EHyperTwistAlgorithmNodeType::Group: Result.Group.Inner = InvertNodeList(Node.Group.Inner); - // Amount stays the same — the inverse repeats the inverted content - // the same number of times, not in a different direction. break; case EHyperTwistAlgorithmNodeType::Commutator: - // [A, B]' = [B, A] Result.Commutator.A = Node.Commutator.B; Result.Commutator.B = Node.Commutator.A; break; case EHyperTwistAlgorithmNodeType::Conjugate: - // [A: B]' = [A: B'] — setup A is unchanged; only B is inverted Result.Conjugate.A = Node.Conjugate.A; Result.Conjugate.B = InvertNodeList(Node.Conjugate.B); break; @@ -72,21 +56,13 @@ namespace HyperTwistAlgorithmTraversalInternal case EHyperTwistAlgorithmNodeType::Comment: case EHyperTwistAlgorithmNodeType::Sequence: default: - // These node types pass through unchanged. break; } return Result; } - // ----------------------------------------------------------------------- - // InvertNodeList - // Reverses the node ordering and inverts each node individually. - // This implements the standard group inversion rule: - // (A B C)' = C' B' A' - // ----------------------------------------------------------------------- - static TArray InvertNodeList( - const TArray& Nodes) + static TArray InvertNodeList(const TArray& Nodes) { TArray Result; Result.Reserve(Nodes.Num()); @@ -99,167 +75,105 @@ namespace HyperTwistAlgorithmTraversalInternal return Result; } - // ----------------------------------------------------------------------- - // Flat-move inversion helper used during expansion. - // Appends the inverse of each move (in reverse order) to OutMoves. - // Does not return a new array — appends directly for efficiency. - // ----------------------------------------------------------------------- - static void AppendInvertedBlockMoves( - const TArray& Moves, - TArray& OutMoves) - { - for (int32 Index = Moves.Num() - 1; Index >= 0; --Index) - { - OutMoves.Add(InvertBlockMove(Moves[Index])); - } - } - - static void AppendRepeatedMoves( - const TArray& Moves, + static void AppendRepeatedNodes( + const TArray& Nodes, const int32 Amount, - TArray& OutMoves) + TArray& OutNodes) { const int32 RepeatCount = FMath::Abs(Amount); - const bool bShouldInvert = Amount < 0; + if (RepeatCount == 0 || Nodes.Num() == 0) + { + return; + } + + if (Amount < 0) + { + const TArray InvertedNodes = InvertNodeList(Nodes); + for (int32 RepeatIndex = 0; RepeatIndex < RepeatCount; ++RepeatIndex) + { + OutNodes.Append(InvertedNodes); + } + return; + } + for (int32 RepeatIndex = 0; RepeatIndex < RepeatCount; ++RepeatIndex) { - if (bShouldInvert) - { - AppendInvertedBlockMoves(Moves, OutMoves); - } - else - { - OutMoves.Append(Moves); - } + OutNodes.Append(Nodes); } } - // Forward declaration for ExpandNodeListToMoves. - static void ExpandNodeListToMoves( + static void ExpandNodeListToSequence( const TArray& Nodes, - TArray& OutMoves); + TArray& OutNodes); - // ----------------------------------------------------------------------- - // ExpandNodeToMoves - // - // Recursively expands a single node into a flat BlockMove list. - // - // BlockMove → itself - // Group n → inner expanded then repeated n times - // (negative n → inverted inner repeated |n| times) - // Commutator → A + B + A⁻¹ + B⁻¹ - // Conjugate → A + B + A⁻¹ - // Other → skipped (Pause, Newline, Comment not emitted) - // ----------------------------------------------------------------------- - static void ExpandNodeToMoves( + static void ExpandNodeToSequence( const FHyperTwistAlgorithmNode& Node, - TArray& OutMoves) + TArray& OutNodes) { switch (Node.NodeType) { case EHyperTwistAlgorithmNodeType::BlockMove: - OutMoves.Add(Node.BlockMove); + case EHyperTwistAlgorithmNodeType::Pause: + case EHyperTwistAlgorithmNodeType::Newline: + case EHyperTwistAlgorithmNodeType::Comment: + OutNodes.Add(Node); break; case EHyperTwistAlgorithmNodeType::Group: { - TArray InnerMoves; - ExpandNodeListToMoves(Node.Group.Inner, InnerMoves); - AppendRepeatedMoves(InnerMoves, Node.Group.Amount, OutMoves); + TArray ExpandedInner; + ExpandNodeListToSequence(Node.Group.Inner, ExpandedInner); + AppendRepeatedNodes(ExpandedInner, Node.Group.Amount, OutNodes); } break; case EHyperTwistAlgorithmNodeType::Commutator: { - // [A, B] = A B A⁻¹ B⁻¹ - TArray AMoves; - TArray BMoves; - ExpandNodeListToMoves(Node.Commutator.A, AMoves); - ExpandNodeListToMoves(Node.Commutator.B, BMoves); + TArray ExpandedLeft; + TArray ExpandedRight; + TArray ExpandedNodeRun; + ExpandNodeListToSequence(Node.Commutator.A, ExpandedLeft); + ExpandNodeListToSequence(Node.Commutator.B, ExpandedRight); - TArray ExpandedMoves; - ExpandedMoves.Append(AMoves); - ExpandedMoves.Append(BMoves); - AppendInvertedBlockMoves(AMoves, ExpandedMoves); - AppendInvertedBlockMoves(BMoves, ExpandedMoves); - AppendRepeatedMoves(ExpandedMoves, Node.Commutator.Amount, OutMoves); + ExpandedNodeRun.Append(ExpandedLeft); + ExpandedNodeRun.Append(ExpandedRight); + ExpandedNodeRun.Append(InvertNodeList(ExpandedLeft)); + ExpandedNodeRun.Append(InvertNodeList(ExpandedRight)); + AppendRepeatedNodes(ExpandedNodeRun, Node.Commutator.Amount, OutNodes); } break; case EHyperTwistAlgorithmNodeType::Conjugate: { - // [A: B] = A B A⁻¹ - TArray AMoves; - TArray BMoves; - ExpandNodeListToMoves(Node.Conjugate.A, AMoves); - ExpandNodeListToMoves(Node.Conjugate.B, BMoves); + TArray ExpandedLeft; + TArray ExpandedRight; + TArray ExpandedNodeRun; + ExpandNodeListToSequence(Node.Conjugate.A, ExpandedLeft); + ExpandNodeListToSequence(Node.Conjugate.B, ExpandedRight); - TArray ExpandedMoves; - ExpandedMoves.Append(AMoves); - ExpandedMoves.Append(BMoves); - AppendInvertedBlockMoves(AMoves, ExpandedMoves); - AppendRepeatedMoves(ExpandedMoves, Node.Conjugate.Amount, OutMoves); + ExpandedNodeRun.Append(ExpandedLeft); + ExpandedNodeRun.Append(ExpandedRight); + ExpandedNodeRun.Append(InvertNodeList(ExpandedLeft)); + AppendRepeatedNodes(ExpandedNodeRun, Node.Conjugate.Amount, OutNodes); } break; - case EHyperTwistAlgorithmNodeType::Pause: - case EHyperTwistAlgorithmNodeType::Newline: - case EHyperTwistAlgorithmNodeType::Comment: case EHyperTwistAlgorithmNodeType::Sequence: default: - // Structural/annotation nodes are not emitted in the flat output. break; } } - // ----------------------------------------------------------------------- - // ExpandNodeListToMoves — expand an array of nodes in order - // ----------------------------------------------------------------------- - static void ExpandNodeListToMoves( + static void ExpandNodeListToSequence( const TArray& Nodes, - TArray& OutMoves) + TArray& OutNodes) { for (const FHyperTwistAlgorithmNode& Node : Nodes) { - ExpandNodeToMoves(Node, OutMoves); + ExpandNodeToSequence(Node, OutNodes); } } - // ----------------------------------------------------------------------- - // NormalizeAmount - // - // Reduce an arbitrary integer turn amount to the canonical range. - // Modulo 4 (since four quarter turns = identity for standard face turns), - // then bias toward the shortest representation in [-2, 2]: - // 0 → 0 (cancelled) - // 1 or -3 → 1 - // -1 or 3 → -1 - // 2 or -2 → 2 - // ----------------------------------------------------------------------- - static int32 NormalizeAmount(int32 Amount) - { - // Reduce modulo 4 to range (-3 .. 3). - Amount = Amount % 4; - - // Bias to [-2, 2] for canonical shortest-path representation. - if (Amount > 2) - { - Amount -= 4; // 3 → -1 - } - else if (Amount < -2) - { - Amount += 4; // -3 → 1 - } - - return Amount; - } - - // ----------------------------------------------------------------------- - // MovesAreCoalesceable - // - // Two moves coalesce if and only if they address the same physical slice: - // same Family, same MoveType, same InnerLayer, same OuterLayer. - // ----------------------------------------------------------------------- static bool MovesAreCoalesceable( const FHyperTwistAlgorithmBlockMove& A, const FHyperTwistAlgorithmBlockMove& B) @@ -271,75 +185,157 @@ namespace HyperTwistAlgorithmTraversalInternal && A.GetCanonicalOuterLayer() == B.GetCanonicalOuterLayer(); } -} // namespace HyperTwistAlgorithmTraversalInternal + static void AppendCoalescedBlockMove( + TArray& OutNodes, + const FHyperTwistAlgorithmBlockMove& Move) + { + if (Move.Amount == 0) + { + return; + } -// --------------------------------------------------------------------------- -// UHyperTwistAlgorithmTraversal — public API -// --------------------------------------------------------------------------- + if (OutNodes.Num() > 0 + && OutNodes.Last().NodeType == EHyperTwistAlgorithmNodeType::BlockMove + && MovesAreCoalesceable(OutNodes.Last().BlockMove, Move)) + { + const int32 CombinedAmount = OutNodes.Last().BlockMove.Amount + Move.Amount; + if (CombinedAmount == 0) + { + OutNodes.RemoveAt(OutNodes.Num() - 1); + } + else + { + OutNodes.Last().BlockMove.Amount = CombinedAmount; + } + return; + } + + OutNodes.Add(MakeBlockMoveNode(Move)); + } + + static FHyperTwistAlgorithmNode CoalesceNode(const FHyperTwistAlgorithmNode& Node) + { + FHyperTwistAlgorithmNode Result = Node; + + switch (Node.NodeType) + { + case EHyperTwistAlgorithmNodeType::Group: + Result.Group.Inner = CoalesceNodeList(Node.Group.Inner); + break; + + case EHyperTwistAlgorithmNodeType::Commutator: + Result.Commutator.A = CoalesceNodeList(Node.Commutator.A); + Result.Commutator.B = CoalesceNodeList(Node.Commutator.B); + break; + + case EHyperTwistAlgorithmNodeType::Conjugate: + Result.Conjugate.A = CoalesceNodeList(Node.Conjugate.A); + Result.Conjugate.B = CoalesceNodeList(Node.Conjugate.B); + break; + + case EHyperTwistAlgorithmNodeType::BlockMove: + case EHyperTwistAlgorithmNodeType::Pause: + case EHyperTwistAlgorithmNodeType::Newline: + case EHyperTwistAlgorithmNodeType::Comment: + case EHyperTwistAlgorithmNodeType::Sequence: + default: + break; + } + + return Result; + } + + static TArray CoalesceNodeList(const TArray& Nodes) + { + TArray Result; + Result.Reserve(Nodes.Num()); + + for (const FHyperTwistAlgorithmNode& Node : Nodes) + { + if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove) + { + AppendCoalescedBlockMove(Result, Node.BlockMove); + continue; + } + + Result.Add(CoalesceNode(Node)); + } + + return Result; + } +} FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::InvertSequence( const FHyperTwistAlgorithmSequence& Sequence) { - using namespace HyperTwistAlgorithmTraversalInternal; - FHyperTwistAlgorithmSequence Result; - Result.Nodes = InvertNodeList(Sequence.Nodes); + Result.Nodes = HyperTwistAlgorithmTraversalInternal::InvertNodeList(Sequence.Nodes); + return Result; +} + +FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::ExpandToFlatSequence( + const FHyperTwistAlgorithmSequence& Sequence) +{ + FHyperTwistAlgorithmSequence Result; + HyperTwistAlgorithmTraversalInternal::ExpandNodeListToSequence(Sequence.Nodes, Result.Nodes); return Result; } TArray UHyperTwistAlgorithmTraversal::ExpandToFlatMoves( const FHyperTwistAlgorithmSequence& Sequence) { - using namespace HyperTwistAlgorithmTraversalInternal; + const FHyperTwistAlgorithmSequence ExpandedSequence = ExpandToFlatSequence(Sequence); TArray Result; - ExpandNodeListToMoves(Sequence.Nodes, Result); + for (const FHyperTwistAlgorithmNode& Node : ExpandedSequence.Nodes) + { + if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove) + { + Result.Add(Node.BlockMove); + } + } + return Result; } TArray UHyperTwistAlgorithmTraversal::CoalesceMoves( const TArray& Moves) { - using namespace HyperTwistAlgorithmTraversalInternal; - - TArray Result; - Result.Reserve(Moves.Num()); + TArray CoalescedNodes; + CoalescedNodes.Reserve(Moves.Num()); for (const FHyperTwistAlgorithmBlockMove& Move : Moves) { - const int32 NormalizedAmount = NormalizeAmount(Move.Amount); - if (NormalizedAmount == 0) - { - // A move with a zero net amount is a no-op; skip it entirely. - continue; - } + HyperTwistAlgorithmTraversalInternal::AppendCoalescedBlockMove(CoalescedNodes, Move); + } - if (Result.Num() > 0 && MovesAreCoalesceable(Result.Last(), Move)) + TArray Result; + Result.Reserve(CoalescedNodes.Num()); + for (const FHyperTwistAlgorithmNode& Node : CoalescedNodes) + { + if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove) { - // Combine with the previous move. - const int32 Combined = NormalizeAmount(Result.Last().Amount + Move.Amount); - if (Combined == 0) - { - // They cancel — remove the previous move. - Result.RemoveAt(Result.Num() - 1); - } - else - { - Result.Last().Amount = Combined; - } - } - else - { - // Start a new coalescing window with this move (normalized). - FHyperTwistAlgorithmBlockMove NormalizedMove = Move; - NormalizedMove.Amount = NormalizedAmount; - Result.Add(NormalizedMove); + Result.Add(Node.BlockMove); } } return Result; } +FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::CoalesceSequence( + const FHyperTwistAlgorithmSequence& Sequence) +{ + FHyperTwistAlgorithmSequence Result; + Result.Nodes = HyperTwistAlgorithmTraversalInternal::CoalesceNodeList(Sequence.Nodes); + return Result; +} + +FHyperTwistAlgorithmSequence UHyperTwistAlgorithmTraversal::ExpandAndCoalesceSequence( + const FHyperTwistAlgorithmSequence& Sequence) +{ + return CoalesceSequence(ExpandToFlatSequence(Sequence)); +} + TArray UHyperTwistAlgorithmTraversal::ExpandAndSimplify( const FHyperTwistAlgorithmSequence& Sequence) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h index 78d3ee4..0e67957 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistAlgorithm/HyperTwistAlgorithmTraversal.h @@ -56,6 +56,20 @@ public: const FHyperTwistAlgorithmSequence& Sequence ); + // ----------------------------------------------------------------------- + // ExpandToFlatSequence + // + // Recursively expand an algorithm sequence into a flat owned sequence. + // Container nodes (Group, Commutator, Conjugate) are replaced by their + // expanded unit sequences. Non-container units such as Pause, Newline, + // and Comment are preserved in-order so the result can still flow through + // the canonical serializer and structured JSON surfaces. + // ----------------------------------------------------------------------- + UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal") + static FHyperTwistAlgorithmSequence ExpandToFlatSequence( + const FHyperTwistAlgorithmSequence& Sequence + ); + // ----------------------------------------------------------------------- // ExpandToFlatMoves // @@ -83,7 +97,8 @@ public: // Accept a flat move list and return a simplified list where: // - Adjacent moves with the same Family, MoveType, InnerLayer, and // OuterLayer are combined by summing their Amount values. - // - Amounts are normalized modulo 4 to the canonical range [-2, -1, 1, 2]. + // - Amounts are preserved as integer sums; no modulo-4 normalization + // is applied in this owner layer. // - Moves with a resulting amount of 0 are removed (cancelled). // // The function makes a single left-to-right pass; it does not repeat @@ -96,6 +111,31 @@ public: const TArray& Moves ); + // ----------------------------------------------------------------------- + // CoalesceSequence + // + // Coalesce adjacent block moves across the owned AST while preserving the + // surrounding unit stream. Non-move nodes remain in place and break + // adjacency. Composite containers are preserved structurally, with their + // child unit lists coalesced recursively. + // ----------------------------------------------------------------------- + UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal") + static FHyperTwistAlgorithmSequence CoalesceSequence( + const FHyperTwistAlgorithmSequence& Sequence + ); + + // ----------------------------------------------------------------------- + // ExpandAndCoalesceSequence + // + // Convenience: ExpandToFlatSequence followed by CoalesceSequence. + // Equivalent to: + // CoalesceSequence(ExpandToFlatSequence(Sequence)) + // ----------------------------------------------------------------------- + UFUNCTION(BlueprintPure, Category = "HyperTwist|Algorithm|Traversal") + static FHyperTwistAlgorithmSequence ExpandAndCoalesceSequence( + const FHyperTwistAlgorithmSequence& Sequence + ); + // ----------------------------------------------------------------------- // ExpandAndSimplify //