feat: PolicyControllerAi integration — Forge AI decision interception via policy server

## New File
- PolicyControllerAi.java (840 lines): Extends Forge's PlayerControllerAi
  to intercept AI decisions at chooseSpellAbilityToPlay() and route them
  through the Python policy server. Falls back to Forge built-in AI on
  any failure. Inner PolicyAiController overrides AiController with full
  legal action gathering via ComputerUtilAbility.

## Wiring
- GameSession.java: Import + Javadoc for PolicyControllerAi decision flow.
  Logs decision pipeline when policy server connects. End-of-game reward
  submission via PolicyControllerAi lifecycle.
- BatchRunner.java: New setPolicyServer(url) method. Appends
  --policy midrange --policyServer <url> to Forge subprocess command when
  policy server URL is configured.

## Path Updates (Forge-Repo rename)
- README.md: Forge link → KoalaTrapLord/Forge-Repo, build path updated
- .env.example: FORGE_JAR_PATH and FORGE_WORKING_DIR → Forge-Repo
- lab_api.py: Auto-detect paths try Forge-Repo first, forge-repo as legacy

## MacroActionExecutor
- Added public isRemovalCard(), isDrawCard(), isRampCard() wrappers so
  PolicyControllerAi can consult the same card-role databases.

Refs: Issue #83 Phase 2.4
This commit is contained in:
KoalaTrapLord committed 2026-04-05 14:47:15 +00:00
1 parent 4cae97ab44
commit a23fdc55a4
7 files changed
+952 -8

No files matched your search

@@ -1,6 +1,7 @@
package commanderailab.batch;
import commanderailab.ai.AiPolicy;
import commanderailab.ai.PolicyControllerAi;
import commanderailab.ml.DecisionExtractor;
import commanderailab.ml.DecisionLogger;
import commanderailab.schema.BatchResult;
@@ -76,6 +77,12 @@ public class BatchRunner {
private boolean useSimplifiedAi = false; // Use faster AI profile for sims
private int aiThinkTimeMs = -1; // -1 = Forge default; >0 = cap AI think time
// ── Policy server integration (PolicyControllerAi) ────────
// When set, the Forge command includes --policyServer so that
// PolicyControllerAi (in-process) or the external policy server
// can intercept AI decisions during batch simulations.
private String policyServerUrl = null;
// ── Regex patterns for parsing Forge sim output ────────────────────
// "Game Outcome: Turn 11"
@@ -186,6 +193,22 @@ public class BatchRunner {
this.aiThinkTimeMs = thinkTimeMs;
}
/**
* Enable policy server integration for batch simulations.
*
* When set, the Forge subprocess command includes
* {@code --policy midrange --policyServer <url>} flags, and
* the log output notes that {@link PolicyControllerAi} is
* available for decision interception.
*
* @param url Base URL of the Python policy server
* (e.g. {@code "http://localhost:8080"})
*/
public void setPolicyServer(String url) {
this.policyServerUrl = url;
System.out.println("[BatchRunner] PolicyControllerAi integration enabled — server: " + url);
}
public void enableMlLogging(String resultsDir, String batchId) {
this.mlLoggingEnabled = true;
this.mlResultsDir = resultsDir;
@@ -438,6 +461,19 @@ public class BatchRunner {
cmd.add("-c");
cmd.add(String.valueOf(clockSeconds));
// Policy server integration (PolicyControllerAi)
// When policyServerUrl is set, tell Forge to use policy-driven AI.
// The Python policy server at this URL handles POST /api/policy/decide
// and returns macro-actions that PolicyControllerAi maps to concrete
// SpellAbility objects.
if (policyServerUrl != null && !policyServerUrl.isBlank()) {
cmd.add("--policy");
cmd.add("midrange");
cmd.add("--policyServer");
cmd.add(policyServerUrl);
System.out.println("[BatchRunner] Policy server: " + policyServerUrl);
}
if (quiet) {
cmd.add("-q");
}