Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Assets/Tests/Editor/PausePointCaptureModeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ private static UloopCapturedVariable CreateVariable(string name, string value)
value,
string.Empty,
string.Empty,
0);
0,
truncated: false);
}

private sealed class FakePausePointPauseController : IUloopPausePointPauseController
Expand Down
7 changes: 6 additions & 1 deletion Assets/Tests/Editor/PausePointStatusResponseContractTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void PausePointStatusResponse_WhenSerialized_MatchesSharedContractFieldSh
JObject expected = ReadSharedContractFieldShape();
PausePointStatusResponse response = new()
{
Success = true,
Id = "Assets/Scripts/Enemy.cs:42",
Status = "Hit",
IsEnabled = true,
Expand All @@ -33,6 +34,7 @@ public void PausePointStatusResponse_WhenSerialized_MatchesSharedContractFieldSh
TimeoutSeconds = 30,
Mode = "continuous",
MaxHistory = 20,
MaxPreviewElements = 15,
CapturedVariableHistory = new List<PausePointStatusCapturedHistoryFrame>
{
new()
Expand Down Expand Up @@ -72,10 +74,13 @@ public void PausePointStatusResponse_WhenSerialized_MatchesSharedContractFieldSh
Value = "Enemy",
UnityObjectKind = "SceneObject",
UnityObjectPath = "MainScene:/Root/Enemy",
UnityObjectInstanceId = -1234
UnityObjectInstanceId = -1234,
Truncated = false
}
},
CapturedVariablesTruncated = true,
TruncatedVariableNames = new[] { "extraField" },
TruncatedVariableCount = 1,
ClearedReason = "",
StatusBeforeClear = "",
LateHitDiscardedAfterClear = false
Expand Down
34 changes: 24 additions & 10 deletions Assets/Tests/Editor/PausePointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ public void PausePointStatusBridge_WhenPausePointHitWithCapturedVariables_Return
UloopPausePointRegistry.Enable("jump", 30);
UloopCapturedVariable[] capturedVariables =
{
new("speed", UloopCapturedVariableScope.Local, "System.Int32", "5", string.Empty, string.Empty, 0)
new("speed", UloopCapturedVariableScope.Local, "System.Int32", "5", string.Empty, string.Empty, 0, false)
};
UloopPausePointRegistry.HitWithCapturedVariables("jump", capturedVariables, true);
JObject parameters = new() { ["id"] = "jump" };
Expand Down Expand Up @@ -1004,7 +1004,7 @@ public void HitWithCapturedVariables_WhenPausePointIsEnabled_StoresCapturedVaria
UloopPausePointRegistry.Enable("jump", 30);
UloopCapturedVariable[] capturedVariables =
{
new("speed", UloopCapturedVariableScope.Local, "System.Int32", "5", string.Empty, string.Empty, 0)
new("speed", UloopCapturedVariableScope.Local, "System.Int32", "5", string.Empty, string.Empty, 0, false)
};

UloopPausePointSnapshot snapshot = UloopPausePointRegistry.HitWithCapturedVariables(
Expand All @@ -1027,10 +1027,12 @@ public void TryGetCapturedValue_WhenLatestHitStoredRawFrame_ReturnsLiveReference
new UloopPausePointCapturedVariableEntry("scores", UloopCapturedVariableScope.Local, scores),
new UloopPausePointCapturedVariableEntry("empty", UloopCapturedVariableScope.Local, null)
},
false);
false,
System.Array.Empty<string>(),
0);
UloopCapturedVariable[] capturedVariables =
{
new("scores", UloopCapturedVariableScope.Local, "System.Collections.Generic.List`1[System.Int32]", "[10,20,30]", string.Empty, string.Empty, 0)
new("scores", UloopCapturedVariableScope.Local, "System.Collections.Generic.List`1[System.Int32]", "[10,20,30]", string.Empty, string.Empty, 0, false)
};

UloopPausePointRegistry.HitWithCapturedFrame("jump", frame, capturedVariables, false);
Expand All @@ -1056,7 +1058,9 @@ public void TryGetCapturedValue_WhenRegistryClearsLatestHit_ReturnsNotFound()
UloopPausePointRegistry.Enable("jump", 30);
UloopPausePointCapturedVariableFrame frame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 5) },
false);
false,
System.Array.Empty<string>(),
0);
UloopPausePointRegistry.HitWithCapturedFrame(
"jump", frame, Array.Empty<UloopCapturedVariable>(), false);

Expand All @@ -1076,10 +1080,14 @@ public void TryGetCapturedValue_WhenNewHitReplacesPrevious_ExposesLatestSnapshot
UloopPausePointRegistry.Enable("land", 30);
UloopPausePointCapturedVariableFrame jumpFrame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 1) },
false);
false,
System.Array.Empty<string>(),
0);
UloopPausePointCapturedVariableFrame landFrame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 2) },
false);
false,
System.Array.Empty<string>(),
0);

UloopPausePointRegistry.HitWithCapturedFrame("jump", jumpFrame, Array.Empty<UloopCapturedVariable>(), false);
UloopPausePointRegistry.HitWithCapturedFrame("land", landFrame, Array.Empty<UloopCapturedVariable>(), false);
Expand All @@ -1098,7 +1106,9 @@ public void TryGetCapturedValue_WhenUnrelatedPausePointIsCleared_KeepsLatestHitR
UloopPausePointRegistry.Enable("land", 30);
UloopPausePointCapturedVariableFrame landFrame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 7) },
false);
false,
System.Array.Empty<string>(),
0);
UloopPausePointRegistry.HitWithCapturedFrame("land", landFrame, Array.Empty<UloopCapturedVariable>(), false);

UloopPausePointRegistry.Clear("jump");
Expand All @@ -1117,7 +1127,9 @@ public void TryGetCapturedValue_WhenSamePausePointIsReenabledWhilePaused_KeepsLa
UloopPausePointRegistry.Enable("jump", 30);
UloopPausePointCapturedVariableFrame frame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 1) },
false);
false,
System.Array.Empty<string>(),
0);
UloopPausePointRegistry.HitWithCapturedFrame("jump", frame, Array.Empty<UloopCapturedVariable>(), false);

UloopPausePointRegistry.Enable("jump", 30);
Expand All @@ -1136,7 +1148,9 @@ public void TryGetCapturedValue_WhenSamePausePointIsReenabledThenCleared_StillCl
UloopPausePointRegistry.Enable("jump", 30);
UloopPausePointCapturedVariableFrame frame = new(
new[] { new UloopPausePointCapturedVariableEntry("speed", UloopCapturedVariableScope.Local, 1) },
false);
false,
System.Array.Empty<string>(),
0);
UloopPausePointRegistry.HitWithCapturedFrame("jump", frame, Array.Empty<UloopCapturedVariable>(), false);

UloopPausePointRegistry.Enable("jump", 30);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ public void Collect_WithNullInstance_AddsNoThisEntry()
[Test]
public void Collect_WhenCountCapReachedBeforeThis_OmitsThisAndReportsTruncated()
{
// Verifies that when locals already fill the count cap, the "this" entry is dropped and
// truncation is reported per the existing TryAppendEntry contract.
// Verifies that when locals already fill the count cap, the "this" entry is dropped from
// Entries but still counted in TruncatedVariableNames / TruncatedVariableCount.
int localCount = SourcePausePointConstants.MaxCapturedVariableCount;
object[] locals = new object[localCount * 2];
for (int i = 0; i < localCount; i++)
Expand All @@ -239,6 +239,45 @@ public void Collect_WhenCountCapReachedBeforeThis_OmitsThisAndReportsTruncated()
Assert.That(frame.Entries.Count, Is.EqualTo(SourcePausePointConstants.MaxCapturedVariableCount));
Assert.That(frame.Entries.Any(entry => entry.Name == "this"), Is.False);
Assert.That(frame.Truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.GreaterThan(0));
Assert.That(frame.TruncatedVariableNames, Does.Contain("this"));
}

[Test]
public void Collect_WhenVariableCountExceedsCap_ReportsTruncatedNamesUpToLimitAndExactCount()
{
// Verifies count-cap overflow keeps collecting names (capped at 20) with an exact total.
int discarded = SourcePausePointConstants.MaxTruncatedVariableNamesReported + 5;
int localCount = SourcePausePointConstants.MaxCapturedVariableCount + discarded;
object[] locals = new object[localCount * 2];
for (int i = 0; i < localCount; i++)
{
locals[i * 2] = $"local{i}";
locals[i * 2 + 1] = i;
}

UloopPausePointCapturedVariableFrame frame = SourcePausePointVariableCollector.Collect(
null, Array.Empty<object>(), locals);

Assert.That(frame.Entries.Count, Is.EqualTo(SourcePausePointConstants.MaxCapturedVariableCount));
Assert.That(frame.Truncated, Is.True);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(discarded));
Assert.That(frame.TruncatedVariableNames.Count, Is.EqualTo(SourcePausePointConstants.MaxTruncatedVariableNamesReported));
Assert.That(frame.TruncatedVariableNames[0], Is.EqualTo($"local{SourcePausePointConstants.MaxCapturedVariableCount}"));
}

[Test]
public void Collect_WhenUnderCountCap_ReportsEmptyTruncatedNames()
{
// Verifies no truncation metadata when every variable fits under the count cap.
object[] locals = { "speed", 5, "damage", 3 };

UloopPausePointCapturedVariableFrame frame = SourcePausePointVariableCollector.Collect(
null, Array.Empty<object>(), locals);

Assert.That(frame.Truncated, Is.False);
Assert.That(frame.TruncatedVariableCount, Is.EqualTo(0));
Assert.That(frame.TruncatedVariableNames, Is.Empty);
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public void Format_WhenValueExceedsMaxLength_TruncatesValueAndSetsTruncatedFlag(

Assert.That(variables.Single().Value.Length, Is.EqualTo(SourcePausePointConstants.MaxCapturedVariableValueLength));
Assert.That(truncated, Is.True);
Assert.That(variables.Single().Truncated, Is.True);
}

[Test]
Expand Down
3 changes: 2 additions & 1 deletion Packages/src/Editor/FirstPartyTools/Compile/CompileSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ public class CompileSchema : UnityCliLoopToolSchema
/// <summary>
/// How long the CLI waits for compilation to complete, in seconds.
/// Unity ignores this value; it is consumed by the CLI.
/// Why no [Description]: first-party schema properties must keep long-form agent guidance
/// in skill files (see FirstPartyToolSchemaMetadataTests), not runtime metadata.
/// </summary>
[Description("How long the CLI waits for compilation to complete, in seconds. Unity ignores this value; it is consumed by the CLI.")]
public int CompileWaitTimeoutSeconds { get; set; } = 600;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public class PausePointCapturedVariable
public string UnityObjectKind { get; set; } = string.Empty;
public string UnityObjectPath { get; set; } = string.Empty;
public int UnityObjectInstanceId { get; set; }
public bool Truncated { get; set; }

internal static PausePointCapturedVariable FromSnapshot(UloopCapturedVariable snapshot)
{
Expand All @@ -208,7 +209,8 @@ internal static PausePointCapturedVariable FromSnapshot(UloopCapturedVariable sn
Value = snapshot.Value,
UnityObjectKind = snapshot.UnityObjectKind,
UnityObjectPath = snapshot.UnityObjectPath,
UnityObjectInstanceId = snapshot.UnityObjectInstanceId
UnityObjectInstanceId = snapshot.UnityObjectInstanceId,
Truncated = snapshot.Truncated
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ internal static class SourcePausePointConstants
// Keeps a single hit's payload small enough for the CLI response and for the console-like
// pause-point evidence to stay skimmable, mirroring the truncation-by-cap pattern MatchingLogs uses.
public const int MaxCapturedVariableCount = 50;
// How many discarded variable names to surface when the count cap drops extras. The exact
// discarded count is still reported in full via TruncatedVariableCount.
public const int MaxTruncatedVariableNamesReported = 20;
public const int MaxCapturedVariableValueLength = 256;
// Mirrors UloopPausePointRegistry.DefaultMaxPreviewElements (the Runtime-owned per-marker
// default enforced at Enable time) instead of a second independent literal, so the two
Expand Down
Loading