runtime.h 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. #pragma once
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <optional>
  6. #include <span>
  7. #include <vector>
  8. #include "../build_data/items/quest_initialization.h"
  9. #include "../build_data/records/definition.h"
  10. #include "../build_data/rewards/definition.h"
  11. #include "../build_data/vendors/definition.h"
  12. #include "../unlocks/definition.h"
  13. #include "state.h"
  14. namespace sunrise::state::unlocks {
  15. struct Table;
  16. }
  17. namespace sunrise::state::account::settings {
  18. struct SettingsDelta;
  19. } // namespace sunrise::state::account::settings
  20. namespace sunrise::state {
  21. /** Which prepare path grants one installed item. */
  22. enum class ItemGrantRoute : std::uint8_t { unavailable, quest, profile, reward };
  23. /** Chooses the prepare path from the item's wrapper, quest setup and bucket. */
  24. [[nodiscard]] ItemGrantRoute item_grant_route(std::uint16_t itemIndex) noexcept;
  25. /**
  26. * Assigns runtime SOIDs only to installed profile mod/shader rows which are socket action sources.
  27. * Currency, material, and consumable profile rows remain canonically non-instanced.
  28. */
  29. [[nodiscard]] bool ensure_profile_item_identities() noexcept;
  30. /** Why one attempt to canonicalize the "Emotes" collection item ended. */
  31. enum class EmoteCollectionOutcome : std::uint8_t {
  32. /** Every character carries a sound collection item, either already or as of this call. */
  33. ready,
  34. /** The build data or account this reads is not published yet, so a retry is still owed. */
  35. notReady,
  36. /** The installed content does not carry the item this expects, so it can never be applied. */
  37. unsupported,
  38. /** The item could not be placed, so no character was changed and a retry is still owed. */
  39. failed,
  40. };
  41. /**
  42. * Grants each character the other 2 subclasses of its equipped subclass's class, placing missing
  43. * ones into unequipped inventory with native socket defaults. Idempotent: one already equipped or
  44. * already in inventory is left alone.
  45. * @return True when every such character holds its whole class, or there was nothing to check.
  46. */
  47. [[nodiscard]] bool ensure_character_subclasses() noexcept;
  48. /** Prepared subclass socket-entry selection for the equipped selected-character subclass. */
  49. struct PendingSubclassSelection {
  50. /** Exact prepare-time character view used as the commit staleness guard. */
  51. CharacterState beforeCharacter{};
  52. /** Canonical after-image. Only one authored ability-entry field differs. */
  53. CharacterState afterCharacter{};
  54. std::uint64_t accountSoid{};
  55. std::uint64_t characterSoid{};
  56. std::uint64_t subclassInstanceSoid{};
  57. std::size_t characterIndex{};
  58. std::uint16_t subclassDefinitionIndex{};
  59. std::uint16_t socketEntryListIndex{};
  60. /** Exact entry named by opcode 801. */
  61. std::uint8_t requestedEntry{};
  62. bool prepared{};
  63. };
  64. /**
  65. * Prepares one opcode-801 selection against the selected character's exact equipped subclass.
  66. * The installed socket-entry table maps the request to whichever of the character's 5 authored
  67. * picks competes in the same group; no class-specific node indices are authored in State.
  68. */
  69. [[nodiscard]] bool prepare_subclass_selection(std::uint64_t subclassInstanceSoid,
  70. std::uint8_t requestedEntry,
  71. PendingSubclassSelection& mutation) noexcept;
  72. /** Produces the complete uncommitted account after-image for a prepared subclass selection. */
  73. [[nodiscard]] bool preview_subclass_selection(const PendingSubclassSelection& mutation,
  74. AccountState& after) noexcept;
  75. /** Commits a prepared subclass selection behind the exact full-character staleness guard. */
  76. [[nodiscard]] bool commit_subclass_selection(PendingSubclassSelection& mutation) noexcept;
  77. /**
  78. * Equips the "Emotes" collection item in each character's emote slot, with seeded default lanes.
  79. * Idempotent and safe from more than one boundary: a sound copy is left alone and a broken one is
  80. * repaired in place, keeping its instance identity.
  81. */
  82. [[nodiscard]] EmoteCollectionOutcome ensure_character_emote_collection() noexcept;
  83. /** Direction of one checked character equipment mutation. */
  84. enum class EquipmentMutationKind : std::uint8_t {
  85. none,
  86. equip,
  87. unequip,
  88. };
  89. /** Prepared character-inventory mutation kept private until its response and update both fit. */
  90. struct PendingEquipmentSwap {
  91. /** Exact prepare-time character view used as the commit staleness guard. */
  92. CharacterState beforeCharacter{};
  93. /** Canonical after-image, including every row-change mutation generation. */
  94. CharacterState afterCharacter{};
  95. std::uint64_t characterSoid{};
  96. std::uint64_t requestedInstanceSoid{};
  97. std::uint64_t previousInstanceSoid{};
  98. std::size_t characterIndex{};
  99. std::size_t equipmentSlotIndex{};
  100. std::size_t inventoryIndex{};
  101. std::uint8_t nativeEquipmentSlot{};
  102. EquipmentMutationKind kind{};
  103. bool prepared{};
  104. };
  105. /** Prepared selected-character inventory insertion kept private until its reply and push fit. */
  106. struct PendingItemAcquisition {
  107. CharacterState beforeCharacter{};
  108. CharacterState afterCharacter{};
  109. /** Profile material view, before and after charging the native requirement set. */
  110. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  111. beforeProfileItems{};
  112. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  113. afterProfileItems{};
  114. std::uint64_t accountSoid{};
  115. std::uint64_t characterSoid{};
  116. std::uint64_t acquiredInstanceSoid{};
  117. std::uint32_t acquiredDefinitionHash{};
  118. std::uint32_t materialRequirementSetHash{};
  119. std::size_t characterIndex{};
  120. std::size_t expectedInventoryCount{};
  121. std::size_t expectedProfileItemCount{};
  122. std::size_t afterProfileItemCount{};
  123. std::size_t inventoryIndex{};
  124. std::uint16_t collectibleIndex{};
  125. std::uint16_t inventoryRow{};
  126. std::uint8_t equipmentSlot{};
  127. std::uint8_t materialRequirementCount{};
  128. bool profileChanged{};
  129. /** Skips Collections revalidation for direct rewards. */
  130. bool directGrant{};
  131. build_data::items::QuestInitialization questInitialization{};
  132. std::int32_t previousQuestValue{};
  133. bool prepared{};
  134. /**
  135. * Account-scoped quest writes need an account update even when no materials were charged.
  136. * @return True for a profile inventory change or an unset account-scoped quest value.
  137. */
  138. [[nodiscard]] bool updates_account() const noexcept {
  139. return profileChanged
  140. || (questInitialization.scope
  141. == build_data::items::QuestInitialization::Scope::account
  142. && previousQuestValue == build_data::items::kUnsetQuestValue);
  143. }
  144. };
  145. /** One profile row an exchange changed, named the way the account's change ring names it. */
  146. struct ProfileStackChange {
  147. std::int32_t mutationSerial{};
  148. std::int32_t afterQuantity{};
  149. };
  150. /** Rows one exchange may announce. Shader recycling announces two: Glimmer and Legendary Shards. */
  151. inline constexpr std::size_t kProfileStackChangeCapacity = 4;
  152. /** Prepared account-profile stack insertion kept private until its reply and account upsert fit. */
  153. struct PendingProfileItemAcquisition {
  154. /** Exact profile inventory observed while preparing the mutation. */
  155. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  156. beforeItems{};
  157. /** Canonical profile inventory after incrementing or appending one stack. */
  158. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  159. afterItems{};
  160. std::uint64_t accountSoid{};
  161. /** Stable profile-row source identity, preserved for increments and allocated for appends. */
  162. std::uint64_t acquiredInstanceSoid{};
  163. std::uint32_t acquiredDefinitionHash{};
  164. std::uint32_t materialRequirementSetHash{};
  165. std::size_t expectedItemCount{};
  166. std::size_t afterItemCount{};
  167. std::size_t profileIndex{};
  168. std::int32_t previousQuantity{};
  169. std::int32_t acquiredQuantity{};
  170. std::int32_t previousMutationSerial{};
  171. std::int32_t acquiredMutationSerial{};
  172. std::uint16_t collectibleIndex{};
  173. std::uint8_t bucketId{};
  174. std::uint8_t materialRequirementCount{};
  175. /**
  176. * Rows this mutation announces to the account's change ring, which is the only way the Client
  177. * is told of a currency gain. Empty marks an ordinary acquisition, which announces its one
  178. * acquired row instead; non-empty marks an exchange.
  179. */
  180. std::array<ProfileStackChange, kProfileStackChangeCapacity> changes{};
  181. std::size_t changeCount{};
  182. /** True only for installed profile mod/shader rows materialized as Family-4 residents. */
  183. bool actionSource{};
  184. bool appended{};
  185. /** Skips Collections revalidation for direct rewards. */
  186. bool directGrant{};
  187. bool prepared{};
  188. };
  189. /** Shared batch capacity bounds one reward transaction. */
  190. inline constexpr std::size_t kRecordRewardGrantCapacity = build_data::rewards::kGrantCapacity;
  191. static_assert(kRecordRewardGrantCapacity >= build_data::records::kRewardPerRecordCapacity);
  192. /** One direct item requested by a record reward policy. */
  193. struct DirectRecordReward {
  194. std::uint16_t itemDefinitionIndex{};
  195. std::int32_t quantity{};
  196. std::span<const build_data::rewards::SocketOverride> sockets{};
  197. bool acquireUnlock{};
  198. };
  199. enum class RecordRewardKind : std::uint8_t {
  200. characterInstance,
  201. characterStack,
  202. profileStack,
  203. accountUnlock,
  204. };
  205. /** Native row identity of one item inside a prepared record-reward batch. */
  206. struct PreparedRecordReward {
  207. std::uint64_t instanceSoid{};
  208. std::uint32_t definitionHash{};
  209. std::size_t stateIndex{};
  210. std::int32_t quantity{};
  211. std::int32_t afterQuantity{};
  212. std::int32_t mutationSerial{};
  213. std::uint16_t inventoryRow{};
  214. RecordRewardKind kind{};
  215. bool appendedProfileResident{};
  216. std::uint16_t acquiredFlag{build_data::rewards::kAbsent};
  217. std::uint8_t previousFlag{};
  218. };
  219. /** One consumed wrapper's acquisition flag and the value its preparation observed. */
  220. struct PreparedRewardFlag {
  221. std::uint16_t index{};
  222. std::uint8_t previousValue{};
  223. };
  224. /** A reward grant that claims no record carries this instead of a record row. */
  225. inline constexpr std::uint16_t kUnclaimedRecordIndex = 0xFFFFU;
  226. /** Record claim and all of its item rows committed as one transaction. */
  227. struct PendingRecordRewardGrant {
  228. CharacterState beforeCharacter{};
  229. CharacterState afterCharacter{};
  230. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  231. beforeProfileItems{};
  232. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  233. afterProfileItems{};
  234. std::array<PreparedRecordReward, kRecordRewardGrantCapacity> rewards{};
  235. /** Sorted acquisition flags from wrappers which opened without remaining in inventory. */
  236. std::vector<PreparedRewardFlag> wrapperFlags;
  237. /** Record claimed with this grant, already written to the banks, or the unclaimed row. */
  238. std::uint16_t claimedRecordIndex{kUnclaimedRecordIndex};
  239. std::uint64_t accountSoid{};
  240. std::uint64_t characterSoid{};
  241. std::size_t characterIndex{};
  242. std::size_t beforeProfileItemCount{};
  243. std::size_t afterProfileItemCount{};
  244. std::size_t rewardCount{};
  245. bool prepared{};
  246. };
  247. /** How one reward preparation ended. */
  248. enum class RewardPreparation : std::uint8_t { prepared, deferred, unresolvable };
  249. /** One uncommitted Season reward, the native row it will claim, and the seed of its draw. */
  250. struct PendingSeasonPassReward {
  251. PendingRecordRewardGrant grant{};
  252. std::uint32_t sourceDefinitionHash{};
  253. std::uint64_t seed{};
  254. std::uint16_t rewardIndex{};
  255. bool prepared{};
  256. };
  257. /** One profile material actually credited by a prepared dismantle. */
  258. struct DismantleReward {
  259. std::uint32_t definitionHash{};
  260. std::size_t profileIndex{};
  261. std::int32_t quantity{};
  262. std::int32_t afterQuantity{};
  263. std::int32_t mutationSerial{};
  264. };
  265. /** Dismantle feedback can publish every bounded server-authored policy row. */
  266. inline constexpr std::size_t kDismantleRewardCapacity = kDismantleRewardPolicyCapacity;
  267. /** Prepared selected-character inventory removal kept private until its reply and push fit. */
  268. struct PendingItemDismantle {
  269. /** Exact prepare-time character view used as the commit staleness guard. */
  270. CharacterState beforeCharacter{};
  271. /** Canonical dense inventory after-image, including row-change mutation generations. */
  272. CharacterState afterCharacter{};
  273. /** Exact profile material view observed before and after applying the dismantle payout. */
  274. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  275. beforeProfileItems{};
  276. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  277. afterProfileItems{};
  278. std::array<DismantleReward, kDismantleRewardCapacity> rewards{};
  279. account::inventory::Item dismantledItem{};
  280. std::uint64_t accountSoid{};
  281. std::uint64_t characterSoid{};
  282. std::uint64_t dismantledInstanceSoid{};
  283. std::size_t characterIndex{};
  284. std::size_t expectedInventoryCount{};
  285. std::size_t expectedProfileItemCount{};
  286. std::size_t afterProfileItemCount{};
  287. std::size_t inventoryIndex{};
  288. std::size_t movedInventoryItemCount{};
  289. std::size_t rewardCount{};
  290. std::uint16_t inventoryRow{};
  291. std::uint8_t equipmentSlot{};
  292. bool profileChanged{};
  293. bool prepared{};
  294. };
  295. /** Prepared ordinary-socket selection for one selected-character item instance. */
  296. struct PendingSocketPlug {
  297. /** Exact prepare-time character view used as the commit staleness guard. */
  298. CharacterState beforeCharacter{};
  299. /** Canonical after-image. Only the target item's authored socket block differs. */
  300. CharacterState afterCharacter{};
  301. /** Exact account-wide material balances observed before applying the installed cost set. */
  302. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  303. beforeProfileItems{};
  304. /** Canonical material balances after every consuming row in the installed cost set. */
  305. std::array<account::inventory::ProfileItem, account::inventory::kProfileItemCapacity>
  306. afterProfileItems{};
  307. std::uint64_t accountSoid{};
  308. std::uint64_t characterSoid{};
  309. std::uint64_t targetInstanceSoid{};
  310. std::uint32_t targetDefinitionHash{};
  311. std::uint32_t plugDefinitionHash{};
  312. std::uint32_t materialRequirementSetHash{};
  313. std::size_t characterIndex{};
  314. std::size_t expectedProfileItemCount{};
  315. std::size_t afterProfileItemCount{};
  316. /** Equipment semantic index or dense inventory index, selected by `targetEquipped`. */
  317. std::size_t itemIndex{};
  318. std::uint16_t targetDefinitionIndex{};
  319. /** Plug that lands in the lane. Differs from the request only for a rolled socket. */
  320. std::uint16_t plugDefinitionIndex{};
  321. /** Plug the Client asked for, which decides the pool check and the material charge. */
  322. std::uint16_t requestedPlugDefinitionIndex{};
  323. std::uint16_t materialRequirementSetIndex{0xFFFFU};
  324. std::uint8_t socketLane{};
  325. std::uint8_t targetBucketId{};
  326. std::uint8_t plugBucketId{};
  327. std::uint8_t materialRequirementCount{};
  328. bool profileChanged{};
  329. bool targetEquipped{};
  330. bool prepared{};
  331. };
  332. /** Prepared accumulated item-state change for one selected-character item instance. */
  333. struct PendingItemState {
  334. CharacterState beforeCharacter{};
  335. CharacterState afterCharacter{};
  336. std::uint64_t characterSoid{};
  337. std::uint64_t targetInstanceSoid{};
  338. std::size_t characterIndex{};
  339. /** Equipment semantic index or dense inventory index, selected by `targetEquipped`. */
  340. std::size_t itemIndex{};
  341. std::uint16_t targetDefinitionIndex{};
  342. std::uint32_t beforeFlags{};
  343. std::uint32_t afterFlags{};
  344. bool targetEquipped{};
  345. bool prepared{};
  346. };
  347. /** Prepared artifact ownership transition for the selected character. */
  348. struct PendingArtifactPurchase {
  349. std::uint64_t accountSoid{};
  350. std::uint64_t characterSoid{};
  351. std::size_t characterIndex{};
  352. std::uint32_t beforeMask{};
  353. std::uint32_t afterMask{};
  354. std::uint16_t saleIndex{};
  355. bool prepared{};
  356. };
  357. /** Item residents whose authored artifact sockets were cleared by one reset. */
  358. struct ArtifactResetResult {
  359. std::array<std::uint64_t,
  360. account::inventory::kEquipmentSlotCount + account::inventory::kCharacterItemCapacity>
  361. instanceSoids{};
  362. std::size_t instanceCount{};
  363. };
  364. /** Result of validating one sparse settings writeback against authoritative State. */
  365. enum class SettingsUpdateDisposition : std::uint8_t {
  366. rejected,
  367. acceptedNoChange,
  368. preparedMutation,
  369. };
  370. /** Complete checked settings before/after images held until the BAP transaction commits. */
  371. struct PendingSettingsUpdate {
  372. account::settings::AccountSettings beforeSettings{};
  373. account::settings::AccountSettings afterSettings{};
  374. std::uint64_t accountSoid{};
  375. bool prepared{};
  376. };
  377. /**
  378. * Loads cached build data and generates secrets with Sunrise's authored activity defaults.
  379. * @param module Loaded Sunrise module, or null to disable disk persistence.
  380. * @return True when the cached data passes its checks and every secret is generated.
  381. */
  382. [[nodiscard]] bool initialize(void* module = nullptr) noexcept;
  383. /**
  384. * Loads cached build data and publishes fixed activity defaults in one step.
  385. * @param module Loaded Sunrise module, or null to disable disk persistence.
  386. * @param activityDefaults Complete local fallback policy from immutable Core settings.
  387. * @return True when account, defaults, cached data, and generated secrets are valid.
  388. */
  389. [[nodiscard]] bool
  390. initialize(void* module, const activity::defaults::ActivityDefaults& activityDefaults) noexcept;
  391. /** Securely clears State, including activity destinations and matchmaking descriptors. */
  392. void shutdown() noexcept;
  393. /** @return Immutable generated SignOn session fields. */
  394. [[nodiscard]] const SignOnState& sign_on() noexcept;
  395. [[nodiscard]] bool publish_bootstrap_token(std::span<const std::byte> token) noexcept;
  396. /**
  397. * Records when the account signed in.
  398. * Every character record publishes this as its last applied daily and weekly reset.
  399. * @param seconds Unix seconds taken when the SignOn success is answered.
  400. */
  401. void publish_sign_in_time(std::uint64_t seconds) noexcept;
  402. /** @return Immutable generated BAP session fields. */
  403. [[nodiscard]] const BapState& bap() noexcept;
  404. /**
  405. * Generates one connection's own secure-channel material.
  406. * Two links sharing a key and a starting nonce would encrypt different plaintexts under the same
  407. * pair, so every accepted connection gets its own.
  408. * @param output Cleared, then filled with a fresh nonce, session key and envelope IV.
  409. * @return True when the system generated every byte.
  410. */
  411. [[nodiscard]] bool new_bap_session(BapState& output) noexcept;
  412. /**
  413. * Stores the active nonzero account key when the account remains complete.
  414. * @param primarySoid Account key selected by the local Client.
  415. * @return False when the key or resulting account State is invalid.
  416. */
  417. [[nodiscard]] bool set_primary_soid(std::uint64_t primarySoid) noexcept;
  418. /**
  419. * Permanently closes the process-local one-time profile-setup gate for the active account.
  420. *
  421. * The transition is monotonic: repeated profile-setting writes after completion are harmless.
  422. * @return False only when no complete active account can be updated.
  423. */
  424. [[nodiscard]] bool complete_profile_setup() noexcept;
  425. /**
  426. * Moves the selection to one authored character.
  427. * The Client names its pick only in the select-character request, so this is where a player's
  428. * choice enters State.
  429. * @param characterSoid Picked character key, which must name an authored character.
  430. * @return False when no authored character carries that key.
  431. */
  432. [[nodiscard]] bool set_selected_character(std::uint64_t characterSoid) noexcept;
  433. /**
  434. * Stores the selected character's equipped title row. The caller proves the record is a claimed
  435. * title; this only writes it.
  436. * @param recordIndex Title record row, or kUnequippedTitleRecordIndex to clear it.
  437. * @param characterSoid Receives the selected character's key, or zero on failure.
  438. * @param changed Receives whether the stored row moved.
  439. * @return False when no character is selected or the account would stop being valid.
  440. */
  441. [[nodiscard]] bool
  442. set_selected_title(std::uint16_t recordIndex, std::uint64_t& characterSoid, bool& changed) noexcept;
  443. /**
  444. * Prepares an equip operation for one unequipped instance on the selected character.
  445. * An occupied slot is swapped; an empty semantic slot receives the requested item directly.
  446. * @param requestedInstanceSoid Unequipped item instance selected by the Client.
  447. * @param mutation Gets the checked after-image without changing account State.
  448. * @return True when the instance is owned, unequipped, and maps to one native equipment slot.
  449. */
  450. [[nodiscard]] bool prepare_equipment_swap(std::uint64_t requestedInstanceSoid,
  451. PendingEquipmentSwap& mutation) noexcept;
  452. /**
  453. * Prepares an unequip operation for one equipped selected-character instance.
  454. * The item is inserted before existing inventory items in its native bucket so their published
  455. * rows remain stable. Native slots without a proven semantic State mapping are rejected.
  456. *
  457. * @param requestedInstanceSoid Equipped item instance selected by the Client.
  458. * @param mutation Gets the checked after-image without changing account State.
  459. * @return True when the instance is equipped and the dense character inventory has room.
  460. */
  461. [[nodiscard]] bool prepare_equipment_unequip(std::uint64_t requestedInstanceSoid,
  462. PendingEquipmentSwap& mutation) noexcept;
  463. /**
  464. * Commits a prepared equipment mutation only while the full captured character still matches.
  465. *
  466. * @param mutation Prepared mutation, always cleared before this function returns.
  467. * @return True when the equip or unequip commits atomically and leaves the account valid.
  468. */
  469. [[nodiscard]] bool commit_equipment_swap(PendingEquipmentSwap& mutation) noexcept;
  470. /**
  471. * Prepares one installed equippable definition as a new selected-character inventory instance.
  472. *
  473. * Native-default sockets, a unique runtime SOID, and the selected character's current item level
  474. * are used. Full loadout resolution is the authoritative bucket-capacity check.
  475. *
  476. * @param collectibleIndex Collections row that owns the item, or kNoCollectibleIndex.
  477. * @param definitionHash Installed item definition requested by the Client.
  478. * @param price A vendor sale row's cost entries, spent in place of the collectible's material
  479. * set; absent for a Collections pull, which pays with the collectible's materials.
  480. * @param mutation Gets a checked after-image without changing account State.
  481. * @return True when the item and every existing loadout row resolve with one free native row.
  482. */
  483. [[nodiscard]] bool
  484. prepare_item_acquisition(std::uint16_t collectibleIndex,
  485. std::uint32_t definitionHash,
  486. std::optional<std::span<const build_data::vendors::SaleCost>> price,
  487. PendingItemAcquisition& mutation) noexcept;
  488. /** Prepares a direct character-item grant without a Collections charge. */
  489. [[nodiscard]] bool prepare_item_acquisition_for_item(std::uint16_t itemDefinitionIndex,
  490. PendingItemAcquisition& mutation) noexcept;
  491. /** Atomically commits one prepared reward grant and its durable Season claim. */
  492. [[nodiscard]] bool commit_season_pass_reward(PendingSeasonPassReward& mutation) noexcept;
  493. /**
  494. * Resolves an installed item or wrapper for the selected character.
  495. * @param seed Caller-drawn entropy for any wrapper draws.
  496. * @param refusal Receives a static reason on failure.
  497. */
  498. [[nodiscard]] RewardPreparation prepare_item_reward(std::uint16_t itemIndex,
  499. std::uint32_t quantity,
  500. std::uint64_t seed,
  501. PendingRecordRewardGrant& mutation,
  502. const char** refusal = nullptr) noexcept;
  503. /**
  504. * Prepares an eligible native pass row without writing its claim or acquisition flags.
  505. * @param seed Caller-drawn entropy; the commit replays the same draw from it.
  506. * @param refusal Receives a static reason on failure.
  507. */
  508. [[nodiscard]] bool prepare_season_pass_reward(std::uint16_t rewardIndex,
  509. std::uint64_t seed,
  510. PendingSeasonPassReward& mutation,
  511. const char** refusal = nullptr) noexcept;
  512. /** Applies a prepared grant's acquisition flags to the saved banks; false if one moved. */
  513. [[nodiscard]] bool preview_reward_unlocks(const PendingRecordRewardGrant& mutation,
  514. unlocks::Table& after) noexcept;
  515. /** Atomically commits one prepared Triumph reward and its durable record claim. */
  516. [[nodiscard]] bool commit_record_reward(PendingRecordRewardGrant& mutation) noexcept;
  517. /**
  518. * Prepares all direct reward rows over one shared account after-image.
  519. * @param rewards Item rows the record grants.
  520. * @param claimedRecordIndex Record already claimed in the banks, or kUnclaimedRecordIndex.
  521. * @param mutation Receives the prepared grant.
  522. * @return Prepared when every row fits the account after-image.
  523. */
  524. [[nodiscard]] RewardPreparation
  525. prepare_record_reward_grant(std::span<const DirectRecordReward> rewards,
  526. std::uint16_t claimedRecordIndex,
  527. PendingRecordRewardGrant& mutation,
  528. const char** refusal = nullptr) noexcept;
  529. /** Builds the full account after-image while a record reward remains current. */
  530. [[nodiscard]] bool preview_record_reward_grant(const PendingRecordRewardGrant& mutation,
  531. AccountState& after) noexcept;
  532. /** Reserves the selected character's next mutation serial for a transient inventory update. */
  533. [[nodiscard]] bool
  534. reserve_selected_character_inventory_serial(std::int32_t& mutationSerial) noexcept;
  535. /**
  536. * Preview inventory and quest values together without changing the save.
  537. * @param mutation Prepared acquisition checked against current saved state.
  538. * @param after Receives the candidate account; use only on success.
  539. * @param afterUnlocks Receives matching account and selected-character unlocks on success.
  540. * @return False when the acquisition is stale or its saved unlocks cannot be read.
  541. */
  542. [[nodiscard]] bool preview_item_acquisition(const PendingItemAcquisition& mutation,
  543. AccountState& after,
  544. unlocks::Table& afterUnlocks) noexcept;
  545. /**
  546. * Inventory and first-step state share one transaction; failure rolls both back.
  547. * @param mutation Prepared grant consumed on either success or failure.
  548. * @return True when both writes commit against the unchanged prepared state.
  549. */
  550. [[nodiscard]] bool commit_item_acquisition(PendingItemAcquisition& mutation) noexcept;
  551. /**
  552. * Prepares one installed profile-owned stackable definition for a Collections pull.
  553. *
  554. * An existing non-full stack is incremented. Otherwise a new dense State entry is appended only
  555. * when the installed profile bucket still owns a free native row.
  556. *
  557. * @param collectibleIndex Collections row that owns the item, or kNoCollectibleIndex.
  558. * @param definitionHash Installed stackable definition requested by the Client.
  559. * @param price A vendor sale row's cost entries, spent in place of the collectible's material
  560. * set; absent for a Collections pull, which pays with the collectible's materials.
  561. * @param mutation Gets the checked profile before/after images without changing account State.
  562. * @return True when the definition belongs to the main profile array and one unit fits.
  563. */
  564. [[nodiscard]] bool prepare_profile_item_acquisition(
  565. std::uint16_t collectibleIndex,
  566. std::uint32_t definitionHash,
  567. std::optional<std::span<const build_data::vendors::SaleCost>> price,
  568. PendingProfileItemAcquisition& mutation) noexcept;
  569. /** Prepares a direct profile-stack grant without a Collections charge. */
  570. [[nodiscard]] bool
  571. prepare_profile_item_acquisition_for_item(std::uint16_t itemDefinitionIndex,
  572. std::int32_t quantity,
  573. PendingProfileItemAcquisition& mutation) noexcept;
  574. /**
  575. * Materializes a prepared profile acquisition over the current account only while its complete
  576. * profile-inventory view is unchanged. This is the account object encoded before commit.
  577. *
  578. * @param mutation Prepared mutation that remains owned by the transaction.
  579. * @param after Gets the exact full-account after-image used by the Family-4 upsert.
  580. * @return True when the mutation is whole and its prepare-time profile remains current.
  581. */
  582. [[nodiscard]] bool preview_profile_item_acquisition(const PendingProfileItemAcquisition& mutation,
  583. AccountState& after) noexcept;
  584. /**
  585. * Commits a prepared profile stack insertion only while its prepare-time profile remains current.
  586. *
  587. * @param mutation Prepared mutation, always cleared before this function returns.
  588. * @return True when the stack update commits atomically and leaves the whole account valid.
  589. */
  590. [[nodiscard]] bool
  591. commit_profile_item_acquisition(PendingProfileItemAcquisition& mutation) noexcept;
  592. /**
  593. * Prepares removal of one unequipped instance from the selected character.
  594. * The authored inventory prefix is compacted. Any surviving item whose installed native row
  595. * changes receives a fresh mutation generation. Equipped items are never accepted.
  596. * @param instanceSoid Unequipped item-instance key selected by the Client.
  597. * @param mutation Gets checked before/after images without changing account State.
  598. * @return True when the selected character uniquely owns it and both loadouts resolve.
  599. */
  600. [[nodiscard]] bool prepare_item_dismantle(std::uint64_t instanceSoid,
  601. PendingItemDismantle& mutation) noexcept;
  602. /** Builds the exact account after-image while a prepared dismantle remains current. */
  603. [[nodiscard]] bool preview_item_dismantle(const PendingItemDismantle& mutation,
  604. AccountState& after) noexcept;
  605. /**
  606. * Commits a prepared inventory removal only while the complete prepare-time character view is
  607. * unchanged.
  608. *
  609. * @param mutation Prepared mutation, always cleared before this function returns.
  610. * @return True when the removal commits atomically and leaves the whole account valid.
  611. */
  612. [[nodiscard]] bool commit_item_dismantle(PendingItemDismantle& mutation) noexcept;
  613. /**
  614. * Prepares one exact opcode-903 ordinary-socket selection on a selected-character item.
  615. * The target may be equipped or unequipped. Native defaults are materialized into a complete
  616. * authored socket block, then only the requested lane changes; everything else stays byte-stable.
  617. * @param targetInstanceSoid Selected-character item-instance key named by the Client.
  618. * @param socketLane Zero-based ordinary socket lane.
  619. * @param plugDefinitionIndex Installed plug-definition row selected by the Client.
  620. * @param mutation Gets the checked before/after images without changing account State.
  621. * @return True when ownership, item detail, lane, plug compatibility, and both loadouts validate.
  622. */
  623. [[nodiscard]] bool prepare_socket_plug(std::uint64_t targetInstanceSoid,
  624. std::uint8_t socketLane,
  625. std::uint16_t plugDefinitionIndex,
  626. PendingSocketPlug& mutation) noexcept;
  627. /**
  628. * Prepares one ordinary-socket selection for the item instance opcode 1901 names.
  629. * @param targetInstanceSoid Whole item-instance identity carried by opcode 1901.
  630. * @param requestedSocketLane Native socket action lane; compatibility resolves the physical lane.
  631. * @param plugDefinitionIndex Installed plug-definition row selected by the Client.
  632. * @param mutation Gets the checked before/after images without changing account State.
  633. * @return True when one item matches, the plug resolves to that lane, and the transition is valid.
  634. */
  635. [[nodiscard]] bool prepare_equipped_socket_plug(std::uint64_t targetInstanceSoid,
  636. std::uint8_t requestedSocketLane,
  637. std::uint16_t plugDefinitionIndex,
  638. PendingSocketPlug& mutation) noexcept;
  639. /** Produces the complete uncommitted account after-image for a prepared socket transaction. */
  640. [[nodiscard]] bool preview_socket_plug(const PendingSocketPlug& mutation,
  641. AccountState& after) noexcept;
  642. /**
  643. * Commits a prepared socket selection only while the complete prepare-time character is unchanged.
  644. * @param mutation Prepared mutation, always cleared before this function returns.
  645. * @return True when the exact canonical transition commits atomically.
  646. */
  647. [[nodiscard]] bool commit_socket_plug(PendingSocketPlug& mutation) noexcept;
  648. /** Prepares one complete native item-state value for an owned selected-character instance. */
  649. [[nodiscard]] bool prepare_item_state(std::uint64_t targetInstanceSoid,
  650. std::uint16_t targetDefinitionIndex,
  651. std::uint32_t flags,
  652. PendingItemState& mutation) noexcept;
  653. /** Commits one prepared item-state change behind an exact full-character staleness guard. */
  654. [[nodiscard]] bool commit_item_state(PendingItemState& mutation) noexcept;
  655. /**
  656. * Merges and validates a sparse WS-701 settings update without publishing it.
  657. * @param delta Supported fields decoded from one reflected settings request.
  658. * @param mutation Receives a complete before/after pair only when State would change.
  659. * @return Rejection, an accepted no-op, or a prepared mutation.
  660. */
  661. [[nodiscard]] SettingsUpdateDisposition
  662. prepare_settings_update(const account::settings::SettingsDelta& delta,
  663. PendingSettingsUpdate& mutation) noexcept;
  664. /**
  665. * Publishes one prepared settings after-image behind account-key and settings staleness guards.
  666. * @param mutation Prepared update, always cleared before this function returns.
  667. * @return True when the after-image was already current or was committed successfully.
  668. */
  669. [[nodiscard]] bool commit_settings_update(PendingSettingsUpdate& mutation) noexcept;
  670. /** One credited side of a vendor exchange: an authored profile stack and how much to add. */
  671. struct ProfileExchangePayout {
  672. std::uint32_t definitionHash{};
  673. std::int32_t quantity{};
  674. };
  675. /**
  676. * Prepares one vendor recycle row: charges the stack it names and credits what it pays out.
  677. * It rides the profile-stack mutation because the change ring is the only way the Client is told
  678. * of a gain. Each credited row gets a fresh serial; only an already-held payout stack is credited.
  679. * @param costDefinitionHash Stack the row charges against.
  680. * @param costQuantity Units of it the row consumes.
  681. * @param payouts Stacks to credit, each clamped to its own native stack limit.
  682. * @param mutation Gets the checked profile before/after images without changing account State.
  683. * @return True only when the charge and every credit fit and the whole account stayed valid.
  684. */
  685. [[nodiscard]] bool prepare_vendor_exchange(std::uint32_t costDefinitionHash,
  686. std::int32_t costQuantity,
  687. std::span<const ProfileExchangePayout> payouts,
  688. PendingProfileItemAcquisition& mutation) noexcept;
  689. /** @return A copy of the active account state, read under the lock. */
  690. [[nodiscard]] AccountState account_snapshot() noexcept;
  691. /**
  692. * Copies the evaluated content state and adds build-derived catalyst completion overrides.
  693. * @param output Receives one complete Family-5 snapshot on success.
  694. * @return False when the fixed override banks cannot hold the complete state.
  695. */
  696. [[nodiscard]] bool investment_snapshot(InvestmentState& output) noexcept;
  697. /** Seasonal artifact item definition, whose equipped row carries the Power bonus stat. */
  698. inline constexpr std::uint32_t kSeasonalArtifactItemHash = 0x613A3DA6U;
  699. /** Native progression row carrying the seasonal artifact Power ladder. */
  700. inline constexpr std::uint16_t kArtifactPowerProgressionIndex = 38;
  701. /** Native progression row carrying the seasonal artifact unlock-point ladder. */
  702. inline constexpr std::uint16_t kArtifactUnlockProgressionIndex = 39;
  703. /** @return Seasonal XP published in the account progression bank. */
  704. [[nodiscard]] std::int32_t seasonal_experience() noexcept;
  705. /**
  706. * Publishes every seasonal value the seeded XP and artifact ownership imply.
  707. * An owed earned-perk walk runs here too.
  708. */
  709. [[nodiscard]] bool seed_seasonal_progression() noexcept;
  710. /** @return One-based Season of Arrivals rank the published XP earns. */
  711. [[nodiscard]] std::uint16_t seasonal_rank() noexcept;
  712. /** @return Account-wide Power bonus published by the seasonal artifact. */
  713. [[nodiscard]] std::uint16_t artifact_power_bonus() noexcept;
  714. /**
  715. * Adds base XP to the seasonal lanes and republishes every value derived from the total,
  716. * including earned-perk flags when a rank is reached or a walk is owed.
  717. * @param amount Positive XP to grant.
  718. * @return False when the amount is not positive or the total would overflow.
  719. */
  720. [[nodiscard]] bool grant_seasonal_experience(std::int32_t amount) noexcept;
  721. /** @param rewardIndex Native reward-array index. @return True when the row is claimed. */
  722. [[nodiscard]] bool season_pass_reward_claimed(std::uint16_t rewardIndex) noexcept;
  723. /**
  724. * Claims one Season pass reward row into the account flag its row names.
  725. * @param rewardIndex Native reward-array index.
  726. * @return False when the row names no flag or is already claimed.
  727. */
  728. [[nodiscard]] bool claim_season_pass_reward(std::uint16_t rewardIndex) noexcept;
  729. /** Undoes one Season pass claim so a refused commit cannot leave it held. */
  730. void revoke_season_pass_reward(std::uint16_t rewardIndex) noexcept;
  731. /** @return Purchased artifact sale rows, one bit per row. */
  732. [[nodiscard]] std::uint32_t artifact_mod_mask() noexcept;
  733. /** Replaces the exact published artifact mask, refusing when it already moved. */
  734. [[nodiscard]] bool replace_artifact_mod_mask(std::uint32_t expected,
  735. std::uint32_t replacement) noexcept;
  736. /** Writes one affordable artifact purchase and keeps its before-image for the commit. */
  737. [[nodiscard]] bool prepare_artifact_mod_unlock(std::uint16_t saleIndex,
  738. PendingArtifactPurchase& mutation) noexcept;
  739. /** Keeps a prepared artifact purchase only while its character and mask are still current. */
  740. [[nodiscard]] bool commit_artifact_mod_unlock(PendingArtifactPurchase& mutation) noexcept;
  741. /** Charges Glimmer, removes artifact mods, and refunds every spent unlock point. */
  742. [[nodiscard]] bool reset_artifact(std::int32_t glimmerCost, ArtifactResetResult& result) noexcept;
  743. } // namespace sunrise::state