wip: Campaign LA LA2 probe mode + idle policy — INCOMPLETE, stopped mid-task

Agent was stopped for token budget. Landed here: probe flag through
LiveSessionConnectOptions + the StartCore short-circuit, the mode field
with JsonRequired-to-semantic-validation move, host exit-code mapping,
and 34 passing tests including 3 new probe tests (agent last reported
green before the stop). NOT DONE: the idle-policy unit tests (next
step), full-suite verification, and the WSL run.

Build/test state UNVERIFIED at this commit. Next session: finish idle
policy tests, run Runtime+Headless Release suites Windows and WSL, then
dispatch the Opus dual-lens review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 16:33:00 +02:00
parent 498f1c1182
commit c601942467
14 changed files with 830 additions and 35 deletions

View file

@ -100,6 +100,21 @@ internal static class HeadlessBotPolicyFactory
}
}
/// <summary>
/// Campaign LA slice LA2: the "idle" consumer policy id — the session enters
/// world (unchanged <see cref="HeadlessSessionHost"/> start/select/EnterWorld
/// path) and then does nothing actively: no chat, no movement, no combat.
/// <see cref="IsComplete"/> is permanently <see langword="false"/>, so
/// <see cref="HeadlessProcessScheduler"/> keeps ticking the session
/// (harmlessly — <see cref="Tick"/> and every delta handler below are no-ops)
/// until the process is stopped (SIGINT/cancellation) or disposed; teardown
/// then rides <see cref="HeadlessSessionHost.Dispose"/>'s existing graceful
/// stop/logout path — the same mechanism K4's endurance gate already proved.
/// No <see cref="HeadlessBotPolicyDescriptor.Role"/> is required. This class
/// predates LA2 (introduced at K1 as dev/test scaffolding); LA2 formalizes it
/// as the documented headless "just sit in world" play policy and adds
/// focused coverage in <c>HeadlessBotPolicyTests</c>.
/// </summary>
internal sealed class IdleHeadlessBotPolicy : IHeadlessBotPolicy
{
public bool IsComplete => false;
@ -149,6 +164,70 @@ internal sealed class IdleHeadlessBotPolicy : IHeadlessBotPolicy
}
}
/// <summary>
/// Campaign LA slice LA2: the policy substituted (never selected via
/// <see cref="HeadlessBotPolicyFactory.Create"/> — a probe session's
/// descriptor carries no <c>policy</c> id at all) for a
/// <see cref="HeadlessSessionMode.Probe"/> session. <see cref="IsComplete"/>
/// is <see langword="true"/> from construction, BEFORE
/// <see cref="HeadlessSessionHost.Start"/> even runs, so
/// <see cref="HeadlessProcessScheduler"/> never dispatches a tick to this
/// session — a probe session's <see cref="AcDream.Runtime.Session.WorldSession"/>
/// is already gracefully torn down by
/// <see cref="AcDream.Runtime.Session.LiveSessionController"/>'s probe
/// short-circuit by the time the scheduler would otherwise look at it, and a
/// single-session probe process's <c>Run()</c> loop returns immediately
/// instead of waiting for SIGINT.
/// </summary>
internal sealed class ProbeHeadlessBotPolicy : IHeadlessBotPolicy
{
public bool IsComplete => true;
public void Tick(
IGameRuntimeView view,
IGameRuntimeCommands commands)
{
ArgumentNullException.ThrowIfNull(view);
ArgumentNullException.ThrowIfNull(commands);
}
public void OnLifecycle(in RuntimeLifecycleDelta delta)
{
}
public void OnCommand(in RuntimeCommandDelta delta)
{
}
public void OnEntity(in RuntimeEntityDelta delta)
{
}
public void OnInventory(in RuntimeInventoryDelta delta)
{
}
public void OnChat(in RuntimeChatDelta delta)
{
}
public void OnMovement(in RuntimeMovementDelta delta)
{
}
public void OnPortal(in RuntimePortalDelta delta)
{
}
public void OnCombat(in RuntimeCombatDelta delta)
{
}
public void Dispose()
{
}
}
/// <summary>
/// Explicit connected-gate policy: wait for the local player, issue one
/// harmless local-speech command and one lifestone recall, reconnect after