activity_sdk_squad_runtime.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. #include "activity_sdk_squad_runtime.h"
  2. #include <algorithm>
  3. #include <cstddef>
  4. #include <limits>
  5. #include <string_view>
  6. #include "../../middleware/content/packages/tables/region_reader.h"
  7. #include "../../state/activity/membership/activity_membership_query.h"
  8. #include "../../state/activity/runtime.h"
  9. #include "../../state/activity_sdk/squad_profiles.h"
  10. #include "../../state/build_data/runtime.h"
  11. #include "../bap/runtime.h"
  12. #include "host_runtime.h"
  13. namespace sunrise::server::activity::activity_sdk_squads {
  14. namespace {
  15. namespace format = state::activity_sdk::format;
  16. namespace layouts = state::build_data::scenarios;
  17. namespace sdk = state::activity_sdk;
  18. /** Spawn rules are type-66 slots; the squad Auth names one by its wire slot index. */
  19. constexpr std::uint32_t kSpawnRuleSlotType = 66;
  20. namespace squad_auth = middleware::bap::activity_message::squad_auth;
  21. namespace tables = middleware::content::packages::tables;
  22. /** Exact private route retained while a generated squad request is staged. */
  23. struct PreparedSquad final {
  24. host::ScriptableTarget target{};
  25. layouts::RosterGroup generatedRosterGroup{};
  26. std::uint64_t activityClientGeneration{};
  27. std::int32_t effectiveRegion{-1};
  28. std::array<std::int8_t, 4> authoredProfile{};
  29. };
  30. /** Maps the SDK binding validator to this API's stable refusal surface. */
  31. [[nodiscard]] Status binding_status(const sdk::BoundView& view,
  32. server::bap::ActivityLinkView& link) noexcept {
  33. if (view.catalog == nullptr || sdk::bound_activity(view) == nullptr
  34. || sdk::bound_scenario(view) == nullptr) {
  35. return Status::invalidView;
  36. }
  37. if (!state::activity::binding_matches(view.binding)) {
  38. return Status::staleBinding;
  39. }
  40. (void)server::bap::activity_link_view(view.binding, link);
  41. switch (
  42. sdk::revalidate(view, view.binding, link.matchingLinks, link.activityClientGeneration)) {
  43. case sdk::Status::ready:
  44. return link.effectiveRegion >= 0 ? Status::ready : Status::noActivityLink;
  45. case sdk::Status::missingClient:
  46. case sdk::Status::ambiguousClient:
  47. return Status::noActivityLink;
  48. case sdk::Status::staleSession:
  49. return Status::staleBinding;
  50. case sdk::Status::staleActivityClient:
  51. return Status::staleActivityClient;
  52. case sdk::Status::notReady:
  53. case sdk::Status::missing:
  54. case sdk::Status::wrongSdkBuild:
  55. case sdk::Status::catalogInvalid:
  56. case sdk::Status::wrongActivity:
  57. case sdk::Status::activityJoinNotExact:
  58. case sdk::Status::missingScenarioLink:
  59. return Status::invalidView;
  60. }
  61. return Status::invalidView;
  62. }
  63. /** Validates the exact authored member vector and every requested safe bound. */
  64. [[nodiscard]] Status member_status(const sdk::Catalog& catalog,
  65. const format::Squad& squad,
  66. std::uint32_t squadRow,
  67. std::span<const std::int32_t> requestedCounts) noexcept {
  68. const auto members = sdk::squad_members(catalog, squad);
  69. if (members.size() < squad_auth::kMinimumRequestedCountLength
  70. || members.size() > squad_auth::kMaximumRequestedCountLength
  71. || requestedCounts.size() != members.size()) {
  72. return Status::memberCountMismatch;
  73. }
  74. for (std::size_t index = 0; index < members.size(); ++index) {
  75. const format::SquadMember& member = members[index];
  76. if (member.squadIndex != squadRow
  77. || member.memberOrdinal != static_cast<std::uint32_t>(index)) {
  78. return Status::invalidSquad;
  79. }
  80. if (requestedCounts[index] < 0) {
  81. return Status::memberCountOutOfRange;
  82. }
  83. }
  84. return Status::ready;
  85. }
  86. /** Copies the exact package actor profile shared by every positively requested member. */
  87. [[nodiscard]] bool authored_profile(const sdk::Catalog& catalog,
  88. const format::Squad& squad,
  89. std::span<const std::int32_t> requestedCounts,
  90. std::array<std::int8_t, 4>& output) noexcept {
  91. return sdk::squad_spawn_profile(
  92. sdk::squad_members(catalog, squad), catalog.actor_classes(), requestedCounts, output);
  93. }
  94. /** Checks the generated source slot before it can select a wire roster target. */
  95. [[nodiscard]] bool valid_generated_slot(const sdk::Catalog& catalog,
  96. const format::Squad& squad,
  97. const format::Object*& object,
  98. const format::Slot*& slot) noexcept {
  99. object = nullptr;
  100. slot = nullptr;
  101. const auto objects = catalog.objects();
  102. const auto slots = catalog.slots();
  103. if (squad.objectIndex >= objects.size() || squad.slotIndex >= slots.size()) {
  104. return false;
  105. }
  106. object = &objects[squad.objectIndex];
  107. slot = &slots[squad.slotIndex];
  108. return slot->objectIndex == squad.objectIndex
  109. && slot->slotIndex <= (std::numeric_limits<std::uint16_t>::max)()
  110. && slot->slotType == format::kSquadSlotType
  111. && slot->componentClass == format::kSquadComponentClass
  112. && slot->senseSchema == format::kSquadSenseSchema
  113. && slot->authSchema == format::kSquadAuthSchema
  114. && (slot->flags & format::kSlotSchemaJoinExact) != 0;
  115. }
  116. /** @return True when two complete roster rows have the same key and wire slot layout. */
  117. [[nodiscard]] bool same_roster_layout(const layouts::RosterGroup& left,
  118. const layouts::RosterGroup& right) noexcept {
  119. if (!layouts::valid_roster_group(left) || !layouts::valid_roster_group(right)
  120. || left.registryKey != right.registryKey || left.slotCount != right.slotCount) {
  121. return false;
  122. }
  123. for (std::size_t index = 0; index < left.slotCount; ++index) {
  124. if (left.slotTypes[index] != right.slotTypes[index]
  125. || left.slotFlags[index] != right.slotFlags[index]
  126. || left.slotIndices[index] != right.slotIndices[index]) {
  127. return false;
  128. }
  129. }
  130. return true;
  131. }
  132. /** Finds one exact type-1 slot in the roster's compressed wire order. */
  133. [[nodiscard]] bool selected_slot_offset(const layouts::RosterGroup& group,
  134. const format::Slot& slot,
  135. std::uint16_t& output) noexcept {
  136. output = 0;
  137. if (!layouts::valid_roster_group(group)
  138. || slot.slotIndex > (std::numeric_limits<std::uint16_t>::max)()) {
  139. return false;
  140. }
  141. std::size_t matches = 0;
  142. for (std::size_t index = 0; index < group.slotCount; ++index) {
  143. if (group.slotIndices[index] != slot.slotIndex || group.slotTypes[index] != slot.slotType
  144. || (group.slotFlags[index] & layouts::kSlotAuthFlag) == 0) {
  145. continue;
  146. }
  147. output = static_cast<std::uint16_t>(index);
  148. ++matches;
  149. }
  150. return matches == 1;
  151. }
  152. /** Resolves one validated SDK row to a canonical group or an inline generated group. */
  153. [[nodiscard]] Status resolve_target(const sdk::Catalog& catalog,
  154. const state::activity::SessionBinding& binding,
  155. const format::Scenario& scenario,
  156. const format::Squad& squad,
  157. const format::Occurrence& occurrence,
  158. const format::Slot& generatedSlot,
  159. const layouts::RosterGroup& generatedGroup,
  160. std::int32_t activityClientRegion,
  161. host::ScriptableTarget& output) noexcept {
  162. output = {};
  163. const auto bubbles = catalog.bubbles();
  164. const auto states = catalog.states();
  165. if (occurrence.bubbleIndex >= bubbles.size() || occurrence.stateIndex >= states.size()) {
  166. return Status::invalidSquad;
  167. }
  168. const format::Bubble& generatedBubble = bubbles[occurrence.bubbleIndex];
  169. const format::State& generatedState = states[occurrence.stateIndex];
  170. if (activityClientRegion < 0) {
  171. return Status::noActivityLink;
  172. }
  173. if (generatedBubble.scenarioIndex != squad.scenarioIndex
  174. || generatedState.scenarioIndex != squad.scenarioIndex
  175. || generatedState.bubbleIndex != occurrence.bubbleIndex
  176. || generatedBubble.bubbleOrdinal >= layouts::kBubbleCapacity
  177. || generatedState.stateOrdinal >= tables::kSliceSetIndexFactor) {
  178. return Status::invalidSquad;
  179. }
  180. const std::uint64_t authoredRegion =
  181. std::uint64_t{generatedBubble.bubbleOrdinal} * tables::kSliceSetIndexFactor
  182. + generatedState.stateOrdinal;
  183. if (authoredRegion > static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)())) {
  184. return Status::invalidSquad;
  185. }
  186. const auto targetRegion = static_cast<std::int32_t>(authoredRegion);
  187. std::uint16_t slotOffset = 0;
  188. if (!selected_slot_offset(generatedGroup, generatedSlot, slotOffset)
  189. || binding.destination.packageNameLength == 0
  190. || binding.destination.packageNameLength > binding.destination.packageName.size()) {
  191. return Status::invalidSquad;
  192. }
  193. const std::string_view destination(
  194. reinterpret_cast<const char*>(binding.destination.packageName.data()),
  195. binding.destination.packageNameLength);
  196. layouts::Definition layout{};
  197. if (!state::build_data::find_scenario_layout(destination, layout) || layout.tag != scenario.tag
  198. || layout.rosterGroupCount > layout.rosterGroups.size()
  199. || layout.bubbleGroupCount > layout.bubbleGroups.size()) {
  200. return Status::targetUnavailable;
  201. }
  202. const std::uint32_t activeBubble =
  203. static_cast<std::uint32_t>(activityClientRegion) / tables::kSliceSetIndexFactor;
  204. const bool authoredBubbleActive = activeBubble == generatedBubble.bubbleOrdinal;
  205. std::size_t matches = 0;
  206. std::size_t activeMatches = 0;
  207. std::uint16_t selectedTable = 0;
  208. layouts::RosterGroup selectedGroup{};
  209. const auto consider = [&](std::uint16_t tableIndex, bool active) noexcept {
  210. layouts::RosterGroup candidate{};
  211. if (!state::build_data::find_roster_group(tableIndex, candidate)) {
  212. return false;
  213. }
  214. if (!same_roster_layout(generatedGroup, candidate)) {
  215. return true;
  216. }
  217. ++matches;
  218. if (active) {
  219. ++activeMatches;
  220. selectedTable = tableIndex;
  221. selectedGroup = candidate;
  222. }
  223. return true;
  224. };
  225. for (std::size_t index = 0; index < layout.rosterGroupCount; ++index) {
  226. if (!consider(layout.rosterGroups[index], authoredBubbleActive)) {
  227. return Status::targetUnavailable;
  228. }
  229. }
  230. for (std::size_t index = 0; index < layout.bubbleGroupCount; ++index) {
  231. const bool active =
  232. authoredBubbleActive && activeBubble < layouts::kBubbleCapacity
  233. && (layout.bubbleGroupMasks[index] & (std::uint64_t{1} << activeBubble)) != 0;
  234. if (!consider(layout.bubbleGroups[index], active)) {
  235. return Status::targetUnavailable;
  236. }
  237. }
  238. if (matches > 1) {
  239. return Status::ambiguousTarget;
  240. }
  241. if (matches == 1 && activeMatches == 1) {
  242. output.objectTag = selectedGroup.objectTag;
  243. output.registryKey = selectedGroup.registryKey;
  244. output.authSchema = generatedSlot.authSchema;
  245. output.rosterGroupIndex = selectedTable;
  246. output.rosterSlotOffset = slotOffset;
  247. output.slotIndex = static_cast<std::uint16_t>(generatedSlot.slotIndex);
  248. output.slotType = static_cast<std::uint8_t>(generatedSlot.slotType);
  249. return Status::ready;
  250. }
  251. output.objectTag = generatedGroup.objectTag;
  252. output.registryKey = generatedGroup.registryKey;
  253. output.authSchema = generatedSlot.authSchema;
  254. output.rosterGroupIndex = host::kGeneratedRosterGroupIndex;
  255. output.rosterSlotOffset = slotOffset;
  256. output.slotIndex = static_cast<std::uint16_t>(generatedSlot.slotIndex);
  257. output.sdkObjectIndex = squad.objectIndex;
  258. output.stateLocalRegion = targetRegion;
  259. output.slotType = static_cast<std::uint8_t>(generatedSlot.slotType);
  260. output.stateLocalRoster = true;
  261. return Status::ready;
  262. }
  263. /** Resolves one public generated squad request without mutating transport state. */
  264. [[nodiscard]] Status prepare(const sdk::BoundView& view,
  265. std::uint32_t squadRow,
  266. std::span<const std::int32_t> requestedCounts,
  267. squad_auth::Mode mode,
  268. PreparedSquad& output) noexcept {
  269. output = {};
  270. server::bap::ActivityLinkView link{};
  271. const Status liveStatus = binding_status(view, link);
  272. if (liveStatus != Status::ready) {
  273. return liveStatus;
  274. }
  275. if (!squad_auth::valid_mode(mode)) {
  276. return Status::invalidMode;
  277. }
  278. const sdk::Catalog& catalog = *view.catalog;
  279. const format::Scenario* const scenario = sdk::bound_scenario(view);
  280. const auto squads = catalog.squads();
  281. if (scenario == nullptr || squadRow >= squads.size()) {
  282. return Status::invalidSquad;
  283. }
  284. const format::Squad& squad = squads[squadRow];
  285. if (squad.scenarioIndex != view.scenarioRow) {
  286. return Status::wrongScenario;
  287. }
  288. if (!format::squad_runnable(squad.flags)) {
  289. return Status::notRunnable;
  290. }
  291. const Status members = member_status(catalog, squad, squadRow, requestedCounts);
  292. if (members != Status::ready) {
  293. return members;
  294. }
  295. if (!authored_profile(catalog, squad, requestedCounts, output.authoredProfile)) {
  296. return Status::notRunnable;
  297. }
  298. const auto occurrences = catalog.occurrences();
  299. if (squad.occurrenceIndex >= occurrences.size()) {
  300. return Status::invalidSquad;
  301. }
  302. const format::Occurrence& occurrence = occurrences[squad.occurrenceIndex];
  303. if (occurrence.scenarioIndex != squad.scenarioIndex
  304. || occurrence.objectIndex != squad.objectIndex) {
  305. return Status::invalidSquad;
  306. }
  307. const format::Object* generatedObject = nullptr;
  308. const format::Slot* generatedSlot = nullptr;
  309. if (!valid_generated_slot(catalog, squad, generatedObject, generatedSlot)) {
  310. return Status::invalidSquad;
  311. }
  312. if (!sdk::materialize_roster_group(catalog, *generatedObject, output.generatedRosterGroup)) {
  313. output = {};
  314. return Status::invalidSquad;
  315. }
  316. const Status target = resolve_target(catalog,
  317. view.binding,
  318. *scenario,
  319. squad,
  320. occurrence,
  321. *generatedSlot,
  322. output.generatedRosterGroup,
  323. link.effectiveRegion,
  324. output.target);
  325. if (target != Status::ready) {
  326. output = {};
  327. return target;
  328. }
  329. output.effectiveRegion = link.effectiveRegion;
  330. output.activityClientGeneration = link.activityClientGeneration;
  331. return Status::ready;
  332. }
  333. /** Opt-in needs one exact actor class for every positively requested member. */
  334. state::gameplay::squad_entity_retirement::Eligibility
  335. retirement_eligibility(const sdk::BoundView& view,
  336. std::uint32_t squadRow,
  337. std::span<const std::int32_t> counts,
  338. const host::ScriptableTarget& target,
  339. bool enabled) noexcept {
  340. state::gameplay::squad_entity_retirement::Eligibility result{};
  341. result.squad = {target.registryKey, target.slotIndex, target.slotType};
  342. if (!enabled || !view.catalog || target.slotType != 1) {
  343. return result;
  344. }
  345. const auto& catalog = *view.catalog;
  346. const auto squads = catalog.squads();
  347. if (squadRow >= squads.size()) {
  348. return result;
  349. }
  350. const auto members = sdk::squad_members(catalog, squads[squadRow]);
  351. const auto classes = catalog.actor_classes();
  352. if (members.size() != counts.size()) {
  353. return result;
  354. }
  355. std::uint32_t selected = 0;
  356. for (std::size_t i = 0; i < members.size(); ++i) {
  357. if (counts[i] <= 0) {
  358. continue;
  359. }
  360. if ((members[i].flags & format::kSquadMemberActorClassExact) == 0
  361. || members[i].actorClassIndex >= classes.size()) {
  362. return result;
  363. }
  364. const auto rsat = classes[members[i].actorClassIndex].rsatTag;
  365. if (rsat == 0 || (selected != 0 && selected != rsat)) {
  366. return result;
  367. }
  368. selected = rsat;
  369. }
  370. if (selected == 0 || std::count_if(classes.begin(), classes.end(), [&](const auto& actor) {
  371. return actor.rsatTag == selected;
  372. }) != 1) {
  373. return result;
  374. }
  375. const auto occurrences = catalog.occurrences();
  376. const auto bubbles = catalog.bubbles();
  377. if (squads[squadRow].occurrenceIndex >= occurrences.size()) {
  378. return result;
  379. }
  380. const auto& occurrence = occurrences[squads[squadRow].occurrenceIndex];
  381. if (occurrence.bubbleIndex >= bubbles.size()
  382. || bubbles[occurrence.bubbleIndex].bubbleOrdinal >= 64) {
  383. return result;
  384. }
  385. result.rsatTag = selected;
  386. result.bubble = static_cast<std::uint8_t>(bubbles[occurrence.bubbleIndex].bubbleOrdinal);
  387. result.enabled = result.bubble < 64;
  388. return result;
  389. }
  390. } // namespace
  391. /** Availability checks never register reuse before Auth delivery. */
  392. /** The destination is another type-1 squad of the same object, sent as its wire slot. */
  393. [[nodiscard]] Status resolve_destination(const sdk::Catalog& catalog,
  394. std::uint32_t squadRow,
  395. std::uint32_t registryKey,
  396. std::optional<std::uint32_t> destinationSquadRow,
  397. std::optional<squad_auth::Destination>& output) noexcept {
  398. output.reset();
  399. if (!destinationSquadRow.has_value()) {
  400. return Status::ready;
  401. }
  402. const auto squads = catalog.squads();
  403. if (squadRow >= squads.size() || *destinationSquadRow >= squads.size()
  404. || *destinationSquadRow == squadRow
  405. || squads[*destinationSquadRow].objectIndex != squads[squadRow].objectIndex) {
  406. return Status::invalidSquad;
  407. }
  408. const format::Object* object = nullptr;
  409. const format::Slot* slot = nullptr;
  410. if (!valid_generated_slot(catalog, squads[*destinationSquadRow], object, slot)) {
  411. return Status::invalidSquad;
  412. }
  413. output = squad_auth::Destination{registryKey, static_cast<std::uint16_t>(slot->slotIndex)};
  414. return Status::ready;
  415. }
  416. /** The rule is a type-66 slot of the squad's own object, sent as its wire slot. */
  417. [[nodiscard]] Status resolve_spawn_rule(const sdk::Catalog& catalog,
  418. std::uint32_t squadRow,
  419. std::uint32_t registryKey,
  420. std::optional<std::uint32_t> spawnRuleSlotRow,
  421. std::optional<squad_auth::SpawnRule>& output) noexcept {
  422. output.reset();
  423. const auto squads = catalog.squads();
  424. const auto slots = catalog.slots();
  425. if (!spawnRuleSlotRow.has_value()) {
  426. // A spawner with no rule of its own cannot be placed without a selected one.
  427. return squadRow < squads.size()
  428. && (squads[squadRow].flags & format::kSquadRequiresSelectedRule) != 0
  429. ? Status::notRunnable
  430. : Status::ready;
  431. }
  432. if (squadRow >= squads.size() || *spawnRuleSlotRow >= slots.size()) {
  433. return Status::invalidSquad;
  434. }
  435. const format::Slot& slot = slots[*spawnRuleSlotRow];
  436. if (slot.objectIndex != squads[squadRow].objectIndex || slot.slotType != kSpawnRuleSlotType
  437. || slot.slotIndex > (std::numeric_limits<std::uint16_t>::max)()) {
  438. return Status::invalidSquad;
  439. }
  440. output = squad_auth::SpawnRule{registryKey, static_cast<std::uint16_t>(slot.slotIndex)};
  441. return Status::ready;
  442. }
  443. /**
  444. * Checks one placement without queuing it; name and retirement choices are not yet validated.
  445. * @param destinationSquadRow Optional actor-spawn destination squad of the same object.
  446. * @param spawnRuleSlotRow Optional type-66 rule slot of the same object.
  447. * @return `ready`, or the refusal a placement would get.
  448. */
  449. Status availability(const sdk::BoundView& view,
  450. std::uint32_t squadRow,
  451. std::span<const std::int32_t> requestedCounts,
  452. squad_auth::Mode mode,
  453. std::optional<std::uint32_t> nameHash,
  454. bool retireOnReturn,
  455. std::optional<std::uint32_t> destinationSquadRow,
  456. std::optional<std::uint32_t> spawnRuleSlotRow) noexcept {
  457. (void)nameHash;
  458. (void)retireOnReturn;
  459. PreparedSquad prepared{};
  460. const Status status = prepare(view, squadRow, requestedCounts, mode, prepared);
  461. if (status != Status::ready) {
  462. return status;
  463. }
  464. std::optional<squad_auth::Destination> destination{};
  465. const Status destinationStatus = resolve_destination(
  466. *view.catalog, squadRow, prepared.target.registryKey, destinationSquadRow, destination);
  467. if (destinationStatus != Status::ready) {
  468. return destinationStatus;
  469. }
  470. std::optional<squad_auth::SpawnRule> spawnRule{};
  471. return resolve_spawn_rule(
  472. *view.catalog, squadRow, prepared.target.registryKey, spawnRuleSlotRow, spawnRule);
  473. }
  474. /** Queues one preflighted generated squad through the proved private type-1 route. */
  475. Status place(const sdk::BoundView& view,
  476. std::uint32_t squadRow,
  477. std::span<const std::int32_t> requestedCounts,
  478. squad_auth::Mode mode,
  479. std::optional<std::uint32_t> nameHash,
  480. bool retireOnReturn,
  481. std::optional<std::uint32_t> destinationSquadRow,
  482. std::optional<std::uint32_t> spawnRuleSlotRow) noexcept {
  483. PreparedSquad prepared{};
  484. const Status status = prepare(view, squadRow, requestedCounts, mode, prepared);
  485. if (status != Status::ready) {
  486. return status;
  487. }
  488. std::optional<squad_auth::Destination> destination{};
  489. const Status destinationStatus = resolve_destination(
  490. *view.catalog, squadRow, prepared.target.registryKey, destinationSquadRow, destination);
  491. if (destinationStatus != Status::ready) {
  492. return destinationStatus;
  493. }
  494. std::optional<squad_auth::SpawnRule> spawnRule{};
  495. const Status ruleStatus = resolve_spawn_rule(
  496. *view.catalog, squadRow, prepared.target.registryKey, spawnRuleSlotRow, spawnRule);
  497. if (ruleStatus != Status::ready) {
  498. return ruleStatus;
  499. }
  500. if (server::bap::request_activity_squad_override(
  501. view.binding,
  502. prepared.target,
  503. prepared.target.stateLocalRoster ? &prepared.generatedRosterGroup : nullptr,
  504. requestedCounts,
  505. mode,
  506. nameHash,
  507. prepared.effectiveRegion,
  508. prepared.activityClientGeneration,
  509. nullptr,
  510. prepared.authoredProfile,
  511. retirement_eligibility(
  512. view, squadRow, requestedCounts, prepared.target, retireOnReturn),
  513. destination,
  514. spawnRule)) {
  515. return Status::queued;
  516. }
  517. return Status::refused;
  518. }
  519. /** Queues one preflighted squad only through an exact unarmed Host revision. */
  520. Status place_reserved(const sdk::BoundView& view,
  521. std::uint32_t squadRow,
  522. std::span<const std::int32_t> requestedCounts,
  523. squad_auth::Mode mode,
  524. const host::ScriptableOutputReservation& reservation,
  525. std::optional<std::uint32_t> nameHash,
  526. bool retireOnReturn,
  527. std::optional<std::uint32_t> destinationSquadRow,
  528. std::optional<std::uint32_t> spawnRuleSlotRow) noexcept {
  529. PreparedSquad prepared{};
  530. const Status status = prepare(view, squadRow, requestedCounts, mode, prepared);
  531. if (status != Status::ready) {
  532. return status;
  533. }
  534. std::optional<squad_auth::Destination> destination{};
  535. const Status destinationStatus = resolve_destination(
  536. *view.catalog, squadRow, prepared.target.registryKey, destinationSquadRow, destination);
  537. if (destinationStatus != Status::ready) {
  538. return destinationStatus;
  539. }
  540. std::optional<squad_auth::SpawnRule> spawnRule{};
  541. const Status ruleStatus = resolve_spawn_rule(
  542. *view.catalog, squadRow, prepared.target.registryKey, spawnRuleSlotRow, spawnRule);
  543. if (ruleStatus != Status::ready) {
  544. return ruleStatus;
  545. }
  546. if (server::bap::request_activity_squad_override(
  547. view.binding,
  548. prepared.target,
  549. prepared.target.stateLocalRoster ? &prepared.generatedRosterGroup : nullptr,
  550. requestedCounts,
  551. mode,
  552. nameHash,
  553. prepared.effectiveRegion,
  554. prepared.activityClientGeneration,
  555. &reservation,
  556. prepared.authoredProfile,
  557. retirement_eligibility(
  558. view, squadRow, requestedCounts, prepared.target, retireOnReturn),
  559. destination,
  560. spawnRule)) {
  561. return Status::queued;
  562. }
  563. return Status::refused;
  564. }
  565. /** Returns the concise operator and VM diagnostic name for one result. */
  566. const char* status_name(Status status) noexcept {
  567. switch (status) {
  568. case Status::ready:
  569. return "ready";
  570. case Status::queued:
  571. return "queued";
  572. case Status::invalidView:
  573. return "invalid_view";
  574. case Status::staleBinding:
  575. return "stale_binding";
  576. case Status::staleActivityClient:
  577. return "stale_activity_client";
  578. case Status::invalidSquad:
  579. return "invalid_squad";
  580. case Status::wrongScenario:
  581. return "wrong_scenario";
  582. case Status::notRunnable:
  583. return "not_runnable";
  584. case Status::invalidMode:
  585. return "invalid_mode";
  586. case Status::memberCountMismatch:
  587. return "member_count_mismatch";
  588. case Status::memberCountOutOfRange:
  589. return "member_count_out_of_range";
  590. case Status::noActivityLink:
  591. return "no_activity_link";
  592. case Status::targetUnavailable:
  593. return "target_unavailable";
  594. case Status::ambiguousTarget:
  595. return "ambiguous_target";
  596. case Status::outputBusy:
  597. return "output_busy";
  598. case Status::refused:
  599. return "refused";
  600. }
  601. return "unknown";
  602. }
  603. } // namespace sunrise::server::activity::activity_sdk_squads