Browse Source

Send a scene only the squads the server casts, not every participant

The type-43 Auth body carries at most eight squad references, so a
scene whose participant table names more (Cayde's names twelve) was
refused once every participant became a dependency. The client does not
need them to play: it binds every role from the participant table in its
own content, and once the scene runs it only uses the references to mark
each named squad's remaining spawn budget as consumed. The installed
content has scenes with nine cell-backed squads, so the native server
could not have sent all participants either.

The server now keeps the complete participant set for the cast, casts
only the squads that one exact actor control in the scene's state
resolves to, and leaves the others to the client instead of refusing the
scene. The wire dependencies are the cast's squads; a cast wider than
the schema sends none and logs the omission.
Fozkais 1 week ago
parent
commit
06e3b6b33d

+ 6 - 5
Sunrise/src/server/activity/activity_sdk_mission_prepare.cpp

@@ -7,7 +7,7 @@
 #include "../../state/activity/runtime.h"
 #include "../../state/activity/runtime.h"
 #include "activity_sdk_behavior_scope.h"
 #include "activity_sdk_behavior_scope.h"
 #include "activity_sdk_mission_internal.h"
 #include "activity_sdk_mission_internal.h"
-#include "activity_sdk_scene_dependencies.h"
+#include "activity_sdk_scene_spawn.h"
 #include "activity_sdk_scriptable_route.h"
 #include "activity_sdk_scriptable_route.h"
 
 
 namespace sunrise::server::activity::activity_sdk_mission::detail {
 namespace sunrise::server::activity::activity_sdk_mission::detail {
@@ -353,11 +353,12 @@ namespace {
         return SceneStatus::missingResource;
         return SceneStatus::missingResource;
     }
     }
 
 
-    const SceneStatus dependencies =
-        scene_dependencies(catalog, slot, resource, output.sceneDependencies);
-    if (dependencies != SceneStatus::ready) {
-        return dependencies;
+    SceneSpawnPlan cast{};
+    const SceneStatus collected = resolve_scene_spawn_plan(view, occurrenceRow, slotRow, cast);
+    if (collected != SceneStatus::ready) {
+        return collected;
     }
     }
+    scene_dependencies(catalog, slotRow, cast, output.sceneDependencies);
 
 
     const SceneStatus lease = scene_lease_status(view, link, occurrence.stateIndex);
     const SceneStatus lease = scene_lease_status(view, link, occurrence.stateIndex);
     if (lease != SceneStatus::ready) {
     if (lease != SceneStatus::ready) {

+ 16 - 6
Sunrise/src/server/activity/activity_sdk_scene_cast.cpp

@@ -70,12 +70,17 @@ using Source = state::gameplay::entity_identity::ActorSourceReference;
 
 
 /**
 /**
  * Resolves the complete cast before exposing any pair.
  * Resolves the complete cast before exposing any pair.
+ *
+ * A squad participant joins the cast only when one exact actor control in the scene's own
+ * scenario state resolves to it. The client binds every role from its content and waits for
+ * the squads nobody supplies, so a participant without an actor control is left to it rather
+ * than refused.
  * @param catalog Authenticated SDK data.
  * @param catalog Authenticated SDK data.
  * @param world Exact generated package references.
  * @param world Exact generated package references.
  * @param occurrenceRow Selected scene occurrence.
  * @param occurrenceRow Selected scene occurrence.
  * @param sceneSlotRow Type 43 slot owned by that occurrence.
  * @param sceneSlotRow Type 43 slot owned by that occurrence.
  * @param output Receives the complete plan; cleared on failure.
  * @param output Receives the complete plan; cleared on failure.
- * @return Ready only when every dependency has one exact actor control.
+ * @return Ready only when every cast squad has exactly one actor control.
  */
  */
 SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
 SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
                                      const state::build_data::scriptables::Snapshot& world,
                                      const state::build_data::scriptables::Snapshot& world,
@@ -101,14 +106,14 @@ SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
     if (resources.size() != 1) {
     if (resources.size() != 1) {
         return SceneStatus::ambiguousResource;
         return SceneStatus::ambiguousResource;
     }
     }
-    middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies dependencies{};
-    const auto resolved = scene_dependencies(catalog, scene, resources.front(), dependencies);
+    SceneSquadParticipants participants{};
+    const auto resolved = scene_squad_participants(catalog, scene, resources.front(), participants);
     if (resolved != SceneStatus::ready) {
     if (resolved != SceneStatus::ready) {
         return resolved;
         return resolved;
     }
     }
     SceneSpawnPlan candidate{};
     SceneSpawnPlan candidate{};
-    for (std::size_t index = 0; index < dependencies.count; ++index) {
-        const auto& reference = dependencies.references[index];
+    for (std::size_t index = 0; index < participants.count; ++index) {
+        const auto& reference = participants.rows[index].reference;
         const Source source{reference.rosterKey,
         const Source source{reference.rosterKey,
                             static_cast<std::uint16_t>(reference.slotIndex),
                             static_cast<std::uint16_t>(reference.slotIndex),
                             static_cast<std::uint8_t>(reference.slotType),
                             static_cast<std::uint8_t>(reference.slotType),
@@ -116,6 +121,10 @@ SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
                             true};
                             true};
         auto& pair = candidate.pairs[candidate.count];
         auto& pair = candidate.pairs[candidate.count];
         const auto parent = source_squad(catalog, occurrenceRow, source, pair.squadRow);
         const auto parent = source_squad(catalog, occurrenceRow, source, pair.squadRow);
+        if (parent == SceneStatus::targetUnavailable) {
+            pair = {};
+            continue;
+        }
         if (parent != SceneStatus::ready) {
         if (parent != SceneStatus::ready) {
             return parent;
             return parent;
         }
         }
@@ -154,7 +163,8 @@ SceneStatus collect_scene_spawn_plan(const sdk::Catalog& catalog,
             }
             }
         }
         }
         if (pair.actorSlotRow == sdk::format::kAbsentIndex) {
         if (pair.actorSlotRow == sdk::format::kAbsentIndex) {
-            return SceneStatus::targetUnavailable;
+            pair = {};
+            continue;
         }
         }
         pair.actorTarget = physical_target(catalog, pair.actorSlotRow);
         pair.actorTarget = physical_target(catalog, pair.actorSlotRow);
         pair.sourceTarget = physical_target(catalog, catalog.squads()[pair.squadRow].slotIndex);
         pair.sourceTarget = physical_target(catalog, catalog.squads()[pair.squadRow].slotIndex);

+ 34 - 17
Sunrise/src/server/activity/activity_sdk_scene_dependencies.h

@@ -1,5 +1,7 @@
 #pragma once
 #pragma once
 
 
+#include <array>
+#include <cstddef>
 #include <limits>
 #include <limits>
 
 
 #include "../../middleware/bap/activity_message/sensor_auth_update.h"
 #include "../../middleware/bap/activity_message/sensor_auth_update.h"
@@ -7,24 +9,40 @@
 
 
 namespace sunrise::server::activity::activity_sdk_mission {
 namespace sunrise::server::activity::activity_sdk_mission {
 
 
+/** One squad participant of a scene: the authored reference and the squad slot it names. */
+struct SceneSquadParticipant final {
+    middleware::bap::activity_message::sensor_message::ClientReference reference{};
+    std::uint32_t squadSlotRow{state::activity_sdk::format::kAbsentIndex};
+};
+
+/** Every squad participant of one scene descriptor; the package bounds the table. */
+struct SceneSquadParticipants final {
+    std::array<SceneSquadParticipant,
+               state::activity_sdk::format::kAuthoredSceneParticipantCapacity>
+        rows{};
+    std::size_t count{};
+};
+
 /**
 /**
- * Collects only the selected scene descriptor's exact squad edges.
+ * Collects the selected scene descriptor's exact squad edges.
+ *
+ * The client binds every role from the participant table in its own content; these rows only
+ * tell the server which squads the scene may draw actors from. The wire dependency set is
+ * narrower (see scene_dependencies in activity_sdk_scene_spawn.h).
  * @param catalog Authenticated SDK catalog.
  * @param catalog Authenticated SDK catalog.
  * @param sceneSlot Owned scene slot.
  * @param sceneSlot Owned scene slot.
  * @param resource Selected resource descriptor for that slot.
  * @param resource Selected resource descriptor for that slot.
- * @param output Receives the complete dependency set; cleared on failure.
- * @return Ready for an exact bounded set, including an empty set.
+ * @param output Receives the complete participant set; cleared on failure.
+ * @return Ready for an exact set, including an empty set.
  */
  */
 [[nodiscard]] inline SceneStatus
 [[nodiscard]] inline SceneStatus
-scene_dependencies(const state::activity_sdk::Catalog& catalog,
-                   const state::activity_sdk::format::Slot& sceneSlot,
-                   const state::activity_sdk::format::AuthoredSceneResource& resource,
-                   middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies&
-                       output) noexcept {
+scene_squad_participants(const state::activity_sdk::Catalog& catalog,
+                         const state::activity_sdk::format::Slot& sceneSlot,
+                         const state::activity_sdk::format::AuthoredSceneResource& resource,
+                         SceneSquadParticipants& output) noexcept {
     namespace sdk = state::activity_sdk;
     namespace sdk = state::activity_sdk;
-    namespace message = middleware::bap::activity_message::sensor_auth_update;
     output = {};
     output = {};
-    message::AuthoredSceneDependencies candidate{};
+    SceneSquadParticipants candidate{};
     const auto slots = catalog.slots();
     const auto slots = catalog.slots();
     if (resource.slotIndex >= slots.size() || &slots[resource.slotIndex] != &sceneSlot
     if (resource.slotIndex >= slots.size() || &slots[resource.slotIndex] != &sceneSlot
         || sceneSlot.slotType != sdk::format::kAuthoredSceneSlotType
         || sceneSlot.slotType != sdk::format::kAuthoredSceneSlotType
@@ -35,7 +53,7 @@ scene_dependencies(const state::activity_sdk::Catalog& catalog,
         return SceneStatus::invalidSlot;
         return SceneStatus::invalidSlot;
     }
     }
     const auto edges = sdk::slot_authored_scene_squad_edges(catalog, sceneSlot);
     const auto edges = sdk::slot_authored_scene_squad_edges(catalog, sceneSlot);
-    if (edges.size() > candidate.references.size()) {
+    if (edges.size() > candidate.rows.size()) {
         return SceneStatus::refused;
         return SceneStatus::refused;
     }
     }
     for (const auto& edge : edges) {
     for (const auto& edge : edges) {
@@ -55,12 +73,11 @@ scene_dependencies(const state::activity_sdk::Catalog& catalog,
                    > static_cast<std::uint32_t>((std::numeric_limits<std::int16_t>::max)())) {
                    > static_cast<std::uint32_t>((std::numeric_limits<std::int16_t>::max)())) {
             return SceneStatus::ambiguousTarget;
             return SceneStatus::ambiguousTarget;
         }
         }
-        candidate.references[candidate.count++] = {edge.targetObjectKey,
-                                                   static_cast<std::int8_t>(target->slotType),
-                                                   static_cast<std::int16_t>(target->slotIndex)};
-    }
-    if (!message::valid_authored_scene_dependencies(candidate)) {
-        return SceneStatus::ambiguousTarget;
+        auto& row = candidate.rows[candidate.count++];
+        row.reference = {edge.targetObjectKey,
+                         static_cast<std::int8_t>(target->slotType),
+                         static_cast<std::int16_t>(target->slotIndex)};
+        row.squadSlotRow = edge.squadSlotIndex;
     }
     }
     output = candidate;
     output = candidate;
     return SceneStatus::ready;
     return SceneStatus::ready;

+ 41 - 0
Sunrise/src/server/activity/activity_sdk_scene_spawn.cpp

@@ -1,7 +1,11 @@
 #include "activity_sdk_scene_spawn.h"
 #include "activity_sdk_scene_spawn.h"
 
 
 #include <algorithm>
 #include <algorithm>
+#include <array>
+#include <cstdio>
+#include <string_view>
 
 
+#include "../../core/logging/log.h"
 #include "../../state/activity_sdk/generated_world/runtime.h"
 #include "../../state/activity_sdk/generated_world/runtime.h"
 #include "activity_sdk_device_internal.h"
 #include "activity_sdk_device_internal.h"
 #include "activity_sdk_mission_internal.h"
 #include "activity_sdk_mission_internal.h"
@@ -114,6 +118,43 @@ prepare_pair(const sdk::BoundView& view, std::uint32_t sceneState, SceneSpawnPai
 
 
 } // namespace
 } // namespace
 
 
+/** Sends the cast's squads when the wire can carry them all, otherwise none. */
+void scene_dependencies(
+    const sdk::Catalog& catalog,
+    std::uint32_t sceneSlotRow,
+    const SceneSpawnPlan& plan,
+    middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies&
+        output) noexcept {
+    output = {};
+    if (plan.count > output.references.size()) {
+        std::array<char, core::log::kLineCapacity> line{};
+        const std::string_view scene = sceneSlotRow < catalog.slots().size()
+                                           ? catalog.string(catalog.slots()[sceneSlotRow].id)
+                                           : std::string_view{};
+        const int written = std::snprintf(line.data(),
+                                          line.size(),
+                                          "ev=scene_dependencies result=omitted scene=%.*s "
+                                          "cast=%zu capacity=%zu",
+                                          static_cast<int>(scene.size()),
+                                          scene.data(),
+                                          plan.count,
+                                          output.references.size());
+        if (written > 0) {
+            core::log::write(
+                core::log::Channel::server,
+                core::log::Level::warn,
+                {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1)});
+        }
+        return;
+    }
+    for (std::size_t index = 0; index < plan.count; ++index) {
+        const host::ScriptableTarget& source = plan.pairs[index].sourceTarget;
+        output.references[output.count++] = {source.registryKey,
+                                             static_cast<std::int8_t>(source.slotType),
+                                             static_cast<std::int16_t>(source.slotIndex)};
+    }
+}
+
 /** Returns immutable scene cast identities without consulting live output state. */
 /** Returns immutable scene cast identities without consulting live output state. */
 SceneStatus resolve_scene_spawn_plan(const sdk::BoundView& view,
 SceneStatus resolve_scene_spawn_plan(const sdk::BoundView& view,
                                      std::uint32_t occurrenceRow,
                                      std::uint32_t occurrenceRow,

+ 20 - 4
Sunrise/src/server/activity/activity_sdk_scene_spawn.h

@@ -21,15 +21,31 @@ struct SceneSpawnPair final {
     bool sourceReady{};
     bool sourceReady{};
 };
 };
 
 
-/** The scene wire format bounds the complete cast before any output is queued. */
+/** The package participant table bounds the complete cast before any output is queued. */
 struct SceneSpawnPlan final {
 struct SceneSpawnPlan final {
-    std::array<
-        SceneSpawnPair,
-        middleware::bap::activity_message::sensor_auth_update::kAuthoredSceneMaximumDependencyCount>
+    std::array<SceneSpawnPair, state::activity_sdk::format::kAuthoredSceneParticipantCapacity>
         pairs{};
         pairs{};
     std::size_t count{};
     std::size_t count{};
 };
 };
 
 
+/**
+ * Names the cast's source squads as the scene's wire dependencies.
+ *
+ * The client uses them for one thing: once the scene runs, it marks each squad's remaining
+ * spawn budget as consumed. It binds roles from its own content, so a scene plays without
+ * them. The schema carries at most eight; a wider cast sends none and logs the omission.
+ * @param catalog Authenticated SDK data, for the log line.
+ * @param sceneSlotRow Type 43 slot the plan was resolved for.
+ * @param plan Complete resolved cast.
+ * @param output Receives the bounded set; empty when the cast exceeds the wire capacity.
+ */
+void scene_dependencies(
+    const state::activity_sdk::Catalog& catalog,
+    std::uint32_t sceneSlotRow,
+    const SceneSpawnPlan& plan,
+    middleware::bap::activity_message::sensor_auth_update::AuthoredSceneDependencies&
+        output) noexcept;
+
 /** Resolves the complete cast from exact package edges without reading Host state. */
 /** Resolves the complete cast from exact package edges without reading Host state. */
 [[nodiscard]] SceneStatus
 [[nodiscard]] SceneStatus
 collect_scene_spawn_plan(const state::activity_sdk::Catalog& catalog,
 collect_scene_spawn_plan(const state::activity_sdk::Catalog& catalog,