chore: ignore runtime artifacts, add patch scripts & training manifest
Ignore static/, logs/, data/rag_chroma/, api/, image_cache/, .claude/, precon-decks/, and dependency-reduced-pom.xml — all runtime/build artifacts that were polluting git status. Also stage the one-off patch scripts (fix_paths.py, patch_eval.py, patch_phase1.py, forge_evaluator_FIXED.py) and training_manifest.json so they're tracked alongside the code they support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a23fdc55a4
commit
ea23f97b9c
6 files changed
+1329
No files matched your search
+12
@@ -41,3 +41,15 @@ Thumbs.db
|
|||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Runtime artifacts / caches
|
||||||
|
static/
|
||||||
|
api/
|
||||||
|
image_cache/
|
||||||
|
data/rag_chroma/
|
||||||
|
.claude/
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
# Precon decks (generated / imported at runtime)
|
||||||
|
precon-decks/
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
with open('ml/eval/forge_evaluator.py', 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
# Fix 1: absolute JAR path in subprocess call
|
||||||
|
data = data.replace(
|
||||||
|
b'"--forge-jar", self.cfg.forge_jar,',
|
||||||
|
b'"--forge-jar", str(Path(self.cfg.forge_jar).resolve()),'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fix 2: resolve forge_work_dir to absolute path too
|
||||||
|
data = data.replace(
|
||||||
|
b'"--forge-dir", self.cfg.forge_work_dir,',
|
||||||
|
b'"--forge-dir", str(Path(self.cfg.forge_work_dir).resolve()),'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fix 3: change default forge_work_dir
|
||||||
|
data = data.replace(
|
||||||
|
b'forge_work_dir: str = "forge"',
|
||||||
|
b'forge_work_dir: str = "D:/ForgeCommander/forge-repo/forge-gui"'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open('ml/eval/forge_evaluator.py', 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
import py_compile
|
||||||
|
py_compile.compile('ml/eval/forge_evaluator.py', doraise=True)
|
||||||
|
print('forge_evaluator.py - Syntax OK')
|
||||||
|
|
||||||
|
# Fix eval_policy.py too
|
||||||
|
with open('scripts/eval_policy.py', 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
data = data.replace(
|
||||||
|
b'"--forge-dir", default="forge"',
|
||||||
|
b'"--forge-dir", default="D:/ForgeCommander/forge-repo/forge-gui"'
|
||||||
|
)
|
||||||
|
|
||||||
|
with open('scripts/eval_policy.py', 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
py_compile.compile('scripts/eval_policy.py', doraise=True)
|
||||||
|
print('eval_policy.py - Syntax OK')
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
"""
|
||||||
|
Commander AI Lab — Forge Evaluator (Issue #153)
|
||||||
|
═════════════════════════════════════════════════
|
||||||
|
Reusable eval harness that:
|
||||||
|
1. Starts the policy inference server pointing at a given checkpoint
|
||||||
|
2. Launches Forge games via MultiThreadBatchRunner with the policy
|
||||||
|
server wired in as the AI decision-maker
|
||||||
|
3. Collects per-game metrics and writes a JSON results file
|
||||||
|
|
||||||
|
Metrics collected per game:
|
||||||
|
- won : bool
|
||||||
|
- turns : int
|
||||||
|
- life_final : int (winning seat final life)
|
||||||
|
- life_delta : int (winner life - loser life at game end)
|
||||||
|
- cmd_damage : int (commander damage dealt by policy seat)
|
||||||
|
- illegal_acts : int (actions rejected by Forge — should be 0)
|
||||||
|
- entropy_mean : float (mean policy entropy across decision steps)
|
||||||
|
- inference_ms : float (mean per-step inference latency)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
from ml.eval.forge_evaluator import ForgeEvaluator, EvalConfig
|
||||||
|
cfg = EvalConfig(
|
||||||
|
checkpoint_path="ml/models/checkpoints/forge-trained/final.pt",
|
||||||
|
forge_jar="forge/forge-gui-desktop-2.0.12-SNAPSHOT-jar-with-dependencies.jar",
|
||||||
|
deck_files=["decks/cedh_test_a.dck", "decks/cedh_test_b.dck"],
|
||||||
|
num_games=200,
|
||||||
|
num_threads=4,
|
||||||
|
run_id="forge-trained",
|
||||||
|
results_dir="results",
|
||||||
|
)
|
||||||
|
evaluator = ForgeEvaluator(cfg)
|
||||||
|
summary = evaluator.run()
|
||||||
|
print(summary)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass, field, asdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
project_root = str(Path(__file__).parent.parent.parent)
|
||||||
|
if project_root not in sys.path:
|
||||||
|
sys.path.insert(0, project_root)
|
||||||
|
|
||||||
|
logger = logging.getLogger("ml.eval.forge_evaluator")
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# Configuration
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EvalConfig:
|
||||||
|
"""All parameters needed for a single evaluation run."""
|
||||||
|
|
||||||
|
# Model
|
||||||
|
checkpoint_path: str = "ml/models/checkpoints/best_policy.pt"
|
||||||
|
embeddings_dir: str = "embeddings"
|
||||||
|
device: str = None # None = auto-detect
|
||||||
|
|
||||||
|
# Forge
|
||||||
|
forge_jar: str = "forge/forge-gui-desktop-2.0.12-SNAPSHOT-jar-with-dependencies.jar"
|
||||||
|
forge_work_dir: str = "forge"
|
||||||
|
java_path: str = "java"
|
||||||
|
deck_files: List[str] = field(default_factory=list)
|
||||||
|
deck_names: List[str] = field(default_factory=list)
|
||||||
|
policy_server_url: str = "http://localhost:8091"
|
||||||
|
clock_seconds: int = 120
|
||||||
|
|
||||||
|
# Run
|
||||||
|
num_games: int = 200
|
||||||
|
num_threads: int = 4
|
||||||
|
seed: Optional[int] = None
|
||||||
|
run_id: str = ""
|
||||||
|
results_dir: str = "results"
|
||||||
|
|
||||||
|
# Inference
|
||||||
|
greedy: bool = True
|
||||||
|
temperature: float = 1.0
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if not self.run_id:
|
||||||
|
self.run_id = str(uuid.uuid4())[:8]
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# Per-game result
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GameRecord:
|
||||||
|
game_index: int
|
||||||
|
won: bool
|
||||||
|
turns: int
|
||||||
|
life_final_policy: int # life total of policy seat at game end
|
||||||
|
life_final_opponent: int
|
||||||
|
life_delta: int # policy life - opponent life
|
||||||
|
commander_damage_dealt: int
|
||||||
|
illegal_actions: int
|
||||||
|
entropy_mean: float # mean per-step policy entropy
|
||||||
|
inference_ms_mean: float # mean per-step inference latency
|
||||||
|
decision_steps: int # number of decision points in this game
|
||||||
|
error: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# Eval summary
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EvalSummary:
|
||||||
|
run_id: str
|
||||||
|
checkpoint_path: str
|
||||||
|
num_games: int
|
||||||
|
wins: int
|
||||||
|
losses: int
|
||||||
|
win_rate: float
|
||||||
|
win_rate_ci95_low: float
|
||||||
|
win_rate_ci95_high: float
|
||||||
|
avg_turns: float
|
||||||
|
avg_life_delta: float
|
||||||
|
avg_commander_damage: float
|
||||||
|
avg_illegal_actions: float
|
||||||
|
avg_entropy: float
|
||||||
|
avg_inference_ms: float
|
||||||
|
total_decision_steps: int
|
||||||
|
elapsed_wall_s: float
|
||||||
|
games_per_sec: float
|
||||||
|
error_count: int
|
||||||
|
result_file: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"EvalSummary run={self.run_id}\n"
|
||||||
|
f" checkpoint : {self.checkpoint_path}\n"
|
||||||
|
f" games : {self.num_games} ({self.wins}W / {self.losses}L)\n"
|
||||||
|
f" win rate : {self.win_rate:.1%} "
|
||||||
|
f"[{self.win_rate_ci95_low:.1%}, {self.win_rate_ci95_high:.1%}] 95% CI\n"
|
||||||
|
f" avg turns : {self.avg_turns:.1f}\n"
|
||||||
|
f" avg life Δ : {self.avg_life_delta:+.1f}\n"
|
||||||
|
f" avg cmd dmg: {self.avg_commander_damage:.1f}\n"
|
||||||
|
f" illegal : {self.avg_illegal_actions:.2f}/game\n"
|
||||||
|
f" entropy : {self.avg_entropy:.4f}\n"
|
||||||
|
f" latency : {self.avg_inference_ms:.1f} ms/step\n"
|
||||||
|
f" throughput : {self.games_per_sec:.2f} games/sec\n"
|
||||||
|
f" result file: {self.result_file}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# Main evaluator
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
class ForgeEvaluator:
|
||||||
|
"""
|
||||||
|
Orchestrates a full eval run:
|
||||||
|
1. Spin up PolicyInferenceService on cfg.policy_server_url port
|
||||||
|
2. Run cfg.num_games Forge games via MultiThreadBatchRunner
|
||||||
|
3. Parse Forge JSONL output for per-game metrics
|
||||||
|
4. Write results to {results_dir}/eval-{run_id}.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cfg: EvalConfig):
|
||||||
|
self.cfg = cfg
|
||||||
|
self._records: List[GameRecord] = []
|
||||||
|
self._result_path = os.path.join(
|
||||||
|
cfg.results_dir, f"eval-{cfg.run_id}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Public entry point ───────────────────
|
||||||
|
|
||||||
|
def run(self) -> EvalSummary:
|
||||||
|
"""Run the full evaluation. Returns an EvalSummary."""
|
||||||
|
t0 = time.time()
|
||||||
|
os.makedirs(self.cfg.results_dir, exist_ok=True)
|
||||||
|
|
||||||
|
logger.info("[Eval %s] Starting %d-game eval run", self.cfg.run_id, self.cfg.num_games)
|
||||||
|
logger.info("[Eval %s] Checkpoint: %s", self.cfg.run_id, self.cfg.checkpoint_path)
|
||||||
|
|
||||||
|
# Load policy service
|
||||||
|
service = self._load_policy_service()
|
||||||
|
|
||||||
|
# Run games — either via real Forge or synthetic fallback
|
||||||
|
forge_available = (
|
||||||
|
self.cfg.forge_jar
|
||||||
|
and Path(self.cfg.forge_jar).exists()
|
||||||
|
and len(self.cfg.deck_files) >= 3
|
||||||
|
)
|
||||||
|
|
||||||
|
if forge_available:
|
||||||
|
self._records = self._run_forge_games(service)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"[Eval %s] Forge JAR or decks not found — running synthetic eval.",
|
||||||
|
self.cfg.run_id
|
||||||
|
)
|
||||||
|
self._records = self._run_synthetic_eval(service)
|
||||||
|
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
summary = self._build_summary(elapsed)
|
||||||
|
self._write_results(summary)
|
||||||
|
logger.info("[Eval %s] Done. %s", self.cfg.run_id, summary)
|
||||||
|
return summary
|
||||||
|
|
||||||
|
# ── Policy service setup ─────────────────
|
||||||
|
|
||||||
|
def _load_policy_service(self):
|
||||||
|
try:
|
||||||
|
from ml.serving.policy_server import PolicyInferenceService
|
||||||
|
svc = PolicyInferenceService(
|
||||||
|
checkpoint_dir=str(Path(self.cfg.checkpoint_path).parent),
|
||||||
|
embeddings_dir=self.cfg.embeddings_dir,
|
||||||
|
device=self.cfg.device,
|
||||||
|
)
|
||||||
|
svc.load()
|
||||||
|
ok = svc.reload(self.cfg.checkpoint_path)
|
||||||
|
if not ok:
|
||||||
|
logger.warning("[Eval] Policy service load failed — entropy/latency metrics unavailable")
|
||||||
|
return svc
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("[Eval] Could not load PolicyInferenceService: %s", e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ── Real Forge game runner ───────────────
|
||||||
|
|
||||||
|
def _run_forge_games(self, service) -> List[GameRecord]:
|
||||||
|
"""Launch LabCli Java subprocess with --ml-log, then parse JSONL output."""
|
||||||
|
logger.info("[Eval %s] Launching Forge CLI subprocess for %d games", self.cfg.run_id, self.cfg.num_games)
|
||||||
|
deck_args = []
|
||||||
|
for i, df in enumerate(self.cfg.deck_files[:4]):
|
||||||
|
deck_args.extend([f"--deck{i+1}", Path(df).stem])
|
||||||
|
# Resolve Java 17
|
||||||
|
java_exe = self.cfg.java_path
|
||||||
|
for c in [r"C:\Program Files\Eclipse Adoptium\jdk-17.0.18.8-hotspot\bin\java.exe",
|
||||||
|
r"C:\Program Files\Java\jdk-17\bin\java.exe"]:
|
||||||
|
if Path(c).exists():
|
||||||
|
java_exe = c
|
||||||
|
break
|
||||||
|
lab_jar = os.path.join(project_root, "target", "commander-ai-lab-1.0.0-SNAPSHOT.jar")
|
||||||
|
if not Path(lab_jar).exists():
|
||||||
|
logger.error("[Eval] Lab JAR not found at %s -- run mvn package -DskipTests first", lab_jar)
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
cmd = [
|
||||||
|
java_exe, "-jar", lab_jar,
|
||||||
|
"--forge-jar", self.cfg.forge_jar,
|
||||||
|
"--forge-dir", self.cfg.forge_work_dir,
|
||||||
|
*deck_args,
|
||||||
|
"--games", str(self.cfg.num_games),
|
||||||
|
"--threads", str(self.cfg.num_threads),
|
||||||
|
"--clock", str(self.cfg.clock_seconds),
|
||||||
|
"--ml-log",
|
||||||
|
"--output", os.path.join(self.cfg.results_dir, f"eval-{self.cfg.run_id}-batch.json"),
|
||||||
|
]
|
||||||
|
if self.cfg.seed is not None:
|
||||||
|
cmd.extend(["--seed", str(self.cfg.seed)])
|
||||||
|
logger.info("[Eval %s] CMD: %s", self.cfg.run_id, " ".join(cmd))
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True,
|
||||||
|
timeout=self.cfg.clock_seconds * self.cfg.num_games + 300)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.warning("[Eval %s] Forge CLI exit %d: %s", self.cfg.run_id, result.returncode, result.stderr[:500])
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError) as e:
|
||||||
|
logger.error("[Eval %s] Forge CLI failed: %s -- falling back to synthetic", self.cfg.run_id, e)
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
return self._parse_batch_json(service)
|
||||||
|
|
||||||
|
def _parse_batch_json(self, service) -> List[GameRecord]:
|
||||||
|
"""Parse the batch JSON output from LabCli Java subprocess."""
|
||||||
|
batch_path = os.path.join(
|
||||||
|
self.cfg.results_dir, f"eval-{self.cfg.run_id}-batch.json"
|
||||||
|
)
|
||||||
|
if not Path(batch_path).exists():
|
||||||
|
logger.warning("[Eval] Batch JSON not found at %s — falling back to synthetic", batch_path)
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
with open(batch_path) as f:
|
||||||
|
batch = json.load(f)
|
||||||
|
games_list = batch.get("games", [])
|
||||||
|
if not games_list:
|
||||||
|
logger.warning("[Eval] No games in batch JSON — falling back to synthetic")
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
records = []
|
||||||
|
for idx, g in enumerate(games_list):
|
||||||
|
won = g.get("winningSeat", -1) == 0
|
||||||
|
turns = g.get("totalTurns", 0)
|
||||||
|
pr = g.get("playerResults", [])
|
||||||
|
lp = pr[0].get("finalLife", 40) if len(pr) > 0 else 40
|
||||||
|
lo = pr[1].get("finalLife", 40) if len(pr) > 1 else 40
|
||||||
|
records.append(GameRecord(
|
||||||
|
game_index=idx,
|
||||||
|
won=won,
|
||||||
|
turns=turns,
|
||||||
|
life_final_policy=lp,
|
||||||
|
life_final_opponent=lo,
|
||||||
|
life_delta=lp - lo,
|
||||||
|
commander_damage_dealt=0,
|
||||||
|
illegal_actions=0,
|
||||||
|
entropy_mean=0.0,
|
||||||
|
inference_ms_mean=0.0,
|
||||||
|
decision_steps=turns,
|
||||||
|
))
|
||||||
|
if len(records) >= self.cfg.num_games:
|
||||||
|
break
|
||||||
|
logger.info("[Eval %s] Parsed %d games from batch JSON", self.cfg.run_id, len(records))
|
||||||
|
return records
|
||||||
|
|
||||||
|
def _run_forge_via_subprocess(self, service) -> List[GameRecord]:
|
||||||
|
"""Parse JSONL files produced by a separately-launched Forge batch."""
|
||||||
|
records = []
|
||||||
|
results_path = Path(self.cfg.results_dir)
|
||||||
|
pattern = f"ml-decisions-forge-eval-{self.cfg.run_id}*.jsonl"
|
||||||
|
|
||||||
|
all_files = sorted(results_path.glob(pattern))
|
||||||
|
if not all_files:
|
||||||
|
logger.warning("[Eval] No JSONL files found matching %s — falling back to synthetic", pattern)
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
|
||||||
|
from ml.training.forge_episode_generator import parse_forge_jsonl, snapshots_to_episode
|
||||||
|
games_seen: Dict[str, list] = {}
|
||||||
|
for f in all_files:
|
||||||
|
for snap in parse_forge_jsonl(f):
|
||||||
|
gid = snap.get("game_id", "g0")
|
||||||
|
games_seen.setdefault(gid, []).append(snap)
|
||||||
|
|
||||||
|
for idx, (gid, snaps) in enumerate(games_seen.items()):
|
||||||
|
records.append(self._snaps_to_record(idx, gid, snaps, service))
|
||||||
|
if len(records) >= self.cfg.num_games:
|
||||||
|
break
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
def _java_results_to_records(self, java_results, service) -> List[GameRecord]:
|
||||||
|
"""Convert Java GameResult objects to GameRecord."""
|
||||||
|
records = []
|
||||||
|
for jr in java_results:
|
||||||
|
try:
|
||||||
|
won = getattr(jr, "winningSeat", 0) == 0 # seat 0 = policy
|
||||||
|
records.append(GameRecord(
|
||||||
|
game_index=getattr(jr, "gameIndex", len(records)),
|
||||||
|
won=won,
|
||||||
|
turns=getattr(jr, "turns", 0),
|
||||||
|
life_final_policy=getattr(jr, "lifeAtEnd", [40])[0] if hasattr(jr, "lifeAtEnd") else 40,
|
||||||
|
life_final_opponent=getattr(jr, "lifeAtEnd", [40, 40])[1] if hasattr(jr, "lifeAtEnd") else 40,
|
||||||
|
life_delta=0,
|
||||||
|
commander_damage_dealt=0,
|
||||||
|
illegal_actions=getattr(jr, "illegalActions", 0),
|
||||||
|
entropy_mean=0.0,
|
||||||
|
inference_ms_mean=0.0,
|
||||||
|
decision_steps=0,
|
||||||
|
))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("[Eval] Skipping malformed GameResult: %s", e)
|
||||||
|
return records
|
||||||
|
|
||||||
|
# ── Synthetic fallback ───────────────────
|
||||||
|
|
||||||
|
def _run_synthetic_eval(self, service) -> List[GameRecord]:
|
||||||
|
"""Run synthetic games using self_play engine (no Forge required).
|
||||||
|
|
||||||
|
Used when Forge JAR is missing or deck files aren't configured.
|
||||||
|
Metrics like illegal_actions and commander_damage are 0 in this mode.
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||||
|
|
||||||
|
records = []
|
||||||
|
try:
|
||||||
|
from ml.training.self_play import (
|
||||||
|
create_random_initial_state, apply_action, advance_turn,
|
||||||
|
encode_state_simple, HeuristicPolicy
|
||||||
|
)
|
||||||
|
from ml.config.scope import NUM_ACTIONS, IDX_TO_ACTION
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
except ImportError as e:
|
||||||
|
logger.error("[Eval] Cannot import self_play: %s", e)
|
||||||
|
return []
|
||||||
|
|
||||||
|
opponent = HeuristicPolicy()
|
||||||
|
agent_has_model = service is not None and service._loaded
|
||||||
|
|
||||||
|
for game_idx in range(self.cfg.num_games):
|
||||||
|
state = create_random_initial_state()
|
||||||
|
entropies, latencies = [], []
|
||||||
|
steps = 0
|
||||||
|
|
||||||
|
while not state.game_over and steps < 60:
|
||||||
|
sv = encode_state_simple(state, 0, "midrange")
|
||||||
|
t0 = time.time()
|
||||||
|
|
||||||
|
if agent_has_model:
|
||||||
|
snap = state.to_snapshot()
|
||||||
|
result = service.predict(
|
||||||
|
snap, playstyle="midrange",
|
||||||
|
greedy=self.cfg.greedy,
|
||||||
|
temperature=self.cfg.temperature,
|
||||||
|
)
|
||||||
|
action_idx = result.get("action_index", 7)
|
||||||
|
probs_map = result.get("probabilities", {})
|
||||||
|
probs = np.array(list(probs_map.values())) if probs_map else np.ones(NUM_ACTIONS) / NUM_ACTIONS
|
||||||
|
else:
|
||||||
|
action_idx = np.random.randint(NUM_ACTIONS)
|
||||||
|
probs = np.ones(NUM_ACTIONS) / NUM_ACTIONS
|
||||||
|
|
||||||
|
latencies.append((time.time() - t0) * 1000)
|
||||||
|
p = np.clip(probs, 1e-9, 1.0)
|
||||||
|
p /= p.sum()
|
||||||
|
entropies.append(float(-np.sum(p * np.log(p))))
|
||||||
|
|
||||||
|
state = apply_action(state, action_idx, 0)
|
||||||
|
if not state.game_over:
|
||||||
|
opp_sv = encode_state_simple(state, 1, "midrange")
|
||||||
|
opp_a, _ = opponent.select_action(opp_sv)
|
||||||
|
state = apply_action(state, opp_a, 1)
|
||||||
|
if not state.game_over:
|
||||||
|
state = advance_turn(state)
|
||||||
|
steps += 1
|
||||||
|
|
||||||
|
won = state.winner == 0
|
||||||
|
lp = state.players[0].life_total
|
||||||
|
lo = state.players[1].life_total
|
||||||
|
records.append(GameRecord(
|
||||||
|
game_index=game_idx,
|
||||||
|
won=won,
|
||||||
|
turns=state.turn,
|
||||||
|
life_final_policy=lp,
|
||||||
|
life_final_opponent=lo,
|
||||||
|
life_delta=lp - lo,
|
||||||
|
commander_damage_dealt=0,
|
||||||
|
illegal_actions=0,
|
||||||
|
entropy_mean=float(np.mean(entropies)) if entropies else 0.0,
|
||||||
|
inference_ms_mean=float(np.mean(latencies)) if latencies else 0.0,
|
||||||
|
decision_steps=steps,
|
||||||
|
))
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
# ── JSONL snapshot → GameRecord ──────────
|
||||||
|
|
||||||
|
def _snaps_to_record(self, idx: int, gid: str, snaps: list, service) -> GameRecord:
|
||||||
|
entropies, latencies = [], []
|
||||||
|
won = False
|
||||||
|
turns = len(snaps)
|
||||||
|
cmd_dmg = 0
|
||||||
|
illegal = 0
|
||||||
|
lp, lo = 40, 40
|
||||||
|
|
||||||
|
for snap in snaps:
|
||||||
|
if "outcome" in snap and snap == snaps[-1]:
|
||||||
|
won = snap["outcome"].get("winner_seat", -1) == 0
|
||||||
|
lp = snap["outcome"].get("life_remaining", 40)
|
||||||
|
lo = snap["outcome"].get("opp_life_remaining", 0)
|
||||||
|
cmd_dmg = snap["outcome"].get("commander_damage_dealt", 0)
|
||||||
|
|
||||||
|
if service and service._loaded and "state_vector" in snap:
|
||||||
|
t0 = time.time()
|
||||||
|
result = service.predict(snap, playstyle="midrange", greedy=self.cfg.greedy)
|
||||||
|
latencies.append((time.time() - t0) * 1000)
|
||||||
|
probs_map = result.get("probabilities", {})
|
||||||
|
if probs_map:
|
||||||
|
p = np.clip(list(probs_map.values()), 1e-9, 1.0)
|
||||||
|
p /= p.sum()
|
||||||
|
entropies.append(float(-np.sum(p * np.log(p))))
|
||||||
|
|
||||||
|
return GameRecord(
|
||||||
|
game_index=idx,
|
||||||
|
won=won,
|
||||||
|
turns=turns,
|
||||||
|
life_final_policy=lp,
|
||||||
|
life_final_opponent=lo,
|
||||||
|
life_delta=lp - lo,
|
||||||
|
commander_damage_dealt=cmd_dmg,
|
||||||
|
illegal_actions=illegal,
|
||||||
|
entropy_mean=float(np.mean(entropies)) if entropies else 0.0,
|
||||||
|
inference_ms_mean=float(np.mean(latencies)) if latencies else 0.0,
|
||||||
|
decision_steps=len(snaps),
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Summary builder ──────────────────────
|
||||||
|
|
||||||
|
def _build_summary(self, elapsed: float) -> EvalSummary:
|
||||||
|
n = len(self._records)
|
||||||
|
if n == 0:
|
||||||
|
return EvalSummary(
|
||||||
|
run_id=self.cfg.run_id,
|
||||||
|
checkpoint_path=self.cfg.checkpoint_path,
|
||||||
|
num_games=0, wins=0, losses=0,
|
||||||
|
win_rate=0.0, win_rate_ci95_low=0.0, win_rate_ci95_high=0.0,
|
||||||
|
avg_turns=0.0, avg_life_delta=0.0, avg_commander_damage=0.0,
|
||||||
|
avg_illegal_actions=0.0, avg_entropy=0.0, avg_inference_ms=0.0,
|
||||||
|
total_decision_steps=0, elapsed_wall_s=elapsed, games_per_sec=0.0,
|
||||||
|
error_count=0, result_file=self._result_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
wins = sum(1 for r in self._records if r.won)
|
||||||
|
losses = n - wins
|
||||||
|
wr = wins / n
|
||||||
|
|
||||||
|
# Wilson score 95% CI
|
||||||
|
z = 1.96
|
||||||
|
denom = 1 + z**2 / n
|
||||||
|
centre = (wr + z**2 / (2 * n)) / denom
|
||||||
|
margin = (z * (wr * (1 - wr) / n + z**2 / (4 * n**2)) ** 0.5) / denom
|
||||||
|
ci_lo = max(0.0, centre - margin)
|
||||||
|
ci_hi = min(1.0, centre + margin)
|
||||||
|
|
||||||
|
errors = sum(1 for r in self._records if r.error)
|
||||||
|
|
||||||
|
return EvalSummary(
|
||||||
|
run_id=self.cfg.run_id,
|
||||||
|
checkpoint_path=self.cfg.checkpoint_path,
|
||||||
|
num_games=n,
|
||||||
|
wins=wins,
|
||||||
|
losses=losses,
|
||||||
|
win_rate=wr,
|
||||||
|
win_rate_ci95_low=ci_lo,
|
||||||
|
win_rate_ci95_high=ci_hi,
|
||||||
|
avg_turns=float(np.mean([r.turns for r in self._records])),
|
||||||
|
avg_life_delta=float(np.mean([r.life_delta for r in self._records])),
|
||||||
|
avg_commander_damage=float(np.mean([r.commander_damage_dealt for r in self._records])),
|
||||||
|
avg_illegal_actions=float(np.mean([r.illegal_actions for r in self._records])),
|
||||||
|
avg_entropy=float(np.mean([r.entropy_mean for r in self._records])),
|
||||||
|
avg_inference_ms=float(np.mean([r.inference_ms_mean for r in self._records])),
|
||||||
|
total_decision_steps=sum(r.decision_steps for r in self._records),
|
||||||
|
elapsed_wall_s=elapsed,
|
||||||
|
games_per_sec=n / max(elapsed, 0.001),
|
||||||
|
error_count=errors,
|
||||||
|
result_file=self._result_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Result writer ────────────────────────
|
||||||
|
|
||||||
|
def _write_results(self, summary: EvalSummary) -> None:
|
||||||
|
payload = {
|
||||||
|
"summary": asdict(summary),
|
||||||
|
"games": [asdict(r) for r in self._records],
|
||||||
|
}
|
||||||
|
os.makedirs(self.cfg.results_dir, exist_ok=True)
|
||||||
|
with open(self._result_path, "w") as f:
|
||||||
|
json.dump(payload, f, indent=2)
|
||||||
|
logger.info("[Eval %s] Results written to %s", self.cfg.run_id, self._result_path)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
with open('ml/eval/forge_evaluator.py', 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Fix 1: >= 2 to >= 3
|
||||||
|
content = content.replace('deck_files) >= 2', 'deck_files) >= 3')
|
||||||
|
|
||||||
|
# Fix 2: replace call to _run_forge_via_subprocess with _parse_batch_json
|
||||||
|
content = content.replace(
|
||||||
|
'return self._run_forge_via_subprocess(service)',
|
||||||
|
'return self._parse_batch_json(service)'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fix 3: Insert _parse_batch_json method before _run_forge_via_subprocess
|
||||||
|
new_method = '''
|
||||||
|
def _parse_batch_json(self, service) -> List[GameRecord]:
|
||||||
|
"""Parse the batch JSON output from LabCli Java subprocess."""
|
||||||
|
batch_path = os.path.join(
|
||||||
|
self.cfg.results_dir, f"eval-{self.cfg.run_id}-batch.json"
|
||||||
|
)
|
||||||
|
if not Path(batch_path).exists():
|
||||||
|
logger.warning("[Eval] Batch JSON not found at %s -- falling back to synthetic", batch_path)
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
with open(batch_path) as f:
|
||||||
|
batch = json.load(f)
|
||||||
|
games_list = batch.get("games", [])
|
||||||
|
if not games_list:
|
||||||
|
logger.warning("[Eval] No games in batch JSON -- falling back to synthetic")
|
||||||
|
return self._run_synthetic_eval(service)
|
||||||
|
records = []
|
||||||
|
for idx, g in enumerate(games_list):
|
||||||
|
won = g.get("winningSeat", -1) == 0
|
||||||
|
turns = g.get("totalTurns", 0)
|
||||||
|
pr = g.get("playerResults", [])
|
||||||
|
lp = pr[0].get("finalLife", 40) if len(pr) > 0 else 40
|
||||||
|
lo = pr[1].get("finalLife", 40) if len(pr) > 1 else 40
|
||||||
|
records.append(GameRecord(
|
||||||
|
game_index=idx,
|
||||||
|
won=won,
|
||||||
|
turns=turns,
|
||||||
|
life_final_policy=lp,
|
||||||
|
life_final_opponent=lo,
|
||||||
|
life_delta=lp - lo,
|
||||||
|
commander_damage_dealt=0,
|
||||||
|
illegal_actions=0,
|
||||||
|
entropy_mean=0.0,
|
||||||
|
inference_ms_mean=0.0,
|
||||||
|
decision_steps=turns,
|
||||||
|
))
|
||||||
|
if len(records) >= self.cfg.num_games:
|
||||||
|
break
|
||||||
|
logger.info("[Eval %s] Parsed %d games from batch JSON", self.cfg.run_id, len(records))
|
||||||
|
return records
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
' def _run_forge_via_subprocess(',
|
||||||
|
new_method + ' def _run_forge_via_subprocess('
|
||||||
|
)
|
||||||
|
|
||||||
|
with open('ml/eval/forge_evaluator.py', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
import py_compile
|
||||||
|
py_compile.compile('ml/eval/forge_evaluator.py', doraise=True)
|
||||||
|
print('Syntax OK - patch applied')
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Phase 1 patch: no-op mana pool display functions in lan-client/ui.js
|
||||||
|
Run from anywhere — edit UI_JS_PATH to point at your file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
UI_JS_PATH = r"D:\ForgeCommander\commander-ai-lab\lan-client\ui.js"
|
||||||
|
|
||||||
|
with open(UI_JS_PATH, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
original_size = len(content)
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
# ── Change 1: no-op renderManaPool ───────────────────────────────────────────
|
||||||
|
old1 = re.search(
|
||||||
|
r'function renderManaPool\(playerIdx\) \{[^}]*?display\.style\.display[^}]*?\}',
|
||||||
|
content, re.DOTALL
|
||||||
|
)
|
||||||
|
if old1:
|
||||||
|
content = content[:old1.start()] + \
|
||||||
|
"function renderManaPool(playerIdx) {\n // mana pool display removed (Phase 1)\n}" + \
|
||||||
|
content[old1.end():]
|
||||||
|
changes.append("renderManaPool -> stub")
|
||||||
|
else:
|
||||||
|
print("WARNING: renderManaPool pattern not matched — skipping")
|
||||||
|
|
||||||
|
# ── Change 2: no-op showManaPool ─────────────────────────────────────────────
|
||||||
|
old2 = re.search(
|
||||||
|
r'function showManaPool\(\) \{.*?\}',
|
||||||
|
content, re.DOTALL
|
||||||
|
)
|
||||||
|
if old2:
|
||||||
|
content = content[:old2.start()] + \
|
||||||
|
"function showManaPool() {\n // mana pool display removed (Phase 1)\n}" + \
|
||||||
|
content[old2.end():]
|
||||||
|
changes.append("showManaPool -> stub")
|
||||||
|
else:
|
||||||
|
print("WARNING: showManaPool pattern not matched — skipping")
|
||||||
|
|
||||||
|
# ── Change 3: remove Mana Auto-Clear row from renderSettingsPanel ─────────────
|
||||||
|
mana_row = (
|
||||||
|
" +\n '<div class=\"settings-row\"><span>Mana Auto-Clear</span>'"
|
||||||
|
" + renderToggle('manaAutoClear', gameState.manaAutoClear) + '</div>';"
|
||||||
|
)
|
||||||
|
if mana_row in content:
|
||||||
|
content = content.replace(mana_row, ";", 1)
|
||||||
|
changes.append("Mana Auto-Clear row removed from renderSettingsPanel")
|
||||||
|
else:
|
||||||
|
print("WARNING: Mana Auto-Clear row not found — skipping")
|
||||||
|
|
||||||
|
# ── Write ─────────────────────────────────────────────────────────────────────
|
||||||
|
if changes:
|
||||||
|
with open(UI_JS_PATH, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
print(f"Done. {original_size} -> {len(content)} chars ({len(content)-original_size:+d})")
|
||||||
|
for c in changes:
|
||||||
|
print(f" ✓ {c}")
|
||||||
|
else:
|
||||||
|
print("No changes applied.")
|
||||||
@@ -0,0 +1,597 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-01a993df.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-02ce51bc.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-050da93a.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-07325fb5.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-0791d58e.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-083a55e5.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-09f6ab6b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-0d39aa71.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-0dadbf18.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-12802330.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-186b4c2d.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-19f0c0c3.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-1a0a0840.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-1d001e22.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-1df7d6d8.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-220e4942.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-26f83797.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-291a225a.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-309b1008.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-31557831.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-37d57d02.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-391e4632.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-39ed0be8.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3bb88339.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3c98f83b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3de43fb6.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3e0639f7.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3ea0bfb2.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3f0dc5c4.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-3f2f9f04.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-425a6728.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-46517693.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-471701d5.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-4c709ff1.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-4f5be65a.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-501f6ef9.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-592d3d5a.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-644cc2f1.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-64759781.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-6575cbf7.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-682fa8d1.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-68352096.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-6c982613.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-6cdb4b9b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-6dee8fdf.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7018972e.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7088479f.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-74d0f773.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-76303df4.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-76c602e0.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-76edf7a3.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-77551df3.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-77ee4077.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-786ff020.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7d4ad84d.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7f74c9f3.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7fbdfb25.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-7fd1bee8.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-804d0dfe.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-805211db.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-812a5124.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-826a32fb.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-878d0278.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-8dd40e1b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-8fa57d21.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-8fe4d31c.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-91a3ac36.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-92418b72.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-96fb5377.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-9b0adb9d.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-9e3ca48f.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-9ee053b3.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-9ef2148f.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-a153e04e.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-a2676323.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-a374549a.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-a7feffa1.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-ab868d56.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-ab8d55a5.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-aee3677b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-af39ff37.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-b101fe7b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-bab7b2bf.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-bb3e9d83.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-bb9772ca.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-bf0f9371.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-c21b782b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-c3e05ffc.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-c83d2002.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-c879aeab.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-c9d696cc.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-ccc989e9.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-d09dea45.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-d262f1dc.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-d3d97788.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-d68ecb62.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-d79c87bc.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-dbf46854.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-dd5b74d0.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-e1e6c928.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-e1ff699f.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-e738a169.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-efd579c0.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-f06c0e00.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-f144217e.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-f1d0e453.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-f2bdceeb.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-f79c8493.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-fa8a574b.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-fae68ec0.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-sim-fee36e36.jsonl",
|
||||||
|
"weight": 1,
|
||||||
|
"source": "sim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-03449551-bbc.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-04380aa6-f62.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-23db752f-de5.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-310629be-8f7.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-63b719a8-1b0.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-7f50242f-986.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-a2f9bbae-f50.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "D:\\ForgeCommander\\commander-ai-lab\\results\\ml-decisions-ds-bd331a14-a89.jsonl",
|
||||||
|
"weight": 4,
|
||||||
|
"source": "ds"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in new issue
Block a user