Просмотр исходного кода

Satisfy the book gates here, and measure what claimable actually is

The lore visibility work existed on the triumph branch but was never ported, so
every gated book read as unnamed. Both overlays now run, and the node and record
tables are published at account encode: they are not in the build data cache, so
a warm start had nothing for the gates to work against and the port would have
looked inert.

Measured, and it contradicts what this branch was written to assume: a triumph
the client shows as claimable carries zero in its completion flag. Record 2, By
Thy Tongue Be Damned, is claimable in game and reads zero, as does every other
child of its book. So the flag does not express claimable, and writing one there
only makes the client count the triumph as known.

Also measured: what separates that record from an obscured lore chapter is in
the content, not in anything sent. Record 2 carries a handle at row offset 0x60
and record 1708 carries the unavailable sentinel; 0xD0 is featuredPriority,
matching the published manifest exactly, which confirms the rows are read right.
Content belongs to the client, so that difference explains the behaviour without
offering anything to change. 1474 of 2242 records carry no handle at 0x60.
Millie 2 недель назад
Родитель
Сommit
c0ba1c6b75

+ 32 - 0
Sunrise/src/middleware/datagen/family4/account/account_encoder.cpp

@@ -1,3 +1,14 @@
+#include "../../../../state/build_data/records/record_persistence.h"
+#include "../../../../state/build_data/records/record_catalog.h"
+#include "../../../../state/build_data/nodes/node_persistence.h"
+#include <atomic>
+#include "../../../../core/logging/log.h"
+#include "../../../../state/build_data/records/definition.h"
+#include <cstdio>
+#include <array>
+#include <span>
+#include <vector>
+#include "../../../../state/build_data/nodes/node_catalog.h"
 #include "../../../../state/record_claims/record_claims.h"
 #include "account_encoder.h"
 
@@ -96,9 +107,30 @@ bool encode(const state::AccountState& state, std::span<std::byte> output) noexc
     object.acquiredFlags = unlocks.accountFlags;
     object.profileUnlockFlags = unlocks.profileFlags;
     object.objectiveValues = unlocks.objectiveValues;
+    // The node and record tables are not in the build data cache, so on a warm start the package
+    // pass is skipped and both are empty. The account image needs them: without nodes no book gate
+    // is satisfied and every book reads as unnamed. Publishing here, latched on success, is what
+    // makes a warm start look like a cold one.
+    {
+        static std::atomic<bool> published{false};
+        if (!published.load(std::memory_order_relaxed)) {
+            const bool haveNodes = state::build_data::nodes::count() != 0
+                                   || state::build_data::nodes::load_and_publish();
+            const bool haveRecords = state::build_data::records::count() != 0
+                                     || state::build_data::records::load_and_publish();
+            if (haveNodes && haveRecords) {
+                published.store(true, std::memory_order_relaxed);
+            }
+        }
+    }
+
     // Claims are laid over the authored bank on the way out, so a claimed record reads Acquired on
     // the next image. The authored policy itself is immutable and is never edited.
     (void)state::record_claims::apply(object.acquiredFlags);
+    // A lore book's category is gated: some read a flag, some test their own progress value. A book
+    // gated on progress cannot open by being played, since with no title shown there is nothing
+    // inside to collect. Satisfying the gate is what makes the book readable at all.
+    (void)state::build_data::nodes::apply_visibility(object.acquiredFlags);
 
     for (layout::CharacterUnlockBlock& block : object.characterUnlocks) {
         block.flags = unlocks.characterFlags;

+ 7 - 0
Sunrise/src/middleware/datagen/family4/character/character_encoder.cpp

@@ -1,3 +1,5 @@
+#include "../../../../state/record_claims/record_claims.h"
+#include "../../../../state/build_data/nodes/node_catalog.h"
 #include "character_encoder.h"
 
 #include <algorithm>
@@ -157,6 +159,11 @@ bool encode(const state::CharacterState& state,
             index < unlocks.characterObjectFlags.size() ? unlocks.characterObjectFlags[index]
                                                         : std::uint8_t{});
     }
+    // One book's gate is character scoped rather than account scoped, and one counts its progress
+    // in the character bank, so both passes run here as well as on the account image.
+    (void)state::build_data::nodes::apply_character_visibility(object.acquiredFlags);
+    (void)state::record_claims::apply_character_node_progress(object.objectiveValues);
+
     for (std::size_t index = 0; index < object.objectiveValues.size(); ++index) {
         object.objectiveValues[index] =
             index < unlocks.characterObjectValues.size() ? unlocks.characterObjectValues[index] : 0;