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:
AncTe committed 2026-10-22 18:38:45 +08:00
1 parent 77f5b3aa19
commit 3d2d375302
5 files changed
+44 -31

No files matched your search

+3 -3
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#与光环、查询、最终的交互)。
## 编辑器与图鉴
+6 -16
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()` 返回实时迭代器**:不要把它保存到回调之外;需要留存时先转成数组。
+1 -1
View File
@@ -63,7 +63,7 @@ async function moveTileAndObserveEvents(
}
```
`keepEvent` 只决定是否把源静态图块的图块事件复制到动态实例;不论取值如何,源静态图块的图块事件都会被清空。细节见[静态图块与动态图块](./static-vs-dynamic-tiles.md)的「双向转换」。
`keepEvent` 只决定是否把源静态图块的图块事件复制到动态实例;不论取值如何,源静态图块的图块事件都会被清空。细节见[静态图块与动态图块的「双向转换」](./static-vs-dynamic-tiles.md#双向转换)。
## 事件视图接口
+1 -1
View File
@@ -206,7 +206,7 @@ async function verifyTileConversion(
- 落点出现同数字的静态图块;
- 如果落点已有静态图块,`toStaticIfSafe` 会返回 `null`,落点不会变化,动态图块仍停在落点。想看到完整结果,先把落点清空,或换一个空格测试。
图块事件在转换中的复制结果由 `keepEvent` 决定,具体验证见[点事件与图块事件](./point-and-tile-events.md)。
图块事件在转换中的复制结果由 `keepEvent` 决定,具体验证见[点事件与图块事件的「怎么验证」](./point-and-tile-events.md#怎么验证)。
## 边界与注意