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.
77 lines
2.6 KiB
C#
77 lines
2.6 KiB
C#
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}.");
|
|
}
|
|
}
|
|
}
|