diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index ef0fbe7..921b5df 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -559,6 +559,22 @@ namespace HyperTwistContractLibraryInternal return Profile; } + bool HasAppliedResolutionForFace( + const TArray& ResolutionLedger, + const FHyperTwistVisionCommittedFaceState& FaceState + ) + { + const FString NormalizedFaceId = NormalizeBrowserFaceHint(FaceState.FaceId); + return ResolutionLedger.ContainsByPredicate( + [&NormalizedFaceId, &FaceState](const FHyperTwistVisionCorrectionResolution& Resolution) + { + return NormalizeBrowserFaceHint(Resolution.FaceId) == NormalizedFaceId + && Resolution.ObservationId == FaceState.LatestObservationId + && Resolution.RevisionCount == FaceState.RevisionCount; + } + ); + } + void ResolveMockClassicFaceSwatch( const FString& FaceId, FString& OutColorId, @@ -829,7 +845,8 @@ namespace HyperTwistContractLibraryInternal const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, const FHyperTwistVisionFaceObservation& ObservedFace, - const TArray& Warnings + const TArray& Warnings, + const TArray& ResolutionLedger ) { static constexpr float LowConfidenceThreshold = 0.85f; @@ -848,6 +865,12 @@ namespace HyperTwistContractLibraryInternal CorrectionState.MissingFaceCount = ReconstructionSession.IsStructurallyValid() ? ReconstructionSession.MissingFaces.Num() : 6; + CorrectionState.ResolutionLedger = ResolutionLedger; + CorrectionState.ResolvedCorrectionCount = ResolutionLedger.Num(); + if (ResolutionLedger.Num() > 0) + { + CorrectionState.LastResolution = ResolutionLedger.Last(); + } CorrectionState.bManualCubeEditVisible = CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet; CorrectionState.bStageRestartVisible = @@ -861,6 +884,10 @@ namespace HyperTwistContractLibraryInternal { const FString FaceId = NormalizeBrowserFaceHint(FaceState.FaceId); const bool bHasCompleteNet = ReconstructionSession.bHasCompleteClassicCubeNet; + if (HasAppliedResolutionForFace(ResolutionLedger, FaceState)) + { + continue; + } if (FaceState.bHasConflicts) { @@ -1494,7 +1521,8 @@ FHyperTwistVisionCommitResult UHyperTwistContractLibrary::MakeMockVisionCommitRe SessionConfig, Result.ReconstructionSession, Result.ObservedFace, - Result.Warnings + Result.Warnings, + TArray() ); Result.SolveExplanationState = HyperTwistContractLibraryInternal::MakeClassicCubeSolveExplanationState( SessionConfig, diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 89f337b..7ac5df7 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2669,6 +2669,140 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + bool IsCorrectionResolutionCommitKind(const FString& CommitKind) + { + return CommitKind.Equals(TEXT("correction-resolution"), ESearchCase::IgnoreCase) + || CommitKind.Equals(TEXT("resolve-correction"), ESearchCase::IgnoreCase); + } + + const FHyperTwistVisionCorrectionTarget* FindCorrectionTarget( + const FHyperTwistVisionCorrectionState& CorrectionState, + const FString& FaceId + ) + { + const FString NormalizedFaceId = NormalizeRecognitionFaceId(FaceId); + return CorrectionState.PendingTargets.FindByPredicate( + [&NormalizedFaceId](const FHyperTwistVisionCorrectionTarget& Candidate) + { + return NormalizeRecognitionFaceId(Candidate.FaceId) == NormalizedFaceId; + } + ); + } + + FHyperTwistVisionFaceObservation BuildObservationFromCommittedFaceState( + const FHyperTwistVisionCommittedFaceState& FaceState + ) + { + FHyperTwistVisionFaceObservation Observation; + Observation.ObservationId = FaceState.LatestObservationId; + Observation.FaceId = NormalizeRecognitionFaceId(FaceState.FaceId); + Observation.OrderingProfile = FaceState.OrderingProfile; + Observation.CandidateContourCount = FaceState.LatestOrderedStickers.Num(); + Observation.AcceptedContourCount = FaceState.LatestOrderedStickers.Num(); + Observation.bFoundStableThreeByThreeGrid = FaceState.LatestOrderedStickers.Num() == 9; + Observation.bOrderingStable = FaceState.LatestOrderedStickers.Num() == 9; + Observation.bCommitReady = true; + Observation.OrderedStickers = FaceState.LatestOrderedStickers; + return Observation; + } + + bool HasAppliedResolutionForFace( + const TArray& ResolutionLedger, + const FHyperTwistVisionCommittedFaceState& FaceState + ) + { + const FString NormalizedFaceId = NormalizeRecognitionFaceId(FaceState.FaceId); + return ResolutionLedger.ContainsByPredicate( + [&NormalizedFaceId, &FaceState](const FHyperTwistVisionCorrectionResolution& Resolution) + { + return NormalizeRecognitionFaceId(Resolution.FaceId) == NormalizedFaceId + && Resolution.ObservationId == FaceState.LatestObservationId + && Resolution.RevisionCount == FaceState.RevisionCount; + } + ); + } + + void UpsertCorrectionResolution( + TArray& ResolutionLedger, + const FHyperTwistVisionCorrectionResolution& Candidate, + bool& bAddedNewResolution + ) + { + bAddedNewResolution = false; + + const int32 ExistingIndex = ResolutionLedger.IndexOfByPredicate( + [&Candidate](const FHyperTwistVisionCorrectionResolution& Existing) + { + return NormalizeRecognitionFaceId(Existing.FaceId) + == NormalizeRecognitionFaceId(Candidate.FaceId) + && Existing.ObservationId == Candidate.ObservationId + && Existing.RevisionCount == Candidate.RevisionCount; + } + ); + if (ExistingIndex == INDEX_NONE) + { + ResolutionLedger.Add(Candidate); + bAddedNewResolution = true; + return; + } + + ResolutionLedger[ExistingIndex] = Candidate; + } + + FHyperTwistVisionCorrectionResolution BuildCorrectionResolution( + const FHyperTwistVisionCommitRequest& Request, + const FHyperTwistVisionCorrectionState& CorrectionState, + const FHyperTwistVisionCommittedFaceState& FaceState + ) + { + const FString FaceId = NormalizeRecognitionFaceId(FaceState.FaceId); + const FHyperTwistVisionCorrectionTarget* Target = FindCorrectionTarget(CorrectionState, FaceId); + + FHyperTwistVisionCorrectionResolution Resolution; + Resolution.FaceId = FaceId; + Resolution.ObservationId = FaceState.LatestObservationId; + Resolution.RevisionCount = FaceState.RevisionCount; + Resolution.RequestedActionId = !Request.RequestedActionId.IsEmpty() + ? Request.RequestedActionId + : (Target != nullptr && !Target->RecommendedActionId.IsEmpty() + ? Target->RecommendedActionId + : TEXT("apply-manual-corrections")); + Resolution.ResolutionKind = !Request.ResolutionKind.IsEmpty() + ? Request.ResolutionKind + : (FaceState.RevisionCount > 0 + ? TEXT("manual-review-accepted") + : TEXT("correction-review-accepted")); + Resolution.ResolutionId = FString::Printf( + TEXT("resolution-%s-%d-%s"), + *FaceId, + FaceState.RevisionCount, + *Resolution.ResolutionKind + ); + + for (const FHyperTwistVisionCorrectionContradiction& Contradiction : CorrectionState.Contradictions) + { + if (NormalizeRecognitionFaceId(Contradiction.FaceId) == FaceId) + { + Resolution.ResolvedContradictionIds.Add(Contradiction.ContradictionId); + } + } + + Resolution.StatusLine = FString::Printf( + TEXT("Accepted face %s after manual correction review."), + *FaceId + ); + Resolution.DetailLine = Resolution.ResolvedContradictionIds.Num() > 0 + ? FString::Printf( + TEXT("The current observation on face %s is now treated as reviewed in the bounded correction shell."), + *FaceId + ) + : FString::Printf( + TEXT("The current bounded reading on face %s is accepted after manual review."), + *FaceId + ); + return Resolution; + } + FHyperTwistVisionShellState BuildClassicCubeWebcamShellState( const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, @@ -2780,7 +2914,8 @@ namespace HyperTwistTrainingSubsystemInternal const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, const FHyperTwistVisionFaceObservation& ObservedFace, - const TArray& Warnings + const TArray& Warnings, + const TArray& ResolutionLedger ) { static constexpr float LowConfidenceThreshold = 0.85f; @@ -2799,6 +2934,12 @@ namespace HyperTwistTrainingSubsystemInternal CorrectionState.MissingFaceCount = ReconstructionSession.IsStructurallyValid() ? ReconstructionSession.MissingFaces.Num() : 6; + CorrectionState.ResolutionLedger = ResolutionLedger; + CorrectionState.ResolvedCorrectionCount = ResolutionLedger.Num(); + if (ResolutionLedger.Num() > 0) + { + CorrectionState.LastResolution = ResolutionLedger.Last(); + } CorrectionState.bManualCubeEditVisible = CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet; CorrectionState.bStageRestartVisible = @@ -2812,6 +2953,10 @@ namespace HyperTwistTrainingSubsystemInternal { const FString FaceId = NormalizeRecognitionFaceId(FaceState.FaceId); const bool bHasCompleteNet = ReconstructionSession.bHasCompleteClassicCubeNet; + if (HasAppliedResolutionForFace(ResolutionLedger, FaceState)) + { + continue; + } if (FaceState.bHasConflicts) { @@ -4202,13 +4347,14 @@ FHyperTwistTrainingCoachPanelState UHyperTwistTrainingSubsystem::GetActiveCoachP ? TEXT("not-ready") : *ActiveRecognitionSessionState.ServiceHealth.LastError); PanelState.RecognitionStatusLine = FString::Printf( - TEXT("Recognition: %s | Service: %s | Session: %s | Frames %d | Commits %d | Corrections %d | Finalizations %d"), + TEXT("Recognition: %s | Service: %s | Session: %s | Frames %d | Commits %d | Corrections %d | Resolved %d | Finalizations %d"), *RecognitionProviderLabel, *RecognitionTransportState, ActiveRecognitionSessionState.bSessionOpen ? TEXT("open") : TEXT("closed"), ActiveRecognitionSessionState.SubmittedFrameCount, ActiveRecognitionSessionState.CommittedObservationCount, ActiveRecognitionSessionState.CorrectionEventCount, + ActiveRecognitionSessionState.ResolvedCorrectionCount, ActiveRecognitionSessionState.FinalizedSessionCount ); const bool bCoachPrefersRecognitionReview = @@ -7476,6 +7622,214 @@ FHyperTwistVisionCommitResult UHyperTwistTrainingSubsystem::CommitActiveRecognit NormalizedRequest.CommitKind = TEXT("face"); } + const bool bIsQbrClassicRoute = + HyperTwistTrainingSubsystemInternal::IsQbrClassicCubeRoute(ActiveRecognitionSessionState.SessionConfig); + if (bIsQbrClassicRoute + && HyperTwistTrainingSubsystemInternal::IsCorrectionResolutionCommitKind(NormalizedRequest.CommitKind)) + { + Result.SessionId = NormalizedRequest.SessionId; + Result.CommitKind = NormalizedRequest.CommitKind; + Result.Snapshot.State.SourceSessionId = NormalizedRequest.SessionId; + Result.ReconstructionSession = ActiveRecognitionSessionState.ActiveReconstructionSession; + Result.MissingUnits = Result.ReconstructionSession.MissingFaces; + Result.Confidence = Result.ReconstructionSession.ConfidenceRollup; + + const FHyperTwistVisionFaceObservation ReferenceObservation = + ActiveRecognitionSessionState.LastCommitResult.ObservedFace.IsStructurallyValid() + ? ActiveRecognitionSessionState.LastCommitResult.ObservedFace + : ActiveRecognitionSessionState.LastPreviewResult.ObservedFace; + Result.CorrectionState = + HyperTwistTrainingSubsystemInternal::BuildClassicCubeCorrectionState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + ReferenceObservation, + Result.Warnings, + ActiveRecognitionSessionState.CorrectionResolutionLedger + ); + + const FString RequestedFaceId = !NormalizedRequest.TargetFace.IsEmpty() + ? HyperTwistTrainingSubsystemInternal::NormalizeRecognitionFaceId(NormalizedRequest.TargetFace) + : Result.CorrectionState.ActiveTargetFaceId; + const FHyperTwistVisionCorrectionTarget* RequestedTarget = + HyperTwistTrainingSubsystemInternal::FindCorrectionTarget(Result.CorrectionState, RequestedFaceId); + const FHyperTwistVisionCommittedFaceState* RequestedFaceState = + HyperTwistTrainingSubsystemInternal::FindCommittedFaceState(Result.ReconstructionSession, RequestedFaceId); + bool bAppliedResolution = false; + bool bAddedNewResolution = false; + + if (!Result.ReconstructionSession.IsStructurallyValid()) + { + Result.Warnings.Add(TEXT("correction-resolution-unavailable")); + } + else if (!Result.ReconstructionSession.bHasCompleteClassicCubeNet) + { + Result.Warnings.Add(TEXT("correction-resolution-requires-complete-net")); + } + else if (RequestedFaceId.IsEmpty()) + { + Result.Warnings.Add(TEXT("correction-target-face-required")); + } + else if (RequestedTarget == nullptr) + { + Result.Warnings.Add(TEXT("correction-target-unavailable")); + } + else if (RequestedTarget->RecommendedActionId != TEXT("apply-manual-corrections")) + { + Result.Warnings.Add(TEXT("correction-target-does-not-support-manual-resolution")); + } + else if (RequestedFaceState == nullptr) + { + Result.Warnings.Add(TEXT("correction-face-state-unavailable")); + } + else + { + Result.CommittedUnit = RequestedFaceId; + Result.ObservedFace = + HyperTwistTrainingSubsystemInternal::BuildObservationFromCommittedFaceState(*RequestedFaceState); + + FHyperTwistVisionCorrectionResolution AppliedResolution = + HyperTwistTrainingSubsystemInternal::BuildCorrectionResolution( + NormalizedRequest, + Result.CorrectionState, + *RequestedFaceState + ); + HyperTwistTrainingSubsystemInternal::UpsertCorrectionResolution( + ActiveRecognitionSessionState.CorrectionResolutionLedger, + AppliedResolution, + bAddedNewResolution + ); + ActiveRecognitionSessionState.LastCorrectionResolution = AppliedResolution; + if (bAddedNewResolution) + { + ++ActiveRecognitionSessionState.ResolvedCorrectionCount; + } + + FHyperTwistPuzzleState ReconstructedState; + FHyperTwistStateSnapshot ReconstructedSnapshot; + if (HyperTwistTrainingSubsystemInternal::TryBuildClassicRecognitionStateSnapshot( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + ReconstructedState, + ReconstructedSnapshot + )) + { + Result.Snapshot = ReconstructedSnapshot; + } + else + { + Result.Snapshot = FHyperTwistStateSnapshot(); + Result.Warnings.AddUnique(TEXT("recognition-reconstruction-unavailable")); + } + + Result.ShellState = HyperTwistTrainingSubsystemInternal::BuildClassicCubeWebcamShellState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + RequestedFaceId, + Result.CommittedUnit + ); + Result.BrowserShellState = HyperTwistTrainingSubsystemInternal::BuildClassicCubeBrowserShellState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.ObservedFace + ); + Result.CorrectionState = + HyperTwistTrainingSubsystemInternal::BuildClassicCubeCorrectionState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.ObservedFace, + Result.Warnings, + ActiveRecognitionSessionState.CorrectionResolutionLedger + ); + AppliedResolution.bClearsSolveExplanationBlock = !Result.CorrectionState.bSolveExplanationBlocked; + ActiveRecognitionSessionState.LastCorrectionResolution = AppliedResolution; + HyperTwistTrainingSubsystemInternal::UpsertCorrectionResolution( + ActiveRecognitionSessionState.CorrectionResolutionLedger, + AppliedResolution, + bAddedNewResolution + ); + Result.AppliedCorrectionResolution = AppliedResolution; + Result.CorrectionState.LastResolution = AppliedResolution; + Result.SolveExplanationState = + HyperTwistTrainingSubsystemInternal::BuildClassicCubeSolveExplanationState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.CorrectionState + ); + bAppliedResolution = true; + } + + if (!Result.ObservedFace.IsStructurallyValid() && RequestedFaceState != nullptr) + { + Result.ObservedFace = + HyperTwistTrainingSubsystemInternal::BuildObservationFromCommittedFaceState(*RequestedFaceState); + } + if (!Result.ShellState.IsStructurallyValid()) + { + Result.ShellState = HyperTwistTrainingSubsystemInternal::BuildClassicCubeWebcamShellState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + RequestedFaceId, + Result.CommittedUnit + ); + } + if (!Result.BrowserShellState.IsStructurallyValid()) + { + Result.BrowserShellState = HyperTwistTrainingSubsystemInternal::BuildClassicCubeBrowserShellState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.ObservedFace + ); + } + if (!Result.CorrectionState.IsStructurallyValid()) + { + Result.CorrectionState = + HyperTwistTrainingSubsystemInternal::BuildClassicCubeCorrectionState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.ObservedFace, + Result.Warnings, + ActiveRecognitionSessionState.CorrectionResolutionLedger + ); + } + if (!Result.SolveExplanationState.IsStructurallyValid()) + { + Result.SolveExplanationState = + HyperTwistTrainingSubsystemInternal::BuildClassicCubeSolveExplanationState( + ActiveRecognitionSessionState.SessionConfig, + Result.ReconstructionSession, + Result.CorrectionState + ); + } + + if (bAppliedResolution) + { + FHyperTwistReplayRecognitionPayload CorrectionPayload; + CorrectionPayload.Snapshot = Result.Snapshot; + CorrectionPayload.Confidence = Result.Confidence; + CorrectionPayload.CommittedFaceOrStage = !RequestedFaceId.IsEmpty() + ? RequestedFaceId + : NormalizedRequest.CommitKind; + AppendRecognitionReplayEvent( + EHyperTwistReplayEventType::RecognitionCorrection, + CorrectionPayload, + INDEX_NONE + ); + ++ActiveRecognitionSessionState.CorrectionEventCount; + } + + ActiveRecognitionSessionState.bHasCommitResult = true; + ActiveRecognitionSessionState.LastCommitResult = Result; + ActiveRecognitionSessionState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601(); + ActiveRecognitionSessionState.LastError = + Result.Warnings.Num() > 0 ? FString::Join(Result.Warnings, TEXT(" | ")) : FString(); + ActiveRecognitionSessionState.ServiceHealth.LastError = ActiveRecognitionSessionState.LastError; + if (bAppliedResolution) + { + SynchronizeRecognitionReplayMutation(); + } + return Result; + } + Result = VisionClient->CommitVisionObservation(NormalizedRequest); if (Result.SessionId.IsEmpty()) { @@ -7560,7 +7914,8 @@ FHyperTwistVisionCommitResult UHyperTwistTrainingSubsystem::CommitActiveRecognit ActiveRecognitionSessionState.SessionConfig, Result.ReconstructionSession, Result.ObservedFace, - Result.Warnings + Result.Warnings, + ActiveRecognitionSessionState.CorrectionResolutionLedger ); Result.SolveExplanationState = HyperTwistTrainingSubsystemInternal::BuildClassicCubeSolveExplanationState( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index 91e899f..d472f7e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -886,6 +886,51 @@ struct FHyperTwistVisionCorrectionContradiction } }; +USTRUCT(BlueprintType) +struct FHyperTwistVisionCorrectionResolution +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ResolutionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString FaceId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ResolutionKind; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestedActionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ObservationId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RevisionCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ResolvedContradictionIds; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bClearsSolveExplanationBlock = false; + + bool IsStructurallyValid() const + { + return !ResolutionId.IsEmpty() + && !FaceId.IsEmpty() + && !ResolutionKind.IsEmpty() + && !RequestedActionId.IsEmpty() + && RevisionCount >= 0; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistVisionCorrectionProfile { @@ -963,6 +1008,9 @@ struct FHyperTwistVisionCorrectionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 ContradictionCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ResolvedCorrectionCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionCorrectionTarget ActiveTarget; @@ -972,6 +1020,12 @@ struct FHyperTwistVisionCorrectionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray Contradictions; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionCorrectionResolution LastResolution; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ResolutionLedger; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") bool bCorrectionRequired = false; @@ -998,7 +1052,9 @@ struct FHyperTwistVisionCorrectionState if (CorrectionProfileId.IsEmpty() || MissingFaceCount < 0 || ContradictionCount < 0 - || ContradictionCount != Contradictions.Num()) + || ContradictionCount != Contradictions.Num() + || ResolvedCorrectionCount < 0 + || ResolvedCorrectionCount != ResolutionLedger.Num()) { return false; } @@ -1029,6 +1085,19 @@ struct FHyperTwistVisionCorrectionState } } + if (!LastResolution.ResolutionId.IsEmpty() && !LastResolution.IsStructurallyValid()) + { + return false; + } + + for (const FHyperTwistVisionCorrectionResolution& Resolution : ResolutionLedger) + { + if (!Resolution.IsStructurallyValid()) + { + return false; + } + } + return true; } }; @@ -1175,6 +1244,12 @@ struct FHyperTwistVisionCommitRequest UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString TargetStage; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RequestedActionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ResolutionKind; }; USTRUCT(BlueprintType) @@ -1215,6 +1290,9 @@ struct FHyperTwistVisionCommitResult UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionCorrectionState CorrectionState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionCorrectionResolution AppliedCorrectionResolution; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") TArray Warnings; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 7ca1f14..bd71e81 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1868,6 +1868,9 @@ struct FHyperTwistTrainingRecognitionSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 CorrectionEventCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ResolvedCorrectionCount = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") int32 FinalizedSessionCount = 0; @@ -1886,6 +1889,12 @@ struct FHyperTwistTrainingRecognitionSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionFinalizeResult LastFinalizeResult; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistVisionCorrectionResolution LastCorrectionResolution; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray CorrectionResolutionLedger; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistVisionReconstructionSession ActiveReconstructionSession; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistClassicCubePhase6RSCorrectionResolutionContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistClassicCubePhase6RSCorrectionResolutionContractTest.cpp new file mode 100644 index 0000000..a476d56 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistClassicCubePhase6RSCorrectionResolutionContractTest.cpp @@ -0,0 +1,235 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistClassicCubePhase6RSTestInternal +{ + FHyperTwistTrainingDeck MakeRecognitionDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-s/classic-cube-correction-resolution"); + Deck.Title = TEXT("Phase 6R-S Classic Cube Correction Resolution"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::RecognitionAssisted, + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-s-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition; + TrainingCase.PromptLabel = TEXT("Phase 6R-S 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-s-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); + } + + FHyperTwistVisionCommitResult ResolveCorrection( + UHyperTwistTrainingSubsystem* TrainingSubsystem, + const FString& FaceId + ) + { + FHyperTwistVisionCommitRequest Request; + Request.CommitKind = TEXT("correction-resolution"); + Request.TargetFace = FaceId; + Request.TargetStage = TEXT("cube-edit"); + Request.RequestedActionId = TEXT("apply-manual-corrections"); + Request.ResolutionKind = TEXT("manual-review-accepted"); + return TrainingSubsystem->CommitActiveRecognitionObservation(Request); + } + + void CommitClassicCubeNet(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + const TArray FaceOrder = { + TEXT("U"), + TEXT("R"), + TEXT("F"), + TEXT("D"), + TEXT("L"), + TEXT("B") + }; + + for (int32 FaceOrdinal = 0; FaceOrdinal < FaceOrder.Num(); ++FaceOrdinal) + { + CommitFace(TrainingSubsystem, FaceOrder[FaceOrdinal], FaceOrdinal + 1); + } + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistClassicCubePhase6RSCorrectionResolutionUnlockTest, + "HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionUnlock", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistClassicCubePhase6RSCorrectionResolutionUnlockTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistClassicCubePhase6RSTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-s-unlock-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-S."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem); + HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7); + const FHyperTwistVisionCommitResult Result = + HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F")); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestTrue(TEXT("The resolution route must surface a structurally valid applied resolution."), Result.AppliedCorrectionResolution.IsStructurallyValid()); + TestEqual(TEXT("The resolution route must apply the reviewed face explicitly."), Result.AppliedCorrectionResolution.FaceId, TEXT("F")); + TestTrue(TEXT("The resolution route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid()); + TestFalse(TEXT("The resolution route must close the open correction target after review."), Result.CorrectionState.bCorrectionRequired); + TestTrue(TEXT("The resolution route must mark the correction shell complete after review."), Result.CorrectionState.bCorrectionComplete); + TestEqual(TEXT("The resolution route must retain one correction resolution in the ledger."), Result.CorrectionState.ResolutionLedger.Num(), 1); + TestEqual(TEXT("The resolution route must count one resolved correction event."), Result.CorrectionState.ResolvedCorrectionCount, 1); + TestTrue(TEXT("Solve guidance must unlock once the correction review closes."), Result.SolveExplanationState.bReadyForExplanation); + TestEqual(TEXT("The solve explanation route must return to the bounded first solve stage."), Result.SolveExplanationState.RecommendedStageId, TEXT("orient-centers")); + TestEqual(TEXT("The session must count one resolved correction event."), SessionState.ResolvedCorrectionCount, 1); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistClassicCubePhase6RSCorrectionResolutionPartialClosureTest, + "HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionPartialClosure", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistClassicCubePhase6RSCorrectionResolutionPartialClosureTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistClassicCubePhase6RSTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-s-partial-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-S."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem); + HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7); + HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("R"), 8); + const FHyperTwistVisionCommitResult Result = + HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F")); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestTrue(TEXT("The partial closure route must still surface an applied resolution."), Result.AppliedCorrectionResolution.IsStructurallyValid()); + TestTrue(TEXT("The partial closure route must keep correction open while another revised face remains."), Result.CorrectionState.bCorrectionRequired); + TestFalse(TEXT("The partial closure route must not mark the shell complete while another face is pending."), Result.CorrectionState.bCorrectionComplete); + TestEqual(TEXT("The partial closure route must leave the earlier-ordered revised face active."), Result.CorrectionState.ActiveTargetFaceId, TEXT("R")); + TestEqual(TEXT("The partial closure route must classify the remaining active target as a revised face."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face")); + TestEqual(TEXT("The partial closure route must preserve one ledger entry after the first accepted review."), Result.CorrectionState.ResolutionLedger.Num(), 1); + TestFalse(TEXT("Solve guidance must stay blocked while another correction target remains."), Result.SolveExplanationState.bReadyForExplanation); + TestEqual(TEXT("The solve explanation route must stay in cube-edit while another review remains."), Result.SolveExplanationState.RecommendedStageId, TEXT("cube-edit")); + TestEqual(TEXT("The session must still count only one resolved correction review."), SessionState.ResolvedCorrectionCount, 1); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistClassicCubePhase6RSCorrectionResolutionReopenTest, + "HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionReopen", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistClassicCubePhase6RSCorrectionResolutionReopenTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistClassicCubePhase6RSTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-s-reopen-session")); + TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-S."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem); + HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7); + HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F")); + const FHyperTwistVisionCommitResult Result = + HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 8); + + const FHyperTwistTrainingRecognitionSessionState SessionState = + TrainingSubsystem->GetActiveRecognitionSessionState(); + TestTrue(TEXT("The reopen route must keep a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid()); + TestTrue(TEXT("The reopen route must reopen correction after a later recommit on the same face."), Result.CorrectionState.bCorrectionRequired); + TestEqual(TEXT("The reopen route must target the recommitted face again."), Result.CorrectionState.ActiveTargetFaceId, TEXT("F")); + TestEqual(TEXT("The reopen route must classify the reopened target as a revised face."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face")); + TestEqual(TEXT("The reopen route must preserve the earlier accepted review in the ledger."), Result.CorrectionState.ResolutionLedger.Num(), 1); + TestFalse(TEXT("Solve guidance must block again after a later recommit reopens correction."), Result.SolveExplanationState.bReadyForExplanation); + TestEqual(TEXT("The session must not double-count a new resolution when only a recommit occurred."), SessionState.ResolvedCorrectionCount, 1); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index d5c44cc..2f17e2e 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -159,8 +159,12 @@ Status update on `2026-05-21`: correction/explanation control pass is now consumed - the bounded permissive `Phase 6R-R` shared classic-cube recognition multi-face correction/explanation shell packet is now landed in current code -- the current next bounded move is a source-backed follow-on control pass for the retained - classic-cube recognition correction/explanation shell remainder, not a new restrictive packet by +- the generic source-backed `Phase 6R-S` shared classic-cube correction-resolution closure + control pass is now consumed +- the bounded permissive `Phase 6R-S` shared classic-cube correction-resolution closure packet is + now landed in current code +- the current next bounded move is a source-backed `Phase 6R-T` `roice3/MagicTile` + transform-aware macro remapping 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 @@ -265,8 +269,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 follow-on control pass for the retained classic-cube - recognition correction/explanation shell remainder +- the next bounded move is a source-backed `Phase 6R-T` `roice3/MagicTile` + transform-aware macro remapping preparation/control pass Companion docs: @@ -755,7 +759,8 @@ Approved working posture: - classic-cube recognition calibration contract - ordered face-observation contract - classic-cube webcam shell - - shared correction-target capture-state and re-scan adjunct via `Phase 6R-R` + - shared correction-target capture-state and re-scan adjunct via the landed shared `Phase 6R-R` + and `Phase 6R-S` correction shell - keep the following families deferred to later queue decisions: - bundled-font redistribution and multilingual solve shell - direct standalone multilingual or asset-shipping widening beyond the landed shared correction @@ -805,7 +810,7 @@ Approved working posture: - 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 two bounded retained slices are now landed in current code: +- the bounded retained slices now landed in current code are: - committed-face reconstruction session - face-vote replacement ledger - final classic-net shaping above aggregated committed faces @@ -813,11 +818,13 @@ Approved working posture: - bounded solve explanation/recommendation stage ladder and recommendation state - bounded correction profile, contradiction-aware correction state, and correction-explanation shell routing + - correction-resolution ledger, manual-review acceptance, and solve-guidance reopen/unlock + semantics - keep the row only partially incorporated by default: - - retained correction/explanation shell remainder beyond the landed - correction-state slice still requires a separate control decision - any redistribution of `frontend/lib/twistysim.min.js` still requires preserved or replaced upstream provenance/license + - generalized solver backend ownership and broad playback-runtime ownership stay separately + deferred ### `tentone/rubix-solver` @@ -3147,8 +3154,8 @@ Approved working posture: - keep ordinary third-party notices if code or substantial portions are incorporated - keep the live widening bounded to the landed tiling-topology contract family rather than flattening the row into blanket permission for full host-shell or runtime replacement use -- after the landed `Phase 6R-D` slice, move the queue head to the bounded speech-input / voice - sidecar set rather than reopening `MagicTile` by default +- the next justified widening is now the bounded `Phase 6R-T` transform-aware macro remapping + control pass rather than blanket host-shell or runtime replacement use ### `superliminal.com/andrey/mc7d` diff --git a/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_IMPLEMENTATION_PACKET_2026-05-24.md b/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_IMPLEMENTATION_PACKET_2026-05-24.md new file mode 100644 index 0000000..a4b6867 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_IMPLEMENTATION_PACKET_2026-05-24.md @@ -0,0 +1,173 @@ +# HyperTwist Phase 6R-S classic-cube correction resolution closure implementation packet + +Created on `2026-05-24` + +## Status + +- first-party HyperTwist packet +- bounded permissive `Phase 6R-S` implementation slice + +## Purpose + +This packet lands the current retained shared classic-cube +correction-resolution closure family: + +- first-party accepted-resolution ledger state, manual-review acceptance + routing, and solve-guidance reopen/unlock semantics above the already landed + `qbr` webcam shell, `rubix-cube-solver` reconstruction/browser-shell/solve + explanation seams, and the first-party `Phase 6R-R` correction shell + +It is not: + +- a full `qbr` row transplant +- a full `rubix-cube-solver` row transplant +- a bundled `twistysim.min.js` redistribution packet +- a bundled `arial-unicode-ms.ttf` redistribution packet +- a multilingual `qbr` shell packet +- a generalized solver backend packet +- a broad playback-runtime packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/REPO_LICENSE_TRACKING.md` +- `docs/ops/HYPERTWIST_CROSS_LANE_AUTHORITY_HIERARCHY_AND_RECONCILIATION_2026-05-20.md` +- `docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_PREPARATION_PACKET_2026-05-24.md` + +The retained owner split stays explicit: + +- `vivaansinghvi07/rubix-cube-solver` + - retained primary owner for correction/explanation semantics, + correction-resolution closure, and solve-guidance reopen/unlock behavior +- `kkoomen/qbr` + - retained adjunct owner for capture-state continuity, target-face re-scan + anchoring, and revision-aware reopen posture + +The top-level product shell owner does not change here: + +- first-party HyperTwist remains the broader recognition-session and + product-shell owner + +## Landed scope + +The current code now owns a bounded first-party correction-resolution closure +slice through: + +- first-party `HyperTwistRecognition` contract types for: + - correction-resolution record shape + - correction-state resolution ledger and last-resolution readout + - commit-request manual-review action metadata + - commit-result applied-resolution payloads +- first-party recognition session-state normalization for: + - resolved correction count + - last correction resolution + - correction-resolution ledger persistence across the active session +- active subsystem normalization in: + - local correction-resolution commit handling above the landed qbr classic + route + - correction-target validation before review acceptance + - resolution upsert behavior keyed by face id plus observation/revision + identity + - solve-guidance unlock after full closure + - correction reopen after a later recommit changes the reviewed face state +- sample contract routing in: + - `UHyperTwistContractLibrary` +- focused automation coverage in: + - `HyperTwistClassicCubePhase6RSCorrectionResolutionContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the next bounded correction-resolution closure slice, but it +does not widen into neighboring families or broader ownership. + +Still excluded: + +- bundled `twistysim.min.js` redistribution +- bundled `arial-unicode-ms.ttf` redistribution +- standalone multilingual `qbr` shell ownership +- generalized solver backend ownership +- broad playback-runtime ownership +- donor-shaped frontend transplant + +## Validation + +Build validation: + +- `C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat UnrealHyperTwistEditor Win64 Development -Project='C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject' -WaitMutex -NoHotReloadFromIDE -NoUba` + +Focused automation validation: + +- `C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe C:\HyperTwist\UnrealHyperTwist\UnrealHyperTwist.uproject -unattended -nop4 -nosplash -NullRHI -log -stdout -FullStdOutLogOutput -AbsLog=C:\HyperTwist\UnrealHyperTwist\Saved\Logs\Phase6R-S-ClassicCube-Verify.log -ReportExportPath=C:\HyperTwist\UnrealHyperTwist\Saved\AutomationReports\Phase6R-S-ClassicCube-Verify -ExecCmds="Automation RunTests HyperTwist.Permissive.ClassicCube.Phase6R.S; Quit" -TestExit="Automation Test Queue Empty"` + +Regression automation validation: + +- `HyperTwist.Permissive.ClassicCube.Phase6R.R` +- `HyperTwist.Permissive.RubixCubeSolver.Phase6R.Q` +- `HyperTwist.Permissive.RubixCubeSolver.Phase6R.P` +- `HyperTwist.Permissive.Qbr.Phase6R.O` +- `HyperTwist.Permissive.RubixCubeSolver.Phase6R.C` +- `HyperTwist.CleanRoom.CubeDesk` + +Expected covered tests: + +- `CorrectionResolutionUnlock` +- `CorrectionResolutionPartialClosure` +- `CorrectionResolutionReopen` +- existing `Phase 6R-R`, `rubix-cube-solver`, `qbr`, and `CubeDesk` + regression suites + +## Queue effect + +This packet consumes the current bounded `Phase 6R-S` implementation slice. + +`kkoomen/qbr` remains partially incorporated: + +- landed now: + - classic-cube recognition calibration contract + - ordered face-observation contract + - classic-cube webcam shell + - shared correction-target capture-state and re-scan adjunct +- still deferred: + - bundled-font redistribution and multilingual solve shell + +`vivaansinghvi07/rubix-cube-solver` remains partially incorporated: + +- landed now: + - committed-face reconstruction session + - face-vote replacement ledger + - final classic-net shaping above aggregated committed faces + - browser/webcam shell profile and browser-shell session-state composition + - bounded solve explanation/recommendation stage ladder and recommendation + state + - bounded correction profile, contradiction-aware correction state, and + correction-explanation shell routing + - correction-resolution ledger, manual-review acceptance, and + solve-guidance reopen/unlock semantics +- still deferred: + - bundled `twistysim.min.js` redistribution + - generalized solver backend ownership + - broad playback-runtime ownership + +The next clean move is: + +- a source-backed `Phase 6R-T` `roice3/MagicTile` transform-aware macro + remapping preparation/control pass + +Keep the future sequencing guards visible: + +- `vivaansinghvi07/rubix-cube-solver` + - do not copy or redistribute `frontend/lib/twistysim.min.js` without + preserving or replacing its upstream provenance/license in any future + widening that would ship that asset +- `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 +- `roice3/MagicTile` + - keep the next widening bounded to transform-aware macro remapping rather + than broad non-Euclidean interaction or host-shell ownership +- first-party HyperTwist + - keep the shared classic-cube correction shell closed by default unless a + narrower first-party gap is proven above the landed resolution-closure + seam diff --git a/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_PREPARATION_PACKET_2026-05-24.md b/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_PREPARATION_PACKET_2026-05-24.md new file mode 100644 index 0000000..3abcb97 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_PREPARATION_PACKET_2026-05-24.md @@ -0,0 +1,168 @@ +# HyperTwist Phase 6R-S classic-cube correction resolution closure preparation packet + +Created on `2026-05-24` + +## Status + +- historical consumed preparation authority +- bounded post-`Phase 6R-R` control slice +- the first bounded implementation slice now lands in: + - `docs/arch/HYPERTWIST_PHASE6R_S_CLASSIC_CUBE_CORRECTION_RESOLUTION_CLOSURE_IMPLEMENTATION_PACKET_2026-05-24.md` + +## Purpose + +This packet freezes the next widening order after the landed `Phase 6R-R` +shared classic-cube correction/explanation shell slice. + +The open task was: + +- define the next bounded source-backed widening packet as the shared + classic-cube correction-resolution closure layer above the already landed + `qbr` webcam shell, `rubix-cube-solver` reconstruction/browser shell/solve + explanation seams, and the first-party `Phase 6R-R` correction shell + +It is not: + +- a full `qbr` row transplant +- a full `rubix-cube-solver` row transplant +- a bundled `twistysim.min.js` redistribution packet +- a bundled `arial-unicode-ms.ttf` redistribution packet +- a multilingual `qbr` shell packet +- a generalized solver backend packet +- a broad playback-runtime packet + +## Current authority basis + +This preparation packet stands on already-closed authority: + +- `docs/REPO_LICENSE_TRACKING.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` +- `docs/arch/HYPERTWIST_PHASE6R_R_CLASSIC_CUBE_MULTI_FACE_CORRECTION_EXPLANATION_IMPLEMENTATION_PACKET_2026-05-24.md` + +The key accepted routing facts are: + +- `kkoomen/qbr` remains the primary classic-cube live-recognition and + capture-state donor +- `vivaansinghvi07/rubix-cube-solver` remains the stronger correction, + reconstruction-companion, and explanation-semantics donor +- the already landed `Phase 6R-R` slice owns: + - correction targets + - contradiction objects + - correction-profile defaults + - correction shell visibility and solve-guidance blocking +- the next donor value strongest for the narrower shared slice is: + - accepted manual-review resolution per face/revision + - explicit correction-resolution ledger state + - reopen-after-recommit behavior + - clear-to-solve transition semantics after correction review closure + +Ownership denied here is: + +- do not widen into bundled `twistysim.min.js` redistribution +- do not widen into bundled `arial-unicode-ms.ttf` redistribution +- do not widen into standalone multilingual `qbr` ownership +- do not widen into generalized solver backend ownership +- do not widen into broad playback-runtime ownership + +## Cross-lane retained split + +Exact contested slice: + +- classic-cube correction-resolution closure + +Candidate repos considered: + +- `kkoomen/qbr` +- `vivaansinghvi07/rubix-cube-solver` + +Authority outcome: + +- `vivaansinghvi07/rubix-cube-solver` - `A1 / R1 / F2` + - retained primary owner for correction-resolution semantics, manual-review + acceptance flow, and solve-guidance reopen/unlock behavior +- `kkoomen/qbr` - `A2 / R1 / F2` + - retained adjunct owner for capture-state continuity, target-face re-scan + anchoring, and revision-aware reopen posture that keeps the correction + shell attached to the recognition substrate + +The top-level product shell owner does not change here: + +- first-party HyperTwist remains the broader recognition-session and + product-shell owner + +## Required result + +The source-backed control pass for this packet is now complete. + +The first actual `6R-S` implementation packet should: + +- define one bounded correction-resolution closure slice +- keep the slice above the landed `Phase 6R-O`, `Phase 6R-P`, `Phase 6R-Q`, + and `Phase 6R-R` seams +- land accepted-resolution ledger state, manual-review acceptance routing, and + solve-guidance reopen/unlock semantics +- preserve `rubix-cube-solver` as the retained correction/explanation-semantic + owner while preserving `qbr` as the capture-state adjunct +- 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: + +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\19-kkoomen-qbr-upstream-dossier.md` +- `C:\visual_studio_solutions\multi_project\GPT 5.4 HyperTwist parse\20-vivaansinghvi07-rubix-cube-solver-upstream-dossier.md` + +Inspected first-party receiving basis: + +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp` +- `UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistClassicCubePhase6RRCorrectionContractTest.cpp` + +## Narrowed 6R-S slice decision + +The next widening slice is fixed as: + +1. correction-resolution closure and solve-guidance reopen/unlock semantics + +That narrowed slice covers: + +- first-party accepted-resolution ledger state for: + - reviewed face id + - observation/revision identity + - resolved contradiction ids + - clear-to-solve transition visibility +- first-party resolution commit behavior for: + - manual-review acceptance + - correction-target validation + - replay mutation and correction-event accounting +- first-party reopen behavior for: + - later recommits on a previously resolved face + - solve-guidance blocking when a later revision reopens correction +- focused automation coverage + +## Deferred neighboring families + +The first `6R-S` implementation packet must keep these capability families +closed: + +- bundled `twistysim.min.js` shipping or redistribution +- bundled `arial-unicode-ms.ttf` shipping or redistribution +- standalone multilingual `qbr` shell ownership +- generalized solver backend ownership +- broad playback-runtime ownership +- any donor-shaped frontend transplant + +## Queue effect + +This preparation packet is now consumed by the landed bounded `Phase 6R-S` +implementation slice. + +Once that slice lands, the next clean move should return to the retained +repo-row queue at: + +- a source-backed `Phase 6R-T` `roice3/MagicTile` transform-aware macro + remapping preparation/control pass diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index bc70ae4..a3c5c96 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -108,15 +108,21 @@ The next bounded move is now: correction/explanation control pass is now consumed 10. the bounded permissive `Phase 6R-R` shared classic-cube recognition multi-face correction/explanation shell packet is now landed in current code -11. the next bounded move is a source-backed follow-on control pass for the retained - classic-cube recognition correction/explanation shell remainder shared across `kkoomen/qbr` - and `vivaansinghvi07/rubix-cube-solver` -12. keep the `rubix-cube-solver` guard visible: +11. the generic source-backed `Phase 6R-S` shared classic-cube correction-resolution closure + control pass is now consumed +12. the bounded permissive `Phase 6R-S` shared classic-cube correction-resolution closure packet + is now landed in current code +13. the next bounded move is a source-backed `Phase 6R-T` `roice3/MagicTile` + transform-aware macro remapping preparation/control pass +14. keep the `rubix-cube-solver` guard visible: - do not copy or redistribute `frontend/lib/twistysim.min.js` without preserving or replacing its upstream provenance/license in any future widening that would ship that asset -13. keep the `qbr` guard visible: +15. keep the `qbr` guard visible: - 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 +16. keep the `MagicTile` guard visible: + - keep the next widening bounded to transform-aware macro remapping rather than broad + non-Euclidean interaction or WinForms/OpenTK host-shell ownership ## Memory-specific sequencing rule diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index 5e7f30d..fd615f9 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -112,6 +112,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | Committed-face reconstruction and final classic-net shaping | Implemented now | landed `rubix-cube-solver` packet | Reconstruction session boundary is live. | | Browser-assisted recognition shell | Implemented now | landed `rubix-cube-solver` `Phase 6R-P` | First-party browser-shell profile, websocket session metadata, stage routing, and manual-edit handoff are live. | | Bounded solve explanation and recommendation shell | Implemented now | landed `rubix-cube-solver` `Phase 6R-Q` | First-party stage-ladder guidance and recommendation-state readout are live after full reconstruction. | +| Correction-resolution closure shell | Implemented now | landed shared `Phase 6R-S` packet | First-party accepted-resolution ledger, per-face manual-review acceptance, and solve-guidance reopen/unlock semantics are live above the bounded correction shell. | | Hyper puzzle catalog contract | Implemented now | landed `Hyperspeedcube` `Phase 6R-A` | Narrow retained hyper-puzzle catalog slice is live. | | Hyper notation and replay-log serialization boundary | Implemented now | landed `Hyperspeedcube` `Phase 6R-K` | Narrow retained notation and log-serialization slice is live. | | Hyper replay verification boundary | Implemented now | landed `Hyperspeedcube` `Phase 6R-L` | Narrow retained replay-verification and solve-proof slice is live. | @@ -151,7 +152,7 @@ repo. | Bounded solve explanation/recommendation shell | Implemented now | `rubix-cube-solver` bounded packet | Live stage-ladder recommendation state above the bounded browser recognition shell. | | Provider-backed recognition service contract | Implemented now | first-party recognition client surfaces | First-party normalized recognition session boundary is real. | | Multilingual recognition guidance shell | Deep-source grounded retained | `qbr` retained remainder | Kept deferred until a font-safe and provenance-safe shipping path is explicit. | -| Multi-face correction/explanation shell | Implemented now | first-party recognition/training surfaces | Current bounded correction profile, contradiction-aware correction state, and correction-explanation shell are live above the landed `Phase 6R-O`, `Phase 6R-P`, and `Phase 6R-Q` seams. Broader playback/runtime and bundled-asset widening remain deferred. | +| Multi-face correction/explanation shell | Implemented now | first-party recognition/training surfaces | Current bounded correction profile, contradiction-aware correction state, accepted-resolution ledger, and correction-explanation / solve-unlock shell are live above the landed `Phase 6R-O`, `Phase 6R-P`, `Phase 6R-Q`, `Phase 6R-R`, and `Phase 6R-S` seams. Bundled-asset, generalized solver-backend, and broad playback/runtime widening remain deferred. | ### 3. Training, coaching, and progression cockpit @@ -187,7 +188,7 @@ repo. | Tiling topology and geometry-family contract | Implemented now | landed `MagicTile` packet | New bounded widening is live. | | Dedicated `120-cell` family runtime profile and persistence boundary | Implemented now | landed `Magic120Cell` packet | Current bounded family-specific widening is live beneath the retained `Hyperspeedcube` runtime anchor. | | Dedicated `5D` family runtime profile and persistence boundary | Implemented now | landed `MagicCube5D` packet | Current bounded family-specific widening is live beneath the retained `Hyperspeedcube` runtime anchor. | -| Transform-aware macro remapping and broad non-Euclidean interaction shell | Deep-source grounded retained | `MagicTile` retained remainder | Not yet promoted into live broad interaction ownership. | +| Transform-aware macro remapping and broad non-Euclidean interaction shell | Deep-source grounded retained | `MagicTile` retained remainder | Transform-aware macro remapping is the current next bounded retained slice; broad interaction ownership is still deferred. | ### 7. Speech input and voice sidecars diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 1068e9b..48e8855 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -135,9 +135,13 @@ Canonical discovery surfaces for roadmap interpretation: correction/explanation control pass is now consumed - the bounded permissive `Phase 6R-R` shared classic-cube recognition multi-face correction/explanation shell packet is now landed in current code -- the current next bounded move is a source-backed follow-on control pass for the retained - classic-cube recognition correction/explanation shell remainder, while keeping both the - `twistysim.min.js` and `arial-unicode-ms.ttf` redistribution guards explicit +- the generic source-backed `Phase 6R-S` shared classic-cube correction-resolution closure + control pass is now consumed +- the bounded permissive `Phase 6R-S` shared classic-cube correction-resolution closure packet is + now landed in current code +- the current next bounded move is a source-backed `Phase 6R-T` `roice3/MagicTile` + transform-aware macro remapping preparation/control pass, while keeping both classic-cube + bundled-asset guards explicit - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -199,15 +203,14 @@ Current routing truth: - active non-live implementation-board rows: `34` - retained benchmark, oracle, or clean-room-later rows outside the active implementation board: `9` -The next bounded move is a source-backed follow-on control pass for the retained classic-cube -recognition correction/explanation shell remainder shared across `kkoomen/qbr` and -`vivaansinghvi07/rubix-cube-solver`. +The next bounded move is a source-backed `Phase 6R-T` `roice3/MagicTile` +transform-aware macro remapping preparation/control pass. Queue interpretation after that control pass: - then continue with the retained repo-row queue - highest current repo-row queue pressure sits in: - - the retained classic-cube recognition shell set + - the retained `MagicTile` transform-aware macro remapping remainder - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: - puzzle catalog contract @@ -257,11 +260,13 @@ Queue interpretation after that control pass: - bounded solve explanation/recommendation stage ladder and recommendation state - bounded correction profile, contradiction-aware correction state, and correction-explanation shell routing + - correction-resolution ledger, manual-review acceptance, and solve-guidance reopen/unlock + semantics - still deferred: - - retained correction/explanation shell remainder beyond the landed - correction-state slice - bundled `twistysim.min.js` redistribution -- after the landed `Phase 6R-R` implementation packet, keep the future legal sequencing guards + - generalized solver backend ownership + - broad playback-runtime ownership +- after the landed `Phase 6R-S` implementation packet, keep the future legal sequencing guards visible: - `vivaansinghvi07/rubix-cube-solver` - do not copy or redistribute `frontend/lib/twistysim.min.js` without preserving or replacing @@ -269,10 +274,12 @@ Queue interpretation after that control pass: - `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 - - `Phase 6R-R` + - `Phase 6R-S` - keep `vivaansinghvi07/rubix-cube-solver` as the retained correction/explanation-semantic owner and preserve `kkoomen/qbr` as the retained capture-state and re-scan adjunct without - widening into either bundled asset family or broad playback/runtime ownership + reopening the shared correction shell unless a narrower first-party gap is proven above the + landed resolution-closure seam or widening into either bundled asset family or broad + playback/runtime ownership - `roice3/MagicTile` remains a partially landed row rather than a closed row: - landed: - tiling topology and geometry-family contract @@ -337,11 +344,11 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - retained classic-cube recognition correction/explanation shell remainder assessment + - retained `MagicTile` transform-aware macro remapping assessment - the next bounded move should stay narrow: - - a source-backed follow-on control pass before any widening into bundled `twistysim.min.js` - redistribution, generalized solver backend ownership, any `qbr` multilingual/font-shipping - path, or broad playback-runtime ownership + - a source-backed `Phase 6R-T` control pass before any widening into broad non-Euclidean + interaction shell, WinForms/OpenTK host ownership, bundled `twistysim.min.js` + redistribution, or any `qbr` multilingual/font-shipping path - keep the speech-input / voice sidecar legal sequencing guard visible: - keep code-license judgments separate from model, voice, and payload-license review - keep the provider-neutral speech-lane guard visible: