From 78763213913d8fadefb3cf958fb0d3ec5f71cff5 Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Tue, 14 Jul 2026 22:03:50 -0500 Subject: [PATCH] Enhance AgentHarness and Chronicle functionality to support detailed jump tracking. Added LastJumpDetail and LastJumpPosition properties for improved state management during jumps. Updated HUD interactions to reflect jump outcomes and integrated new test scenarios for world event navigation. --- IdleSpectator/AgentHarness.cs | 54 ++++++++++++++++++++++- IdleSpectator/Chronicle.cs | 71 +++++++++++++++++++++++++++++-- IdleSpectator/ChronicleHud.cs | 13 +++++- IdleSpectator/HarnessScenarios.cs | 9 ++++ IdleSpectator/mod.json | 4 +- 5 files changed, 143 insertions(+), 8 deletions(-) diff --git a/IdleSpectator/AgentHarness.cs b/IdleSpectator/AgentHarness.cs index 25b52b0..9c607df 100644 --- a/IdleSpectator/AgentHarness.cs +++ b/IdleSpectator/AgentHarness.cs @@ -702,7 +702,50 @@ public static class AgentHarness _cmdFail++; } - Emit(cmd, ok, detail: $"jumped={ok} focus={MoveCamera.hasFocusUnit()}"); + Emit( + cmd, + ok, + detail: + $"jumped={ok} detail={Chronicle.LastJumpDetail} focus={MoveCamera.hasFocusUnit()} pos={Chronicle.LastJumpPosition}"); + break; + } + + case "lore_jump_world": + { + Chronicle.ShowHud(); + ChronicleHud.SetTab(ChronicleHud.LoreTab.World); + ChronicleEntry target = null; + IReadOnlyList snap = Chronicle.SnapshotMemory(); + for (int i = snap.Count - 1; i >= 0; i--) + { + ChronicleEntry e = snap[i]; + if (e == null || e.Kind == ChronicleKind.AgeChapter) + { + continue; + } + + if (e.Kind == ChronicleKind.World || e.IsLegend) + { + target = e; + break; + } + } + + bool ok = target != null && Chronicle.JumpTo(target); + if (ok) + { + _cmdOk++; + } + else + { + _cmdFail++; + } + + Emit( + cmd, + ok, + detail: + $"jumped={ok} detail={Chronicle.LastJumpDetail} focus={MoveCamera.hasFocusUnit()} line={(target != null ? target.Line : "")}"); break; } @@ -1700,6 +1743,15 @@ public static class AgentHarness $"fallenFocus={Chronicle.LastFallenFocusDetail} hasFocus={MoveCamera.hasFocusUnit()} pos={Chronicle.LastFallenFocusPosition}"; break; } + case "world_jump_place": + { + pass = Chronicle.LastJumpDetail == "place" + && !MoveCamera.hasFocusUnit() + && Chronicle.LastJumpPosition.sqrMagnitude > 0.0001f; + detail = + $"jump={Chronicle.LastJumpDetail} hasFocus={MoveCamera.hasFocusUnit()} pos={Chronicle.LastJumpPosition}"; + break; + } case "fallen_killer_camera": { Actor focus = MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null; diff --git a/IdleSpectator/Chronicle.cs b/IdleSpectator/Chronicle.cs index aa65a51..b3dd8c0 100644 --- a/IdleSpectator/Chronicle.cs +++ b/IdleSpectator/Chronicle.cs @@ -449,6 +449,12 @@ public static class Chronicle ChronicleHud.SetVisible(false); } + /// Harness: how the last JumpTo resolved (unit:id | place | none). + public static string LastJumpDetail { get; private set; } = ""; + + /// Harness: world position used by the last place-style JumpTo. + public static Vector3 LastJumpPosition { get; private set; } = Vector3.zero; + public static bool JumpToLatest() { // Prefer a Memory landmark when it can resolve a unit/position; else focus history. @@ -463,15 +469,37 @@ public static class Chronicle public static bool JumpTo(ChronicleEntry entry) { + LastJumpDetail = "none"; + LastJumpPosition = Vector3.zero; if (entry == null || entry.Kind == ChronicleKind.AgeChapter) { return false; } + // World Memory landmarks: always go to the recorded place when we have one, + // even if the related unit is gone or has moved on. + bool memoryPlaceFirst = entry.Kind == ChronicleKind.World || entry.IsLegend; + if (memoryPlaceFirst && EntryHasWorldPosition(entry)) + { + CameraDirector.ClearFollow(); + CameraDirector.PanTo(entry.Position); + LastJumpDetail = "place"; + LastJumpPosition = entry.Position; + LogService.LogInfo( + $"[IdleSpectator][MEMORY] jump place pos={entry.Position} line={entry.DisplayLine}"); + return true; + } + Actor unit = FindUnitById(entry.SubjectId); if (unit != null && unit.isAlive()) { - return FocusSubject(unit); + bool ok = FocusSubject(unit); + if (ok) + { + LastJumpDetail = "unit:" + entry.SubjectId; + } + + return ok; } if (entry.OtherId != 0) @@ -479,13 +507,24 @@ public static class Chronicle Actor other = FindUnitById(entry.OtherId); if (other != null && other.isAlive()) { - return FocusSubject(other); + bool ok = FocusSubject(other); + if (ok) + { + LastJumpDetail = "unit:" + entry.OtherId; + } + + return ok; } } - if (entry.Position != Vector3.zero) + if (EntryHasWorldPosition(entry)) { + CameraDirector.ClearFollow(); CameraDirector.PanTo(entry.Position); + LastJumpDetail = "place"; + LastJumpPosition = entry.Position; + LogService.LogInfo( + $"[IdleSpectator][MEMORY] jump place pos={entry.Position} line={entry.DisplayLine}"); return true; } @@ -1180,13 +1219,37 @@ public static class Chronicle return false; } + Vector3 pos = Vector3.zero; + if (MoveCamera.hasFocusUnit() && MoveCamera._focus_unit != null) + { + try + { + pos = MoveCamera._focus_unit.current_position; + } + catch + { + pos = Vector3.zero; + } + } + + if (pos.sqrMagnitude < 0.0001f && MoveCamera.instance != null) + { + pos = MoveCamera.instance.transform.position; + pos.z = 0f; + } + + if (pos.sqrMagnitude < 0.0001f) + { + pos = new Vector3(48f, 48f, 0f); + } + AppendLandmark( ChronicleKind.World, null, null, label, LoreProse.Rewrite(ChronicleKind.World, label), - Vector3.zero, + pos, "harness", isLegend: false); return true; diff --git a/IdleSpectator/ChronicleHud.cs b/IdleSpectator/ChronicleHud.cs index 9aa50eb..77d51de 100644 --- a/IdleSpectator/ChronicleHud.cs +++ b/IdleSpectator/ChronicleHud.cs @@ -1549,8 +1549,19 @@ public static class ChronicleHud ChronicleEntry captured = entry; button.onClick.AddListener(new UnityAction(() => { + if (SpectatorMode.Active) + { + SpectatorMode.SetActive(false, quiet: true); + WatchCaption.PinWhilePaused(); + WatchCaption.ShowStatusBanner( + captured.Kind == ChronicleKind.World || captured.IsLegend + ? "Paused (viewing world)" + : "Paused (viewing landmark)"); + } + bool ok = Chronicle.JumpTo(captured); - LogService.LogInfo($"[IdleSpectator][MEMORY] jump ok={ok} line={captured.DisplayLine}"); + LogService.LogInfo( + $"[IdleSpectator][MEMORY] jump ok={ok} detail={Chronicle.LastJumpDetail} line={captured.DisplayLine}"); })); return go; diff --git a/IdleSpectator/HarnessScenarios.cs b/IdleSpectator/HarnessScenarios.cs index 3c3d60f..02786c3 100644 --- a/IdleSpectator/HarnessScenarios.cs +++ b/IdleSpectator/HarnessScenarios.cs @@ -447,6 +447,15 @@ internal static class HarnessScenarios Step("ch17c", "assert", expect: "world_memory_contains", value: "harness_alpha"), Step("ch17d", "assert", expect: "chronicle_latest_contains", value: "Burned"), Step("ch17e", "assert", expect: "world_memory_age"), + // Selecting a world event pans to its recorded place (even with no unit there). + Step("ch17j", "lore_jump_world"), + Step("ch17k", "wait", wait: 0.2f), + Step("ch17l", "assert", expect: "world_jump_place"), + Step("ch17m", "assert", expect: "focus", value: "false"), + // Restore a living focus for the rest of the scenario. + Step("ch17n", "focus", asset: "auto"), + Step("ch17o", "wait", wait: 0.2f), + Step("ch17p", "assert", expect: "focus", value: "true"), // World Memory panel + dossier controls Step("ch18", "chronicle_open"), diff --git a/IdleSpectator/mod.json b/IdleSpectator/mod.json index 77bb180..efce34e 100644 --- a/IdleSpectator/mod.json +++ b/IdleSpectator/mod.json @@ -1,7 +1,7 @@ { "name": "IdleSpectator", "author": "dazed", - "version": "0.12.14", - "description": "AFK Idle Spectator (I) + Lore (L). Tabs: World / Living / Fallen (newest first).", + "version": "0.12.16", + "description": "AFK Idle Spectator (I) + Lore (L). World events pan to location; Living/Fallen tabs.", "GUID": "com.dazed.idlespectator" }