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

Clarify quest initialization contracts and submission coverage

Gage Fulwood 3 дней назад
Родитель
Сommit
1496ce51c8

+ 2 - 2
Sunrise/src/client/content/items/packages/package_item_build.cpp

@@ -113,8 +113,8 @@ bool build() noexcept {
             }
             }
             // The same root names the bucket and socket-list tables.
             // The same root names the bucket and socket-list tables.
             storage.root = storage.child;
             storage.root = storage.child;
-            // Records, nodes, season pass rewards and catalysts all resolve slots through the
-            // two unlock mapping tables, so they are read once here.
+            // Quest initialization, records, nodes, season rewards and catalysts share the
+            // unlock mapping tables, so they are read once here.
             if (!state::build_data::item_definitions_ready()
             if (!state::build_data::item_definitions_ready()
                 || !state::build_data::record_definitions_ready()
                 || !state::build_data::record_definitions_ready()
                 || !state::build_data::node_definitions_ready()
                 || !state::build_data::node_definitions_ready()

+ 13 - 12
Sunrise/src/middleware/content/packages/tables/quest_initialization_reader.cpp

@@ -11,10 +11,10 @@ namespace {
 using Quest = state::build_data::items::QuestInitialization;
 using Quest = state::build_data::items::QuestInitialization;
 
 
 /** These are serialized block pointers, not count/relative array descriptors. */
 /** These are serialized block pointers, not count/relative array descriptors. */
-bool block(std::span<const std::byte> bytes,
-           std::size_t field,
-           std::uint32_t expectedClass,
-           std::size_t& offset) noexcept {
+[[nodiscard]] bool block(std::span<const std::byte> bytes,
+                         std::size_t field,
+                         std::uint32_t expectedClass,
+                         std::size_t& offset) noexcept {
     std::int64_t relative = 0;
     std::int64_t relative = 0;
     if (!read(bytes, field, relative) || relative == 0
     if (!read(bytes, field, relative) || relative == 0
         || relative
         || relative
@@ -31,11 +31,12 @@ bool block(std::span<const std::byte> bytes,
     return read(bytes, offset - 4, actualClass) && actualClass == expectedClass;
     return read(bytes, offset - 4, actualClass) && actualClass == expectedClass;
 }
 }
 
 
-bool array(std::span<const std::byte> bytes,
-           std::size_t field,
-           std::uint32_t expectedClass,
-           std::size_t stride,
-           Array& rows) noexcept {
+/** Bounds an authored array using its element class and fixed stride. */
+[[nodiscard]] bool array(std::span<const std::byte> bytes,
+                         std::size_t field,
+                         std::uint32_t expectedClass,
+                         std::size_t stride,
+                         Array& rows) noexcept {
     return find_array_at(bytes, field, rows) && rows.elementClass == expectedClass
     return find_array_at(bytes, field, rows) && rows.elementClass == expectedClass
            && rows.dataOffset <= bytes.size()
            && rows.dataOffset <= bytes.size()
            && rows.count <= (bytes.size() - rows.dataOffset) / stride;
            && rows.count <= (bytes.size() - rows.dataOffset) / stride;
@@ -48,7 +49,7 @@ std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept {
     std::size_t objective = 0;
     std::size_t objective = 0;
     std::uint16_t parent = 0xFFFFU;
     std::uint16_t parent = 0xFFFFU;
     Array objectives{};
     Array objectives{};
-    if (definition.size() < 240 || !read(definition, 184, bucket) || bucket != 40
+    if (definition.size() < 240 || !read(definition, kBucketIdOffset, bucket) || bucket != 40
         || !block(definition, 0x30, 0x808077EBU, objective)
         || !block(definition, 0x30, 0x808077EBU, objective)
         || !array(definition, objective, 0x808087B1U, 2, objectives)
         || !array(definition, objective, 0x808087B1U, 2, objectives)
         || !read(definition, objective + 0x1C, parent)) {
         || !read(definition, objective + 0x1C, parent)) {
@@ -95,8 +96,8 @@ Quest read_quest_initialization(std::span<const std::byte> definition,
     if (separateRoot) {
     if (separateRoot) {
         std::uint8_t parentBucket = 0;
         std::uint8_t parentBucket = 0;
         std::int64_t parentObjective = 0;
         std::int64_t parentObjective = 0;
-        if (parentIndex == itemIndex || !read(parent, 184, parentBucket) || parentBucket != 37
-            || !read(parent, 0x30, parentObjective) || parentObjective != 0) {
+        if (parentIndex == itemIndex || !read(parent, kBucketIdOffset, parentBucket)
+            || parentBucket != 37 || !read(parent, 0x30, parentObjective) || parentObjective != 0) {
             return {};
             return {};
         }
         }
     }
     }

+ 13 - 2
Sunrise/src/middleware/content/packages/tables/quest_initialization_reader.h

@@ -1,14 +1,25 @@
 #pragma once
 #pragma once
 
 
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
 #include "../../../../state/build_data/items/quest_initialization.h"
 #include "../../../../state/build_data/items/quest_initialization.h"
-#include "items.h"
 
 
 namespace sunrise::middleware::content::packages::tables::items {
 namespace sunrise::middleware::content::packages::tables::items {
 
 
 /** Returns the objective block's set-bearing item index, or 0xFFFF when unsupported. */
 /** Returns the objective block's set-bearing item index, or 0xFFFF when unsupported. */
 [[nodiscard]] std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept;
 [[nodiscard]] std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept;
 
 
-/** Unsupported/malformed contracts return an empty plan; they never imply a guessed write. */
+/**
+ * Resolves the first member of a supported quest set to its saved value-bank row.
+ * @param definition Objective-bearing pursuit definition.
+ * @param itemIndex Definition's ordinal in the item table.
+ * @param parent Set-bearing definition named by quest_parent, possibly definition itself.
+ * @param itemCount Bounds for authored item indices.
+ * @param valueMap Serialized unlock value mapping table.
+ * @return An empty plan for unsupported or malformed content; no guessed state write.
+ */
 [[nodiscard]] state::build_data::items::QuestInitialization
 [[nodiscard]] state::build_data::items::QuestInitialization
 read_quest_initialization(std::span<const std::byte> definition,
 read_quest_initialization(std::span<const std::byte> definition,
                           std::uint16_t itemIndex,
                           std::uint16_t itemIndex,

+ 2 - 1
Sunrise/src/state/build_data/items/quest_initialization.h

@@ -6,7 +6,7 @@
 
 
 namespace sunrise::state::build_data::items {
 namespace sunrise::state::build_data::items {
 
 
-/** Only the first member of a supported, item-presence-gated quest set. */
+/** Authored initial value and bank row for the first member of a supported quest set. */
 struct QuestInitialization {
 struct QuestInitialization {
     enum class Scope : std::uint8_t { none, account, character };
     enum class Scope : std::uint8_t { none, account, character };
     std::int32_t value{};
     std::int32_t value{};
@@ -16,6 +16,7 @@ struct QuestInitialization {
     bool operator==(const QuestInitialization&) const = default;
     bool operator==(const QuestInitialization&) const = default;
 };
 };
 
 
+/** An empty plan is valid; a supported plan must fit its persistent value bank. */
 [[nodiscard]] constexpr bool valid(const QuestInitialization& quest) noexcept {
 [[nodiscard]] constexpr bool valid(const QuestInitialization& quest) noexcept {
     using Scope = QuestInitialization::Scope;
     using Scope = QuestInitialization::Scope;
     if (quest.scope == Scope::none) {
     if (quest.scope == Scope::none) {

+ 2 - 1
Sunrise/src/state/runtime/runtime.h

@@ -140,6 +140,7 @@ struct PendingItemAcquisition {
     std::int32_t previousQuestValue{};
     std::int32_t previousQuestValue{};
     bool prepared{};
     bool prepared{};
 
 
+    /** Account-scoped initialization must publish even when acquisition charges no materials. */
     [[nodiscard]] bool updates_account() const noexcept {
     [[nodiscard]] bool updates_account() const noexcept {
         return profileChanged
         return profileChanged
                || (questInitialization.scope
                || (questInitialization.scope
@@ -574,7 +575,7 @@ set_selected_title(std::uint16_t recordIndex, std::uint64_t& characterSoid, bool
 [[nodiscard]] bool
 [[nodiscard]] bool
 reserve_selected_character_inventory_serial(std::int32_t& mutationSerial) noexcept;
 reserve_selected_character_inventory_serial(std::int32_t& mutationSerial) noexcept;
 
 
-/** Builds the exact full-account after-image while a prepared item pull remains current. */
+/** Builds account and unlock after-images without committing the prepared acquisition. */
 [[nodiscard]] bool preview_item_acquisition(const PendingItemAcquisition& mutation,
 [[nodiscard]] bool preview_item_acquisition(const PendingItemAcquisition& mutation,
                                             AccountState& after,
                                             AccountState& after,
                                             unlocks::Table& afterUnlocks) noexcept;
                                             unlocks::Table& afterUnlocks) noexcept;

+ 3 - 2
Sunrise/src/state/runtime/state_account_acquisition_runtime.cpp

@@ -25,6 +25,7 @@ namespace runtime::detail {
 
 
 using Quest = build_data::items::QuestInitialization;
 using Quest = build_data::items::QuestInitialization;
 
 
+/** Selects the persistent bank after the nonempty initialization plan has been validated. */
 [[nodiscard]] investment::store::Bank quest_bank(const Quest& quest) noexcept {
 [[nodiscard]] investment::store::Bank quest_bank(const Quest& quest) noexcept {
     return quest.scope == Quest::Scope::account ? investment::store::Bank::objectiveValues
     return quest.scope == Quest::Scope::account ? investment::store::Bank::objectiveValues
                                                 : investment::store::Bank::characterObjectValues;
                                                 : investment::store::Bank::characterObjectValues;
@@ -387,7 +388,7 @@ valid_item_acquisition_source(const PendingItemAcquisition& mutation) noexcept {
            && definition.definitionHash == mutation.acquiredDefinitionHash;
            && definition.definitionHash == mutation.acquiredDefinitionHash;
 }
 }
 
 
-/** Applies one validated insertion over an exact current account without taking State locks. */
+/** Applies an insertion and checks its saved quest value while the caller holds the State lock. */
 [[nodiscard]] bool materialize_item_acquisition(const AccountState& current,
 [[nodiscard]] bool materialize_item_acquisition(const AccountState& current,
                                                 const PendingItemAcquisition& mutation,
                                                 const PendingItemAcquisition& mutation,
                                                 AccountState& after) noexcept {
                                                 AccountState& after) noexcept {
@@ -523,7 +524,7 @@ bool preview_direct_item_bundle(const PendingDirectItemBundle& mutation,
     return materialize_direct_item_bundle(account_snapshot(), mutation, after);
     return materialize_direct_item_bundle(account_snapshot(), mutation, after);
 }
 }
 
 
-/** Commits one prepared insertion only while its prepare-time loadout remains current. */
+/** Commits inventory and initial quest state together while the prepared view remains current. */
 bool commit_item_acquisition(PendingItemAcquisition& mutation) noexcept {
 bool commit_item_acquisition(PendingItemAcquisition& mutation) noexcept {
     const PendingItemAcquisition& prepared = mutation;
     const PendingItemAcquisition& prepared = mutation;
     const PendingConsumption consume{mutation};
     const PendingConsumption consume{mutation};

+ 6 - 12
tests/README.md

@@ -1,7 +1,5 @@
 # Quest-set initialization checks
 # Quest-set initialization checks
 
 
-Branch: `fix/quest-set-initialization`, based on `878b639dd2257924feb01252679634e7bdca4259`.
-
 This change initializes a supported quest set when its first item is acquired and
 This change initializes a supported quest set when its first item is acquired and
 the saved set value is zero. The value comes from the first `(value, itemIndex)`
 the saved set value is zero. The value comes from the first `(value, itemIndex)`
 entry, not the set block's ordering field and not a fixed `100`. Existing nonzero
 entry, not the set block's ordering field and not a fixed `100`. Existing nonzero
@@ -59,16 +57,12 @@ separate-root extension recognizes 34 additional character-scoped first steps,
 for 216 total (50 account + 166 character). It includes the confirmed contract
 for 216 total (50 account + 166 character). It includes the confirmed contract
 for item 13138: character row 162, initial value -1583618456; its five later
 for item 13138: character row 162, initial value -1583618456; its five later
 members remain excluded. The optional content check reports the current total.
 members remain excluded. The optional content check reports the current total.
-This is **not** an
-in-game pass count or proof of every vendor's eligibility policy. Technical
-Knockout's gameplay regression has passed on the installed patch: the user
-confirmed the vendor behavior, and the post-test save contains the acquired
-item and initialized account value. Do not repeat that case without a relevant
-regression or behavior change. The user also confirmed Sight, Shoot, Repeat's
-quest-step acquisition behavior and persistence across a client restart after
-the separate-root extension was installed. That gameplay report is accepted;
-its post-test database was not independently inspected. These two completed
-cases do not establish completion/turn-in or text-acknowledgement support.
+These counts describe structural coverage, not gameplay passes or universal
+vendor eligibility. Gameplay checks passed for Technical Knockout acquisition
+and vendor availability (also checked in saved state), and Sight, Shoot, Repeat
+acquisition and persistence across restart (tester-reported). Neither establishes
+quest completion, turn-in or text-acknowledgement support. Manual repairs of
+pre-existing campaign saves are not fresh-acquisition tests for this patch.
 
 
 The supported shape is an objective-bearing pursuit (bucket 40), linked to a
 The supported shape is an objective-bearing pursuit (bucket 40), linked to a
 mode-1 set, with one unambiguous first-member identifier and one supported
 mode-1 set, with one unambiguous first-member identifier and one supported