通用赛季预测与 S17 结算复盘
方法版本:season-scale-damped-transfer-v1。这是在 S17 结算后提出、用于下一期验证的候选方法。原有 s17-threshold-transfer-v1/v2 冻结记录保留不变;不能把新方法回放结果说成当时实际发布的预测。
1. 先看实际结算与旧预测表现
本次评分目标
伤害单位为亿;底层保留截图玩家的完整整数伤害。
| 区服 | 3% | 1% | 0.5% | Top50 | 第一名 |
|---|---|---|---|---|---|
| 151.31889571 | 208.59939478 | 225.64981691 | 272.76792383 | 353.06239475 | |
| 微信 | 待补 | 207.22191920 | 222.64459051 | 260.53708737 | 343.41630854 |
五条百分位线来自站长采纳的结算截图:玩家的显示百分比分别为 3.00%、1.00%、0.50%。整数伤害是该玩家的精确伤害,百分位显示只有两位小数,因此它们属于结算边界附近的观测代理值,不能宣称理论分位数阈值精确到个位。
名为“微信百三”的图片实际显示 3.08% / 142.42638473 亿。保留该近线观测,但不把它当作微信 3% 训练标签,不据此填出一个精确 3% 结算线。双服 Top200 没有本次最终证据,继续留空。
原样评分当时已发布的记录
以下 MAPE 为 QQ 三个、微信两个可核对百分位档位的平均绝对百分比误差。每批只有五个相关档位,全部来自同一个目标赛季;不是五个独立赛季。
| 北京时间批次 | 当时版本 | A:同阶段倍率 MAPE | B:门槛增长参照 MAPE |
|---|---|---|---|
| 10月17日 16:00 | v1 | 13.827% | 17.454% |
| 10月17日 20:00 | v2 | 1.098% | 2.354% |
| 10月18日 14:00 | v2 | 2.295% | 2.295% |
| 10月19日 14:00 | v2 | 2.879% | 3.904% |
| 10月20日 15:00 | v2 | 4.247% | 5.328% |
| 10月21日 18:00 | v2 | 4.895% | 5.331% |
10月21日晚上,另外取得百分位玩家观测后发布的校正版本,五档 MAPE 为 1.576%。它使用了比双服 Top50 更多的额外信息,所以与首日只输入 Top50 的方法分开呈现。
这次实际结果说明:首版把头部倍率全部传给百分位,确实明显偏高;首日晚间改为 0.8 弹性后,已经非常接近结算。后续更新反而渐渐偏低,说明“头部玩家剩余提分”和“更广玩家群体的分位线增长”并不总同步。不能只展示赛末校正,也不能假定晚一天的数据必然更准。
2. 数据能支持哪些判断
- S15、S16、S17 有部分最终百分位标签与双服最终 Top50。
- S16、S17 有赛中多批 Top50;只有两个独立赛季的轨迹。S16 QQ 有 14 批完整 Top50,微信有 11 批完整 Top50;另外两批微信只到第49名,保留但不补造第50名。
- S1—S14 的一些最终排名可继续展示,但没有相应完整赛中轨迹或百分位标签时,不能当作本模型的完整训练样本。
- S15 与 S17 的 Boss 弱点为燃烧代码;S16 弱点为风压代码。这支持“本期阵容与属性会改变总尺度”的建模方向,但只有这些样本,无法分离属性、角色池、Boss 机制、练度、氪金和参赛者变化各自的因果贡献。
Top50 是分布最前端的有选择样本。即使前50完全一样,后面玩家的分布仍然可以很不一样。因此仅靠 Top50,数学上无法唯一还原全体 3%、1%、0.5% 档位。新模型必须明确使用历史迁移假设,而不能把缺少的数据包装成已经观测到的结果。
3. 新模型:把强弱尺度、剩余增长和分位映射分开
3.1 首批就能确定的赛季强弱尺度
令 D_s(r,t) 为当前赛季 s、时刻 t、第 r 名伤害。用第 6—45 名中段降低单个冠军和第50名压线波动的影响。
H_s(t) = median(D_s(r,t)), r = 6…45
u = (t - 本期挑战开始) / (本期挑战结束 - 本期挑战开始)
q_s,h(t) = median(D_s(r,t) / D_h(r,u)), r = 6…45h 是已经结束的较早赛季。按照挑战进度 u 对齐历史,历史相邻快照之间采用对数插值;超出历史可观测范围则采用最近快照并扩大情景压力。未来赛季活动时长变化时,不会误套 S17 的具体日期。
q_s,h(t) 是随本期实际数据变化的尺度,没有固定的“火属性乘 1.5、风属性乘 0.7”。强力火队、铁队、练度变化与 Boss 适配性如果提升了实测伤害,就直接体现在这个尺度里。
3.2 次批开始估计本期增长是否快于历史
对近期48小时的 log(q_s,h(t)) 计算所有相隔至少3小时的两点斜率,取中位数。这个斜率描述本期相对历史的增长变化,避免把历史正常成熟增长又算一次。
数据少时先向零收缩:
w = 时间跨度 / (时间跨度 + 24小时) × min(1, (批次数-1)/3)
b = clip(w × 斜率中位数, -log(1.2)/24, +log(1.2)/24)
有效外推小时 = 24 × [1 - exp(-剩余小时/24)]
G_s,h(t) = exp(b × 有效外推小时)首批没有可识别的增长斜率,G=1。第二批会产生小幅更新,批次数和时间跨度增加后再提高趋势权重。阻尼让短期增长的总影响逐渐饱和,不把首日快速抬升线性延伸到全部剩余天数。
上述 48小时、24小时、20% 等是公开的工程候选参数,不是从 S17 最终答案优化得到的最优参数,也没有被少量数据证明为最佳。后续应按整季划分训练与测试来比较参数,不能用每个截图当独立样本反复调参。
3.3 预测本期最终头部尺度
本期最终头部候选_h = H_h(结算) × q_s,h(t) × G_s,h(t)
本期最终头部 H_hat_s = 各历史候选的对数中位数结果不低于当前有效头部成绩。这个约束以“已审核成绩不会因处罚或勘误撤销”为条件。
Top50 和第一名属于确实观察到的名次,分别使用相同历史名次的剩余增长,并设置当前有效成绩下限。百分位与 Top200 没有本期实测边界时,不设置虚构的当前下限。
3.4 每个百分位有自己的弹性,但少样本时收缩
历史已结束相邻赛季的弹性为:
β_(a→b,k) = log(F_b,k / F_a,k) / log(H_b,final / H_a,final)只有头部尺度变化达到10%,才使用该关系,避免分母接近0时制造巨大弹性。每条跨季转移的两个区服先合并为一个代表值,再计为一个转移样本。
β_k = clip((n × median(历史转移弹性_k) + 2 × 0.8) / (n + 2), 0.2, 1.5)
本期档位候选_h,k = F_h,k × (H_hat_s / H_h,final)^β_k
本期最终档位估计 = 各可用历史候选的对数中位数0.8 是已有方法的共同先验,权重2表示少量证据不足以完全推翻它;不是“多造两季训练数据”。S17 结算后,供未来赛季使用的参数约为:
| 目标 | 新候选 β | 可识别跨季转移数 |
|---|---|---|
| 3% | 0.7747 | 2 |
| 1% | 0.8123 | 2 |
| 0.5% | 0.8400 | 2 |
| Top200 | 0.8172 | 1 |
| Top50、第一名 | 1 | 直接名次增长 |
这些小数仅用于记录计算结果,不意味着参数估计达到了小数点后四位的统计精度。S17 的训练标签不能用于计算“S17 当时会如何预测”的回放;回放每次只传入目标赛季之前的数据。
3.5 未来怎样引入属性信息
当某种弱点至少有3个已结束赛季,才允许学习该弱点的残差:
残差 = log(最终档位) - β_k × log(最终头部尺度)使用同弱点残差相对全部历史的偏移,再按样本量收缩。这是在已经扣除实际强弱尺度后判断“该属性的普通玩家能否跟上头部”,避免先乘一次火属性倍率,又被 Top50 的高伤害再乘一次。
当前同属性样本不足,该修正为1。系统已预留计算能力,但不会用两三个季的数据硬造出一张貌似精确的元素倍率表。
3.6 一次性事件
商店刷新、新角色上线等事件记录在赛季配置的 forecastEvents 中,必须有稳定事件ID、开始时间和获知时间。已进入当前观测窗口的事件视为已经包含在成绩中,不再次相乘。无法量化的“多了十几个石头”只作说明,不直接换算伤害。
尚未发生且有来源支持的额外头部倍率,只影响敏感性情景上侧,不暗中抬高主估计。事件ID去重,避免重复录入后把相同加成乘多遍。
4. 新方法的历史回放:结果并没有全面胜过旧 v2
每个时点只使用当时已审核的本期快照、较早季的结算值与已结束历史轨迹。S17 最终值只用于评分,不参与回放训练。由于方法是在结算后提出,这仍是事后时间切分回放,不是当时实际发行的新预测。
时间口径要分清:asOf 是站长指定的批次标签,用于对齐挑战进度;informationAvailableBy 才是本次回放的实际信息可用截止。例如10月17日20:00批次的截图持续到20:12,资料审核时间为21:05:24,因此回放用21:05:24作为知识截止,不能宣称20:00整已经看到了20:12的图。每份输入的批次、截图结束及审核时间均记录在回放JSON中;晚于实际信息截止的资料不会被使用。
| 北京时间批次 | 旧A真实事前记录 MAPE | 新候选事后回放 MAPE |
|---|---|---|
| 10月17日 16:00 | 13.827%(v1) | 3.194% |
| 10月17日 20:00 | 1.098%(v2) | 2.167% |
| 10月18日 14:00 | 2.295% | 4.291% |
| 10月19日 14:00 | 2.879% | 5.818% |
| 10月20日 15:00 | 4.247% | 5.400% |
| 10月21日 18:00 | 4.895% | 5.911% |
新候选改掉了首版过度传递头部膨胀的问题,但没有全面超过已修正的 v2;负向相对趋势甚至放大了中后期低估。本次不能据此宣布新方法更准。 保留 v2 作为基准,新候选作为可复用、可审计的下一期对照。继续积累 S18 的独立结果后,才能判断逐档弹性、趋势更新或属性残差哪部分真的有效。
S16 回放明确返回 missing_preceding_season_trajectory:S15 缺少赛中曲线,不能用 S17 的后期资料假装“当时只用首日就准确预测了 S16”。也不把 S16 参与过校准后的开发期重构称为样本外预测。
5. 计算、发布与后续录入接口
以下命令在 standalone/nikke-cn-solo-raid 目录运行。
结算后:训练资料与所有旧记录评分
node scripts/build-season-forecast-model.mjs
node tests/season-forecast-model.test.mjs输出:
data/season-forecast-model.json:方法、公开参数、训练覆盖、逐档弹性、最终训练标签与限制。data/season-forecast-evaluation.json:每个旧冻结版本、额外百分位校正版本、新候选回放的逐档误差。data/season-forecast-current.json:固定模板读取的当前赛季预测状态;已结算、资料不足和可展示预测明确区分。
旧冻结文件仍在 data/threshold-forecast-runs/,不覆盖、不回填。已经结算的 S17 构建器只读取该文件,避免因为真实发布时间超过截止而发布“新的旧预测”。
新一期:先登记赛季,再导入已审核快照
data/season-registry.json 中配置 season、status、challengeStart、endsAt、weakness 等字段。新赛季不再写死在预测代码里。
正常更新只需登记 registry.currentSeason、将新赛季设为 provisional、录入已审核快照,再运行统一构建。默认的 build-season-forecast-model.mjs 会自动判断当前赛季并冻结预测,无需每期增加一条专属命令或修改模板。至少一服具备完整已审核Top50和较早历史轨迹时可生成;缺失区服/档位仍留空。资料不足只输出状态说明,不制造预测;挑战结束后停止发行,正式结算后只展示实际成绩与回测。
下面的显式命令仅用于人工指定赛季或数据批次,不是正常构建必需步骤:
node scripts/build-season-forecast-model.mjs --forecast --season 18可用 --as-of ISO时间 指定已经存在的批次,但真实 issuedAt 始终为执行时刻,不能在结算后倒填。没有完整当前 Top50、没有较早历史轨迹、缺少某档历史标签时,分别明确返回原因,不能编造缺失值。
新预测存入 data/season-forecast-runs/,当前展示副本为 data/s18-season-forecast.json。新模型不会覆盖原有 S17 v1/v2 文件。配置与方法版本改变也必须保留旧运行记录。
新赛季结算后,默认构建会自动扫描这些新冻结记录,在 season-forecast-evaluation.json 的 prospective.runs 中按对应赛季实际结果评分,并分别保留候选与固定弹性基准的误差。尚未结算的记录进入 prospective.unscored。这些真实事前发行结果与 replay.runs 的事后回放分开计数。
每个可计算档位还返回 baseline:以最近一个已有完整赛中轨迹的历史季为参照,固定0.8弹性的通用基准。候选和基准同时记录,不把看过S17后的选择伪装成预先设定的胜者。新聚合评估文件的 signedPercentageError 与 absolutePercentageError 统一以百分数表示,例如 -5 代表低估5%;旧冻结记录原结构不变。
固定临时页面的读取契约
模板读 data/season-forecast-current.json,仅当 season 与页面赛季匹配且 status 为 forecast_ready 时展示预测。正式页、已结算临时归档和历史单批快照不得误展示最新预测。
| 字段 | 含义 |
|---|---|
season | 当前登记赛季编号 |
status | forecast_ready、missing_evidence、settled、not_started、ended_awaiting_settlement 或 configuration_missing |
reason / methodNotice | 当前状态和候选限制的纯文本说明 |
asOf | 站长批次标签 |
issuedAt | 冻结记录真实发行时间;重建复用原值 |
informationAvailableBy | 实际资料可用截止 |
forecastPath | /data/s{N}-season-forecast.json |
frozenPath | /data/season-forecast-runs/{id}.json |
servers.qq / servers.wx | 分服状态、输入时间、估计与解释 |
servers.*.thresholds[].key | p3、p1、p0_5、top200、top50、top1 |
estimate / lower / upper | 候选整数伤害与未校准情景范围;缺失为 null |
baseline.estimate | 固定0.8基准整数伤害;该历史档位缺失时为 null |
构建输出需复制该固定状态JSON、被引用的当前预测JSON和对应不可变运行文件到相同的公开 /data/ 路径。页面全部数字仍写入HTML文本,JSON用于溯源及Agent读取。
关键验证
测试覆盖:目标季/未来最终值不泄漏、晚审核数据不提前使用、缺失百分位不跨服抄值、首批不编造趋势、阻尼限制短期外推、事件不重复叠加、结算后不能重发预测、官方可见名次下限,以及所有旧冻结批次都纳入评分。
6. 方法参考与本项目推论
- Hyndman 与 Athanasopoulos:Forecasting: Principles and Practice,评估与时间序列交叉验证。依据是训练集与测试集分离、不能用拟合误差替代预测误差;具体到本项目的“按整季而非截图划分”是针对数据相关性的应用。
- 同书:指数平滑与阻尼趋势。借鉴限制长期趋势外推的思想;本项目的稳健比率趋势不是标准 Holt 模型,不宣称直接拟合了一个经过验证的 ETS 模型。
- Stan User’s Guide:Truncated or Censored Data。本项目只观测头部属于受选择数据;“相同Top50可以对应不同更低分位线”是本任务的可识别性推论,不是来源给出的本游戏经验结论。
本候选的阈值、窗口、先验权重、压力范围均在代码与JSON中公开。情景范围不是80%/95%概率区间,也不承诺下期必然命中。