Переглянути джерело

Reveal lore books on collection; keep bars on claims where they differ

A live account reveals a lore book once something in it has been collected
and needs nothing claimed, while a bar moves on claim. Those are two
numbers, and the node's own value slot is the first of them: it counts what
has been collected and it is what the category gate reads.

For twenty-five books that slot is distinct from the parent bar and both
behave correctly. For ten it is the same slot, so it can hold one number.
Confirmed three ways: the count shows on the parent triumph, it tracked the
collected total exactly book by book, and redirecting the bar to
node.parentValueIndex changed nothing -- 2351 and its neighbours are not
their bars. Holding claims there would hide the book until a claim and a
chapter cannot be claimed while invisible, so it holds collected and those
ten read collected on their parent triumph. Forced by the allocation.

Every other bank was swept before accepting that: the whole account value
bank at 100 with the gate slots excluded, the character value bank, the
profile and character flag banks, and the account flags around the known
visibility run. None reveals these books. The account flag bank cannot be
swept wholesale -- 4009 flags at once takes a character load down.
Millie 2 тижнів тому
батько
коміт
4c1603ea91

+ 34 - 11
Sunrise/src/state/record_claims/parent_bar_table.h

@@ -42,7 +42,30 @@ namespace sunrise::state::record_claims::parent_bar_table {
  * Node 819, Letters from a Renegade, is the one row the +8 method fails: its shipped objective
  * tracks an unrelated record's value. Its entry below is measured and stands.
  *
- * An entry naming the node's own gate is therefore legitimate, not a defect. What keeps such a
+ * Ten entries name the same index the node's gate reads, and that is not an error to fix.
+ * Confirmed three times in game: the number appears on the book's parent triumph, it tracked the
+ * collected count exactly book by book, and pointing the bar instead at node.parentValueIndex
+ * changed nothing -- 2351 and its neighbours are not those books' bars.
+ *
+ * One slot, two roles, so it can hold one number. A live account reveals a lore book once
+ * something in it has been collected and needs nothing claimed, while a bar elsewhere moves on
+ * claim. Here the two coincide: holding claims would hide the book until a claim, and a chapter
+ * cannot be claimed while it is invisible, so the count written here is what has been COLLECTED.
+ * Their parent triumph therefore reads collected rather than claimed. That is a consequence of
+ * the shipped allocation, not a choice, and it is the only value that does not deadlock.
+ *
+ * Every other bank was swept looking for a separate gate before accepting this: the whole account
+ * value bank at 100, the character value bank, the profile and character flag banks, and the
+ * account flags around the known visibility run. None of them reveals these books.
+ *
+ * The original superseded reading follows. That is genuine and confirmed in game:
+ * the number appears on the book's parent triumph, and it tracked the collected count exactly,
+ * book by book, when that count was written there. So the slot is the parent bar AND the gate.
+ *
+ * That is not a contradiction to resolve by dropping the entries -- they were dropped once on the
+ * theory that the index was a category-header counter, and it is not. It does mean those ten books
+ * cannot reveal themselves: the bar counts claims, a hidden chapter cannot be claimed, so the gate
+ * can never rise on its own. Something outside this table opens them on a live account. What keeps such a
  * book visible at zero chapters is ordering alone: nodes::apply_category_gates runs after this
  * pass and raises a zero gate back to one. That call must stay last -- see account_encoder.
  */
@@ -72,21 +95,21 @@ inline constexpr std::array<Bar, 35> kBars{{
     {848U, 2267U},  // The Black Armory Papers (measured)
     {849U, 2348U},  // Ecdysis (measured)
     {851U, 2397U},  // Nothing Ends (measured)
-    {822U, 2342U},  // Dust — gate is bar (measured 2342, equals its gate)
-    {823U, 2344U},  // Stolen Intelligence — gate is bar
-    {825U, 2520U},  // Luna's Lost — gate is bar
-    {826U, 2521U},  // Letters from Eris — gate is bar
-    {839U, 2514U},  // Unveiling — gate is bar
-    {840U, 2517U},  // Last Days on Kraken Mare — gate is bar
-    {841U, 2519U},  // Inquisition of the Damned — gate is bar
-    {850U, 2341U},  // A Man with No Name — gate is bar
-    {852U, 2516U},  // Aspect — gate is bar
-    {853U, 2518U},  // Revelation — gate is bar
     {824U, 2349U},  // The Warlock Aunor — decoded, unconfirmed (gate 2346)
     {828U, 2575U},  // Constellations — decoded, unconfirmed (gate 2574)
     {829U, 2665U},  // Duress and Egress — decoded, unconfirmed (gate 2664)
     {854U, 2583U},  // The Liar — decoded, unconfirmed (gate 2584)
     {835U, 2265U},  // The Book of Unmaking (measured)
+    {850U, 2341U},  // A Man with No Name — gate and parent bar are one slot; see below
+    {822U, 2342U},  // Dust — gate and parent bar are one slot; see below
+    {823U, 2344U},  // Stolen Intelligence — gate and parent bar are one slot; see below
+    {839U, 2514U},  // Unveiling — gate and parent bar are one slot; see below
+    {852U, 2516U},  // Aspect — gate and parent bar are one slot; see below
+    {840U, 2517U},  // Last Days on Kraken Mare — gate and parent bar are one slot; see below
+    {853U, 2518U},  // Revelation — gate and parent bar are one slot; see below
+    {841U, 2519U},  // Inquisition of the Damned — gate and parent bar are one slot; see below
+    {825U, 2520U},  // Luna's Lost — gate and parent bar are one slot; see below
+    {826U, 2521U},  // Letters from Eris — gate and parent bar are one slot; see below
 }};
 
 } // namespace sunrise::state::record_claims::parent_bar_table

+ 26 - 9
Sunrise/src/state/record_claims/record_claims.cpp

@@ -584,6 +584,7 @@ std::size_t apply_node_progress(std::span<std::int32_t> objectiveValues) noexcep
             // resolved -- several books ship with no resolvable slot at all and were skipped
             // entirely while the count keyed on it.
             std::int32_t chapters = 0;
+            std::int32_t collected = 0;
             build_data::records::Definition parent{};
             bool haveParent = false;
             for (std::size_t child = 0; child < node.childCount; ++child) {
@@ -605,6 +606,14 @@ std::size_t apply_node_progress(std::span<std::int32_t> objectiveValues) noexcep
                 if (claimed_locked(record.completionFlagIndex)) {
                     ++chapters;
                 }
+                // The category tile counts what has been collected, not what has been claimed.
+                // It has to: the tile's counter is also the gate that reveals the book, and a
+                // book that stayed hidden until a claim could never be opened at all, since a
+                // chapter cannot be claimed while it is invisible.
+                if (claimed_locked(record.completionFlagIndex)
+                    || claimable_locked(record.completionFlagIndex)) {
+                    ++collected;
+                }
             }
 
             // The bar reads the value index the parent record's own expression names, read out of
@@ -629,18 +638,26 @@ std::size_t apply_node_progress(std::span<std::int32_t> objectiveValues) noexcep
                     break;
                 }
             }
-            // A book whose gate slot is not its bar slot needs the count in both. That slot is the
-            // book's entries-read counter: the category opens when it rises above zero, and on a
-            // cumulative book -- where chapter n completes at value n rather than 1 -- every
-            // chapter compares against it, so a token 1 left chapters 2 upward locked however
-            // correct their own objectives were. Kept below the record-objective range for the
-            // same reason apply_category_gates is: three books name a slot inside it that belongs
-            // to a record's objective, and writing a count there redacts records wholesale.
+            // The node's own value slot is the category tile's counter, and it is a different
+            // number from the parent triumph's bar: the tile counts entries collected, the bar
+            // counts triumphs claimed. Conflating them is what made ten books look as though
+            // their gate were their bar -- writing the bar revealed them, but only because a
+            // category with progress shows itself. On a cumulative book every chapter compares
+            // against this counter too, so it has to carry the real total and not a token 1.
+            // Kept below the record-objective range for the same reason apply_category_gates is:
+            // three books name a slot inside it that belongs to a record's objective, and writing
+            // a count there redacts records wholesale.
             if (node.valueIndex != build_data::nodes::kUnavailableValueIndex
                 && static_cast<std::int32_t>(node.valueIndex) < objective_slot_table::kRecordObjectiveRangeStart
                 && static_cast<std::size_t>(node.valueIndex) < state->values.size()
-                && static_cast<std::int32_t>(node.valueIndex) != parentSlot) {
-                state->values[node.valueIndex] = chapters;
+                ) {
+                // Collected, not claimed. This slot is the book's entries counter, and on a
+                // cumulative book every chapter compares against it -- chapter n completes at n --
+                // so a counter tracking claims leaves exactly one chapter claimable and the rest
+                // locked, which is what claiming-only produced. It is only reached when the slot
+                // is not also the book's bar: the guard above skips it when they coincide, and for
+                // those ten the slot is the bar and has to keep counting claims.
+                state->values[node.valueIndex] = collected;
                 ++state->written;
             }
             // Eight books name no value at field 136 and so have no table entry, but their parent