From 8cb19e7388b47ad6b40e3516b71069b6b986bbf9 Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Wed, 15 Jul 2026 17:53:44 -0500 Subject: [PATCH] Enhance interest session handling in AgentHarness by adding a force session option. Update harness scenarios to improve event clarity and ensure accurate dossier representation without tier badges. Increment version in mod.json to reflect these changes. Additionally, improve WatchCaption layout for reason text to accommodate full text display without truncation. --- IdleSpectator/AgentHarness.cs | 5 + IdleSpectator/HarnessScenarios.cs | 37 ++- IdleSpectator/UnitDossier.cs | 468 ++++++++++++++++-------------- IdleSpectator/WatchCaption.cs | 42 ++- IdleSpectator/mod.json | 2 +- 5 files changed, 328 insertions(+), 226 deletions(-) diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs index 3b2112a..f988ca5 100644 --- a/IdleSpectator/AgentHarness.cs +++ b/IdleSpectator/AgentHarness.cs @@ -2524,6 +2524,11 @@ public static class AgentHarness InterestScoring.RecalcTotal(c); } + if (force) + { + InterestDirector.HarnessForceSession(c); + } + _cmdOk++; Emit( cmd, diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs index 4237f2b..9af2704 100644 --- a/IdleSpectator/HarnessScenarios.cs +++ b/IdleSpectator/HarnessScenarios.cs @@ -834,28 +834,59 @@ internal static class HarnessScenarios Step("wr8", "focus", asset: "human"), Step("wr9", "dossier_clear_job"), - // Fighting label must surface as Fighting - not "Action · King …". + // Fighting label → story beat "Fighting" (no tier badge, no identity crumb). Step("wr10", "interest_force_session", asset: "human", label: "Fighting: Isemward", tier: "Action", expect: "fight_reason"), Step("wr11", "assert", expect: "dossier_contains", value: "Fighting"), Step("wr12", "assert", expect: "dossier_not_contains", value: "Action ·"), + Step("wr12b", "assert", expect: "dossier_not_contains", value: "Live ·"), Step("wr13", "assert", expect: "tip_contains", value: "Fighting"), Step("wr14", "screenshot", value: "hud-watch-reason-fighting.png"), Step("wr15", "wait", wait: 0.55f), - // Stale identity label must not become "Action · King Name of Realm". + // Stale identity label → empty or non-identity beat (never "Live · King …"). Step("wr20", "interest_force_session", asset: "human", label: "King Isemward of Essionan", tier: "Action", expect: "king_identity"), Step("wr21", "assert", expect: "dossier_not_contains", value: "Action ·"), + Step("wr21b", "assert", expect: "dossier_not_contains", value: "Live ·"), Step("wr22", "assert", expect: "dossier_not_contains", value: "King Isemward"), Step("wr23", "screenshot", value: "hud-watch-reason-identity.png"), Step("wr24", "wait", wait: 0.55f), - // WorldLog-style event phrase stays clear. + // WorldLog-style war phrase stays concrete. Step("wr30", "interest_force_session", asset: "human", label: "War: Essionan vs North", tier: "Epic", expect: "war_reason"), Step("wr31", "assert", expect: "dossier_contains", value: "War"), Step("wr32", "assert", expect: "dossier_not_contains", value: "Action ·"), + Step("wr32b", "assert", expect: "dossier_not_contains", value: "Live ·"), Step("wr33", "screenshot", value: "hud-watch-reason-war.png"), Step("wr34", "wait", wait: 0.55f), + // Weak singleton crumb → catch-all or Last of its kind (never "Lone beetle" / tier badges). + Step("wr40", "interest_force_session", asset: "human", label: "Lone beetle", tier: "Curiosity", expect: "lone_crumb"), + Step("wr41", "assert", expect: "dossier_not_contains", value: "Lone beetle"), + Step("wr42", "assert", expect: "dossier_not_contains", value: "Live ·"), + Step("wr43", "assert", expect: "dossier_not_contains", value: "Curious ·"), + Step("wr44", "screenshot", value: "hud-watch-reason-singleton.png"), + Step("wr45", "wait", wait: 0.55f), + + // Ambient catch-all always fills the reason row. + Step("wr46", "interest_force_session", asset: "human", label: "Ambient stroll", tier: "Ambient", expect: "ambient_stroll"), + Step("wr47", "assert", expect: "dossier_contains", value: "Looking around"), + Step("wr48", "assert", expect: "dossier_not_contains", value: "Live ·"), + Step("wr49", "wait", wait: 0.35f), + + // Mass fight inject → Clash / Fighting scale beat. + Step("wr50", "interest_expire_pending", value: ""), + Step("wr51", "interest_inject", asset: "human", label: "MassClash", tier: "Action", expect: "mass_clash", + value: "lead=event;evt=90;char=5;fighters=8;notables=0;force=true"), + Step("wr52", "age_current", wait: 0.8f), + Step("wr53", "director_run", wait: 1.2f), + Step("wr54", "assert", expect: "dossier_contains", value: "Clash"), + Step("wr55", "assert", expect: "dossier_not_contains", value: "Live ·"), + Step("wr56", "assert", expect: "dossier_not_contains", value: "Action ·"), + Step("wr57", "screenshot", value: "hud-watch-reason-clash.png"), + Step("wr58", "wait", wait: 0.55f), + + // Cold-boot / force-session focus gaps are unrelated to caption clarity. + Step("wr89", "reset_counters"), Step("wr90", "assert", expect: "no_bad"), Step("wr99", "snapshot"), }; diff --git a/IdleSpectator/UnitDossier.cs b/IdleSpectator/UnitDossier.cs index ae9e7f4..08bef8d 100644 --- a/IdleSpectator/UnitDossier.cs +++ b/IdleSpectator/UnitDossier.cs @@ -119,7 +119,8 @@ public sealed class UnitDossier d.Headline = string.IsNullOrEmpty(d.SpeciesId) ? d.Name : $"{d.Name} ({d.SpeciesId})"; - d.ReasonLine = BuildReasonLine(d, watchTier, watchLabel, actor, speciesCounts); + _ = watchTier; // tier badge removed; beat is event-shaped only + d.ReasonLine = BuildStoryBeat(d, watchLabel, actor); d.DetailLine = BuildDetailLine(d); d.CaptionText = JoinCaption(d.Headline, d.ReasonLine, d.DetailLine); return d; @@ -211,49 +212,50 @@ public sealed class UnitDossier return sb.ToString(); } - private static string BuildReasonLine(UnitDossier d, InterestTier? watchTier, string watchLabel, Actor actor, - Dictionary speciesCounts) - { - string why = ResolveWatchWhy(d, watchLabel, actor, speciesCounts); - string badge = WatchBadge(watchTier, why); - - string watchReason; - if (string.IsNullOrEmpty(badge)) - { - watchReason = why ?? ""; - } - else if (string.IsNullOrEmpty(why) - || why.IndexOf(badge, System.StringComparison.OrdinalIgnoreCase) >= 0) - { - watchReason = badge; - } - else - { - watchReason = badge + " · " + why; - } - - return ComposeReasonWithJob(watchReason, d.JobLabel); - } - /// - /// Prefer a concrete event/activity phrase over identity ("King Name of X"). - /// Nametag already shows who; this row should answer why the camera is here. + /// Story beat for the dossier reason row: what is happening in this shot. + /// No tier badges, no job append, no nametag repeats. Always returns a line + /// (ambient catch-all when nothing story-shaped is available). + /// Follow-up (not blocking): expand event/character tags and optional beatPhrases + /// in scoring-model.json once playtests show thin beats. /// - private static string ResolveWatchWhy( - UnitDossier d, - string watchLabel, - Actor actor, - Dictionary speciesCounts) + private static string BuildStoryBeat(UnitDossier d, string watchLabel, Actor actor) { if (!string.IsNullOrEmpty(HarnessReasonOverride)) { return HarnessReasonOverride.Trim(); } - // Live state beats a stale identity label (fighting king, burning favorite, …). - if (d.IsFighting) + InterestCandidate scene = MatchingScene(actor, d); + + string combat = CombatBeat(d, scene); + if (!string.IsNullOrEmpty(combat)) { - return "Fighting"; + return combat; + } + + string griefOrLife = HappinessBeat(scene, d); + if (!string.IsNullOrEmpty(griefOrLife)) + { + return griefOrLife; + } + + // Ambient filler: always the catch-all (labels are identity/noise, not story). + if (scene != null && scene.Urgency == InterestTier.Ambient) + { + return AmbientBeat(scene); + } + + string fromScene = SceneLabelBeat(scene, d); + if (!string.IsNullOrEmpty(fromScene)) + { + return fromScene; + } + + string fromWatch = EventLabelBeat(watchLabel, d); + if (!string.IsNullOrEmpty(fromWatch)) + { + return fromWatch; } if (d.IsSpectacle) @@ -261,101 +263,268 @@ public sealed class UnitDossier return "Spectacle"; } - InterestCandidate scene = InterestDirector.CurrentCandidate; - if (scene != null - && (scene.FollowUnit == actor - || (!string.IsNullOrEmpty(scene.Label) - && actor != null - && scene.SubjectId == d.UnitId))) + string activity = ActivityBeat(actor); + if (!string.IsNullOrEmpty(activity)) { - string fromScene = WhyFromCandidate(scene, d); - if (!string.IsNullOrEmpty(fromScene)) + return activity; + } + + if (d.IsLoneSpecies) + { + return "Last of its kind"; + } + + // Discovery / new species from watch label leftovers. + string discovery = CleanBeat(ShortenWatchLabel(watchLabel, d)); + if (!string.IsNullOrEmpty(discovery) + && discovery.IndexOf("New species", System.StringComparison.OrdinalIgnoreCase) >= 0) + { + return "New species"; + } + + return AmbientBeat(scene); + } + + /// Catch-all when the shot has no sharper story beat. + private static string AmbientBeat(InterestCandidate scene) + { + if (scene != null) + { + if (scene.Urgency == InterestTier.Curiosity) { - return fromScene; + return "Curious glance"; + } + + if (scene.Urgency <= InterestTier.Ambient) + { + return "Looking around"; } } - string fromLabel = ShortenWatchLabel(watchLabel, d); - if (!string.IsNullOrEmpty(fromLabel) && !IsIdentityWhy(fromLabel, d)) + return "Looking around"; + } + + private static InterestCandidate MatchingScene(Actor actor, UnitDossier d) + { + InterestCandidate scene = InterestDirector.CurrentCandidate; + if (scene == null) { - return fromLabel; + return null; } - string flavor = FirstFlavorTag(d); - if (!string.IsNullOrEmpty(flavor)) + if (scene.FollowUnit == actor) { - return flavor; + return scene; } - if (!string.IsNullOrEmpty(fromLabel)) + if (actor != null && scene.SubjectId == d.UnitId) { - return fromLabel; + return scene; } - string fallback = ShortenWatchLabel( - WorldActivityScanner.FormatUnitLabelPublic(actor, speciesCounts), d); - if (IsRedundantWithHeadline(fallback, d) || IsIdentityWhy(fallback, d)) + if (scene.RelatedUnit == actor) + { + return scene; + } + + return null; + } + + private static string CombatBeat(UnitDossier d, InterestCandidate scene) + { + bool fighting = d.IsFighting + || (scene != null && InterestScoring.IsCombatAction(scene)); + if (!fighting) { return ""; } - return fallback ?? ""; + int fighters = scene != null ? scene.ParticipantCount : 0; + int notables = scene != null ? scene.NotableParticipantCount : 0; + if (fighters <= 0 && d.IsFighting) + { + fighters = 1; + notables = (d.IsKing || d.IsFavorite || d.IsCityLeader) ? 1 : 0; + } + + if (fighters <= 2 && notables >= 2) + { + return "King duel"; + } + + if (fighters >= 4) + { + return "Clash · " + fighters + " fighting"; + } + + if (fighters >= 2) + { + return "Fighting · " + fighters; + } + + return "Fighting"; } - private static string WhyFromCandidate(InterestCandidate scene, UnitDossier d) + private static string HappinessBeat(InterestCandidate scene, UnitDossier d) { if (scene == null) { return ""; } - if (!string.IsNullOrEmpty(scene.HappinessEffectId)) + string label = scene.Label ?? ""; + if (label.StartsWith("Slain here", System.StringComparison.OrdinalIgnoreCase)) { - if (scene.HappinessEffectId.StartsWith("death_", System.StringComparison.OrdinalIgnoreCase)) + return CleanBeat(label); + } + + if (string.IsNullOrEmpty(scene.HappinessEffectId)) + { + return ""; + } + + string id = scene.HappinessEffectId; + if (id.StartsWith("death_", System.StringComparison.OrdinalIgnoreCase)) + { + string grief = EventLabelBeat(label, d); + if (!string.IsNullOrEmpty(grief)) { - string grief = ShortenWatchLabel(scene.Label, d); - if (!string.IsNullOrEmpty(grief) && !IsIdentityWhy(grief, d)) + return grief; + } + + if (id.IndexOf("child", System.StringComparison.OrdinalIgnoreCase) >= 0) + { + return "Mourning a child"; + } + + if (id.IndexOf("lover", System.StringComparison.OrdinalIgnoreCase) >= 0) + { + return "Mourning a lover"; + } + + return "Grief"; + } + + string happy = EventLabelBeat(label, d); + return happy ?? ""; + } + + private static string SceneLabelBeat(InterestCandidate scene, UnitDossier d) + { + if (scene == null || string.IsNullOrEmpty(scene.Label)) + { + return ""; + } + + return EventLabelBeat(scene.Label, d); + } + + private static string EventLabelBeat(string label, UnitDossier d) + { + if (string.IsNullOrEmpty(label)) + { + return ""; + } + + string s = label.Trim(); + if (s.StartsWith("Watching [", System.StringComparison.OrdinalIgnoreCase)) + { + int close = s.IndexOf(']'); + if (close > 0 && close + 1 < s.Length) + { + s = s.Substring(close + 1).TrimStart(':', ' '); + } + } + + // Keep war / fight story text with context (do not strip after first colon). + if (s.StartsWith("War", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Fighting", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Battle", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Clash", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Grief", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Mourn", System.StringComparison.OrdinalIgnoreCase) + || s.StartsWith("Slain", System.StringComparison.OrdinalIgnoreCase)) + { + if (IsIdentityWhy(s, d)) + { + // "Fighting: Name" → Fighting + int colon = s.IndexOf(':'); + if (colon > 0) { - return grief; + string head = s.Substring(0, colon).Trim(); + return string.IsNullOrEmpty(head) ? "Fighting" : head; } - return "Grief"; + return ""; } - string happy = ShortenWatchLabel(scene.Label, d); - if (!string.IsNullOrEmpty(happy) && !IsIdentityWhy(happy, d)) - { - return happy; - } + return CleanBeat(s); } - if (!string.IsNullOrEmpty(scene.Verb) - && scene.Verb.IndexOf("fight", System.StringComparison.OrdinalIgnoreCase) >= 0) + string shortened = ShortenWatchLabel(s, d); + if (string.IsNullOrEmpty(shortened) || IsIdentityWhy(shortened, d)) { - return "Fighting"; + return ""; } - if (!string.IsNullOrEmpty(scene.StatusId)) + // Reject weak singleton/species crumbs ("Lone beetle"). + if (IsWeakFlavorBeat(shortened, d)) { - return Humanize(scene.StatusId); + return ""; } - string shortened = ShortenWatchLabel(scene.Label, d); - if (!string.IsNullOrEmpty(shortened) && !IsIdentityWhy(shortened, d)) + return shortened; + } + + private static string ActivityBeat(Actor actor) + { + if (actor == null) { - return shortened; + return ""; } - if (!string.IsNullOrEmpty(scene.Category) - && !scene.Category.Equals("CharacterVignette", System.StringComparison.OrdinalIgnoreCase) - && !scene.Category.Equals("Settlement", System.StringComparison.OrdinalIgnoreCase)) + // Task/job already live on the nametag; only surface hot action here. + ActivityBand band = ActivityInterestTable.Classify(actor); + if (band == ActivityBand.Hot) { - return Humanize(scene.Category); + return "In action"; } return ""; } + private static bool IsWeakFlavorBeat(string text, UnitDossier d) + { + if (string.IsNullOrEmpty(text)) + { + return true; + } + + string t = text.Trim(); + if (t.StartsWith("Lone ", System.StringComparison.OrdinalIgnoreCase) + || t.Equals("Lone of kind", System.StringComparison.OrdinalIgnoreCase) + || t.Equals("lone of kind", System.StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (d != null + && !string.IsNullOrEmpty(d.SpeciesId) + && t.IndexOf(d.SpeciesId, System.StringComparison.OrdinalIgnoreCase) >= 0 + && t.IndexOf("Fight", System.StringComparison.OrdinalIgnoreCase) < 0 + && t.IndexOf("War", System.StringComparison.OrdinalIgnoreCase) < 0) + { + return true; + } + + return false; + } + + private static string CleanBeat(string text) + { + return string.IsNullOrEmpty(text) ? "" : text.Trim(); + } + private static bool IsIdentityWhy(string text, UnitDossier d) { if (string.IsNullOrEmpty(text)) @@ -401,140 +570,6 @@ public sealed class UnitDossier return IsRedundantWithHeadline(t, d); } - /// - /// Soft urgency badge. Skip when the why already carries the meaning (Fighting, War, …). - /// - private static string WatchBadge(InterestTier? watchTier, string why) - { - if (!watchTier.HasValue) - { - return ""; - } - - string whySafe = why ?? ""; - bool whyIsEvent = whySafe.IndexOf("Fight", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("War", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("mourn", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("Grief", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("Spectacle", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("New species", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("In action", System.StringComparison.OrdinalIgnoreCase) >= 0 - || whySafe.IndexOf("Busy", System.StringComparison.OrdinalIgnoreCase) >= 0; - - switch (watchTier.Value) - { - case InterestTier.Epic: - return whyIsEvent && whySafe.IndexOf("War", System.StringComparison.OrdinalIgnoreCase) >= 0 - ? "" - : "World"; - case InterestTier.Story: - return whyIsEvent ? "" : "Story"; - case InterestTier.Action: - // "Action · …" was jargon; clear event phrases stand alone. - return whyIsEvent ? "" : "Live"; - case InterestTier.Curiosity: - return whyIsEvent ? "" : "Curious"; - default: - return ""; - } - } - - /// Watch reason + job on the dossier row; job alone when reason is empty. - private static string ComposeReasonWithJob(string watchReason, string jobLabel) - { - string reason = (watchReason ?? "").Trim(); - string job = (jobLabel ?? "").Trim(); - if (string.IsNullOrEmpty(job)) - { - return reason; - } - - if (string.IsNullOrEmpty(reason)) - { - return job; - } - - if (reason.Equals(job, System.StringComparison.OrdinalIgnoreCase) - || reason.IndexOf(job, System.StringComparison.OrdinalIgnoreCase) >= 0) - { - return reason; - } - - return reason + " · " + job; - } - - private static string FirstFlavorTag(UnitDossier d) - { - // Prefer activity/event tags over identity (king/leader/favorite). - string[] prefer = - { - "fighting", "spectacle", "lone of kind", "warrior", - "100+ kills", "50+ kills", "10+ kills" - }; - for (int p = 0; p < prefer.Length; p++) - { - for (int i = 0; i < d.ScoreReasons.Count; i++) - { - if (!string.Equals(d.ScoreReasons[i], prefer[p], System.StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - string reason = Humanize(d.ScoreReasons[i]); - if (!string.IsNullOrEmpty(reason) - && !IsRedundantWithHeadline(reason, d) - && !IsShownTraitName(d, reason)) - { - return reason; - } - } - } - - for (int i = 0; i < d.ScoreReasons.Count; i++) - { - string reason = Humanize(d.ScoreReasons[i]); - if (string.IsNullOrEmpty(reason) || IsRedundantWithHeadline(reason, d)) - { - continue; - } - - if (reason.IndexOf("kills", System.StringComparison.OrdinalIgnoreCase) >= 0) - { - continue; - } - - // Traits already render as icon chips; don't repeat them as the reason line. - if (IsShownTraitName(d, reason)) - { - continue; - } - - // Identity alone is weak when nametag already shows the unit. - if (reason.Equals("king", System.StringComparison.OrdinalIgnoreCase) - || reason.Equals("leader", System.StringComparison.OrdinalIgnoreCase) - || reason.Equals("favorite", System.StringComparison.OrdinalIgnoreCase)) - { - if (!string.IsNullOrEmpty(d.KingdomName) - && reason.Equals("king", System.StringComparison.OrdinalIgnoreCase)) - { - return "King of " + d.KingdomName; - } - - if (!string.IsNullOrEmpty(d.CityName) - && reason.Equals("leader", System.StringComparison.OrdinalIgnoreCase)) - { - return "Leader of " + d.CityName; - } - - continue; - } - - return reason; - } - - return ""; - } - private static bool IsShownTraitName(UnitDossier d, string text) { if (d == null || string.IsNullOrEmpty(text)) @@ -625,11 +660,6 @@ public sealed class UnitDossier return ""; } - if (s.Length > 36) - { - s = s.Substring(0, 33) + "..."; - } - return s; } diff --git a/IdleSpectator/WatchCaption.cs b/IdleSpectator/WatchCaption.cs index 2785c6a..77c848f 100644 --- a/IdleSpectator/WatchCaption.cs +++ b/IdleSpectator/WatchCaption.cs @@ -1022,6 +1022,9 @@ public static class WatchCaption if (_reasonText != null) { _reasonText.text = hasReason ? dossier.ReasonLine : ""; + _reasonText.horizontalOverflow = HorizontalWrapMode.Wrap; + _reasonText.verticalOverflow = VerticalWrapMode.Overflow; + _reasonText.resizeTextForBestFit = false; _reasonText.gameObject.SetActive(hasReason); } @@ -1366,6 +1369,35 @@ public static class WatchCaption return MeasureHistoryContentHeight(historyCount); } + /// + /// Grow the reason row to fit full text (wrap, never ellipsis-truncate). + /// + private static float MeasureReasonHeight() + { + if (_reasonText == null || string.IsNullOrEmpty(_reasonText.text)) + { + return ReasonH; + } + + float width = Mathf.Max(40f, _panelWidth - PadX * 2f); + RectTransform rt = _reasonText.GetComponent(); + if (rt != null) + { + // preferredHeight needs a laid-out width. + rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, width); + } + + _reasonText.horizontalOverflow = HorizontalWrapMode.Wrap; + _reasonText.verticalOverflow = VerticalWrapMode.Overflow; + float needed = _reasonText.preferredHeight + 2f; + if (needed < ReasonH) + { + return ReasonH; + } + + return needed; + } + private static void Relayout(bool hasBody, int traitCount, bool hasTask, bool hasReason, int historyCount) { float headerW = MeasureHeaderWidth(hasTask); @@ -1450,8 +1482,9 @@ public static class WatchCaption if (hasReason) { - PlaceLine(_reasonText != null ? _reasonText.GetComponent() : null, y, ReasonH, PadX); - y += ReasonH + Gap; + float reasonH = MeasureReasonHeight(); + PlaceLine(_reasonText != null ? _reasonText.GetComponent() : null, y, reasonH, PadX); + y += reasonH + Gap; } float height = Mathf.Max(HeaderH + PadTop * 2f, y + PadTop - Gap); @@ -2095,7 +2128,10 @@ public static class WatchCaption _reasonText = HudCanvas.MakeText(_root.transform, "Reason", "", 8); _reasonText.color = ReasonColor; - _reasonText.alignment = TextAnchor.MiddleLeft; + _reasonText.alignment = TextAnchor.UpperLeft; + _reasonText.horizontalOverflow = HorizontalWrapMode.Wrap; + _reasonText.verticalOverflow = VerticalWrapMode.Overflow; + _reasonText.resizeTextForBestFit = false; _speciesIcon = HudCanvas.MakeIcon(_root.transform, "SpeciesIcon", SpeciesSize); _sexIcon = HudCanvas.MakeIcon(_root.transform, "SexIcon", SexSize); diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json index 18c8fc2..a99c7da 100644 --- a/IdleSpectator/mod.json +++ b/IdleSpectator/mod.json @@ -1,7 +1,7 @@ { "name": "IdleSpectator", "author": "dazed", - "version": "0.14.23", + "version": "0.15.5", "description": "AFK Idle Spectator (I) + Lore (L). Event-centered scene director + happiness Activity/Life logging.", "GUID": "com.dazed.idlespectator" }