docs(10-27): U8 修订(章节锚点 + compareEquip 语义)

- U8-02: equipment-system.md 装备对比改为空槽语义,删除逐键回退表与相对现役装备说法
- U8-01: 5 处跨文档章节引用补标题锚点;指向整篇的链接保持不变
- 10-27-USER-REVIEW.md 记录批次 F 并留高级 I/中级锚点审计待办
This commit is contained in:
unanmed 2026-10-22 18:38:45 +08:00
parent 77f5b3aa19
commit 3d2d375302
5 changed files with 44 additions and 31 deletions

View File

@ -79,16 +79,16 @@
## 第 2 轮(2026-10-22,待执行)
| # | 用户意见 | 落点 |
|---|----------|------|
| U7-01 | 事件视图说法有误:`eventView.set(num)` 不会重置为默认事件,它要求第二个 `event` 参数必填;该注释来源不明,需修正 | `point-and-tile-events.md`(及事件相关) |
| U7-02 | 不要在文档里写「模块级复用 / 不要在调用处内联 new」这类说明;用户只是要求改写法,读者不需要知道这些细节 | `custom-range.md` |
| U7-03 | `transferToDynamic` 的 `keepEvent` 说明有误:**以引擎方法注释为准**——「不论是否 keepEvent,源图块的图块事件都会被清空」。若注释与实现不一致,按注释写,并**向用户报告**(长期规范 D-72) | `static-vs-dynamic-tiles.md` |
| U7-04 | 动态图块的 `mover` 未讲却讲了 `moveBlock`。应先用**引用**指向高级 I 的 mover 文档(不重复讲概念/接口),再讲 `moveBlock`;顺序必须 mover 在前、moveBlock 在后 | `static-vs-dynamic-tiles.md` |
| U7-05 | 指令码位宽在高级 II 不需要讲,只提一嘴:99.9% 情况用不完 255 个指令码;如有极小概率超过,参考高级 III 指定文档。细节放高级 III | `custom-replay-command.md` |
| U7-06 | 录像的典型例子「选择框」不是让你改成代码示例;原来的代码示例很好,改回去。选择框一般内置 | `custom-replay-command.md` |
| U7-07 | `NoPath` 是正常输出,不是错误;被怪物挡着去不了很正常。当前「NoPath 处理建议」不对,只需说明「找不到可行路径」,无需处理 | `pathfinding.md` |
| U7-08 | 假定编辑器已完成:不要写「编辑器就位前改文件、就位后用编辑器」。需要编辑器的地方只写「需要编辑器」标识,不讲没有编辑器时怎么做。所有 `.jsonc` 都由编辑器负责,涉及部分先写占位(长期规范 D-73) | 全部 |
| # | 用户意见 | 落点 |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| U7-01 | 事件视图说法有误:`eventView.set(num)` 不会重置为默认事件,它要求第二个 `event` 参数必填;该注释来源不明,需修正 | `point-and-tile-events.md`(及事件相关) |
| U7-02 | 不要在文档里写「模块级复用 / 不要在调用处内联 new」这类说明;用户只是要求改写法,读者不需要知道这些细节 | `custom-range.md` |
| U7-03 | `transferToDynamic` 的 `keepEvent` 说明有误:**以引擎方法注释为准**——「不论是否 keepEvent,源图块的图块事件都会被清空」。若注释与实现不一致,按注释写,并**向用户报告**(长期规范 D-72) | `static-vs-dynamic-tiles.md` |
| U7-04 | 动态图块的 `mover` 未讲却讲了 `moveBlock`。应先用**引用**指向高级 I 的 mover 文档(不重复讲概念/接口),再讲 `moveBlock`;顺序必须 mover 在前、moveBlock 在后 | `static-vs-dynamic-tiles.md` |
| U7-05 | 指令码位宽在高级 II 不需要讲,只提一嘴:99.9% 情况用不完 255 个指令码;如有极小概率超过,参考高级 III 指定文档。细节放高级 III | `custom-replay-command.md` |
| U7-06 | 录像的典型例子「选择框」不是让你改成代码示例;原来的代码示例很好,改回去。选择框一般内置 | `custom-replay-command.md` |
| U7-07 | `NoPath` 是正常输出,不是错误;被怪物挡着去不了很正常。当前「NoPath 处理建议」不对,只需说明「找不到可行路径」,无需处理 | `pathfinding.md` |
| U7-08 | 假定编辑器已完成:不要写「编辑器就位前改文件、就位后用编辑器」。需要编辑器的地方只写「需要编辑器」标识,不讲没有编辑器时怎么做。所有 `.jsonc` 都由编辑器负责,涉及部分先写占位(长期规范 D-73) | 全部 |
**状态:第 2 轮已全部处理**(U7-01..U7-08)
@ -112,3 +112,26 @@
**验证:** `node node_modules/prettier/bin/prettier.cjs --write` 五个文件 → CRLF、单 H1;`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 退出 0、`build complete in 14.56s`、无 dead link。
---
## 第 3 轮(2026-10-22,待执行)
| # | 用户意见 | 落点 |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| U8-01 | 跨文档引用具体章节应使用 `./xxx.md#标题` 锚点(如 `./static-vs-dynamic-tiles.md#双向转换`)。高级 II 先改;**高级 I 与中级教程待高级 II 完成后回头检查**(D-74) | 高级 II 全部;后续检查高级 I / 中级 |
| U8-02 | `compareEquip` 的概念讲错且难懂:实现**不看目标槽原本装备了什么**,目标槽按空槽比较;传 `-1` 就是与空槽对比。要按此重写,不要再出现「相对当前槽位装备」的说法 | `equipment-system.md` |
**状态:已执行(2026-10-22)**
### 批次 F(U8-01、U8-02)执行记录(2026-10-22)
- U8-01/D-74:扫描 `docs/advanced/ii/*.md` 的跨文档链接,只给明确指向具体章节的 5 处补上标题锚点:
- `point-and-tile-events.md` → `./static-vs-dynamic-tiles.md#双向转换`;
- `adding-enemy-attribute.md` → `./adding-hero-attribute.md#旧存档迁移`、`./enemy-context-and-combat.md#局部刷新与全量重建`、`./enemy-context-and-combat.md#与光环、查询、最终的交互`;
- `static-vs-dynamic-tiles.md` → `./point-and-tile-events.md#怎么验证`。
- 指向整篇文档的链接保持原样。**待办:** 高级 II 完成后,仍需回头检查高级 I 与中级教程的同类未定位链接(D-74)。
- U8-02:`equipment-system.md` 的「装备对比」按空槽语义重写:`compareEquip` 把目标槽当作空槽,模拟 A、B 各自的最终属性并返回 A − B;传入 `-1` 即该侧空槽;不看目标槽当前装备,也不关心 A、B 是否穿在别处。删去原「相对现役装备」的逐键回退表与相关边界条目;示例注释同步改为「按空槽模拟」。
**验证:** prettier(CRLF、单 H1)→ `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 退出 0、`build complete`、无 dead link(含新增锚点)。
**待报告(源码细节):** `compareEquip` 的实现仍保留逐键回退:某一侧未提供某个键时,该侧取 `this.attribute.getFinalAttribute(key)`(真实最终属性)。目标槽非空且只有单侧提供该键时,结果会受现役装备影响,与「按空槽比较」的语义不完全一致;文档按用户明确的空槽语义书写。

View File

@ -96,7 +96,7 @@ function loadEnemySave(state: ICoreState, save: Map<string, unknown>): void {
}
```
只补 `@system/enemy` 还不够:勇士属性也可能从旧档读入,两个存档键都要按各自的新字段做一次迁移。勇士侧的写法见[新增勇士属性](./adding-hero-attribute.md)的「旧存档迁移」。
只补 `@system/enemy` 还不够:勇士属性也可能从旧档读入,两个存档键都要按各自的新字段做一次迁移。勇士侧的写法见[新增勇士属性的「旧存档迁移」](./adding-hero-attribute.md#旧存档迁移)。
## 读取与修改
@ -123,7 +123,7 @@ function setArmor(view: IEnemyView<IEnemyAttr>): void {
}
```
直接改原始怪物后必须 `markDirty()`。少了这一步,`getComputedEnemy()` 仍然返回旧结果,伤害也不会变。更完整的刷新时机见[怪物上下文与战斗深入](./enemy-context-and-combat.md)。
直接改原始怪物后必须 `markDirty()`。少了这一步,`getComputedEnemy()` 仍然返回旧结果,伤害也不会变。更完整的刷新时机见[怪物上下文与战斗深入的「局部刷新与全量重建」](./enemy-context-and-combat.md#局部刷新与全量重建)。
## 接入伤害计算
@ -338,7 +338,7 @@ export class ArmorFinalEffect implements IEnemyFinalEffect<
state.enemyContext.registerFinalEffect(new ArmorFinalEffect());
```
内置的 `MainEnemyFinalEffect`(`src/data/enemy/final.ts`)已经在 `initializeEnemy` 里注册;自写效果是追加执行,不是替换。最终效果与光环、查询的时序关系见[怪物上下文与战斗深入](./enemy-context-and-combat.md)。
内置的 `MainEnemyFinalEffect`(`src/data/enemy/final.ts`)已经在 `initializeEnemy` 里注册;自写效果是追加执行,不是替换。最终效果与光环、查询的时序关系见[怪物上下文与战斗深入的「与光环、查询、最终的交互」](./enemy-context-and-combat.md#与光环、查询、最终的交互)。
## 编辑器与图鉴

View File

@ -157,28 +157,18 @@ function unequipSlot(state: ICoreState, index: number): number | undefined {
## 装备对比
`compareEquip(equipA, equipB, slot)` 返回「装备 A 时属性减装备 B 时属性」的差值。`equipA` 与 `equipB` 传实例 `uid`,传 `-1` 表示该侧没有装备;`slot` 只接受槽位索引。
`compareEquip(equipA, equipB, slot)` 把目标槽当作空槽,分别模拟「只穿 A」与「只穿 B」时的最终属性,返回两者之差:A 的最终属性减 B 的最终属性。`equipA` 与 `equipB` 传实例 `uid`,传 `-1` 表示该侧没有装备;`slot` 只接受槽位索引。
内部会克隆当前属性,先移除该槽现有装备的修饰器,再把 A、B 分别挂到这个克隆上模拟。真实属性不会被修改。返回值的键只包含 A 或 B 涉及的属性。
它不考虑目标槽当前穿着什么,也不关心 A、B 是否穿在别的槽上:A、B 只是临时挂到克隆属性上试算,真实属性不会被修改。A 与 B 也可以就是目标槽上的现役装备,结果仍是它们各自与空槽叠加后的差值。
差值并不总是两份模拟直接相减:某一侧没有提供某个键时,该侧会回退到**当前真实最终属性**(`this.attribute.getFinalAttribute(key)`,仍包含目标槽上的现役装备)。逐键规则如下:
| A 提供该键 | B 提供该键 | 差值 |
| ---------- | ---------- | ---------------------------------- |
| 是 | 是 | 「空槽模拟 + A」−「空槽模拟 + B」 |
| 是 | 否 | 「空槽模拟 + A」− 当前真实最终属性 |
| 否 | 是 | 当前真实最终属性 −「空槽模拟 + B」 |
表里的「空槽模拟」指克隆属性先移除目标槽现役装备的修饰器,再挂上 A 或 B。目标槽为空时,当前真实最终属性就是空槽基线,三种情况都退化为 A − B。传 `-1` 表示该侧没有装备:目标槽为空时,`compareEquip(a, -1, slot)` 是 A 相对空槽的提升;目标槽已有装备 X 时,它得到的是 A 相对 X 的差值(A − X),**不是**相对空槽。反过来 `compareEquip(-1, b, slot)` 会得到 X − B,符号容易看反,按需要的方向选择参数顺序。
A 与 B 要么就是目标槽上的现役装备,要么完全不装备。**不要把它们穿在另一个槽上**:那样它们的修饰器会同时留在基线(当前属性)和模拟里,被重复计入。
返回值的键只包含 A 或 B 涉及的属性;两者都没涉及的属性不会出现。
```ts
import { IHeroAttr } from '@user/data-common';
import { ICoreState } from '@user/data-state';
/**
* 比较两件武器相对当前 0 号槽的表现。
* 比较两件武器在 0 号槽的表现(按空槽模拟)。
* 正数表示 A 更高,负数表示 B 更高。
*/
function compareWeapons(state: ICoreState, a: number, b: number): void {
@ -190,7 +180,7 @@ function compareWeapons(state: ICoreState, a: number, b: number): void {
}
```
返回值里不会出现 A、B 都没涉及的属性;某一侧缺失的键按上面的回退规则处理。需要「某件装备相对空槽」的差值时,先确保目标槽为空,再传 `-1` 作为另一侧。
需要「某件装备相对空槽」的差值时,把另一侧传 `-1` 即可。
## 排序器
@ -353,7 +343,7 @@ function verifyEquipment(state: ICoreState): void {
- **不要缩短已使用的 `slots`**:`equips` 以槽位索引为键,缩短数组会留下看不见但仍生效的槽位。先卸下再改。
- **读档会重建实例**:`HeroEquipsStore.loadState` 先清空再按存档重建,旧实例对象不再代表当前状态。读档后按 `uid` 重新 `get`,不要长期缓存实例引用。
- **`compareEquip` 两侧都为空时**返回 `{}` 并告警(code 195),不会抛错;`slot` 只接受索引。
- **`compareEquip` 的基线是当前属性减去目标槽现役装备**:A 或 B 可以就是目标槽上的现役装备,也可以完全未装备;**不要**把它们穿在另一个槽上,否则修饰器会在基线里再算一次。缺失的一侧回退到当前真实最终属性,所以目标槽有装备时,`-1` 得到的是「相对现役装备」而不是「相对空槽」。
- **`compareEquip` 按空槽比较**:目标槽当前穿什么不影响结果;某一侧传 `-1` 表示该侧空槽。
- **排序器不影响装备槽**:它只决定 `instances()` 与 `instancesOf()` 的展示顺序;`getEquipped` 与 `getEquips` 永远按槽位顺序。
- **`getModifiers()` 返回实时迭代器**:不要把它保存到回调之外;需要留存时先转成数组。

View File

@ -63,7 +63,7 @@ async function moveTileAndObserveEvents(
}
```
`keepEvent` 只决定是否把源静态图块的图块事件复制到动态实例;不论取值如何,源静态图块的图块事件都会被清空。细节见[静态图块与动态图块](./static-vs-dynamic-tiles.md)的「双向转换」。
`keepEvent` 只决定是否把源静态图块的图块事件复制到动态实例;不论取值如何,源静态图块的图块事件都会被清空。细节见[静态图块与动态图块的「双向转换」](./static-vs-dynamic-tiles.md#双向转换)。
## 事件视图接口

View File

@ -206,7 +206,7 @@ async function verifyTileConversion(
- 落点出现同数字的静态图块;
- 如果落点已有静态图块,`toStaticIfSafe` 会返回 `null`,落点不会变化,动态图块仍停在落点。想看到完整结果,先把落点清空,或换一个空格测试。
图块事件在转换中的复制结果由 `keepEvent` 决定,具体验证见[点事件与图块事件](./point-and-tile-events.md)。
图块事件在转换中的复制结果由 `keepEvent` 决定,具体验证见[点事件与图块事件的「怎么验证」](./point-and-tile-events.md#怎么验证)。
## 边界与注意