app.hypergames.hypersdk 1.11.0-beta.9 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/Editor/Scripts/Inspectors/HyperSDKSettingsEditor.cs +15 -0
  3. package/HyperLoader.unity +2 -2
  4. package/Runtime/Internal/Bootstrap/HyperLoader.cs +5 -1
  5. package/Runtime/Internal/Core/HyperRuntime.cs +63 -1
  6. package/Runtime/Internal/Managers/BackendManager.cs +26 -3
  7. package/Runtime/Internal/Managers/DataManager.cs +8 -0
  8. package/Runtime/Internal/Managers/EventManager.cs +7 -1
  9. package/Runtime/Internal/Managers/GameManager.cs +21 -13
  10. package/Runtime/Internal/Managers/ReplayManager.cs +7 -2
  11. package/Runtime/Internal/Managers/TimeManager.cs +30 -12
  12. package/Runtime/Internal/Replay/ReplaySessionState.cs +15 -4
  13. package/Runtime/Internal/ScriptableObjects/HyperSDKSettings.cs +10 -0
  14. package/Runtime/Internal/UI/Components/ReplayController.cs +205 -27
  15. package/Runtime/Internal/UI/Components/ReplayPresentationCanvas.cs +1 -1
  16. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs +18 -0
  17. package/Runtime/Internal/UI/Components/ReplaySeekOverlay.cs.meta +2 -0
  18. package/Runtime/Internal/UI/Modals/ReplayPausedModal.cs +9 -1
  19. package/Runtime/Internal/UI/Modals/StartingInstructionModal.cs +1 -1
  20. package/Runtime/Internal/UI/Modals/TimeUpModal.cs +2 -2
  21. package/Runtime/Public/Replay/ReplayPlaybackEngine.cs +71 -2
  22. package/Runtime/Resources/Fonts/Wonder Boys/Wonder Boys.asset +394 -379
  23. package/Runtime/Resources/HyperSDKSettings.asset +2 -0
  24. package/Runtime/Resources/Sprites/Circle Loader/Property 1=1.png.meta +7 -7
  25. package/Runtime/Resources/Sprites/Circle Loader/Property 1=2.png.meta +7 -7
  26. package/Runtime/Resources/Sprites/Circle Loader/Property 1=3.png.meta +7 -7
  27. package/Runtime/Resources/Sprites/Circle Loader/Property 1=4.png.meta +7 -7
  28. package/Runtime/Resources/Sprites/Circle Loader/Property 1=5.png.meta +7 -7
  29. package/Runtime/Resources/UIPrefabs/ReplayController.prefab +295 -15
  30. package/Runtime/Resources/UIPrefabs/ReplaySeekOverlay.prefab +291 -6
  31. package/Samples~/LaneRunner/Prefabs/DoubleScorePickup.prefab +1 -1
  32. package/Samples~/LaneRunner/Prefabs/Obstacle.prefab +1 -1
  33. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerSpawner.cs +1 -1
  34. package/Samples~/LaneRunner/Scripts/Gameplay/LaneRunnerTrackItem.cs +9 -2
  35. package/Samples~/LaneRunner/Scripts/Hyper/LaneRunnerTournamentAdapter.cs +1 -0
  36. package/package.json +1 -1
  37. /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs +0 -0
  38. /package/Runtime/{Public → Shared}/PlayerDeviceClassification.cs.meta +0 -0
@@ -103,14 +103,28 @@ namespace Hyper.Internal.Managers
103
103
  if (Mathf.Approximately(Time.timeScale, 0f))
104
104
  return;
105
105
 
106
- float gameTimerDelta = _gameManager != null && _gameManager.IsReplayMode
107
- ? Time.fixedDeltaTime
108
- : Time.fixedUnscaledDeltaTime;
106
+ float gameTimerDelta = GetGameTimerDelta(
107
+ _gameManager != null && _gameManager.IsReplayMode,
108
+ Time.fixedDeltaTime,
109
+ Time.timeScale);
109
110
 
110
111
  UpdatePracticeTimer(gameTimerDelta);
111
112
  UpdateBattleTimer(gameTimerDelta);
112
113
  }
113
114
 
115
+ internal static float GetGameTimerDelta(
116
+ bool isReplayMode,
117
+ float fixedDeltaTime,
118
+ float timeScale)
119
+ {
120
+ if (timeScale <= 0f) return 0f;
121
+
122
+ // The first FixedUpdate after a pause can report the whole paused
123
+ // interval in fixedUnscaledDeltaTime. Derive one live simulation
124
+ // step instead, keeping real-time duration at custom time scales.
125
+ return isReplayMode ? fixedDeltaTime : fixedDeltaTime / timeScale;
126
+ }
127
+
114
128
  public void Reset()
115
129
  {
116
130
  _practiceTimer = 0;
@@ -299,9 +313,9 @@ namespace Hyper.Internal.Managers
299
313
  }
300
314
 
301
315
  _battleTimer -= delta;
302
- if (_battleTimer <= 0)
316
+ if (_battleTimer <= 0f || Mathf.Approximately(_battleTimer, 0f))
303
317
  {
304
- _battleTimer = 0;
318
+ _battleTimer = 0f;
305
319
  if (!_timeUpTriggered)
306
320
  {
307
321
  QueueTimeUp();
@@ -314,7 +328,8 @@ namespace Hyper.Internal.Managers
314
328
  {
315
329
  _battleTimer = _sessionTimer;
316
330
 
317
- if (_battleTimer <= 0 && !_timeUpTriggered)
331
+ if ((_battleTimer <= 0f || Mathf.Approximately(_battleTimer, 0f)) &&
332
+ !_timeUpTriggered)
318
333
  {
319
334
  QueueTimeUp();
320
335
  }
@@ -324,6 +339,14 @@ namespace Hyper.Internal.Managers
324
339
 
325
340
  private void QueueTimeUp()
326
341
  {
342
+ if (_timeUpPending || _timeUpTriggered || _hasGameEnded) return;
343
+
344
+ _battleTimer = 0f;
345
+ _isBattleRunning = false;
346
+ ClearPausedGameplayTimerState();
347
+ // The SDK runs before gameplay FixedUpdate. The expiry step is blocked,
348
+ // even when several fixed steps run before the next rendered frame.
349
+ _gameManager.ReachGameplayCutoff();
327
350
  _timeUpPending = true;
328
351
  }
329
352
 
@@ -372,12 +395,7 @@ namespace Hyper.Internal.Managers
372
395
 
373
396
  if (_hasGameStarted && _gameManager.IsBattleMode)
374
397
  {
375
- _battleTimer = 0;
376
- if (!_timeUpTriggered)
377
- {
378
- _timeUpTriggered = true;
379
- _eventManager?.InvokeTimeUpEvent();
380
- }
398
+ QueueTimeUp();
381
399
  }
382
400
  else
383
401
  {
@@ -14,6 +14,7 @@ namespace Hyper.Internal
14
14
  public static string AvatarUrl { get; private set; }
15
15
  public static string EntryId { get; private set; }
16
16
  public static string ReplayNdjson { get; private set; }
17
+ public static int? FinalScore { get; private set; }
17
18
  public static Texture2D AvatarTexture { get; private set; }
18
19
  public static bool HasReplayData => !string.IsNullOrWhiteSpace(ReplayNdjson);
19
20
  public static long OriginalRandomSeed { get; private set; }
@@ -48,6 +49,7 @@ namespace Hyper.Internal
48
49
  AvatarUrl = avatarUrl ?? string.Empty;
49
50
  EntryId = entryId ?? string.Empty;
50
51
  ReplayNdjson = string.Empty;
52
+ FinalScore = null;
51
53
  ReleaseAvatarTexture();
52
54
  OriginalRandomSeed = originalRandomSeed;
53
55
  Progress01 = 0f;
@@ -61,13 +63,18 @@ namespace Hyper.Internal
61
63
  GameplayStartProgress01 = 0f;
62
64
  }
63
65
 
64
- public static void SetMetadata(string playerName, string avatarUrl, long originalRandomSeed)
66
+ public static void SetMetadata(
67
+ string playerName,
68
+ string avatarUrl,
69
+ long originalRandomSeed,
70
+ int finalScore)
65
71
  {
66
72
  if (!IsActive) return;
67
73
 
68
74
  PlayerName = playerName ?? string.Empty;
69
75
  AvatarUrl = avatarUrl ?? string.Empty;
70
76
  OriginalRandomSeed = originalRandomSeed;
77
+ FinalScore = finalScore;
71
78
  }
72
79
 
73
80
  public static void SetReplayData(string ndjson)
@@ -83,10 +90,10 @@ namespace Hyper.Internal
83
90
  AvatarTexture = texture;
84
91
  }
85
92
 
86
- public static void SetProgress(float progress)
93
+ public static bool SetProgress(float progress)
87
94
  {
88
- if (!IsActive) return;
89
- if (IsSeeking && !_seekPlaybackReady) return;
95
+ if (!IsActive) return false;
96
+ if (IsSeeking && !_seekPlaybackReady) return false;
90
97
  float rawProgress = Math.Max(0f, Math.Min(1f, progress));
91
98
  Progress01 = GameplayStartProgress01 >= 1f
92
99
  ? 0f
@@ -101,7 +108,10 @@ namespace Hyper.Internal
101
108
  PlaybackSpeed = UserPlaybackSpeed;
102
109
  RestoreAudioAfterSeek();
103
110
  SeekCompleted?.Invoke(PlaybackSpeed);
111
+ return true;
104
112
  }
113
+
114
+ return false;
105
115
  }
106
116
 
107
117
  public static void SetPlaybackSpeed(float speed)
@@ -190,6 +200,7 @@ namespace Hyper.Internal
190
200
  AvatarUrl = string.Empty;
191
201
  EntryId = string.Empty;
192
202
  ReplayNdjson = string.Empty;
203
+ FinalScore = null;
193
204
  ReleaseAvatarTexture();
194
205
  OriginalRandomSeed = 0;
195
206
  Progress01 = 0f;
@@ -76,6 +76,16 @@ namespace Hyper.Internal
76
76
  "instead of using default restart. Listen to HyperSDK.Event.OnCustomRestart in your game manager.")]
77
77
  public bool useCustomRestartFlow = false;
78
78
 
79
+ [Tooltip("Use the game-owned custom restart flow for replay Watch Again and seeking. " +
80
+ "Leave disabled to rebuild the game through HyperLoader for deterministic replay state.")]
81
+ public bool useCustomReplayRestartFlow = false;
82
+
83
+ [Space(20)]
84
+ [Header("Replay Settings")]
85
+ [Tooltip("Allow replay viewers to seek from the replay progress bar. " +
86
+ "Leave disabled until the game has been validated for accelerated replay reconstruction.")]
87
+ public bool allowReplaySeeking = false;
88
+
79
89
  #endregion
80
90
 
81
91
  #region Editor Testing
@@ -12,35 +12,59 @@ namespace Hyper.Internal
12
12
  {
13
13
  internal const int AvatarTextureSize = 128;
14
14
 
15
- [SerializeField] private Slider progressSlider;
15
+ [SerializeField] private Slider seekableSlider;
16
+ [SerializeField] private Slider readOnlySlider;
16
17
  [SerializeField] private TextMeshProUGUI replayLabel;
17
18
  [SerializeField] private RawImage playerAvatar;
18
19
  [SerializeField] private Button pauseButton;
19
20
  [SerializeField] private Button playbackSpeedButton;
20
21
  [SerializeField] private TextMeshProUGUI playbackSpeedLabel;
21
- [SerializeField] private float[] playbackSpeeds = { 1.5f, 2f, 3f, 4f, 5f, 1f };
22
- [SerializeField, Min(1f)] private float seekPlaybackSpeed = 30f;
22
+ [SerializeField] private float[] playbackSpeeds = { 1f, 1.5f, 2f, 0.5f };
23
+ [SerializeField, Min(1f)] private float seekPlaybackSpeed = 15f;
24
+ [SerializeField, Min(0f)] private float readOnlyProgressSmoothTime = 0.08f;
23
25
  [SerializeField, Range(0.25f, 1f)] private float seekSnapshotScale = 0.55f;
26
+ [SerializeField, Range(0f, 0.5f)] private float seekPreparationProgress = 0.2f;
27
+ [SerializeField, Range(0f, 0.15f)] private float seekPreparationProgressVariation = 0.05f;
28
+ [SerializeField, Min(0.1f)] private float seekPreparationDuration = 3f;
24
29
  [SerializeField] private GameObject seekLoadingOverlay;
25
30
  [SerializeField] private RawImage seekSnapshotImage;
26
31
 
27
32
  private int playbackSpeedIndex;
33
+ private Slider progressSlider;
28
34
  private ReplaySeekInput seekInput;
35
+ private bool seekingAllowed;
29
36
  private bool wasSeeking;
30
37
  private bool isCapturingSeek;
31
38
  private float pendingSeekTarget;
39
+ private float displayedReadOnlyProgress;
40
+ private float readOnlyProgressVelocity;
32
41
  private Texture2D seekSnapshotTexture;
33
42
  private Image seekOverlayBackground;
34
43
  private Color seekOverlayBackgroundColor;
44
+ private ReplaySeekOverlay seekOverlay;
45
+ private static readonly System.Random SeekProgressRandom = new System.Random();
46
+ private static float displayedSeekProgress;
47
+ private static float firstSeekPreparationTarget;
48
+ private static float secondSeekPreparationTarget;
49
+ private static float seekPreparationCeiling;
35
50
 
36
51
  private void Awake()
37
52
  {
53
+ seekingAllowed = HyperRuntime.Settings != null && HyperRuntime.Settings.allowReplaySeeking;
38
54
  AutoBindReferences();
39
- ConfigureProgressSlider();
40
- ConfigureSeekOverlay();
41
- ConfigureSeekSnapshot();
55
+ ConfigureProgressSliders();
56
+ if (seekingAllowed)
57
+ {
58
+ ConfigureSeekOverlay();
59
+ ConfigureSeekSnapshot();
60
+ }
61
+ else if (seekLoadingOverlay != null)
62
+ {
63
+ seekLoadingOverlay.SetActive(false);
64
+ }
65
+
42
66
  ApplyInitialPlaybackSpeed();
43
- ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
67
+ if (seekingAllowed) ReplaySessionState.SeekCompleted += RestorePlaybackAfterSeek;
44
68
  if (pauseButton != null) pauseButton.onClick.AddListener(PauseReplay);
45
69
  if (playbackSpeedButton != null) playbackSpeedButton.onClick.AddListener(CyclePlaybackSpeed);
46
70
  }
@@ -52,13 +76,13 @@ namespace Hyper.Internal
52
76
  if (replayLabel != null) replayLabel.text = $"Replay of @{playerName}";
53
77
  if (progressSlider != null)
54
78
  {
55
- float displayedProgress = ReplaySessionState.IsSeeking
79
+ float displayedProgress = seekingAllowed && ReplaySessionState.IsSeeking
56
80
  ? ReplaySessionState.SeekTarget01
57
81
  : ReplaySessionState.Progress01;
58
- progressSlider.SetValueWithoutNotify(displayedProgress);
82
+ SetProgressSliderValue(displayedProgress);
59
83
  }
60
84
  UpdatePlaybackSpeedLabel(ReplaySessionState.UserPlaybackSpeed);
61
- SetSeekingPresentation(ReplaySessionState.IsSeeking);
85
+ SetSeekingPresentation(seekingAllowed && ReplaySessionState.IsSeeking);
62
86
 
63
87
  if (playerAvatar != null && ReplaySessionState.AvatarTexture != null)
64
88
  {
@@ -68,7 +92,7 @@ namespace Hyper.Internal
68
92
 
69
93
  private void Update()
70
94
  {
71
- bool isSeeking = ReplaySessionState.IsSeeking;
95
+ bool isSeeking = seekingAllowed && ReplaySessionState.IsSeeking;
72
96
  if (wasSeeking && !isSeeking && !ReplaySessionState.HasEnded)
73
97
  {
74
98
  RestorePlaybackAfterSeek(ReplaySessionState.PlaybackSpeed);
@@ -79,6 +103,11 @@ namespace Hyper.Internal
79
103
  SetSeekingPresentation(isSeeking);
80
104
  }
81
105
 
106
+ if (isSeeking)
107
+ {
108
+ UpdateSeekProgress(true);
109
+ }
110
+
82
111
  if (progressSlider != null && (seekInput == null || !seekInput.IsDragging))
83
112
  {
84
113
  float displayedProgress = isCapturingSeek
@@ -86,7 +115,7 @@ namespace Hyper.Internal
86
115
  : isSeeking
87
116
  ? ReplaySessionState.SeekTarget01
88
117
  : ReplaySessionState.Progress01;
89
- progressSlider.SetValueWithoutNotify(displayedProgress);
118
+ SetProgressSliderValue(displayedProgress, !seekingAllowed);
90
119
  }
91
120
 
92
121
  wasSeeking = isSeeking;
@@ -129,7 +158,7 @@ namespace Hyper.Internal
129
158
  if (progressSlider != null) progressSlider.interactable = false;
130
159
  if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(false);
131
160
  ClearSeekSnapshot();
132
- if (progressSlider != null) progressSlider.SetValueWithoutNotify(1f);
161
+ SetProgressSliderValue(1f);
133
162
  }
134
163
 
135
164
  internal void ResetForReplayRestart()
@@ -148,10 +177,7 @@ namespace Hyper.Internal
148
177
  : 0f;
149
178
  wasSeeking = ReplaySessionState.IsSeeking;
150
179
 
151
- if (progressSlider != null)
152
- {
153
- progressSlider.SetValueWithoutNotify(pendingSeekTarget);
154
- }
180
+ SetProgressSliderValue(pendingSeekTarget);
155
181
 
156
182
  UpdatePlaybackSpeedLabel(playbackSpeed);
157
183
  AlignPlaybackSpeedIndex(playbackSpeed);
@@ -181,7 +207,7 @@ namespace Hyper.Internal
181
207
 
182
208
  private void SeekTo(float targetProgress)
183
209
  {
184
- if (!ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
210
+ if (!seekingAllowed || !ReplaySessionState.IsActive || ReplaySessionState.HasEnded ||
185
211
  ReplaySessionState.IsSeeking || isCapturingSeek)
186
212
  {
187
213
  return;
@@ -189,6 +215,7 @@ namespace Hyper.Internal
189
215
 
190
216
  float resumeSpeed = SanitizePlaybackSpeed(ReplaySessionState.UserPlaybackSpeed);
191
217
  float fastForwardSpeed = SanitizePlaybackSpeed(seekPlaybackSpeed);
218
+ InitializeSeekProgressPresentation();
192
219
  StartCoroutine(CaptureAndSeek(targetProgress, resumeSpeed, fastForwardSpeed));
193
220
  }
194
221
 
@@ -238,7 +265,6 @@ namespace Hyper.Internal
238
265
  private void RestorePlaybackAfterSeek(float playbackSpeed)
239
266
  {
240
267
  float speed = SanitizePlaybackSpeed(playbackSpeed);
241
- Time.timeScale = speed;
242
268
  AlignPlaybackSpeedIndex(speed);
243
269
  UpdatePlaybackSpeedLabel(speed);
244
270
  SetSeekingPresentation(false);
@@ -259,9 +285,14 @@ namespace Hyper.Internal
259
285
 
260
286
  private void SetSeekingPresentation(bool isSeeking)
261
287
  {
288
+ isSeeking &= seekingAllowed;
262
289
  ReplayPresentationCanvas.SetSeekingLayer(isSeeking);
290
+ UpdateSeekProgress(isSeeking);
263
291
  if (seekLoadingOverlay != null) seekLoadingOverlay.SetActive(isSeeking);
264
- if (progressSlider != null) progressSlider.interactable = !isSeeking && !ReplaySessionState.HasEnded;
292
+ if (progressSlider != null)
293
+ {
294
+ progressSlider.interactable = seekingAllowed && !isSeeking && !ReplaySessionState.HasEnded;
295
+ }
265
296
  if (playbackSpeedButton != null)
266
297
  {
267
298
  playbackSpeedButton.interactable = !isSeeking && !ReplaySessionState.HasEnded;
@@ -285,7 +316,7 @@ namespace Hyper.Internal
285
316
 
286
317
  if (playbackSpeedLabel != null)
287
318
  {
288
- playbackSpeedLabel.text = $"{speed:0.#}x";
319
+ playbackSpeedLabel.text = $"{speed:0.##}x";
289
320
  }
290
321
  }
291
322
 
@@ -339,8 +370,6 @@ namespace Hyper.Internal
339
370
 
340
371
  private void AutoBindReferences()
341
372
  {
342
- if (progressSlider == null) progressSlider = GetComponentInChildren<Slider>(true);
343
-
344
373
  Button[] buttons = GetComponentsInChildren<Button>(true);
345
374
  for (int i = 0; i < buttons.Length; i++)
346
375
  {
@@ -360,24 +389,69 @@ namespace Hyper.Internal
360
389
  }
361
390
  }
362
391
 
363
- private void ConfigureProgressSlider()
392
+ private void ConfigureProgressSliders()
364
393
  {
394
+ progressSlider = seekingAllowed ? seekableSlider : readOnlySlider;
395
+
396
+ if (seekableSlider != null)
397
+ {
398
+ seekableSlider.gameObject.SetActive(seekingAllowed);
399
+ }
400
+
401
+ if (readOnlySlider != null)
402
+ {
403
+ readOnlySlider.gameObject.SetActive(!seekingAllowed);
404
+ }
405
+
365
406
  if (progressSlider == null) return;
366
407
 
367
408
  progressSlider.minValue = 0f;
368
409
  progressSlider.maxValue = 1f;
369
410
  progressSlider.wholeNumbers = false;
370
- progressSlider.interactable = true;
371
- float displayedProgress = ReplaySessionState.IsSeeking
411
+ progressSlider.interactable = seekingAllowed;
412
+ float displayedProgress = seekingAllowed && ReplaySessionState.IsSeeking
372
413
  ? ReplaySessionState.SeekTarget01
373
414
  : ReplaySessionState.Progress01;
374
- progressSlider.SetValueWithoutNotify(displayedProgress);
415
+ SetProgressSliderValue(displayedProgress);
416
+
417
+ if (!seekingAllowed) return;
375
418
 
376
419
  seekInput = progressSlider.GetComponent<ReplaySeekInput>();
377
420
  if (seekInput == null) seekInput = progressSlider.gameObject.AddComponent<ReplaySeekInput>();
378
421
  seekInput.Initialize(progressSlider, SeekTo);
379
422
  }
380
423
 
424
+ private void SetProgressSliderValue(float targetProgress, bool smoothReadOnly = false)
425
+ {
426
+ if (progressSlider == null) return;
427
+
428
+ targetProgress = Mathf.Clamp01(targetProgress);
429
+ if (!smoothReadOnly || readOnlyProgressSmoothTime <= 0f ||
430
+ targetProgress < displayedReadOnlyProgress)
431
+ {
432
+ displayedReadOnlyProgress = targetProgress;
433
+ readOnlyProgressVelocity = 0f;
434
+ }
435
+ else
436
+ {
437
+ displayedReadOnlyProgress = Mathf.SmoothDamp(
438
+ displayedReadOnlyProgress,
439
+ targetProgress,
440
+ ref readOnlyProgressVelocity,
441
+ readOnlyProgressSmoothTime,
442
+ Mathf.Infinity,
443
+ Time.unscaledDeltaTime);
444
+
445
+ if (Mathf.Abs(targetProgress - displayedReadOnlyProgress) < 0.0001f)
446
+ {
447
+ displayedReadOnlyProgress = targetProgress;
448
+ readOnlyProgressVelocity = 0f;
449
+ }
450
+ }
451
+
452
+ progressSlider.SetValueWithoutNotify(displayedReadOnlyProgress);
453
+ }
454
+
381
455
  private void ConfigureSeekOverlay()
382
456
  {
383
457
  if (seekLoadingOverlay == null)
@@ -404,10 +478,114 @@ namespace Hyper.Internal
404
478
  seekLoadingOverlay.transform.SetAsFirstSibling();
405
479
  seekOverlayBackground = seekLoadingOverlay.GetComponent<Image>();
406
480
  if (seekOverlayBackground != null) seekOverlayBackgroundColor = seekOverlayBackground.color;
481
+ seekOverlay = seekLoadingOverlay.GetComponent<ReplaySeekOverlay>();
407
482
  seekLoadingOverlay.SetActive(ReplaySessionState.IsSeeking);
408
483
  wasSeeking = ReplaySessionState.IsSeeking;
409
484
  }
410
485
 
486
+ private void UpdateSeekProgress(bool isSeeking)
487
+ {
488
+ if (seekOverlay == null) return;
489
+
490
+ float progress;
491
+ if (!isSeeking)
492
+ {
493
+ progress = 1f;
494
+ }
495
+ else if (isCapturingSeek || ReplaySessionState.IsSeekRestartPending)
496
+ {
497
+ AdvanceSeekPreparationProgress();
498
+ progress = displayedSeekProgress;
499
+ }
500
+ else
501
+ {
502
+ EnsureSeekProgressPresentation();
503
+ float target = ReplaySessionState.SeekTarget01;
504
+ float reconstructionProgress = target <= Mathf.Epsilon
505
+ ? 1f
506
+ : ReplaySessionState.Progress01 / target;
507
+ float realProgress = Mathf.Lerp(
508
+ seekPreparationCeiling,
509
+ 1f,
510
+ Mathf.Clamp01(reconstructionProgress));
511
+ AdvanceSeekReconstructionProgress(realProgress);
512
+ progress = displayedSeekProgress;
513
+ }
514
+
515
+ seekOverlay.SetProgress(progress);
516
+ }
517
+
518
+ private void AdvanceSeekPreparationProgress()
519
+ {
520
+ EnsureSeekProgressPresentation();
521
+ float duration = Mathf.Max(0.1f, seekPreparationDuration);
522
+ float deltaTime = Mathf.Min(Time.unscaledDeltaTime, 0.1f);
523
+
524
+ if (displayedSeekProgress < firstSeekPreparationTarget)
525
+ {
526
+ float speed = firstSeekPreparationTarget / (duration * 0.35f);
527
+ displayedSeekProgress = Mathf.MoveTowards(
528
+ displayedSeekProgress,
529
+ firstSeekPreparationTarget,
530
+ speed * deltaTime);
531
+ return;
532
+ }
533
+
534
+ if (displayedSeekProgress < secondSeekPreparationTarget)
535
+ {
536
+ float speed = (secondSeekPreparationTarget - firstSeekPreparationTarget) / (duration * 0.2f);
537
+ displayedSeekProgress = Mathf.MoveTowards(
538
+ displayedSeekProgress,
539
+ secondSeekPreparationTarget,
540
+ speed * deltaTime);
541
+ return;
542
+ }
543
+
544
+ float crawlResponse = 1f - Mathf.Exp(-deltaTime / (duration * 0.65f));
545
+ displayedSeekProgress = Mathf.Lerp(displayedSeekProgress, seekPreparationCeiling, crawlResponse);
546
+ }
547
+
548
+ private void InitializeSeekProgressPresentation()
549
+ {
550
+ float center = Mathf.Clamp01(seekPreparationProgress);
551
+ float variation = Mathf.Max(0f, seekPreparationProgressVariation);
552
+ float minimum = Mathf.Clamp01(center - variation);
553
+ float maximum = Mathf.Clamp01(center + variation);
554
+
555
+ displayedSeekProgress = 0f;
556
+ seekPreparationCeiling = RandomRange(minimum, maximum);
557
+ firstSeekPreparationTarget = seekPreparationCeiling * RandomRange(0.25f, 0.35f);
558
+ secondSeekPreparationTarget = seekPreparationCeiling * RandomRange(0.62f, 0.76f);
559
+ }
560
+
561
+ private void EnsureSeekProgressPresentation()
562
+ {
563
+ if (seekPreparationCeiling > 0f) return;
564
+ InitializeSeekProgressPresentation();
565
+ }
566
+
567
+ private static float RandomRange(float minimum, float maximum)
568
+ {
569
+ if (maximum <= minimum) return minimum;
570
+ return minimum + ((float)SeekProgressRandom.NextDouble() * (maximum - minimum));
571
+ }
572
+
573
+ private void AdvanceSeekReconstructionProgress(float realProgress)
574
+ {
575
+ float target = Mathf.Max(displayedSeekProgress, realProgress);
576
+ if (target >= 0.999f)
577
+ {
578
+ displayedSeekProgress = 1f;
579
+ return;
580
+ }
581
+
582
+ float deltaTime = Mathf.Min(Time.unscaledDeltaTime, 0.1f);
583
+ float gap = target - displayedSeekProgress;
584
+ float responseSpeed = Mathf.Lerp(5f, 14f, Mathf.Clamp01(gap * 4f));
585
+ float response = 1f - Mathf.Exp(-responseSpeed * deltaTime);
586
+ displayedSeekProgress = Mathf.Lerp(displayedSeekProgress, target, response);
587
+ }
588
+
411
589
  private void ConfigureSeekSnapshot()
412
590
  {
413
591
  if (seekSnapshotImage == null)
@@ -110,7 +110,7 @@ namespace Hyper.Internal
110
110
  CanvasScaler scaler = GetComponent<CanvasScaler>();
111
111
  scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
112
112
  scaler.referenceResolution = new Vector2(1080f, 2160f);
113
- scaler.matchWidthOrHeight = isPortrait ? 1f : 0.593f;
113
+ scaler.matchWidthOrHeight = isPortrait ? 1f : (PlayerDeviceClassification.PlayerDeviceIsPC ? 0.75f : 0.593f);
114
114
  }
115
115
 
116
116
  private void EnsurePresentationObjects()
@@ -0,0 +1,18 @@
1
+ using TMPro;
2
+ using UnityEngine;
3
+
4
+ namespace Hyper.Internal
5
+ {
6
+ internal sealed class ReplaySeekOverlay : MonoBehaviour
7
+ {
8
+ [SerializeField] private TextMeshProUGUI progressText;
9
+
10
+ internal void SetProgress(float progress01)
11
+ {
12
+ if (progressText == null) return;
13
+
14
+ int percentage = Mathf.FloorToInt(Mathf.Clamp01(progress01) * 10f) * 10;
15
+ progressText.text = $"{percentage}%";
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: a7d0eb352f3c4d64a687b8c4509e1fa2
@@ -1,3 +1,4 @@
1
+ using System.Globalization;
1
2
  using TMPro;
2
3
  using UnityEngine;
3
4
  using UnityEngine.UI;
@@ -38,7 +39,7 @@ namespace Hyper.Internal
38
39
 
39
40
  if (scoreValue != null)
40
41
  {
41
- scoreValue.text = (HyperRuntime.DataManager?.CurrentScore ?? 0).ToString("N0");
42
+ scoreValue.text = FormatFinalScore(ReplaySessionState.FinalScore);
42
43
  }
43
44
 
44
45
  if (scoreOwnerName != null)
@@ -99,6 +100,13 @@ namespace Hyper.Internal
99
100
  : $"@{normalizedName.ToUpperInvariant()}'S SCORE";
100
101
  }
101
102
 
103
+ internal static string FormatFinalScore(int? finalScore)
104
+ {
105
+ return finalScore.HasValue
106
+ ? finalScore.Value.ToString("N0", CultureInfo.InvariantCulture)
107
+ : "--";
108
+ }
109
+
102
110
  private void UpdateSoundPresentation()
103
111
  {
104
112
  bool isSoundOn = HyperRuntime.DataManager == null || HyperRuntime.DataManager.GetSound() == 1;
@@ -14,7 +14,7 @@ namespace Hyper.Internal
14
14
  [SerializeField] private BlinkingText blinkingText;
15
15
  [SerializeField] private RectTransform swipeGif;
16
16
  [SerializeField] private RectTransform handSpriteInGif;
17
- [SerializeField] private float replayAutoStartDelay = 1f;
17
+ [SerializeField] private float replayAutoStartDelay = 1.02f;
18
18
  private GameManager _gameManager;
19
19
  private StartCTA _currentCTA;
20
20
  private SwipeDirection _swipeDirection;
@@ -16,13 +16,13 @@ namespace Hyper.Internal
16
16
 
17
17
  private void OnEnable()
18
18
  {
19
- // After 3 seconds, call the Game Over modal
19
+ // Show Game Over after one real-time second.
20
20
  StartCoroutine(GameOver());
21
21
  }
22
22
 
23
23
  IEnumerator GameOver()
24
24
  {
25
- yield return new WaitForSecondsRealtime(2f);
25
+ yield return new WaitForSecondsRealtime(1f);
26
26
  gameObject.SetActive(false);
27
27
 
28
28
  _gameManager.EndGame("Hyper_Time_Up");