From a0d1b9a68827e6cf1e1e83d69f63d8a8e81560f8 Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Wed, 22 Jul 2026 19:57:12 -0500 Subject: [PATCH] fix(saga): restore cast relationship labels - Give each Cast entry enough space to render muted relationship subtext - Preserve fixed Saga geometry with the slightly taller Cast rows - Add populated-Cast regression coverage for visible relationship labels --- IdleSpectator/AgentHarness.cs | 8 ++++++++ IdleSpectator/HarnessScenarios.cs | 5 +++++ IdleSpectator/Story/LifeSagaPanel.cs | 23 ++++++++++++++++------- docs/saga-ux-redesign-plan.md | 4 ++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs index cdb0eda..64919e9 100644 --- a/IdleSpectator/AgentHarness.cs +++ b/IdleSpectator/AgentHarness.cs @@ -9590,6 +9590,14 @@ public static class AgentHarness $"panel={panel} ownsAvatar={ownsAvatar} avatarVisible={avatarVisible} live={DossierAvatar.ShowingLiveAvatar} dbg='{dbg}' pass={pass}"; break; } + case "saga_panel_cast_relations": + { + int want = ParseInt(cmd.value, 1); + int have = LifeSagaPanel.LastVisibleRelationCount; + pass = LifeSagaPanel.Visible && have >= want; + detail = $"relationLabels={have} wantMin={want} visible={LifeSagaPanel.Visible}"; + break; + } case "caption_hover_keeps_beat": { bool hover = LifeSagaViewController.IsHoverPreview; diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs index 156b007..b614830 100644 --- a/IdleSpectator/HarnessScenarios.cs +++ b/IdleSpectator/HarnessScenarios.cs @@ -3628,8 +3628,12 @@ internal static class HarnessScenarios Step("sad6", "spectator", value: "on"), Step("sad7", "interest_saga_clear"), Step("sad8", "pick_unit", asset: "human"), + Step("sad8a", "happiness_remember_partner"), + Step("sad8b", "pick_unit", asset: "human", value: "other"), Step("sad9", "focus", asset: "human"), Step("sad10", "saga_force_admit_focus"), + Step("sad10a", "saga_memory_record_focus", asset: "War", value: "opposing commander", + expect: "harness_cast_relation"), Step("sad11", "assert", expect: "saga_tab_selected", value: "dossier"), // saga_select_tab saga → hover preview of watched MC (compat). Step("sad11b", "caption_capture_beat"), @@ -3639,6 +3643,7 @@ internal static class HarnessScenarios Step("sad14b", "assert", expect: "saga_panel_fixed_size"), Step("sad14c", "assert", expect: "caption_hover_keeps_beat"), Step("sad14d", "assert", expect: "saga_panel_cast_first"), + Step("sad14e", "assert", expect: "saga_panel_cast_relations", value: "1"), Step("sad15", "screenshot", value: "saga-hover-depth"), Step("sad15b", "saga_stamp_chapter_focus", asset: "Love", value: "Bound with a lasting partner"), Step("sad15c", "saga_memory_record_focus", asset: "Founding", value: "kingdom"), diff --git a/IdleSpectator/Story/LifeSagaPanel.cs b/IdleSpectator/Story/LifeSagaPanel.cs index a8483b5..b2673f6 100644 --- a/IdleSpectator/Story/LifeSagaPanel.cs +++ b/IdleSpectator/Story/LifeSagaPanel.cs @@ -20,7 +20,7 @@ public static class LifeSagaPanel /// Full inner hover width; Cast starts beside the portrait, Legacy spans below it. public const float BodyWidthFixed = 232f; /// Fixed hover depth; never derived from Cast or Legacy content. - public const float BodyHeightFixed = 74f; + public const float BodyHeightFixed = 82f; public const float BodyHeightMax = BodyHeightFixed; public const float BodyWidthMin = BodyWidthFixed; @@ -37,12 +37,12 @@ public static class LifeSagaPanel private const float Pad = 3f; private const float CastFace = 14f; private const float CastSlotW = 59f; - private const float CastSlotH = 15f; + private const float CastSlotH = 18f; private const float SecLabelH = 9f; private const float LineH = 9f; private const float PortraitReserveW = 44f; private const int BodyFont = 7; - private const int BuildVersion = 14; + private const int BuildVersion = 15; private static GameObject _root; private static RectTransform _rootRt; @@ -74,6 +74,8 @@ public static class LifeSagaPanel /// Saga hover reuses the dossier avatar for the hovered character. public static bool OwnsAvatar => true; public static string LastLayoutDebug { get; private set; } = ""; + /// Harness/UI diagnostic: rendered Cast relationship sublabels. + public static int LastVisibleRelationCount { get; private set; } public static void EnsureBuilt(Transform parent) { @@ -128,6 +130,7 @@ public static class LifeSagaPanel _speciesId = ""; _fingerprint = ""; LegacyParts.Clear(); + LastVisibleRelationCount = 0; _laidOutHeight = BodyHeightFixed; if (_root != null) { @@ -275,11 +278,17 @@ public static class LifeSagaPanel } int castN = 0; + LastVisibleRelationCount = 0; for (int i = 0; i < CastSlots.Length; i++) { if (CastSlots[i].Root.activeSelf) { castN++; + if (CastSlots[i].Relation != null + && !string.IsNullOrEmpty(CastSlots[i].Relation.text)) + { + LastVisibleRelationCount++; + } } } @@ -313,7 +322,7 @@ public static class LifeSagaPanel } // Reserve both Cast rows even when sparse so Legacy never moves between characters. - y = 44f; + y = 52f; float legacyBudget = Mathf.Max(LineH, yMax - y - SecLabelH); int omittedLegacy = 0; string fittedLegacy = FitLegacyToHeight(fullW, legacyBudget, out omittedLegacy); @@ -406,10 +415,10 @@ public static class LifeSagaPanel Text relation = MakeLabel(root.transform, "Rel", BodyFont, HudTheme.Muted, FontStyle.Normal); RectTransform nameRt = name.rectTransform; nameRt.anchoredPosition = new Vector2(CastFace + 2f, 0f); - nameRt.sizeDelta = new Vector2(CastSlotW - CastFace - 2f, 8f); + nameRt.sizeDelta = new Vector2(CastSlotW - CastFace - 2f, 9f); RectTransform relRt = relation.rectTransform; - relRt.anchoredPosition = new Vector2(CastFace + 2f, -8f); - relRt.sizeDelta = new Vector2(CastSlotW - CastFace - 2f, 7f); + relRt.anchoredPosition = new Vector2(CastFace + 2f, -9f); + relRt.sizeDelta = new Vector2(CastSlotW - CastFace - 2f, 9f); root.SetActive(false); return new CastSlot diff --git a/docs/saga-ux-redesign-plan.md b/docs/saga-ux-redesign-plan.md index c7a5735..bbb695f 100644 --- a/docs/saga-ux-redesign-plan.md +++ b/docs/saga-ux-redesign-plan.md @@ -39,12 +39,12 @@ Use one fixed logical envelope for every Saga state: | Region | Size | Contract | |---|---:|---| -| Whole surface | `240 × 117` | Never content-sized | +| Whole surface | `240 × 125` | Never content-sized | | Glyph rail | `232 × 18` | Fixed position and hit targets | | Gap | `232 × 2` | Reserved | | Nametag | `232 × 15` | One line; fixed controls at right | | Gap | `232 × 1` | Reserved | -| Body | `232 × 74` | Portrait, Cast, and Legacy stay inside | +| Body | `232 × 82` | Portrait, Cast, and Legacy stay inside | | Outer padding | `4` horizontal, `3` vertical | Constant | The body must never report a content-derived preferred height. Empty and full characters use