test: Campaign LA finish LA2 probe and idle gates
Prove idle play remains passive and live until cancellation, then converges through one truthful status teardown. Keep probe mode string-only so numeric enum aliases cannot expand the pinned v1 contract, and record the Windows/WSL gates.
This commit is contained in:
parent
c601942467
commit
000ea979d5
5 changed files with 243 additions and 38 deletions
77
tests/AcDream.Headless.Tests/HeadlessBotPolicyTests.cs
Normal file
77
tests/AcDream.Headless.Tests/HeadlessBotPolicyTests.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System.Reflection;
|
||||
using AcDream.Headless.Policies;
|
||||
using AcDream.Runtime;
|
||||
|
||||
namespace AcDream.Headless.Tests;
|
||||
|
||||
public sealed class HeadlessBotPolicyTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Campaign LA slice LA2: <c>idle</c> is a deliberately passive,
|
||||
/// non-terminal policy. It must neither inspect Runtime state nor reach
|
||||
/// any command surface, and no event can make it complete on its own.
|
||||
/// The process scheduler therefore keeps the play session alive until
|
||||
/// external cancellation/stop drives the host's ordinary teardown path.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IdlePolicyIsPassiveAndNeverCompletesAutonomously()
|
||||
{
|
||||
var policy = new IdleHeadlessBotPolicy();
|
||||
IGameRuntimeView view = CreateNoTouchProxy<IGameRuntimeView>(
|
||||
out InvocationCountingProxy viewCalls);
|
||||
IGameRuntimeCommands commands =
|
||||
CreateNoTouchProxy<IGameRuntimeCommands>(
|
||||
out InvocationCountingProxy commandCalls);
|
||||
|
||||
for (int index = 0; index < 3; index++)
|
||||
policy.Tick(view, commands);
|
||||
|
||||
RuntimeLifecycleDelta lifecycle = default;
|
||||
RuntimeCommandDelta command = default;
|
||||
RuntimeEntityDelta entity = default;
|
||||
RuntimeInventoryDelta inventory = default;
|
||||
RuntimeChatDelta chat = default;
|
||||
RuntimeMovementDelta movement = default;
|
||||
RuntimePortalDelta portal = default;
|
||||
RuntimeCombatDelta combat = default;
|
||||
policy.OnLifecycle(in lifecycle);
|
||||
policy.OnCommand(in command);
|
||||
policy.OnEntity(in entity);
|
||||
policy.OnInventory(in inventory);
|
||||
policy.OnChat(in chat);
|
||||
policy.OnMovement(in movement);
|
||||
policy.OnPortal(in portal);
|
||||
policy.OnCombat(in combat);
|
||||
|
||||
Assert.False(policy.IsComplete);
|
||||
Assert.Equal(0, viewCalls.InvocationCount);
|
||||
Assert.Equal(0, commandCalls.InvocationCount);
|
||||
|
||||
policy.Dispose();
|
||||
policy.Dispose();
|
||||
Assert.False(policy.IsComplete);
|
||||
}
|
||||
|
||||
private static T CreateNoTouchProxy<T>(
|
||||
out InvocationCountingProxy proxy)
|
||||
where T : class
|
||||
{
|
||||
T value = DispatchProxy.Create<T, InvocationCountingProxy>();
|
||||
proxy = (InvocationCountingProxy)(object)value;
|
||||
return value;
|
||||
}
|
||||
|
||||
public class InvocationCountingProxy : DispatchProxy
|
||||
{
|
||||
public int InvocationCount { get; private set; }
|
||||
|
||||
protected override object? Invoke(
|
||||
MethodInfo? targetMethod,
|
||||
object?[]? args)
|
||||
{
|
||||
InvocationCount++;
|
||||
throw new InvalidOperationException(
|
||||
$"Idle policy unexpectedly invoked {targetMethod?.Name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue