Launching Godot with --automation=<dir> activates an AutomationHarness node that polls <dir>/inbox/ for JSON command files, executes them via a thin facade over existing public surfaces (GameSim, InputMapper, EventAnimator, ControlBar, PieceStockPanel), and writes results plus screenshots back to disk. The black-box simulation boundary is not crossed — every command routes through the same signals/methods a real player would trigger. A stdlib-only Python helper (tools/automation/harness.py) wraps the protocol for test scripts and interactive REPLs. Smoke test passes end-to-end: load mission, place a piece, step 10 turns, capture 14 1280x720 PNGs, handle rejections, quit cleanly. Existing 102 engine unit tests still green.
65 lines
2.2 KiB
C#
65 lines
2.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
namespace Chessistics.Scripts.Automation;
|
|
|
|
/// <summary>
|
|
/// Tiny helpers for the file-based IPC. All writes are atomic: write .tmp then rename.
|
|
/// </summary>
|
|
internal static class IpcFiles
|
|
{
|
|
public const string InboxDir = "inbox";
|
|
public const string OutboxDir = "outbox";
|
|
public const string ScreensDir = "screens";
|
|
public const string ReadyFile = "ready.json";
|
|
public const string LogFile = "harness.log";
|
|
|
|
public static void EnsureDirs(string root)
|
|
{
|
|
Directory.CreateDirectory(root);
|
|
Directory.CreateDirectory(Path.Combine(root, InboxDir));
|
|
Directory.CreateDirectory(Path.Combine(root, OutboxDir));
|
|
Directory.CreateDirectory(Path.Combine(root, ScreensDir));
|
|
}
|
|
|
|
public static void AtomicWrite(string path, string contents)
|
|
{
|
|
var tmp = path + ".tmp";
|
|
File.WriteAllText(tmp, contents);
|
|
if (File.Exists(path)) File.Delete(path);
|
|
File.Move(tmp, path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return the oldest inbox file whose id hasn't been processed yet, or null.
|
|
/// Sorted by filename — agents should name files with a monotonic prefix for ordering.
|
|
/// </summary>
|
|
public static string? NextInbox(string root, HashSet<string> processed)
|
|
{
|
|
var inbox = Path.Combine(root, InboxDir);
|
|
if (!Directory.Exists(inbox)) return null;
|
|
|
|
var files = Directory.GetFiles(inbox, "*.json")
|
|
.Where(f => !f.EndsWith(".tmp.json", StringComparison.Ordinal))
|
|
.OrderBy(f => Path.GetFileName(f), StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
foreach (var f in files)
|
|
{
|
|
if (!processed.Contains(f))
|
|
return f;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static string OutboxPath(string root, string id) => Path.Combine(root, OutboxDir, id + ".json");
|
|
|
|
public static string ScreenshotPath(string root, string name)
|
|
{
|
|
var safe = name.Replace('/', '_').Replace('\\', '_').Replace(':', '_');
|
|
if (!safe.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) safe += ".png";
|
|
return Path.Combine(root, ScreensDir, safe);
|
|
}
|
|
}
|