feat: game pause/speed control API with AI pause protocol

- POST /api/action/pause - pause game (AI must call before operations)
- POST /api/action/unpause - resume game at desired speed
- POST /api/action/speed - set game speed (1x/2x/3x)
- Game state now includes isPaused and gameSpeed
- AI pause protocol documented in SKILL.md: pause before every operation
- Introduces cell.isDiggable flag that filters neutronium properly
- pause/unpause/speed CLI commands added to oni_api.py
This commit is contained in:
root
2026-05-22 09:04:04 +08:00
parent a4f2062c1a
commit 48a70e4928
4 changed files with 183 additions and 1 deletions

View File

@ -181,6 +181,15 @@ namespace ONIAgentBridge
case ("/api/action/priority_type", "POST"):
responseJson = ExecutePriorityType(ctx);
break;
case ("/api/action/pause", "POST"):
responseJson = ExecutePause(ctx);
break;
case ("/api/action/unpause", "POST"):
responseJson = ExecuteUnpause(ctx);
break;
case ("/api/action/speed", "POST"):
responseJson = ExecuteSpeed(ctx);
break;
default:
ctx.Response.StatusCode = 404;
@ -293,6 +302,9 @@ namespace ONIAgentBridge
PushEvent("state_poll", "info", "Game state polled", $"Cycle {GameClock.Instance?.GetCycle() ?? 0}");
bool isPaused = SpeedControlScreen.Instance?.IsPaused ?? false;
int gameSpeed = isPaused ? 0 : (SpeedControlScreen.Instance?.GetSpeed() ?? 1);
return JsonSerializer.Serialize(new
{
cycle = GameClock.Instance?.GetCycle() ?? 0,
@ -304,6 +316,8 @@ namespace ONIAgentBridge
worldSize = cellCount,
gridWidth = Grid.WidthInCells,
gridHeight = Grid.HeightInCells,
isPaused,
gameSpeed,
eventCount = _eventSeq
});
}
@ -847,7 +861,8 @@ namespace ONIAgentBridge
bool isDiggable = Grid.Solid[cell] && elem != null
&& elem.id != SimHashes.Unobtanium
&& elem.id != SimHashes.Katairite
&& elem.id != SimHashes.Void;
&& elem.id != SimHashes.Void
&& !elem.name.Contains("Neutronium");
// Check dupe safety
bool isSafeForDupe = elem != null
@ -1246,6 +1261,74 @@ namespace ONIAgentBridge
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Pause / Speed Control
// ===================================================================
private string ExecutePause(HttpListenerContext ctx)
{
try
{
var data = ReadBody<PauseRequest>(ctx);
// No body needed, but accept optional { "reason": "..." }
string reason = data?.reason ?? "AI operation in progress";
SpeedControlScreen.Instance?.Pause(false, true);
PushEvent("pause", "info", "Game paused",
$"Game paused by AI: {reason}", "system");
return JsonSerializer.Serialize(ActionOk("game_paused",
new { reason, isPaused = true }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
private string ExecuteUnpause(HttpListenerContext ctx)
{
try
{
var data = ReadBody<UnpauseRequest>(ctx);
int speed = data?.speed ?? 1;
if (speed < 1) speed = 1;
if (speed > 3) speed = 3;
SpeedControlScreen.Instance?.Unpause(true);
SpeedControlScreen.Instance?.SetSpeed(speed);
PushEvent("unpause", "info", "Game resumed",
$"Game resumed by AI at {speed}x speed", "system");
return JsonSerializer.Serialize(ActionOk("game_unpaused",
new { speed, isPaused = false }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
private string ExecuteSpeed(HttpListenerContext ctx)
{
try
{
var data = ReadBody<SpeedRequest>(ctx);
if (data == null)
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
int speed = data.speed;
if (speed < 1) speed = 1;
if (speed > 3) speed = 3;
bool isPaused = SpeedControlScreen.Instance?.IsPaused ?? false;
if (!isPaused)
{
SpeedControlScreen.Instance?.SetSpeed(speed);
}
PushEvent("speed", "info", $"Game speed set to {speed}x",
$"Speed changed to {speed}x", "system");
return JsonSerializer.Serialize(ActionOk("speed_set",
new { speed, isPaused }));
}
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
}
// ===================================================================
// Batch System
// ===================================================================
@ -1578,6 +1661,9 @@ namespace ONIAgentBridge
internal class MopRequest { public int x { get; set; } public int y { get; set; } }
internal class HarvestRequest { public int x { get; set; } public int y { get; set; } }
internal class CancelRequest { public int x { get; set; } public int y { get; set; } }
internal class PauseRequest { public string reason { get; set; } }
internal class UnpauseRequest { public int speed { get; set; } }
internal class SpeedRequest { public int speed { get; set; } }
internal class PriorityGlobalRequest { public string target { get; set; } public int priority { get; set; } }
internal class PriorityTypeRequest { public string buildingType { get; set; } public int priority { get; set; } }