mock draft 要真实最后都得回答同一个问题它是按照你自己的 league 规则跑出来的还是套了一个通用默认流程在带 skill 机制的开发场景里这类需求越来越多开发者把规则说明、数据文件和处理脚本打包成一个轻量 skill file让助手或本地脚本读取后生成贴近实际业务的 mock 结果。看 Hacker News 上“Realer Mock Draft”这类展示项目时最先被问到的往往不是“它能模拟几轮”而是“它怎么知道我的 league 长什么样”。下面这套做法会沿着结构到排错的思路完整拆解一个可以长期维护的 league mock skill先理解为什么要用 skill file再准备 league 配置和球员池两份数据文件然后搭出 SKILL.md、Node 脚本、校验脚本的最小骨架最后重点处理最容易卡住的两个实际问题独立 JS 脚本的 export default 怎么写以及在线 mock 平台不可用时如何用本地 fixture 兜底。整个过程不需要复杂的工程框架Node 18 以上环境就可以直接跑通。先把核心认知理清mock draft 不是随机抽签而是一套输入、决策、输出和校验机制。1. 为什么需要 skill file把“模拟选秀”焊死在你的 league 上1.1 通用 mock 与基于 league 的 mock 差异很大很多在线模拟器打开就能用但它背后是一套写死的默认 league。默认可能是 12 支队伍、15 轮选秀、标准阵容槽位、固定计分。真实 league 通常比这复杂队伍数量可能是 10 支、12 支也可能是 14 支。选秀轮次需要匹配 roster 槽位而不是随便设一个整数。计分可能是 PPR、半 PPR、标准分或自定义的上场人数限制。部分球员已经作为 keeper 被锁定不应进入可选中池。每个 manager 的 pick 顺序、蛇形方向、交易后的轮次错位都可能不同。这些信息如果只写在自然语言里靠人类逐条执行容易漏如果只写进可视化界面的下拉框里团队每次要花时间维护配置项。skill file 解决的是同一个问题把规则、数据和算法集中到一个可复用单元里让不同 league 可以通过替换数据文件和参数来使用同一套 mock 逻辑。下表能看出两类方案的关键差异对比维度通用 mock 模拟器基于 skill file 的 league mock队伍数量通常写死默认值从 league.json 读取轮次数量脚本内置固定值根据 roster 需求计算或配置阵容槽位默认 QB/RB/WR/TE/FLEX按 roster_slots 驱动计分权重界面提供少量模式league.json 自由定义球员池平台维护的大名单替换 JSON 即可适配不同联赛keeper 锁定状态需要单独管理写入球员字段即可排除本地离线运行依赖在线服务数据文件加 Node 脚本即可运行1.2 skill file 在技术上是什么skill file 并不是一门新技术它只是把“技能描述 数据 处理脚本”放在一个约定目录里。常见结构类似skill-name/ ├─ SKILL.md ├─ data/ └─ scripts/其中 SKILL.md 描述这个技能什么时候启用、需要哪些输入、处理步骤是什么、输出格式是什么。真正负责计算和处理结果的是 scripts 目录里的脚本。以 Agent Skills 这类形态为例运行器通过读取 SKILL.md 决定是否启用该技能启用后模型或脚本引擎再调用 Node、Python 等本地程序完成计算而不是在提示词里硬推理。这样设计的核心原因很直接语言模型擅长理解意图但不擅长稳定执行重复的中长尾规则。脚本擅长计算、遍历、排序、校验但不擅长理解“用户说的第 1 轮第 7 顺位是什么意思”。把两者结合SKILL.md 负责理解意图脚本负责稳定输出。1.3 容易误解的地方误区一skill file 是一份给人看的文档。现实中它是运行时读取的程序单元SKILL.md 里写的 description 会直接决定某个助手能否在合适时刻想起这个技能。误区二mock 既然是模拟就允许随机。实际上没有可控随机种子和固定规则哈希的 mock很难排查问题。你无法向同事解释为什么两次生成结果不同是因为真的模拟了“策略变化”还是脚本状态污染。误区三把算法写在提示词里也没问题。简单的五个球员可以但一旦 league 有 300 人池、15 轮、蛇形顺位、keeper 锁定提示词里的描述很容易出现矛盾而脚本不会。2. 先定输入模型league 配置和球员池怎么组织2.1 league.json 是 mock 的第一输入源先创建一份最小但完整的 league 配置示例。这份文件回答的问题是这次 mock 要模拟什么环境。{ league_name: demo-league, league_id: demo-2025, team_count: 12, snake: true, rounds: 15, roster_slots: { QB: 1, RB: 2, WR: 2, TE: 1, FLEX: 1, BENCH: 8 }, scoring: { pass_td: 4, rush_rec_td: 6, interception: -2, reception_points: 1 } }字段含义如下league_id用于区分不同 league 的唯一标识后续输出文件建议带上它。team_count参与模拟的经理人数直接决定每一轮生成多少个 pick。snake是否采用蛇形选秀。设为 true 时第 2 轮顺序会反过来。rounds总共进行多少轮。生产环境建议根据 roster_slots 的总需求计算而不是手写一个容易和阵容不匹配的数字。roster_slots每个位置槽位的数量。示例配置中每队需要 1 个 QB、2 个 RB、2 个 WR、1 个 TE、1 个 FLEX、8 个替补。scoring计分权重。同一个球员在不同 ppr 设置下得分不同排序结果也会不同。这里有一个值得注意的设计选择rounds和roster_slots应该保持一致。比如阵容槽位加起来是 15那么一轮 mock 应该也是 15 轮否则会出现多选或漏选。2.2 player_pool.json 保存球员池快照球员池不需要在每次 mock 时动态抓取。更稳妥的做法是先拉取一份快照写入 data 目录让 mock 过程可以重复执行。示例结构如下{ snapshot_time: 2025-05-01T08:00:00Z, players: [ { id: RB-001, name: Demo RB 1, positions: [RB], adp: 4, bye: 10, keeper_team_index: null, projections: { rush_yards: 1150, rush_td: 9, receiving_yards: 220, receptions: 45 } }, { id: WR-001, name: Demo WR 1, positions: [WR], adp: 8, bye: 11, keeper_team_index: null, projections: { receiving_yards: 1280, rush_rec_td: 7, receptions: 95 } } ] }说明几个字段id球员唯一标识mock 输出里只存这个 id 会比反复拼接名字更可靠。positions球员可打的位置。双位置球员写成数组如[RB, WR]。adp平均被选中位置。主要用于同分排序也可以作为纯 mock 时的默认策略信号。keeper_team_index如果某球员已经被某队锁定就不会出现在可选中候选池。projections预测数据由积分函数计算得到综合分。不同 scoring 配置会改变最终得分。player_pool 不需要真实球星数据才能演示流程用占位 ID 跑通后把 JSON 换成真实数据源即可。这里不建议在 skill 内直接携带整份付费数据或比赛日内部数据能放 schema 和样例就不要放原始全量内容。2.3 输入模型独立带来的收益一旦 league 配置和球员池变成独立 JSONmock skill 就拥有了第一个可复用能力换 league 时只需要替换数据文件。比如同时存在league-2024.json和league-2025.json同一套脚本无需改动就能输出两套不同结果。这也让“你的 league”不再是 UI 里的临时表单而成为文件、Git 历史和审计记录的一部分。3. 搭出最小可运行骨架目录、SKILL.md 和 package.json3.1 目录结构先行在进入代码前先约定一个稳定的目录。这样后续脚本里不会出现散落的相对路径。league-mock-skill/ ├─ SKILL.md ├─ package.json ├─ data/ │ ├─ league.example.json │ └─ player_pool.example.json ├─ scripts/ │ ├─ generate-mock.mjs │ └─ validate-mock.mjs └─ output/目录中每个元素的职责SKILL.md技能声明文件给外部运行器或协作者判断入口。package.json定义项目依赖和 npm scripts。data/存放所有输入数据文件league.example.json和player_pool.example.json是示例文件。scripts/generate-mock.mjs真正执行 mock 的生成脚本。scripts/validate-mock.mjs对生成结果做约束校验。output/存放每次生成结果便于 diff。3.2 SKILL.md 的内容怎么写SKILL.md 不负责计算它负责告诉外部运行器“什么时候该调用这个技能”。示例--- name: demo-league-mock-draft description: 根据用户自己的 league 配置
