Просмотр исходного кода

Hold native player spawn until the client reports world arrival

Direct travel between destinations could leave the screen black: the spawn gate
was released as soon as the destination region was instantiated, which can be
before bootflow step 37 arms the world-transition fade, so the spawn released an
inactive fade and the later arm stayed opaque.

Keep awaiting_client_sync set until the client's WS-702 write-back reports world
state 8. That report follows the bootflow's arrival independently of the player
spawn, so holding the spawn on it cannot stall the transition, and the release
lands after the arm without a client patch or a load-duration timeout. The
loading lifetime still advances when the region is instantiated, because state 4
refuses the spawn gate on its own and would otherwise keep the loading
presentation over a built world.
chnsw 4 дней назад
Родитель
Сommit
8a34b7f64a

+ 3 - 2
Sunrise/src/server/bap/encrypted/push/activity/activity_roster_push.cpp

@@ -291,8 +291,9 @@ bool append_roster_notification(
     const bool lifetimePending =
         hasScriptablePending && singleScriptableLink
         && scriptablePending.kind == server::activity::host::ScriptableOverrideKind::lifetime;
-    // The loading lifetime holds only until the destination region is instantiated. The stricter
-    // in-world state is reported after spawning and would make this field wait on its own result.
+    // The loading lifetime is the presentation, not the spawn hold: state 4 shows the loading
+    // screen and refuses the native spawn gate on its own, so it is released once the region is
+    // instantiated. `awaiting_client_sync` carries the hold on to the client's arrival report.
     // An explicit lifetime request still wins.
     const bool clientLoading = !client_region_ready(session, refresh);
     const bool bodyPending = hasScriptablePending && singleScriptableLink && !lifetimePending;

+ 7 - 5
Sunrise/src/server/bap/encrypted/push/activity/activity_roster_snapshot.cpp

@@ -98,9 +98,10 @@ bool client_region_ready(const Session& session, const RefreshReport* refresh) n
     return !movePending && held >= 0;
 }
 
-/** Tests whether the client has completed spawning into its instantiated region. */
+/** Tests whether the client has reported arrival in its instantiated region. */
 bool client_in_world(const Session& session, const RefreshReport* refresh) noexcept {
-    // World-state 8 is post-spawn, so no roster field that releases the spawn may read it.
+    // WS-702 world-state 8 follows the bootflow's arrival, independently of the player spawn.
+    // Holding a region alone can precede that report and the world-transition fade's final arm.
     const state::activity::membership::ClientPlacement placement =
         client_placement(session, refresh);
     return placement.entered && client_region_ready(session, refresh);
@@ -607,9 +608,10 @@ build_roster_snapshot(Session& session,
     // carries matches nothing.
     snapshot.playerKey = published_player_key(session);
     snapshot.lifetime = lifetimeState;
-    // Hold the spawn gate only until the region is instantiated. World-state 8 is written after
-    // the spawn, so waiting on it here deadlocks the spawn.
-    snapshot.awaitClientSync = !client_region_ready(session, refresh);
+    // Hold the native spawn gate until the client's arrival report, WS-702 world-state 8. A
+    // region can be loaded before the bootflow arms its fade; a spawn before the arm releases an
+    // inactive fade and leaves the screen black. Arrival does not depend on the spawn (RE/30).
+    snapshot.awaitClientSync = !client_in_world(session, refresh);
     // Player_BindComponents walks every type-13 reference and the player datum can name any one of
     // them. So every participation record carries the same player key. Selecting the first slot
     // leaves the authored cinematic participant unbound whenever it names another record.

+ 5 - 4
Sunrise/src/server/bap/encrypted/push/activity/internal.h

@@ -173,16 +173,17 @@ struct RefreshReport final {
 client_placement(const Session& session, const RefreshReport* refresh) noexcept;
 
 /**
- * Tests whether the client is in a live world: it holds the region it reported and no host
- * move is waiting for its arrival.
+ * Tests whether the client has reported arrival in its instantiated region: its WS-702 world
+ * state reached 8 while it holds the region it reported and no host move is waiting. This is
+ * the report that releases the native spawn gate.
  * @param session Connection whose activity session the client reports on.
  * @param refresh Refresh being answered, or null.
  */
 [[nodiscard]] bool client_in_world(const Session& session, const RefreshReport* refresh) noexcept;
 
 /**
- * Tests whether the client's destination region is instantiated far enough for the native spawn.
- * Never reads the post-spawn world-state 8 write-back; that would be a circular wait.
+ * Tests whether the client's destination region is instantiated, before its arrival report.
+ * This advances the loading lifetime. The spawn gate itself waits for `client_in_world`.
  * @param session Connection whose activity session the client reports on.
  * @param refresh Refresh being answered, or null.
  */