games-development-ai/wg1/gen-worker/post_fallback_callback.py
zizi 238ec2dbe8 test(aigc-3b-B): 视觉终局达成——真游戏真玩入 feed(亲眼验);一句话现生成腿缺口=tap类driver:none→L1
3b-B 重跑视觉终局(mini-desktop·证据 wg1/gen-worker/evidence-3bB-final/):

 视觉终局达成(亲眼复核截图):feed 里一款真渲+真玩有进展的井字棋(gameId=9306/versionId=93112)——
  空盘「轮到X」→ 落子填盘 X·O → 「X 胜!」+ 重新开始(latch 终态);feed 真玩门 10/11 过
  (no_demo_fallback/engine_rendered/lively_interactive 全 true,唯一 false=play_start 遥测非阻断)。
  经真 HMAC 回调写链落库(验签通过 engineBundleLen=210891→建版本→组包 checksum 自洽→翻 feed)。

走兜底路(诚实标注):主路一句话现生成两次均失败,故取已验证过九门样板 tictactoe(便宜模型 flash 真生成、
  verdict A-I 全 pass)的 engineBundle 经派发面真回调路落 feed,DB bundle 与样板 sha256 字节级一致。
  仍是便宜模型真生成的过门游戏,只是非本次现生成。

★ 主路两次 fresh 失败真因(非模型壁垒,铁证):design agent 在 dispatch 面 run_studio(play_spec={}) 下
  均给 driver:{type:none}→harness 盲打打不中目标→moves/score 恒 0→H_progress 挂+latch 不触发,repairs=5
  耗尽。游戏本体全对(gen9306/9307 verdict 8/9 门过,targetX/Y/radius·board·moves 导出正确,G_input/
  I_control/F_wiring 均过)。样板过门靠 lane brief 手供 play_spec.driver=tap-targets。
  → 蒸馏入 cheap-model §7:DESIGN_SYSTEM 须对离散点击类强制产 tap-targets driver(纯 L1 生成域,
  moat『一句话现生成』腿的真·临门一脚)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:48:50 +00:00

96 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""post_fallback_callback.py —— P3 W-G1 视觉终局·P2 兜底:直接 POST succeeded 回调(已验证样板 bundle
【缘由】主路两次一句话真生成(gen9306 whack / gen9307 tictactoe)均栽于 driver-coverage
(design agent 给 driver:none、harness 盲打打不中目标→moves/score 恒 0→H_progress 挂,8/9 门过,非模型壁垒)。
兜底取【已验证过九门】样板 tictactoe(verdict.json A-I 全 pass)的 bundle.iife.js 作 engineBundle,
经派发面【真回调写链】(handleCallback·HMAC 验签·与真生成同代码路)落库入 feed。
【与 worker service.py 的 post_callback 逐字节对齐】payload 用 json.dumps(ensure_ascii=False).encode("utf-8")
一次算定既签既发;HMAC-SHA256(secret, data) hex 小写置头 X-Callback-Signature;Content-Type 带 charset=utf-8。
这是【真回调路】——后端 CallbackSignatureVerifier 验签 → handleCallback 建版本/组包(含 manifest+checksum)/落包。
【用法mini-desktopcwd=repo 根)】
CALLBACK_SECRET=<与后端 AIGC_CALLBACK_SECRET 同值> \\
python3 wg1/gen-worker/post_fallback_callback.py \\
--bundle game-runtime/games/_wg1-gen/tictactoe/bundle.iife.js \\
--trace-id wg1-3bB-gen9306-fb-001 \\
--title "井字棋(兜底)" \\
--callback http://localhost:48080/admin-api/aigc/dify/callback-internal
"""
import argparse
import hashlib
import hmac
import json
import os
import sys
import urllib.error
import urllib.request
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--bundle", required=True, help="已验证样板 bundle.iife.js 路径")
ap.add_argument("--trace-id", required=True, help="须与 RUNNING 任务 trace_id 一致(handleCallback 据此定位)")
ap.add_argument("--title", default="井字棋(兜底)", help="meta 标题")
ap.add_argument("--template-id", default="generic")
ap.add_argument("--callback", default="http://localhost:48080/admin-api/aigc/dify/callback-internal")
args = ap.parse_args()
secret = os.environ.get("CALLBACK_SECRET", "")
if not secret:
print("❌ CALLBACK_SECRET 未设(须与后端 AIGC_CALLBACK_SECRET 逐字节一致)", file=sys.stderr)
sys.exit(2)
# 读已验证样板 bundle 全文(engineBundle 真值)
with open(args.bundle, "r", encoding="utf-8") as f:
bundle_text = f.read()
if "__GameBundle" not in bundle_text:
print(f"❌ bundle 缺 __GameBundle 全局名:{args.bundle}", file=sys.stderr)
sys.exit(3)
print(f"[fallback] 读 bundle {len(bundle_text)} 字符(含 __GameBundle ✓)← {args.bundle}")
# 组 DifyCallbackReqVO(succeeded 路,与 worker build_callback_payload 同形)
payload = {
"traceId": args.trace_id,
"status": "succeeded",
"templateId": args.template_id,
"gameConfig": {
"templateId": args.template_id,
"title": args.title[:20],
"theme": "generic",
"engineDriven": True,
},
"assets": [],
"engineBundle": bundle_text,
}
# 逐字节对齐铁律:一次算定既签既发
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
sig = hmac.new(secret.encode("utf-8"), data, hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json; charset=utf-8",
"tenant-id": "0",
"X-Callback-Signature": sig,
}
print(f"[fallback] HMAC 签名(前8)={sig[:8]}, payload 字节={len(data)}, POST → {args.callback}")
req = urllib.request.Request(args.callback, data=data, method="POST", headers=headers)
try:
with urllib.request.urlopen(req, timeout=60) as resp:
body = resp.read().decode("utf-8", "replace")
print(f"[fallback] 回调完成 HTTP={resp.status} body={body[:300]}")
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", "replace") if e.fp else ""
print(f"[fallback] 回调 HTTP={e.code} body={body[:300]}")
# 401/4xx 视为失败
if e.code >= 400:
sys.exit(4)
except Exception as e:
print(f"❌ 回调 POST 异常:{type(e).__name__}: {e}", file=sys.stderr)
sys.exit(5)
if __name__ == "__main__":
main()