Implement Phase 6R-R correction shell

This commit is contained in:
axiomlogicnexus 2026-05-24 04:36:57 +02:00
parent 861947b07b
commit dafaf397cc
10 changed files with 1506 additions and 43 deletions

View file

@ -300,6 +300,83 @@ namespace HyperTwistContractLibraryInternal
return Normalized;
}
const TArray<FString>& GetClassicCorrectionFaceOrder()
{
static const TArray<FString> FaceOrder = {
TEXT("U"),
TEXT("R"),
TEXT("F"),
TEXT("D"),
TEXT("L"),
TEXT("B")
};
return FaceOrder;
}
int32 GetClassicCorrectionFaceOrderIndex(const FString& FaceId)
{
const FString NormalizedFaceId = NormalizeBrowserFaceHint(FaceId);
const TArray<FString>& FaceOrder = GetClassicCorrectionFaceOrder();
for (int32 Index = 0; Index < FaceOrder.Num(); ++Index)
{
if (FaceOrder[Index] == NormalizedFaceId)
{
return Index;
}
}
return MAX_int32;
}
float CalculateCommittedFaceConfidence(const FHyperTwistVisionCommittedFaceState& FaceState)
{
if (FaceState.LatestOrderedStickers.Num() <= 0)
{
return 0.0f;
}
float ConfidenceSum = 0.0f;
for (const FHyperTwistVisionObservedSticker& Sticker : FaceState.LatestOrderedStickers)
{
ConfidenceSum += Sticker.Confidence;
}
return ConfidenceSum / static_cast<float>(FaceState.LatestOrderedStickers.Num());
}
void UpsertCorrectionTarget(
TArray<FHyperTwistVisionCorrectionTarget>& Targets,
const FHyperTwistVisionCorrectionTarget& Candidate
)
{
const FString CandidateFaceId = NormalizeBrowserFaceHint(Candidate.FaceId);
const int32 ExistingIndex = Targets.IndexOfByPredicate(
[&CandidateFaceId](const FHyperTwistVisionCorrectionTarget& ExistingTarget)
{
return NormalizeBrowserFaceHint(ExistingTarget.FaceId) == CandidateFaceId;
}
);
if (ExistingIndex == INDEX_NONE)
{
Targets.Add(Candidate);
return;
}
if (Candidate.PriorityOrdinal < Targets[ExistingIndex].PriorityOrdinal)
{
Targets[ExistingIndex] = Candidate;
return;
}
if (Candidate.PriorityOrdinal == Targets[ExistingIndex].PriorityOrdinal
&& Candidate.bManualReviewPreferred
&& !Targets[ExistingIndex].bManualReviewPreferred)
{
Targets[ExistingIndex].bManualReviewPreferred = true;
}
}
FHyperTwistVisionBrowserShellProfile MakeDefaultClassicCubeBrowserShellProfile(const FString& ProfileId)
{
FHyperTwistVisionBrowserShellProfile Profile;
@ -452,6 +529,36 @@ namespace HyperTwistContractLibraryInternal
return Profile;
}
FHyperTwistVisionCorrectionProfile MakeDefaultClassicCubeCorrectionProfile(const FString& ProfileId)
{
FHyperTwistVisionCorrectionProfile Profile;
Profile.CorrectionProfileId = ProfileId;
Profile.PuzzleId = TEXT("cube/3x3x3");
Profile.CorrectionMode = TEXT("bounded-multi-face-correction-v1");
Profile.bSupportsTargetedRescan = true;
Profile.bSupportsManualCubeEdit = true;
Profile.bSupportsStageRestart = true;
auto AddAction = [&Profile](
const TCHAR* ActionId,
const TCHAR* InputBinding,
const TCHAR* SurfaceId
)
{
FHyperTwistVisionShellActionBinding ActionBinding;
ActionBinding.ActionId = ActionId;
ActionBinding.InputBinding = InputBinding;
ActionBinding.SurfaceId = SurfaceId;
Profile.ActionBindings.Add(ActionBinding);
};
AddAction(TEXT("capture-target-face"), TEXT("button:capture-target-face"), TEXT("video-stream"));
AddAction(TEXT("rescan-target-face"), TEXT("button:rescan-target-face"), TEXT("video-stream"));
AddAction(TEXT("apply-manual-corrections"), TEXT("button:apply-corrections"), TEXT("cube-edit"));
AddAction(TEXT("restart-browser-session"), TEXT("button:restart-session"), TEXT("cube-edit"));
return Profile;
}
void ResolveMockClassicFaceSwatch(
const FString& FaceId,
FString& OutColorId,
@ -718,9 +825,275 @@ namespace HyperTwistContractLibraryInternal
return BrowserShellState;
}
FHyperTwistVisionCorrectionState MakeClassicCubeCorrectionState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
const FHyperTwistVisionFaceObservation& ObservedFace,
const TArray<FString>& Warnings
)
{
static constexpr float LowConfidenceThreshold = 0.85f;
FHyperTwistVisionCorrectionState CorrectionState;
const FHyperTwistVisionCorrectionProfile CorrectionProfile =
SessionConfig.CorrectionProfileDefinition.IsStructurallyValid()
? SessionConfig.CorrectionProfileDefinition
: MakeDefaultClassicCubeCorrectionProfile(
!SessionConfig.CorrectionProfileId.IsEmpty()
? SessionConfig.CorrectionProfileId
: TEXT("classic-cube-correction-shell-v1")
);
CorrectionState.CorrectionProfileId = CorrectionProfile.CorrectionProfileId;
CorrectionState.MissingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet;
CorrectionState.bStageRestartVisible =
CorrectionProfile.bSupportsStageRestart && Warnings.Num() > 0;
TArray<FHyperTwistVisionCorrectionTarget> PendingTargets;
TArray<FHyperTwistVisionCorrectionContradiction> Contradictions;
if (ReconstructionSession.IsStructurallyValid())
{
for (const FHyperTwistVisionCommittedFaceState& FaceState : ReconstructionSession.CommittedFaces)
{
const FString FaceId = NormalizeBrowserFaceHint(FaceState.FaceId);
const bool bHasCompleteNet = ReconstructionSession.bHasCompleteClassicCubeNet;
if (FaceState.bHasConflicts)
{
FHyperTwistVisionCorrectionContradiction Contradiction;
Contradiction.ContradictionId = FString::Printf(TEXT("conflict-%s"), *FaceId);
Contradiction.FaceId = FaceId;
Contradiction.ContradictionKind = TEXT("conflicting-face");
Contradiction.ObservationId = FaceState.LatestObservationId;
Contradiction.RevisionCount = FaceState.RevisionCount;
Contradiction.StatusLine =
FString::Printf(TEXT("Face %s has conflicting sticker votes."), *FaceId);
Contradiction.DetailLine =
TEXT("At least one sticker vote disagrees with the current winner. Re-scan the face or review the manual cube edit before solve guidance continues.");
Contradiction.bBlocksSolveExplanation = true;
Contradiction.bManualReviewPreferred = bHasCompleteNet;
for (const FHyperTwistVisionReconstructionStickerVote& Vote : FaceState.StickerVotes)
{
if (Vote.bHadConflict)
{
Contradiction.AffectedStickerIndices.Add(Vote.GridIndex);
}
}
Contradictions.Add(Contradiction);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("conflicting-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan face %s before capture advances."), *FaceId);
Target.DetailLine = bHasCompleteNet
? TEXT("Conflicting sticker votes survived the bounded reconstruction pass. Use cube edit or a targeted re-scan to close the contradiction.")
: TEXT("Conflicting sticker votes are present while capture is still open. Re-scan the face before committing the remaining sides.");
Target.PriorityOrdinal = 0;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
if (FaceState.RevisionCount > 0)
{
FHyperTwistVisionCorrectionContradiction Contradiction;
Contradiction.ContradictionId = FString::Printf(TEXT("revision-%s"), *FaceId);
Contradiction.FaceId = FaceId;
Contradiction.ContradictionKind = TEXT("manual-revision");
Contradiction.ObservationId = FaceState.LatestObservationId;
Contradiction.RevisionCount = FaceState.RevisionCount;
Contradiction.StatusLine =
FString::Printf(TEXT("Face %s was recommitted and needs review."), *FaceId);
Contradiction.DetailLine = FString::Printf(
TEXT("The latest bounded reconstruction includes %d revision(s) on face %s. Preserve the latest capture or review the cube edit before solve guidance continues."),
FaceState.RevisionCount,
*FaceId
);
Contradiction.bBlocksSolveExplanation = true;
Contradiction.bManualReviewPreferred = bHasCompleteNet;
Contradictions.Add(Contradiction);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("revised-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review the revised face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan revised face %s before capture advances."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Face %s has already been recommitted %d time(s) in this bounded session."),
*FaceId,
FaceState.RevisionCount
);
Target.PriorityOrdinal = 1;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
const float FaceConfidence = CalculateCommittedFaceConfidence(FaceState);
if (FaceConfidence > 0.0f && FaceConfidence < LowConfidenceThreshold)
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("low-confidence-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review low-confidence face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan low-confidence face %s before capture advances."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Average sticker confidence %.2f on face %s is below the bounded correction threshold %.2f."),
FaceConfidence,
*FaceId,
LowConfidenceThreshold
);
Target.PriorityOrdinal = 2;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
for (const FString& MissingFaceId : ReconstructionSession.MissingFaces)
{
const FString FaceId = NormalizeBrowserFaceHint(MissingFaceId);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("missing-face");
Target.RecommendedActionId = TEXT("capture-target-face");
Target.StatusLine =
FString::Printf(TEXT("Capture face %s to continue the bounded reconstruction pass."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Commit the next missing face. %d face(s) still need capture before solve guidance unlocks."),
ReconstructionSession.MissingFaces.Num()
);
Target.PriorityOrdinal = 3;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
if (PendingTargets.Num() == 0)
{
const FString FallbackFaceId = !ObservedFace.FaceId.IsEmpty()
? NormalizeBrowserFaceHint(ObservedFace.FaceId)
: TEXT("F");
if (Warnings.Num() > 0)
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FallbackFaceId;
Target.ReasonId = TEXT("warning-restart");
Target.RecommendedActionId = TEXT("restart-browser-session");
Target.StatusLine = TEXT("Restart the capture stage before correction continues.");
Target.DetailLine = FString::Printf(
TEXT("The bounded correction shell reported: %s"),
*FString::Join(Warnings, TEXT(" | "))
);
Target.PriorityOrdinal = 0;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
else if (!ReconstructionSession.IsStructurallyValid())
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FallbackFaceId;
Target.ReasonId = TEXT("capture-bootstrap");
Target.RecommendedActionId = TEXT("capture-target-face");
Target.StatusLine =
FString::Printf(TEXT("Capture face %s to start bounded correction routing."), *FallbackFaceId);
Target.DetailLine =
TEXT("No committed reconstruction session is available yet. Capture the first face before correction or solve guidance can advance.");
Target.PriorityOrdinal = 3;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
PendingTargets.Sort(
[](const FHyperTwistVisionCorrectionTarget& Left, const FHyperTwistVisionCorrectionTarget& Right)
{
if (Left.PriorityOrdinal != Right.PriorityOrdinal)
{
return Left.PriorityOrdinal < Right.PriorityOrdinal;
}
return GetClassicCorrectionFaceOrderIndex(Left.FaceId)
< GetClassicCorrectionFaceOrderIndex(Right.FaceId);
}
);
Contradictions.Sort(
[](const FHyperTwistVisionCorrectionContradiction& Left, const FHyperTwistVisionCorrectionContradiction& Right)
{
return GetClassicCorrectionFaceOrderIndex(Left.FaceId)
< GetClassicCorrectionFaceOrderIndex(Right.FaceId);
}
);
CorrectionState.PendingTargets = PendingTargets;
CorrectionState.Contradictions = Contradictions;
CorrectionState.ContradictionCount = Contradictions.Num();
CorrectionState.bCorrectionRequired = PendingTargets.Num() > 0;
CorrectionState.bManualReviewRequired = Contradictions.Num() > 0;
CorrectionState.bCorrectionComplete =
ReconstructionSession.bHasCompleteClassicCubeNet && !CorrectionState.bCorrectionRequired;
CorrectionState.bSolveExplanationBlocked = !CorrectionState.bCorrectionComplete;
if (CorrectionState.bCorrectionRequired)
{
CorrectionState.ActiveTarget = PendingTargets[0];
CorrectionState.ActiveTargetFaceId = CorrectionState.ActiveTarget.FaceId;
CorrectionState.RecommendationStatusLine = CorrectionState.ActiveTarget.StatusLine;
CorrectionState.RecommendationDetailLine = CorrectionState.ActiveTarget.DetailLine;
CorrectionState.NextRecommendedActionId = CorrectionState.ActiveTarget.RecommendedActionId;
CorrectionState.bTargetedRescanVisible =
CorrectionState.ActiveTarget.RecommendedActionId == TEXT("capture-target-face")
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("rescan-target-face");
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit
&& (ReconstructionSession.bHasCompleteClassicCubeNet
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("apply-manual-corrections"));
CorrectionState.bStageRestartVisible =
CorrectionProfile.bSupportsStageRestart
&& (Warnings.Num() > 0
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("restart-browser-session"));
CorrectionState.bManualReviewRequired =
CorrectionState.bManualReviewRequired || CorrectionState.ActiveTarget.bManualReviewPreferred;
CorrectionState.RecommendedStageId =
CorrectionState.ActiveTarget.RecommendedActionId == TEXT("apply-manual-corrections")
? TEXT("cube-edit")
: TEXT("video-stream");
}
else
{
CorrectionState.RecommendedStageId = TEXT("orient-centers");
CorrectionState.RecommendationStatusLine =
TEXT("Correction is clear; solve guidance can begin.");
CorrectionState.RecommendationDetailLine =
TEXT("No missing faces, contradictions, or low-confidence targets remain in the bounded correction shell.");
CorrectionState.NextRecommendedActionId = TEXT("start-solve-explanation");
CorrectionState.bTargetedRescanVisible = false;
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet;
}
return CorrectionState;
}
FHyperTwistVisionSolveExplanationState MakeClassicCubeSolveExplanationState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
const FHyperTwistVisionCorrectionState& CorrectionState
)
{
FHyperTwistVisionSolveExplanationState SolveExplanationState;
@ -738,10 +1111,9 @@ namespace HyperTwistContractLibraryInternal
SolveExplanationState.bPlaybackDeferred = !SolveExplanationProfile.bSupportsPlaybackStage;
SolveExplanationState.bRequiresFullReconstruction = true;
const int32 RemainingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
if (ReconstructionSession.bHasCompleteClassicCubeNet)
if (ReconstructionSession.bHasCompleteClassicCubeNet
&& CorrectionState.IsStructurallyValid()
&& CorrectionState.bCorrectionComplete)
{
SolveExplanationState.RecommendedStageId =
SolveExplanationProfile.Steps.Num() > 0
@ -757,8 +1129,30 @@ namespace HyperTwistContractLibraryInternal
SolveExplanationState.bNextStepVisible = false;
SolveExplanationState.bPreviousStepVisible = false;
}
else if (ReconstructionSession.bHasCompleteClassicCubeNet
&& CorrectionState.IsStructurallyValid()
&& CorrectionState.bCorrectionRequired)
{
SolveExplanationState.RecommendedStageId = !CorrectionState.RecommendedStageId.IsEmpty()
? CorrectionState.RecommendedStageId
: TEXT("video-stream");
SolveExplanationState.RecommendationStatusLine =
TEXT("Solve guidance stays blocked until correction closes.");
SolveExplanationState.RecommendationDetailLine =
!CorrectionState.RecommendationDetailLine.IsEmpty()
? CorrectionState.RecommendationDetailLine
: TEXT("Finish the bounded correction shell before solve guidance unlocks.");
SolveExplanationState.CurrentStepOrdinal = -1;
SolveExplanationState.bReadyForExplanation = false;
SolveExplanationState.bStartExplanationVisible = false;
SolveExplanationState.bNextStepVisible = false;
SolveExplanationState.bPreviousStepVisible = false;
}
else
{
const int32 RemainingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
SolveExplanationState.RecommendedStageId = TEXT("capture-remaining-faces");
SolveExplanationState.RecommendationStatusLine =
TEXT("Solve guidance unlocks after capture completes.");
@ -1019,6 +1413,11 @@ FHyperTwistVisionSessionConfig UHyperTwistContractLibrary::MakeSampleVisionSessi
HyperTwistContractLibraryInternal::MakeDefaultClassicCubeSolveExplanationProfile(
Config.SolveExplanationProfileId
);
Config.CorrectionProfileId = TEXT("classic-cube-correction-shell-v1");
Config.CorrectionProfileDefinition =
HyperTwistContractLibraryInternal::MakeDefaultClassicCubeCorrectionProfile(
Config.CorrectionProfileId
);
Config.FrameSize = FIntPoint(1280, 720);
Config.MaxFrameRate = 30;
return Config;
@ -1091,9 +1490,16 @@ FHyperTwistVisionCommitResult UHyperTwistContractLibrary::MakeMockVisionCommitRe
Result.ReconstructionSession,
Result.ObservedFace
);
Result.CorrectionState = HyperTwistContractLibraryInternal::MakeClassicCubeCorrectionState(
SessionConfig,
Result.ReconstructionSession,
Result.ObservedFace,
Result.Warnings
);
Result.SolveExplanationState = HyperTwistContractLibraryInternal::MakeClassicCubeSolveExplanationState(
SessionConfig,
Result.ReconstructionSession
Result.ReconstructionSession,
Result.CorrectionState
);
return Result;
}

View file

@ -1928,6 +1928,55 @@ namespace HyperTwistTrainingSubsystemInternal
return MAX_int32;
}
float CalculateCommittedFaceConfidence(const FHyperTwistVisionCommittedFaceState& FaceState)
{
if (FaceState.LatestOrderedStickers.Num() <= 0)
{
return 0.0f;
}
float ConfidenceSum = 0.0f;
for (const FHyperTwistVisionObservedSticker& Sticker : FaceState.LatestOrderedStickers)
{
ConfidenceSum += Sticker.Confidence;
}
return ConfidenceSum / static_cast<float>(FaceState.LatestOrderedStickers.Num());
}
void UpsertCorrectionTarget(
TArray<FHyperTwistVisionCorrectionTarget>& Targets,
const FHyperTwistVisionCorrectionTarget& Candidate
)
{
const FString CandidateFaceId = NormalizeRecognitionFaceId(Candidate.FaceId);
const int32 ExistingIndex = Targets.IndexOfByPredicate(
[&CandidateFaceId](const FHyperTwistVisionCorrectionTarget& ExistingTarget)
{
return NormalizeRecognitionFaceId(ExistingTarget.FaceId) == CandidateFaceId;
}
);
if (ExistingIndex == INDEX_NONE)
{
Targets.Add(Candidate);
return;
}
if (Candidate.PriorityOrdinal < Targets[ExistingIndex].PriorityOrdinal)
{
Targets[ExistingIndex] = Candidate;
return;
}
if (Candidate.PriorityOrdinal == Targets[ExistingIndex].PriorityOrdinal
&& Candidate.bManualReviewPreferred
&& !Targets[ExistingIndex].bManualReviewPreferred)
{
Targets[ExistingIndex].bManualReviewPreferred = true;
}
}
const FHyperTwistVisionCommittedFaceState* FindCommittedFaceState(
const FHyperTwistVisionReconstructionSession& Session,
const FString& FaceId
@ -2588,6 +2637,38 @@ namespace HyperTwistTrainingSubsystemInternal
return Profile;
}
FHyperTwistVisionCorrectionProfile BuildDefaultClassicCubeCorrectionProfile(
const FString& ProfileId
)
{
FHyperTwistVisionCorrectionProfile Profile;
Profile.CorrectionProfileId = ProfileId;
Profile.PuzzleId = TEXT("cube/3x3x3");
Profile.CorrectionMode = TEXT("bounded-multi-face-correction-v1");
Profile.bSupportsTargetedRescan = true;
Profile.bSupportsManualCubeEdit = true;
Profile.bSupportsStageRestart = true;
auto AddAction = [&Profile](
const TCHAR* ActionId,
const TCHAR* InputBinding,
const TCHAR* SurfaceId
)
{
FHyperTwistVisionShellActionBinding ActionBinding;
ActionBinding.ActionId = ActionId;
ActionBinding.InputBinding = InputBinding;
ActionBinding.SurfaceId = SurfaceId;
Profile.ActionBindings.Add(ActionBinding);
};
AddAction(TEXT("capture-target-face"), TEXT("button:capture-target-face"), TEXT("video-stream"));
AddAction(TEXT("rescan-target-face"), TEXT("button:rescan-target-face"), TEXT("video-stream"));
AddAction(TEXT("apply-manual-corrections"), TEXT("button:apply-corrections"), TEXT("cube-edit"));
AddAction(TEXT("restart-browser-session"), TEXT("button:restart-session"), TEXT("cube-edit"));
return Profile;
}
FHyperTwistVisionShellState BuildClassicCubeWebcamShellState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
@ -2695,9 +2776,275 @@ namespace HyperTwistTrainingSubsystemInternal
return BrowserShellState;
}
FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
const FHyperTwistVisionFaceObservation& ObservedFace,
const TArray<FString>& Warnings
)
{
static constexpr float LowConfidenceThreshold = 0.85f;
FHyperTwistVisionCorrectionState CorrectionState;
const FHyperTwistVisionCorrectionProfile CorrectionProfile =
SessionConfig.CorrectionProfileDefinition.IsStructurallyValid()
? SessionConfig.CorrectionProfileDefinition
: BuildDefaultClassicCubeCorrectionProfile(
!SessionConfig.CorrectionProfileId.IsEmpty()
? SessionConfig.CorrectionProfileId
: TEXT("classic-cube-correction-shell-v1")
);
CorrectionState.CorrectionProfileId = CorrectionProfile.CorrectionProfileId;
CorrectionState.MissingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet;
CorrectionState.bStageRestartVisible =
CorrectionProfile.bSupportsStageRestart && Warnings.Num() > 0;
TArray<FHyperTwistVisionCorrectionTarget> PendingTargets;
TArray<FHyperTwistVisionCorrectionContradiction> Contradictions;
if (ReconstructionSession.IsStructurallyValid())
{
for (const FHyperTwistVisionCommittedFaceState& FaceState : ReconstructionSession.CommittedFaces)
{
const FString FaceId = NormalizeRecognitionFaceId(FaceState.FaceId);
const bool bHasCompleteNet = ReconstructionSession.bHasCompleteClassicCubeNet;
if (FaceState.bHasConflicts)
{
FHyperTwistVisionCorrectionContradiction Contradiction;
Contradiction.ContradictionId = FString::Printf(TEXT("conflict-%s"), *FaceId);
Contradiction.FaceId = FaceId;
Contradiction.ContradictionKind = TEXT("conflicting-face");
Contradiction.ObservationId = FaceState.LatestObservationId;
Contradiction.RevisionCount = FaceState.RevisionCount;
Contradiction.StatusLine =
FString::Printf(TEXT("Face %s has conflicting sticker votes."), *FaceId);
Contradiction.DetailLine =
TEXT("At least one sticker vote disagrees with the current winner. Re-scan the face or review the manual cube edit before solve guidance continues.");
Contradiction.bBlocksSolveExplanation = true;
Contradiction.bManualReviewPreferred = bHasCompleteNet;
for (const FHyperTwistVisionReconstructionStickerVote& Vote : FaceState.StickerVotes)
{
if (Vote.bHadConflict)
{
Contradiction.AffectedStickerIndices.Add(Vote.GridIndex);
}
}
Contradictions.Add(Contradiction);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("conflicting-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan face %s before capture advances."), *FaceId);
Target.DetailLine = bHasCompleteNet
? TEXT("Conflicting sticker votes survived the bounded reconstruction pass. Use cube edit or a targeted re-scan to close the contradiction.")
: TEXT("Conflicting sticker votes are present while capture is still open. Re-scan the face before committing the remaining sides.");
Target.PriorityOrdinal = 0;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
if (FaceState.RevisionCount > 0)
{
FHyperTwistVisionCorrectionContradiction Contradiction;
Contradiction.ContradictionId = FString::Printf(TEXT("revision-%s"), *FaceId);
Contradiction.FaceId = FaceId;
Contradiction.ContradictionKind = TEXT("manual-revision");
Contradiction.ObservationId = FaceState.LatestObservationId;
Contradiction.RevisionCount = FaceState.RevisionCount;
Contradiction.StatusLine =
FString::Printf(TEXT("Face %s was recommitted and needs review."), *FaceId);
Contradiction.DetailLine = FString::Printf(
TEXT("The latest bounded reconstruction includes %d revision(s) on face %s. Preserve the latest capture or review the cube edit before solve guidance continues."),
FaceState.RevisionCount,
*FaceId
);
Contradiction.bBlocksSolveExplanation = true;
Contradiction.bManualReviewPreferred = bHasCompleteNet;
Contradictions.Add(Contradiction);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("revised-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review the revised face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan revised face %s before capture advances."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Face %s has already been recommitted %d time(s) in this bounded session."),
*FaceId,
FaceState.RevisionCount
);
Target.PriorityOrdinal = 1;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
const float FaceConfidence = CalculateCommittedFaceConfidence(FaceState);
if (FaceConfidence > 0.0f && FaceConfidence < LowConfidenceThreshold)
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("low-confidence-face");
Target.RecommendedActionId = bHasCompleteNet
? TEXT("apply-manual-corrections")
: TEXT("rescan-target-face");
Target.StatusLine = bHasCompleteNet
? FString::Printf(TEXT("Review low-confidence face %s before solve guidance begins."), *FaceId)
: FString::Printf(TEXT("Re-scan low-confidence face %s before capture advances."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Average sticker confidence %.2f on face %s is below the bounded correction threshold %.2f."),
FaceConfidence,
*FaceId,
LowConfidenceThreshold
);
Target.PriorityOrdinal = 2;
Target.bManualReviewPreferred = bHasCompleteNet;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
for (const FString& MissingFaceId : ReconstructionSession.MissingFaces)
{
const FString FaceId = NormalizeRecognitionFaceId(MissingFaceId);
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FaceId;
Target.ReasonId = TEXT("missing-face");
Target.RecommendedActionId = TEXT("capture-target-face");
Target.StatusLine =
FString::Printf(TEXT("Capture face %s to continue the bounded reconstruction pass."), *FaceId);
Target.DetailLine = FString::Printf(
TEXT("Commit the next missing face. %d face(s) still need capture before solve guidance unlocks."),
ReconstructionSession.MissingFaces.Num()
);
Target.PriorityOrdinal = 3;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
if (PendingTargets.Num() == 0)
{
const FString FallbackFaceId = !ObservedFace.FaceId.IsEmpty()
? NormalizeRecognitionFaceId(ObservedFace.FaceId)
: TEXT("F");
if (Warnings.Num() > 0)
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FallbackFaceId;
Target.ReasonId = TEXT("warning-restart");
Target.RecommendedActionId = TEXT("restart-browser-session");
Target.StatusLine = TEXT("Restart the capture stage before correction continues.");
Target.DetailLine = FString::Printf(
TEXT("The bounded correction shell reported: %s"),
*FString::Join(Warnings, TEXT(" | "))
);
Target.PriorityOrdinal = 0;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
else if (!ReconstructionSession.IsStructurallyValid())
{
FHyperTwistVisionCorrectionTarget Target;
Target.FaceId = FallbackFaceId;
Target.ReasonId = TEXT("capture-bootstrap");
Target.RecommendedActionId = TEXT("capture-target-face");
Target.StatusLine =
FString::Printf(TEXT("Capture face %s to start bounded correction routing."), *FallbackFaceId);
Target.DetailLine =
TEXT("No committed reconstruction session is available yet. Capture the first face before correction or solve guidance can advance.");
Target.PriorityOrdinal = 3;
Target.bManualReviewPreferred = false;
UpsertCorrectionTarget(PendingTargets, Target);
}
}
PendingTargets.Sort(
[](const FHyperTwistVisionCorrectionTarget& Left, const FHyperTwistVisionCorrectionTarget& Right)
{
if (Left.PriorityOrdinal != Right.PriorityOrdinal)
{
return Left.PriorityOrdinal < Right.PriorityOrdinal;
}
return GetClassicRecognitionFaceOrderIndex(Left.FaceId)
< GetClassicRecognitionFaceOrderIndex(Right.FaceId);
}
);
Contradictions.Sort(
[](const FHyperTwistVisionCorrectionContradiction& Left, const FHyperTwistVisionCorrectionContradiction& Right)
{
return GetClassicRecognitionFaceOrderIndex(Left.FaceId)
< GetClassicRecognitionFaceOrderIndex(Right.FaceId);
}
);
CorrectionState.PendingTargets = PendingTargets;
CorrectionState.Contradictions = Contradictions;
CorrectionState.ContradictionCount = Contradictions.Num();
CorrectionState.bCorrectionRequired = PendingTargets.Num() > 0;
CorrectionState.bManualReviewRequired = Contradictions.Num() > 0;
CorrectionState.bCorrectionComplete =
ReconstructionSession.bHasCompleteClassicCubeNet && !CorrectionState.bCorrectionRequired;
CorrectionState.bSolveExplanationBlocked = !CorrectionState.bCorrectionComplete;
if (CorrectionState.bCorrectionRequired)
{
CorrectionState.ActiveTarget = PendingTargets[0];
CorrectionState.ActiveTargetFaceId = CorrectionState.ActiveTarget.FaceId;
CorrectionState.RecommendationStatusLine = CorrectionState.ActiveTarget.StatusLine;
CorrectionState.RecommendationDetailLine = CorrectionState.ActiveTarget.DetailLine;
CorrectionState.NextRecommendedActionId = CorrectionState.ActiveTarget.RecommendedActionId;
CorrectionState.bTargetedRescanVisible =
CorrectionState.ActiveTarget.RecommendedActionId == TEXT("capture-target-face")
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("rescan-target-face");
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit
&& (ReconstructionSession.bHasCompleteClassicCubeNet
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("apply-manual-corrections"));
CorrectionState.bStageRestartVisible =
CorrectionProfile.bSupportsStageRestart
&& (Warnings.Num() > 0
|| CorrectionState.ActiveTarget.RecommendedActionId == TEXT("restart-browser-session"));
CorrectionState.bManualReviewRequired =
CorrectionState.bManualReviewRequired || CorrectionState.ActiveTarget.bManualReviewPreferred;
CorrectionState.RecommendedStageId =
CorrectionState.ActiveTarget.RecommendedActionId == TEXT("apply-manual-corrections")
? TEXT("cube-edit")
: TEXT("video-stream");
}
else
{
CorrectionState.RecommendedStageId = TEXT("orient-centers");
CorrectionState.RecommendationStatusLine =
TEXT("Correction is clear; solve guidance can begin.");
CorrectionState.RecommendationDetailLine =
TEXT("No missing faces, contradictions, or low-confidence targets remain in the bounded correction shell.");
CorrectionState.NextRecommendedActionId = TEXT("start-solve-explanation");
CorrectionState.bTargetedRescanVisible = false;
CorrectionState.bManualCubeEditVisible =
CorrectionProfile.bSupportsManualCubeEdit && ReconstructionSession.bHasCompleteClassicCubeNet;
}
return CorrectionState;
}
FHyperTwistVisionSolveExplanationState BuildClassicCubeSolveExplanationState(
const FHyperTwistVisionSessionConfig& SessionConfig,
const FHyperTwistVisionReconstructionSession& ReconstructionSession
const FHyperTwistVisionReconstructionSession& ReconstructionSession,
const FHyperTwistVisionCorrectionState& CorrectionState
)
{
FHyperTwistVisionSolveExplanationState SolveExplanationState;
@ -2715,10 +3062,9 @@ namespace HyperTwistTrainingSubsystemInternal
SolveExplanationState.bPlaybackDeferred = !SolveExplanationProfile.bSupportsPlaybackStage;
SolveExplanationState.bRequiresFullReconstruction = true;
const int32 RemainingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
if (ReconstructionSession.bHasCompleteClassicCubeNet)
if (ReconstructionSession.bHasCompleteClassicCubeNet
&& CorrectionState.IsStructurallyValid()
&& CorrectionState.bCorrectionComplete)
{
SolveExplanationState.RecommendedStageId =
SolveExplanationProfile.Steps.Num() > 0
@ -2734,8 +3080,30 @@ namespace HyperTwistTrainingSubsystemInternal
SolveExplanationState.bNextStepVisible = false;
SolveExplanationState.bPreviousStepVisible = false;
}
else if (ReconstructionSession.bHasCompleteClassicCubeNet
&& CorrectionState.IsStructurallyValid()
&& CorrectionState.bCorrectionRequired)
{
SolveExplanationState.RecommendedStageId = !CorrectionState.RecommendedStageId.IsEmpty()
? CorrectionState.RecommendedStageId
: TEXT("video-stream");
SolveExplanationState.RecommendationStatusLine =
TEXT("Solve guidance stays blocked until correction closes.");
SolveExplanationState.RecommendationDetailLine =
!CorrectionState.RecommendationDetailLine.IsEmpty()
? CorrectionState.RecommendationDetailLine
: TEXT("Finish the bounded correction shell before solve guidance unlocks.");
SolveExplanationState.CurrentStepOrdinal = -1;
SolveExplanationState.bReadyForExplanation = false;
SolveExplanationState.bStartExplanationVisible = false;
SolveExplanationState.bNextStepVisible = false;
SolveExplanationState.bPreviousStepVisible = false;
}
else
{
const int32 RemainingFaceCount = ReconstructionSession.IsStructurallyValid()
? ReconstructionSession.MissingFaces.Num()
: 6;
SolveExplanationState.RecommendedStageId = TEXT("capture-remaining-faces");
SolveExplanationState.RecommendationStatusLine =
TEXT("Solve guidance unlocks after capture completes.");
@ -7187,10 +7555,18 @@ FHyperTwistVisionCommitResult UHyperTwistTrainingSubsystem::CommitActiveRecognit
Result.ReconstructionSession,
Result.ObservedFace
);
Result.CorrectionState =
HyperTwistTrainingSubsystemInternal::BuildClassicCubeCorrectionState(
ActiveRecognitionSessionState.SessionConfig,
Result.ReconstructionSession,
Result.ObservedFace,
Result.Warnings
);
Result.SolveExplanationState =
HyperTwistTrainingSubsystemInternal::BuildClassicCubeSolveExplanationState(
ActiveRecognitionSessionState.SessionConfig,
Result.ReconstructionSession
Result.ReconstructionSession,
Result.CorrectionState
);
}
@ -8186,6 +8562,28 @@ FHyperTwistVisionSessionConfig UHyperTwistTrainingSubsystem::BuildActiveRecognit
SessionConfig.SolveExplanationProfileDefinition.ExplanationProfileId =
SessionConfig.SolveExplanationProfileId;
}
if (SessionConfig.CorrectionProfileId.IsEmpty()
&& SessionConfig.CorrectionProfileDefinition.IsStructurallyValid())
{
SessionConfig.CorrectionProfileId =
SessionConfig.CorrectionProfileDefinition.CorrectionProfileId;
}
if (SessionConfig.CorrectionProfileId.IsEmpty())
{
SessionConfig.CorrectionProfileId = TEXT("classic-cube-correction-shell-v1");
}
if (!SessionConfig.CorrectionProfileDefinition.IsStructurallyValid())
{
SessionConfig.CorrectionProfileDefinition =
HyperTwistTrainingSubsystemInternal::BuildDefaultClassicCubeCorrectionProfile(
SessionConfig.CorrectionProfileId
);
}
if (SessionConfig.CorrectionProfileDefinition.CorrectionProfileId.IsEmpty())
{
SessionConfig.CorrectionProfileDefinition.CorrectionProfileId =
SessionConfig.CorrectionProfileId;
}
}
return SessionConfig;

View file

@ -807,6 +807,232 @@ struct FHyperTwistVisionSolveExplanationState
}
};
USTRUCT(BlueprintType)
struct FHyperTwistVisionCorrectionTarget
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FaceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ReasonId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecommendedActionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PriorityOrdinal = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bManualReviewPreferred = false;
bool IsStructurallyValid() const
{
return !FaceId.IsEmpty()
&& !ReasonId.IsEmpty()
&& !RecommendedActionId.IsEmpty()
&& PriorityOrdinal >= 0;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistVisionCorrectionContradiction
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ContradictionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString FaceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ContradictionKind;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ObservationId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevisionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<int32> AffectedStickerIndices;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bBlocksSolveExplanation = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bManualReviewPreferred = false;
bool IsStructurallyValid() const
{
return !ContradictionId.IsEmpty()
&& !FaceId.IsEmpty()
&& !ContradictionKind.IsEmpty()
&& RevisionCount >= 0;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistVisionCorrectionProfile
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CorrectionProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PuzzleId = TEXT("cube/3x3x3");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CorrectionMode = TEXT("bounded-multi-face-correction-v1");
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsTargetedRescan = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsManualCubeEdit = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSupportsStageRestart = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistVisionShellActionBinding> ActionBindings;
bool IsStructurallyValid() const
{
if (CorrectionProfileId.IsEmpty()
|| PuzzleId.IsEmpty()
|| CorrectionMode.IsEmpty()
|| ActionBindings.Num() <= 0)
{
return false;
}
for (const FHyperTwistVisionShellActionBinding& ActionBinding : ActionBindings)
{
if (!ActionBinding.IsStructurallyValid())
{
return false;
}
}
return true;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistVisionCorrectionState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CorrectionProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecommendedStageId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecommendationStatusLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RecommendationDetailLine;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString NextRecommendedActionId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ActiveTargetFaceId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 MissingFaceCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ContradictionCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistVisionCorrectionTarget ActiveTarget;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistVisionCorrectionTarget> PendingTargets;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistVisionCorrectionContradiction> Contradictions;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCorrectionRequired = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bManualReviewRequired = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bTargetedRescanVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bManualCubeEditVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bStageRestartVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bSolveExplanationBlocked = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bCorrectionComplete = false;
bool IsStructurallyValid() const
{
if (CorrectionProfileId.IsEmpty()
|| MissingFaceCount < 0
|| ContradictionCount < 0
|| ContradictionCount != Contradictions.Num())
{
return false;
}
if (bCorrectionRequired)
{
if (ActiveTargetFaceId.IsEmpty()
|| !ActiveTarget.IsStructurallyValid()
|| ActiveTargetFaceId != ActiveTarget.FaceId)
{
return false;
}
}
for (const FHyperTwistVisionCorrectionTarget& Target : PendingTargets)
{
if (!Target.IsStructurallyValid())
{
return false;
}
}
for (const FHyperTwistVisionCorrectionContradiction& Contradiction : Contradictions)
{
if (!Contradiction.IsStructurallyValid())
{
return false;
}
}
return true;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistVisionSessionConfig
{
@ -854,6 +1080,12 @@ struct FHyperTwistVisionSessionConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistVisionSolveExplanationProfile SolveExplanationProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CorrectionProfileId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistVisionCorrectionProfile CorrectionProfileDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FIntPoint FrameSize = FIntPoint(1280, 720);
@ -980,6 +1212,9 @@ struct FHyperTwistVisionCommitResult
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistVisionSolveExplanationState SolveExplanationState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistVisionCorrectionState CorrectionState;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> Warnings;

View file

@ -0,0 +1,241 @@
// 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 HyperTwistClassicCubePhase6RRTestInternal
{
FHyperTwistTrainingDeck MakeRecognitionDeck()
{
FHyperTwistTrainingDeck Deck;
Deck.DeckId = TEXT("phase6r-r/classic-cube-correction-shell");
Deck.Title = TEXT("Phase 6R-R Classic Cube Correction Shell");
Deck.DeliveryModes = {
EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
FHyperTwistTrainingCase TrainingCase;
TrainingCase.CaseId = TEXT("phase6r-r-case");
TrainingCase.PuzzleId = TEXT("cube/3x3x3");
TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
TrainingCase.PromptLabel = TEXT("Phase 6R-R Recognition");
TrainingCase.AllowedDeliveryModes = {
EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
Deck.Cases = {TrainingCase};
return Deck;
}
void ForceMockRecognitionClient(UHyperTwistTrainingSubsystem* TrainingSubsystem)
{
if (TrainingSubsystem == nullptr)
{
return;
}
if (FStrProperty* RecognitionClientKindProperty = FindFProperty<FStrProperty>(
UHyperTwistTrainingSubsystem::StaticClass(),
TEXT("RecognitionClientKind")
))
{
RecognitionClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, TEXT("mock"));
}
}
UHyperTwistTrainingSubsystem* MakeRecognitionSubsystem(const FString& SessionId)
{
UGameInstance* GameInstance = NewObject<UGameInstance>(GetTransientPackage());
if (GameInstance == nullptr)
{
return nullptr;
}
UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
if (TrainingSubsystem == nullptr)
{
return nullptr;
}
ForceMockRecognitionClient(TrainingSubsystem);
const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
MakeRecognitionDeck(),
TEXT("phase6r-r-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);
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistClassicCubePhase6RRCorrectionSessionConfigTest,
"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionSessionConfig",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistClassicCubePhase6RRCorrectionSessionConfigTest::RunTest(const FString& Parameters)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistClassicCubePhase6RRTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-r-config-session"));
TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-R."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
FString OpenError;
TestTrue(TEXT("The recognition session must open for the correction shell route."), TrainingSubsystem->OpenActiveRecognitionSession(OpenError));
TestTrue(TEXT("Opening the recognition session must not report an error."), OpenError.IsEmpty());
const FHyperTwistTrainingRecognitionSessionState SessionState =
TrainingSubsystem->GetActiveRecognitionSessionState();
TestEqual(
TEXT("The correction shell route must normalize the first-party correction profile id."),
SessionState.SessionConfig.CorrectionProfileId,
TEXT("classic-cube-correction-shell-v1")
);
TestTrue(
TEXT("The correction shell route must provide a structurally valid correction profile definition."),
SessionState.SessionConfig.CorrectionProfileDefinition.IsStructurallyValid()
);
TestEqual(
TEXT("The correction shell route must retain four bounded correction actions."),
SessionState.SessionConfig.CorrectionProfileDefinition.ActionBindings.Num(),
4
);
TestTrue(
TEXT("The correction shell route must support targeted re-scan in this packet."),
SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsTargetedRescan
);
TestTrue(
TEXT("The correction shell route must support manual cube edit in this packet."),
SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsManualCubeEdit
);
TestTrue(
TEXT("The correction shell route must preserve explicit stage-restart support."),
SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsStageRestart
);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistClassicCubePhase6RRCorrectionPartialStateTest,
"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionPartialState",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistClassicCubePhase6RRCorrectionPartialStateTest::RunTest(const FString& Parameters)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistClassicCubePhase6RRTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-r-partial-session"));
TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-R."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
const FHyperTwistVisionCommitResult Result =
HyperTwistClassicCubePhase6RRTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 1);
TestTrue(TEXT("The partial correction route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
TestTrue(TEXT("The partial correction route must keep correction open before capture completes."), Result.CorrectionState.bCorrectionRequired);
TestFalse(TEXT("The partial correction route must not report any contradictions after a clean first face."), Result.CorrectionState.bManualReviewRequired);
TestEqual(TEXT("The partial correction route must target the first missing canonical face next."), Result.CorrectionState.ActiveTargetFaceId, TEXT("U"));
TestEqual(TEXT("The partial correction route must tag the active target as a missing-face correction."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("missing-face"));
TestEqual(TEXT("The partial correction route must route the next action back to capture."), Result.CorrectionState.NextRecommendedActionId, TEXT("capture-target-face"));
TestEqual(TEXT("The partial correction route must keep the correction stage in video-stream."), Result.CorrectionState.RecommendedStageId, TEXT("video-stream"));
TestEqual(TEXT("The partial correction route must report five faces still missing."), Result.CorrectionState.MissingFaceCount, 5);
TestEqual(TEXT("The partial correction route must report zero contradiction objects after the first clean face."), Result.CorrectionState.ContradictionCount, 0);
TestTrue(TEXT("The partial correction route must keep targeted re-scan or capture visible."), Result.CorrectionState.bTargetedRescanVisible);
TestFalse(TEXT("Solve guidance must remain blocked while correction stays open."), Result.SolveExplanationState.bReadyForExplanation);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistClassicCubePhase6RRCorrectionRevisionStateTest,
"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionRevisionState",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistClassicCubePhase6RRCorrectionRevisionStateTest::RunTest(const FString& Parameters)
{
UHyperTwistTrainingSubsystem* TrainingSubsystem =
HyperTwistClassicCubePhase6RRTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-r-revision-session"));
TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-R."), TrainingSubsystem);
if (TrainingSubsystem == nullptr)
{
return false;
}
const TArray<FString> FaceOrder = {
TEXT("U"),
TEXT("R"),
TEXT("F"),
TEXT("D"),
TEXT("L"),
TEXT("B")
};
FHyperTwistVisionCommitResult Result;
for (int32 FaceOrdinal = 0; FaceOrdinal < FaceOrder.Num(); ++FaceOrdinal)
{
Result = HyperTwistClassicCubePhase6RRTestInternal::CommitFace(
TrainingSubsystem,
FaceOrder[FaceOrdinal],
FaceOrdinal + 1
);
}
Result = HyperTwistClassicCubePhase6RRTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7);
const FHyperTwistTrainingRecognitionSessionState SessionState =
TrainingSubsystem->GetActiveRecognitionSessionState();
TestTrue(TEXT("The revision route must keep a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid());
TestTrue(TEXT("The revision route must keep the classic cube net complete after a face recommit."), Result.ReconstructionSession.bHasCompleteClassicCubeNet);
TestEqual(TEXT("The revision route must record exactly one bounded reconstruction revision."), Result.ReconstructionSession.TotalRevisionCount, 1);
TestTrue(TEXT("The revision route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
TestTrue(TEXT("The revision route must reopen correction after a face recommit."), Result.CorrectionState.bCorrectionRequired);
TestTrue(TEXT("The revision route must require manual review after a full-net recommit."), Result.CorrectionState.bManualReviewRequired);
TestTrue(TEXT("The revision route must expose manual cube edit once review is required."), Result.CorrectionState.bManualCubeEditVisible);
TestFalse(TEXT("The revision route must not report correction complete while review is still open."), Result.CorrectionState.bCorrectionComplete);
TestEqual(TEXT("The revision route must keep the recommitted face as the active correction target."), Result.CorrectionState.ActiveTargetFaceId, TEXT("F"));
TestEqual(TEXT("The revision route must classify the active correction as a revised-face target."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face"));
TestEqual(TEXT("The revision route must hand the next action to manual correction once the full net exists."), Result.CorrectionState.NextRecommendedActionId, TEXT("apply-manual-corrections"));
TestEqual(TEXT("The revision route must switch the correction stage into cube-edit."), Result.CorrectionState.RecommendedStageId, TEXT("cube-edit"));
TestEqual(TEXT("The revision route must surface one contradiction object for the recommit."), Result.CorrectionState.ContradictionCount, 1);
TestEqual(TEXT("The revision route must tag the contradiction as a manual revision."), Result.CorrectionState.Contradictions[0].ContradictionKind, TEXT("manual-revision"));
TestFalse(TEXT("Solve guidance must stay blocked until the correction review closes."), Result.SolveExplanationState.bReadyForExplanation);
TestFalse(TEXT("The solve explanation start action must stay hidden while correction is open."), Result.SolveExplanationState.bStartExplanationVisible);
TestEqual(TEXT("The solve explanation route must redirect back to cube-edit while correction is open."), Result.SolveExplanationState.RecommendedStageId, TEXT("cube-edit"));
TestEqual(TEXT("A bounded face recommit must still count as one correction event."), SessionState.CorrectionEventCount, 1);
return true;
}
#endif

View file

@ -157,8 +157,11 @@ Status update on `2026-05-21`:
solve explanation/recommendation packet is now landed in current code
- the generic source-backed `Phase 6R-R` shared classic-cube recognition multi-face
correction/explanation control pass is now consumed
- the current next bounded move is the bounded permissive `Phase 6R-R` classic-cube recognition
multi-face correction/explanation shell packet, not a new restrictive packet by default
- 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
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
@ -262,8 +265,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 the bounded permissive `Phase 6R-R` classic-cube recognition
multi-face correction/explanation shell packet
- the next bounded move is a source-backed follow-on control pass for the retained classic-cube
recognition correction/explanation shell remainder
Companion docs:
@ -752,10 +755,11 @@ 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`
- keep the following families deferred to later queue decisions:
- bundled-font redistribution and multilingual solve shell
- direct standalone multi-face reconstruction / correction / explanation
- only the shared `Phase 6R-R` capture-state adjunct remains live now
- direct standalone multilingual or asset-shipping widening beyond the landed shared correction
adjunct
- if later implementation copies bundled non-code assets, capture and preserve any asset-specific
license or replace the asset with a clearly redistributable alternative
@ -807,9 +811,11 @@ Approved working posture:
- 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
- keep the row only partially incorporated by default:
- multi-face correction or explanation shell beyond the bounded stage ladder is the queued
`Phase 6R-R` implementation slice
- 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

View file

@ -0,0 +1,169 @@
# HyperTwist Phase 6R-R classic-cube multi-face correction explanation implementation packet
Created on `2026-05-24`
## Status
- first-party HyperTwist packet
- bounded permissive `Phase 6R-R` implementation slice
## Purpose
This packet lands the current retained shared classic-cube recognition
correction/explanation family:
- first-party correction-profile, correction-target, contradiction, and
correction-explanation shell composition above the already landed `qbr`
webcam shell plus the already landed `rubix-cube-solver` reconstruction,
browser-shell, and bounded solve-explanation seams
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_R_CLASSIC_CUBE_MULTI_FACE_CORRECTION_EXPLANATION_PREPARATION_PACKET_2026-05-23.md`
The retained owner split stays explicit:
- `vivaansinghvi07/rubix-cube-solver`
- retained primary owner for correction/explanation semantics, revision
review, contradiction visibility, and manual-correction routing
- `kkoomen/qbr`
- retained adjunct owner for capture-state continuity, target-face re-scan
anchoring, preview/snapshot discipline, and the shared correction shell's
live recognition 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 shell through:
- first-party `HyperTwistRecognition` contract types for:
- correction target definition
- contradiction object definition
- correction profile definition
- correction session-state readout
- correction profile id on recognition session config
- commit-result correction payloads
- recognition session config defaults for:
- first-party classic-cube correction profile id
- correction profile definition with explicit bounded action bindings
- first-party commit-result shaping for:
- missing-face correction routing
- conflicting-face contradiction objects
- recommitted-face contradiction objects
- low-confidence face targeting
- targeted re-scan, manual cube edit, and stage-restart visibility
- explicit correction-complete versus solve-ready gating
- solve-explanation blocking and stage redirect once a full net exists but
correction remains open
- sample contract routing in:
- `UHyperTwistContractLibrary`
- active subsystem normalization in:
- `UHyperTwistTrainingSubsystem`
- focused automation coverage in:
- `HyperTwistClassicCubePhase6RRCorrectionContractTest.cpp`
## Why this is still intentionally bounded
This packet lands the first bounded correction-state and
correction-explanation shell 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-R-ClassicCube-Verify.log -ReportExportPath=C:\HyperTwist\UnrealHyperTwist\Saved\AutomationReports\Phase6R-R-ClassicCube-Verify -ExecCmds="Automation RunTests HyperTwist.Permissive.ClassicCube.Phase6R.R; Quit" -TestExit="Automation Test Queue Empty"`
Regression automation validation:
- `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:
- `CorrectionSessionConfig`
- `CorrectionPartialState`
- `CorrectionRevisionState`
- existing `rubix-cube-solver`, `qbr`, and `CubeDesk` regression suites
## Queue effect
This packet consumes the current bounded `Phase 6R-R` 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
- still deferred:
- retained correction/explanation shell remainder beyond the landed
correction-state slice
- bundled `twistysim.min.js` redistribution
The next clean 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`
Keep the future sequencing guards 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
- 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
- do not widen into broad playback-runtime or generalized solver-backend
ownership unless a narrower first-party gap is proven above the landed
correction shell

View file

@ -6,7 +6,8 @@ Created on `2026-05-23`
- historical consumed preparation authority
- bounded post-`Phase 6R-Q` control slice
- the first bounded implementation slice now lands separately after this packet
- the first bounded implementation slice now lands in:
- `docs/arch/HYPERTWIST_PHASE6R_R_CLASSIC_CUBE_MULTI_FACE_CORRECTION_EXPLANATION_IMPLEMENTATION_PACKET_2026-05-24.md`
## Purpose

View file

@ -106,13 +106,15 @@ The next bounded move is now:
preset-backed overrideable context-assembly profiles rather than a second memory system
9. the generic source-backed `Phase 6R-R` shared classic-cube recognition multi-face
correction/explanation control pass is now consumed
10. the next bounded move is the bounded permissive `Phase 6R-R` classic-cube recognition
multi-face correction/explanation shell packet shared across `kkoomen/qbr` and
`vivaansinghvi07/rubix-cube-solver`
11. keep the `rubix-cube-solver` guard visible:
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:
- 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
12. keep the `qbr` guard visible:
13. 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

View file

@ -151,7 +151,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 | Deep-source grounded retained | `rubix-cube-solver` with `qbr` capture-state adjunct value | Shared retained shell is source-backed, but keep it above the landed `Phase 6R-O`, `Phase 6R-P`, and `Phase 6R-Q` seams and keep both bundled-asset guards 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. |
### 3. Training, coaching, and progression cockpit

View file

@ -133,9 +133,11 @@ Canonical discovery surfaces for roadmap interpretation:
preset-backed overrideable context-assembly profiles rather than a second memory system
- the generic source-backed `Phase 6R-R` shared classic-cube recognition multi-face
correction/explanation control pass is now consumed
- the current next bounded move is the bounded permissive `Phase 6R-R` classic-cube recognition
multi-face correction/explanation shell packet, while keeping both the `twistysim.min.js` and
`arial-unicode-ms.ttf` redistribution guards explicit
- 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 repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than
waiting on another first-party packet
@ -197,11 +199,11 @@ 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 the bounded permissive `Phase 6R-R` classic-cube recognition
multi-face correction/explanation shell packet shared across `kkoomen/qbr` and
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`.
Queue interpretation after that packet:
Queue interpretation after that control pass:
- then continue with the retained repo-row queue
- highest current repo-row queue pressure sits in:
@ -221,10 +223,9 @@ Queue interpretation after that packet:
- classic-cube recognition calibration contract
- ordered face-observation contract
- classic-cube webcam UI shell
- shared correction-target capture-state and re-scan adjunct
- still deferred:
- bundled-font redistribution and multilingual solve shell
- no standalone correction packet remains; the surviving adjacent value is
the shared `Phase 6R-R` capture-state adjunct
- `roice3/Magic120Cell` remains a partially landed row rather than a closed row:
- landed:
- dedicated `120-cell` family runtime profile
@ -254,10 +255,14 @@ Queue interpretation after that packet:
- 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
- still deferred:
- multi-face correction or explanation shell beyond the bounded stage ladder
- retained correction/explanation shell remainder beyond the landed
correction-state slice
- bundled `twistysim.min.js` redistribution
- after the `Phase 6R-Q` implementation packet, keep the future legal sequencing guards visible:
- after the landed `Phase 6R-R` 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
its upstream provenance/license in any future widening that would ship that asset
@ -267,7 +272,7 @@ Queue interpretation after that packet:
- `Phase 6R-R`
- 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
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
@ -332,11 +337,11 @@ Queue interpretation after that packet:
- viseme / gesture runtime integration
- broad assistant-platform scope
- the next queue shape is now:
- retained classic-cube recognition multi-face correction/explanation shell remainder
- the next bounded packet should stay narrow:
- first bounded `Phase 6R-R` correction-state and correction-explanation shell widening before
bundled `twistysim.min.js` redistribution, generalized solver backend ownership, or any `qbr`
multilingual/font-shipping path
- retained classic-cube recognition correction/explanation shell remainder 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
- 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: