Kaynağa Gözat

remove dead hooks

stan 2 gün önce
ebeveyn
işleme
ae465b0f5d
24 değiştirilmiş dosya ile 172 ekleme ve 1223 silme
  1. 2 10
      Sunrise/Sunrise.vcxproj
  2. 1 5
      Sunrise/resources/default_settings.json
  3. 7 8
      Sunrise/src/client/hooks/bootflow/bootflow_hook_lifecycle.cpp
  4. 0 122
      Sunrise/src/client/hooks/bootflow/fade_release.cpp
  5. 2 40
      Sunrise/src/client/hooks/bootflow/internal.h
  6. 0 91
      Sunrise/src/client/hooks/bootflow/spawn/probe.h
  7. 126 0
      Sunrise/src/client/hooks/bootflow/spawn/slice_set_sample.cpp
  8. 19 0
      Sunrise/src/client/hooks/bootflow/spawn/slice_set_sample.h
  9. 0 162
      Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_probe.cpp
  10. 0 113
      Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_record_dump.cpp
  11. 0 39
      Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_record_dump.h
  12. 0 150
      Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_targets.cpp
  13. 0 154
      Sunrise/src/client/hooks/bootflow/spawn_hold.cpp
  14. 2 31
      Sunrise/src/client/hooks/bootflow/world_step.cpp
  15. 0 130
      Sunrise/src/client/hooks/peer_relay/peer_relay_direct.cpp
  16. 0 11
      Sunrise/src/client/hooks/peer_relay/peer_relay_direct.h
  17. 0 4
      Sunrise/src/client/runtime/client_hook_activation.cpp
  18. 0 27
      Sunrise/src/core/settings/client/client_settings_parser.cpp
  19. 2 34
      Sunrise/src/core/settings/client/definition.h
  20. 9 5
      Sunrise/src/core/settings/settings_upgrade.cpp
  21. 1 1
      Sunrise/src/server/bap/encrypted/push/activity/activity_roster_snapshot.cpp
  22. 0 56
      Sunrise/src/state/activity/activity_world_arrival.cpp
  23. 0 29
      Sunrise/src/state/activity/runtime.h
  24. 1 1
      Sunrise/src/state/investment/investment.h

+ 2 - 10
Sunrise/Sunrise.vcxproj

@@ -199,7 +199,6 @@
     <ClCompile Include="src\client\hooks\graphics\discovery\graphics_target_discovery.cpp" />
     <ClCompile Include="src\client\hooks\graphics\graphics_hook_lifecycle.cpp" />
     <ClCompile Include="src\client\hooks\hitch_probe\hitch_probe.cpp" />
-    <ClCompile Include="src\client\hooks\peer_relay\peer_relay_direct.cpp" />
     <ClCompile Include="src\client\hooks\sense_chain_guard\sense_chain_guard.cpp" />
     <ClCompile Include="src\client\hooks\stall_probe\stall_probe.cpp" />
     <ClCompile Include="src\client\hooks\graphics\graphics_hook_replacements.cpp" />
@@ -282,11 +281,7 @@
     <ClCompile Include="src\client\hooks\bootflow\orbit_slice_set.cpp" />
     <ClCompile Include="src\client\hooks\bootflow\region_private.cpp" />
     <ClCompile Include="src\client\hooks\bootflow\world_step.cpp" />
-    <ClCompile Include="src\client\hooks\bootflow\spawn_hold.cpp" />
-    <ClCompile Include="src\client\hooks\bootflow\fade_release.cpp" />
-    <ClCompile Include="src\client\hooks\bootflow\spawn\spawn_gate_probe.cpp" />
-    <ClCompile Include="src\client\hooks\bootflow\spawn\spawn_gate_targets.cpp" />
-    <ClCompile Include="src\client\hooks\bootflow\spawn\spawn_gate_record_dump.cpp" />
+    <ClCompile Include="src\client\hooks\bootflow\spawn\slice_set_sample.cpp" />
     <ClCompile Include="src\server\bap\encrypted\push\activity\activity_arrival.cpp" />
     <ClCompile Include="src\middleware\bap\activity_host_manager\request\selection\activity_manager_selection_bits.cpp" />
     <ClCompile Include="src\middleware\bap\activity_host_manager\request\selection\activity_manager_selection_snapshot.cpp" />
@@ -589,7 +584,6 @@
     <ClCompile Include="src\state\gameplay\external\replication_view_receptor.cpp" />
     <ClCompile Include="src\state\gameplay\external\replication_world_state.cpp" />
     <ClCompile Include="src\state\activity\activity_session_lookup.cpp" />
-    <ClCompile Include="src\state\activity\activity_world_arrival.cpp" />
     <ClCompile Include="src\state\activity\mission\activity_mission_state.cpp" />
     <ClCompile Include="src\state\activity\mission\activity_mission_publication.cpp" />
     <ClCompile Include="src\state\activity\mission\activity_mission_intents.cpp" />
@@ -1345,7 +1339,6 @@
     <ClInclude Include="src\client\runtime\host\game_host_classification.h" />
     <ClInclude Include="src\client\hooks\graphics\graphics_hook_lifecycle.h" />
     <ClInclude Include="src\client\hooks\hitch_probe\hitch_probe.h" />
-    <ClInclude Include="src\client\hooks\peer_relay\peer_relay_direct.h" />
     <ClInclude Include="src\client\hooks\sense_chain_guard\sense_chain_guard.h" />
     <ClInclude Include="src\client\hooks\stall_probe\stall_probe.h" />
     <ClInclude Include="src\client\hooks\graphics\graphics_hook_replacements.h" />
@@ -1835,8 +1828,7 @@
     <ClInclude Include="src\client\hooks\bootflow\internal.h" />
     <ClInclude Include="src\client\hooks\bootflow\bootflow_hook_lifecycle.h" />
     <ClInclude Include="src\client\hooks\bootflow\bootflow_texture_override.h" />
-    <ClInclude Include="src\client\hooks\bootflow\spawn\spawn_gate_record_dump.h" />
-    <ClInclude Include="src\client\hooks\bootflow\spawn\probe.h" />
+    <ClInclude Include="src\client\hooks\bootflow\spawn\slice_set_sample.h" />
     <ClInclude Include="src\client\hooks\config_getter\config_getter_answers.h" />
     <ClInclude Include="src\client\hooks\config_getter\config_getter_lifecycle.h" />
     <ClInclude Include="src\client\hooks\egress\lifecycle\internal.h" />

+ 1 - 5
Sunrise/resources/default_settings.json

@@ -34,15 +34,11 @@
       "config_url": "https://127.0.0.1/config/",
       "config_guid": "d2legacy-0000-0000-0000-000000000001"
     },
-    "suppress_peer_relay": true,
     "custom_bootflow_textures": true,
-    "fade_release": true,
     "socket_menu_routing": false,
     "reveal_lore_books": true,
     "region_private": false,
-    "pin_replicated_record": true,
-    "hold_spawn": true,
-    "spawn_hold_ms": 30000
+    "pin_replicated_record": true
   },
   "server": {
     "bap_port": 30974,

+ 7 - 8
Sunrise/src/client/hooks/bootflow/bootflow_hook_lifecycle.cpp

@@ -6,6 +6,7 @@
 #include <span>
 
 #include "internal.h"
+#include "spawn/slice_set_sample.h"
 
 namespace sunrise::client::hooks::bootflow {
 namespace {
@@ -19,8 +20,8 @@ struct Fix {
 };
 
 /**
- * Every fix that attaches a detour. `world_step` and `fade_release` are absent: they only find
- * addresses to call, so they open no transaction and cost the group nothing.
+ * Every fix that attaches a detour. `world_step` and the slice-set sample are absent: they only
+ * find addresses to call, so they open no transaction and cost the group nothing.
  */
 constexpr std::array kFixes{
     Fix{&stage_character_select_hold, &publish_character_select_hold},
@@ -29,7 +30,6 @@ constexpr std::array kFixes{
     Fix{&stage_orbit_handoff, &publish_orbit_handoff},
     Fix{&stage_owner_activity_slot, &publish_owner_activity_slot},
     Fix{&stage_region_private, &publish_region_private},
-    Fix{&stage_spawn_hold, &publish_spawn_hold},
 };
 
 /** Marks a fix that staged nothing, so no handle is ever published to it. */
@@ -95,16 +95,15 @@ bool install() noexcept {
 
     // Neither of these attaches anything, so they stay outside the transaction.
     const bool worldStep = install_world_step();
-    const bool fade = install_fade_release();
-    anyFix = anyFix || worldStep || fade;
+    const bool sliceSet = spawn::install_targets();
+    anyFix = anyFix || worldStep || sliceSet;
     g_installed.store(anyFix, std::memory_order_release);
-    return everyFix && worldStep && fade;
+    return everyFix && worldStep && sliceSet;
 }
 
 /** Detaches every boot-step fix, in the reverse order of install. */
 void uninstall() noexcept {
-    uninstall_fade_release();
-    uninstall_spawn_hold();
+    spawn::uninstall_targets();
     uninstall_world_step();
     uninstall_region_private();
     uninstall_owner_activity_slot();

+ 0 - 122
Sunrise/src/client/hooks/bootflow/fade_release.cpp

@@ -1,122 +0,0 @@
-#include <array>
-#include <atomic>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <string_view>
-
-#include "../../../core/logging/log.h"
-#include "../../../core/settings/settings.h"
-#include "internal.h"
-
-namespace sunrise::client::hooks::bootflow {
-namespace {
-
-using core::log::kLineCapacity;
-
-/**
- * The narrow channel release: one channel, with a blend time, so the world fades in.
- * Anchored on the load of the channel key, then run on through the argument spills, because the
- * wildcarded frame size leaves the head too short to be unique.
- */
-constexpr std::string_view kReleaseSignatureText =
-    "48 83 EC ? 8B 02 0F 57 C0 F3 0F 10 0D ? ? ? ? 48 8D 54 24 ? F3 0F 11 44 24";
-/** Compiled pattern bytes of the signature text above. */
-constexpr auto kReleaseSignature =
-    signature<signature_length(kReleaseSignatureText)>(kReleaseSignatureText);
-
-/**
- * The fade manager accessor: one load-effective-address of its static object, then a return.
- * Every displacement is wildcarded, so the match runs past the return to stay unique. The bytes
- * after it are the next function's prologue and its thread-block read in this build.
- */
-constexpr std::string_view kAccessorSignatureText =
-    "48 8D 05 ? ? ? ? C3 48 63 C0 E9 ? ? ? ? 48 83 EC ? 65 48 8B 04 25 58 00 00 00";
-/** Compiled pattern bytes of the signature text above. */
-constexpr auto kAccessorSignature =
-    signature<signature_length(kAccessorSignatureText)>(kAccessorSignatureText);
-
-/** Byte offsets inside the accessor, used to decode the object address from its operand. */
-struct AccessorLayout {
-    /** The 4-byte displacement follows the 2-byte load opcode. */
-    static constexpr std::size_t displacement = 3;
-    /** The instruction after the load, which the displacement is relative to. */
-    static constexpr std::size_t nextInstruction = 7;
-};
-
-/** The world-transition fade channel. Stage two of the transition arms it with opaque black. */
-constexpr std::uint32_t kWorldTransitionChannel = 0x57572DAC;
-/** The channel's colour is a static initialiser, so it needs no lookup. */
-constexpr std::array<float, 4> kOpaqueBlack{0.0F, 0.0F, 0.0F, 1.0F};
-/** Blend seconds, so the world fades in rather than popping. */
-constexpr float kFadeInSeconds = 0.5F;
-
-using ReleaseChannel = std::int64_t(__fastcall*)(void*, std::uint32_t*, float*, float) noexcept;
-
-void* g_manager{nullptr};
-std::atomic<ReleaseChannel> g_release{nullptr};
-std::atomic_bool g_released{false};
-
-} // namespace
-
-/** Re-arms the release, so the next world load fades in once and logs its own line. */
-void rearm_fade_release() noexcept {
-    g_released.store(false, std::memory_order_release);
-}
-
-/** Releases the world-transition fade channel. The spawn gate decides when. */
-void release_world_fade() noexcept {
-    const ReleaseChannel release = g_release.load(std::memory_order_acquire);
-    if (release == nullptr || g_manager == nullptr || !core::settings::get().client.fadeRelease) {
-        return;
-    }
-    // Fire once per arming. The spawn gate polls this every frame while the player is arriving, and
-    // restarting the blend each frame re-slams the channel to black, which reads as flicker. The
-    // off-destination step re-arms it through `rearm_fade_release`.
-    if (g_released.exchange(true, std::memory_order_relaxed)) {
-        return;
-    }
-    std::uint32_t channel = kWorldTransitionChannel;
-    std::array<float, 4> colour = kOpaqueBlack;
-    (void)release(g_manager, &channel, colour.data(), kFadeInSeconds);
-    std::array<char, kLineCapacity> line{};
-    const int written = std::snprintf(line.data(),
-                                      line.size(),
-                                      "ev=bootflow stage=fade_release result=issued channel=0x%X",
-                                      kWorldTransitionChannel);
-    if (written > 0) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::info,
-                         {line.data(), static_cast<std::size_t>(written)});
-    }
-}
-
-/** Finds the fade release and its manager object. */
-bool install_fade_release() noexcept {
-    std::byte* const release = scan_main_image_unique(kReleaseSignature, "fade_release_channel");
-    std::byte* const accessor = scan_main_image_unique(kAccessorSignature, "fade_manager_accessor");
-    if (release == nullptr || accessor == nullptr) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=bootflow stage=fade_release result=fail reason=target");
-        return false;
-    }
-    // The manager is the static object the accessor returns. The address comes from that
-    // instruction's own operand, not from a stored offset.
-    g_manager = resolve_relative(accessor + AccessorLayout::displacement,
-                                 accessor + AccessorLayout::nextInstruction);
-    g_release.store(reinterpret_cast<ReleaseChannel>(release), std::memory_order_release);
-    core::log::write(core::log::Channel::client,
-                     core::log::Level::info,
-                     "ev=bootflow stage=fade_release result=ok");
-    return true;
-}
-
-/** Clears the fade release it found. */
-void uninstall_fade_release() noexcept {
-    g_release.store(nullptr, std::memory_order_release);
-    g_manager = nullptr;
-    g_released.store(false, std::memory_order_release);
-}
-
-} // namespace sunrise::client::hooks::bootflow

+ 2 - 40
Sunrise/src/client/hooks/bootflow/internal.h

@@ -105,8 +105,8 @@ void publish_region_private(const hooking::detour::Handle& handle) noexcept;
 void uninstall_region_private() noexcept;
 
 /**
- * Finds the boot-flow step accessor, the only input to the world phase.
- * Nothing is detoured: the accessor is called, so a miss leaves the phase idle.
+ * Finds the boot-flow step accessor behind `in_world`.
+ * Nothing is detoured: the accessor is called, so a miss reads as out of world.
  * @return True when the target was found.
  */
 [[nodiscard]] bool install_world_step() noexcept;
@@ -114,42 +114,4 @@ void uninstall_region_private() noexcept;
 /** Clears the boot-flow step accessor it found. */
 void uninstall_world_step() noexcept;
 
-/**
- * Maps the client's own boot-flow step onto the world phase.
- * Runs on the spawn gate poll, which is the only tick the phase is read on.
- */
-void observe_world_step() noexcept;
-
-/**
- * Stages the spawn hold, which puts the player spawn after the world-transition fade is armed.
- * @param spec Receives the target and replacement.
- * @return staged when the target was found, unavailable on a miss.
- */
-[[nodiscard]] StageResult stage_spawn_hold(hooking::detour::Spec& spec) noexcept;
-
-/** Takes the spawn hold's attached handle, or a detached one. */
-void publish_spawn_hold(const hooking::detour::Handle& handle) noexcept;
-
-/** Detaches the spawn hold. */
-void uninstall_spawn_hold() noexcept;
-
-/**
- * Finds the world-transition fade release and its manager object.
- * Nothing is detoured: both are called, so a miss leaves the feature off, not the client changed.
- * @return True when both targets were found.
- */
-[[nodiscard]] bool install_fade_release() noexcept;
-
-/** Clears the fade release it found. */
-void uninstall_fade_release() noexcept;
-
-/**
- * Releases the world-transition fade channel.
- * The spawn gate owns the timing. Does nothing unless `client.fade_release` is set.
- */
-void release_world_fade() noexcept;
-
-/** Re-arms the one line the release logs, so the next load reports its own. */
-void rearm_fade_release() noexcept;
-
 } // namespace sunrise::client::hooks::bootflow

+ 0 - 91
Sunrise/src/client/hooks/bootflow/spawn/probe.h

@@ -1,91 +0,0 @@
-#pragma once
-
-#include <cstddef>
-#include <cstdint>
-#include <span>
-
-namespace sunrise::client::hooks::bootflow::spawn {
-
-/** Which gate condition refused, in the gate's own order. */
-enum class Refusal : std::uint8_t {
-    /** Nothing tested here refused, so the remaining dev-switch arm is what did. */
-    devHold,
-    /** The gate was not asked, or its predicates were not found. */
-    unknown,
-    /** G0: the caller passed the absent player datum. */
-    datum,
-    /** G1: no world is present. */
-    world,
-    /** G2: the current slice-set index is not addressable. */
-    sliceSet,
-    /** G3: the datum has no participation record at all. */
-    participationMissing,
-    /** G3: the record exists and its `+10` byte is clear. */
-    participation,
-    /** G4: the activity lifetime state is outside the passing set. */
-    lifetime,
-    /** G5: the world-controller handles are not ready. */
-    worldControl,
-    /** G6a: the team state or team block is absent. */
-    teamMissing,
-    /** G6b: the host published a nonzero team-state byte. */
-    teamState,
-    /** G6c: no team index is assigned. */
-    teamIndex,
-    /** G7: the host published the spawn-suppressed state. */
-    suppressed,
-    /** Nothing refused, so the gate allowed. */
-    none,
-};
-
-/** Everything one probe pass read, so the log can name values, not just a result. */
-struct Reading {
-    Refusal refusal{Refusal::unknown};
-    /** True when the datum has a participation record, which msg 5's type-13 body fills. */
-    bool hasRecord{};
-    /** Record `+8`, the step-36 task-9 latch. */
-    std::uint8_t recordTask9{};
-    /** Record `+10`, the byte G3 requires to be set. */
-    std::uint8_t recordReady{};
-    /** Activity lifetime state the gate switched on, or -1 when it was not read. */
-    std::int32_t lifetimeState{-1};
-    /** The team-state byte at `+1`, whose four low bits are the host's suppression reasons. */
-    std::uint8_t teamStateBits{};
-    /** The team index at identity `+9`; 0xFF means none was assigned. */
-    std::uint8_t teamIndex{};
-    /** True once the client reference manager holds a type-13 entry, which arms the team block. */
-    bool hasType13{};
-};
-
-/**
- * Finds the gate's own predicates from the call operands inside it.
- * Every target is read out of the gate body, so this adds no signature of its own.
- * @param gate Base of the spawn gate, found by pattern.
- * @return True when every call site held the expected opcode and decoded.
- */
-[[nodiscard]] bool resolve(const std::byte* gate) noexcept;
-
-/** Clears the predicates it found. */
-void forget() noexcept;
-
-/** @return The addressable local slice-set index, -1 while absent, or -2 when unresolved. */
-[[nodiscard]] std::int32_t sample_current_slice_set() noexcept;
-
-/**
- * Re-runs the gate's conditions in order and reports the first that refuses.
- * Calls only the gate's own read-only accessors, with its argument, on its thread and inside its
- * call, so it sees what the gate saw.
- * @param datum Player datum handle the gate was called with.
- * @return What the pass read; `unknown` when the predicates were not found.
- */
-[[nodiscard]] Reading examine(std::int32_t datum) noexcept;
-
-/**
- * Formats one reading as log fields.
- * @param reading Result of a probe pass.
- * @param output Caller-owned character storage.
- * @return Characters written, not counting the null.
- */
-[[nodiscard]] std::size_t describe(const Reading& reading, std::span<char> output) noexcept;
-
-} // namespace sunrise::client::hooks::bootflow::spawn

+ 126 - 0
Sunrise/src/client/hooks/bootflow/spawn/slice_set_sample.cpp

@@ -0,0 +1,126 @@
+#include "slice_set_sample.h"
+
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+#include "../../../../core/logging/log.h"
+#include "../internal.h"
+
+namespace sunrise::client::hooks::bootflow::spawn {
+namespace {
+
+/** The player spawn gate. The head alone is not unique, so the pattern runs to the cookie store. */
+constexpr std::string_view kSpawnGateSignatureText =
+    "40 53 57 41 57 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 84 24 ? ? ? ? 8B D9 "
+    "40 B7 01";
+/** Compiled pattern bytes of the signature text above. */
+constexpr auto kSpawnGateSignature =
+    signature<signature_length(kSpawnGateSignatureText)>(kSpawnGateSignatureText);
+
+/** Every site must hold a near call, or the offset is stale. */
+constexpr std::byte kCallOpcode{0xE8};
+/** Length of a near call, and the offset of its displacement. */
+constexpr std::size_t kCallLength = 5;
+constexpr std::size_t kCallOperand = 1;
+/** Largest index accepted by the client's own slice-set-to-bubble mapper. */
+constexpr std::int32_t kMaximumSliceSet = 0x1FF;
+
+using NoArgPointer = void*(__fastcall*)() noexcept;
+using PointerPredicate = bool(__fastcall*)(void*) noexcept;
+using SliceSetIndexGetter = void*(__fastcall*)(void*, std::int32_t*) noexcept;
+
+std::atomic<NoArgPointer> g_sliceSetManager{nullptr};
+std::atomic<PointerPredicate> g_worldPresent{nullptr};
+std::atomic<SliceSetIndexGetter> g_currentSliceSet{nullptr};
+std::atomic<PointerPredicate> g_sliceSetAddressable{nullptr};
+
+/** Byte offsets of each call from the gate's base. A site without a near call refuses. */
+struct Site {
+    /** The slice-set manager accessor both world conditions start from. */
+    static constexpr std::size_t sliceSetManager = 0x23;
+    /** G1: a world is present. */
+    static constexpr std::size_t worldPresent = 0x2B;
+    /** G2's input: the current slice-set index. */
+    static constexpr std::size_t currentSliceSet = 0x49;
+    /** G2: that index is addressable. */
+    static constexpr std::size_t sliceSetAddressable = 0x51;
+};
+
+/**
+ * Decodes one call target.
+ * @param gate Base of the spawn gate.
+ * @param offset Byte offset of the call from the gate base.
+ * @return The called address, or null when the site is not a near call.
+ */
+[[nodiscard]] void* call_target(const std::byte* gate, std::size_t offset) noexcept {
+    const std::byte* const site = gate + offset;
+    if (*site != kCallOpcode) {
+        return nullptr;
+    }
+    return resolve_relative(site + kCallOperand, site + kCallLength);
+}
+
+} // namespace
+
+/** Finds the spawn gate and decodes the four slice-set calls inside it. */
+bool install_targets() noexcept {
+    uninstall_targets();
+    const std::byte* const gate = scan_main_image_unique(kSpawnGateSignature, "player_spawn_gate");
+    if (gate == nullptr) {
+        core::log::write(core::log::Channel::client,
+                         core::log::Level::warn,
+                         "ev=bootflow stage=current_slice result=fail reason=target");
+        return false;
+    }
+    const auto manager = reinterpret_cast<NoArgPointer>(call_target(gate, Site::sliceSetManager));
+    const auto present = reinterpret_cast<PointerPredicate>(call_target(gate, Site::worldPresent));
+    const auto current =
+        reinterpret_cast<SliceSetIndexGetter>(call_target(gate, Site::currentSliceSet));
+    const auto addressable =
+        reinterpret_cast<PointerPredicate>(call_target(gate, Site::sliceSetAddressable));
+    if (manager == nullptr || present == nullptr || current == nullptr || addressable == nullptr) {
+        core::log::write(core::log::Channel::client,
+                         core::log::Level::warn,
+                         "ev=bootflow stage=current_slice result=fail reason=calls");
+        return false;
+    }
+    g_sliceSetManager.store(manager, std::memory_order_release);
+    g_worldPresent.store(present, std::memory_order_release);
+    g_currentSliceSet.store(current, std::memory_order_release);
+    g_sliceSetAddressable.store(addressable, std::memory_order_release);
+    core::log::write(core::log::Channel::client,
+                     core::log::Level::info,
+                     "ev=bootflow stage=current_slice result=ok");
+    return true;
+}
+
+/** Clears the calls it found. */
+void uninstall_targets() noexcept {
+    g_sliceSetAddressable.store(nullptr, std::memory_order_release);
+    g_currentSliceSet.store(nullptr, std::memory_order_release);
+    g_worldPresent.store(nullptr, std::memory_order_release);
+    g_sliceSetManager.store(nullptr, std::memory_order_release);
+}
+
+/** Reads the client's current local slice-set index through the spawn gate's own calls. */
+std::int32_t sample_current_slice_set() noexcept {
+    const NoArgPointer managerGetter = g_sliceSetManager.load(std::memory_order_acquire);
+    const PointerPredicate worldPresent = g_worldPresent.load(std::memory_order_acquire);
+    const SliceSetIndexGetter current = g_currentSliceSet.load(std::memory_order_acquire);
+    const PointerPredicate addressable = g_sliceSetAddressable.load(std::memory_order_acquire);
+    if (managerGetter == nullptr || worldPresent == nullptr || current == nullptr
+        || addressable == nullptr) {
+        return -2;
+    }
+    void* const manager = managerGetter();
+    if (!worldPresent(manager)) {
+        return -1;
+    }
+    std::int32_t index = -1;
+    void* const sliceSet = current(manager, &index);
+    return addressable(sliceSet) && index >= 0 && index <= kMaximumSliceSet ? index : -1;
+}
+
+} // namespace sunrise::client::hooks::bootflow::spawn

+ 19 - 0
Sunrise/src/client/hooks/bootflow/spawn/slice_set_sample.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <cstdint>
+
+namespace sunrise::client::hooks::bootflow::spawn {
+
+/**
+ * Finds the spawn gate and the four calls it reads the current slice set through.
+ * @return True when every call decoded.
+ */
+[[nodiscard]] bool install_targets() noexcept;
+
+/** Clears the calls it found. */
+void uninstall_targets() noexcept;
+
+/** @return The addressable local slice-set index, -1 while absent, or -2 when unresolved. */
+[[nodiscard]] std::int32_t sample_current_slice_set() noexcept;
+
+} // namespace sunrise::client::hooks::bootflow::spawn

+ 0 - 162
Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_probe.cpp

@@ -1,162 +0,0 @@
-#include <array>
-#include <cstdio>
-
-#include "probe.h"
-#include "spawn_gate_record_dump.h"
-
-namespace sunrise::client::hooks::bootflow::spawn {
-namespace {
-
-/** The absent player datum the gate rejects outright. */
-constexpr std::int32_t kAbsentDatum = -1;
-/** Participation record byte the gate needs set. */
-constexpr std::size_t kRecordReadyOffset = 10;
-/** Participation record byte step 36's task 9 reads, carried on the same body. */
-constexpr std::size_t kRecordTask9Offset = 8;
-/** Identity byte holding the team index, and the value meaning none was assigned. */
-constexpr std::size_t kIdentityTeamOffset = 9;
-constexpr std::uint8_t kNoTeamIndex = 0xFF;
-/** Team-state byte offset; every set bit is a host-published suppression reason. */
-constexpr std::size_t kTeamStateOffset = 1;
-/** Lifetime states that reach the passing arm of the gate's jump table. */
-constexpr std::array<std::int32_t, 3> kPassingLifetimeStates{3, 6, 10};
-
-/** Log names for each refusal, in the enum's own order. */
-constexpr std::array<const char*, 14> kRefusalNames{"dev_hold",
-                                                    "unknown",
-                                                    "datum",
-                                                    "world",
-                                                    "slice_set",
-                                                    "no_record",
-                                                    "participation",
-                                                    "lifetime",
-                                                    "world_control",
-                                                    "team_missing",
-                                                    "team_state",
-                                                    "team_index",
-                                                    "suppressed",
-                                                    "none"};
-
-/** @return True when the lifetime state reaches the passing arm. */
-[[nodiscard]] bool passing_lifetime(std::int32_t state) noexcept {
-    for (const std::int32_t passing : kPassingLifetimeStates) {
-        if (state == passing) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/**
- * Runs the team block, which the gate reaches only once a type-13 entry exists.
- * @param reading Receives the team bytes read.
- * @return The refusal, or none when every team condition passes.
- */
-[[nodiscard]] Refusal examine_team(std::int32_t datum, Reading& reading) noexcept {
-    const std::uint8_t* const identity = g_calls.identity(datum);
-    const std::uint8_t* const teamState = g_calls.teamState(datum);
-    const std::uint8_t* const teamBlock = g_calls.teamBlock(datum);
-    if (teamState == nullptr || teamBlock == nullptr) {
-        return Refusal::teamMissing;
-    }
-    reading.teamStateBits = teamState[kTeamStateOffset];
-    if (reading.teamStateBits != 0) {
-        return Refusal::teamState;
-    }
-    reading.teamIndex = identity != nullptr ? identity[kIdentityTeamOffset] : kNoTeamIndex;
-    return reading.teamIndex == kNoTeamIndex ? Refusal::teamIndex : Refusal::none;
-}
-
-} // namespace
-
-/** Re-runs the gate's conditions in order and reports the first that refuses. */
-Reading examine(std::int32_t datum) noexcept {
-    Reading reading{};
-    if (!g_ready.load(std::memory_order_acquire)) {
-        return reading;
-    }
-    // Read the type-13 and type-17 witnesses before the ordered walk, because the walk returns at
-    // the first refusal and these say which auth body applied. The lifetime state is the type-17
-    // body's own value; the reference is what the type-13 roster slot registers.
-    std::int32_t witness = 0;
-    reading.lifetimeState = g_calls.lifetimeState(&witness) ? witness : -1;
-    reading.hasType13 = g_calls.hasAnyType13();
-    if (datum == kAbsentDatum) {
-        reading.refusal = Refusal::datum;
-        return reading;
-    }
-    if (!g_calls.worldPresent(g_calls.sliceSetManager())) {
-        reading.refusal = Refusal::world;
-        return reading;
-    }
-    std::int32_t sliceSetIndex = 0;
-    void* const current = g_calls.currentSliceSet(g_calls.sliceSetManager(), &sliceSetIndex);
-    if (!g_calls.sliceSetAddressable(current)) {
-        reading.refusal = Refusal::sliceSet;
-        return reading;
-    }
-    const std::uint8_t* const record = g_calls.participationRecord(datum);
-    reading.hasRecord = record != nullptr;
-    if (record == nullptr) {
-        reading.refusal = Refusal::participationMissing;
-        return reading;
-    }
-    dump_record(record);
-    reading.recordTask9 = record[kRecordTask9Offset];
-    reading.recordReady = record[kRecordReadyOffset];
-    if (reading.recordReady == 0) {
-        reading.refusal = Refusal::participation;
-        return reading;
-    }
-    if (reading.lifetimeState < 0 || !passing_lifetime(reading.lifetimeState)) {
-        reading.refusal = Refusal::lifetime;
-        return reading;
-    }
-    if (!g_calls.worldControl()) {
-        reading.refusal = Refusal::worldControl;
-        return reading;
-    }
-    if (reading.hasType13) {
-        const Refusal team = examine_team(datum, reading);
-        if (team != Refusal::none) {
-            reading.refusal = team;
-            return reading;
-        }
-    }
-    if (g_calls.spawnSuppressed()) {
-        reading.refusal = Refusal::suppressed;
-        return reading;
-    }
-    // Everything this probe can test passed, so the remaining dev-switch arm is what refused.
-    reading.refusal = Refusal::devHold;
-    return reading;
-}
-
-/** Formats one reading as log fields. */
-std::size_t describe(const Reading& reading, std::span<char> output) noexcept {
-    if (output.empty()) {
-        return 0;
-    }
-    const auto index = static_cast<std::size_t>(reading.refusal);
-    const char* const name = index < kRefusalNames.size() ? kRefusalNames[index] : "unknown";
-    const int written = std::snprintf(output.data(),
-                                      output.size(),
-                                      "reason=%s rec=%u rec8=%u rec10=%u lifetime=%d type13=%u "
-                                      "team_bits=0x%02X "
-                                      "faction=%c sync=%c picker=%c side=%c team_idx=0x%02X",
-                                      name,
-                                      reading.hasRecord ? 1U : 0U,
-                                      reading.recordTask9,
-                                      reading.recordReady,
-                                      reading.lifetimeState,
-                                      reading.hasType13 ? 1U : 0U,
-                                      reading.teamStateBits,
-                                      (reading.teamStateBits & 1U) != 0 ? 'Y' : 'N',
-                                      (reading.teamStateBits & 2U) != 0 ? 'Y' : 'N',
-                                      (reading.teamStateBits & 4U) != 0 ? 'Y' : 'N',
-                                      (reading.teamStateBits & 8U) != 0 ? 'Y' : 'N',
-                                      reading.teamIndex);
-    return written > 0 ? static_cast<std::size_t>(written) : 0;
-}
-
-} // namespace sunrise::client::hooks::bootflow::spawn

+ 0 - 113
Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_record_dump.cpp

@@ -1,113 +0,0 @@
-#include "spawn_gate_record_dump.h"
-
-#include <Windows.h>
-
-#include <array>
-#include <atomic>
-#include <cstdio>
-
-#include "../../../../core/logging/log.h"
-#include "../../../../state/account/account_state.h"
-#include "../../../../state/runtime/runtime.h"
-
-namespace sunrise::client::hooks::bootflow::spawn {
-namespace {
-
-/**
- * Head of the participation record to dump once, in bytes.
- * The record heads a 760-byte type-13 snapshot, so this stays well inside it. 96 bytes covers
- * every small field plus the player key at `+72`, the known value that locates the run.
- */
-constexpr std::size_t kDumpBytes = 96;
-/** Bytes of the component head to dump, which covers the object identity and its registry key. */
-constexpr std::size_t kHeadBytes = 32;
-/** Two hex digits per byte for both windows, plus the fixed prefix. */
-constexpr std::size_t kDumpCapacity = (kDumpBytes + kHeadBytes) * 3 + 96;
-
-/**
- * Bytes swept each side of the record for the player key.
- * Record-relative, because the record's own offset is the question: local authority binds `+1256`,
- * the proxy arm `+496`. A key at `+72` means the body is the record, `-688` that it is not.
- */
-constexpr std::int32_t kSweepBack = 768;
-constexpr std::int32_t kSweepForward = 768;
-/** Head window dumped ahead of the record, which covers whichever base is in play. */
-constexpr std::size_t kRecordComponentOffset = 496;
-/** Reported when the sweep finds no copy of the key. */
-constexpr std::int32_t kKeyAbsent = 0x7FFFFFFF;
-
-std::atomic_bool g_dumped{false};
-
-/**
- * Finds the player key the roster publishes, near the record.
- * The key is the one value in the type-13 body with no other producer, so where it lands says
- * whether that body reached this object and which arm bound the record.
- * @return Record-relative offset of the key, or `kKeyAbsent` when it is not near it.
- */
-[[nodiscard]] std::int32_t find_player_key(const std::uint8_t* record) noexcept {
-    const state::AccountState account = state::account_snapshot();
-    // A backward sweep can leave the allocation on the proxy arm, so a fault ends the sweep, not
-    // the process.
-    __try {
-        for (std::int32_t offset = -kSweepBack; offset <= kSweepForward; ++offset) {
-            std::uint64_t candidate = 0;
-            for (std::size_t byte = 0; byte < sizeof candidate; ++byte) {
-                candidate |= static_cast<std::uint64_t>(record[offset + static_cast<int>(byte)])
-                             << (8U * byte);
-            }
-            // The roster publishes the character the join named, which is not always the selected
-            // one, so any authored character means the body landed.
-            for (std::size_t index = 0; index < account.characterCount; ++index) {
-                if (candidate != 0 && candidate == account.characters[index].soid) {
-                    return offset;
-                }
-            }
-        }
-    } __except (EXCEPTION_EXECUTE_HANDLER) {
-        return kKeyAbsent;
-    }
-    return kKeyAbsent;
-}
-
-} // namespace
-
-/** Dumps the head of the participation record once per run, with the player key's own offset. */
-void dump_record(const std::uint8_t* record) noexcept {
-    if (g_dumped.exchange(true, std::memory_order_relaxed)) {
-        return;
-    }
-    // The component head carries the object's own identity, including the registry key at its
-    // `+12`. That key says whether this object is in the group message 5 publishes.
-    const std::uint8_t* const component = record - kRecordComponentOffset;
-    std::array<char, kDumpCapacity> line{};
-    int written = std::snprintf(line.data(),
-                                line.size(),
-                                "ev=bootflow stage=spawn_record key_at=%d head=",
-                                find_player_key(record));
-    for (std::size_t index = 0; index < kHeadBytes && written > 0; ++index) {
-        const int step = std::snprintf(line.data() + written,
-                                       line.size() - static_cast<std::size_t>(written),
-                                       "%02X",
-                                       component[index]);
-        written = step > 0 ? written + step : 0;
-    }
-    if (written > 0) {
-        const int step = std::snprintf(
-            line.data() + written, line.size() - static_cast<std::size_t>(written), " bytes=");
-        written = step > 0 ? written + step : 0;
-    }
-    for (std::size_t index = 0; index < kDumpBytes && written > 0; ++index) {
-        const int step = std::snprintf(line.data() + written,
-                                       line.size() - static_cast<std::size_t>(written),
-                                       "%02X",
-                                       record[index]);
-        written = step > 0 ? written + step : 0;
-    }
-    if (written > 0) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::debug,
-                         {line.data(), static_cast<std::size_t>(written)});
-    }
-}
-
-} // namespace sunrise::client::hooks::bootflow::spawn

+ 0 - 39
Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_record_dump.h

@@ -1,39 +0,0 @@
-#pragma once
-
-#include <atomic>
-#include <cstdint>
-
-namespace sunrise::client::hooks::bootflow::spawn {
-
-using NoArgPointer = void*(__fastcall*)() noexcept;
-using PointerPredicate = bool(__fastcall*)(void*) noexcept;
-using SliceSetIndexGetter = void*(__fastcall*)(void*, std::int32_t*) noexcept;
-using DatumPointer = std::uint8_t*(__fastcall*)(std::int32_t) noexcept;
-using OutPredicate = bool(__fastcall*)(std::int32_t*) noexcept;
-using NoArgPredicate = bool(__fastcall*)() noexcept;
-
-/** Every predicate the gate calls, found from the call operands inside its own body. */
-struct Predicates {
-    NoArgPointer sliceSetManager{};
-    PointerPredicate worldPresent{};
-    SliceSetIndexGetter currentSliceSet{};
-    PointerPredicate sliceSetAddressable{};
-    DatumPointer participationRecord{};
-    OutPredicate lifetimeState{};
-    NoArgPredicate worldControl{};
-    NoArgPredicate hasAnyType13{};
-    DatumPointer identity{};
-    DatumPointer teamState{};
-    DatumPointer teamBlock{};
-    NoArgPredicate spawnSuppressed{};
-};
-
-/** The predicates, published only once every one of them decoded. */
-inline Predicates g_calls{};
-/** Guards g_calls: the probe reads nothing until the resolver publishes a complete set. */
-inline std::atomic_bool g_ready{false};
-
-/** Dumps the head of the participation record once per run, with the player key's own offset. */
-void dump_record(const std::uint8_t* record) noexcept;
-
-} // namespace sunrise::client::hooks::bootflow::spawn

+ 0 - 150
Sunrise/src/client/hooks/bootflow/spawn/spawn_gate_targets.cpp

@@ -1,150 +0,0 @@
-#include <atomic>
-#include <cstddef>
-
-#include "../../../patterns/image_scan.h"
-#include "probe.h"
-#include "spawn_gate_record_dump.h"
-
-namespace sunrise::client::hooks::bootflow::spawn {
-namespace {
-
-/** Opcode of the near call every site must hold. It proves the offset is still right. */
-constexpr std::byte kCallOpcode{0xE8};
-/** Length of a near call, and the offset of its displacement. */
-constexpr std::size_t kCallLength = 5;
-constexpr std::size_t kCallOperand = 1;
-/** Largest index accepted by the client's own slice-set-to-bubble mapper. */
-constexpr std::int32_t kMaximumSliceSet = 0x1FF;
-
-std::atomic<NoArgPointer> g_sliceSetManager{nullptr};
-std::atomic<PointerPredicate> g_worldPresent{nullptr};
-std::atomic<SliceSetIndexGetter> g_currentSliceSet{nullptr};
-std::atomic<PointerPredicate> g_sliceSetAddressable{nullptr};
-
-/**
- * Byte offsets of each predicate's call, from the gate's own base, in gate order.
- * Each site is checked for the call opcode before its operand is decoded, so a moved body
- * refuses the probe instead of calling a wrong address.
- */
-struct Site {
-    /** The slice-set manager accessor both world conditions start from. */
-    static constexpr std::size_t sliceSetManager = 0x23;
-    /** G1: a world is present. */
-    static constexpr std::size_t worldPresent = 0x2B;
-    /** G2's input: the current slice-set index. */
-    static constexpr std::size_t currentSliceSet = 0x49;
-    /** G2: that index is addressable. */
-    static constexpr std::size_t sliceSetAddressable = 0x51;
-    /** G3: the participation record message 5's type-13 body binds. */
-    static constexpr std::size_t participationRecord = 0x83;
-    /** G4: the activity lifetime state the jump table switches on. */
-    static constexpr std::size_t lifetimeState = 0xC5;
-    /** G5: the world-controller handles are ready. */
-    static constexpr std::size_t worldControl = 0xF0;
-    /** G6: a type-13 client reference exists, which arms the whole team block. */
-    static constexpr std::size_t hasAnyType13 = 0x100;
-    /** G6c's input: the identity block carrying the team index. */
-    static constexpr std::size_t identity = 0x10F;
-    /** G6a and G6b's input: the team state whose byte 1 holds the suppression bits. */
-    static constexpr std::size_t teamState = 0x119;
-    /** G6a's second input: the team block. */
-    static constexpr std::size_t teamBlock = 0x123;
-    /** G7: the host published the spawn-suppressed state. */
-    static constexpr std::size_t spawnSuppressed = 0x197;
-};
-
-/**
- * Decodes one call target, refusing unless the site really holds a near call.
- * @param offset Byte offset of the call from the gate base.
- * @return The called address, or null when the site is not a near call.
- */
-[[nodiscard]] void* call_target(const std::byte* gate, std::size_t offset) noexcept {
-    const std::byte* const site = gate + offset;
-    if (*site != kCallOpcode) {
-        return nullptr;
-    }
-    return patterns::resolve_relative(site + kCallOperand, site + kCallLength);
-}
-
-/** @return True when every predicate decoded. */
-[[nodiscard]] bool complete(const Predicates& calls) noexcept {
-    return calls.sliceSetManager != nullptr && calls.worldPresent != nullptr
-           && calls.currentSliceSet != nullptr && calls.sliceSetAddressable != nullptr
-           && calls.participationRecord != nullptr && calls.lifetimeState != nullptr
-           && calls.worldControl != nullptr && calls.hasAnyType13 != nullptr
-           && calls.identity != nullptr && calls.teamState != nullptr && calls.teamBlock != nullptr
-           && calls.spawnSuppressed != nullptr;
-}
-
-} // namespace
-
-/** Finds the gate's own predicates from the call operands inside it. */
-bool resolve(const std::byte* gate) noexcept {
-    g_ready.store(false, std::memory_order_release);
-    g_sliceSetManager.store(nullptr, std::memory_order_release);
-    g_worldPresent.store(nullptr, std::memory_order_release);
-    g_currentSliceSet.store(nullptr, std::memory_order_release);
-    g_sliceSetAddressable.store(nullptr, std::memory_order_release);
-    if (gate == nullptr) {
-        return false;
-    }
-    Predicates calls{};
-    calls.sliceSetManager =
-        reinterpret_cast<NoArgPointer>(call_target(gate, Site::sliceSetManager));
-    calls.worldPresent = reinterpret_cast<PointerPredicate>(call_target(gate, Site::worldPresent));
-    calls.currentSliceSet =
-        reinterpret_cast<SliceSetIndexGetter>(call_target(gate, Site::currentSliceSet));
-    calls.sliceSetAddressable =
-        reinterpret_cast<PointerPredicate>(call_target(gate, Site::sliceSetAddressable));
-    calls.participationRecord =
-        reinterpret_cast<DatumPointer>(call_target(gate, Site::participationRecord));
-    calls.lifetimeState = reinterpret_cast<OutPredicate>(call_target(gate, Site::lifetimeState));
-    calls.worldControl = reinterpret_cast<NoArgPredicate>(call_target(gate, Site::worldControl));
-    calls.hasAnyType13 = reinterpret_cast<NoArgPredicate>(call_target(gate, Site::hasAnyType13));
-    calls.identity = reinterpret_cast<DatumPointer>(call_target(gate, Site::identity));
-    calls.teamState = reinterpret_cast<DatumPointer>(call_target(gate, Site::teamState));
-    calls.teamBlock = reinterpret_cast<DatumPointer>(call_target(gate, Site::teamBlock));
-    calls.spawnSuppressed =
-        reinterpret_cast<NoArgPredicate>(call_target(gate, Site::spawnSuppressed));
-    if (!complete(calls)) {
-        return false;
-    }
-    g_calls = calls;
-    g_sliceSetManager.store(calls.sliceSetManager, std::memory_order_release);
-    g_worldPresent.store(calls.worldPresent, std::memory_order_release);
-    g_currentSliceSet.store(calls.currentSliceSet, std::memory_order_release);
-    g_sliceSetAddressable.store(calls.sliceSetAddressable, std::memory_order_release);
-    g_ready.store(true, std::memory_order_release);
-    return true;
-}
-
-/** Clears the predicates it found. */
-void forget() noexcept {
-    g_ready.store(false, std::memory_order_release);
-    g_sliceSetAddressable.store(nullptr, std::memory_order_release);
-    g_currentSliceSet.store(nullptr, std::memory_order_release);
-    g_worldPresent.store(nullptr, std::memory_order_release);
-    g_sliceSetManager.store(nullptr, std::memory_order_release);
-    g_calls = {};
-}
-
-/** Reads the client's current local slice-set index through the spawn gate's own calls. */
-std::int32_t sample_current_slice_set() noexcept {
-    const NoArgPointer managerGetter = g_sliceSetManager.load(std::memory_order_acquire);
-    const PointerPredicate worldPresent = g_worldPresent.load(std::memory_order_acquire);
-    const SliceSetIndexGetter current = g_currentSliceSet.load(std::memory_order_acquire);
-    const PointerPredicate addressable = g_sliceSetAddressable.load(std::memory_order_acquire);
-    if (managerGetter == nullptr || worldPresent == nullptr || current == nullptr
-        || addressable == nullptr) {
-        return -2;
-    }
-    void* const manager = managerGetter();
-    if (!worldPresent(manager)) {
-        return -1;
-    }
-    std::int32_t index = -1;
-    void* const sliceSet = current(manager, &index);
-    return addressable(sliceSet) && index >= 0 && index <= kMaximumSliceSet ? index : -1;
-}
-
-} // namespace sunrise::client::hooks::bootflow::spawn

+ 0 - 154
Sunrise/src/client/hooks/bootflow/spawn_hold.cpp

@@ -1,154 +0,0 @@
-#include <Windows.h>
-
-#include <algorithm>
-#include <array>
-#include <atomic>
-#include <cstdint>
-#include <cstdio>
-#include <string_view>
-
-#include "../../../core/logging/log.h"
-#include "../../../core/settings/settings.h"
-#include "../../../state/activity/runtime.h"
-#include "../../hooking/detour.h"
-#include "internal.h"
-#include "spawn/probe.h"
-
-namespace sunrise::client::hooks::bootflow {
-namespace {
-
-/**
- * The player spawn gate. Anchored on the load of the encrypted manager global, then run on
- * through the stack-cookie store because the wildcarded frame size leaves the head too short.
- */
-constexpr std::string_view kSpawnGateSignatureText =
-    "40 53 57 41 57 48 81 EC ? ? ? ? 48 8B 05 ? ? ? ? 48 33 C4 48 89 84 24 ? ? ? ? 8B D9 "
-    "40 B7 01";
-/** Compiled pattern bytes of the signature text above. */
-constexpr auto kSpawnGateSignature =
-    signature<signature_length(kSpawnGateSignatureText)>(kSpawnGateSignatureText);
-
-/** Answer that holds the spawn for this tick. The gate is polled, so a refusal only delays it. */
-constexpr bool kHeld = false;
-
-/** The gate runs every frame, so an unchanged refusal is repeated only this often. */
-constexpr std::uint64_t kRefusalRepeatMs = 5'000;
-
-using SpawnGate = bool(__fastcall*)(std::int32_t) noexcept;
-
-hooking::detour::Handle g_handle{};
-std::atomic<SpawnGate> g_original{nullptr};
-std::atomic<std::uint64_t> g_lastProbeTick{};
-std::atomic<spawn::Refusal> g_lastRefusal{spawn::Refusal::unknown};
-
-/** Reports a changed refusal immediately and a persistent refusal every five seconds. */
-void report_spawn_refusal(std::int32_t datum,
-                          state::activity::WorldPhase phase,
-                          std::uint64_t age) noexcept {
-    const spawn::Reading reading = spawn::examine(datum);
-    const std::uint64_t now = GetTickCount64();
-    const spawn::Refusal previous = g_lastRefusal.exchange(reading.refusal);
-    const std::uint64_t last = g_lastProbeTick.load(std::memory_order_relaxed);
-    if (reading.refusal == previous && now - last < kRefusalRepeatMs) {
-        return;
-    }
-    g_lastProbeTick.store(now, std::memory_order_relaxed);
-    std::array<char, core::log::kLineCapacity> fields{};
-    const std::size_t count = spawn::describe(reading, fields);
-    std::array<char, core::log::kLineCapacity> line{};
-    const int written = std::snprintf(line.data(),
-                                      line.size(),
-                                      "ev=bootflow stage=spawn_gate result=blocked phase=%u "
-                                      "age=%llu %.*s",
-                                      static_cast<unsigned>(phase),
-                                      static_cast<unsigned long long>(age),
-                                      static_cast<int>(count),
-                                      fields.data());
-    if (written > 0) {
-        core::log::write(
-            core::log::Channel::client,
-            core::log::Level::warn,
-            {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
-    }
-}
-
-/**
- * Puts the spawn after the world-transition fade is armed.
- * A release on a channel that is not up does nothing, so a spawn during the load leaves the
- * screen black. The client's own predicate reads a host field this destination never fills.
- * @param datum Borrowed player datum handle; the answer does not depend on it.
- * @return The native answer, or held while a destination load is still running.
- */
-__declspec(noinline) bool __fastcall spawn_gate(std::int32_t datum) noexcept {
-    const SpawnGate original = g_original.load(std::memory_order_acquire);
-    const bool allowed = original != nullptr && original(datum);
-    observe_world_step();
-    const state::activity::WorldPhase phase = state::activity::world_phase();
-    const bool transitioning = phase == state::activity::WorldPhase::transitioning;
-    // Zero unless a load is running.
-    const std::uint64_t age = state::activity::world_transition_age();
-    const core::settings::client::Settings& client = core::settings::get().client;
-    const bool gaveUp = age >= client.spawnHoldMs;
-    const bool loading = transitioning && !gaveUp && client.holdSpawn;
-    // Release only on arrival. The step-37 exit re-arms the fade unless one is already up, and
-    // nothing polls this gate after the spawn, so an early release leaves a fade nobody clears.
-    if (phase == state::activity::WorldPhase::arrived) {
-        release_world_fade();
-    }
-    if (!allowed && transitioning) {
-        report_spawn_refusal(datum, phase, age);
-    }
-    return allowed && loading ? kHeld : allowed;
-}
-
-} // namespace
-
-/** Stages the spawn hold. */
-StageResult stage_spawn_hold(hooking::detour::Spec& spec) noexcept {
-    if (g_handle.attached) {
-        return StageResult::attached;
-    }
-    std::byte* const target = scan_main_image_unique(kSpawnGateSignature, "player_spawn_gate");
-    if (target == nullptr) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=bootflow stage=spawn_hold result=fail reason=target");
-        return StageResult::unavailable;
-    }
-    if (!spawn::resolve(target)) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=bootflow stage=current_slice result=fail reason=targets");
-    }
-    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&spawn_gate)};
-    return StageResult::staged;
-}
-
-/** Takes the spawn hold's attached handle, or a detached one. */
-void publish_spawn_hold(const hooking::detour::Handle& handle) noexcept {
-    if (!handle.attached) {
-        spawn::forget();
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=bootflow stage=spawn_hold result=fail reason=attach");
-        return;
-    }
-    g_handle = handle;
-    g_original.store(reinterpret_cast<SpawnGate>(g_handle.original), std::memory_order_release);
-    core::log::write(core::log::Channel::client,
-                     core::log::Level::info,
-                     "ev=bootflow stage=spawn_hold result=ok");
-}
-
-/** Detaches the spawn hold. */
-void uninstall_spawn_hold() noexcept {
-    if (g_handle.attached) {
-        (void)hooking::detour::uninstall(g_handle);
-    }
-    g_original.store(nullptr, std::memory_order_release);
-    g_lastProbeTick.store(0, std::memory_order_relaxed);
-    g_lastRefusal.store(spawn::Refusal::unknown, std::memory_order_relaxed);
-    spawn::forget();
-}
-
-} // namespace sunrise::client::hooks::bootflow

+ 2 - 31
Sunrise/src/client/hooks/bootflow/world_step.cpp

@@ -5,10 +5,9 @@
 #include <string_view>
 
 #include "../../../core/logging/log.h"
-#include "../../../state/activity/runtime.h"
 #include "bootflow_hook_lifecycle.h"
 #include "internal.h"
-#include "spawn/probe.h"
+#include "spawn/slice_set_sample.h"
 
 namespace sunrise::client::hooks::bootflow {
 namespace {
@@ -24,9 +23,7 @@ constexpr std::string_view kStepSignatureText =
 /** Compiled pattern bytes of the signature text above. */
 constexpr auto kStepSignature = signature<signature_length(kStepSignatureText)>(kStepSignatureText);
 
-/** First step that loads the map with no player in it yet. */
-constexpr std::int32_t kActivityLoadFirst = 33;
-/** `activity:in_world`. The fade is armed by then, so a spawn now releases it. */
+/** `activity:in_world`. */
 constexpr std::int32_t kInWorld = 38;
 /** No step has been published. */
 constexpr std::int32_t kNoStep = -1;
@@ -63,13 +60,6 @@ void poll_world_step() noexcept {
 /** Publishes the client's current local slice-set index. */
 void poll_current_slice_set() noexcept {
     const std::int32_t index = spawn::sample_current_slice_set();
-    const std::int32_t previous = g_publishedSliceSet.load(std::memory_order_relaxed);
-    // A slice-set change is a world replacement whose transition arms a fresh fade, and a
-    // teleport never passes the off-destination step that re-arms the release. Re-arm here or
-    // the new world stays black behind the spent one-shot.
-    if (index >= 0 && previous >= 0 && index != previous) {
-        rearm_fade_release();
-    }
     g_publishedSliceSet.store(index, std::memory_order_relaxed);
     g_publishedSliceSetTick.store(GetTickCount64(), std::memory_order_release);
 }
@@ -97,25 +87,6 @@ bool in_world() noexcept {
     return published != 0 && GetTickCount64() - published < kStepStaleMs;
 }
 
-/** Maps the client's own boot-flow step onto the world phase. */
-void observe_world_step() noexcept {
-    // A missing accessor leaves the phase alone. A step of -1 is a real answer: off a destination.
-    if (g_step.load(std::memory_order_acquire) == nullptr) {
-        return;
-    }
-    const std::int32_t step = read_step();
-    state::activity::WorldPhase phase = state::activity::WorldPhase::idle;
-    if (step == kInWorld) {
-        phase = state::activity::WorldPhase::arrived;
-    } else if (step >= kActivityLoadFirst && step < kInWorld) {
-        phase = state::activity::WorldPhase::transitioning;
-    } else {
-        // Off a destination, so the next load is a fresh arming and logs its own release line.
-        rearm_fade_release();
-    }
-    state::activity::note_world_phase(phase);
-}
-
 /** Finds the boot-flow step accessor. */
 bool install_world_step() noexcept {
     std::byte* const target = scan_main_image_unique(kStepSignature, "bootflow_current_step");

+ 0 - 130
Sunrise/src/client/hooks/peer_relay/peer_relay_direct.cpp

@@ -1,130 +0,0 @@
-/**
- * Forces the peer channel to connect directly instead of through a NAT relay.
- * The channel manager suppresses its relay only when this call's fourth argument is set, and
- * every stock caller passes zero, so the gameplay peer channel always relays. On a loopback
- * host with no relay server that channel never connects. Forcing the flag makes the client
- * secure the channel directly against the endpoint the host binds.
- */
-
-#include "peer_relay_direct.h"
-
-#include <array>
-#include <atomic>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <string_view>
-
-#include "../../../core/logging/log.h"
-#include "../../../core/settings/settings.h"
-#include "../../hooking/detour.h"
-#include "../../patterns/image_scan.h"
-#include "../../patterns/signature_text.h"
-
-namespace sunrise::client::hooks::peer_relay {
-namespace {
-
-using patterns::scan_main_image_unique;
-using patterns::signature;
-using patterns::signature_length;
-
-/**
- * `NetChannel_RequestConnectForFamily`. Its fourth argument is the suppress-relay flag; the body
- * tests the channel flag byte at `+0x3068` and its stride constant is `0x41F0`.
- */
-constexpr std::string_view kConnectSignatureText =
-    "48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 20 41 0F B6 F9 49 63 D8 8B F2 E8 ? ? ? ? 4C 69 C3 "
-    "F0 41 00 00 49 81 C0 A8 00 00 00 4C 03 C0 41 F6 80 68 30 00 00 01";
-/** Compiled form of the pattern above; the scan requires one match. */
-constexpr auto kConnectSignature =
-    signature<signature_length(kConnectSignatureText)>(kConnectSignatureText);
-
-/** The suppress-relay flag value that selects a direct connect. */
-constexpr std::uint64_t kSuppressRelay = 1;
-
-/** The channel connect request this detour replaces. */
-using ConnectForFamily = std::int64_t(__fastcall*)(void*,
-                                                   std::uint32_t,
-                                                   std::int32_t,
-                                                   std::uint64_t);
-
-hooking::detour::Handle g_handle{};
-std::atomic_bool g_reported{false};
-
-/** Writes the one line naming which answer this run uses. */
-void report(const char* result) noexcept {
-    if (g_reported.exchange(true, std::memory_order_relaxed)) {
-        return;
-    }
-    std::array<char, 96> line{};
-    const int written =
-        std::snprintf(line.data(), line.size(), "ev=peer_relay stage=connect result=%s", result);
-    if (written > 0) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::info,
-                         {line.data(), static_cast<std::size_t>(written)});
-    }
-}
-
-/**
- * Requests the channel connect, forcing the relay suppressed when the direct mode is on.
- * @param container Borrowed channel container, passed through.
- * @param family Family index, passed through.
- * @param slot Channel slot index, passed through.
- * @param suppressRelay The stock flag; replaced with the direct value when the mode is on.
- * @return The original result.
- */
-__declspec(noinline)
-/** Detour body: forces the direct-connect flag when the setting is on. */
-std::int64_t __fastcall connect_for_family(void* container,
-                                           std::uint32_t family,
-                                           std::int32_t slot,
-                                           std::uint64_t suppressRelay) noexcept {
-    const auto original = reinterpret_cast<ConnectForFamily>(g_handle.original);
-    if (original == nullptr) {
-        return 0;
-    }
-    if (core::settings::get().client.suppressPeerRelay) {
-        report("direct");
-        return original(container, family, slot, kSuppressRelay);
-    }
-    report("native");
-    return original(container, family, slot, suppressRelay);
-}
-
-} // namespace
-
-/** Attaches the peer-channel direct-connect forcing. */
-bool install() noexcept {
-    if (g_handle.attached) {
-        return true;
-    }
-    std::byte* const target = scan_main_image_unique(kConnectSignature, "peer_relay_connect");
-    if (target == nullptr) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=peer_relay stage=connect result=fail reason=target");
-        return false;
-    }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&connect_for_family)};
-    if (!hooking::detour::install(spec, g_handle)) {
-        core::log::write(core::log::Channel::client,
-                         core::log::Level::warn,
-                         "ev=peer_relay stage=connect result=fail reason=attach");
-        return false;
-    }
-    core::log::write(core::log::Channel::client,
-                     core::log::Level::info,
-                     "ev=peer_relay stage=connect result=ok");
-    return true;
-}
-
-/** Detaches the peer-channel direct-connect forcing. */
-void uninstall() noexcept {
-    if (g_handle.attached) {
-        (void)hooking::detour::uninstall(g_handle);
-    }
-    g_reported.store(false, std::memory_order_release);
-}
-
-} // namespace sunrise::client::hooks::peer_relay

+ 0 - 11
Sunrise/src/client/hooks/peer_relay/peer_relay_direct.h

@@ -1,11 +0,0 @@
-#pragma once
-
-namespace sunrise::client::hooks::peer_relay {
-
-/** Attaches the peer-channel direct-connect forcing. @return True when the detour attaches. */
-[[nodiscard]] bool install() noexcept;
-
-/** Detaches the peer-channel direct-connect forcing. */
-void uninstall() noexcept;
-
-} // namespace sunrise::client::hooks::peer_relay

+ 0 - 4
Sunrise/src/client/runtime/client_hook_activation.cpp

@@ -31,7 +31,6 @@
 #include "../hooks/network/runtime.h"
 #include "../hooks/noclip/runtime.h"
 #include "../hooks/package_trust/package_trust_bypass.h"
-#include "../hooks/peer_relay/peer_relay_direct.h"
 #include "../hooks/polled_input/runtime.h"
 #include "../hooks/queuez/queuez_hook_lifecycle.h"
 #include "../hooks/retail_log/retail_log_lifecycle.h"
@@ -185,9 +184,6 @@ void clear_game_targets() noexcept {
     // A sense-record chain that stops terminating after a slice-set teardown holds the whole
     // frame graph. The guard logs the runaway chain and skips its walk for that tick.
     (void)hooks::sense_chain_guard::install();
-    // The stock client always relays the gameplay peer channel, which cannot complete against a
-    // loopback host. When enabled, this forces a direct connect. Off by default.
-    (void)hooks::peer_relay::install();
     // The stock async-I/O wrapper reloads its singleton after pumping it and can observe the
     // legitimate teardown/recreate null window. This optional guard keeps the owner it pumped.
     (void)hooks::async_io::install();

+ 0 - 27
Sunrise/src/core/settings/client/client_settings_parser.cpp

@@ -11,15 +11,11 @@ bool Parser::client_settings(client::Settings& output) noexcept {
     bool hasUserInterface = false;
     bool hasExternalServer = false;
     bool hasCustomBootflowTextures = false;
-    bool hasFadeRelease = false;
     bool hasSocketMenuRouting = false;
     bool hasRevealLoreBooks = false;
     bool hasRegionPrivate = false;
     bool hasSkipOrbitCinematicWait = false;
-    bool hasSuppressPeerRelay = false;
     bool hasPinReplicatedRecord = false;
-    bool hasHoldSpawn = false;
-    bool hasSpawnHoldMs = false;
     if (consume('}')) {
         return true;
     }
@@ -43,11 +39,6 @@ bool Parser::client_settings(client::Settings& output) noexcept {
                 return false;
             }
             hasCustomBootflowTextures = true;
-        } else if (key == "fade_release") {
-            if (hasFadeRelease || !boolean(candidate.fadeRelease)) {
-                return false;
-            }
-            hasFadeRelease = true;
         } else if (key == "socket_menu_routing") {
             if (hasSocketMenuRouting || !boolean(candidate.socketMenuRouting)) {
                 return false;
@@ -68,29 +59,11 @@ bool Parser::client_settings(client::Settings& output) noexcept {
                 return false;
             }
             hasSkipOrbitCinematicWait = true;
-        } else if (key == "suppress_peer_relay") {
-            if (hasSuppressPeerRelay || !boolean(candidate.suppressPeerRelay)) {
-                return false;
-            }
-            hasSuppressPeerRelay = true;
         } else if (key == "pin_replicated_record") {
             if (hasPinReplicatedRecord || !boolean(candidate.pinReplicatedRecord)) {
                 return false;
             }
             hasPinReplicatedRecord = true;
-        } else if (key == "hold_spawn") {
-            if (hasHoldSpawn || !boolean(candidate.holdSpawn)) {
-                return false;
-            }
-            hasHoldSpawn = true;
-        } else if (key == "spawn_hold_ms") {
-            std::uint64_t value = 0;
-            if (hasSpawnHoldMs || !unsigned_integer(value) || value == 0
-                || value > client::kMaximumSpawnHoldMs) {
-                return false;
-            }
-            candidate.spawnHoldMs = value;
-            hasSpawnHoldMs = true;
         } else if (!skip_value(0)) {
             return false;
         }

+ 2 - 34
Sunrise/src/core/settings/client/definition.h

@@ -1,17 +1,10 @@
 #pragma once
 
-#include <cstdint>
-
 #include "../../ui/runtime/settings.h"
 #include "external/definition.h"
 
 namespace sunrise::core::settings::client {
 
-/** A load this long has stopped making progress, so the spawn stops waiting for it. */
-inline constexpr std::uint64_t kDefaultSpawnHoldMs = 30'000;
-/** A load past this is a hang, not a slow machine, and holding the spawn would never end. */
-inline constexpr std::uint64_t kMaximumSpawnHoldMs = 600'000;
-
 /** Read-only Client settings parsed by Core. */
 struct Settings {
     /** In-game UI visibility and input policy. */
@@ -20,17 +13,7 @@ struct Settings {
     external::Settings externalServer;
     /** Replaces stock bootflow textures that have matching DDS assets embedded in Sunrise. */
     bool customBootflowTextures{true};
-    /**
-     * Releases the world-transition fade channel at the in-world step.
-     * The client only releases it on the player spawn, so this covers a spawn that never runs
-     * and leaves the world black. On by default.
-     */
-    bool fadeRelease{true};
-    /**
-     * Moves the four Arrivals leg mods into the leg plug set, so the leg mod menu lists them.
-     * Off by default. Retail shipped the same package data and showed the same gap, so this is a
-     * content correction, not a retail behaviour. It may be removed.
-     */
+    /** Moves the four Arrivals leg mods into the leg plug set, so the leg mod menu lists them. */
     bool socketMenuRouting{false};
     /**
      * Clears the visibility gates on the loaded lore presentation nodes.
@@ -49,26 +32,11 @@ struct Settings {
      * suppresses the orbit-side entry cinematic.
      */
     bool skipOrbitCinematicWait{false};
-    /**
-     * Forces the peer channel to connect directly instead of through a NAT relay.
-     * The stock client always relays the gameplay peer channel, which cannot complete against a
-     * loopback host with no relay server. On by default; a client stand-in for the peer relay.
-     */
-    bool suppressPeerRelay{true};
     /**
      * Pins the participation record to the replicated snapshot at `comp + 496`.
-     * Off, the record is the local one at `comp + 1256`, whose spawn-gate byte no wire field
-     * reaches.
+     * The msg-5 spawn hold reaches no other record.
      */
     bool pinReplicatedRecord{true};
-    /**
-     * Runs the player spawn after the world-transition fade is armed.
-     * A spawn before the arm releases nothing, so the screen stays black. Settable because it is
-     * the only thing that can turn an allowed spawn into a refusal.
-     */
-    bool holdSpawn{true};
-    /** How long the spawn waits for a load. `hold_spawn` decides whether it waits at all. */
-    std::uint64_t spawnHoldMs{kDefaultSpawnHoldMs};
 };
 
 } // namespace sunrise::core::settings::client

+ 9 - 5
Sunrise/src/core/settings/settings_upgrade.cpp

@@ -30,7 +30,7 @@ struct ReplacedMember {
  * Members replaced with the bundled default, each with the version that changed it.
  * A member is listed because its value form changed, or because its default changed.
  */
-constexpr std::array<ReplacedMember, 11> kReplacedMembers{{
+constexpr std::array<ReplacedMember, 10> kReplacedMembers{{
     {"\"key_bindings\"", 3},
     {"\"region_private\"", 5},
     {"\"topology\"", 5},
@@ -39,9 +39,8 @@ constexpr std::array<ReplacedMember, 11> kReplacedMembers{{
     // Version 8 turned the flat payout list into rows filtered by rarity, gear class and
     // masterwork state.
     {"\"dismantle_rewards\"", 8},
-    // Version 13 turned these two on. A file that never carried them takes the new default; one
-    // that carried the old value is corrected here.
-    {"\"suppress_peer_relay\"", 13},
+    // Version 13 turned this on. A file that never carried it takes the new default; one that
+    // carried the old value is corrected here.
     {"\"activity_public_membership\"", 13},
     // Version 15 seeded the lore book unlock slots, so both banks take the new default.
     {"\"character_flags\"", 15},
@@ -83,7 +82,7 @@ struct RemovedMember {
  * Only the first occurrence of a name is removed, so a member repeated per character, such as the
  * version-16 `accepted`, is left for the parser to skip as an unknown key.
  */
-constexpr std::array<RemovedMember, 7> kRemovedMembers{{
+constexpr std::array<RemovedMember, 11> kRemovedMembers{{
     {"\"force_join_request_ready\"", 12},
     // Version 14 dropped two client stand-ins and moved the catalyst gate under
     // `state.investment`, so the root copy is no longer read.
@@ -94,6 +93,11 @@ constexpr std::array<RemovedMember, 7> kRemovedMembers{{
     {"\"activity_compatibility_mirror\"", 16},
     {"\"gameplay_external_body\"", 16},
     {"\"server_default_entity\"", 16},
+    // Version 16 dropped the four switches of the deleted spawn hold, fade release and relay hooks.
+    {"\"suppress_peer_relay\"", 16},
+    {"\"fade_release\"", 16},
+    {"\"hold_spawn\"", 16},
+    {"\"spawn_hold_ms\"", 16},
 }};
 
 /** One splice per replaced, renamed and removed member, plus the version member itself. */

+ 1 - 1
Sunrise/src/server/bap/encrypted/push/activity/activity_roster_snapshot.cpp

@@ -610,7 +610,7 @@ build_roster_snapshot(Session& session,
     snapshot.lifetime = lifetimeState;
     // Hold the native spawn gate until the client's arrival report, WS-702 world-state 8. A
     // region can be loaded before the bootflow arms its fade; a spawn before the arm releases an
-    // inactive fade and leaves the screen black. Arrival does not depend on the spawn (RE/30).
+    // inactive fade and leaves the screen black. Arrival does not depend on the spawn.
     snapshot.awaitClientSync = !client_in_world(session, refresh);
     // Player_BindComponents walks every type-13 reference and the player datum can name any one of
     // them. So every participation record carries the same player key. Selecting the first slot

+ 0 - 56
Sunrise/src/state/activity/activity_world_arrival.cpp

@@ -1,56 +0,0 @@
-#include <Windows.h>
-
-#include <atomic>
-
-#include "runtime.h"
-
-namespace sunrise::state::activity {
-namespace {
-
-/**
- * How far the client has got through the current destination load.
- * The spawn gate writes and reads this, so it is an atomic, not lock-guarded State. It is one
- * observed value and no transaction depends on it.
- */
-std::atomic<WorldPhase> g_phase{WorldPhase::idle};
-/** Tick the current load started on. Read only while the phase is transitioning. */
-std::atomic<std::uint64_t> g_transitionTick{};
-/** Durable edge generation; the mission runtime may sample after arrived has already fallen. */
-std::atomic<std::uint64_t> g_arrivalRevision{};
-
-} // namespace
-
-/** Records how far the current destination load has got. */
-void note_world_phase(WorldPhase phase) noexcept {
-    const WorldPhase previous = g_phase.load(std::memory_order_relaxed);
-    // Stamped before the phase, so a reader that sees transitioning never reads a stale tick.
-    if (phase == WorldPhase::transitioning && previous != WorldPhase::transitioning) {
-        g_transitionTick.store(GetTickCount64(), std::memory_order_relaxed);
-    }
-    g_phase.store(phase, std::memory_order_relaxed);
-    if (phase == WorldPhase::arrived && previous != WorldPhase::arrived) {
-        g_arrivalRevision.fetch_add(1, std::memory_order_release);
-    }
-}
-
-/** @return How far the client has got through the current destination load. */
-WorldPhase world_phase() noexcept {
-    return g_phase.load(std::memory_order_relaxed);
-}
-
-/** @return Durable generation of the latest edge into arrived. */
-std::uint64_t world_arrival_revision() noexcept {
-    return g_arrivalRevision.load(std::memory_order_acquire);
-}
-
-/** @return Milliseconds since the running load started, or zero when none is running. */
-std::uint64_t world_transition_age() noexcept {
-    if (g_phase.load(std::memory_order_relaxed) != WorldPhase::transitioning) {
-        return 0;
-    }
-    const std::uint64_t started = g_transitionTick.load(std::memory_order_relaxed);
-    const std::uint64_t now = GetTickCount64();
-    return now > started ? now - started : 0;
-}
-
-} // namespace sunrise::state::activity

+ 0 - 29
Sunrise/src/state/activity/runtime.h

@@ -142,33 +142,4 @@ struct SessionRosterRow final {
 /** Releases one retain only when the exact record generation still matches. */
 void release_binding(const SessionBinding& binding) noexcept;
 
-/** How far the client has got through the current destination load. */
-enum class WorldPhase : std::uint8_t {
-    /** No destination load is running. Orbit sits here, and the spawn is never held. */
-    idle,
-    /** The step that arms the black fade has started and the in-world step has not been reached. */
-    transitioning,
-    /** The in-world step is entered, so the fade is armed and a spawn now releases it. */
-    arrived,
-};
-
-/**
- * Records how far the current destination load has got.
- * Entering transitioning from any other phase resets the load's start tick.
- * @param phase Phase the client's own boot-flow step maps to.
- */
-void note_world_phase(WorldPhase phase) noexcept;
-
-/** @return How far the client has got through the current destination load. */
-[[nodiscard]] WorldPhase world_phase() noexcept;
-
-/**
- * @return Monotonic generation incremented on every edge into arrived.
- * An arrival that begins and ends between two server ticks still moves this.
- */
-[[nodiscard]] std::uint64_t world_arrival_revision() noexcept;
-
-/** @return Milliseconds since the running load started, or zero when none is running. */
-[[nodiscard]] std::uint64_t world_transition_age() noexcept;
-
 } // namespace sunrise::state::activity

+ 1 - 1
Sunrise/src/state/investment/investment.h

@@ -6,7 +6,7 @@
 
 namespace sunrise::state {
 
-/** The native family-5 lists hold 100 rows each (RE/27). The 7-bit wire count is not the limit. */
+/** The native family-5 lists hold 100 rows each. The 7-bit wire count is not the limit. */
 inline constexpr std::size_t kUnlockOverrideCapacity = 100;
 
 /** One logical unlock-flag value stored by slot. */