Implement Phase 6R-R correction shell
This commit is contained in:
parent
861947b07b
commit
dafaf397cc
10 changed files with 1506 additions and 43 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue