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