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
package/CHANGELOG.md CHANGED
@@ -1,3 +1,191 @@
1
+ ## [1.11.1](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0...v1.11.1) (2026-09-24)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * stop battle gameplay on the fixed timeout tick ([d265c6d](https://github.com/mvmhyper/hyper-sdk/commit/d265c6de6d4fb19f6194b73000bfbef93397228d))
7
+
8
+ # [1.11.0](https://github.com/mvmhyper/hyper-sdk/compare/v1.10.0...v1.11.0) (2026-09-23)
9
+
10
+
11
+ ### Features
12
+
13
+ * **sdk:** promote replay support and stability improvements to stable ([#159](https://github.com/mvmhyper/hyper-sdk/issues/159)) ([8172b37](https://github.com/mvmhyper/hyper-sdk/commit/8172b376cc587d139a43c4f4b4204f5c29af7aef))
14
+
15
+ # [1.11.0-beta.33](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.32...v1.11.0-beta.33) (2026-09-11)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **ui:** preserve mobile landscape canvas scaling ([c8a3f27](https://github.com/mvmhyper/hyper-sdk/commit/c8a3f2793bddaf89971a33d8752546a502de80e4))
21
+
22
+ # [1.11.0-beta.32](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.31...v1.11.0-beta.32) (2026-09-11)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **ui:** set landscape canvas scaler match to 0.75 ([edc44eb](https://github.com/mvmhyper/hyper-sdk/commit/edc44ebcc229d68ed40eb60b05df3ed25c5053f8))
28
+
29
+ # [1.11.0-beta.31](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.30...v1.11.0-beta.31) (2026-09-09)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * prevent gameplay timers jumping after pause ([d9d9de4](https://github.com/mvmhyper/hyper-sdk/commit/d9d9de4b7c71a0493b379bfb3f8e7712785c1401))
35
+
36
+ # [1.11.0-beta.30](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.29...v1.11.0-beta.30) (2026-09-08)
37
+
38
+
39
+ ### Bug Fixes
40
+
41
+ * **replay:** improve seek slider handle usability ([10c4847](https://github.com/mvmhyper/hyper-sdk/commit/10c48474c6dff689903aae99bff40215152710f6))
42
+
43
+ # [1.11.0-beta.29](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.28...v1.11.0-beta.29) (2026-09-08)
44
+
45
+
46
+ ### Bug Fixes
47
+
48
+ * **replay:** simplify seek progress presentation ([16cf45a](https://github.com/mvmhyper/hyper-sdk/commit/16cf45afb3bf4fed8abc094ce2405237f0bf3b1d))
49
+
50
+ # [1.11.0-beta.28](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.27...v1.11.0-beta.28) (2026-09-08)
51
+
52
+
53
+ ### Features
54
+
55
+ * update replay playback speed options ([fc5f653](https://github.com/mvmhyper/hyper-sdk/commit/fc5f65350b31425c53a131b7c44e8780e1b0ce2d))
56
+
57
+ # [1.11.0-beta.27](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.26...v1.11.0-beta.27) (2026-09-08)
58
+
59
+
60
+ ### Features
61
+
62
+ * **replay:** increase seek reconstruction speed ([97ce729](https://github.com/mvmhyper/hyper-sdk/commit/97ce72978d64711e0463def42283704e5d880266))
63
+
64
+ # [1.11.0-beta.26](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.25...v1.11.0-beta.26) (2026-09-07)
65
+
66
+
67
+ ### Bug Fixes
68
+
69
+ * **sample:** refine replay integration template ([5a345df](https://github.com/mvmhyper/hyper-sdk/commit/5a345df87d19a6df11af4117fbeed1e21e8e9f9a))
70
+
71
+ # [1.11.0-beta.25](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.24...v1.11.0-beta.25) (2026-09-04)
72
+
73
+
74
+ ### Features
75
+
76
+ * **replay:** include final score metadata ([463dcad](https://github.com/mvmhyper/hyper-sdk/commit/463dcadd4df5e5684f3be328b0ec361f25f26f2a))
77
+
78
+ # [1.11.0-beta.24](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.23...v1.11.0-beta.24) (2026-09-04)
79
+
80
+
81
+ ### Bug Fixes
82
+
83
+ * **replay:** add 1.5x playback speed ([3f7875f](https://github.com/mvmhyper/hyper-sdk/commit/3f7875fcc4934ed01aaa521dce45327a177f67a6))
84
+
85
+ # [1.11.0-beta.23](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.22...v1.11.0-beta.23) (2026-09-03)
86
+
87
+
88
+ ### Bug Fixes
89
+
90
+ * **replay:** smooth read-only progress display ([ead4579](https://github.com/mvmhyper/hyper-sdk/commit/ead45798883b24e4b4354f3aab22400a1399e212))
91
+
92
+ # [1.11.0-beta.22](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.21...v1.11.0-beta.22) (2026-09-03)
93
+
94
+
95
+ ### Features
96
+
97
+ * **replay:** finalize playback controls and settings ([559a7e4](https://github.com/mvmhyper/hyper-sdk/commit/559a7e48d9998efa022fe87253645dc13f83e901))
98
+
99
+ # [1.11.0-beta.21](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.20...v1.11.0-beta.21) (2026-09-03)
100
+
101
+
102
+ ### Bug Fixes
103
+
104
+ * **replay:** align gameplay activities to fixed ticks ([1e681b9](https://github.com/mvmhyper/hyper-sdk/commit/1e681b9c7f314a48c417f6e76cb2d4024c890ad3))
105
+
106
+ # [1.11.0-beta.20](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.19...v1.11.0-beta.20) (2026-09-02)
107
+
108
+
109
+ ### Bug Fixes
110
+
111
+ * **replay:** separate replay restart configuration ([de01d37](https://github.com/mvmhyper/hyper-sdk/commit/de01d37027fdde5d4f8803fa10e060d48a080316))
112
+
113
+ # [1.11.0-beta.19](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.18...v1.11.0-beta.19) (2026-09-01)
114
+
115
+
116
+ ### Bug Fixes
117
+
118
+ * **replay:** stop seeking at requested progress ([54bf74a](https://github.com/mvmhyper/hyper-sdk/commit/54bf74a4b66b7c19e1398acd611a7d9557ccbcf3))
119
+
120
+ # [1.11.0-beta.18](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.17...v1.11.0-beta.18) (2026-09-01)
121
+
122
+
123
+ ### Features
124
+
125
+ * **replay:** support custom replay restarts ([bf722ab](https://github.com/mvmhyper/hyper-sdk/commit/bf722abe48add18c356922c60231cb2017e28443))
126
+
127
+ # [1.11.0-beta.17](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.16...v1.11.0-beta.17) (2026-09-01)
128
+
129
+
130
+ ### Features
131
+
132
+ * **replay:** refine seek loading progress ([3d01f94](https://github.com/mvmhyper/hyper-sdk/commit/3d01f94d7e6ce360a61dd3d257149cf7f3237bcb))
133
+
134
+ # [1.11.0-beta.16](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.15...v1.11.0-beta.16) (2026-08-31)
135
+
136
+
137
+ ### Bug Fixes
138
+
139
+ * **replay:** show seek preparation progress ([60213f7](https://github.com/mvmhyper/hyper-sdk/commit/60213f784c5bff39da4634590545fe4d0e4c0911))
140
+
141
+ # [1.11.0-beta.15](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.14...v1.11.0-beta.15) (2026-08-31)
142
+
143
+
144
+ ### Features
145
+
146
+ * **replay:** show seek completion progress ([4e7c1b8](https://github.com/mvmhyper/hyper-sdk/commit/4e7c1b807bd9f34b97477ef39aa9ca3c06316ef3))
147
+
148
+ # [1.11.0-beta.14](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.13...v1.11.0-beta.14) (2026-08-31)
149
+
150
+
151
+ ### Bug Fixes
152
+
153
+ * **replay:** keep seek loader animating ([7e13938](https://github.com/mvmhyper/hyper-sdk/commit/7e13938ce718a238506c08c30744ff24ed7fa710))
154
+
155
+ # [1.11.0-beta.13](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.12...v1.11.0-beta.13) (2026-08-31)
156
+
157
+
158
+ ### Bug Fixes
159
+
160
+ * **replay:** reduce default seek speed to 10x ([ccaf5b9](https://github.com/mvmhyper/hyper-sdk/commit/ccaf5b9807fd9439627072518317ad7f079e544c))
161
+
162
+ # [1.11.0-beta.12](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.11...v1.11.0-beta.12) (2026-08-31)
163
+
164
+
165
+ ### Bug Fixes
166
+
167
+ * **replay:** reduce default seek speed ([d757710](https://github.com/mvmhyper/hyper-sdk/commit/d757710d107a0a496c9d7daa1434004a682bd355))
168
+
169
+ # [1.11.0-beta.11](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.10...v1.11.0-beta.11) (2026-08-30)
170
+
171
+
172
+ ### Bug Fixes
173
+
174
+ * **replay:** apply 30x seek speed on beta ([564769e](https://github.com/mvmhyper/hyper-sdk/commit/564769e4764f27fd301f0e1dc52a4434635f93b7))
175
+ * **replay:** reduce default seek speed ([2cd0a34](https://github.com/mvmhyper/hyper-sdk/commit/2cd0a34c6390725d1c57fc3fa5f691f618c45083))
176
+
177
+
178
+ ### Features
179
+
180
+ * **replay:** increase default seek speed ([d1a5a46](https://github.com/mvmhyper/hyper-sdk/commit/d1a5a464c1e3e149255bf0d2d183439e9c29cfb3))
181
+
182
+ # [1.11.0-beta.10](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.9...v1.11.0-beta.10) (2026-08-30)
183
+
184
+
185
+ ### Bug Fixes
186
+
187
+ * **replay:** reduce default seek speed ([f70551a](https://github.com/mvmhyper/hyper-sdk/commit/f70551aea942274ace1cb952ddae4e9edb0c1c12))
188
+
1
189
  # [1.11.0-beta.9](https://github.com/mvmhyper/hyper-sdk/compare/v1.11.0-beta.8...v1.11.0-beta.9) (2026-08-30)
2
190
 
3
191
 
@@ -170,6 +170,21 @@ namespace Hyper.Editor
170
170
  settings.useCustomRestartFlow = EditorGUILayout.Toggle(
171
171
  new GUIContent("Use Custom Restart Flow"),
172
172
  settings.useCustomRestartFlow);
173
+ settings.useCustomReplayRestartFlow = EditorGUILayout.Toggle(
174
+ new GUIContent(
175
+ "Replay Custom Restart",
176
+ "Use game-owned restart logic for replay Watch Again and seeking. " +
177
+ "Disabled uses HyperLoader to rebuild deterministic game state."),
178
+ settings.useCustomReplayRestartFlow);
179
+
180
+ EditorGUILayout.Space(12);
181
+ EditorGUILayout.LabelField("Replay Settings", EditorStyles.boldLabel);
182
+ settings.allowReplaySeeking = EditorGUILayout.Toggle(
183
+ new GUIContent(
184
+ "Allow Replay Seeking",
185
+ "Allow viewers to seek using the replay progress bar. " +
186
+ "Leave disabled until accelerated replay reconstruction is validated for this game."),
187
+ settings.allowReplaySeeking);
173
188
 
174
189
  EditorGUILayout.Space(12);
175
190
  // Draw Editor Testing
package/HyperLoader.unity CHANGED
@@ -304,7 +304,7 @@ RectTransform:
304
304
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
305
305
  m_AnchorMin: {x: 0, y: 1}
306
306
  m_AnchorMax: {x: 0, y: 1}
307
- m_AnchoredPosition: {x: 489.112, y: -280.1698}
307
+ m_AnchoredPosition: {x: 489.20746, y: -280.1698}
308
308
  m_SizeDelta: {x: 1044.9, y: 239.347}
309
309
  m_Pivot: {x: 0.5, y: 0.5}
310
310
  --- !u!114 &751153944
@@ -1092,7 +1092,7 @@ RectTransform:
1092
1092
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
1093
1093
  m_AnchorMin: {x: 0, y: 1}
1094
1094
  m_AnchorMax: {x: 0, y: 1}
1095
- m_AnchoredPosition: {x: 489.112, y: -133.2274}
1095
+ m_AnchoredPosition: {x: 489.20746, y: -133.2274}
1096
1096
  m_SizeDelta: {x: 875, y: 54.5378}
1097
1097
  m_Pivot: {x: 0.5, y: 0.5}
1098
1098
  --- !u!1 &2874543288936963705
@@ -448,7 +448,11 @@ namespace Hyper.Internal
448
448
  return false;
449
449
  }
450
450
 
451
- ReplaySessionState.SetMetadata(replay.PlayerName, replay.AvatarUrl, replay.Seed);
451
+ ReplaySessionState.SetMetadata(
452
+ replay.PlayerName,
453
+ replay.AvatarUrl,
454
+ replay.Seed,
455
+ replay.FinalScore);
452
456
  ReplaySessionState.SetReplayData(replay.ActivityNdjson);
453
457
  HyperDebug.Log($"Replay loaded from {path}");
454
458
  return true;
@@ -15,6 +15,8 @@ namespace Hyper.Internal
15
15
  /// </summary>
16
16
  internal static class HyperRuntime
17
17
  {
18
+ private const float ReplayCustomRestartTimeoutSeconds = 10f;
19
+
18
20
  private static HyperRuntimeHost _host;
19
21
  private static GameObject _singletonObject;
20
22
 
@@ -22,6 +24,7 @@ namespace Hyper.Internal
22
24
  private static bool _isSoftRestarting;
23
25
  private static bool _replayRandomResetPending;
24
26
  private static bool _replayCustomRestartPending;
27
+ private static int _replayCustomRestartVersion;
25
28
  private static bool _customLoadingComplete;
26
29
  private static bool _customLoadingActive;
27
30
  private static float _customLoadingProgress;
@@ -123,7 +126,8 @@ namespace Hyper.Internal
123
126
  }
124
127
 
125
128
  /// <summary>
126
- /// Restarts replay playback through HyperLoader while preserving replay session data.
129
+ /// Restarts replay playback while preserving replay session data. Uses the game-owned
130
+ /// replay restart flow only when explicitly enabled and otherwise reloads HyperLoader.
127
131
  /// </summary>
128
132
  internal static void RestartReplaySession()
129
133
  {
@@ -147,8 +151,64 @@ namespace Hyper.Internal
147
151
 
148
152
  _replayCustomRestartPending = false;
149
153
  _replayRandomResetPending = false;
154
+ int restartVersion = ++_replayCustomRestartVersion;
150
155
  _replayManager?.PrepareForRestart();
151
156
 
157
+ if (_settings != null && _settings.useCustomReplayRestartFlow &&
158
+ _host != null && _gameManager != null && _eventManager != null)
159
+ {
160
+ _replayCustomRestartPending = true;
161
+ _replayRandomResetPending = true;
162
+ _gameManager.ResetUIStateForCustomRestart();
163
+
164
+ try
165
+ {
166
+ if (!_eventManager.InvokeCustomRestartEvent())
167
+ {
168
+ HyperDebug.LogWarning(
169
+ "[Replay] Custom restart has no handler; falling back to HyperLoader.");
170
+ BeginReplaySceneRestart(restartVersion);
171
+ return;
172
+ }
173
+
174
+ _host.StartCoroutine(WaitForReplayCustomRestart(restartVersion));
175
+ return;
176
+ }
177
+ catch (Exception exception)
178
+ {
179
+ HyperDebug.LogWarning(
180
+ $"[Replay] Custom restart failed; falling back to HyperLoader: {exception.Message}");
181
+ BeginReplaySceneRestart(restartVersion);
182
+ return;
183
+ }
184
+ }
185
+
186
+ BeginReplaySceneRestart(restartVersion);
187
+ }
188
+
189
+ private static IEnumerator WaitForReplayCustomRestart(int restartVersion)
190
+ {
191
+ float timeoutAt = Time.realtimeSinceStartup + ReplayCustomRestartTimeoutSeconds;
192
+ while (_replayCustomRestartPending && restartVersion == _replayCustomRestartVersion &&
193
+ Time.realtimeSinceStartup < timeoutAt)
194
+ {
195
+ yield return null;
196
+ }
197
+
198
+ if (!_replayCustomRestartPending || restartVersion != _replayCustomRestartVersion) yield break;
199
+
200
+ HyperDebug.LogWarning(
201
+ $"[Replay] Custom restart did not complete within {ReplayCustomRestartTimeoutSeconds:0} seconds; " +
202
+ "falling back to HyperLoader.");
203
+ BeginReplaySceneRestart(restartVersion);
204
+ }
205
+
206
+ private static void BeginReplaySceneRestart(int restartVersion)
207
+ {
208
+ if (restartVersion != _replayCustomRestartVersion) return;
209
+
210
+ _replayCustomRestartPending = false;
211
+ _replayRandomResetPending = false;
152
212
  if (_host != null)
153
213
  {
154
214
  _host.StartCoroutine(RestartSessionCoroutine());
@@ -443,6 +503,7 @@ namespace Hyper.Internal
443
503
  _customLoadingProgress = 0f;
444
504
  _replayRandomResetPending = false;
445
505
  _replayCustomRestartPending = false;
506
+ _replayCustomRestartVersion++;
446
507
  _fixedSimulationTimeSeconds = 0d;
447
508
 
448
509
  if (_singletonObject != null)
@@ -504,6 +565,7 @@ namespace Hyper.Internal
504
565
  _customLoadingProgress = 0f;
505
566
  _replayRandomResetPending = false;
506
567
  _replayCustomRestartPending = false;
568
+ _replayCustomRestartVersion++;
507
569
  _fixedSimulationTimeSeconds = 0d;
508
570
  }
509
571
  }
@@ -179,7 +179,8 @@ namespace Hyper.Internal.Managers
179
179
  ReplaySessionState.SetMetadata(
180
180
  replay.PlayerName,
181
181
  replay.AvatarUrl,
182
- replay.Seed);
182
+ replay.Seed,
183
+ replay.FinalScore);
183
184
  ReplaySessionState.SetReplayData(replay.ActivityNdjson);
184
185
  HyperDebug.Log($"Replay downloaded for entry {entryId}.");
185
186
  onSuccess?.Invoke();
@@ -289,9 +290,10 @@ namespace Hyper.Internal.Managers
289
290
  string seedLine = reader.ReadLine();
290
291
  string nameLine = reader.ReadLine();
291
292
  string avatarLine = reader.ReadLine();
293
+ string scoreLine = reader.ReadLine();
292
294
  string activities = reader.ReadToEnd();
293
295
 
294
- if (seedLine == null || nameLine == null || avatarLine == null)
296
+ if (seedLine == null || nameLine == null || avatarLine == null || scoreLine == null)
295
297
  {
296
298
  error = "The replay response is missing required metadata.";
297
299
  return false;
@@ -302,6 +304,7 @@ namespace Hyper.Internal.Managers
302
304
  JArray seedData = JArray.Parse(seedLine);
303
305
  JArray nameData = JArray.Parse(nameLine);
304
306
  JArray avatarData = JArray.Parse(avatarLine);
307
+ JArray scoreData = JArray.Parse(scoreLine);
305
308
 
306
309
  if (seedData.Count != 1 ||
307
310
  seedData[0].Type != JTokenType.Integer ||
@@ -327,6 +330,18 @@ namespace Hyper.Internal.Managers
327
330
  return false;
328
331
  }
329
332
 
333
+ if (scoreData.Count != 1 ||
334
+ scoreData[0].Type != JTokenType.Integer ||
335
+ !int.TryParse(
336
+ scoreData[0].ToString(),
337
+ NumberStyles.Integer,
338
+ CultureInfo.InvariantCulture,
339
+ out int finalScore))
340
+ {
341
+ error = "Replay metadata contains an invalid final score.";
342
+ return false;
343
+ }
344
+
330
345
  if (string.IsNullOrWhiteSpace(activities))
331
346
  {
332
347
  error = "The replay response contains no gameplay activities.";
@@ -337,6 +352,7 @@ namespace Hyper.Internal.Managers
337
352
  seed,
338
353
  nameData[0].Value<string>() ?? string.Empty,
339
354
  avatarData[0].Value<string>() ?? string.Empty,
355
+ finalScore,
340
356
  activities);
341
357
 
342
358
  return true;
@@ -354,13 +370,20 @@ namespace Hyper.Internal.Managers
354
370
  internal long Seed { get; }
355
371
  internal string PlayerName { get; }
356
372
  internal string AvatarUrl { get; }
373
+ internal int FinalScore { get; }
357
374
  internal string ActivityNdjson { get; }
358
375
 
359
- internal ReplayResponse(long seed, string playerName, string avatarUrl, string activityNdjson)
376
+ internal ReplayResponse(
377
+ long seed,
378
+ string playerName,
379
+ string avatarUrl,
380
+ int finalScore,
381
+ string activityNdjson)
360
382
  {
361
383
  Seed = seed;
362
384
  PlayerName = playerName;
363
385
  AvatarUrl = avatarUrl;
386
+ FinalScore = finalScore;
364
387
  ActivityNdjson = activityNdjson;
365
388
  }
366
389
  }
@@ -375,6 +375,7 @@ namespace Hyper.Internal.Managers
375
375
  new object[] { HyperRandom.CurrentSeed }));
376
376
  _editorReplay.AppendLine(JsonConvert.SerializeObject(new object[] { "hypergamer" }));
377
377
  _editorReplay.AppendLine(JsonConvert.SerializeObject(new object[] { string.Empty }));
378
+ _editorReplayMetadataLength = _editorReplay.Length;
378
379
  #endif
379
380
  _replayRecorder?.StartSession();
380
381
  }
@@ -426,6 +427,7 @@ namespace Hyper.Internal.Managers
426
427
 
427
428
  #if UNITY_EDITOR
428
429
  private readonly StringBuilder _editorReplay = new StringBuilder(4096);
430
+ private int _editorReplayMetadataLength;
429
431
 
430
432
  private void CaptureEditorReplayActivity(object[] activity)
431
433
  {
@@ -434,6 +436,8 @@ namespace Hyper.Internal.Managers
434
436
 
435
437
  private void SaveEditorReplay()
436
438
  {
439
+ string finalScoreLine = JsonConvert.SerializeObject(new object[] { CurrentScore }) + Environment.NewLine;
440
+ _editorReplay.Insert(_editorReplayMetadataLength, finalScoreLine);
437
441
  try
438
442
  {
439
443
  File.WriteAllText(ReplaySessionState.EditorReplayFilePath, _editorReplay.ToString());
@@ -443,6 +447,10 @@ namespace Hyper.Internal.Managers
443
447
  {
444
448
  HyperDebug.LogWarning($"Unable to save Editor replay: {exception.Message}");
445
449
  }
450
+ finally
451
+ {
452
+ _editorReplay.Remove(_editorReplayMetadataLength, finalScoreLine.Length);
453
+ }
446
454
  }
447
455
  #endif
448
456
 
@@ -79,7 +79,13 @@ namespace Hyper.Internal.Managers
79
79
  internal void InvokeGameEndedEvent() => OnGameEnded?.Invoke();
80
80
  internal void InvokeLivesExhaustedEvent() => OnLivesExhausted?.Invoke();
81
81
  internal void InvokeHealthExhaustedEvent() => OnHealthExhausted?.Invoke();
82
- internal void InvokeCustomRestartEvent() => OnCustomRestart?.Invoke();
82
+ internal bool InvokeCustomRestartEvent()
83
+ {
84
+ Action handler = OnCustomRestart;
85
+ if (handler == null) return false;
86
+ handler.Invoke();
87
+ return true;
88
+ }
83
89
  internal void InvokeConnectionLostEvent() => OnInternetConnectionLost?.Invoke();
84
90
  internal void InvokeConnectionFoundEvent() => OnInternetConnectionFound?.Invoke();
85
91
  internal void InvokeBackendSocketManuallyClosedEvent() => OnBackendSocketManuallyClosed?.Invoke();
@@ -62,14 +62,14 @@ namespace Hyper.Internal.Managers
62
62
 
63
63
  private void HandleLivesExhausted()
64
64
  {
65
- if (!IsInGameScene || _hasGameEnded || !_hasGameStarted) return;
65
+ if (!IsInGameScene || HasGameEnded || !_hasGameStarted) return;
66
66
 
67
67
  EndGame();
68
68
  }
69
69
 
70
70
  private void HandleHealthExhausted()
71
71
  {
72
- if (!IsInGameScene || _hasGameEnded || !_hasGameStarted) return;
72
+ if (!IsInGameScene || HasGameEnded || !_hasGameStarted) return;
73
73
 
74
74
  EndGame();
75
75
  }
@@ -98,7 +98,10 @@ namespace Hyper.Internal.Managers
98
98
  public bool IsReplayMode => Config.GameMode == GameMode.Replay;
99
99
  public bool IsPaused => _isPaused;
100
100
  public bool HasGameStarted => _hasGameStarted;
101
- public bool HasGameEnded => _hasGameEnded;
101
+ // Gameplay stops at the timer boundary; presentation/finalization may follow later.
102
+ public bool HasGameEnded => _hasGameEnded || _gameplayCutoffReached;
103
+
104
+ internal void ReachGameplayCutoff() => _gameplayCutoffReached = true;
102
105
 
103
106
  internal int BattleTimeRemaining =>
104
107
  IsBattleMode && HyperRuntime.Timer != null
@@ -177,7 +180,7 @@ namespace Hyper.Internal.Managers
177
180
  internal void PauseGame()
178
181
  {
179
182
  if (IsReplayMode) return;
180
- if (!_hasGameStarted || _isPaused || _hasGameEnded) return;
183
+ if (!_hasGameStarted || _isPaused || HasGameEnded) return;
181
184
 
182
185
  _isPaused = true;
183
186
  _isFrozenBeforePause = Time.timeScale == 0;
@@ -193,7 +196,7 @@ namespace Hyper.Internal.Managers
193
196
 
194
197
  internal void PauseReplay()
195
198
  {
196
- if (!IsReplayMode || !_hasGameStarted || _isPaused || _hasGameEnded)
199
+ if (!IsReplayMode || !_hasGameStarted || _isPaused || HasGameEnded)
197
200
  {
198
201
  return;
199
202
  }
@@ -214,7 +217,7 @@ namespace Hyper.Internal.Managers
214
217
 
215
218
  internal void ResumeReplay()
216
219
  {
217
- if (!IsReplayMode || !_isPaused || _hasGameEnded) return;
220
+ if (!IsReplayMode || !_isPaused || HasGameEnded) return;
218
221
 
219
222
  _isPaused = false;
220
223
  HidePauseModals();
@@ -256,7 +259,7 @@ namespace Hyper.Internal.Managers
256
259
 
257
260
  private void HandleReplayFocus(bool hasFocus)
258
261
  {
259
- if (!_hasGameStarted || _hasGameEnded) return;
262
+ if (!_hasGameStarted || HasGameEnded) return;
260
263
  if (!hasFocus) PauseReplay();
261
264
  }
262
265
 
@@ -292,13 +295,13 @@ namespace Hyper.Internal.Managers
292
295
 
293
296
  // Always restart capturing when game resumes, even if offline,
294
297
  // so scores accumulate for the offline batch and are flushed on reconnect.
295
- if (!IsReplayMode && !_hasGameEnded && _hasGameStarted)
298
+ if (!IsReplayMode && !HasGameEnded && _hasGameStarted)
296
299
  {
297
300
  _dataManager.StartCapturingScores();
298
301
  }
299
302
 
300
303
  // Only resume server session if connected
301
- if (!IsReplayMode && _backendManager.isSocketConnected && !_hasGameEnded && _hasGameStarted)
304
+ if (!IsReplayMode && _backendManager.isSocketConnected && !HasGameEnded && _hasGameStarted)
302
305
  {
303
306
  _backendManager.SendGameResumed();
304
307
  }
@@ -714,6 +717,7 @@ namespace Hyper.Internal.Managers
714
717
  private bool _gameStartRequested;
715
718
  private bool _isPaused;
716
719
  private bool _hasGameEnded;
720
+ private bool _gameplayCutoffReached;
717
721
  private bool _replayEndHandled;
718
722
  private bool _isFrozenBeforePause;
719
723
  private bool _isPortrait;
@@ -948,6 +952,7 @@ namespace Hyper.Internal.Managers
948
952
  _hasGameStarted = false;
949
953
  _gameStartRequested = false;
950
954
  _hasGameEnded = false;
955
+ _gameplayCutoffReached = false;
951
956
  _isPaused = false;
952
957
  _isFrozenBeforePause = false;
953
958
  HideTopBarElements();
@@ -973,6 +978,7 @@ namespace Hyper.Internal.Managers
973
978
  _hasGameStarted = false;
974
979
  _gameStartRequested = false;
975
980
  _hasGameEnded = false;
981
+ _gameplayCutoffReached = false;
976
982
  _replayEndHandled = false;
977
983
  _isPaused = false;
978
984
  _isFrozenBeforePause = false;
@@ -1012,7 +1018,7 @@ namespace Hyper.Internal.Managers
1012
1018
 
1013
1019
  internal void StartGameplay()
1014
1020
  {
1015
- if (_gameStartRequested || _hasGameStarted || _hasGameEnded || !IsInGameScene) return;
1021
+ if (_gameStartRequested || _hasGameStarted || HasGameEnded || !IsInGameScene) return;
1016
1022
  _gameStartRequested = true;
1017
1023
 
1018
1024
  HyperRuntime.ApplyPendingReplayRandomReset();
@@ -1045,6 +1051,7 @@ namespace Hyper.Internal.Managers
1045
1051
  {
1046
1052
  _hasGameStarted = true;
1047
1053
  _hasGameEnded = false;
1054
+ _gameplayCutoffReached = false;
1048
1055
  HyperRuntime.DataManager.UpdateScore(0);
1049
1056
  }
1050
1057
 
@@ -1066,6 +1073,7 @@ namespace Hyper.Internal.Managers
1066
1073
  _hasGameStarted = false;
1067
1074
  _gameStartRequested = false;
1068
1075
  _hasGameEnded = false;
1076
+ _gameplayCutoffReached = false;
1069
1077
  _replayEndHandled = false;
1070
1078
  _isPaused = false;
1071
1079
  _isFrozenBeforePause = false;
@@ -1096,7 +1104,7 @@ namespace Hyper.Internal.Managers
1096
1104
 
1097
1105
  internal void CompleteReplayCustomRestart()
1098
1106
  {
1099
- if (!IsReplayMode || _gameStartRequested || _hasGameStarted || _hasGameEnded) return;
1107
+ if (!IsReplayMode || _gameStartRequested || _hasGameStarted || HasGameEnded) return;
1100
1108
 
1101
1109
  if (ReplaySessionState.IsSeeking)
1102
1110
  {
@@ -1143,7 +1151,7 @@ namespace Hyper.Internal.Managers
1143
1151
  {
1144
1152
  if (ReplaySessionState.IsSeekRestartPending) return;
1145
1153
 
1146
- // Time Up marks _hasGameEnded before its two-second presentation finishes.
1154
+ // Time Up marks _hasGameEnded before its one-second presentation finishes.
1147
1155
  // Track replay finalization separately so that delayed end still runs once.
1148
1156
  if (_replayEndHandled) return;
1149
1157
 
@@ -1334,7 +1342,7 @@ namespace Hyper.Internal.Managers
1334
1342
  _canvasScaler = _canvas.GetComponent<CanvasScaler>();
1335
1343
  _canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
1336
1344
  _canvasScaler.referenceResolution = new Vector2(1080, 2160);
1337
- _canvasScaler.matchWidthOrHeight = IsPortrait() ? 1f : 0.593f;
1345
+ _canvasScaler.matchWidthOrHeight = IsPortrait() ? 1f : (PlayerDeviceClassification.PlayerDeviceIsPC ? 0.75f : 0.593f);
1338
1346
 
1339
1347
  CreateHyperCanvasSafeArea();
1340
1348
  CreateHyperCanvasBlockerUI();
@@ -36,7 +36,10 @@ namespace Hyper.Internal.Managers
36
36
  {
37
37
  if (_playback == null || !_playback.IsPlaying) return;
38
38
  _playback.Tick();
39
- ReplaySessionState.SetProgress(_playback.Progress01);
39
+ if (ReplaySessionState.SetProgress(_playback.Progress01))
40
+ {
41
+ UnityEngine.Time.timeScale = ReplaySessionState.PlaybackSpeed;
42
+ }
40
43
  }
41
44
 
42
45
  internal void PrepareForRestart()
@@ -100,7 +103,9 @@ namespace Hyper.Internal.Managers
100
103
  ReplaySessionState.MarkEnded();
101
104
 
102
105
  GameManager gameManager = HyperRuntime.GameManager;
103
- if (gameManager == null || gameManager.HasGameEnded) return;
106
+ // Time Up can freeze gameplay before the recorded GE arrives.
107
+ // EndReplayGame has its own once-only finalization guard.
108
+ if (gameManager == null) return;
104
109
 
105
110
  _endingGame = true;
106
111
  gameManager.EndGame();