From 175fdce3fb862e064faf2d846dbb45e5b6dc61f3 Mon Sep 17 00:00:00 2001 From: axiomlogicnexus Date: Thu, 21 May 2026 01:35:34 +0200 Subject: [PATCH] Implement Phase 6R-C reconstruction session contract --- .../HyperTwistContractLibrary.cpp | 152 ++++- .../HyperTwistMockVisionClient.cpp | 82 ++- .../HyperTwistTrainingSubsystem.cpp | 561 +++++++++++++++++- .../HyperTwistRecognitionTypes.h | 208 +++++++ .../HyperTwistTrainingTypes.h | 3 + ...lverPhase6RCReconstructionContractTest.cpp | 245 ++++++++ docs/REPO_LICENSE_TRACKING.md | 41 +- ...UCTION_IMPLEMENTATION_PACKET_2026-05-20.md | 134 +++++ ...STRUCTION_PREPARATION_PACKET_2026-05-20.md | 184 ++++++ .../HyperTwist/ROADMAP.md | 43 +- 10 files changed, 1616 insertions(+), 37 deletions(-) create mode 100644 UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistRubixCubeSolverPhase6RCReconstructionContractTest.cpp create mode 100644 docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_IMPLEMENTATION_PACKET_2026-05-20.md create mode 100644 docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_PREPARATION_PACKET_2026-05-20.md diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index e1649eb..f11709a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -131,6 +131,55 @@ namespace HyperTwistContractLibraryInternal return Profile; } + void ResolveMockClassicFaceSwatch( + const FString& FaceId, + FString& OutColorId, + FString& OutNotation, + FIntVector& OutSampleBgr + ) + { + const FString NormalizedFaceId = FaceId.ToUpper(); + if (NormalizedFaceId == TEXT("U")) + { + OutColorId = TEXT("white"); + OutNotation = TEXT("U"); + OutSampleBgr = FIntVector(255, 255, 255); + return; + } + if (NormalizedFaceId == TEXT("R")) + { + OutColorId = TEXT("red"); + OutNotation = TEXT("R"); + OutSampleBgr = FIntVector(0, 0, 255); + return; + } + if (NormalizedFaceId == TEXT("D")) + { + OutColorId = TEXT("yellow"); + OutNotation = TEXT("D"); + OutSampleBgr = FIntVector(0, 255, 255); + return; + } + if (NormalizedFaceId == TEXT("L")) + { + OutColorId = TEXT("orange"); + OutNotation = TEXT("L"); + OutSampleBgr = FIntVector(0, 165, 255); + return; + } + if (NormalizedFaceId == TEXT("B")) + { + OutColorId = TEXT("blue"); + OutNotation = TEXT("B"); + OutSampleBgr = FIntVector(255, 0, 0); + return; + } + + OutColorId = TEXT("green"); + OutNotation = TEXT("F"); + OutSampleBgr = FIntVector(0, 255, 0); + } + FHyperTwistVisionFaceObservation MakeMockClassicFaceObservation( const FString& ObservationId, const FString& FaceId, @@ -148,21 +197,103 @@ namespace HyperTwistContractLibraryInternal Observation.bOrderingStable = true; Observation.bCommitReady = bCommitReady; + FString ColorId; + FString Notation; + FIntVector SampleBgr = FIntVector::ZeroValue; + ResolveMockClassicFaceSwatch(FaceId, ColorId, Notation, SampleBgr); + for (int32 GridIndex = 0; GridIndex < 9; ++GridIndex) { FHyperTwistVisionObservedSticker Sticker; Sticker.GridIndex = GridIndex; Sticker.GridRow = GridIndex / 3; Sticker.GridColumn = GridIndex % 3; - Sticker.ColorId = TEXT("green"); - Sticker.Notation = TEXT("F"); - Sticker.SampleBgr = FIntVector(0, 255, 0); + Sticker.ColorId = ColorId; + Sticker.Notation = Notation; + Sticker.SampleBgr = SampleBgr; Sticker.Confidence = StickerConfidence; Observation.OrderedStickers.Add(Sticker); } return Observation; } + + FHyperTwistVisionReconstructionSession MakeMockClassicReconstructionSession( + const FString& SessionId, + const TArray& FaceIds, + const float StickerConfidence + ) + { + FHyperTwistVisionReconstructionSession Session; + Session.SessionId = SessionId; + Session.PuzzleId = TEXT("cube/3x3x3"); + Session.ReconstructionProfile = TEXT("classic-face-vote-v1"); + Session.OrientationProfile = TEXT("classic-target-face-net-v1"); + Session.bUsesObservationVoting = true; + Session.bHasStableOrientation = FaceIds.Num() > 0; + + const TArray ExpectedFaces = { + TEXT("U"), + TEXT("R"), + TEXT("F"), + TEXT("D"), + TEXT("L"), + TEXT("B") + }; + + for (const FString& FaceId : FaceIds) + { + FHyperTwistVisionFaceObservation Observation = MakeMockClassicFaceObservation( + FString::Printf(TEXT("%s/%s"), *SessionId, *FaceId), + FaceId, + StickerConfidence, + true + ); + + FHyperTwistVisionCommittedFaceState FaceState; + FaceState.FaceId = FaceId; + FaceState.LatestObservationId = Observation.ObservationId; + FaceState.OrderingProfile = Observation.OrderingProfile; + FaceState.ObservationCount = 1; + FaceState.RevisionCount = 0; + FaceState.LatestOrderedStickers = Observation.OrderedStickers; + + for (const FHyperTwistVisionObservedSticker& Sticker : Observation.OrderedStickers) + { + FHyperTwistVisionStickerVoteTally Tally; + Tally.Notation = Sticker.Notation; + Tally.ColorId = Sticker.ColorId; + Tally.VoteCount = 1; + + FHyperTwistVisionReconstructionStickerVote Vote; + Vote.GridIndex = Sticker.GridIndex; + Vote.GridRow = Sticker.GridRow; + Vote.GridColumn = Sticker.GridColumn; + Vote.WinningNotation = Sticker.Notation; + Vote.WinningColorId = Sticker.ColorId; + Vote.WinningVoteCount = 1; + Vote.TotalVoteCount = 1; + Vote.Tallies.Add(Tally); + FaceState.StickerVotes.Add(Vote); + } + + Session.CommittedFaces.Add(FaceState); + } + + Session.TotalCommittedFaceCount = Session.CommittedFaces.Num(); + Session.TotalRevisionCount = 0; + Session.ConfidenceRollup = StickerConfidence; + + for (const FString& ExpectedFace : ExpectedFaces) + { + if (!FaceIds.Contains(ExpectedFace)) + { + Session.MissingFaces.Add(ExpectedFace); + } + } + Session.bHasCompleteClassicCubeNet = Session.MissingFaces.Num() == 0; + return Session; + } } FHyperTwistPuzzleDefinitionRef UHyperTwistContractLibrary::MakeSampleClassicPuzzleDefinition() @@ -435,6 +566,11 @@ FHyperTwistVisionCommitResult UHyperTwistContractLibrary::MakeMockVisionCommitRe 0.96f, true ); + Result.ReconstructionSession = HyperTwistContractLibraryInternal::MakeMockClassicReconstructionSession( + Result.SessionId, + {TEXT("F")}, + Result.Confidence + ); return Result; } @@ -449,8 +585,13 @@ FHyperTwistVisionFinalizeResult UHyperTwistContractLibrary::MakeMockVisionFinali Result.FinalSnapshot.SnapshotId = TEXT("snap_final"); Result.FinalSnapshot.State = Result.FinalState; Result.FinalSnapshot.DerivedHash = TEXT("final_hash"); - Result.ReplaySeedEventsJson.Add(SerializeReplayPacketToJson(MakeSampleRecognitionReplayPacket())); Result.ConfidenceRollup = 0.97f; + Result.ReconstructionSession = HyperTwistContractLibraryInternal::MakeMockClassicReconstructionSession( + Result.SessionId, + {TEXT("U"), TEXT("R"), TEXT("F"), TEXT("D"), TEXT("L"), TEXT("B")}, + Result.ConfidenceRollup + ); + Result.ReplaySeedEventsJson.Add(SerializeReplayPacketToJson(MakeSampleRecognitionReplayPacket())); return Result; } @@ -464,7 +605,8 @@ FHyperTwistVisionServiceHealth UHyperTwistContractLibrary::MakeMockVisionService TEXT("liveCamera3x3"), TEXT("faceCommitFlow"), TEXT("calibrationProfiles"), - TEXT("orderedFaceObservation") + TEXT("orderedFaceObservation"), + TEXT("reconstructionSession") }; Health.bReady = true; return Health; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockVisionClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockVisionClient.cpp index 6cc97db..49ffee5 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockVisionClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockVisionClient.cpp @@ -2,6 +2,78 @@ #include "HyperTwistBootstrap/HyperTwistContractLibrary.h" +namespace HyperTwistMockVisionClientInternal +{ + void ResolveMockClassicFaceSwatch( + const FString& FaceId, + FString& OutColorId, + FString& OutNotation, + FIntVector& OutSampleBgr + ) + { + const FString NormalizedFaceId = FaceId.ToUpper(); + if (NormalizedFaceId == TEXT("U")) + { + OutColorId = TEXT("white"); + OutNotation = TEXT("U"); + OutSampleBgr = FIntVector(255, 255, 255); + return; + } + if (NormalizedFaceId == TEXT("R")) + { + OutColorId = TEXT("red"); + OutNotation = TEXT("R"); + OutSampleBgr = FIntVector(0, 0, 255); + return; + } + if (NormalizedFaceId == TEXT("D")) + { + OutColorId = TEXT("yellow"); + OutNotation = TEXT("D"); + OutSampleBgr = FIntVector(0, 255, 255); + return; + } + if (NormalizedFaceId == TEXT("L")) + { + OutColorId = TEXT("orange"); + OutNotation = TEXT("L"); + OutSampleBgr = FIntVector(0, 165, 255); + return; + } + if (NormalizedFaceId == TEXT("B")) + { + OutColorId = TEXT("blue"); + OutNotation = TEXT("B"); + OutSampleBgr = FIntVector(255, 0, 0); + return; + } + + OutColorId = TEXT("green"); + OutNotation = TEXT("F"); + OutSampleBgr = FIntVector(0, 255, 0); + } + + void ApplyMockClassicFaceToObservation( + FHyperTwistVisionFaceObservation& Observation, + const FString& FaceId + ) + { + Observation.FaceId = FaceId; + + FString ColorId; + FString Notation; + FIntVector SampleBgr = FIntVector::ZeroValue; + ResolveMockClassicFaceSwatch(FaceId, ColorId, Notation, SampleBgr); + + for (FHyperTwistVisionObservedSticker& Sticker : Observation.OrderedStickers) + { + Sticker.ColorId = ColorId; + Sticker.Notation = Notation; + Sticker.SampleBgr = SampleBgr; + } + } +} + bool UHyperTwistMockVisionClient::OpenVisionSession(const FHyperTwistVisionSessionConfig& Config, FString& OutError) { OutError.Reset(); @@ -26,7 +98,10 @@ FHyperTwistVisionPreviewResult UHyperTwistMockVisionClient::SubmitVisionFrame(co const int32 NextFrameCount = SessionFrameCounts.FindRef(Frame.SessionId) + 1; SessionFrameCounts.Add(Frame.SessionId, NextFrameCount); - Result.ObservedFace.FaceId = !Frame.TargetFaceHint.IsEmpty() ? Frame.TargetFaceHint : Result.ObservedFace.FaceId; + HyperTwistMockVisionClientInternal::ApplyMockClassicFaceToObservation( + Result.ObservedFace, + !Frame.TargetFaceHint.IsEmpty() ? Frame.TargetFaceHint : Result.ObservedFace.FaceId + ); Result.ObservedFace.ObservationId = Result.FrameId.IsEmpty() ? TEXT("observation_preview") : FString::Printf(TEXT("%s/preview"), *Result.FrameId); @@ -43,7 +118,10 @@ FHyperTwistVisionCommitResult UHyperTwistMockVisionClient::CommitVisionObservati Result.CommitKind = Request.CommitKind; Result.CommittedUnit = !Request.TargetFace.IsEmpty() ? Request.TargetFace : Request.TargetStage; Result.Snapshot.State.SourceSessionId = Request.SessionId; - Result.ObservedFace.FaceId = !Request.TargetFace.IsEmpty() ? Request.TargetFace : Result.ObservedFace.FaceId; + HyperTwistMockVisionClientInternal::ApplyMockClassicFaceToObservation( + Result.ObservedFace, + !Request.TargetFace.IsEmpty() ? Request.TargetFace : Result.ObservedFace.FaceId + ); Result.ObservedFace.ObservationId = Request.SessionId.IsEmpty() ? TEXT("observation_commit") : FString::Printf(TEXT("%s/commit"), *Request.SessionId); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 63075ac..ec4e52a 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -1872,6 +1872,464 @@ namespace HyperTwistTrainingSubsystemInternal && SessionConfig.PuzzleId == TEXT("cube/3x3x3"); } + const TArray& GetClassicRecognitionFaceOrder() + { + static const TArray FaceOrder = { + TEXT("U"), + TEXT("R"), + TEXT("F"), + TEXT("D"), + TEXT("L"), + TEXT("B") + }; + return FaceOrder; + } + + FString NormalizeRecognitionFaceId(const FString& FaceId) + { + FString Normalized = FaceId; + Normalized = Normalized.TrimStartAndEnd(); + Normalized.ToUpperInline(); + return Normalized; + } + + int32 GetClassicRecognitionFaceOrderIndex(const FString& FaceId) + { + const FString NormalizedFaceId = NormalizeRecognitionFaceId(FaceId); + const TArray& FaceOrder = GetClassicRecognitionFaceOrder(); + for (int32 Index = 0; Index < FaceOrder.Num(); ++Index) + { + if (FaceOrder[Index] == NormalizedFaceId) + { + return Index; + } + } + + return MAX_int32; + } + + const FHyperTwistVisionCommittedFaceState* FindCommittedFaceState( + const FHyperTwistVisionReconstructionSession& Session, + const FString& FaceId + ) + { + const FString NormalizedFaceId = NormalizeRecognitionFaceId(FaceId); + return Session.CommittedFaces.FindByPredicate( + [&NormalizedFaceId](const FHyperTwistVisionCommittedFaceState& Candidate) + { + return NormalizeRecognitionFaceId(Candidate.FaceId) == NormalizedFaceId; + } + ); + } + + const FHyperTwistVisionReconstructionStickerVote* FindStickerVote( + const FHyperTwistVisionCommittedFaceState& FaceState, + const int32 GridIndex + ) + { + return FaceState.StickerVotes.FindByPredicate( + [GridIndex](const FHyperTwistVisionReconstructionStickerVote& Candidate) + { + return Candidate.GridIndex == GridIndex; + } + ); + } + + FString MakeStickerVoteKey(const FString& Notation, const FString& ColorId) + { + FString NormalizedColorId = ColorId; + NormalizedColorId.ToLowerInline(); + return FString::Printf( + TEXT("%s|%s"), + *NormalizeRecognitionFaceId(Notation), + *NormalizedColorId + ); + } + + FString ResolveClassicFaceletToken(const FString& Notation, const FString& ColorId) + { + FString NormalizedColorId = ColorId; + NormalizedColorId.ToLowerInline(); + if (NormalizedColorId == TEXT("white")) + { + return TEXT("W"); + } + if (NormalizedColorId == TEXT("red")) + { + return TEXT("R"); + } + if (NormalizedColorId == TEXT("green")) + { + return TEXT("G"); + } + if (NormalizedColorId == TEXT("yellow")) + { + return TEXT("Y"); + } + if (NormalizedColorId == TEXT("orange")) + { + return TEXT("O"); + } + if (NormalizedColorId == TEXT("blue")) + { + return TEXT("B"); + } + + const FString NormalizedNotation = NormalizeRecognitionFaceId(Notation); + if (NormalizedNotation == TEXT("U")) + { + return TEXT("W"); + } + if (NormalizedNotation == TEXT("R")) + { + return TEXT("R"); + } + if (NormalizedNotation == TEXT("F")) + { + return TEXT("G"); + } + if (NormalizedNotation == TEXT("D")) + { + return TEXT("Y"); + } + if (NormalizedNotation == TEXT("L")) + { + return TEXT("O"); + } + if (NormalizedNotation == TEXT("B")) + { + return TEXT("B"); + } + + return FString(); + } + + bool IsUniformFaceletTokenArray(const TArray& FaceTokens) + { + if (FaceTokens.Num() != 9 || FaceTokens[0].IsEmpty()) + { + return false; + } + + for (int32 Index = 1; Index < FaceTokens.Num(); ++Index) + { + if (FaceTokens[Index] != FaceTokens[0]) + { + return false; + } + } + + return true; + } + + TArray* ResolveMutableClassicFaceTokens( + FHyperTwistClassicFaceletSnapshotState& FaceletState, + const FString& FaceId + ) + { + const FString NormalizedFaceId = NormalizeRecognitionFaceId(FaceId); + if (NormalizedFaceId == TEXT("U")) + { + return &FaceletState.U; + } + if (NormalizedFaceId == TEXT("R")) + { + return &FaceletState.R; + } + if (NormalizedFaceId == TEXT("F")) + { + return &FaceletState.F; + } + if (NormalizedFaceId == TEXT("D")) + { + return &FaceletState.D; + } + if (NormalizedFaceId == TEXT("L")) + { + return &FaceletState.L; + } + if (NormalizedFaceId == TEXT("B")) + { + return &FaceletState.B; + } + + return nullptr; + } + + FHyperTwistVisionReconstructionSession BuildClassicRecognitionReconstructionSession( + const FHyperTwistVisionSessionConfig& SessionConfig, + const FString& SessionId, + const FHyperTwistVisionReconstructionSession& ExistingSession, + const FHyperTwistVisionFaceObservation& Observation, + bool& OutWasRevision + ) + { + OutWasRevision = false; + + FHyperTwistVisionReconstructionSession Session = ExistingSession.IsStructurallyValid() + ? ExistingSession + : FHyperTwistVisionReconstructionSession(); + Session.SessionId = SessionId; + Session.PuzzleId = !SessionConfig.PuzzleId.IsEmpty() ? SessionConfig.PuzzleId : TEXT("cube/3x3x3"); + Session.ReconstructionProfile = TEXT("classic-face-vote-v1"); + Session.OrientationProfile = TEXT("classic-target-face-net-v1"); + Session.bUsesObservationVoting = true; + + const FString FaceId = NormalizeRecognitionFaceId(Observation.FaceId); + const FHyperTwistVisionCommittedFaceState* PreviousFaceState = FindCommittedFaceState(Session, FaceId); + OutWasRevision = PreviousFaceState != nullptr; + + FHyperTwistVisionCommittedFaceState FaceState; + FaceState.FaceId = FaceId; + FaceState.LatestObservationId = Observation.ObservationId; + FaceState.OrderingProfile = Observation.OrderingProfile; + FaceState.ObservationCount = PreviousFaceState != nullptr + ? PreviousFaceState->ObservationCount + 1 + : 1; + FaceState.RevisionCount = PreviousFaceState != nullptr + ? PreviousFaceState->RevisionCount + 1 + : 0; + FaceState.LatestOrderedStickers = Observation.OrderedStickers; + + for (const FHyperTwistVisionObservedSticker& Sticker : Observation.OrderedStickers) + { + TArray Tallies; + if (PreviousFaceState != nullptr) + { + if (const FHyperTwistVisionReconstructionStickerVote* PreviousVote = + FindStickerVote(*PreviousFaceState, Sticker.GridIndex)) + { + Tallies = PreviousVote->Tallies; + } + } + + const FString StickerVoteKey = MakeStickerVoteKey(Sticker.Notation, Sticker.ColorId); + bool bUpdatedExistingTally = false; + for (FHyperTwistVisionStickerVoteTally& Tally : Tallies) + { + if (MakeStickerVoteKey(Tally.Notation, Tally.ColorId) == StickerVoteKey) + { + ++Tally.VoteCount; + bUpdatedExistingTally = true; + break; + } + } + + if (!bUpdatedExistingTally) + { + FHyperTwistVisionStickerVoteTally Tally; + Tally.Notation = NormalizeRecognitionFaceId(Sticker.Notation); + Tally.ColorId = Sticker.ColorId; + Tally.VoteCount = 1; + Tallies.Add(Tally); + } + + int32 WinningIndex = INDEX_NONE; + int32 TotalVoteCount = 0; + for (int32 TallyIndex = 0; TallyIndex < Tallies.Num(); ++TallyIndex) + { + const FHyperTwistVisionStickerVoteTally& Tally = Tallies[TallyIndex]; + TotalVoteCount += Tally.VoteCount; + + const bool bPreferThisTally = WinningIndex == INDEX_NONE + || Tally.VoteCount > Tallies[WinningIndex].VoteCount + || (Tally.VoteCount == Tallies[WinningIndex].VoteCount + && MakeStickerVoteKey(Tally.Notation, Tally.ColorId) == StickerVoteKey); + if (bPreferThisTally) + { + WinningIndex = TallyIndex; + } + } + + if (WinningIndex == INDEX_NONE) + { + continue; + } + + const FHyperTwistVisionStickerVoteTally& WinningTally = Tallies[WinningIndex]; + + FHyperTwistVisionReconstructionStickerVote Vote; + Vote.GridIndex = Sticker.GridIndex; + Vote.GridRow = Sticker.GridRow; + Vote.GridColumn = Sticker.GridColumn; + Vote.WinningNotation = WinningTally.Notation; + Vote.WinningColorId = WinningTally.ColorId; + Vote.WinningVoteCount = WinningTally.VoteCount; + Vote.TotalVoteCount = TotalVoteCount; + Vote.bHadConflict = Tallies.Num() > 1; + Vote.Tallies = Tallies; + FaceState.bHasConflicts = FaceState.bHasConflicts || Vote.bHadConflict; + FaceState.StickerVotes.Add(Vote); + } + + const int32 ExistingFaceIndex = Session.CommittedFaces.IndexOfByPredicate( + [&FaceId](const FHyperTwistVisionCommittedFaceState& Candidate) + { + return NormalizeRecognitionFaceId(Candidate.FaceId) == FaceId; + } + ); + if (ExistingFaceIndex == INDEX_NONE) + { + Session.CommittedFaces.Add(FaceState); + } + else + { + Session.CommittedFaces[ExistingFaceIndex] = FaceState; + } + + Session.CommittedFaces.Sort( + [](const FHyperTwistVisionCommittedFaceState& Left, const FHyperTwistVisionCommittedFaceState& Right) + { + return GetClassicRecognitionFaceOrderIndex(Left.FaceId) + < GetClassicRecognitionFaceOrderIndex(Right.FaceId); + } + ); + + Session.TotalCommittedFaceCount = Session.CommittedFaces.Num(); + Session.TotalRevisionCount = 0; + float ConfidenceSum = 0.0f; + int32 ConfidenceCount = 0; + for (const FHyperTwistVisionCommittedFaceState& CommittedFace : Session.CommittedFaces) + { + Session.TotalRevisionCount += CommittedFace.RevisionCount; + for (const FHyperTwistVisionObservedSticker& LatestSticker : CommittedFace.LatestOrderedStickers) + { + ConfidenceSum += LatestSticker.Confidence; + ++ConfidenceCount; + } + } + Session.ConfidenceRollup = ConfidenceCount > 0 + ? ConfidenceSum / static_cast(ConfidenceCount) + : 0.0f; + Session.bHasStableOrientation = Session.TotalCommittedFaceCount > 0; + Session.OrientationStateOrdinal = Session.TotalCommittedFaceCount; + + Session.MissingFaces.Reset(); + for (const FString& ExpectedFaceId : GetClassicRecognitionFaceOrder()) + { + if (FindCommittedFaceState(Session, ExpectedFaceId) == nullptr) + { + Session.MissingFaces.Add(ExpectedFaceId); + } + } + Session.bHasCompleteClassicCubeNet = + Session.MissingFaces.Num() == 0 + && Session.CommittedFaces.Num() == GetClassicRecognitionFaceOrder().Num(); + + return Session; + } + + bool TryBuildClassicFaceletSnapshotFromReconstruction( + const FHyperTwistVisionReconstructionSession& Session, + FHyperTwistClassicFaceletSnapshotState& OutFaceletState + ) + { + if (!Session.IsStructurallyValid() || !Session.bHasCompleteClassicCubeNet) + { + return false; + } + + OutFaceletState = FHyperTwistClassicFaceletSnapshotState(); + OutFaceletState.bPreviewState = false; + + for (const FString& ExpectedFaceId : GetClassicRecognitionFaceOrder()) + { + const FHyperTwistVisionCommittedFaceState* CommittedFace = FindCommittedFaceState(Session, ExpectedFaceId); + TArray* FaceTokens = ResolveMutableClassicFaceTokens(OutFaceletState, ExpectedFaceId); + if (CommittedFace == nullptr || FaceTokens == nullptr || CommittedFace->StickerVotes.Num() != 9) + { + return false; + } + + FaceTokens->Reset(); + for (int32 GridIndex = 0; GridIndex < 9; ++GridIndex) + { + const FHyperTwistVisionReconstructionStickerVote* Vote = FindStickerVote(*CommittedFace, GridIndex); + if (Vote == nullptr) + { + return false; + } + + const FString Token = ResolveClassicFaceletToken(Vote->WinningNotation, Vote->WinningColorId); + if (Token.IsEmpty()) + { + return false; + } + + FaceTokens->Add(Token); + } + } + + return OutFaceletState.U.Num() == 9 + && OutFaceletState.R.Num() == 9 + && OutFaceletState.F.Num() == 9 + && OutFaceletState.D.Num() == 9 + && OutFaceletState.L.Num() == 9 + && OutFaceletState.B.Num() == 9; + } + + bool TryBuildClassicRecognitionStateSnapshot( + const FHyperTwistVisionSessionConfig& SessionConfig, + const FHyperTwistVisionReconstructionSession& Session, + FHyperTwistPuzzleState& OutState, + FHyperTwistStateSnapshot& OutSnapshot + ) + { + FHyperTwistClassicFaceletSnapshotState FaceletState; + if (!TryBuildClassicFaceletSnapshotFromReconstruction(Session, FaceletState)) + { + return false; + } + + FString PayloadJson; + if (!SerializeStruct(FaceletState, PayloadJson)) + { + return false; + } + + FHyperTwistPuzzleDefinitionRef Definition; + Definition.PuzzleId = !Session.PuzzleId.IsEmpty() ? Session.PuzzleId : TEXT("cube/3x3x3"); + Definition.PuzzleFamily = EHyperTwistPuzzleFamily::ClassicCube; + Definition.Dimension = 3; + Definition.DefinitionVersion = TEXT("2026.05"); + Definition.NotationProfile = !SessionConfig.NotationProfile.IsEmpty() + ? SessionConfig.NotationProfile + : TEXT("classic-wca"); + Definition.SizeVector = {3, 3, 3}; + + OutState = FHyperTwistPuzzleState(); + OutState.Definition = Definition; + OutState.StateEncodingKind = EHyperTwistStateEncodingKind::Facelet; + OutState.StateEncoding.EncodingProfile = FaceletState.EncodingProfile; + OutState.StateEncoding.PayloadJson = PayloadJson; + OutState.OrientationFrame.Reference = !Session.OrientationProfile.IsEmpty() + ? Session.OrientationProfile + : TEXT("classic-target-face-net-v1"); + OutState.bIsSolved = + IsUniformFaceletTokenArray(FaceletState.U) + && IsUniformFaceletTokenArray(FaceletState.R) + && IsUniformFaceletTokenArray(FaceletState.F) + && IsUniformFaceletTokenArray(FaceletState.D) + && IsUniformFaceletTokenArray(FaceletState.L) + && IsUniformFaceletTokenArray(FaceletState.B); + OutState.Source = EHyperTwistStateSource::Recognition; + OutState.CapturedAtUtc = FDateTime::UtcNow().ToIso8601(); + OutState.SourceConfidence = Session.ConfidenceRollup; + OutState.SourceSessionId = Session.SessionId; + OutState.Notes = TEXT("Phase 6R-C reconstruction session aggregate."); + + OutSnapshot = FHyperTwistStateSnapshot(); + OutSnapshot.SnapshotId = FString::Printf(TEXT("recognition_reconstruction_%s"), *Session.SessionId); + OutSnapshot.State = OutState; + OutSnapshot.DerivedHash = FString::Printf( + TEXT("recognition-reconstruction-%s-%d-%d"), + *Session.SessionId, + Session.TotalCommittedFaceCount, + Session.TotalRevisionCount + ); + return OutSnapshot.IsStructurallyValid(); + } + FHyperTwistVisionCalibrationProfile BuildDefaultClassicCubeCalibrationProfile(const FString& ProfileId) { FHyperTwistVisionCalibrationProfile Profile; @@ -6078,6 +6536,7 @@ bool UHyperTwistTrainingSubsystem::OpenActiveRecognitionSession(FString& OutErro ActiveRecognitionSessionState.SessionConfig = SessionConfig; ActiveRecognitionSessionState.bHasPreviewResult = false; ActiveRecognitionSessionState.bHasCommitResult = false; + ActiveRecognitionSessionState.ActiveReconstructionSession = FHyperTwistVisionReconstructionSession(); ActiveRecognitionSessionState.LastPreviewResult = FHyperTwistVisionPreviewResult(); ActiveRecognitionSessionState.LastCommitResult = FHyperTwistVisionCommitResult(); RefreshRecognitionServiceHealth(); @@ -6231,16 +6690,70 @@ FHyperTwistVisionCommitResult UHyperTwistTrainingSubsystem::CommitActiveRecognit Result.Snapshot.State.SourceSessionId = NormalizedRequest.SessionId; } + bool bWasReconstructionRevision = false; + if (HyperTwistTrainingSubsystemInternal::IsQbrClassicCubeRoute(ActiveRecognitionSessionState.SessionConfig) + && Result.ObservedFace.IsStructurallyValid()) + { + if (Result.CommittedUnit.IsEmpty()) + { + Result.CommittedUnit = HyperTwistTrainingSubsystemInternal::NormalizeRecognitionFaceId( + Result.ObservedFace.FaceId + ); + } + + Result.ReconstructionSession = + HyperTwistTrainingSubsystemInternal::BuildClassicRecognitionReconstructionSession( + ActiveRecognitionSessionState.SessionConfig, + Result.SessionId, + ActiveRecognitionSessionState.ActiveReconstructionSession, + Result.ObservedFace, + bWasReconstructionRevision + ); + ActiveRecognitionSessionState.ActiveReconstructionSession = Result.ReconstructionSession; + + TArray ProviderMissingUnits = Result.MissingUnits; + Result.MissingUnits = Result.ReconstructionSession.MissingFaces; + for (const FString& ProviderMissingUnit : ProviderMissingUnits) + { + Result.MissingUnits.AddUnique(ProviderMissingUnit); + } + + Result.Confidence = Result.ReconstructionSession.ConfidenceRollup; + if (Result.ReconstructionSession.bHasCompleteClassicCubeNet) + { + FHyperTwistPuzzleState ReconstructedState; + FHyperTwistStateSnapshot ReconstructedSnapshot; + if (HyperTwistTrainingSubsystemInternal::TryBuildClassicRecognitionStateSnapshot( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + ReconstructedState, + ReconstructedSnapshot + )) + { + Result.Snapshot = ReconstructedSnapshot; + } + else + { + Result.Snapshot = FHyperTwistStateSnapshot(); + } + } + else + { + Result.Snapshot = FHyperTwistStateSnapshot(); + } + } + const FString ResolvedStage = !Result.CommittedUnit.IsEmpty() ? Result.CommittedUnit : (!NormalizedRequest.TargetStage.IsEmpty() ? NormalizedRequest.TargetStage : NormalizedRequest.TargetFace); const bool bHasCorrectionSignals = - Result.Warnings.Num() > 0 || Result.MissingUnits.Num() > 0; + Result.Warnings.Num() > 0 || bWasReconstructionRevision; const bool bHasCommitSignal = !Result.CommittedUnit.IsEmpty() || Result.Snapshot.IsStructurallyValid() || Result.Snapshot.State.IsStructurallyValid() - || Result.Confidence > 0.0f; + || Result.Confidence > 0.0f + || Result.ReconstructionSession.IsStructurallyValid(); if (bHasCorrectionSignals) { @@ -6327,6 +6840,48 @@ FHyperTwistVisionFinalizeResult UHyperTwistTrainingSubsystem::FinalizeActiveReco Result.FinalSnapshot.State.SourceSessionId = SessionId; } + if (HyperTwistTrainingSubsystemInternal::IsQbrClassicCubeRoute(ActiveRecognitionSessionState.SessionConfig)) + { + Result.ReconstructionSession = ActiveRecognitionSessionState.ActiveReconstructionSession; + if (Result.ReconstructionSession.IsStructurallyValid()) + { + Result.ConfidenceRollup = Result.ReconstructionSession.ConfidenceRollup; + } + + if (Result.ReconstructionSession.IsStructurallyValid() + && Result.ReconstructionSession.bHasCompleteClassicCubeNet) + { + FHyperTwistPuzzleState ReconstructedState; + FHyperTwistStateSnapshot ReconstructedSnapshot; + if (HyperTwistTrainingSubsystemInternal::TryBuildClassicRecognitionStateSnapshot( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + ReconstructedState, + ReconstructedSnapshot + )) + { + Result.FinalState = ReconstructedState; + Result.FinalSnapshot = ReconstructedSnapshot; + } + else + { + Result.FinalState = FHyperTwistPuzzleState(); + Result.FinalSnapshot = FHyperTwistStateSnapshot(); + Result.NormalizationWarnings.AddUnique(TEXT("recognition-reconstruction-unavailable")); + } + } + else + { + Result.FinalState = FHyperTwistPuzzleState(); + Result.FinalSnapshot = FHyperTwistStateSnapshot(); + Result.NormalizationWarnings.AddUnique( + Result.ReconstructionSession.IsStructurallyValid() + ? TEXT("recognition-reconstruction-incomplete") + : TEXT("recognition-reconstruction-unavailable") + ); + } + } + TArray PendingRecognitionEvents; for (const FString& SeedJson : Result.ReplaySeedEventsJson) { @@ -6433,6 +6988,7 @@ FHyperTwistVisionFinalizeResult UHyperTwistTrainingSubsystem::FinalizeActiveReco ActiveRecognitionSessionState.bSessionOpen = false; ActiveRecognitionSessionState.bHasFinalizeResult = true; ActiveRecognitionSessionState.LastFinalizeResult = Result; + ActiveRecognitionSessionState.ActiveReconstructionSession = FHyperTwistVisionReconstructionSession(); ActiveRecognitionSessionState.ActiveSessionId.Reset(); ActiveRecognitionSessionState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601(); ++ActiveRecognitionSessionState.FinalizedSessionCount; @@ -6480,6 +7036,7 @@ bool UHyperTwistTrainingSubsystem::CloseActiveRecognitionSession(FString& OutErr ActiveRecognitionSessionState.bSessionOpen = false; ActiveRecognitionSessionState.ActiveSessionId.Reset(); + ActiveRecognitionSessionState.ActiveReconstructionSession = FHyperTwistVisionReconstructionSession(); ActiveRecognitionSessionState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601(); ActiveRecognitionSessionState.LastError.Reset(); RefreshRecognitionServiceHealth(); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index ad79120..dc90878 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -173,6 +173,208 @@ struct FHyperTwistVisionFaceObservation } }; +USTRUCT(BlueprintType) +struct FHyperTwistVisionStickerVoteTally +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString Notation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ColorId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 VoteCount = 0; + + bool IsStructurallyValid() const + { + return !Notation.IsEmpty() && !ColorId.IsEmpty() && VoteCount > 0; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistVisionReconstructionStickerVote +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GridIndex = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GridRow = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 GridColumn = -1; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString WinningNotation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString WinningColorId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 WinningVoteCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalVoteCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHadConflict = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Tallies; + + bool IsStructurallyValid() const + { + if (GridIndex < 0 + || GridRow < 0 + || GridColumn < 0 + || WinningNotation.IsEmpty() + || WinningColorId.IsEmpty() + || WinningVoteCount <= 0 + || TotalVoteCount <= 0 + || Tallies.Num() <= 0) + { + return false; + } + + for (const FHyperTwistVisionStickerVoteTally& Tally : Tallies) + { + if (!Tally.IsStructurallyValid()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistVisionCommittedFaceState +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestObservationId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString OrderingProfile = TEXT("3x3-row-major"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ObservationCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RevisionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasConflicts = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray LatestOrderedStickers; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray StickerVotes; + + bool IsStructurallyValid() const + { + if (FaceId.IsEmpty() + || LatestObservationId.IsEmpty() + || LatestOrderedStickers.Num() != 9 + || StickerVotes.Num() != 9 + || ObservationCount <= 0 + || RevisionCount < 0) + { + return false; + } + + for (const FHyperTwistVisionObservedSticker& Sticker : LatestOrderedStickers) + { + if (!Sticker.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistVisionReconstructionStickerVote& Vote : StickerVotes) + { + if (!Vote.IsStructurallyValid()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistVisionReconstructionSession +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString SessionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString PuzzleId = TEXT("cube/3x3x3"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ReconstructionProfile = TEXT("classic-face-vote-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString OrientationProfile = TEXT("classic-target-face-net-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bUsesObservationVoting = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasStableOrientation = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 OrientationStateOrdinal = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalCommittedFaceCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 TotalRevisionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float ConfidenceRollup = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHasCompleteClassicCubeNet = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray CommittedFaces; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray MissingFaces; + + bool IsStructurallyValid() const + { + if (SessionId.IsEmpty() || PuzzleId.IsEmpty() || ReconstructionProfile.IsEmpty() || OrientationProfile.IsEmpty()) + { + return false; + } + + for (const FHyperTwistVisionCommittedFaceState& Face : CommittedFaces) + { + if (!Face.IsStructurallyValid()) + { + return false; + } + } + + return TotalCommittedFaceCount >= 0 && TotalRevisionCount >= 0; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistVisionSessionConfig { @@ -310,6 +512,9 @@ struct FHyperTwistVisionCommitResult UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionFaceObservation ObservedFace; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionReconstructionSession ReconstructionSession; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray Warnings; @@ -331,6 +536,9 @@ struct FHyperTwistVisionFinalizeResult UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistStateSnapshot FinalSnapshot; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionReconstructionSession ReconstructionSession; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray ReplaySeedEventsJson; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 102addd..f7b8536 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1885,6 +1885,9 @@ struct FHyperTwistTrainingRecognitionSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionFinalizeResult LastFinalizeResult; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionReconstructionSession ActiveReconstructionSession; }; USTRUCT(BlueprintType) diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistRubixCubeSolverPhase6RCReconstructionContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistRubixCubeSolverPhase6RCReconstructionContractTest.cpp new file mode 100644 index 0000000..8baf928 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistRubixCubeSolverPhase6RCReconstructionContractTest.cpp @@ -0,0 +1,245 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistCore/HyperTwistCoreLibrary.h" +#include "HyperTwistCore/HyperTwistCoreTypes.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "JsonObjectConverter.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistRubixCubeSolverPhase6RCTestInternal +{ + FHyperTwistTrainingDeck MakeRecognitionDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-c/rubix-cube-solver-recognition"); + Deck.Title = TEXT("Phase 6R-C Rubix Cube Solver Recognition"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::RecognitionAssisted, + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-c-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition; + TrainingCase.PromptLabel = TEXT("Phase 6R-C Recognition"); + TrainingCase.AllowedDeliveryModes = { + EHyperTwistTrainingDeliveryMode::RecognitionAssisted, + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + Deck.Cases = {TrainingCase}; + return Deck; + } + + void ForceMockRecognitionClient(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + if (TrainingSubsystem == nullptr) + { + return; + } + + if (FStrProperty* RecognitionClientKindProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("RecognitionClientKind") + )) + { + RecognitionClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, TEXT("mock")); + } + } + + UHyperTwistTrainingSubsystem* MakeRecognitionSubsystem(const FString& SessionId) + { + UGameInstance* GameInstance = NewObject(GetTransientPackage()); + if (GameInstance == nullptr) + { + return nullptr; + } + + UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject(GameInstance); + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + ForceMockRecognitionClient(TrainingSubsystem); + const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck( + MakeRecognitionDeck(), + TEXT("phase6r-c-user"), + SessionId, + EHyperTwistTrainingDeliveryMode::RecognitionAssisted + ); + return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr; + } + + void SubmitPreviewFrame(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& FaceId, const int32 FrameOrdinal) + { + FHyperTwistVisionFrameEnvelope Frame; + Frame.FrameOrdinal = FrameOrdinal; + Frame.TargetFaceHint = FaceId; + TrainingSubsystem->SubmitActiveRecognitionFrame(Frame); + } + + FHyperTwistVisionCommitResult CommitFace( + UHyperTwistTrainingSubsystem* TrainingSubsystem, + const FString& FaceId, + const int32 FrameOrdinal + ) + { + SubmitPreviewFrame(TrainingSubsystem, FaceId, FrameOrdinal); + + FHyperTwistVisionCommitRequest Request; + Request.TargetFace = FaceId; + return TrainingSubsystem->CommitActiveRecognitionObservation(Request); + } + + const FHyperTwistVisionCommittedFaceState* FindCommittedFace( + const FHyperTwistVisionReconstructionSession& Session, + const FString& FaceId + ) + { + return Session.CommittedFaces.FindByPredicate( + [&FaceId](const FHyperTwistVisionCommittedFaceState& Candidate) + { + return Candidate.FaceId.Equals(FaceId, ESearchCase::IgnoreCase); + } + ); + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistRubixCubeSolverPhase6RCCommitBuildsReconstructionSessionTest, + "HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.CommitBuildsReconstructionSession", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistRubixCubeSolverPhase6RCCommitBuildsReconstructionSessionTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistRubixCubeSolverPhase6RCTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-c-commit-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-C."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + const FHyperTwistVisionCommitResult Result = + HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 1); + + TestTrue(TEXT("The commit result must carry a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid()); + TestEqual(TEXT("The first face commit must keep one committed face."), Result.ReconstructionSession.CommittedFaces.Num(), 1); + TestEqual(TEXT("The first face commit must report five missing faces."), Result.ReconstructionSession.MissingFaces.Num(), 5); + TestEqual(TEXT("The reconstruction session must keep the committed face id."), Result.ReconstructionSession.CommittedFaces[0].FaceId, TEXT("F")); + TestFalse(TEXT("An incomplete reconstruction session must not expose a full snapshot yet."), Result.Snapshot.IsStructurallyValid()); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestTrue(TEXT("The active recognition state must keep the same reconstruction session."), SessionState.ActiveReconstructionSession.IsStructurallyValid()); + TestEqual(TEXT("The active recognition state must track one committed face."), SessionState.ActiveReconstructionSession.CommittedFaces.Num(), 1); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistRubixCubeSolverPhase6RCRevisionLedgerTest, + "HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.RevisionLedger", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistRubixCubeSolverPhase6RCRevisionLedgerTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistRubixCubeSolverPhase6RCTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-c-revision-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-C."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 1); + const FHyperTwistVisionCommitResult RevisionResult = + HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 2); + + const FHyperTwistVisionCommittedFaceState* FaceState = + HyperTwistRubixCubeSolverPhase6RCTestInternal::FindCommittedFace(RevisionResult.ReconstructionSession, TEXT("F")); + TestNotNull(TEXT("The revised reconstruction session must still track the front face."), FaceState); + if (FaceState == nullptr) + { + return false; + } + + TestEqual(TEXT("A second face commit must increment the observation count."), FaceState->ObservationCount, 2); + TestEqual(TEXT("A second face commit must increment the revision count."), FaceState->RevisionCount, 1); + TestEqual(TEXT("The revision ledger must preserve one total revision in the session."), RevisionResult.ReconstructionSession.TotalRevisionCount, 1); + TestEqual(TEXT("The first sticker vote must record two votes after a recommit."), FaceState->StickerVotes[0].WinningVoteCount, 2); + TestFalse(TEXT("A revised but incomplete reconstruction session must still not expose a full snapshot."), RevisionResult.Snapshot.IsStructurallyValid()); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestEqual(TEXT("A recommit must count as one correction event."), SessionState.CorrectionEventCount, 1); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistRubixCubeSolverPhase6RCFinalizeBuildsClassicNetTest, + "HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.FinalizeBuildsClassicNet", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistRubixCubeSolverPhase6RCFinalizeBuildsClassicNetTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistRubixCubeSolverPhase6RCTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-c-finalize-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-C."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + const TArray FaceOrder = { + TEXT("U"), + TEXT("R"), + TEXT("F"), + TEXT("D"), + TEXT("L"), + TEXT("B") + }; + for (int32 FaceOrdinal = 0; FaceOrdinal < FaceOrder.Num(); ++FaceOrdinal) + { + HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace( + TrainingSubsystem, + FaceOrder[FaceOrdinal], + FaceOrdinal + 1 + ); + } + + const FHyperTwistVisionFinalizeResult Result = TrainingSubsystem->FinalizeActiveRecognitionSession(); + + TestTrue(TEXT("The finalized session must carry a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid()); + TestTrue(TEXT("The finalized session must carry a complete classic cube net."), Result.ReconstructionSession.bHasCompleteClassicCubeNet); + TestTrue(TEXT("A complete reconstruction session must emit a structurally valid final state."), Result.FinalState.IsStructurallyValid()); + TestTrue(TEXT("A complete reconstruction session must emit a structurally valid final snapshot."), Result.FinalSnapshot.IsStructurallyValid()); + TestTrue(TEXT("The reconstructed final state should resolve as solved for the mock color net."), UHyperTwistCoreLibrary::IsSolved(Result.FinalState)); + TestTrue(TEXT("The bounded finalize path should not need normalization warnings for a complete mock route."), Result.NormalizationWarnings.IsEmpty()); + + FHyperTwistClassicFaceletSnapshotState FaceletState; + TestTrue( + TEXT("The final facelet payload must deserialize."), + FJsonObjectConverter::JsonObjectStringToUStruct(Result.FinalState.StateEncoding.PayloadJson, &FaceletState, 0, 0) + ); + TestEqual(TEXT("The up face must keep nine stickers."), FaceletState.U.Num(), 9); + TestEqual(TEXT("The right face must keep nine stickers."), FaceletState.R.Num(), 9); + TestEqual(TEXT("The front face must keep the green face token."), FaceletState.F[0], TEXT("G")); + TestEqual(TEXT("The back face must keep the blue face token."), FaceletState.B[0], TEXT("B")); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestFalse(TEXT("The active recognition session must be closed after finalization."), SessionState.bSessionOpen); + TestTrue(TEXT("The last finalize result must keep the complete reconstruction session."), SessionState.LastFinalizeResult.ReconstructionSession.bHasCompleteClassicCubeNet); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index c71543a..8763f4f 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -94,7 +94,7 @@ Status update on `2026-05-20`: - the current canonical repo-row portfolio count is `75` - all `75` current portfolio rows now have explicit root-tracker coverage -- the current implemented-row count is `26` +- the current implemented-row count is `27` - the four later restrictive clean-room rows now implemented after the original `2026-05-13` truth block are: - `cubing/alg.js` @@ -105,6 +105,8 @@ Status update on `2026-05-20`: - `HactarCE/Hyperspeedcube` - the sixth additional implemented row is the same-day bounded permissive partial row: - `kkoomen/qbr` +- the seventh additional implemented row is the same-day bounded permissive partial row: + - `vivaansinghvi07/rubix-cube-solver` - the earlier first-party packet block is now confirmed landed in current code through: - `d4f4ad3` `Add primary coach orchestration entry lane` - `1860f14` `Add provider-backed recognition sidecar client` @@ -114,8 +116,11 @@ Status update on `2026-05-20`: - `HactarCE/Hyperspeedcube` remains partially incorporated - the bounded permissive `Phase 6R-B` `kkoomen/qbr` classic-cube recognition calibration and ordered face-observation packet is now landed in current code -- `kkoomen/qbr` remains partially incorporated; the current next bounded move is a source-backed - `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` preparation/control pass, not a new +- `kkoomen/qbr` remains partially incorporated +- the bounded permissive `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` committed-face + reconstruction and face-vote replacement packet is now landed in current code +- `vivaansinghvi07/rubix-cube-solver` remains partially incorporated; the current next bounded + move is a source-backed `Phase 6R-D` `roice3/MagicTile` preparation/control pass, not a new restrictive packet by default - use the repo-row README census, the portfolio standing refresh backfill, and the `2R-A` ownership contract for the current queue after that correction @@ -123,16 +128,17 @@ Status update on `2026-05-20`: This file now also preserves the current truth that future models must not lose: - current curated HyperTwist shallow-eval set: `75` repos -- currently verified live/implemented in checked `UnrealHyperTwist` surfaces: `26` -- permissive live lanes: `15` +- currently verified live/implemented in checked `UnrealHyperTwist` surfaces: `27` +- permissive live lanes: `16` - boundary-sensitive live lanes: `6` - restrictive live lanes: `5` -The fifteen permissive live lanes are: +The sixteen permissive live lanes are: - `Aarav2709/KubeTimr` - `HactarCE/Hyperspeedcube` - `kkoomen/qbr` +- `vivaansinghvi07/rubix-cube-solver` - `apache/echarts` - `abunickabhi/5style-Trainer` - `google/model-viewer` @@ -215,8 +221,8 @@ Current practical interpretation: - the landed `PostHog/posthog` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_D_POSTHOG_CONTROL_PLANE_IMPLEMENTATION_2026-05-13.md` - the landed `screenpipe/screenpipe` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_E_SCREENPIPE_CAPTURE_HISTORY_IMPLEMENTATION_2026-05-13.md` - the landed `remotion-dev/remotion` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_F_REMOTION_MEDIA_EXPORT_IMPLEMENTATION_2026-05-13.md` -- the next bounded move is a source-backed `Phase 6R-C` - `vivaansinghvi07/rubix-cube-solver` preparation/control pass +- the next bounded move is a source-backed `Phase 6R-D` `roice3/MagicTile` + preparation/control pass Companion docs: @@ -748,9 +754,18 @@ Approved working posture: - HyperTwist may use the checked repo directly under the upstream `MIT` posture - preserve the upstream `MIT` license text and copyright notice: - `Copyright (c) 2023 Vivaan Singhvi` -- keep the row queued as the recognition companion candidate after `kkoomen/qbr` +- preserve the row as the bounded recognition companion lane after `kkoomen/qbr` - if later implementation ships or copies bundled third-party frontend assets, capture and preserve those assets' own upstream license/provenance rather than flattening them into the repo-level `MIT` +- the first bounded retained slice is now landed in current code: + - committed-face reconstruction session + - face-vote replacement ledger + - final classic-net shaping above aggregated committed faces +- keep the row only partially incorporated by default: + - browser/webcam shell remains deferred + - broad solve explanation or recommendation shell remains deferred + - any redistribution of `frontend/lib/twistysim.min.js` still requires preserved or replaced + upstream provenance/license ### `tentone/rubix-solver` @@ -1258,10 +1273,10 @@ Approved working posture: permission to transplant the whole Rust application shell - do not let this row absorb non-Euclidean topology ownership that remains queued for `roice3/MagicTile` -- after the landed puzzle-catalog slice and the landed bounded `qbr` calibration slice, the next - queue head should move to a source-backed `Phase 6R-C` - `vivaansinghvi07/rubix-cube-solver` preparation/control pass rather than pretending the entire - retained recognition family is already closed +- after the landed puzzle-catalog slice, the landed bounded `qbr` calibration slice, and the + landed bounded `rubix-cube-solver` reconstruction slice, the next queue head should move to a + source-backed `Phase 6R-D` `roice3/MagicTile` preparation/control pass rather than pretending + the entire retained topology family is already closed ### `coqui-ai/TTS` diff --git a/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_IMPLEMENTATION_PACKET_2026-05-20.md b/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_IMPLEMENTATION_PACKET_2026-05-20.md new file mode 100644 index 0000000..a0ea26f --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_IMPLEMENTATION_PACKET_2026-05-20.md @@ -0,0 +1,134 @@ +# HyperTwist Phase 6R-C rubix-cube-solver reconstruction implementation packet + +Created on `2026-05-20` + +## Status + +- first-party HyperTwist packet +- bounded permissive `Phase 6R-C` implementation slice + +## Purpose + +This packet lands the first narrower bounded slice from the retained +`vivaansinghvi07/rubix-cube-solver` recognition-companion row. + +The landed slice is: + +- first-party classic-cube committed-face reconstruction session plus face-vote replacement ledger + +It is not: + +- a full `rubix-cube-solver` row transplant +- a browser/webcam shell packet +- a broad solve-explanation packet +- a bundled `twistysim.min.js` redistribution packet +- a `MagicTile` topology packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/HYPERTWIST_PHASE_0R_PACKET_0R_A_EVALUATION_2026-05-12.md` +- `docs/HYPERTWIST_PHASE_2R_PACKET_2R_A_OWNERSHIP_AND_ACCEPTANCE_CONTRACT_2026-05-13.md` +- `docs/REPO_LICENSE_TRACKING.md` +- `docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_PREPARATION_PACKET_2026-05-20.md` + +The retained owner remains: + +- `vivaansinghvi07/rubix-cube-solver` + +The granted family remains bounded to: + +- committed-face reconstruction flow +- face-vote replacement and correction ledger +- bounded final net shaping above committed faces + +This packet lands only the first narrower family in that granted set. + +## Landed scope + +The current code now owns a retained classic-cube reconstruction contract through: + +- retained recognition contract types for: + - sticker vote tallies + - reconstruction sticker votes + - committed face state + - reconstruction session +- recognition session state tracking for the active reconstruction session +- classic-cube reconstruction-session aggregation and face-vote replacement in + `UHyperTwistTrainingSubsystem` +- classic-cube missing-face accounting and confidence rollup derived from the bounded + reconstruction session +- final classic facelet snapshot and net shaping when the six-face reconstruction becomes complete +- sample reconstruction-session routing in `UHyperTwistContractLibrary` +- ordered classic-face preview and commit payload correction in `UHyperTwistMockVisionClient` +- focused automation coverage in: + - `HyperTwistRubixCubeSolverPhase6RCReconstructionContractTest.cpp` + +## Why this is still intentionally bounded + +This packet replaces the thinner commit/finalize reconstruction heuristics with a reconstruction +session owned by first-party code, but it does not widen into the neighboring retained families. + +Still deferred: + +- browser/webcam shell +- broad solve explanation or recommendation shell +- bundled `twistysim.min.js` redistribution +- generalized solver backend ownership +- `MagicTile` or broader topology widening + +## Validation + +Build validation: + +- `Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE` + +Focused automation validation: + +- `Automation RunTests HyperTwist.Permissive.RubixCubeSolver.Phase6R.C` + +Regression automation validation: + +- `Automation RunTests HyperTwist.Permissive.Qbr.Phase6R.B` +- `Automation RunTests HyperTwist.Permissive.Hyperspeedcube.Phase6R.A` +- `Automation RunTests HyperTwist.CleanRoom.CubeDesk` + +Expected covered tests: + +- `CommitBuildsReconstructionSession` +- `RevisionLedger` +- `FinalizeBuildsClassicNet` +- `CalibrationSessionConfig` +- `PreviewObservation` +- `CommitObservation` +- `CatalogLookup` +- `SampleDefinition` +- `TrainingRunDefinition` +- existing `CubeDesk` clean-room regression suite + +## Queue effect + +This packet consumes the current `Phase 6R-C` implementation slice. + +`vivaansinghvi07/rubix-cube-solver` remains only partially incorporated: + +- landed now: + - committed-face reconstruction session + - face-vote replacement ledger + - final classic-net shaping above aggregated committed faces +- still deferred: + - browser/webcam shell + - broad solve explanation or recommendation shell + - bundled `twistysim.min.js` redistribution + +The next clean move is not another immediate `rubix-cube-solver` widening by default. + +The next queue head should be: + +- a source-backed `Phase 6R-D` `roice3/MagicTile` preparation/control pass + +Keep the future legal sequencing guard visible when this row widens again: + +- do not copy or redistribute `frontend/lib/twistysim.min.js` without preserving or replacing its + upstream provenance/license diff --git a/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_PREPARATION_PACKET_2026-05-20.md b/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_PREPARATION_PACKET_2026-05-20.md new file mode 100644 index 0000000..f28a7ec --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_PREPARATION_PACKET_2026-05-20.md @@ -0,0 +1,184 @@ +# HyperTwist Phase 6R-C rubix-cube-solver reconstruction preparation packet + +Created on `2026-05-20` + +## Status + +- historical same-day preparation authority +- bounded post-Phase-`6R-B` preparation slice +- the first bounded implementation slice now lands separately under: + - `docs/arch/HYPERTWIST_PHASE6R_C_RUBIX_CUBE_SOLVER_RECONSTRUCTION_IMPLEMENTATION_PACKET_2026-05-20.md` + +## Purpose + +This packet freezes the next widening order after the landed `Phase 6R-B` +`kkoomen/qbr` classic-cube recognition calibration and ordered face-observation packet. + +The open task is: + +- define the first bounded source-backed widening packet for + `vivaansinghvi07/rubix-cube-solver` as the retained `Phase 6R-C` reconstruction companion lane + +It is not: + +- a full `rubix-cube-solver` row transplant +- a browser/webcam shell packet +- a broad solve-explanation packet +- a direct redistribution packet for the bundled `frontend/lib/twistysim.min.js` asset +- a `MagicTile` topology packet +- a general recognition-provider widening packet + +## Current authority basis + +This preparation packet stands on already-closed authority: + +- `docs/HYPERTWIST_PHASE_0R_PACKET_0R_A_EVALUATION_2026-05-12.md` +- `docs/HYPERTWIST_PHASE_2R_PACKET_2R_A_OWNERSHIP_AND_ACCEPTANCE_CONTRACT_2026-05-13.md` +- `docs/REPO_LICENSE_TRACKING.md` +- `docs/arch/HYPERTWIST_PHASE6R_B_QBR_CALIBRATED_FACE_OBSERVATION_IMPLEMENTATION_PACKET_2026-05-20.md` + +The key accepted routing facts are: + +- `vivaansinghvi07/rubix-cube-solver` is the retained recognition companion donor immediately after + the landed `6R-B` slice +- earliest widening route is `Phase 6R / Packet 6R-C` +- the retained donor value there is: + - committed-face reconstruction flow + - face-vote replacement and correction ledger + - bounded final net shaping above committed faces +- ownership denied there is: + - do not let the repo absorb the full browser shell + - do not widen into broad solve explanation or generic solver UI transport + - do not copy or redistribute the bundled `frontend/lib/twistysim.min.js` asset without + preserving or replacing its upstream provenance/license + - do not let the row absorb the queued `MagicTile` topology lane + +## Why this is now the next packet + +The earlier `Phase 6R-B` queue head is already landed: + +- retained classic-cube recognition calibration contract +- ordered face-observation contract + +That means the live queue advances to: + +- `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` + +with the adjacent retained rows still ordered behind it: + +- `Phase 6R-D` `roice3/MagicTile` +- the bounded speech-input / voice sidecar set + +## Required result + +The source-backed control pass for this packet is now complete. + +The first actual `6R-C` implementation packet should: + +- use the retained `0R-A` and `2R-A` authority surfaces plus the inspected + `rubix-cube-solver` source basis +- define one bounded retained slice from `rubix-cube-solver` +- keep the slice inside the accepted recognition-companion ownership +- widen only the first reconstruction family that can stand on its own without dragging in the full + browser shell, bundled frontend asset redistribution, or the next queued topology row +- explicitly state which neighboring retained rows stay closed in that packet + +## Source-backed retained basis + +The queue-head decision is now source-backed rather than README-only. + +Inspected retained donor basis: + +- `README.md` +- `backend/cv.py` +- `backend/server.py` +- `frontend/src/script.js` + +Inspected first-party receiving basis: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockVisionClient.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp` + +## Narrowed 6R-C slice decision + +The first widening slice is now fixed as: + +1. classic-cube committed-face reconstruction session plus face-vote replacement ledger + +not as the broader browser or explanation shell. + +That narrowed slice should cover: + +- retained reconstruction-session contract types +- committed-face state and sticker-vote aggregation +- bounded correction-friendly replacement ledger for recommitted faces +- classic-cube missing-face accounting above committed observations +- final facelet snapshot and classic-net shaping when the six-face reconstruction becomes complete +- subsystem and contract-library routing for that bounded reconstruction session +- focused automation coverage for: + - reconstruction-session accumulation + - revision ledger behavior + - final classic-net shaping + +## Deferred neighboring families + +The first `6R-C` implementation packet must keep these capability families closed: + +- browser/webcam shell +- broad solve explanation +- bundled `twistysim.min.js` redistribution +- generalized solver transport or remote-service ownership +- `MagicTile` or broader topology widening +- speech-input / voice sidecar ownership + +Why this slice is first: + +- the donor exposes its strongest narrow retained seam at committed-face reconstruction and + correction-friendly replacement, not at the full browser shell +- current first-party recognition code already had session, preview, and commit envelopes plus the + landed `qbr` ordered face-observation contract, but it still lacked an owned reconstruction + session that could accumulate corrected committed faces into a final classic-cube net +- broad explanation or frontend widening would prematurely absorb neighboring rows and bundled + asset obligations + +## Out of scope for the first 6R-C packet + +- full browser/webcam UI transplant +- broad solve explanation or recommendation shell +- bundled `frontend/lib/twistysim.min.js` redistribution +- generalized solver backend ownership +- `MagicTile` or broader topology widening + +## Queue-adjacent legal sequencing guards + +Do not omit the already-recorded legal caveats when the neighboring retained rows later come up: + +- `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` + - preserve or replace the bundled `frontend/lib/twistysim.min.js` with its upstream + provenance/license intact before redistribution if any packet copies or ships that dependency +- `Phase 6R-D` `roice3/MagicTile` + - keep non-Euclidean topology ownership bounded to that later packet rather than letting the + current recognition companion lane absorb it by convenience + +## Acceptance criteria + +- the packet records why the live queue now advances from landed `6R-B` to `6R-C` +- the packet promotes `rubix-cube-solver` as the current queue head without widening the + neighboring retained rows prematurely +- the packet records that the first `6R-C` widening slice is committed-face reconstruction session + plus face-vote replacement ledger only +- the packet states exact out-of-scope families for the first `6R-C` pass +- the packet keeps the `twistysim.min.js` caveat visible in later queue sequencing + +## Validation checklist + +1. confirm the landed `Phase 6R-B` packet is now the consumed prior queue head +2. confirm the retained reconstruction seam in `rubix-cube-solver` narrows cleanly to committed + face aggregation and replacement ledger behavior +3. confirm the next packet is framed as bounded source-backed widening rather than README-only + routing + +That is the packet. diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 573cca5..3ebbd92 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -3,7 +3,7 @@ Status update on `2026-05-20`: - the canonical HyperTwist repo-row portfolio is now treated as `75` rows, not `71` -- currently implemented rows are now `26`, not `20` +- currently implemented rows are now `27`, not `20` - four of the additional current implemented rows are the later-landed restrictive clean-room lanes: - `cubing/alg.js` - `cubing/twisty.js` @@ -13,6 +13,8 @@ Status update on `2026-05-20`: - `HactarCE/Hyperspeedcube` - the sixth additional current implemented row is the same-day bounded permissive partial row: - `kkoomen/qbr` +- the seventh additional current implemented row is the same-day bounded permissive partial row: + - `vivaansinghvi07/rubix-cube-solver` - the current next bounded move is **not** another restrictive packet by default - the earlier first-party packet block that was still being treated as next is now confirmed landed: - `d4f4ad3` `Add primary coach orchestration entry lane` @@ -29,8 +31,14 @@ Status update on `2026-05-20`: - `kkoomen/qbr` remains partially incorporated; webcam UI shell, bundled-font redistribution, multilingual solve shell, and multi-face reconstruction/correction/explanation families stay deferred -- the current next bounded move is a source-backed `Phase 6R-C` - `vivaansinghvi07/rubix-cube-solver` preparation/control pass +- the generic source-backed `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` control pass is now + consumed +- the bounded permissive `Phase 6R-C` `vivaansinghvi07/rubix-cube-solver` committed-face + reconstruction and face-vote replacement packet is now landed in current code +- `vivaansinghvi07/rubix-cube-solver` remains partially incorporated; browser/webcam shell, broad + solve explanation, and bundled `twistysim.min.js` redistribution stay deferred +- the current next bounded move is a source-backed `Phase 6R-D` `roice3/MagicTile` + preparation/control pass - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -58,8 +66,8 @@ Donor-strength rule: - route difficulty changes how retained value may enter the product, not whether it may win technically - current shallow-eval set: `75` repos -- currently verified live/implemented in checked Unreal surfaces: `26` -- permissive live lanes: `15` +- currently verified live/implemented in checked Unreal surfaces: `27` +- permissive live lanes: `16` - boundary-sensitive live lanes: `6` - restrictive live lanes: `5` - the restrictive landed lanes are: @@ -69,7 +77,7 @@ Donor-strength rule: - `HactarCE/2x2x2x2-Scrambler` - `kash/cubedesk` - each restrictive landed lane is to be treated as properly clean-roomed and implemented afterward -- `Phase 0R` is now closed for the remaining `49` non-live rows +- `Phase 0R` is now closed for the remaining `48` non-live rows - `Phase 1R` is now closed as the retained-set contract and handoff overhaul - `Phase 2R` is now closed as the retained-set ownership and acceptance packet sequence - `Phase 3R-A` is now closed as the landed `Aarav2709/KubeTimr` timer subsystem widening packet @@ -89,17 +97,16 @@ Current routing truth: - retained rows total: `68` - discarded from the active retained set: `3` -- active non-live implementation-board rows: `37` +- active non-live implementation-board rows: `36` - retained benchmark, oracle, or clean-room-later rows outside the active implementation board: `9` -The next bounded move is a source-backed `Phase 6R-C` -`vivaansinghvi07/rubix-cube-solver` preparation/control pass. +The next bounded move is a source-backed `Phase 6R-D` `roice3/MagicTile` +preparation/control pass. Queue interpretation after that packet: - then continue with the retained repo-row queue - highest current repo-row queue pressure sits in: - - `vivaansinghvi07/rubix-cube-solver` - `roice3/MagicTile` - the bounded speech-input / voice sidecar set - `HactarCE/Hyperspeedcube` remains a partially landed row rather than a closed row: @@ -118,13 +125,19 @@ Queue interpretation after that packet: - webcam UI shell - bundled-font redistribution and multilingual solve shell - multi-face reconstruction / correction / explanation -- after the `Phase 6R-B` implementation packet, keep the adjacent legal sequencing guards visible: - - `kkoomen/qbr` - - do not copy or redistribute `src/assets/arial-unicode-ms.ttf` without separate - confirmation or replacement in any future widening that would ship that asset +- `vivaansinghvi07/rubix-cube-solver` remains a partially landed row rather than a closed row: + - landed: + - committed-face reconstruction session + - face-vote replacement ledger + - final classic-net shaping above aggregated committed faces + - still deferred: + - browser/webcam shell + - broad solve explanation or recommendation shell + - bundled `twistysim.min.js` redistribution +- after the `Phase 6R-C` implementation packet, keep the future legal sequencing guard visible: - `vivaansinghvi07/rubix-cube-solver` - do not copy or redistribute `frontend/lib/twistysim.min.js` without preserving or replacing - its upstream provenance/license + its upstream provenance/license in any future widening that would ship that asset - keep the optional `Phase 3R-G` browser comparison lane deferred unless the landed primary browser spatial stack exposes a real gap