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

Fill the chapter gate block instead of addressing it per chapter

Addressing the block as 1935 + record row was wrong and the measurements
could not have caught it: every sweep that located the block wrote one
value across a contiguous span, and a uniform span satisfies a chapter
whichever slot it occupies. Per-chapter values exposed it -- The Dreaming
City's "Riven" vanished while the slots either side of its supposed one
stayed lit, and Truth to Power lost all eleven chapters a uniform fill had
shown.

The block itself is measured and stands. It is now filled to the largest
completion value any chapter in it asks for, read from the shipped table
rather than hard-coded, which reproduces the state measured good. The
slot-to-chapter mapping inside the block remains unknown and is documented
as such; resolving it needs a per-slot sweep with distinct values.
Millie 2 недель назад
Родитель
Сommit
318e20767c
1 измененных файлов с 29 добавлено и 18 удалено
  1. 29 18
      Sunrise/src/state/record_claims/record_claims.cpp

+ 29 - 18
Sunrise/src/state/record_claims/record_claims.cpp

@@ -502,7 +502,10 @@ constexpr std::uint16_t kChapterGateLastRow = 106;
 /** Publishes the per-chapter visibility gate of the Year 1 lore chapters. */
 std::size_t apply_chapter_visibility_gates(std::span<std::int32_t> objectiveValues) noexcept {
     const std::span<const objective_slot_table::RecordEntry> table{objective_slot_table::kRecords};
-    std::size_t written = 0;
+    // The largest completion value any chapter in the block asks for. Read from the shipped table
+    // rather than hard-coded: the corrupted-egg records count to nine and Truth to Power's last
+    // chapter to eleven, and a gate written below a chapter's own requirement leaves it redacted.
+    std::int32_t ceiling = 1;
     for (std::uint16_t row = 0; row <= kChapterGateLastRow; ++row) {
         build_data::records::Definition record{};
         if (!build_data::find_record_definition(row, record)
@@ -510,30 +513,38 @@ std::size_t apply_chapter_visibility_gates(std::span<std::int32_t> objectiveValu
             || record.completionFlagIndex == build_data::records::kUnavailableFlagIndex) {
             continue;
         }
-        const std::int32_t slot = kChapterGateBase + static_cast<std::int32_t>(row);
-        if (slot < kChapterGateFirstWritable
-            || static_cast<std::size_t>(slot) >= objectiveValues.size()) {
-            continue;
-        }
-        // The gate is a threshold, so the chapter's own completion value is exactly enough: a
-        // counted chapter -- the corrupted-egg records run to nine -- needs its real count, and a
-        // flat 1 would leave every one of those redacted.
-        std::int32_t need = 1;
         const auto found = std::lower_bound(
             table.begin(), table.end(), record.completionFlagIndex,
             [](const objective_slot_table::RecordEntry& entry, std::uint16_t flag) noexcept {
                 return entry.flagIndex < flag;
             });
-        if (found != table.end() && found->flagIndex == record.completionFlagIndex) {
-            for (std::uint8_t i = 0; i < found->objectiveCount; ++i) {
-                const std::size_t at = static_cast<std::size_t>(found->firstObjective) + i;
-                if (at < objective_slot_table::kObjectives.size()) {
-                    need = std::max(need, objective_slot_table::kObjectives[at].completionValue);
-                }
+        if (found == table.end() || found->flagIndex != record.completionFlagIndex) {
+            continue;
+        }
+        for (std::uint8_t i = 0; i < found->objectiveCount; ++i) {
+            const std::size_t at = static_cast<std::size_t>(found->firstObjective) + i;
+            if (at < objective_slot_table::kObjectives.size()) {
+                ceiling = std::max(ceiling, objective_slot_table::kObjectives[at].completionValue);
             }
         }
-        if (objectiveValues[static_cast<std::size_t>(slot)] < need) {
-            objectiveValues[static_cast<std::size_t>(slot)] = need;
+    }
+
+    // The block is filled uniformly rather than addressed per chapter. Which slot inside it belongs
+    // to which chapter is NOT known: every measurement that located this block wrote one value
+    // across a contiguous span, and a uniform span satisfies a chapter wherever it sits, so none of
+    // them could distinguish the mapping. Addressing it as 1935 + record row was tried and is
+    // wrong -- The Dreaming City's "Riven" vanished while the slots either side of its supposed one
+    // stayed lit, and Truth to Power lost all eleven chapters that a uniform fill had shown.
+    // Filling to the ceiling reproduces the state measured good; resolving the mapping needs a
+    // per-slot sweep with distinct values and is worth doing before anything relies on it.
+    std::size_t written = 0;
+    const std::int32_t last = kChapterGateBase + static_cast<std::int32_t>(kChapterGateLastRow);
+    for (std::int32_t slot = kChapterGateFirstWritable; slot <= last; ++slot) {
+        if (static_cast<std::size_t>(slot) >= objectiveValues.size()) {
+            break;
+        }
+        if (objectiveValues[static_cast<std::size_t>(slot)] < ceiling) {
+            objectiveValues[static_cast<std::size_t>(slot)] = ceiling;
         }
         ++written;
     }