docs: 订正 wand_fast 饱和点 0.05→0.0667;roadmap 关闭已决定项并澄清移速表述

【订正】wand_fast(0.25s) 的饱和点按公式 (1/60)/0.25 = 0.0667,不是 0.05。
numerical_design:21 的标注与它自己上一句的公式直接矛盾,spec/plan 另有三处同源
错误标注——均派生自一句口头转述,实测表里记的 0.0667 一直是对的。

结论也随之改对,不只是改数字:hard=0.05 使 wand_basic(饱和 0.0333) 与
circuit_fork(0.0278) 无无效区间,但 wand_fast 仍剩 [0.05, 0.0667) 这段买不到东西
(较原 hard:0.01 的 6.7 倍缩至 1.33 倍)。仍取 0.05 的理由:饱和点 = (1/60)/法杖
基准间隔,随杖而异,不存在对所有杖都最优的单一 hard——取 0.0667 让 wand_fast 干净
会使另两把杖够不到自己的饱和点,反而制造新的不可达区间。hard 数值不变。

顺带说明实测表里 wand_fast@0.0667 读到 2 帧的原因:采样点比真实边界 1/15 高 3.3e-5
(T 上高 8.3e-6 s),再叠加整帧倍数多花一帧的浮点余量效应,故实际 1 帧上界略低于
0.0667。数据没错,是标签错了。

【roadmap】E3-① 下的 cast_delay_mod 条目仍记为「待人定」并指向两个选项,而该决定
已在 ba7c885 关闭——roadmap 是查「E3 还剩什么」的索引,留着会把已关闭的决定重新打开。
改为已处理,并注明彻底消除需让 hard 逐杖化(挪进 cores.json),属独立立项。

【roadmap】「实测 200→300 px/s」读起来像发版移速由 200 改成 300,与同批改动里
numerical_design 的订正(发版是 200,300 是从未被读过的纸面值)直接冲突。改写为
「发版基准仍是 200;实测手段是临时挂 +50% 加成使生效值变 300」。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:36:53 +08:00
co-authored by Claude Opus 5
parent ba7c885291
commit 84c10725e2
4 changed files with 52 additions and 16 deletions
@@ -132,7 +132,7 @@ if _cast_timer <= 0.0:
| | **0.1soft** | 0.025 s | 2 帧 | **30 /s** |
| | 0.07 | 0.0175 s | 2 帧 | 30 /s |
| | 0.0667 | 0.016675 s | 2 帧 | 30 /s |
| | **0.05(现 hard** | 0.0125 s | 1 帧 | **60 /s ← 恰好饱和** |
| | **0.05(现 hard** | 0.0125 s | 1 帧 | **60 /s ← 已在饱和区内(饱和点是 0.0667,见下)** |
| | 0.01(原 hard | 0.0025 s | 1 帧 | 60 /s |
#### 四条结论
@@ -141,8 +141,13 @@ if _cast_timer <= 0.0:
| 法杖 | 基准间隔 | 饱和点 | 来源 |
| :-- | --: | --: | :-- |
| `wand_basic` | 0.5 s | ≈ **0.0333** | 实测(0.034 仍 2 帧、0.033 已 1 帧)|
| `wand_fast` | 0.25 s | ≈ **0.0667** | 实测0.0667 仍 2 帧、0.05 已 1 帧|
| `wand_fast` | 0.25 s | ≈ **0.0667**= 1/15| 实测夹在 (0.05, 0.0667] 内:0.0667 仍 2 帧、0.05 已 1 帧 |
| `circuit_fork` | 0.6 s | ≈ 0.0278 | **公式推导,未实测** |
> **关于 `wand_fast @ 0.0667` 读到 2 帧**:数据没错,是采样点的问题。真实边界是 `1/15 = 0.06666…`
> 我取的 `0.0667` 比它高 `3.3e-5`T 上高 `8.3e-6 s`),故落在 2 帧那侧。再叠加结论 3 的浮点余量效应
> —— T 恰为整帧倍数时反而多花一帧 —— **实际的 1 帧上界略低于 0.0667**。故 `wand_fast` 的饱和点
> 应读作「0.0667 稍下方」,本文一律以公式值 0.0667 记,误差方向是保守的(真实无效区间只会**更大**一点)。
2. **soft→hard 之间的可达档位是台阶而非曲线**`wand_basic` 只有 20 / 30 / 60 次每秒三档,
`wand_fast` 只有 30 / 60 两档。中间值买不到。
3. **周期恒为 `ceil(T × 60)` 帧,且 T 恰为整帧倍数时会多花一帧**`T = 0.5`**31** 帧而非 30
@@ -154,8 +159,31 @@ if _cast_timer <= 0.0:
#### 决定(用户 2026-07-31
`hard: 0.01 → 0.05`取 0.05 的依据正是上表:`wand_fast` 恰在 0.05 饱和,`wand_basic` 在 0.0333 饱和,
故 0.05 使**两把主力杖都不再有「买了没用」的无效区间**,让 JSON 承诺的是真能交付的东西。
`hard: 0.01 → 0.05`
**关键前提:饱和点 = `(1/60) / 法杖基准间隔`,随杖而异,因此不存在对所有杖都最优的单一 `hard`。**
`hard` 是**全局**的一个数,而每把杖的饱和点各不相同(0.0278 / 0.0333 / 0.0667),任何取值都只能覆盖一部分:
| `hard` 取值 | `circuit_fork`(0.0278) | `wand_basic`(0.0333) | `wand_fast`(0.0667) |
| :-- | :-- | :-- | :-- |
| 0.01(原值) | 无效区间 0.0278→0.01 | 无效区间 0.0333→0.01 | 无效区间 0.0667→0.016.7 倍)|
| **0.05(已采用)** | ✅ 无无效区间 | ✅ 无无效区间 | ⚠️ 仍剩 `[0.05, 0.0667)` |
| 0.0667 | ✅ | ✅ | ✅ 无无效区间 |
—— 但 `0.0667` 会让 `wand_basic`(饱和 0.0333)与 `circuit_fork`(0.0278)**够不到自己的饱和点**,
即它们的最高射速被硬上限提前截断,**制造出新的"不可达区间"**(想更快但买不到)。
**故 0.05 是折中**:让**多数杖**`wand_basic` / `circuit_fork`,以及所有基准间隔 ≥ 0.333 s 的杖)
无无效区间,只在最快的 `wand_fast` 上留 `[0.05, 0.0667)` 这一小段。相对原 `0.01`
(两把主力杖都深陷饱和区)是大幅改进,但**不是"两把主力杖都不再有无效区间"** ——
`wand_fast` 那段仍在,只是从 6.7 倍缩到 1.33 倍。
> **📌 订正记录(2026-07-31**:本节初稿曾写「`wand_fast` 恰好在 0.05 饱和 / 两把主力杖都无无效区间」,
> **算错了** —— 按同一节自己给出的公式 `(1/60)/0.25 = 0.0667`,不是 0.05。实测数据表(§2.2b 上方)
> 记的 0.0667 一直是对的,错的只是从那句口头转述派生出来的几处标注。已连同
> `numerical_design.md` §1.1、roadmap E3-①、plan Step 7b② 一并订正。
> **教训与本期其余三条同源**:结论若与自己上一句的公式矛盾,是最容易被略过的一类错 ——
> 因为读者会假定作者算过。
**明确不做**:不在代码里加下限钳制。下限已由 `attributes.json` + `AttributeFormula`
`maxf(hard, ...)` 强制;在 `player_manager` 再加一个会形成**重复权威**,设计师调 `hard` 时立刻脱同步,
@@ -165,7 +193,7 @@ if _cast_timer <= 0.0:
| 法杖 | 请求 `mod` | 生效 `mod` | 生效间隔 T | 实测帧距 | 实际速率 | 判读 |
| :-- | --: | --: | --: | --: | --: | :-- |
| `wand_fast` | 0.05 | 0.05 | 0.0125 s | **1 帧×179** | 60 /s | ✅ 恰好饱和,硬上限一格不浪费 |
| `wand_fast` | 0.05 | 0.05 | 0.0125 s | **1 帧×179** | 60 /s | ⚠️ 已达 60 /s,但饱和点是 0.0667 —— `[0.05, 0.0667)` 这段买了没用 |
| `wand_fast` | 0.01 | **0.05** | 0.0125 s | 1 帧×179 | 60 /s | ✅ `maxf(hard, …)` 生效,越界请求被钳回 |
| `wand_basic` | 0.05 | 0.05 | 0.025 s | **2 帧×89** | 30 /s | ✅ |
| `wand_basic` | 0.01 | **0.05** | 0.025 s | 2 帧×89 | 30 /s | ✅ 钳制生效 |
@@ -173,10 +201,14 @@ if _cast_timer <= 0.0:
| `circuit_fork` | 0.05 | 0.05 | 0.03 s | **2 帧×89** | 30 /s | ✅ 见下方遗留 |
> **遗留(实测)**`circuit_fork`0.6 s)在新 `hard` 0.05 处实测为 **2 帧/次(30 /s** ——
> 它的饱和点 0.0278(公式推导)低于 0.05,故**没有**无效区间,但也吃不满 60 /s。这是
> 「用最快的杖定 `hard`」的必然结果,属可接受的取舍:**无效区间清零优先于人人吃满硬顶**
> (无效区间是"花了钱没效果",吃不满硬顶只是"上限没那么高")。若日后引入基准间隔更长的
> 法杖,此结论不变。
> 它的饱和点 0.0278(公式推导)低于 0.05,故**没有**无效区间,但也吃不满 60 /s`wand_basic`
> 同理(0.0333 < 0.0530 /s)。反过来 `wand_fast`0.0667 > 0.05)能吃满 60 /s,代价是
> `[0.05, 0.0667)` 那段无效区间。
>
> **这正是"单一全局 `hard` 撞上逐杖饱和点"的必然取舍**:`hard` 偏小则慢杖有无效区间,偏大则
> 快杖被截断。0.05 选择了**让无效区间只留在一把杖上**,因为无效区间是"花了钱没效果"(欺骗),
> 而吃不满硬顶只是"上限没那么高"(诚实)。若日后要彻底消除,正确做法是让 `hard` 逐杖化
> (即把它变成 `cores.json` 的一个字段),而不是继续挪这个全局数 —— 那属于新的设计立项。
### 2.3 公式模块 —— `AttributeFormula`