Przeglądaj źródła

Leave a found chapter claimable rather than claimed

Finding lore completes a triumph; claiming it is the player's act. The pickup
now marks a record complete-but-unclaimed instead of claiming it outright.

The completion flag reads as a small enum, not a boolean: the authored policy
uses zero and two, and one is unused. Setting one makes the client count the
triumph as known without counting it complete -- Confessions went from three of
four complete under a claim to zero of one under a mark -- which is what a
found, unclaimed chapter should look like.

A claim supersedes a mark when a record carries both, so the two never fight.

The name still reads Secret Triumph, and did so under a claim too, so the
obscuring is not driven by completion. That is the record visibility gate and is
a separate problem.
Millie 2 tygodni temu
rodzic
commit
be59598cb9

+ 6 - 2
Sunrise/src/state/lore/lore_grant.cpp

@@ -104,10 +104,14 @@ GrantOutcome grant_next_chapter(std::uint16_t node) noexcept {
             continue;
         }
         sawChapter = true;
-        if (record_claims::claimed(record.completionFlagIndex)) {
+        // Finding lore completes a chapter; claiming it is the player's act, not this one. So the
+        // record is left claimable rather than claimed, and a chapter already in either state is
+        // passed over.
+        if (record_claims::claimed(record.completionFlagIndex)
+            || record_claims::claimable(record.completionFlagIndex)) {
             continue;
         }
-        if (!record_claims::claim(record.completionFlagIndex, record.scoreValue)) {
+        if (!record_claims::mark_claimable(record.completionFlagIndex)) {
             return GrantOutcome::refused;
         }
         g_lastGranted.store(record.definitionIndex, std::memory_order_relaxed);

+ 43 - 0
Sunrise/src/state/record_claims/record_claims.cpp

@@ -37,6 +37,8 @@ constexpr std::uint32_t kMaximumEntries = 8192;
 
 std::mutex g_lock;
 std::array<std::uint64_t, kWordCount> g_claimed{};
+/** Records complete but not yet claimed. A claim supersedes this, never the other way round. */
+std::array<std::uint64_t, kWordCount> g_claimable{};
 std::array<std::uint16_t, kIndexCapacity> g_scoreByIndex{};
 std::size_t g_count{};
 std::uint32_t g_score{};
@@ -189,6 +191,7 @@ bool initialize(void* module) noexcept {
 void clear() noexcept {
     const std::lock_guard<std::mutex> guard(g_lock);
     g_claimed.fill(0);
+    g_claimable.fill(0);
     g_scoreByIndex.fill(0);
     g_count = 0;
     g_score = 0;
@@ -235,6 +238,24 @@ std::size_t apply(std::span<std::uint8_t> accountFlags) noexcept {
             }
         }
     }
+
+    // Claimable records fill in behind the claims: a record that is both stays claimed, since a
+    // claim is the later state and overwriting it would undo what the player did.
+    for (std::size_t word = 0; word < g_claimable.size(); ++word) {
+        std::uint64_t bits = g_claimable[word] & ~g_claimed[word];
+        while (bits != 0) {
+            const auto offset = static_cast<std::size_t>(std::countr_zero(bits));
+            bits &= bits - 1;
+            const std::size_t index = word * kWordBits + offset;
+            if (index >= accountFlags.size()) {
+                continue;
+            }
+            if (accountFlags[index] != unlocks::kFlagAvailable) {
+                accountFlags[index] = unlocks::kFlagAvailable;
+                ++changed;
+            }
+        }
+    }
     return changed;
 }
 
@@ -376,6 +397,28 @@ bool claimed(std::uint16_t flagIndex) noexcept {
     return claimed_locked(flagIndex);
 }
 
+/** Marks one record complete but unclaimed. */
+bool mark_claimable(std::uint16_t flagIndex) noexcept {
+    if (static_cast<std::size_t>(flagIndex) >= kIndexCapacity) {
+        return false;
+    }
+    const std::lock_guard<std::mutex> guard(g_lock);
+    g_claimable[static_cast<std::size_t>(flagIndex) / kWordBits] |=
+        1ULL << (static_cast<std::size_t>(flagIndex) % kWordBits);
+    return true;
+}
+
+/** @return True when this index is marked claimable. */
+bool claimable(std::uint16_t flagIndex) noexcept {
+    if (static_cast<std::size_t>(flagIndex) >= kIndexCapacity) {
+        return false;
+    }
+    const std::lock_guard<std::mutex> guard(g_lock);
+    return (g_claimable[static_cast<std::size_t>(flagIndex) / kWordBits]
+            & (1ULL << (static_cast<std::size_t>(flagIndex) % kWordBits)))
+           != 0;
+}
+
 /** @return Total score of every held claim. */
 std::uint32_t total_score() noexcept {
     const std::lock_guard<std::mutex> guard(g_lock);

+ 13 - 0
Sunrise/src/state/record_claims/record_claims.h

@@ -37,6 +37,19 @@ void clear() noexcept;
  */
 [[nodiscard]] bool claim(std::uint16_t flagIndex, std::uint16_t scoreValue) noexcept;
 
+/**
+ * Marks one record complete but unclaimed, so the client offers it as claimable.
+ *
+ * Finding lore completes a triumph; claiming it is a separate act by the player. A record marked
+ * this way and later claimed carries the claim instead, so the two never conflict.
+ * @param flagIndex Mapping-table row whose object byte feeds the record's completion flag.
+ * @return True when the index is in range and the record is now marked.
+ */
+[[nodiscard]] bool mark_claimable(std::uint16_t flagIndex) noexcept;
+
+/** @return True when this index is marked claimable, whether or not it is also claimed. */
+[[nodiscard]] bool claimable(std::uint16_t flagIndex) noexcept;
+
 /**
  * Lays every held claim over one account flag bank.
  * @param accountFlags Bank already filled from the authored policy.

+ 11 - 0
Sunrise/src/state/unlocks/definition.h

@@ -32,6 +32,17 @@ using ProgressionBank = std::array<ProgressionLanes, build_data::progressions::k
 
 /** A set acquired flag is stored as its biased 2-bit true value. */
 inline constexpr std::uint8_t kFlagSet = 2;
+
+/**
+ * A record that is complete but not yet claimed, which the client offers as claimable.
+ *
+ * The flag reads as a small enum rather than a boolean: zero and two are the values the authored
+ * policy uses, and one is unused by it. Finding lore should leave a triumph waiting to be claimed
+ * rather than claiming it, which is what this value is for. That reading is inferred from the gap
+ * in the enum, not measured, so a record that stays invisible when set to it is the sign it is
+ * wrong.
+ */
+inline constexpr std::uint8_t kFlagAvailable = 1;
 /** A clear acquired flag is stored as zero. */
 inline constexpr std::uint8_t kFlagClear = 0;