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

Keep cue text when the authored window disagrees

A cue whose only content tree has a different length than its authored
window now keeps its lines and text rows. The cue row records the
mismatch with a new flag, and the generator logs it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
stan 4 дней назад
Родитель
Сommit
32e0892856

+ 12 - 2
Sunrise/src/client/content/activity/activity_sdk_dialogue_list.cpp

@@ -246,6 +246,7 @@ bool read(std::span<const std::byte> list, Snapshot& output) {
         std::size_t matches = 0;
         bool malformed = false;
         std::vector<Line> matched{};
+        std::vector<Line> unmatched{};
         for (auto tree = range.first; tree != range.second; ++tree) {
             Walk walk{};
             walk.definitionHash = cue.definitionHash;
@@ -257,6 +258,8 @@ bool read(std::span<const std::byte> list, Snapshot& output) {
             if (std::fabs(seconds - cue.seconds) <= kSecondsTolerance) {
                 ++matches;
                 matched = std::move(walk.lines);
+            } else {
+                unmatched = std::move(walk.lines);
             }
         }
         const std::size_t candidates = static_cast<std::size_t>(range.second - range.first);
@@ -264,10 +267,17 @@ bool read(std::span<const std::byte> list, Snapshot& output) {
             cue.status = CueStatus::missing;
         } else if (matches == 1) {
             cue.status = CueStatus::resolved;
+        } else if (malformed) {
+            cue.status = CueStatus::malformed;
+        } else if (candidates == 1) {
+            // A sole tree is the cue's content even when its length disagrees; keep its lines.
+            cue.status = CueStatus::mismatched;
+            matched = std::move(unmatched);
         } else {
-            cue.status = candidates > 1 && !malformed ? CueStatus::ambiguous : CueStatus::malformed;
+            cue.status = CueStatus::ambiguous;
         }
-        if (matches != 1 || matched.size() > (std::numeric_limits<std::uint32_t>::max)()
+        if ((cue.status != CueStatus::resolved && cue.status != CueStatus::mismatched)
+            || matched.size() > (std::numeric_limits<std::uint32_t>::max)()
             || output.lines.size() > (std::numeric_limits<std::uint32_t>::max)() - matched.size()) {
             continue;
         }

+ 4 - 2
Sunrise/src/client/content/activity/activity_sdk_dialogue_list.h

@@ -29,11 +29,13 @@ enum class CueStatus : std::uint8_t {
     ambiguous,
     /** No tree carries the definition hash. */
     missing,
-    /** The matched tree is malformed or its duration differs from the authored one. */
+    /** A tree carrying the definition hash is malformed. */
     malformed,
+    /** The one tree carrying the definition hash differs from the authored duration. */
+    mismatched,
 };
 
-/** One cue in list order. Lines are present only for resolved cues. */
+/** One cue in list order. Lines are present only for resolved and mismatched cues. */
 struct Cue final {
     std::uint32_t definitionHash{};
     /** Authored total: a sequence adds its children, a selection lasts its longest branch. */

+ 32 - 14
Sunrise/src/client/content/activity/activity_sdk_native_pack_text.cpp

@@ -186,6 +186,23 @@ void log_dialogue_list(const squads::DescriptorFact& descriptor,
     }
 }
 
+/** @return The log result name of one cue status. */
+const char* cue_status_name(dialogue_list::CueStatus status) noexcept {
+    switch (status) {
+    case dialogue_list::CueStatus::resolved:
+        return "cue_resolved";
+    case dialogue_list::CueStatus::ambiguous:
+        return "cue_ambiguous";
+    case dialogue_list::CueStatus::missing:
+        return "cue_missing";
+    case dialogue_list::CueStatus::malformed:
+        return "cue_malformed";
+    case dialogue_list::CueStatus::mismatched:
+        return "cue_window_mismatch";
+    }
+    return "cue_unknown";
+}
+
 } // namespace
 
 /**
@@ -402,23 +419,24 @@ bool attach_authored_text(const topology_inventory::Snapshot& topology,
                     cueRow.listTag = resourceTag;
                     cueRow.definitionHash = row.definitionHash;
                     cueRow.authoredWindowSeconds = row.seconds;
-                    if (row.status == dialogue_list::CueStatus::resolved) {
+                    const bool mismatched = row.status == dialogue_list::CueStatus::mismatched;
+                    const bool hasLines =
+                        mismatched || row.status == dialogue_list::CueStatus::resolved;
+                    if (hasLines) {
                         cueRow.lineCount = row.lineCount;
-                        cueRow.flags = format::kDialogueCueLinesExact;
+                        cueRow.flags = format::kDialogueCueLinesExact
+                                       | (mismatched ? format::kDialogueCueWindowMismatch : 0U);
                     }
                     output.dialogueCues.push_back(cueRow);
-                    if (row.status == dialogue_list::CueStatus::ambiguous
-                        || row.status == dialogue_list::CueStatus::missing
-                        || row.status == dialogue_list::CueStatus::malformed) {
-                        log_dialogue_list(
-                            descriptor,
-                            resourceTag,
-                            row.status == dialogue_list::CueStatus::ambiguous ? "cue_ambiguous"
-                            : row.status == dialogue_list::CueStatus::missing ? "cue_missing"
-                                                                              : "cue_malformed",
-                            row.definitionHash,
-                            static_cast<std::uint32_t>(cue),
-                            core::log::Level::warn);
+                    if (row.status != dialogue_list::CueStatus::resolved) {
+                        log_dialogue_list(descriptor,
+                                          resourceTag,
+                                          cue_status_name(row.status),
+                                          row.definitionHash,
+                                          static_cast<std::uint32_t>(cue),
+                                          core::log::Level::warn);
+                    }
+                    if (!hasLines) {
                         continue;
                     }
                     // Both takes of a line normally reference the same text; each is still its

+ 5 - 2
Sunrise/src/state/activity_sdk/format.h

@@ -139,9 +139,12 @@ inline constexpr std::uint32_t kDialogueGroupArrayClass = 0x80808D19U;
 inline constexpr std::uint32_t kDialogueMaximumCueCount = 128U;
 /** Every dialogue line holds two takes of the same text. */
 inline constexpr std::uint32_t kDialogueTakeCount = 2U;
-/** A cue row's lines were read from the one content tree matching its authored duration. */
+/** A cue row's lines were read from one content tree carrying its definition hash. */
 inline constexpr std::uint32_t kDialogueCueLinesExact = 0x1U;
-inline constexpr std::uint32_t kDialogueCueFlagMask = kDialogueCueLinesExact;
+/** That tree's length differs from the authored window; its lines are still kept. */
+inline constexpr std::uint32_t kDialogueCueWindowMismatch = 0x2U;
+inline constexpr std::uint32_t kDialogueCueFlagMask =
+    kDialogueCueLinesExact | kDialogueCueWindowMismatch;
 /** Inspect exposure is read-only and may be rendered by diagnostics. */
 inline constexpr std::uint32_t kInspectExposure = 0x1U;
 /** Panel-test exposure marks a candidate that still passes runtime gates. */