|
@@ -1,133 +1,33 @@
|
|
|
-// Regression for the selected-state arrival window.
|
|
|
|
|
-//
|
|
|
|
|
-// An authored region is `sliceSetIndex + stateOrdinal`. Ember's apex gameplay and both ending
|
|
|
|
|
-// bookends are sibling states of slice set 0 (regions 0, 1 and 2), while every earlier area is
|
|
|
|
|
-// its own slice set. Only a slice-set change replaces the client's instantiated world.
|
|
|
|
|
-//
|
|
|
|
|
-// Treating a sibling move as a replacement is what froze the mission's ending: the roster
|
|
|
|
|
-// withheld region 1's groups until the client reported holding region 1, and the client's
|
|
|
|
|
-// current region leg only advances on a real slice-set switch, so the report never came.
|
|
|
|
|
-
|
|
|
|
|
|
|
+// Packed regions select native entries: 4C8E60 base, 4C8E40 alternates.
|
|
|
#include <cassert>
|
|
#include <cassert>
|
|
|
-#include <cstdint>
|
|
|
|
|
-
|
|
|
|
|
#include "../Sunrise/src/server/bap/encrypted/push/activity/mission_seed_world_change.h"
|
|
#include "../Sunrise/src/server/bap/encrypted/push/activity/mission_seed_world_change.h"
|
|
|
-
|
|
|
|
|
-namespace {
|
|
|
|
|
-
|
|
|
|
|
namespace seed = sunrise::server::bap::encrypted::push::activity;
|
|
namespace seed = sunrise::server::bap::encrypted::push::activity;
|
|
|
-
|
|
|
|
|
-/** Ember's authored states, as `{sliceSetIndex, effectiveRegion}`. */
|
|
|
|
|
-constexpr std::uint32_t kApexSliceSet = 0;
|
|
|
|
|
-constexpr std::uint32_t kApexRegion = 0;
|
|
|
|
|
-constexpr std::uint32_t kFirstMovieRegion = 1;
|
|
|
|
|
-constexpr std::uint32_t kSecondMovieRegion = 2;
|
|
|
|
|
-constexpr std::uint32_t kArrivalSliceSet = 48;
|
|
|
|
|
-constexpr std::uint32_t kArrivalRegion = 49;
|
|
|
|
|
-constexpr std::uint32_t kPowerhouseSliceSet = 64;
|
|
|
|
|
-constexpr std::uint32_t kPowerhouseRegion = 64;
|
|
|
|
|
-constexpr std::uint32_t kLinkSliceSet = 56;
|
|
|
|
|
-constexpr std::uint32_t kLinkRegion = 56;
|
|
|
|
|
-constexpr std::uint32_t kCinderSliceSet = 40;
|
|
|
|
|
-constexpr std::uint32_t kCinderRegion = 40;
|
|
|
|
|
-
|
|
|
|
|
-void sibling_states_keep_their_world() {
|
|
|
|
|
- // Apex gameplay to the first ending bookend: same slice set, so nothing is rebuilt.
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kApexSliceSet, kApexRegion, kApexSliceSet, kFirstMovieRegion));
|
|
|
|
|
- // First bookend to the second: still slice set 0.
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kApexSliceSet, kFirstMovieRegion, kApexSliceSet, kSecondMovieRegion));
|
|
|
|
|
- // And back, so a checkpoint or reselection is treated the same way.
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kApexSliceSet, kSecondMovieRegion, kApexSliceSet, kApexRegion));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void slice_set_changes_replace_the_world() {
|
|
|
|
|
- // Every transition the mission already made is a real slice-set change and must stay one,
|
|
|
|
|
- // or the roster would register the new region's groups into a world being torn down.
|
|
|
|
|
- assert(seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kArrivalSliceSet, kArrivalRegion, kPowerhouseSliceSet, kPowerhouseRegion));
|
|
|
|
|
- assert(seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kPowerhouseSliceSet, kPowerhouseRegion, kLinkSliceSet, kLinkRegion));
|
|
|
|
|
- assert(seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kLinkSliceSet, kLinkRegion, kCinderSliceSet, kCinderRegion));
|
|
|
|
|
- assert(seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kCinderSliceSet, kCinderRegion, kApexSliceSet, kApexRegion));
|
|
|
|
|
- // Backtracking is a replacement in both directions.
|
|
|
|
|
- assert(seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kCinderSliceSet, kCinderRegion, kLinkSliceSet, kLinkRegion));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void reselecting_the_same_state_is_never_a_replacement() {
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kApexSliceSet, kApexRegion, kApexSliceSet, kApexRegion));
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kCinderSliceSet, kCinderRegion, kCinderSliceSet, kCinderRegion));
|
|
|
|
|
- // A same-region selection stays a no-op even if the slice sets disagree: the region is the
|
|
|
|
|
- // publication key, and re-registering identical groups must not reopen an arrival window.
|
|
|
|
|
- assert(!seed::mission_seed_region_change_replaces_world(
|
|
|
|
|
- kApexSliceSet, kApexRegion, kCinderSliceSet, kApexRegion));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void decision_is_available_at_compile_time() {
|
|
|
|
|
- // The production caller is in a lock-held path, so the predicate must fold away.
|
|
|
|
|
- static_assert(!seed::mission_seed_region_change_replaces_world(0, 0, 0, 1));
|
|
|
|
|
- static_assert(seed::mission_seed_region_change_replaces_world(40, 40, 0, 0));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void arrival_window_closes_when_no_arrival_can_be_reported() {
|
|
|
|
|
- constexpr std::uint32_t factor = 8;
|
|
|
|
|
- // Ember's ending: apex gameplay held, region 1 pending, both slice set 0. No slice-set switch
|
|
|
|
|
- // happens, so the client never reports region 1 and the window must close on its own.
|
|
|
|
|
- assert(seed::mission_seed_arrival_window_closed(kApexRegion, kFirstMovieRegion,
|
|
|
|
|
- kApexSliceSet, factor));
|
|
|
|
|
- assert(seed::mission_seed_arrival_window_closed(kFirstMovieRegion, kSecondMovieRegion,
|
|
|
|
|
- kApexSliceSet, factor));
|
|
|
|
|
- // The ordinary case still closes on the arrival itself.
|
|
|
|
|
- assert(seed::mission_seed_arrival_window_closed(kPowerhouseRegion, kPowerhouseRegion,
|
|
|
|
|
- kPowerhouseSliceSet, factor));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void arrival_window_stays_open_across_a_real_slice_set_change() {
|
|
|
|
|
- constexpr std::uint32_t factor = 8;
|
|
|
|
|
- // Holding Cinder while Apex is pending is a genuine teardown: the window must stay open or
|
|
|
|
|
- // the publication registers the new region's groups into the world being torn down.
|
|
|
|
|
- assert(!seed::mission_seed_arrival_window_closed(kCinderRegion, kApexRegion,
|
|
|
|
|
- kApexSliceSet, factor));
|
|
|
|
|
- assert(!seed::mission_seed_arrival_window_closed(kPowerhouseRegion, kLinkRegion,
|
|
|
|
|
- kLinkSliceSet, factor));
|
|
|
|
|
- // Holding nothing yet is not an arrival.
|
|
|
|
|
- assert(!seed::mission_seed_arrival_window_closed(-1, kApexRegion, kApexSliceSet, factor));
|
|
|
|
|
- // A zero factor would make the slice-set test meaningless; it must not close the window.
|
|
|
|
|
- assert(!seed::mission_seed_arrival_window_closed(kApexRegion, kFirstMovieRegion,
|
|
|
|
|
- kApexSliceSet, 0));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-} // namespace
|
|
|
|
|
-
|
|
|
|
|
int main() {
|
|
int main() {
|
|
|
- // Closing the arrival lease alone is insufficient: the roster must include the
|
|
|
|
|
- // state-local cinematic groups while the client still reports Apex gameplay.
|
|
|
|
|
- assert(!seed::mission_seed_transition_subset_only(false, true, false, 0, 1, 0, 8));
|
|
|
|
|
- assert(!seed::mission_seed_transition_subset_only(false, true, false, 0, 2, 0, 8));
|
|
|
|
|
- assert(seed::mission_seed_transition_subset_only(false, true, false, 40, 1, 0, 8));
|
|
|
|
|
- assert(seed::mission_seed_transition_subset_only(false, true, false, -1, 1, 0, 8));
|
|
|
|
|
- assert(seed::mission_seed_transition_subset_only(false, false, false, 0, 1, 0, 8));
|
|
|
|
|
- assert(!seed::mission_seed_transition_subset_only(false, false, true, 0, 1, 0, 8));
|
|
|
|
|
- assert(!seed::mission_seed_transition_subset_only(true, true, false, 40, 1, 0, 8));
|
|
|
|
|
- // Real full-mission case: the last explicit seed is the landing, but ordinary
|
|
|
|
|
- // traversal already brought the player to Apex before selecting its ending.
|
|
|
|
|
- assert(!seed::mission_seed_selection_needs_arrival(64, 64, 0, 1, 0, 8));
|
|
|
|
|
- assert(!seed::mission_seed_selection_needs_arrival(0, 1, 0, 2, 0, 8));
|
|
|
|
|
- assert(seed::mission_seed_selection_needs_arrival(64, 64, 0, 1, 64, 8));
|
|
|
|
|
- assert(seed::mission_seed_selection_needs_arrival(64, 64, 0, 1, -1, 8));
|
|
|
|
|
-
|
|
|
|
|
- sibling_states_keep_their_world();
|
|
|
|
|
- slice_set_changes_replace_the_world();
|
|
|
|
|
- reselecting_the_same_state_is_never_a_replacement();
|
|
|
|
|
- decision_is_available_at_compile_time();
|
|
|
|
|
- arrival_window_closes_when_no_arrival_can_be_reported();
|
|
|
|
|
- arrival_window_stays_open_across_a_real_slice_set_change();
|
|
|
|
|
- return 0;
|
|
|
|
|
|
|
+ // Both movies need their own native world entry even though the bubble stays zero.
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(0, 0, 0, 1));
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(0, 1, 0, 2));
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(0, 2, 0, 0));
|
|
|
|
|
+ assert(!seed::mission_seed_region_change_replaces_world(0, 1, 0, 1));
|
|
|
|
|
+ // Preserve ordinary mission travel and exact same-state idempotence.
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(48, 49, 64, 64));
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(64, 64, 56, 56));
|
|
|
|
|
+ assert(seed::mission_seed_region_change_replaces_world(40, 40, 0, 0));
|
|
|
|
|
+ assert(!seed::mission_seed_region_change_replaces_world(40, 40, 40, 40));
|
|
|
|
|
+ // Held base gameplay is not a bookend arrival; pending/unknown is not held.
|
|
|
|
|
+ assert(!seed::mission_seed_arrival_window_closed(0, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_arrival_window_closed(1, 2, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_arrival_window_closed(-1, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_arrival_window_closed(1, 1, 0, 0));
|
|
|
|
|
+ assert(seed::mission_seed_arrival_window_closed(1, 1, 0, 8));
|
|
|
|
|
+ assert(seed::mission_seed_arrival_window_closed(2, 2, 0, 8));
|
|
|
|
|
+ // The last scripted seed can be landing while natural traversal reached Apex.
|
|
|
|
|
+ assert(seed::mission_seed_selection_needs_arrival(64, 64, 0, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_selection_needs_arrival(64, 64, 0, 1, 1, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_selection_needs_arrival(64, 64, 0, 0, 0, 8));
|
|
|
|
|
+ // Local objects must wait for their actual world; global-only startup stays intact.
|
|
|
|
|
+ assert(seed::mission_seed_transition_subset_only(false, true, false, 0, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_transition_subset_only(false, true, false, 1, 1, 0, 8));
|
|
|
|
|
+ assert(seed::mission_seed_transition_subset_only(false, false, false, 1, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_transition_subset_only(false, false, true, 1, 1, 0, 8));
|
|
|
|
|
+ assert(!seed::mission_seed_transition_subset_only(true, true, false, 0, 1, 0, 8));
|
|
|
}
|
|
}
|