fix: CAST_PATTERN and LAND_PLAY_PATTERN regex to match Forge verbose log prefixes

- CAST_PATTERN: Add optional (?:Add To Stack:\s*)? prefix to match Forge's verbose log format
- LAND_PLAY_PATTERN: Add optional (?:Land:\s*)? prefix to match Forge's verbose log format
- Add [PARSE] diagnostic logging after combat stats parsing to confirm spells/lands/cmdr stats per seat

Root cause: Forge verbose output prefixes cast lines with "Add To Stack: " and land lines with "Land: "
but the regexes expected Ai(N) at the start of the line, causing 0 matches for all combat stats.
This commit is contained in:
KoalaTrapLord authored and GitHub committed 2026-03-19 22:06:17 -04:00
1 parent c83f7e0141
commit 030bdf2b81
1 file changed
+9 -2
@@ -117,12 +117,12 @@ public class BatchRunner {
// Format: "Add To Stack: Ai(N)-DeckName cast CardName"
// Note: Forge uses past tense "cast" not "casts"
private static final Pattern CAST_PATTERN =
Pattern.compile("Ai\\((\\d+)\\)-[^\\s].*?\\s+cast\\s+(.+?)(?:\\s+targeting.*)?$", Pattern.CASE_INSENSITIVE);
Pattern.compile("(?:Add To Stack:\\s*)?Ai\\((\\d+)\\)-[^\\s].*?\\s+cast\\s+(.+?)(?:\\s+targeting.*)?$", Pattern.CASE_INSENSITIVE);
// "Land: Ai(1)-Name played LandName (SET)" or "Ai(1)-Name plays LandName."
// Forge verbose log uses "played" (past tense) with set info in parens, no trailing period
private static final Pattern LAND_PLAY_PATTERN =
Pattern.compile("Ai\\((\\d+)\\)-[^\\s].*?\\s+play(?:s|ed)\\s+(.+?)(?:\\s+\\(\\d+\\))?(?:\\.|$)", Pattern.CASE_INSENSITIVE);
Pattern.compile("(?:Land:\\s*)?Ai\\((\\d+)\\)-[^\\s].*?\\s+play(?:s|ed)\\s+(.+?)(?:\\s+\\(\\d+\\))?(?:\\.|$)", Pattern.CASE_INSENSITIVE);
// "Zone Change: CardName (N) was put into Graveyard from Battlefield." — creature/permanent death
// Also: "is destroyed" / "dies" from older format
@@ -879,6 +879,13 @@ public class BatchRunner {
// Leave at 0 rather than fabricating data
}
// ── Diagnostic: log parsed combat stats ──────────────────────
for (int seat = 0; seat < result.playerResults.size(); seat++) {
PlayerResult pr = result.playerResults.get(seat);
System.out.printf("[PARSE] Game %d Seat %d: spells=%d, lands=%d, cmdrCasts=%d, cmdrDmg=%d, life=%d%n",
gameIndex, seat, pr.spellsCast, pr.landsPlayed, pr.commanderCasts, pr.commanderDamageDealt, pr.finalLife);
}
// ── Build per-card stats from verbose output (for coach analytics) ──
if (!quiet) {
try {