Guard replay scene context against mismatched snapshots

This commit is contained in:
axiomlogicnexus 2026-06-25 05:26:59 +00:00
parent 47ee4980ed
commit fac15cba51
2 changed files with 90 additions and 2 deletions

View file

@ -186,6 +186,18 @@ namespace HyperTwistPuzzleViewerComponentInternal
continue;
}
const FHyperTwistPuzzleDefinitionRef& PacketDefinition = ReplayPacket.PuzzleDefinition;
const FHyperTwistPuzzleDefinitionRef& SnapshotDefinition = Snapshot.State.Definition;
const bool bDefinitionMatchesPacket =
PacketDefinition.PuzzleId == SnapshotDefinition.PuzzleId
&& PacketDefinition.PuzzleFamily == SnapshotDefinition.PuzzleFamily
&& PacketDefinition.Dimension == SnapshotDefinition.Dimension
&& PacketDefinition.NotationProfile == SnapshotDefinition.NotationProfile;
if (!bDefinitionMatchesPacket)
{
continue;
}
OutContext.SceneContextId = !ReplayPacket.ReplayId.IsEmpty()
? FString::Printf(TEXT("replay/%s"), *ReplayPacket.ReplayId)
: Snapshot.SnapshotId;
@ -662,8 +674,7 @@ bool UHyperTwistPuzzleViewerComponent::LoadReplayPacket(const FHyperTwistReplayP
{
SceneContext = ReplaySceneContext;
}
else if (!SceneContext.IsStructurallyValid()
|| SceneContext.PuzzleState.Definition.PuzzleId != ReplayPacket.PuzzleDefinition.PuzzleId)
else
{
SceneContext = FHyperTwistSimulationSceneContext();
}

View file

@ -208,4 +208,81 @@ bool FHyperTwistTwistyBound1ReplayShellTest::RunTest(const FString& Parameters)
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FHyperTwistTwistyBound1ReplaySceneContextMismatchGuardTest,
"HyperTwist.CleanRoom.TwistyJs.Bound1.ReplaySceneContextMismatchGuard",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FHyperTwistTwistyBound1ReplaySceneContextMismatchGuardTest::RunTest(const FString& Parameters)
{
static_cast<void>(Parameters);
UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
if (Viewer == nullptr)
{
return false;
}
FHyperTwistReplayPacket ReplayPacket;
ReplayPacket.ReplayId = TEXT("replay-bound1-mismatch");
ReplayPacket.SessionId = TEXT("session-bound1-mismatch");
ReplayPacket.PuzzleDefinition = UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition();
ReplayPacket.CaptureMode = EHyperTwistCaptureMode::Imported;
FHyperTwistStateSnapshot Snapshot;
Snapshot.SnapshotId = TEXT("snapshot-mismatch");
Snapshot.State = UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
Snapshot.DerivedHash = TEXT("snapshot-mismatch-hash");
ReplayPacket.Events = {
HyperTwistTwistyBound1ReplayShellTestInternal::MakeReplayEvent(
TEXT("evt-001"),
1,
0,
EHyperTwistReplayEventType::StateSnapshot,
HyperTwistTwistyBound1ReplayShellTestInternal::SerializeStructToJson(Snapshot)
),
HyperTwistTwistyBound1ReplayShellTestInternal::MakeReplayEvent(
TEXT("evt-002"),
2,
100,
EHyperTwistReplayEventType::Move,
TEXT("{\"notation\":\"R\",\"transformationRef\":\"classic/r\",\"source\":\"unit-test\"}")
)
};
TestTrue(
TEXT("The initial valid replay packet should populate a scene context before the mismatch case."),
Viewer->LoadReplayPacket(ReplayPacket)
);
TestTrue(
TEXT("The initial valid replay packet should mark the scene context as loaded."),
Viewer->GetPlaybackState().bSceneContextLoaded
);
Snapshot.State.Definition = UHyperTwistContractLibrary::MakeSampleClassicPuzzleDefinition();
ReplayPacket.Events[0].PayloadJson =
HyperTwistTwistyBound1ReplayShellTestInternal::SerializeStructToJson(Snapshot);
TestTrue(
TEXT("Replay packet load should still succeed when the packet remains structurally valid."),
Viewer->LoadReplayPacket(ReplayPacket)
);
const FHyperTwistViewerPlaybackState PlaybackState = Viewer->GetPlaybackState();
TestTrue(TEXT("Mismatch replay packets should still report the replay as loaded."), PlaybackState.bReplayLoaded);
TestFalse(
TEXT("Mismatch replay snapshots must not materialize a misleading scene context."),
PlaybackState.bSceneContextLoaded
);
TestFalse(
TEXT("Mismatch replay snapshots must clear any viewer scene-context state."),
Viewer->GetSceneContext().IsStructurallyValid()
);
return true;
}
#endif // WITH_AUTOMATION_TESTS