主力批 7/8 真可玩(Opus 对抗审计 0 假绿)、压测 match3 ✅;失败收敛:3 键盘款(2048/Tetris/Asteroids)挂宿主缺 keydown 桥=唯一真·L0 门面缺口(已 spawn_task 交 L0)、multiball/flappy/愤怒小鸟=driver-coverage(非模型)。spec 预判 CCD/文本HUD/网格/match/手势缺口全推翻(便宜 flash 自带 swept CCD/自写网格文本)。
- driver 库 1→6 型(tap-targets/flap-to-gap/seek-x/tap-pairs/key-cycle/paddle-intercept)+ drag 输入事件 + key() Arrow 修
- M3 视觉 player 当生产过滤器实证(与门 8/9 一致、抓显性空心)→ 门(地板)+M3(主观过滤)+创始人(人锚)三层互补
- 短板量化报告定稿 + 两 skill 沉淀(game-e2e §8 driver库/门承重/键盘缺口 · cheap-model §9 模型能力画像)
- 2 个 ultracode 对抗 workflow(审计10+诊断7 agents)抓 saolei 弱断言、纠 flappy 归因(driver 非模型)、定位宿主键盘根因
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
4.1 KiB
Python
97 lines
4.1 KiB
Python
"""m3_player_test.py —— 用 MiniMax-M3(视觉)跑生产 player 判定的离线测试。
|
|
|
|
目的:验证「便宜视觉模型 M3」能否担当生产三层的 player 层(看首帧/玩后截图 + 九门数据判可玩/好玩),
|
|
与「确定性九门」「Opus 审计」三方对照。production-faithful:用 roles.player_system 同款人格 + 同款多模态消息,
|
|
不引 agentscope(只用 _client/openai 兼容网关)。读各游戏已存 evidence,不重开服务=便宜可复跑。
|
|
"""
|
|
import base64
|
|
import json
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent # wg1/gen-worker
|
|
sys.path.insert(0, str(ROOT))
|
|
from worker import _client, run # noqa: E402 (裸客户端 + GEN_DIR;均不引 agentscope)
|
|
from worker.agent_loop import roles # noqa: E402 (纯 prompt 字符串)
|
|
|
|
try:
|
|
from json_repair import repair_json
|
|
except ImportError:
|
|
repair_json = None
|
|
|
|
VIS_MODEL = "MiniMax-M3" # 用户指定:M3 有视觉能力,作生产 player 视觉位
|
|
PERSONA = "急性子休闲玩家,凭第一眼观感和反馈下判断"
|
|
GAMES = ["tictactoe", "saolei", "jianfeng", "breakout", "runner", "invaders", "doodle", "flappy", "tetris"]
|
|
|
|
|
|
def b64(p):
|
|
p = Path(p)
|
|
return "data:image/png;base64," + base64.b64encode(p.read_bytes()).decode() if p.exists() else None
|
|
|
|
|
|
def play_summary(v):
|
|
"""把九门 verdict 摘成给 player 的运行数据(同 studio._play_summary 口径)。"""
|
|
if not v:
|
|
return "(无 verdict:疑似装载即崩)"
|
|
g = v.get("guards") or {}
|
|
return "九门真玩取证:\n" + "\n".join(
|
|
"- %s: %s %s" % (k, "过" if x.get("pass") else "未过", json.dumps(x, ensure_ascii=False)[:140])
|
|
for k, x in g.items()
|
|
)
|
|
|
|
|
|
def parse(raw):
|
|
obj = None
|
|
if repair_json is not None:
|
|
try:
|
|
obj = repair_json(raw, return_objects=True)
|
|
except Exception:
|
|
obj = None
|
|
if not isinstance(obj, dict):
|
|
try:
|
|
obj = json.loads(raw)
|
|
except Exception:
|
|
obj = None
|
|
return obj if isinstance(obj, dict) else {"raw": (raw or "")[:160]}
|
|
|
|
|
|
def main():
|
|
client = _client.get_client()
|
|
out = []
|
|
for g in GAMES:
|
|
bp = ROOT / "briefs" / (g + ".json")
|
|
brief = json.loads(bp.read_text(encoding="utf-8"))["brief"] if bp.exists() else g
|
|
vp = run.GEN_DIR / g / "evidence" / "verdict.json"
|
|
v = json.loads(vp.read_text(encoding="utf-8")) if vp.exists() else None
|
|
gate = bool(v and v.get("pass"))
|
|
user_text = (f"游戏题面:\n{brief}\n\n{play_summary(v)}\n\n"
|
|
"请基于以上证据(及截图,若有)给出你的玩家评判。")
|
|
content = [{"type": "text", "text": user_text}]
|
|
ev = run.GEN_DIR / g / "evidence"
|
|
for fn in ("first-paint.png", "after-play.png"):
|
|
u = b64(ev / fn)
|
|
if u:
|
|
content.append({"type": "text", "text": "【%s】" % fn})
|
|
content.append({"type": "image_url", "image_url": {"url": u}})
|
|
msgs = [{"role": "system", "content": roles.player_system(PERSONA)},
|
|
{"role": "user", "content": content}]
|
|
t0 = time.perf_counter()
|
|
try:
|
|
r = client.chat.completions.create(model=VIS_MODEL, messages=msgs, max_tokens=900, temperature=0.3, stream=False)
|
|
j = parse(r.choices[0].message.content or "")
|
|
u = r.usage
|
|
row = {"game": g, "gate": "PASS" if gate else "FAIL",
|
|
"m3": {k: j.get(k) for k in ("completeness", "fun", "verdict", "note")},
|
|
"tok": [u.prompt_tokens, u.completion_tokens], "wall_s": round(time.perf_counter() - t0, 1)}
|
|
except Exception as e:
|
|
row = {"game": g, "gate": "PASS" if gate else "FAIL", "m3": {"error": str(e)[:200]}}
|
|
out.append(row)
|
|
print("%-11s gate=%-4s M3=%s" % (row["game"], row["gate"], json.dumps(row["m3"], ensure_ascii=False)[:220]))
|
|
(ROOT / "calibration" / "m3_test.json").write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
print("\nsaved → wg1/gen-worker/calibration/m3_test.json")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|