runtime.h 38 KB

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