Implement cubing alg bound 2 traversal helpers

This commit is contained in:
axiomlogicnexus 2026-05-15 16:19:07 +02:00
parent fe75513126
commit d76b0cf9b7
3 changed files with 538 additions and 190 deletions

View file

@ -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<FInvertCase> 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<FExpandCase> 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<FCoalesceCase> 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<FHyperTwistAlgorithmBlockMove> 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

View file

@ -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<FHyperTwistAlgorithmNode> InvertNodeList(
const TArray<FHyperTwistAlgorithmNode>& Nodes);
static FHyperTwistAlgorithmNode MakeBlockMoveNode(const FHyperTwistAlgorithmBlockMove& Move)
{
FHyperTwistAlgorithmNode Result;
Result.NodeType = EHyperTwistAlgorithmNodeType::BlockMove;
Result.BlockMove = Move;
return Result;
}
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes);
static TArray<FHyperTwistAlgorithmNode> CoalesceNodeList(const TArray<FHyperTwistAlgorithmNode>& 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<FHyperTwistAlgorithmNode> InvertNodeList(
const TArray<FHyperTwistAlgorithmNode>& Nodes)
static TArray<FHyperTwistAlgorithmNode> InvertNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes)
{
TArray<FHyperTwistAlgorithmNode> 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<FHyperTwistAlgorithmBlockMove>& Moves,
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
{
for (int32 Index = Moves.Num() - 1; Index >= 0; --Index)
{
OutMoves.Add(InvertBlockMove(Moves[Index]));
}
}
static void AppendRepeatedMoves(
const TArray<FHyperTwistAlgorithmBlockMove>& Moves,
static void AppendRepeatedNodes(
const TArray<FHyperTwistAlgorithmNode>& Nodes,
const int32 Amount,
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
TArray<FHyperTwistAlgorithmNode>& OutNodes)
{
const int32 RepeatCount = FMath::Abs(Amount);
const bool bShouldInvert = Amount < 0;
if (RepeatCount == 0 || Nodes.Num() == 0)
{
return;
}
if (Amount < 0)
{
const TArray<FHyperTwistAlgorithmNode> 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<FHyperTwistAlgorithmNode>& Nodes,
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves);
TArray<FHyperTwistAlgorithmNode>& 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<FHyperTwistAlgorithmBlockMove>& OutMoves)
TArray<FHyperTwistAlgorithmNode>& 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<FHyperTwistAlgorithmBlockMove> InnerMoves;
ExpandNodeListToMoves(Node.Group.Inner, InnerMoves);
AppendRepeatedMoves(InnerMoves, Node.Group.Amount, OutMoves);
TArray<FHyperTwistAlgorithmNode> ExpandedInner;
ExpandNodeListToSequence(Node.Group.Inner, ExpandedInner);
AppendRepeatedNodes(ExpandedInner, Node.Group.Amount, OutNodes);
}
break;
case EHyperTwistAlgorithmNodeType::Commutator:
{
// [A, B] = A B A⁻¹ B⁻¹
TArray<FHyperTwistAlgorithmBlockMove> AMoves;
TArray<FHyperTwistAlgorithmBlockMove> BMoves;
ExpandNodeListToMoves(Node.Commutator.A, AMoves);
ExpandNodeListToMoves(Node.Commutator.B, BMoves);
TArray<FHyperTwistAlgorithmNode> ExpandedLeft;
TArray<FHyperTwistAlgorithmNode> ExpandedRight;
TArray<FHyperTwistAlgorithmNode> ExpandedNodeRun;
ExpandNodeListToSequence(Node.Commutator.A, ExpandedLeft);
ExpandNodeListToSequence(Node.Commutator.B, ExpandedRight);
TArray<FHyperTwistAlgorithmBlockMove> 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<FHyperTwistAlgorithmBlockMove> AMoves;
TArray<FHyperTwistAlgorithmBlockMove> BMoves;
ExpandNodeListToMoves(Node.Conjugate.A, AMoves);
ExpandNodeListToMoves(Node.Conjugate.B, BMoves);
TArray<FHyperTwistAlgorithmNode> ExpandedLeft;
TArray<FHyperTwistAlgorithmNode> ExpandedRight;
TArray<FHyperTwistAlgorithmNode> ExpandedNodeRun;
ExpandNodeListToSequence(Node.Conjugate.A, ExpandedLeft);
ExpandNodeListToSequence(Node.Conjugate.B, ExpandedRight);
TArray<FHyperTwistAlgorithmBlockMove> 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<FHyperTwistAlgorithmNode>& Nodes,
TArray<FHyperTwistAlgorithmBlockMove>& OutMoves)
TArray<FHyperTwistAlgorithmNode>& 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<FHyperTwistAlgorithmNode>& 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<FHyperTwistAlgorithmNode> CoalesceNodeList(const TArray<FHyperTwistAlgorithmNode>& Nodes)
{
TArray<FHyperTwistAlgorithmNode> 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<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::ExpandToFlatMoves(
const FHyperTwistAlgorithmSequence& Sequence)
{
using namespace HyperTwistAlgorithmTraversalInternal;
const FHyperTwistAlgorithmSequence ExpandedSequence = ExpandToFlatSequence(Sequence);
TArray<FHyperTwistAlgorithmBlockMove> Result;
ExpandNodeListToMoves(Sequence.Nodes, Result);
for (const FHyperTwistAlgorithmNode& Node : ExpandedSequence.Nodes)
{
if (Node.NodeType == EHyperTwistAlgorithmNodeType::BlockMove)
{
Result.Add(Node.BlockMove);
}
}
return Result;
}
TArray<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::CoalesceMoves(
const TArray<FHyperTwistAlgorithmBlockMove>& Moves)
{
using namespace HyperTwistAlgorithmTraversalInternal;
TArray<FHyperTwistAlgorithmBlockMove> Result;
Result.Reserve(Moves.Num());
TArray<FHyperTwistAlgorithmNode> 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<FHyperTwistAlgorithmBlockMove> 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<FHyperTwistAlgorithmBlockMove> UHyperTwistAlgorithmTraversal::ExpandAndSimplify(
const FHyperTwistAlgorithmSequence& Sequence)
{

View file

@ -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<FHyperTwistAlgorithmBlockMove>& 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
//