Persist full GSI payloads to per-match JSONL for later analysis.
Default-on dump keeps every POST body under samples/raw/<matchid>/gsi.jsonl, with CLI overrides to disable when disk use is a concern. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+27
-1
@@ -6,6 +6,8 @@ Usage:
|
||||
python gsi_watch.py --once # capture+recognize right now, no GSI
|
||||
python gsi_watch.py --port 3223
|
||||
python gsi_watch.py --states HERO_SELECTION,STRATEGY_TIME
|
||||
python gsi_watch.py --dump-gsi # force full GSI JSONL logging
|
||||
python gsi_watch.py --no-dump-gsi # disable it
|
||||
|
||||
When the game enters hero selection the watcher follows the whole draft,
|
||||
polling the screen and logging each pick as it is revealed (All Pick reveals
|
||||
@@ -14,6 +16,9 @@ which lane role you queued for. The result lands in results/draft_<ts>.json.
|
||||
|
||||
If config.json has no calibrated slots yet the watcher runs in capture-only
|
||||
mode: it still saves frames to samples/raw/<matchid>/ so you can calibrate from them.
|
||||
|
||||
With gsi.dump_payloads (default on), every POST body is appended to
|
||||
samples/raw/<matchid>/gsi.jsonl for later analysis.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -25,7 +30,7 @@ from pathlib import Path
|
||||
|
||||
import mss
|
||||
|
||||
from capture import grab_frame, raw_dir_for_match, save_frame
|
||||
from capture import append_gsi_payload, grab_frame, raw_dir_for_match, save_frame
|
||||
from common import ROOT, load_config, load_template_library
|
||||
from draft_session import HERO_SELECTION, DraftSession, describe, gsi_slot, loc
|
||||
from recognize import recognize_image
|
||||
@@ -52,6 +57,7 @@ class Watcher:
|
||||
gsi = cfg.get("gsi", {})
|
||||
self.trigger_states = set(gsi.get("trigger_states", ["DOTA_GAMERULES_STATE_STRATEGY_TIME"]))
|
||||
self.interval = gsi.get("capture_interval", 1.0)
|
||||
self.dump_payloads = bool(gsi.get("dump_payloads", True))
|
||||
|
||||
self.library = load_template_library() if calibrated else []
|
||||
self.last_state: str | None = None
|
||||
@@ -59,7 +65,9 @@ class Watcher:
|
||||
self.handled_matches: set[str] = set()
|
||||
self.connected = False
|
||||
self.busy = threading.Lock()
|
||||
self.dump_lock = threading.Lock()
|
||||
self.self_info: dict = {}
|
||||
self._dump_announced: set[str] = set()
|
||||
|
||||
def on_payload(self, payload: dict) -> None:
|
||||
if not self.connected:
|
||||
@@ -79,6 +87,8 @@ class Watcher:
|
||||
state = (payload.get("map") or {}).get("game_state")
|
||||
match_id = str((payload.get("map") or {}).get("matchid") or "no-match")
|
||||
self.last_match_id = match_id
|
||||
if self.dump_payloads:
|
||||
self._dump_gsi(match_id, payload)
|
||||
if state is None:
|
||||
# main menu / no active match
|
||||
if self.last_state is not None:
|
||||
@@ -108,6 +118,17 @@ class Watcher:
|
||||
return
|
||||
threading.Thread(target=self.track, args=(match_id, state, key), daemon=True).start()
|
||||
|
||||
def _dump_gsi(self, match_id: str, payload: dict) -> None:
|
||||
"""Persist the full POST body; announce the path once per match folder."""
|
||||
try:
|
||||
with self.dump_lock:
|
||||
path = append_gsi_payload(match_id, payload)
|
||||
if match_id not in self._dump_announced:
|
||||
self._dump_announced.add(match_id)
|
||||
print(f"[gsi] dumping payloads -> {path}", flush=True)
|
||||
except OSError as e:
|
||||
print(f"[gsi] dump failed: {e}", flush=True)
|
||||
|
||||
def track(self, match_id: str, state: str, key: str) -> dict | None:
|
||||
"""Follow the draft from here to the end, recording every reveal."""
|
||||
if not self.busy.acquire(blocking=False):
|
||||
@@ -225,6 +246,10 @@ def main() -> None:
|
||||
print()
|
||||
|
||||
watcher = Watcher(cfg, calibrated, verbose)
|
||||
if "--dump-gsi" in args:
|
||||
watcher.dump_payloads = True
|
||||
if "--no-dump-gsi" in args:
|
||||
watcher.dump_payloads = False
|
||||
|
||||
if "--states" in args:
|
||||
names = args[args.index("--states") + 1].split(",")
|
||||
@@ -251,6 +276,7 @@ def main() -> None:
|
||||
)
|
||||
print(f"listening on http://127.0.0.1:{port}/ (Ctrl+C to stop)")
|
||||
print(f"trigger states: {', '.join(sorted(watcher.trigger_states))}")
|
||||
print(f"gsi dump : {'on -> samples/raw/<matchid>/gsi.jsonl' if watcher.dump_payloads else 'off'}")
|
||||
if calibrated:
|
||||
print(f"template library: {len(watcher.library)} entries")
|
||||
print("waiting for Dota 2 ... (needs -gamestateintegration launch option)")
|
||||
|
||||
Reference in New Issue
Block a user