玩法说明

这是写脚本驱动角色对战的竞技场。选择适合你的方式,从第一份战术脚本开始。

人类玩家指南 · 推荐新手

  1. 1选择角色

    从你喜欢的角色开始,先熟悉他的招式。

  2. 2让 AI 帮你写脚本

    说清楚你的战术想法,让 AI 生成第一版。

  3. 3在训练赛验证

    观察真实对局,再按结果调整规则。

  4. 4过关斩将并挑战天梯

    逐层挑战,带着验证过的脚本正式参赛。

脚本规则示例(通俗版)

如果(IF) 对手距离小于 1.5 身位,而且正在蓄力

那么(THEN) 使用突进技打断,并接重击

否则(ELSE) 保持中距离移动,寻找机会

开发者接入高级 Agent API / Gym / MCP / 观测与动作参考

自循环闭环 · self-loop

  1. 1拿说明书GET /api/arena/script-spec
  2. 2写 playbookrules[] 规则集 · 当[观测]→做[动作]
  3. 3校验(不跑)POST /api/scripts/train
  4. 4跑对局 · 多 seedPOST /gym/sessions/<id>/run
  5. 5读复盘per-seed 战果 + per-rule 裁决
  6. 6改 → 回到 3过关则自动推进下一关

循环条件:没赢够当前关 → 读复盘(哪条规则赢了 / 哪条被高优先级饿死 / 哪条是从未命中的死规则)→ 改脚本回到第 3 步。赢够(默认每关 3 seed 里赢 2 场)→ 自动推进下一关。全关通过 = 通关。

两层接口 · 诚实分清

● 已托管 · 公网直连

A 层:说明书 + 校验

跑在 www.ai-fight.top 上,无需鉴权,普通 HTTPS 直接调。一个 agent 不必在本地起任何东西,就能拿到完整说明书、角色起手骨架,并把写好的 playbook 提交校验、拿到逐条错误。

  • 拿规格:/api/arena/script-spec
  • 拿示例:/api/arena/playbook-examples/<角色>
  • 校验:POST /api/scripts/train
● 本地自跑 · runner 需 X-Gym-Key

B 层:真跑对局 + 复盘 + 推进

真打一场拿胜负和复盘,走 agent_gym 训练场 API(默认 :8770)。它在本地起、自己连游戏服跑对局,返回每场战果 + 裁剪复盘,赢够自动推进。也有 MCP 封装和参考 driver。

  • 开 session:POST /gym/sessions
  • 当前关情报:GET /gym/sessions/<id>/level
  • 跑 + 推进:POST /gym/sessions/<id>/run

为什么"跑对局"不是公网托管端点? AFA 的一场对局是客户端 WASM 引擎在浏览器里逐帧算出来的,不是一次服务端 HTTP 请求。agent_gym 用 playwright 无头浏览器把这个 WASM 包成可调用的 runner,所以它是本地自跑的,而不在生产托管。

是否把 agent_gym API 部署到生产做"托管 run 端点"是单列的后续评估(涉及无头浏览器资源/并发/限流),当前 MVP 不默认做——闭环的"跑"这一步先靠本地 runner。

端点清单 · endpoints

● 已托管 www.ai-fight.top · 公网直连、无鉴权
方法路径作用
GET/api/arena/script-spec机器可读脚本说明书(观测 / 动作 / 破压制 / 完整模板)。
GET/api/arena/project-doc项目说明:产品循环、写作契约、安全边界。
GET/api/arena/playbook-examples角色示例索引。
GET/api/arena/playbook-examples/<角色>单角色起手骨架 playbook(照着改)。
POST/api/scripts/train校验一份 {"script": ...},返回 ok + 逐条 errors
● 本地 gym :8770 · 全部需 header X-Gym-Key
方法路径作用
GET/gym/spec说明书(转发 A 层 script-spec)。
GET/gym/levels闯关阶梯(每关 CPU 等级 / 对手 / 要赢几场)。这是关卡的权威源。
GET/gym/example/<角色>角色起手骨架。
POST/gym/sessions{player_character?} → 开 session,返回 session_id + 第 1 关情报。
GET/gym/sessions/<id>看进度(到第几关 / 状态 active|cleared)。
GET/gym/sessions/<id>/level当前关情报(CPU / 对手 / wins_required / seeds)。
POST/gym/validate{script} → 只校验、不跑。
POST/gym/sessions/<id>/run{script} → 当前关跑多 seed,返回每场胜负 + 复盘,过关自动推进。

直接复制 · curl / Python / MCP

A 层 · 公网直连(无需本地起服务)

curl · 拉说明书
curl -sS https://www.ai-fight.top/api/arena/script-spec
curl · 拿角色起手骨架
curl -sS https://www.ai-fight.top/api/arena/playbook-examples/kyo
curl · 校验一份 playbook
curl -sS https://www.ai-fight.top/api/scripts/train \
  -H 'content-type: application/json' \
  -d '{"script": {"schema": "ai-fight-arena.public_ai_playbook.v3",
                  "contestant": {"character": "kyo"},
                  "rules": []}}'
# → {"ok": false, "errors": [ ... 逐条告诉你缺什么/写错什么 ... ]}
# 把上面的 script 换成你照 examples 改出来的完整 playbook,errors 为空即过校验。

B 层 · 本地 gym(真跑对局)

先在项目根起两个服务:游戏资产服(:8766)+ 训练场 API(:8770);后者启动时设 AFA_GYM_KEY

bash · 起服务
# 1) 游戏资产 / 校验服(cwd 必须是项目根)
python3.13 ~/afa-backup/20260612-p0-script-guard/harness/afa_1b_server.py   # :8766
# 2) 训练场 API(另开一个终端)
AFA_GYM_KEY=your-gym-key python3.13 -m arena.agent_gym.api                  # :8770
curl · 开 session 并跑一关
# 开 session(选你的角色)
curl -sS http://127.0.0.1:8770/gym/sessions -X POST \
  -H 'X-Gym-Key: your-gym-key' -H 'content-type: application/json' \
  -d '{"player_character": "kyo"}'
# → {"session_id": "...", "level": {"cpu": 2, "wins_required": 2, "seeds": 3, ...}}

# 在当前关跑你的脚本(多 seed,返回胜负 + 复盘,过关自动推进)
curl -sS http://127.0.0.1:8770/gym/sessions/SESSION_ID/run -X POST \
  -H 'X-Gym-Key: your-gym-key' -H 'content-type: application/json' \
  -d '{"script": { ...你的 playbook... }}'
python · 最小自循环客户端
import json, urllib.request

BASE = "http://127.0.0.1:8770"
KEY  = "your-gym-key"            # = 启动 gym 时的 AFA_GYM_KEY

def call(method, path, payload=None):
    data = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(BASE + path, data=data, method=method,
        headers={"content-type": "application/json", "X-Gym-Key": KEY})
    with urllib.request.urlopen(req, timeout=900) as r:
        return json.loads(r.read())

spec = call("GET", "/gym/spec")                          # 1. 拿说明书
sid  = call("POST", "/gym/sessions", {"player_character": "kyo"})["session_id"]

while True:
    level = call("GET", f"/gym/sessions/{sid}/level")    # 当前关情报
    if level.get("cleared"):
        print("全关通过"); break
    script = write_playbook(spec, level)                 # 2. 你的 AI 写/改脚本
    out = call("POST", f"/gym/sessions/{sid}/run", {"script": script})   # 3-4 校验+跑
    if out.get("stage") == "validate":                   # 校验没过
        feedback = out["errors"]                          # → 读 errors 改 script
        continue
    print(f"L{out['level']} CPU{out['cpu']}  胜 {out['wins']}/{out['wins_required']}")
    feedback = out["matches"]                             # 5. 每场含裁剪复盘
    # 没过:据 feedback 改 write_playbook 的产出;过了 out['session'] 已推进下一关

注:write_playbook(spec, level) 就是你的 AI 干的活——读说明书与关卡情报、产出一份 JSON playbook。换成真 LLM 只改这个函数,其余链路一字不动。

MCP · 即插即用

把训练场 API 包成 MCP 工具,配进 Claude Code / Desktop 的 mcpServers 即可。工具:gym_get_spec · gym_list_levels · gym_create_session · gym_get_session · gym_get_level · gym_validate_script · gym_run_level

json · mcpServers 配置
{
  "mcpServers": {
    "afa-gym": {
      "command": "python3.13",
      "args": ["-m", "arena.agent_gym.mcp_server"],
      "env": {
        "AFA_GYM_BASE": "http://127.0.0.1:8770",
        "AFA_GYM_KEY": "your-gym-key"
      }
    }
  }
}

参考 driver · 不想自己写循环就用它

仓库自带一个走 HTTP 的自循环 driver(模拟外部 AI),三种写手共用同一 write(...) 接口:机器启发式(heuristic,无需 LLM,先验证机制)、已登录的 claude_cliopenai_compat 兼容端点。

bash · 跑 driver
# 机器写手(无需 LLM,证明闭环机制)
python3.13 -m arena.agent_gym.driver --writer heuristic --player kyo
# 真 LLM:已登录的 claude CLI
python3.13 -m arena.agent_gym.driver --writer claude_cli --model <model>
# 真 LLM:OpenAI 兼容端点(配 base_url / key / model)
python3.13 -m arena.agent_gym.driver --writer openai_compat
# 续跑已有 session(断点续:从当前关继续,不重跑已过的关)
python3.13 -m arena.agent_gym.driver --writer openai_compat --session <session_id>

写脚本参考 · 观测 / 动作 / 破压制

脚本是什么

一份 playbook = rules[],每条 {priority, when:[观测条件], do:{动作}}:"当观测满足 → 做动作"。引擎每隔若干帧评估一次(进攻起手),防御 / 连招走逐帧快路径。脚本只能用下面列出的公开观测与动作——这是合规闸:脚本是"稳定复现的巅峰人类操作",不是开图作弊。完整结构、连招路线、能量计划见 说明书

观测 · 脚本能"看到"什么

蓝框标 裸写 的是布尔观测,条件里直接写名字(如 self_control);写成 self_control = 1 引擎恒判 false、校验器会拦。其余为数值观测,可用比较(如 distance_x < 80)。

自己 · 自知全留13

self_airborne裸写self_attacking裸写self_combo_hitsself_control裸写self_cornered裸写self_grounded裸写self_guard裸写self_guard_heightself_hitstun裸写self_knockdown裸写self_lifeself_powerself_state_no

看自己的状态号、血气、连段命中、是否硬直 / 防御 / 倒地 / 空中 / 贴墙。

对手 · 只读看得见的16

opponent_airborne裸写opponent_approaching裸写opponent_attack_heightopponent_attacking裸写opponent_combo_hitsopponent_cornered裸写opponent_grounded裸写opponent_guard裸写opponent_hitstun裸写opponent_knockdown裸写opponent_lifeopponent_moveopponent_move_idopponent_movementopponent_poweropponent_recovering裸写

对手姿态(出招 / 被打 / 防御 / 倒地 / 空中)、血气、距离趋势、出哪招——肉眼能从屏幕感知的,不含精确剩余帧或内部状态号。

关系 · 全局6

distance_xdistance_x_deltadistance_ylast_hit_confirmed裸写scoretime_remaining_frames

横纵距离、剩余时间、比分、上一击是否确认命中。

动作 · do.kind

kind作用
change_state出招(到招式状态号)。
guard脚本防御,frames 1-120(缺省 30)。
wait立回:本次刻意不动,钓招 / 控节奏。
move走位,dir=forward/backward,frames
hit_confirms
combos
命中确认连段 / 成套连段,引擎自动执行(起手真命中才续,被防/被打/超时自动断)。

破压制 · 被压时的脱身

change_state 到下列固定状态号。引擎只在被压制(self_guard 防御硬直)或倒地(self_knockdown)且非受击硬直的窗口放行;能自由行动时它们退化成普通移动、无脱身价值。

状态动作说明
700dodge 闪避带无敌的脱身闪避
710roll 前滚向前受身翻滚脱离压制
715roll 后滚向后受身翻滚(不耗气)
905max mode烧 1 气、启动无敌反压制(非后滚)
5035受身起身倒地后 fall recovery 受身

门控铁律:一条破压制规则的 when 必须含 self_guardself_knockdown,否则"校验绿、运行时归零"——这是脚本强弱常见的分水岭。

关卡阶梯(示例):5 关纯 CPU 等级 [2, 3, 4, 6, 7],L1=CPU2 入门 .. L5=CPU7 终极;每关跑 3 个 seed、赢 2 场算过。运行时以 GET /gym/levels 返回为准。 ·  可选角色:kyo / benimaru / joe / shingo / kim / king(镜像对战,难度纯由 CPU 等级决定)。
沙箱与边界:gym 进度走独立 sqlite,绝不碰生产库、绝不进真天梯;对局单线程串行(对局重 = 天然限流)。 ·  模块素材:arena/agent_gym/(README / api.py / mcp_server.py / driver.py)。