| name | adjust-lottery-tier |
| description | 基于主动/被动技能统计 CSV(列:技能,等级维度,胜率,事件数),复盘抽奖池 Tier 分布。 读取 src/vscripts/modules/lottery/lottery-abilities.ts 当前池形状, 对照目标占比(T5 4–5% / T4 13–15% / T3 21–23% / T2 33–36% / T1 24–27%), 结合胜率与事件数为每条技能生成调整建议(升/降档、加强/削弱、移除、询问用户), 写入 plan 并与用户确认后执行池结构调整。数值加强/削弱类交给 update-abilities-override 接手。 |
抽奖池 Tier 复盘与调整
基于 CSV 统计数据调整 src/vscripts/modules/lottery/lottery-abilities.ts 的 Tier 分布。
参考文件路径见 CLAUDE.md「Dota 2 参考文件速查」。
设计原则(背景知识)
理论权重 vs 实际行为
BASE_TIER_RATES = [1, 5, 20, 60, 100](T5→T1)对应单次抽取权重 1:4:15:40:40。但玩家"抽 6 选 1"会挑优,实际选取分布严重偏向高档:
| Tier | 理论权重% | 实际选取%(历史加权) | 目标池占比 |
|---|
| T5 | 1 | ~6 | 4–5% |
| T4 | 4 | ~18 | 13–15% |
| T3 | 15 | ~26 | 21–23% |
| T2 | 40 | ~33 | 33–36% |
| T1 | 40 | ~17 | 24–27% |
目标池占比 = 0.35 × 理论 + 0.65 × 实际。主动/被动两池使用同一套目标占比。
样本量与胜率置信度(p≈0.8,95% CI)
| n | ±pp | 可用性 |
|---|
| 50 | ±11.1 | 不可用 |
| 100 | ±7.8 | 仅看极端 |
| 150 | ±6.4 | 需辅助证据 |
| 200 | ±5.5 | 判档阈值线 |
| 300 | ±4.5 | 较稳 |
| 500 | ±3.5 | 稳健 |
第一步:收集 CSV 输入
用 AskUserQuestion 询问用户提供:
若只提供一份,只处理该池;两份都有则两池独立分析。
CSV 格式:技能,等级维度,胜率,事件数(表头行跳过)。
第二步:解析与聚合
对每份 CSV:
- 解析每行 →
(name, tier, winrate, events)。
- 按 tier 聚合:
tier_events[t] = 该档总事件数
tier_count[t] = 该档技能数
tier_winrate_avg[t] = 加权均值 Σ(wr·ev) / Σev
tier_winrate_sd[t] = 事件数加权标准差(用于判"偏离 > Nσ")
- 计算池总事件数、各档实际占比
tier_events[t] / total。
第三步:整体样本充分性检查(关键:数据不足必须停止)
按以下规则判断数据是否足够支持本次分析,任一条触发即停止工作,提示用户补充数据后再运行 skill:
- 池总事件数 < 5000
- 任一档的中位技能事件数 < 100(整档普遍样本不足)
- 条数 ≥ 10 的档中,
n ≥ 200 的技能占比 < 30%
停止时告知用户当前数据量与缺口,不生成 plan。
第四步:读取当前池状态
Read src/vscripts/modules/lottery/lottery-abilities.ts。
解析两个数组 abilityTiersActive / abilityTiersPassive,统计每档条数与技能列表。注意 lottery 文件中技能带中文注释,保留注释用于 plan 展示。
第五步:计算池形状差距
对每池:
- 目标条数(按中点占比 T5 4.5% / T4 14% / T3 22% / T2 34% / T1 25.5% × 当前总条数,取整)
- 差值 = 目标 − 当前
输出每档"需增减多少条"。
5.1 关键原则:按全池胜率分位数划分理想档位,限速收敛
档位的本质是"在整个池子里的胜率排名位置",不是"配额"也不是"相对当前档均值"。
5.1.1 错误做法警示
- ❌ 按档均值 argmin 判档:档均值被档内容污染,导致"T5 均值被 T5 尾部差技能拉低" → T4 头部
|wr−μ_5| 反而小 → 错判升档。此方法会放大池形状偏差,不收敛。
- ❌ 按"配额决定方向":T5 需 −1 就不许升档,T1 需 +3 就不许降档。与胜率实际匹配度脱节,导致明显该升的技能(如
life_stealer_rage 91.13%)被压在 T4、明显该降的坐在 T5。
5.1.2 正确做法:分位数锚定 ideal_tier
仅用池内 n ≥ 200 的技能计算理想档:
- 按胜率降序排所有 n ≥ 200 的技能,得到 rank。
- 按目标占比切分位数:
- 前 4.5%:理想 T5
- 接下来 14%:理想 T4
- 接下来 22%:理想 T3
- 接下来 34%:理想 T2
- 剩余 25.5%:理想 T1
- 每条技能的
ideal_tier = 其 rank 所落分位区间对应的档。
shift = ideal_tier − current_tier。
分位数法的优点:
- 自我稳定:不依赖当前档均值,不会被档内容污染。
- 自动收敛池形状:按定义,完全按 ideal_tier 迁移后,池形状就是目标分位。
5.1.3 保守限速(避免一次大改)
但一次完全按 ideal_tier 迁移可能产生 30+ 条变动,单版本太激进,容易误伤边界技能,也让玩家一版体验剧变。因此引入限速规则:
- 单档单次变动上限 =
max(3, 当前档条数 × 15%)。即每档升出 + 降出总数不超过该档 15%(或至少 3 条)。
- 优先级排序:按
|shift| 降序、胜率偏离原档中位数幅度降序。每档取排序靠前的候选执行,超限部分保留到下版。
- 跨档 shift ≥ 2:优先保留这些技能的迁移,因为它们偏离最远、最拖累池结构。
- shift = ±1 且边界接近:若与相邻档分位线距离 ≤ 1pp,优先保留为 shift=0(避免因样本抖动反复升降)。
5.1.4 终态模拟与池形状验证
所有迁移候选确定后,用以下公式模拟终态:
终态[t] = 初始[t] + Σ(进入该档) − Σ(离开该档) − Σ(该档被移除)
收敛验证:每档 |终态 − 目标| 必须 ≤ |初始 − 目标|(即不能反向偏离)。若反向偏离:
- 检查是否误把 shift=0 的技能移动了(回滚)。
- 检查该档升出/降出是否失衡——若升出过多但实际需要增加条数,减少升出候选。
5.1.5 多版本逐步收敛
差距大(|初始 − 目标| > 5)时,一次调整通常无法到位。在 plan 末尾明确"本轮收敛 X/Y"与"下版待处理 Z 条",作为下次运行 skill 的输入锚点。
第六步:逐技能打标签
核心方法:为每条 n ≥ 200 的技能计算"最匹配档位"(见 5.1.1),根据 shift = best_tier − current_tier 决定调整方向。 同档均值与 σ 用于 T1 特判与极端判定。
| 条件 | 标签 |
|---|
| n < 50,且同档其他技能普遍 n ≥ 200 | 询问用户(选择率过低) |
| 50 ≤ n < 200 | 观察(样本小),仅当 shift ≥ +2 或 shift ≤ −2 时才带"可能需调整"备注 |
| n ≥ 200 且 shift = +1 | 升 1 档 |
| n ≥ 200 且 shift = −1,非 T1 | 降 1 档 |
| n ≥ 300 且 shift ≥ +2 且 偏离原档均值 > 15pp | 升 2 档(罕见) |
| n ≥ 300 且 shift ≤ −2 且 偏离原档均值 < −15pp,非 T1 | 降 2 档 |
| n ≥ 300 且 shift = +1 且 事件数同档 TOP 3 且 原档位 ≥ T4 | 建议削弱(OP 信号,数值处理,档位可保留) |
| n ≥ 200 且 事件数同档 TOP 2 且 shift = 0 | 观察(过曝光) |
| T1 档 且 n ≥ 200 且 胜率 < T1 均值 − 8pp | 询问用户(T1 胜率异常) |
| 其他(shift = 0 或无效信号) | 无调整 |
best_tier 取 argmin(|wr − μ_t|)。若两档距离接近(差 < 1pp),优先保留在较低档(保守)。
T1 末档询问规则:仅当某 T1 技能胜率低于 T1 加权均值 > 8pp 才询问。不是"末尾 10% 必淘汰"——正常偏低的 T1 技能保留。
第七步:补齐中文名
对每条将要调整或标记的技能,按顺序查中文名用于 plan 展示:
Grep game/resource/addon_schinese.txt,模式 DOTA_Tooltip_ability_{系统名}\s+,提取下一行的值
- 若未命中,
Grep docs/reference/<latest-version>/abilities_schinese.txt 同模式
- 两处都找不到 → 跳过中文名,plan 中只显示系统名(不触发 AskUserQuestion)
<latest-version> 取 docs/reference/ 下最新数字版本目录。
第八步:生成 Plan
写 plan 前必须完成第五·5.1 节的"终态模拟",plan 中必须包含"初始→终态→目标"对比表(见下方模板),验证每档都在向目标收敛。
写入 C:\Users\windy\.claude\plans\adjust-lottery-tier-<yyyymmdd-hhmm>.md,结构:
# 抽奖池 Tier 复盘 - <日期>
## 数据概览
### 主动池(总事件 N)
| Tier | 条数 | 事件数 | 占比% | 加权胜率 | 胜率 σ |
### 被动池(总事件 N)
(同上)
## 池形状差距
| Tier | 目标% | 主动当前/目标条数/差 | 被动当前/目标条数/差 |
## 主动技能调整清单
### T5(需增/减 N 条)
- `system_name` (中文名) | n=XXX | wr=XX% | **升 1 档** → 偏离 +Xpp, 样本充足
### T4 / T3 / T2 / T1
(同上结构)
### 需人工确认(T1 胜率异常)
- `system_name` | ... | 执行阶段将用 AskUserQuestion 询问
### 需人工确认(选择率过低)
- `system_name` | n=XX(同档中位 N) | 几乎无人选,询问降档/加强/移除/保留
## 被动技能调整清单
(同上结构)
## 数值调整清单(移交 update-abilities-override)
- `system_name` | 建议加强 | 理由:胜率 XX% 低于同档均值 Xpp,n=XXX
- `system_name` | 建议削弱 | 理由:OP 信号
## 汇总
- 升档:N 条 / 降档:N 条
- 加强建议:N 条 / 削弱建议:N 条
- 待询问用户:N 条
- 无调整:N 条
第九步:用户确认
输出 plan 文件路径,告知用户查看并确认。等待用户批准后进入执行阶段。
第十步:执行池结构调整(用户批准后)
10.1 逐条处理「询问用户」标签
对每条 询问用户(T1 胜率异常) 或 询问用户(选择率过低) 的技能,单独调用一次 AskUserQuestion:
- T1 胜率异常选项:
加强数值 / 移除 / 保留观察
- 选择率过低选项:
降一档(提升曝光) / 加强数值 / 移除 / 保留观察
10.2 修改 lottery-abilities.ts
用 Edit 工具处理每条带"升/降档"标签的技能:
- 从旧档
names 数组中删除(包括其中文注释行)
- 插入新档
names 数组,尽量放入相似分区(// 大招 / // 小技能 / // 自定义技能 / // 法球/开关技能 / // 单位技能)
- 保留原中文注释
对移除类:从数组中删除该行(含注释)。
不修改 BASE_TIER_RATES / PREMIUM_TIER_RATES。
10.3 数值调整类建议移交
对每条 建议加强 / 建议削弱 / 被用户选为"加强数值"的技能:
- 调用
update-abilities-override skill 处理对应 KV 数值
- 每次处理一条技能,单独与用户确认数值改动
10.4 验证提示
执行完成后提示:
- 运行
npm test
- 进游戏实际抽奖验证高档出现频率变化
Skill 交互规范
- 数据不足 → 停止工作(第三步),不生成 plan。
- T1 胜率异常 / 选择率过低 → AskUserQuestion,单独一问一答。
- 中文名查不到 → 跳过,不询问。
- 数值加强/削弱 → 移交
update-abilities-override,不在本 skill 内改 KV。
- 调整幅度:常规 ±1 档;极端(n≥300 且偏离>15pp)才 ±2 档。
- 不修改
BASE_TIER_RATES / PREMIUM_TIER_RATES。