Harden replay scene context projection ownership

This commit is contained in:
axiomlogicnexus 2026-06-25 05:18:40 +00:00
parent 89259aeb1c
commit 47ee4980ed
4 changed files with 72 additions and 1 deletions

View file

@ -133,6 +133,26 @@ namespace HyperTwistPuzzleViewerComponentInternal
return Result;
}
FHyperTwistProjectionSettings BuildReplayProjectionSettings(
const FHyperTwistPuzzleDefinitionRef& PuzzleDefinition
)
{
FHyperTwistProjectionSettings Settings;
if (PuzzleDefinition.PuzzleFamily == EHyperTwistPuzzleFamily::Hypercube
|| PuzzleDefinition.Dimension > 3)
{
Settings.ProjectionKind = EHyperTwistProjectionKind::HyperProjection;
Settings.ProjectionProfile = TEXT("hyper-default");
Settings.ProjectionDepth = 2.0f;
return Settings;
}
Settings.ProjectionKind = EHyperTwistProjectionKind::ClassicPerspective;
Settings.ProjectionProfile = TEXT("classic-default");
Settings.ProjectionDepth = 1.0f;
return Settings;
}
int32 ResolvePlaybackTerminalTimeMs(
const TArray<int32>& MoveBoundaryTimesMs,
const FHyperTwistDerivedReplaySummary& ReplaySummary,
@ -170,6 +190,7 @@ namespace HyperTwistPuzzleViewerComponentInternal
? FString::Printf(TEXT("replay/%s"), *ReplayPacket.ReplayId)
: Snapshot.SnapshotId;
OutContext.PuzzleState = Snapshot.State;
OutContext.ProjectionSettings = BuildReplayProjectionSettings(ReplayPacket.PuzzleDefinition);
OutContext.RenderStateProfile = ReplayPacket.PuzzleDefinition.PuzzleFamily == EHyperTwistPuzzleFamily::ClassicCube
? TEXT("classic-replay")
: TEXT("replay-default");

View file

@ -25,6 +25,11 @@ struct FHyperTwistProjectionSettings
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
float ProjectionDepth = 1.0f;
bool IsStructurallyValid() const
{
return !ProjectionProfile.IsEmpty() && ProjectionDepth > 0.0f;
}
};
USTRUCT(BlueprintType)
@ -46,6 +51,9 @@ struct FHyperTwistSimulationSceneContext
bool IsStructurallyValid() const
{
return !SceneContextId.IsEmpty() && PuzzleState.IsStructurallyValid() && !RenderStateProfile.IsEmpty();
return !SceneContextId.IsEmpty()
&& PuzzleState.IsStructurallyValid()
&& ProjectionSettings.IsStructurallyValid()
&& !RenderStateProfile.IsEmpty();
}
};

View file

@ -440,6 +440,27 @@ bool FHyperTwistClassicCubeReplayViewerLoadTest::RunTest(const FString& Paramete
const FHyperTwistViewerPlaybackState PlaybackState = ViewerComponent->GetPlaybackState();
TestTrue(TEXT("The viewer must mark the replay as loaded."), PlaybackState.bReplayLoaded);
TestTrue(TEXT("The viewer must materialize scene context from the replay snapshot."), PlaybackState.bSceneContextLoaded);
const FHyperTwistSimulationSceneContext ReplaySceneContext = ViewerComponent->GetSceneContext();
TestTrue(TEXT("The classic-cube replay scene context must stay structurally valid."), ReplaySceneContext.IsStructurallyValid());
TestEqual(
TEXT("The classic-cube replay scene context must use the generic classic projection kind."),
ReplaySceneContext.ProjectionSettings.ProjectionKind,
EHyperTwistProjectionKind::ClassicPerspective
);
TestEqual(
TEXT("The classic-cube replay scene context must use the generic classic projection profile."),
ReplaySceneContext.ProjectionSettings.ProjectionProfile,
FString(TEXT("classic-default"))
);
TestTrue(
TEXT("The classic-cube replay scene context must preserve a positive generic projection depth."),
FMath::IsNearlyEqual(ReplaySceneContext.ProjectionSettings.ProjectionDepth, 1.0f)
);
TestEqual(
TEXT("The classic-cube replay scene context must keep the bounded classic replay render-state profile."),
ReplaySceneContext.RenderStateProfile,
FString(TEXT("classic-replay"))
);
TestEqual(TEXT("The viewer must reconstruct the expected move count."), PlaybackState.TotalMoveCount, 10);
TestEqual(TEXT("The viewer must retain the replay id after load."), PlaybackState.ReplayId, Packet.ReplayId);
return true;

View file

@ -120,6 +120,27 @@ bool FHyperTwistTwistyBound1ReplayShellTest::RunTest(const FString& Parameters)
TestEqual(TEXT("Replay load must initialize current time to the start boundary."), InitialState.CurrentTimeMs, 0);
TestEqual(TEXT("Replay load must derive total replay time from replay events."), InitialState.TotalTimeMs, 900);
TestTrue(TEXT("Replay load should derive a scene context from a snapshot event when available."), InitialState.bSceneContextLoaded);
const FHyperTwistSimulationSceneContext ReplaySceneContext = Viewer->GetSceneContext();
TestTrue(TEXT("Replay-backed scene context must stay structurally valid."), ReplaySceneContext.IsStructurallyValid());
TestEqual(
TEXT("Hypercube replay scene context should use the generic hyper projection kind."),
ReplaySceneContext.ProjectionSettings.ProjectionKind,
EHyperTwistProjectionKind::HyperProjection
);
TestEqual(
TEXT("Hypercube replay scene context should use the generic hyper projection profile."),
ReplaySceneContext.ProjectionSettings.ProjectionProfile,
FString(TEXT("hyper-default"))
);
TestTrue(
TEXT("Hypercube replay scene context should preserve a positive generic projection depth."),
FMath::IsNearlyEqual(ReplaySceneContext.ProjectionSettings.ProjectionDepth, 2.0f)
);
TestEqual(
TEXT("Hypercube replay scene context should keep the bounded generic replay render-state profile."),
ReplaySceneContext.RenderStateProfile,
FString(TEXT("replay-default"))
);
TestEqual(TEXT("Initial replay-backed scrubber position must stay at the start boundary."), UHyperTwistViewerLibrary::GetScrubberNormalizedPosition(InitialState), 0.0f);
TestEqual(TEXT("Timeline label must reflect replay-backed duration."), UHyperTwistViewerLibrary::GetTimelineLabel(InitialState), FString(TEXT("0.00s / 0.90s")));
TestEqual(TEXT("Replay summary must count move events."), Viewer->GetReplaySummary().MoveCount, 2);