ending_retirement_test.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #include <array>
  2. #include <cassert>
  3. #include "../Sunrise/src/client/hooks/ember_movies/readiness_rules.h"
  4. #include "../Sunrise/src/client/hooks/ember_movies/sunburn_rules.h"
  5. #include "../Sunrise/src/client/hooks/ember_movies/playback_rules.h"
  6. #include "../Sunrise/src/client/hooks/ember_movies/surface_rules.h"
  7. #include "../Sunrise/src/client/hooks/ember_movies/orbit_rules.h"
  8. #include "../Sunrise/src/client/hooks/mission_retirement/mission_retirement.h"
  9. #include "../Sunrise/src/middleware/bap/activity_message/roster_presence.h"
  10. #include "../Sunrise/src/middleware/encoding/bit_reader.h"
  11. #include "../Sunrise/src/state/activity/membership/teleport_rules.h"
  12. #include "../Sunrise/src/core/logging/log.h"
  13. namespace sunrise::core::log {
  14. bool accepts(Channel, Level) noexcept { return false; }
  15. void write(Channel, Level, std::string_view) noexcept {}
  16. }
  17. namespace wire = sunrise::middleware::bap::activity_message::sensor_auth_update;
  18. namespace retire = sunrise::client::hooks::mission_retirement;
  19. namespace member = sunrise::state::activity::membership;
  20. using sunrise::middleware::encoding::bits::Reader;
  21. static void expect(Reader& reader, unsigned width, std::uint64_t expected) {
  22. std::uint64_t value{};
  23. assert(reader.read(static_cast<std::uint8_t>(width), value) && value == expected);
  24. }
  25. int main() {
  26. namespace movies=sunrise::client::hooks::ember_movies;
  27. // The first visible-video run reached native EOF, but the retained textures
  28. // blocked request 2. A same-owner EOF authorizes reuse, not cache eviction.
  29. const movies::Owner movieOwner{12,34};
  30. assert(movies::can_chain_movies(movieOwner,movieOwner,movieOwner,1,2,movies::Status::complete));
  31. assert(!movies::can_chain_movies(movieOwner,{13,34},movieOwner,1,2,movies::Status::complete));
  32. assert(!movies::can_chain_movies(movieOwner,{12,35},movieOwner,1,2,movies::Status::complete));
  33. assert(!movies::can_chain_movies(movieOwner,movieOwner,{},1,2,movies::Status::complete));
  34. assert(!movies::can_chain_movies({}, {}, {},1,2,movies::Status::complete));
  35. for (auto state : {movies::Status::queued,movies::Status::preparing,movies::Status::playing,
  36. movies::Status::failed,movies::Status::absent})
  37. assert(!movies::can_chain_movies(movieOwner,movieOwner,movieOwner,1,2,state));
  38. assert(!movies::can_chain_movies(movieOwner,movieOwner,movieOwner,2,1,movies::Status::complete));
  39. assert(!movies::can_chain_movies(movieOwner,movieOwner,movieOwner,1,1,movies::Status::complete));
  40. // Native presentation selects the cinematic category, preserving higher
  41. // priority UI states. Playback completion restores the original selection.
  42. for (int ui=-1;ui<=0x2F;++ui) {
  43. assert(movies::movie_ui_state(ui,false)==ui);
  44. const bool gameplay=ui==0x2B || ui==0x2D || ui==0x2F;
  45. assert(movies::movie_ui_state(ui,true)==(gameplay ? 0x21 : ui));
  46. }
  47. assert(movies::movie_ui_state(0x22,true)==0x22); // An actual loading request keeps priority.
  48. movies::OrbitReturn orbit;
  49. movies::OrbitObservation inApex{true,true,false,false,true,0,38,38,true};
  50. using OA=movies::OrbitAction;
  51. assert(orbit.observe(1000,inApex)==OA::none); // Playing movies cannot arm return.
  52. orbit.arm(1000);
  53. assert(orbit.observe(9000,inApex)==OA::none); // Host must actually publish completion.
  54. inApex.complete=true;
  55. assert(orbit.observe(10000,inApex)==OA::select); // First completion frame: no banner delay.
  56. assert(orbit.observe(10001,inApex)==OA::none); // No duplicate commit.
  57. auto inOrbit=inApex; inOrbit.emberSelected=false;inOrbit.orbitSelected=true;
  58. inOrbit.exactOwner=false; // Committing orbit may already retire the old ActivityClient link.
  59. assert(orbit.observe(10002,inOrbit)==OA::cleanup); // Requires destination readback.
  60. assert(orbit.observe(10003,inOrbit)==OA::none); // No repeated cleanup request.
  61. movies::OrbitObservation betweenWorlds{};betweenWorlds.step=28;
  62. assert(orbit.observe(10004,betweenWorlds)==OA::none);
  63. assert(orbit.active());
  64. inOrbit.step=29;
  65. assert(orbit.observe(10100,inOrbit)==OA::orbitSetup);
  66. assert(!orbit.active() && orbit.observe(20000,inApex)==OA::none);
  67. orbit.arm(30000);
  68. auto stale=inApex;stale.exactOwner=false;
  69. assert(orbit.observe(31000,stale)==OA::canceled);
  70. orbit.arm(40000);
  71. auto departed=inApex;departed.step=28;
  72. assert(orbit.observe(41000,departed)==OA::canceled);
  73. orbit.arm(50000);inApex.complete=false;
  74. assert(orbit.observe(140001,inApex)==OA::timedOut);
  75. orbit.arm(150000);inApex.complete=true;
  76. inApex.ready=false;
  77. assert(orbit.observe(150000,inApex)==OA::none);
  78. inApex.ready=true;
  79. assert(orbit.observe(150001,inApex)==OA::select); // No delay once native readiness returns.
  80. assert(orbit.observe(150002,departed)==OA::none); // Native cleanup already began.
  81. assert(orbit.observe(150100,inOrbit)==OA::orbitSetup);
  82. orbit.arm(160000);
  83. inApex.pendingStep=28;
  84. assert(orbit.observe(160000,inApex)==OA::none);
  85. inApex.pendingStep=38;
  86. assert(orbit.observe(160001,inApex)==OA::select);
  87. auto replaced=inOrbit;replaced.step=38;replaced.sameWorld=false;
  88. assert(orbit.observe(160002,replaced)==OA::canceled);
  89. // a47adbd: container 80BCA022 loaded, but definition 80BCA021 was
  90. // FEFE0000 / 001044FB / null. Native 1204163 faulted reading its slot.
  91. assert(!movies::movie_definition_resident(0xFEFE0000,0x1044FB,0));
  92. assert(!movies::movie_definition_resident(0xFEFE0000,0x1044FB,0x1234000));
  93. assert(!movies::movie_definition_resident(0xC0000010,0x44FB,0));
  94. assert(!movies::movie_definition_resident(0xC0000008,0x44FB,0x1234000));
  95. assert(!movies::movie_definition_resident(0xC0000010,0x254FB,0x1234000)); // raw buffer is not a definition
  96. assert(movies::movie_definition_resident(0xC0000010,0x44FB,0x1234000));
  97. assert(movies::movie_definition_resident(0xC0000010,0x4044FB,0x1234000)); // native residency flags
  98. for (unsigned i=0;i<6;++i) {
  99. assert(movies::movie_definition_matches(i,i+1,i<2 ? 0x7F : 0x23));
  100. assert(!movies::movie_definition_matches(i,0,i<2 ? 0x7F : 0x23));
  101. }
  102. assert(!movies::movie_definition_matches(0,1,0x23));
  103. assert(!movies::movie_definition_matches(6,7,0x23));
  104. movies::SurfaceRegistrations surfaces{};
  105. for (unsigned i=1;i<=6;++i) surfaces[i].entries[0]=movies::movie_surface_definitions[i-1];
  106. // Live black-video capture: old candidate handles remain, but no container
  107. // holds a registration and every selected surface is FFFFFFFF.
  108. assert(!movies::movie_surfaces_registered(surfaces));
  109. assert(movies::movie_surfaces_absent(surfaces)); // stale candidates do not retain references
  110. for (unsigned i=1;i<=6;++i) surfaces[i].count=1;
  111. assert(!movies::movie_surfaces_absent(surfaces)); // hold definitions until native unregister
  112. assert(movies::movie_surfaces_registered(surfaces));
  113. assert(!movies::movie_surfaces_selected(surfaces));
  114. for (unsigned i=1;i<=6;++i) surfaces[i].selected=surfaces[i].entries[0];
  115. assert(movies::movie_surfaces_selected(surfaces));
  116. surfaces[4].count=0;
  117. assert(!movies::movie_surfaces_registered(surfaces));
  118. surfaces[4].count=4;
  119. assert(!movies::movie_surfaces_registered(surfaces));
  120. surfaces[4].count=2;surfaces[4].entries[1]=123;
  121. assert(!movies::movie_surfaces_registered(surfaces)); // another surface owns the top
  122. surfaces[4].count=1;
  123. surfaces[7].count=1;surfaces[7].entries[0]=123;
  124. assert(!movies::movie_surfaces_registered(surfaces)); // do not publish an unrelated pending slot
  125. surfaces[7].selected=123;
  126. assert(movies::movie_surfaces_selected(surfaces));
  127. auto removed=surfaces;
  128. for (unsigned i=1;i<=6;++i) removed[i].count=0;
  129. assert(!movies::movie_surfaces_absent(removed)); // selection is still using the definition
  130. for (unsigned i=1;i<=6;++i) removed[i].selected=0xFFFFFFFF;
  131. assert(movies::movie_surfaces_absent(removed));
  132. // Native kind 2 resolves shared-tag records; these ordinary movie tags require 1.
  133. static_assert(movies::movie_resource_kind==1);
  134. assert((movies::movie_metadata(0x80BCA001)==std::array<std::uint32_t,4>{0x80BCA001,0x80BCA000,0x80B9EB33,0x80BCA032}));
  135. assert((movies::movie_metadata(0x80BCA003)==std::array<std::uint32_t,4>{0x80BCA003,0x80BCA002,0x80B9EB34,0x80BCA032}));
  136. assert((movies::movie_metadata(0x80BCA034)==std::array<std::uint32_t,4>{})); // media is not a wrapper
  137. assert(movies::movie_stream(0x80BCA001)==0x80BCA034);
  138. assert(movies::movie_stream(0x80BCA003)==0x80C7C000);
  139. assert(movies::movie_stream(0x80BCA000)==0xFFFFFFFF);
  140. // The 13f07ee capture: metadata was loaded, but the video stream still held a
  141. // free-list entry (FEFE0035, 0, location 0). Native CRI entered error state 7.
  142. assert(!movies::movie_stream_ready(0xFEFE0035,0,0));
  143. assert(movies::movie_stream_ready(0xE80779A0,0x41363B,0x10000005));
  144. assert(!movies::movie_stream_ready(0xC0000000,0x41363B,0x10000005));
  145. assert(!movies::movie_stream_ready(0x280779A0,0x41363B,0x10000005));
  146. assert(!movies::movie_stream_ready(0xE80779A0,0x40103B,0x10000005));
  147. assert(!movies::movie_stream_ready(0xE80779A0,0x41363B,0));
  148. assert(!movies::movie_stream_ready(0xE80779A0,0x41363B,0x100000005ULL));
  149. // Captured crash: registered movie tags contain free-list entries, not resident headers.
  150. assert(!movies::movie_resources_ready(1,0x80BCA001,false,0,false,0xFFFFFFFF));
  151. assert(!movies::movie_resources_ready(2,0x80BCA001,false,0x80BCA000,true,0x80BCA034));
  152. assert(!movies::movie_resources_ready(2,0x80BCA001,true,0x80BCA000,false,0x80BCA034));
  153. assert(!movies::movie_resources_ready(2,0x80BCA001,true,0x80BCA002,true,0x80BCA034));
  154. assert(!movies::movie_resources_ready(3,0x80BCA001,true,0x80BCA000,true,0x80BCA034));
  155. assert(!movies::movie_resources_ready(2,0x80BCA001,true,0x80BCA000,true,0xFFFFFFFF));
  156. assert(movies::movie_resources_ready(2,0x80BCA001,true,0x80BCA000,true,0x80BCA034));
  157. assert(movies::movie_resources_ready(2,0x80BCA003,true,0x80BCA002,true,0x80C7C000));
  158. assert(!movies::resource_can_release(0) && !movies::resource_can_release(1));
  159. assert(movies::resource_can_release(2) && movies::resource_can_release(3));
  160. // Only Ember slot 43 may borrow sunburn. The global Foundry effect and rail shock stay distinct.
  161. assert(movies::ember_burn_source(0x80B3C0C6,0x80809540,0xAC8,0x80C1D9E0));
  162. assert(!movies::ember_burn_source(0x80BEB26F,0x80809540,0xAC8,0x80C1D9E0));
  163. assert(!movies::ember_burn_source(0x80B3C0C6,0x8080953F,0xAC8,0x80C1D9E0));
  164. assert(!movies::ember_burn_source(0x80B3C0C6,0x80809540,0xAC0,0x80C1D9E0));
  165. assert(!movies::ember_burn_source(0x80B3C0C6,0x80809540,0xAC8,0x80C1D389));
  166. movies::Playback playback;
  167. assert(playback.observe(false,5,true)==movies::Status::preparing);
  168. assert(playback.observe(true,0,false)==movies::Status::preparing);
  169. assert(playback.observe(true,6,false)==movies::Status::preparing);
  170. assert(playback.observe(true,3,true)==movies::Status::preparing);
  171. assert(playback.observe(true,5,true)==movies::Status::playing);
  172. assert(playback.observe(true,6,true)==movies::Status::playing);
  173. assert(playback.observe(true,6,false)==movies::Status::complete);
  174. assert(playback.observe(false,6,false)==movies::Status::failed);
  175. assert(playback.observe(true,7,false)==movies::Status::failed);
  176. movies::Playback skipped;
  177. assert(skipped.observe(true,5,true)==movies::Status::playing);
  178. assert(skipped.observe(true,0,false)==movies::Status::complete);
  179. std::array<std::uint32_t, 5> history{102, 103, 104};
  180. std::size_t historyCount = 3;
  181. const std::array<std::uint32_t, 3> movieOne{105, 102, 104};
  182. const std::array<std::uint32_t, 3> movieTwo{106, 105, 104};
  183. assert(wire::extend_key_order(history, historyCount, movieOne));
  184. assert(wire::extend_key_order(history, historyCount, movieTwo));
  185. assert(historyCount == 5 && (history == std::array<std::uint32_t, 5>{102,103,104,105,106}));
  186. assert(wire::extend_key_order(history, historyCount, movieOne));
  187. const std::array<std::uint32_t, 1> overflow{107};
  188. assert(!wire::extend_key_order(history, historyCount, overflow));
  189. // Unknown/empty worlds and a different owner cannot masquerade as cleanup.
  190. retire::Progress progress{retire::Status::baselinePending, 0};
  191. progress.observe(0, 1234, 4, 0);
  192. assert(progress.value == retire::Status::baselinePending);
  193. progress.observe(0, 1234, 8, 4);
  194. assert(progress.value == retire::Status::retiring);
  195. progress.observe(1, 1234, 4, 0);
  196. progress.observe(0, 5678, 4, 0);
  197. progress.observe(0, 1234, 0, 0);
  198. progress.observe(0, 1234, 5, 1);
  199. assert(progress.value == retire::Status::retiring);
  200. progress.observe(0, 1234, 4, 0);
  201. assert(progress.value == retire::Status::complete);
  202. // Exercise the actual encoder: old keys retain their ordinal, a clear bit removes
  203. // only the retired key, and phase two contains no body for that removed group.
  204. static wire::Snapshot snapshot{};
  205. std::array<std::uint8_t, 1> types{1}, flags{0};
  206. std::array<std::uint16_t, 1> indices{0};
  207. std::array<std::uint32_t, 3> keys{102, 103, 104};
  208. std::array<wire::BubbleSubBlock, 1> blocks{{{0, keys}}};
  209. snapshot.roster.groupCount = 4; snapshot.roster.topLevelGroupCount = 1;
  210. snapshot.roster.bubbleSubBlocks = blocks;
  211. for (unsigned i = 0; i < 4; ++i) {
  212. auto& group = snapshot.roster.groups[i];
  213. group.key = 101 + i; group.slotTypes = types; group.slotFlags = flags; group.slotIndices = indices;
  214. }
  215. snapshot.roster.groups[2].retired = true;
  216. std::array<std::byte, 4096> bytes{}; std::size_t size{};
  217. assert(wire::encode_sensor_auth_update(snapshot, bytes, size));
  218. Reader mask{std::span(bytes).first(size)};
  219. assert(mask.skip(wire::kLatchBitWithoutGrant + 1 + wire::delta_bits(1, {}) - 1));
  220. expect(mask, 1, 1); expect(mask, 7, 1); // Field one, one bubble.
  221. expect(mask, 1, 1); expect(mask, 32, 0x80000000U);
  222. expect(mask, 1, 1); expect(mask, 1, 1); expect(mask, 7, 3);
  223. for (auto key : keys) expect(mask, 32, key);
  224. expect(mask, 1, 1); expect(mask, 32, 5); // Keep ordinals 0 and 2.
  225. expect(mask, 32, 0); expect(mask, 32, 0);
  226. Reader bodies{std::span(bytes).first(size)};
  227. assert(bodies.skip(wire::kLatchBitWithoutGrant + 1 + wire::delta_bits(1, blocks)));
  228. for (auto key : {101U, 102U, 104U}) {
  229. expect(bodies, 1, 1); expect(bodies, 32, key); expect(bodies, 32, 0);
  230. expect(bodies, 1, 1); expect(bodies, 32, key); expect(bodies, 7, 2);
  231. expect(bodies, 16, 32768); expect(bodies, 32, 0); expect(bodies, 1, 0);
  232. }
  233. expect(bodies, 1, 0); expect(bodies, 1, 0);
  234. snapshot.roster.groups[0].retired = true;
  235. assert(!wire::encode_sensor_auth_update(snapshot, bytes, size));
  236. member::MembershipState state{};
  237. state.hasHostTeleport = true; state.hostTeleportQualified = true;
  238. state.hostTeleport = {1, 8, 1, 123}; state.teleport = {2, 8, 1, 123};
  239. state.region.index = 1; state.currentReported = true; state.currentRegion.index = 0;
  240. member::observe_qualified_teleport(state);
  241. assert(state.hostTeleport.state == 1); // Pending-region advertisement is not arrival.
  242. state.teleport.state = 3; state.currentRegion.index = 1;
  243. state.teleport.sliceSetHash = 124;
  244. member::observe_qualified_teleport(state); assert(state.hostTeleport.state == 1);
  245. state.teleport.sliceSetHash = 123; state.teleport.token = 7;
  246. member::observe_qualified_teleport(state); assert(state.hostTeleport.state == 1);
  247. state.teleport.token = 8;
  248. member::observe_qualified_teleport(state); assert(state.hostTeleport.state == 3);
  249. state.teleport.state = 0;
  250. member::observe_qualified_teleport(state); assert(!state.hasHostTeleport);
  251. assert(member::next_teleport_token(255) == 1 && member::next_teleport_token(8) == 9);
  252. }