roster_intersection.h 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. #pragma once
  2. #include <array>
  3. #include <cstddef>
  4. #include <cstdint>
  5. #include <span>
  6. #include "scenario_reader.h"
  7. namespace sunrise::middleware::content::packages::tables {
  8. /**
  9. * A destination reaches at most this many slice sets.
  10. * Slice-set indices are spaced by the slice-set factor across a 512-wide region space, so this
  11. * is the hard bound. The widest installed destination reaches 47.
  12. */
  13. inline constexpr std::size_t kSliceSetCapacity = 64;
  14. /**
  15. * Roster keys tracked for one destination.
  16. * No installed destination reaches more than 2 objects carrying a wire slot type. This leaves
  17. * room to spare without a heap allocation.
  18. */
  19. inline constexpr std::size_t kRosterKeyCapacity = 16;
  20. static_assert(kSliceSetCapacity * kSliceSetIndexFactor == 512);
  21. // One bit per slice set, so the mask must cover the whole capacity.
  22. static_assert(kSliceSetCapacity == 64);
  23. /**
  24. * Which authored states and bubbles each candidate key appears in.
  25. * A key is safe to send for a bubble only when every authored state of that bubble carries it.
  26. * Two states may share one slice-set index, so a region-bit union alone is not sufficient.
  27. */
  28. struct RosterIntersection {
  29. std::array<std::uint32_t, kRosterKeyCapacity> keys{};
  30. /** Bubbles where each key appeared in at least one state. */
  31. std::array<std::uint64_t, kRosterKeyCapacity> masks{};
  32. /** Number of authored states carrying each key, over the whole destination. */
  33. std::array<std::size_t, kRosterKeyCapacity> keyStateCounts{};
  34. /** Per-key authored-state counts in each bubble. */
  35. std::array<std::array<std::size_t, kSliceSetCapacity>, kRosterKeyCapacity>
  36. keyBubbleStateCounts{};
  37. /** Last authored-state serial that recorded each key, to collapse registry duplicates. */
  38. std::array<std::size_t, kRosterKeyCapacity> lastKeyState{};
  39. /** Number of authored states observed in each bubble. */
  40. std::array<std::size_t, kSliceSetCapacity> bubbleStateCounts{};
  41. std::size_t keyCount{};
  42. std::size_t stateCount{};
  43. std::uint32_t currentBubble{};
  44. /** Every slice set observed, whichever key carried it. */
  45. std::uint64_t observedSets{};
  46. /** Set after a valid authored-state observation and cleared only with the accumulator. */
  47. bool stateOpen{};
  48. /** Set when a key or a slice set did not fit, which makes the result unusable. */
  49. bool overflowed{};
  50. /** Set when a state's slice set could not be read, which makes every key unsafe. */
  51. bool unresolvedSet{};
  52. };
  53. /**
  54. * Records a slice set whose entry could not be read.
  55. * The destination still moves into that slice set and no key can be proved present in it, so no
  56. * key of this destination is safe to send afterwards.
  57. * @param state Accumulator for one destination.
  58. */
  59. void observe_unresolved_slice_set(RosterIntersection& state) noexcept;
  60. /**
  61. * The slot types that make an object worth publishing as a roster group.
  62. * Only 56 installed objects declare any of them, and the key limit above holds only for that
  63. * filtered set. Feeding every placed object instead overflows most destinations.
  64. */
  65. /**
  66. * Object keys admitted whatever slot types they declare.
  67. *
  68. * The slot-type filter below is what decides which placed objects become roster groups, and a
  69. * placement trace shows it admitting **68 of 5986** objects overall and **1 of 32** across the
  70. * whole raid. Bubble 14 -- the Wall of Wishes room -- has exactly two objects, `0x101DECCF`
  71. * (785 slots) and `0x432A36E6` (21 slots), and neither declares an admitted type, so the host
  72. * sends no per-object data for that bubble at all while the client builds its twenty panels
  73. * locally and never shows them.
  74. *
  75. * Widening the type list is not the way to test that: only 56 installed objects declare any of the
  76. * nine types, the key limit holds only for that filtered set, and admitting common types overflows
  77. * `kRosterKeyCapacity` on most destinations, which makes a destination publish ZERO groups. Naming
  78. * one key instead adds a single group to one destination -- 3 keys become 4 of 16, and 21 slots sit
  79. * well inside `kRosterSlotCapacity` -- so the experiment is bounded and reversible.
  80. *
  81. * The 785-slot container is deliberately NOT listed: its slots would each carry a header and the
  82. * roster body is already 976 bytes, so it risks the message size rather than testing the idea.
  83. */
  84. inline constexpr std::array<std::uint32_t, 1> kForcedRosterKeys = {0x432A36E6U};
  85. inline constexpr std::array<std::uint16_t, 9> kRosterSlotTypes = {
  86. 8, 13, 16, 17, 21, 35, 37, 41, 67};
  87. /**
  88. * Tests whether one placed object is a roster candidate.
  89. * @param object Whole placed-object bytes.
  90. * @return True when it declares a slot of one of the wire types.
  91. */
  92. [[nodiscard]] bool carries_roster_slot(std::span<const std::byte> object) noexcept;
  93. /**
  94. * Begins one authored state in a slice set, whether or not it holds a roster object.
  95. * Repeated slice-set indices are distinct states and all count toward bubble safety.
  96. * @param state Accumulator for one destination.
  97. * @param sliceSetIndex Slice-set index as the entry reports it, already scaled by the factor.
  98. * @return True when the index is inside the region space.
  99. */
  100. [[nodiscard]] bool observe_slice_set(RosterIntersection& state,
  101. std::uint32_t sliceSetIndex) noexcept;
  102. /**
  103. * Records that one key appears in the current authored state.
  104. * @param state Accumulator for one destination.
  105. * @param sliceSetIndex Slice-set index as the entry reports it, already scaled by the factor.
  106. * @param objectKey Registry key of the placed object.
  107. * @return True when the observation matches the current state and was recorded.
  108. */
  109. [[nodiscard]] bool observe_roster_key(RosterIntersection& state,
  110. std::uint32_t sliceSetIndex,
  111. std::uint32_t objectKey) noexcept;
  112. /**
  113. * Reports the keys present in every authored state of the destination.
  114. * @param state Accumulator for one destination.
  115. * @param output Receives the safe keys.
  116. * @param count Receives how many were written.
  117. * @return True when nothing overflowed and every safe key fits the output.
  118. */
  119. [[nodiscard]] bool safe_roster_keys(const RosterIntersection& state,
  120. std::span<std::uint32_t> output,
  121. std::size_t& count) noexcept;
  122. /**
  123. * Reports keys present in every authored state of some bubbles and not the whole destination.
  124. * A key missing from even one state of a bubble is excluded from that bubble's sub-block.
  125. * @param state Accumulator for one destination.
  126. * @param keys Receives the partially present keys.
  127. * @param masks Receives each key's bubbles, one bit per bubble index, in the same order.
  128. * @param count Receives how many were written.
  129. * @return True when nothing overflowed and every partial key fits the output.
  130. */
  131. [[nodiscard]] bool partial_roster_keys(const RosterIntersection& state,
  132. std::span<std::uint32_t> keys,
  133. std::span<std::uint64_t> masks,
  134. std::size_t& count) noexcept;
  135. } // namespace sunrise::middleware::content::packages::tables