﻿## 实测七百四十八【手册「骑乘指挥棒」整章重写 + 首个公开发布 v1.3.0(beta)】

> 承接 七百四十七（投弹安全高度 + 六台载具不可驾）。本次两件事：**① 把手册里「骑乘指挥棒」那一章整章重写**（只讲当前功能，把历年修复叙事全部移出正文）；**② 首次把 v1.3.0(beta) 发到平台**（GitHub Release + Modrinth + CurseForge）。

### ① 为什么重写：那一章已经变成第二份更新日志

玩家原话：「我发现手册内的介绍内容是陈旧的。而且其称指挥棒的介绍内容显得过于冗杂（把更新日志什么都搬上去是很不对的行为。这些都应该搬在后面的更新日志里面。），主要要介绍现在的功能。」

- **事实核对下来，玩家说得对**。旧版 `rideGuide()` 从 714 那一版建章起，**每一轮修复都往正文里追一段**——「实测七百二十三…「实测七百二十四…「实测七百二十五…一路堆到「实测七百四十七」，正文里同时存在**至少 15 处「实测NNN」的叙事块**，写的是症状 / 根因 / 改法（「旧版怎样、现在改成怎样」）。那是**更新日志的写法**，不是手册的写法：手册要回答「现在怎么用」，而它花了大半篇幅讲「以前为什么坏」。
- **这一章原本 561 行，重写后 130 行**（两树同步）。
- **移出去的东西**：全部「实测NNN / 旧版…现在…」叙事块、根因分析、以及已经被后继版本推翻的中间态描述（例如「悬空鞍位降级方案」那套内部措辞、七百三十八 的「更正」条）。这些内容**在更新日志章里本来就有一份**（`changelog.txt` / `CHANGELOG.md`），正文不必再抄一遍。
- **移出去、但换了说法的**：凡属「当前行为」的，按现在时重写进正文；凡属「过程记录」的，只留更新日志。

### ② 重写时补齐的三处「描述不准」（玩家点名的）

玩家原话：「并且存在一些错误，比如现在必须要先点击坐骑再点击女仆。左键可以切换正副驾驶座，这些都没有一个详细而精准的描述。」

- **绑定顺序**：旧正文的小标题写的是「一、怎么绑（**顺序随意**）」，还在正文里明说「**反过来也行**：先右击女仆选中她、再右击坐骑」——**这是过时且错误的**：七百二十六 起已经**固定为「先右击坐骑、再右击女仆」**，先选女仆会被回一句「先用骑乘棒右击坐骑，再来右击我」（`RideBindManager` 实证）。新正文把小标题改为「顺序固定：先点坐骑、再点女仆」，并写明「反过来不行」及其原因。
- **左击换座**：`RideBindManager` 里七百四十一 / 七百四十二 就做了「手持指挥棒**左击** = 主驾 ↔ 副驾 换座」（走 SWB 自己的 `changeSeat`），但**旧正文只在排查段里一笔带过**，没有正面章节。新正文新增「三、手持指挥棒的三种操作」整节，把**右击 = 选中 / 配对 / 解除、左击 = 换座、空手右击 = 坐副驾、拿棍子绝不登乘**四种情形**分开写清**（这正是玩家说「最容易记混」的地方）。
- 顺带纠正一处历史遗留：旧正文的「六、模组坐骑」还在讲「**顺序随意**」，与「八、」那一节的固定顺序自相矛盾——重写后只有一套口径。

### ③ 冰火传说龙：从「五大节 × 八条技术说明」压成一句「能挂但不建议」

玩家原话：「同时手册里面冰火传说的龙也不要过多介绍了。只说一下现在大概可以把女仆挂在龙上面，但是观感上十分违和，所以不建议大家这么做。本来开发者想做女仆乘坐在龙上的，但是冰火传说龙的代码的防护太强了所以很难让女仆骑上去并操纵。」

- 旧正文用**八个编号条目 + 前后五处补充**讲龙的悬空鞍位实现（「她不是乘客了」「位置每拍摆到龙给玩家的鞍位」「传送只传她不传龙」…）以及历轮「横跳 / 模型只剩一块 / 被咬死」的修复史。
- 新正文压成**一段**：龙可以绑、女仆会被摆到**龙给玩家的那个鞍位**上；但**观感上十分违和**（摆位靠公式硬算，飞起来、转身时有明显错位），**不推荐**；并如实写出**开发者的原意是做「真正乘坐在龙上」，但冰火传说龙的代码防护太强，很难让女仆骑上去并操纵**，最后只能退成这个折中。
- 排查段里对应的「龙背漂移 / 怎么下不来 / 光标不熄」三条也一并收成简短一行。

### ④ 六台不可驾载具写进手册（承接 747）

- 新增「六、这六台载具她不开」整节：点名 **Ju-87 斯图卡轰炸机 / A-10 雷电二攻击机 / AC-130H 空中炮艇 / KV-16 幽灵战斗机 / 汤姆 F6F / 迷你快艇**，写明拒绝气泡原文，并**如实交代两类原因**——固定翼「操作要求太高」（没有竖直轴，高度要靠速度与俯仰积分）、汤姆 F6F 与迷你快艇「代码上直接认准玩家」。
- 新增「七、投弹安全高度（基洛夫先拉高再投）」整节（默认比目标高 20 格，填 0 关闭）。
- 原「五、数值」「七、排查」两节按 747 的新配置项（`投弹安全高度`）与新的日志分类补全。

### ⑤ 首个公开发布：v1.3.0(beta)

- 平台：**GitHub Release**（tag `v1.3.0-beta`，prerelease）+ **Modrinth**（`version_type = beta`）+ **CurseForge**（`releaseType = beta`），两个 jar（Forge 1.20.1 / NeoForge 1.21.1）同批。
- **版本号仍写作 `1.3.0`（mods.toml / neoforge.mods.toml 不变）**，平台上的版本号与标题写 `1.3.0-beta`，并在说明里标明这是 beta。
- **发布声明（release notes）单独成文**：按玩家要求「根据此版本与上个版本的 change log 之间的差别来写」——上一个是已公开发布的 **v1.2.4(b)**，所以声明覆盖**整条 1.3.0 beta 线**（武装拴绳 / 骑乘指挥棒 / 卓越前线载具 / 飞行载具 / 扫帚与空袭 / 仿创造飞行 / 面板与文档…），并**特此声明六台载具被开发者列入黑名单、无法被骑乘指挥棒选中**（因为那套驾驶逻辑做不出来），以及**龙只能挂、观感违和、不建议使用**。
- 发布脚本从写死的 `1.2.4` 路径改为读 `1.3.0` 的两个 jar；`store/version_changelog.md` 仍是完整日志，平台 `changelog` 字段改读**专门的发布声明文件**（此前那个字段塞的是整份 46 万字符的历史，平台侧根本读不完）。

### 验证

- 两树 `javac` **0 错误**；`_mixchk.py` PASS=186 FAIL=0 UNRES=0。
- 只读核对 `_f748_guidechk.py`：新章的 9 个新措辞在两树 jar 里**全部存在**，15 个旧叙事措辞**全部消失**（`GUIDECHK: OK`）。
- `build_promaid.py` / `build_promaid_neo.py`：条目 944 / 947，`verify_jar_classes: OK`（mixin 86/86 与 85/85）、`lang json: OK`、`MISSING: none`。
- 部署：1.20.1 实例 + `pack1201` 服务端包 + 1.21.1 实例三处同 jar，逐处校验 SHA-256。

## 实测七百四十七【投弹安全高度 + 六台载具不可驾（两版同改）】

> 承接 七百四十六（1.20.1 Goety 位移聚晶非 OP 入口）。本次两条，**1.20.1（Forge）与 1.21.1（NeoForge）两树同步改**。

### ① 基洛夫等飞行载具：投弹前先爬升到安全高度（默认比目标高 20 格）

玩家原话：「女仆在乘坐基洛夫空艇时，如果要进行投放炸药，那么要先自己向上飞 20 格，防止被炸到。」

- **根因**：基洛夫的唯一武器就叫 `Bomb`，往下丢的是 SWB 里那颗 **42 格爆炸半径**的航空炸弹（`bor_57`）——她贴地投弹等于把自己也圈进爆心。旧版只按"接敌高度 15 格"站位，对"能不能安全投弹"毫无概念。
- **做法**：新增配置 `combat.ride.bombStandoff`（面板「骑飞行载具·悬停与盘旋 → 投弹安全高度」，默认 **20 格**）。她骑**飞行载具**且这一下有**投弹意图**时：
  1. 机身高度被抬到「**目标高度 + 本项格数**」——走的是与「接敌升到敌上」**同一条通道**（飞艇用升力轴 `liftSpeed`、直升机用总距 `power`），不新增控制档；
  2. **没爬到位就按住这一发不投**（不耗弹、不占冷却、不占 BOMB_NEXT 间隔），到位了才照常投。
- **什么叫"这一下有投弹意图"**：车上当前那门武器**名字里带 Bomb**（基洛夫唯一武器就是它），**或**她自己背包里带着能放的炸料（TNT / 末地水晶 / 重生锚 / 床）。不投弹时（例如只开机炮护航）高度照常按接敌档走，**不会白白飞高丢掉命中率**。
- **闸落在三处**（都在两树同步）：车上炸弹武器 `MaidMountCompat.tickAttack`、她的 TNT 投掷 `BombTntTick.flushTnt`、她的放置链路 `MaidBombing.tryStartMelee`。三者共用 `MaidAirCombat.holdDropForStandoff` 一问。
- **填 0 = 关掉这道闸**；只作用于飞行载具，地面坦克/装甲车不受影响；「骑飞行载具·悬停与盘旋」总开关关着时本闸也不生效（那时没人负责把她抬上去，再按住投弹等于永远不投）。
- **性能**：投弹意图判定里的背包扫描按女仆缓存 10 拍（0.5 秒）——高度那几处每拍都会问到它。
- 日志：搜「空战」看 `投弹安全高度未到（现在 X，目标上 20 格 = Y）→ 先爬升、这一发按住`（每场遭遇只写一行）。

### ② 骑乘棒不再绑定六台"认人"的载具

玩家原话：「骑乘棒没有办法绑定 Ju-87 斯图卡轰炸机、A-10 雷电二攻击机、AC-130H 空中炮艇、汤姆 F6F、KV-16 幽灵战斗机、迷你快艇。在手册里面就写这些载具要么对于操作的要求太高，要么在代码上直接认准的人。让女仆来指会搞得一团糟，所以没有办法骑。」

- **名单**（实体注册名，两树共用，SWB 实体 id 逐字相同，jar 实证）：`superbwarfare:ju_87` / `:a_10a` / `:ac_130h` / `:kv_16` / `:tom_6` / `:tiny_speedboat`。
- **为什么这六台**——不是"她开得不好"，是**这车根本不接受非玩家驾驶**：
  - **操作要求高**（Ju-87 / A-10 / AC-130H / KV-16）：全是**固定翼**。固定翼**没有竖直轴**，高度只由「速度 + 俯仰」积分出来（`aircraftEngine` 反编译实证），起飞要助跑、盘旋要维持速度；交给"去某个点"这一层意图，结果必然是一头栽地或一路飞走。玩家自己飞都得练，女仆没有"练"这件事。
  - **代码上直接认准的人**（汤姆 F6F / 迷你快艇）：汤姆 F6F 的油门与姿态**整段只写给「乘客是玩家」**那一支（`tomEngine` 反编译实证），女仆是生物、压根进不去；迷你快艇只有一个座位、走水面物理。与其继续打补丁，不如照玩家的话——**认人，不给她开**。
- **落点**：`MaidMountCompat.isUnridable(Entity)` + `denyReason` 最前面一道闸（在"报废 / 没座位"这些技术判据**之前**，玩家看到的是"这架认人"而不是"没有能坐的位子"）。`RideBindManager` 的绑定/解绑/客户端独占右击三处 `recognized` 集合都收进它——**否则这一下右击会被静默吞掉、玩家等不到任何解释**。
- **拒绝气泡**：`这架 <中文名> 操作门槛太高（或只认玩家来开），我驾驭不了～`。中文名写死一份小表（这六台的名字散在 SWB 两套 lang 键里、还要判语言；固定六台直接写死最稳，也不受 SWB 换翻译影响）。
- **其余载具一律不受影响**：坦克 / 装甲车 / 直升机 / 飞艇（基洛夫等）照旧能骑能开能打。
- **旧存档自愈**：链路校验每拍扫到"她骑的是名单里的车"就自动解绑（走 `releaseMaidQuiet`，坐姿 / 重力 / 龙的行动档都还原）。搜日志「骑乘指挥棒」能看到解绑那一行。

### 配置 / 手册

- 新增配置项 `combat.ride.bombStandoff`（`投弹安全高度`，默认 20.0，范围 0~64）+ 面板行 + 两份 lang（`config.promaid.ride.bombStandoff`）。
- 手册「骑乘指挥棒」章节新增两节：**八、这六台载具她不开**、**九、投弹安全高度：基洛夫先拉高再投**（两树同步）。

### 验证

- 两树 `javac` **0 错误**；`_mixchk.py` PASS=186 FAIL=0 UNRES=0（本轮无新增注入点）。
- `build_promaid.py` 944 条目 / 637 class，`verify_jar_classes: OK`（mixin 86/86）、`lang json: OK`、`MISSING: none`；`build_promaid_neo.py` 947 条目 / 640 class，mixin 85/85。
- 新增只读核对 `_f747_jarchk.py`：33 项检查 FAIL=0（六个 SWB id、`bombStandoff` 键、`holdDropForStandoff`、`isUnridable`、两份 lang、两节手册文案都在包里）。
- 两树新逻辑逐行对照（`_f747_rev.py` / `_f747_rev2.py`）：除三处加载器专有写法（注册表 / `m_20202_`→`getVehicle` / `m_6084_`→`isAlive`）外**完全一致**。
- 部署：1.20.1 实例 + `pack1201` 服务端包已更新并校验 SHA-256 一致（`8b3f6fd07f0090e2`）；1.21.1 实例当时**被运行中的游戏占用**（写入 Permission denied），保留上一版 jar —— 需等游戏退出后重跑部署。

## 实测七百四十六【1.20.1 侧：飞行聚晶（Goety 位移聚晶）非 OP 入口 —— 与 1.21.1 对齐】

> 承接 七百四十五：那一版只把「飞行聚晶不用 OP 也能开」做在了 **1.21.1（NeoForge）**。本版把同一套补到 **1.20.1（Forge）**，两树口径逐字对齐。

### 一、为什么这一版只动 1.20.1

- 需求方原话：「我去 1.21.1 里面测试，你帮我搞一下 1.20.1 的飞行聚晶非 OP 入口和 Goety 部分。」
- 七百一十八 在 1.21.1 做了 Goety 位移聚晶，但**1.20.1 那一棵树整个 `com/maidsmart/goety` 包从来就不存在**——所以 1.20.1 的玩家不但没有非 OP 入口，连这一档功能都没有。本版把**整个 Goety 档 + 非 OP 入口**一起补进 1.20.1。

### 二、1.20.1 的 Goety 是同一套 API（javap 实证）

对 `[诡厄巫法] goety-2.5.58.4.jar`（部署目录里的实际版本）逐项核过，与 1.21.1 的 3.1.5.1 **类名/方法名完全同名**，所以软兼容层的反射一个字节都不用改：

- 包根仍是 `com.Polarice3.Goety`（`api.items.magic.IWand` / `common.magic.Spell` / `common.magic.spells.wind.FlyingSpell` / `...LaunchSpell` / `utils.WandUtil` / `common.magic.SpellStat`）；
- `IWand.getFocus(ItemStack)`、`Spell.mobSpellResult(LivingEntity, ItemStack)`、`Spell.rightStaff(ItemStack)`、`WandUtil.getStats(LivingEntity, ISpell)`、`SpellStat.getPotency()` —— 签名逐字相同；
- 物品注册名同样是 `goety:flying_focus`（Flight Focus）/ `goety:launch_focus`（Launching Focus）/ `goety:wind_staff`（Wind Staff）/ `goety:focus_bag`（Focus Bag），lang 实证。

**只有三处加载器差异**（已按 1.20.1 改写）：
1. 物品注册名：`ForgeRegistries.ITEMS.getKey(...)`（1.21.1 是 `BuiltInRegistries.ITEM`）；
2. 物品栏 capability：`ItemStack.getCapability(ForgeCapabilities.ITEM_HANDLER, null)` 返回 `LazyOptional`，要 `.orElse(null)` 拆壳（1.21.1 是一参直接返回 `IItemHandler`）；
3. `maid.getMaidBauble()` 在 Forge 上**直接就是 `IItemHandler`**（1.21.1 要 `instanceof` 一次）。

### 三、新增到 1.20.1 的内容

- **核心三件**（与 1.21.1 逐字对应，只换 SRG 名）：`MaidGoetyCompat`（软兼容层）、`MaidGoetyFlight`（推进器：ARRIVE/FOLLOW/COMBAT 三档）、`MaidGoetyAuto`（自动档：跟主人跨地形，16 格起飞 / 6 格落地，默认关）。
- **非 OP 入口两条**（与仿创造飞行同口径）：
  - 右键女仆 → 配置界面第三行 **「飞行聚晶：开/关」**（`MaidConfigGoetyMixin`，y=36；本树「创造飞行」在 y=26，按钮 164×13，都在 TLM 原生开关列 y=52 之上）；
  - 快捷键 **「切换女仆的飞行聚晶自动档（准星对准女仆）」**（`MaidGoetyKeysClient`，默认未绑定，玩家自绑）。
- **网络层** `MaidGoetyNetworking`：Forge `SimpleChannel`（频道 `maid_smart:goety`，int 判别号 0/1），服务端校验**主人本人或 OP + 距离 ≤ 8 格**，并保留**保真门禁**（她身上没有飞行聚晶就不给开，如实告知缺哪一样）。
- **命令入口** `/maid_smart goety_*`（OP，测试/服主用）：`goety_fly` / `goety_follow` / `goety_combat` / `goety_boost` / `goety_auto` / `goety_stop` / `goety_status`。
- **`MaidGoetyMoveSuppressMixin`**：推进期间取消 `MoveToTargetSink`（1.20.1 的 SRG 名 `m_6725_`），从源头掐掉走路目标——否则飞行聚晶取 `getLookAngle()` 时会被 TLM 的跟随目标掰走 yaw、绕着主人画圈。
- **接线**：`ProMaidMod` 注册网络层 + 挂两个 handler；`ProMaidExtension.onRegisterCommands` 注册命令；`mixins.promaid.json` 的 mixins/client 各加一项；lang 中英各加 `key.promaid.goety_toggle`。

### 四、SRG 名怎么定的（不靠猜）

本树手工编译、生产环境跑 SRG 类，所以每个原生方法名都必须是自己解析出来的。做法是**用本地映射链**算：

- `client-1.20.1-*-mappings.txt`（Mojang 官方，mojmap ↔ obf）+ `joined_1201.tsrg`（obf ↔ srg，工程内既有产物）；
- 写出 `_f746_srg5.py`：把 mojmap 方法声明的**参数/返回类型编码成 JVM 描述符**并翻译类名，再按 `(obf类, obf名, obf描述符)` 精确join（早先只按名字 join 会撞上一堆同名重载，全是错的）；
- 再拿 `_f746_rev.py` 反向核对（SRG 令牌 → 类/描述符/mojmap 名），并与**本树既有写法**逐一对齐（`m_20184_`=getDeltaMovement、`m_20256_`=setDeltaMovement、`m_146922_`=setYRot、`m_20096_`=onGround、`f_19789_`=fallDistance、`f_19812_`=hasImpulse……）。

一处**踩过的坑**：`EntityArgument.getEntities(ctx, name)` 与 `getEntity(ctx, name)` 在 1.20.1 是两个不同 SRG（`m_91461_` / `m_91452_`），一开始写成了后者，编译报「Entity 没有 iterator()」——已改正。

### 五、验证

- 1.20.1 树 `javac` **EXITCODE=0**（0 错误）；
- `_mixchk.py`：`PASS=186 SKIP=10 UNRES=0 FAIL=0`（比上一版多 3 项 = 新增的三个注入点都被目标类自己声明）；
- `build_promaid.py`：jar **944 entries / 637 class**，`verify_jar_classes: OK`（mixin 注册表 86/86 全登记、jar 无多余 class）、`lang json: OK`、`MISSING: none`；
- `_f746_jarchk.py`（jar 内容闸，36 项）：**FAIL=0**——Goety 五个类 + 两个嵌套包类 + 两个 mixin + 命令都在包里；命令七个字面量齐全；GUI 行文案「飞行聚晶」与反射 SRG 名 `m_142416_` 都在；**并断言编译产物里没有 mojmap 残留**（`setDeltaMovement` 这类字面量一个都不能有，防止 SRG 漏改）。
- 1.21.1 树**本次未改动**（它的 Goety 与 745 已就位）。

## 实测七百四十五【1.21.1 + 1.20.1 两树镜像：飞行载具急停四类通用 + 空中炮艇真会起飞 + 载具没弹自动换模式 + 飞行聚晶补非 OP 入口】

### 一、玩家四条原话

1. 「你的意思是聚晶必须要使用指令这些 OP 权限才可以使用吗？常规生存不能使用？」
2. 「直升机好了。但是极乐恶魂以及其他的飞行载具都没有像直升机那样的同款急停。导致漂移非常严重。」
3. 「空中炮艇现在可以大致跟随主人的轨迹了，但它仍然飞不起来。」
4. 「我发现女仆在操纵载具进行攻击的时候，它只会检查当前模式下是否存在对应的弹药，举个简单的例子，假设模式 A 可以发射 A 弹药，模式 B 可以发射 B 弹药，玩家初始给直升机的状态为模式 A，但是只有 B 的子弹。那么女仆就不会操纵直升机发射弹药，必须要玩家手动调整到模式 B，这就很烦。应该在没有发现链路之后，再检查一下其他模式有没有对应的弹药，然后考虑切换到那个模式。」

### 二、飞行聚晶补非 OP 入口（1.21.1 侧）

- **根因**：七百一十八 那一版把 Goety 位移聚晶**只**挂在了 `/maid_smart goety_*` 命令上，而那一批全部 `requires(hasPermission(2))`（OP 专属）。普通生存玩家即使身上真有飞行聚晶、也把女仆驯服了，**开不了这个功能**——只有服主能开。而"仿创造飞行"（同类飞行功能）一直有主人可用的路径。
- **新增两条主人自己就能用的入口**（与"仿创造飞行"同口径）：
  - 右键女仆 → 配置界面 → 第三行 **「飞行聚晶：开/关」**（`MaidConfigGoetyMixin`；布局：AI 记忆 y=8、创造飞行 y=22、飞行聚晶 y=36，三个按钮 164×13，都在 TLM 原生开关列 y=52 之上，互不遮挡）；
  - 快捷键 **「切换女仆飞行聚晶自动档（准星对准她）」**（`MaidGoetyKeysClient`；默认未绑定，玩家自绑）。
- **两条路开的是同一个东西**：她的**自动档**（`MaidGoetyAuto` 那个默认关的开关）。权限与仿创造飞行完全同口径：**主人本人或 OP**，且她在加载范围内、距离 8 格以内。**保真门禁照旧**——她身上没有飞行聚晶时直接告诉你缺哪一样、不给开。
- 「立刻飞往某坐标/某目标」那种带参数的仍然只有 OP 命令。
- 新增：`MaidGoetyNetworking`（C2S 切换 + S2C 状态）、`MaidGoetyKeysClient`、`MaidConfigGoetyMixin`（已进 `mixins.promaid.json` client 列表）、`MaidGoetyAuto` 加客户端缓存；lang 新增 `key.promaid.goety_toggle`（中英）。

### 三、飞行载具急停四类通用 + 空中炮艇真会起飞（两树）

- **急停不再专属直升机**（点2）：
  - 根因：七百三十一/七百三十二/七百四十三 那三记刹车（**到位硬刹 + 每 0.5 秒周期脉冲 + 水平速度硬上限**）**只写在 `driveFlight` 的 `if (heli)` 块里**。飞艇（极乐恶魂/基洛夫）、固定翼、汤姆6 全程没有急停；它们松油后引擎每拍只按 0.9~0.96 衰减（反编译 `airShipEngine:1117`、`:1178`），到点还会滑出很远。
  - 改法：抽成 `flightBrake(mount, hardBrake, fighting)`，**四种飞行档共用**（直升机/飞艇/固定翼/汤姆6）；`stopVehicle` 收手时也抹水平速度（`FLIGHT_STOP_RETAIN=0.15`）。**只动水平分量，竖直分量一个字不碰**（空中失去升力=掉高度）；减速方向对 SWB `setDeltaMovement` 是直通的，安全。
- **空中炮艇（AC-130H）起飞**（点3）：
  - 根因（反编译 `aircraftEngine` 实证，三条叠加）：① 俯仰**只在空中才写**（`:801 if(!onGround)`）⇒ 地面写 mouseY 是空操作；② 升力**正比当前速度**（`:877 upVec*…*speed*…`）⇒ 静止时 speed=0 → 升力恒 0；③ 地面推力也**正比当前速度**（`:702`），且地面摩擦 `f=0.497+0.45|dotView|`（`:701`）远大于推力。三条合起来=**它是一架要"跑道助跑"的真飞机，没有初速就永远起不来**；而唯一能让她停住盘旋的 loiter 又要求 `!onGround`（`baseTick:3845`）——**死锁**。
  - 改法：贴地且确实要去某处时，**每拍写抬头角 20° + 直接补竖直速度 0.25/拍 + power 拉满 1.0 + 顺手写好 loiter 参数**，把 `onGround` 顶掉；一旦离地立刻交回引擎自己的俯仰/loiter。稳态推算：地面摩擦 f≈0.497、重力 0.06 ⇒ `v≈0.128 格/拍`（≈2.5 格/秒），足以离地、不冲天。日志新增「固定翼起飞助跑」。
- 常量新增：`AIRCRAFT_TAKEOFF_PITCH=20°`、`AIRCRAFT_TAKEOFF_LIFT=0.25`、`FLIGHT_STOP_RETAIN=0.15`、`BRAKE_FLAG_*`。

### 四、载具"当前模式没弹就自动换模式"（两树）

- **玩家原话即规格**：模式 A 能发 A 弹、模式 B 能发 B 弹，玩家初始给的是 A 但只有 B 的子弹 → 女仆不开火。要求：**没发现链路之后，再检查其他模式有没有对应的弹药，然后切换到那个模式**。
- **SWB 的"模式"是两级**（反编译实证）：
  1. **逐座武器**：一个座可挂多门炮，`getSelectedWeapon()` 记录每座选中的是哪门（`getWeaponIndex(int)` / `setWeaponIndex(int,int)` / `getGunName(int,int)`，`VehicleEntity:2795/2820/939`）——玩家说的"模式 A/B"多半是这一级；
  2. **同一门炮的弹种**：`selectedAmmoType` 指向 `GunProp.AMMO_CONSUMER` 表第几项（`changeAmmoConsumer(int,Entity)`，`GunData:607`）。
- **实现**：`aimBallistic` 在 `ensureAmmo` 之后调 `ensureUsableAmmoMode(mount, maid)`：当前武器/弹种**真没弹**时 → 先在同一门炮里**换弹种** → 不行再**换到同一座上另一门有弹的炮**；换完立刻用新模式瞄+打。判据用 `AmmoConsumer.count(gunData, supplier)` 与 `currentAvailableAmmo`——与引擎 `canShoot` 内部同一套账（`hasEnoughAmmoToShoot → countBackupAmmo → countBackupAmmoItem`）。
  - **关键修正**：`currentAvailableAmmo` 在"不背包取弹"时返回的是**弹匣里现有几发**（`GunData:809`），不是"这个模式配得上的弹药有多少"——所以判据必须读 `selectedAmmoConsumer().count()`（弹匣空了但容器里有对得上的弹=该装填，不是该换模式）。两者取或。
- 新增反射：`getWeaponIndex(int)` / `setWeaponIndex(int,int)` / `getGunName(int,int)` / `GunProp.AMMO_CONSUMER` / `GunData.get(GunProp)` / `changeAmmoConsumer(int,Entity)` / `AmmoConsumer.count(GunData,Entity)`；新增日志「模组坐骑·弹种」「模组坐骑·换炮」。

### 五、本批不动的东西

- 不改物品/合成、不加新物品、不加开关键（沿用现有 `modMounts`/`modMountFire`/`airCombat`）。
- 不动扫帚/空袭链路、不动原版兽那条 `GroundPathNavigation` 通路。
- 飞行聚晶的**1.20.1 树适配**仍不在本批（七百一十八 已明确"需要时另起一批"）；本批 1.20.1 树只镜像了第二/三/四节那三项（急停/炮艇起飞/换模式）。
- 版本仍是 v1.3.0(beta)（同名覆盖），**不发平台**。

### 六、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` **PASS=183 SKIP=10 UNRES=0 FAIL=0**。
- 两树 jar 重新构建（含 `verify_jar_classes.py` + lang json 校验）。
- jar 内容核对：新文案（「急停四类通用」「固定翼起飞助跑」「模组坐骑·弹种」「飞行聚晶」非 OP 入口）进包、旧口径不在。
- 三处部署目标（1.20.1 实例 / 1.20.1 服务端包 / 1.21.1 实例）SHA-256 全 MATCH。
- 四份 changelog 镜像（根 `CHANGELOG.md` + 两棵树 `changelog.txt` + `store/version_changelog.md`）均加本段，保持各自 BOM+LF。

## 实测七百四十四【1.21.1 + 1.20.1 两树镜像：手册补章（仿创造飞行 1.20.1 侧 + 诡厄巫法位移聚晶 1.21.1 侧）+ 战斗章交叉引用去残】

> 版本号不变，仍是 v1.3.0(beta)。两树手册各补一段、同名 jar 覆盖；**不发平台**。
> 本批**只动手册文本**（`GuideChaptersFlight` / `GuideChaptersCombat` 的章节字符串），
> 零逻辑改动、零配置项改动、零新文件。

### 一、玩家两条原话

1. 「那我们补一下这个章节」
2. 「还有一件事，PR 里面提到的飞行聚晶现在可以使用了吗？该怎么使用？如何激活仿创造飞行？」

一句话回答：**都能用**，缺的不是代码，是**手册没写**。

### 二、1.20.1（Forge）侧：补「仿创造飞行」整章（此前是空缺口）

**根因不是功能缺失，是文档缺失。** 逐文件核对后确认：1.20.1 树的仿创造飞行**实现是齐的**
（`flight/MaidFreeFlightController`（1261 行）/ `Kit` / `Handler` / `Flags` / `KeysClient` /
`Networking` / `AnimState` 七个类 + `FreeFlightWalkGuardMixin` / `MaidConfigFreeFlightMixin` /
`MaidFreeFlightAnimMixin` / `MaidFreeFlightAnimInvoker` 四个 mixin + 配置项 + 命令 + 键位 + 面板行
+ lang 键），**唯独手册里一个字都没有**——`promaid_src` 的 `GuideChaptersFlight.java` 里
「创造飞行 / 仿创造飞行」出现 **0 次**，而战斗章却写着「见手册『仿创造飞行』一节」，
**指向一个不存在的章节**。

**修法**：在 1.20.1 树 `GuideChaptersFlight.flightGuide()` 尾部、紧跟空袭章「注意」那一段之后，
补一整节「【实测七百〇二】仿创造飞行（1.20.1 精简版）」，七小节：
①这是什么 ②资格怎么算 ③她会怎么飞（智能待命 / 软着陆 / 有敌人时）④每只女仆单独关掉（含键位）
⑤有飞行能力就不搭路 ⑥本来要走过去的活也交给飞 ⑦排错与验收入口。

**必须按 1.20.1 的真实能力写，不能照抄 1.21.1 那一节**——两边差的是**资格探测路数**：

| 资格路 | 1.21.1（NeoForge） | 1.20.1（Forge） | 1.20.1 为什么没有 |
|---|---|---|---|
| 资格物品表 | 有（四种写法：id / `#标签` / `@组件` / `@组件~文本`） | 有（两种写法：id / `#标签`） | 数据组件（`DataComponentType` / `stack.getComponents()`）是 1.20.5+ 的机制；1.20.1 的 `ItemStack` 还是 NBT |
| 资格效果表 | 有 | 有 | — |
| 重力属性 ≈ 0 | 有 | **无** | `Attributes.GRAVITY` 在 1.20.1 的 `Attributes` 里没有这个字段（1.20.5 才有） |

所以面板行数是 **1.20.1 八条 / 1.21.1 九条**——差的正好是「重力归零也算资格」那一条。
手册里把这两处差别**明写出来并给出原因**，免得玩家在 1.20.1 里照 1.21.1 的说明去找
一个不存在的开关。

### 三、1.21.1（NeoForge）侧：补「诡厄巫法位移聚晶」实操节

这是**同一个病**：七百一十八 收编了社区 PR #26 的 Goety 半截（`goety/` 三个类 + 两个命令 +
一个 mixin + 三处 `ProMaidExtension` 注册），**代码在包里、手册里 0 处提及**
（全手册搜「聚晶」只在别处出现过一次，与此功能无关）。玩家问「现在可以使用了吗、该怎么用」
本身就是文档缺口的证据。

**修法**：在 1.21.1 树 `GuideChaptersFlight.flightGuide()` 尾部补一整节
「【实测七百一十八】诡厄巫法（Goety）位移聚晶」，六小节：

- **这是什么**：`goety:flying_focus`（飞行聚晶，每 tick 把速度覆盖成视线方向 × 0.5~1.0 格/tick）
  与 `goety:launch_focus`（发射聚晶，一次性冲量）；Goety 里只对玩家生效（写死 `instanceof Player`），
  我们走它**给怪物留的正门** `Spell#mobSpellResult`（不查灵魂、不扣灵魂、不进冷却）；
  **威力完全由聚晶给，我们一个数字都不改**，只管「瞄准 → 续放 → 到地方收手」。
  三种飞行的位置：鞘翅 = 战斗机、仿创造飞行 = 直升机、**本档 = 喷气式**（约 10 格/秒，
  持 `goety:wind_staff` 翻倍到约 20 格/秒，不吃耐久、不耗烟花）。
- **怎么用（命令，都仅 OP）**：`goety_fly <xyz> [女仆]` 飞坐标（巡航→下降→落地收手）、
  `goety_follow <实体> [女仆]` 跟飞、`goety_combat <实体> [女仆]` 绕敌盘旋
  （复用上游 `MaidBroomDrive.combatPoint` 的接敌机动几何，五种机动照跑）、
  `goety_boost [女仆] [远离谁]` 发射聚晶加力、`goety_stop` 收手、`goety_status` 查状态。
- **她会自己飞（默认关）**：`/maid_smart goety_auto on [女仆]`——主人拉开 16 格自动起飞追、
  追到 6 格落地交还 TLM 跟随（滞回）；开关存在**她身上**（随魂符 / 存档走）；
  已有别的飞法时**一律让位**。
- **两个前提**：装了 Goety + **她身上真的有那枚聚晶**（法杖当前槽 / 聚晶包 / 任意容器任一）——
  这是刻意的**保真门禁**（没道具就不飞，不与"凭空给推进器"的作弊口径妥协）。
- **前中期还是后期**：飞行聚晶是**后期**（材料含鞘翅且**会吃掉那张鞘翅**），是鞘翅的平级替代；
  **发射聚晶才是前期**（`goety_boost` 那一档）。
- **排错**：`goety_probe` 只读扫描、`goety_probe cast flying 5` 无头实测入口，
  日志搜「Goety推进 / Goety自动 / Goety加力 / Goety探针」。

### 四、顺手修掉一处交叉引用残句（两树）

两树的战斗章都还写着「**（这条只在 1.21.1 树上有意义，见手册「仿创造飞行」一节。）**」——
1.20.1 侧现在**已有**那一节了，括号里那句「只在 1.21.1 树上有意义」就是**过时口径**。
两树一并改成「（见本手册飞行章「仿创造飞行」一节。）」。jar 内核对断言了旧句**已消失**。

### 五、本批不动的东西

- **不动任何逻辑**：控制器 / 探测 / 网络 / 命令 / 配置项 / mixin 一个字节没改。
- **不动版本号**：仍是 v1.3.0(beta)，同名 jar 覆盖。
- **不发平台**：CurseForge 那个 Pending Release 照旧不动。
- 辅助件一律 `_f744*`（gitignore 内）。

### 六、验证与交付

- 两树 `javac` **EXITCODE=0**。
- `_mixchk.py` 注入点审计 **PASS=181 SKIP=10 UNRES=0 FAIL=0**（本批不加注入点）。
- 打包门禁全过：Forge **930 entries**（623 class）、Neo **941 entries**（634 class）；
  `verify_jar_classes` OK、`lang json: OK`、`MISSING: none`。
- `_f744_jarchk.py` 只读核对 **PASS=20 FAIL=0**：两树新文案均在 `GuideChaptersFlight.class` 内、
  两树旧口径均已从 `GuideChaptersCombat.class` 消失。
- 部署三处（1.20.1 客户端 / 1.21.1 客户端 / pack1201 服务端），事后哈希比对一致。

## 实测七百四十三【1.21.1 + 1.20.1 两树镜像：飞行载具四修】

> 版本号不变，仍是 v1.3.0(beta)。**两树镜像**（1.21.1 NeoForge + 1.20.1 Forge）；同名 jar 覆盖；**不发平台**。

### 一、玩家原话（四条）

1. 「我发现直升机盘旋没有上个版本稳定了。上个版本不太容易出现离敌人范围太远的情况，可以正常盘旋，但是这个版本明显就出现了刹不住车的情况。可能是某些链路被你改了。要求就是女仆在驾驶直升机的时候要进行停顿，不能滑出太远，或者获得太大的速度。」
2. 「ACH13 空中炮艇，我发现女仆根本就不会骑这个东西。全程乱窜并且没有停止和飞行能力。」
3. 「女仆在乘坐极恶乐魂这个飞行载具的时候应该直接套用直升机的那一套电路，现在女仆乘坐它不会在遇到敌人之后自己升高。」
4. 「骑乘汤姆 6F6 的时候女仆不会移动。」

### 二、根因（全部反编译 / 实机日志实证）

**① 直升机"刹不住车" = 741 那一记"机头瞄准"打断了飞行速度环。**

741 在飞行档末尾加了一句 `applyNoseAim(mount)`：只要这车有炮、有敌人，它就把机头按"炮口对准敌人"逐拍掰过去。反编译 `helicopterEngine` 实证：直升机的**水平推力全部来自 `viewVector`**（机头前倾分量）——机头一被掰向敌人，飞行档刚算好的俯仰就被覆盖，高度环与水平环互相打架。实机日志（`promaid.log` 19:51 那一段）正是这么一串：`水平速度=0.42 期望=0.75` 之后紧接 `水平速度=0.31`、`0.11`，机头在 ±6° 之间反复跳，速度起不来也收不住 → 玩家看到的"滑出很远、刹不住"。

**② 固定翼（AC-130H）"乱窜" = 它没有竖直轴，而"停住并盘旋"是引擎自带的一档。**

反编译 `aircraftEngine` 实证：固定翼的高度只由俯仰 + 速度决定，**没有竖直输入轴**，所以旧版给它目标点它只会**一直朝前飞**——实机日志：`距目标=58格 → 170格 → 214格`，一路飞走。而它的"停住并绕圈"是引擎自带的 loiter：`VehicleEntity.baseTick` 实证，`EngineType.AIRCRAFT && getLoiterActive()` 时每拍调 `VehicleEngineUtils.aircraftLoiter(this)`，那个方法自己写 mouseX/mouseY/power 绕 `getLoiterParams()` 给的圆心 + 半径转圈，还自带地形回避。旧版从没写过 loiter 参数，所以那套机制对女仆从来没生效过。

**③ 极恶乐魂（飞艇）"不会自己升高" = 它此前不进空战档。**

`isFlyingVehicle` 原来**只含直升机与固定翼**，飞艇落在外面 → 接敌机动整个不生效，高度永远贴着敌人。而飞艇其实有真正的竖直轴：反编译 `airShipEngine` 实证 `deltaMovement.y += maxUpSpeedRate * 0.06 * liftSpeed`。

**④ 汤姆 6F6"不会移动" = 它的控制整段只给玩家。**

反编译 `tomEngine` 实证：油门/俯仰/偏航/滚转**全部**写在 `else if (passenger instanceof Player)` 分支里。女仆是 Mob，压根进不去那条分支，`power` 恒为 0（另一条 `passenger == null` 分支还会主动 `setPower(0)` 并清空所有输入位）——所以一动不动。

### 三、改法（四条，两树同源）

- **① 退掉飞行档的机头瞄准 + 加水平速度硬上限。** 飞行档（直升机/固定翼/飞艇/汤姆6）**不再写机头**：机头归飞行控制器，命中交给已有的 `MaidShellHoming` 末制导（炮弹出膛后逐拍纠向目标），不再"拿姿态换命中"。另加 `capHorizontal`：把水平分量钳在速度环包络 `FLIGHT_HMAX`（0.75 格/拍）的 **1.25 倍**——巡航/绕圈都在包络内、一点不受影响，只有发动机惯性把她冲过包络两成半以上才动手（"获得太大的速度"）。竖直分量一律不动（空中不能失去升力）。
- **② 固定翼改用引擎自己的 loiter。** 新增 `tryAircraftLoiter`：把目标点写成 loiter 的圆心 + 高度、半径取配置盘旋半径（下限 8 格，保证非 0），并打开 `setLoiterActive`。另补一处前置：引擎那条 loiter 调用要求 `getEngineStartOver()`，而它靠 `power > 0.2` 置位——所以写 loiter 之前先给一个 0.35 的推力脉冲"点火"，起来后 power 由 loiter 自己的 PD 接管。收手（下鞍）时关掉 loiter。
- **③ 飞艇并入空战档，高度走 `setLiftSpeed` 的 PD。** `isFlyingVehicle` 扩到四种引擎（直升机/固定翼/飞艇/汤姆6）；飞艇分支直接把 `liftSpeed` 写成"按当前竖直速度预测落点"的值（钳 ±0.25，与引擎自己的 clamp 同域），偏航/推力仍走位掩码。此后它**跟直升机同一套高度分档**：跟随 = 比主人高 `airAlt`（默认 3）、接敌 = 升到敌上 `fightAlt`（默认 15）。
- **④ 汤姆6 直写 power + 姿态。** 新增 TOM6 分支：清输入位后直接写 `power`（推力，按距离给量，上限 1.0）、`XRot`（俯仰）、`YRot`（机头，走与直升机同源的 `forceHeadingOnto`）。俯仰低空时**强制给 15° 抬头角**保证能离地（纯 P 在起飞时给的角太小、升力不足），过 6 格高差后交回 P。

### 四、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=181 SKIP=10 UNRES=0 FAIL=0**。
- 打包闸门全过：Forge **623 个 class**（930 entries）、Neo **634 个 class**（941 entries），`verify_jar_classes` 全过、`lang json: OK`、`MISSING: none`。
- 部署三处（1.20.1 实例 / 1.21.1 实例 / 服务器包），jar 哈希一致。

---

## 实测七百四十二【左击换座能换过去了 + 载具弹药打不出来/打不准已修 + 末制导后门（炮弹直接指向敌人）】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像）；
> 同名 jar 覆盖；**不发平台**。

### 一、玩家原话（两条）

1. 「从副驾驶换到主驾驶座这个操作一直没能实现。老是提示失败。」
2. 「看一下刚才运行的日志，我发现还是大部分载具上的弹药没有办法发射，或者说准度特别的差。
   帮我检查一下，到底是打歪了还是说压根就没射出来。这边需要再加一个强力的后门让女仆发射出来的
   炮弹拥有直接指向敌人的效果。」

### 二、日志实证（先摆证据，再谈根因）

- 2026-09-30 17:28~17:35 那一局（**741 那份 jar**）里，「左击换座」**打了 5 次**
  （HELICOPTER 2 次、AIRSHIP 3 次），每次都只留下那一行日志、**没有一次成功**。
- 同一局的 `promaid.log` 里「模组坐骑·开火」**一行都没有**；而同一辆车在 **740 那份 jar** 下
  （同一天 06:18~13:08）「模组坐骑·开火」**每 5 秒一条**（warden / cataclysm.ignis）。
  → 结论：**炮弹不是打歪了，是压根没射出来**。

### 三、根因与改法（两条）

**① 左击换座老是失败 → 我们自己的登乘闸把换座自己绊住了。**

左击是靠「手里拿着骑乘指挥棒」触发的；而 722/738 那两道登乘闸
（`EntityBatonMountGateMixin` 拦 `startRiding` 本体 + `RideBindManager.onMount` 拦
`EntityMountEvent`）**就是专门拦「拿指挥棒的人」上车的**。换座那三步是
「主人先下车 → 女仆挪过去 → **主人重新上车**」——最后一步 `startRiding` 恒被自己的闸回绝、
返回 false，于是每次都走「换座失败：坐不回去了」。**女仆那一半其实是动了的**，只有玩家回不去。

- 新增**放行条** `RideBindManager.grantRemount / revokeRemount / remountPermitted`：
  只在「这一次换座」这一个动作期间有效（`grantRemount` 紧贴 `startRiding` 之前开、
  `revokeRemount` 用 `finally` 同一拍关掉），键是**玩家 UUID → 载具 UUID**，
  换成别的车、别的玩家拿棍子都无效。两道闸都读它。
- 客户端那份另发：`MaidSeatNetworking.ArmRemountPacket`（S2C，单 int）。因为服务端上车成功后
  客户端 `handleSetEntityPassengersPacket` 会**本地**再上一次车，而客户端也装着同一道闸
  ——不放行就会出现「服务端上了车、你画面里没动」的抖动。
- **不改口径**：拿指挥棒右击载具仍然**绝对不会自己坐上去**（738 那条一字未动）。

**② 载具弹药打不出来 / 打不准 → 741 按「炮在谁座上」分档分错了，加末制导后门。**

741 把开火权劈成两半：该用的炮**正好在她坐的那一座**时 → 交给引擎内置的「Mob 乘客自动开火」；
否则我们接管。反编译 `VehicleEntity.baseTick:3765-3775` 实证引擎那条链自己有一道硬闸：
`angleTo(炮管当前朝向, 炮口→目标) < 4.0` 才开火。而**机炮挂在车体、没有炮塔**的车
（AC-130H 的 M61/Bofors/M102、AH-6 的 Cannon、飞艇的 Bomb）炮管方向写死在车体坐标里
（`Directions` 不是 `"Barrel"` 而是具体向量/字符串），**根本不能独立转**——除非机头恰好正对
敌人，那道 4° 闸永远不过 → **一枪不发**。AH-6 / 飞艇的机炮正好在座 0（她坐的那一座），
于是全都被分进了「交给引擎」那一档。

改法（恢复并固化 740 的口径，再补两条保证）：

- **不再分档，一律由我们接管**：每拍把她的 `getTarget` 清掉（那是引擎自动开火的唯一开关），
  瞄准与开火都走我们这条路。`gunNameFor` 选炮、按炮名瞄准、按炮名开火（741 已有）。
- **对不准也打（只给不能自己转的炮）**：新增 `AIM_FORCE_TICKS = 40`（2 秒）——
  没有炮塔也没有武器站的车（`hasSlewableMount` 为假）连续 2 秒没对准就直接开火，不再干等。
  **能转的炮（M1A2/Mi-28 炮塔、有武器站的车）不受这条影响**，仍然「等它转到位再打」
  ——`prism_tank` 那种「地上的光棱坦克倒是可以」的一档就是这条在保。
- **末制导后门（玩家点名要的那条）**：新增 `MaidShellHoming`。`tickAttack` 每打一发就记一笔
  「她刚朝谁开火」；服务端每 tick 扫一遍**在飞的卓越前线炮弹**，把「owner 是她」的那些挑出来，
  逐拍把速度方向**朝目标掰**（限速 25°/拍，**只掰方向、不改速度**）。
  - 认弹体只用三层：`instanceof Projectile` + 类名以 `com.atsuishio.superbwarfare.` 开头
    （**用字符串判，不 import 那个模组**）+ `getOwner()` == 我们记的那只女仆
    （反编译 `GunItem.shootBullet` 实证弹体创建后第一件事就是 `setOwner(shooter)`，
    而女仆开火那条重载传的 shooter 就是她本人）。**玩家的炮弹、别的模组的弹一律不碰**。
  - 制导弹（类名含 `Missile`）**跳过**——它们本来就有自己的寻的，插手只会打架。
  - 每发最多纠 60 拍（≈3 秒）就放手（可能是绕圈的火箭弹）；目标死了/不同维度立刻放手。
- 新增诊断日志（都节流 5 秒/车）：`模组坐骑·瞄准`（在不在瞄、差几度、连续没对准几拍）、
  `模组坐骑·开火`（打了几发）、`模组坐骑·制导`（炮弹有没有在被纠向）。

### 四、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` mixin 注入点审计 **PASS=181 SKIP=10 UNRES=0 FAIL=0**。
- 只读核对 `_f742_vt.py`：本轮改动逐项静态核对（两树同 56 项）**FAILS=0**。
- jar 内容核对 `_f742_jar.py`：新符号（`MaidShellHoming`、`grantRemount/revokeRemount/
  remountPermitted/armClientRemount/ArmRemountPacket`、`AIM_FORCE_TICKS/aimStalledTicks/
  hasSlewableMount/lastDesired`、neo 的 `maid_smart:arm_remount` 资源 id）**46 项 FAILS=0**。
- 打包闸门全过：Forge **623 个 class**（930 entries）、Neo **634 个 class**（941 entries），
  `verify_jar_classes` 全过、`lang json: OK`、`MISSING: none`。
- 部署三处（1.20.1 实例 / 1.21.1 实例 / 服务器包），jar 哈希一致。

---

## 实测七百四十一【左击换座 + 同乘直升机不再爬升 + 直升机用上炮塔炮弹 + 炮艇会装弹开火 + 自己上车后绑不上已修】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像）；
> 同名 jar 覆盖；**不发平台**。

### 一、玩家原话（四条）

1. 「加入一个功能，如果玩家处于副座，可以通过手持骑乘指挥棒进行左击，从而把自己交换到主座位。
   再左击一下再换回去。」
2. 「目前和女仆同时乘坐直升机的时候，悬浮状态下还是会有缓慢的上升。而且直升机似乎不会使用炮塔
   里面的炮弹了，地上的光棱坦克倒是可以。」
3. 「我发现还有个空中炮艇，但是女仆不会填充和发射炮弹。」
4. 「有的时候，女仆会自己坐到某个载具上，这个时候就会导致骑乘棒没有办法绑定女仆女仆就会一直卡在
   车上不动。有什么解决方案吗？我想的是在排班表里面的召她过来最好可以规避掉骑乘模式。直接把女仆
   自己召唤过来。」

### 二、根因与改法（四条）

**① 左击换座（新增）—— 与女仆互换座位。**
719 那条口径（反编译实证）是：卓越前线的引擎只认 `getFirstPassenger()`（**座位 0**）——
座位 0 是谁，谁在开这辆车。所以"主人坐主驾"这件事本质就是"把女仆从 0 号座换下来、把主人放上去"，
再按一次就反过来。
- 客户端：`InteractionKeyMappingTriggered`（左击键在真正发起攻击**之前**发，取消即整段跳过）里
  认出「手里拿着指挥棒 + 自己正坐在一辆卓越前线载具上」→ 发 C2S 请求并吞掉这一下
  （否则左击会顺便把自己坐的车敲掉血）。见 `RideBatonViewClamp.onInteractionKey`。
- 服务端：`RideBindManager.handleSwapSeatRequest` **只信车辆 id**（座位自己算，防伪造包），
  再复核"手里有棒 + 是卓越前线载具 + 车上是我们绑的女仆"。
- 换座走 SWB 自己的 `changeSeat(Entity,int)`（要求"目标座为空 + 该实体已是乘客"）：
  **主人先下车让位 → 女仆挪过去 → 主人重新上车并钉到目标座**；判成功**只看最终座号**
  （原版 `startRiding` 会先落进第一个空座，`changeSeat` 此时会返回 false，但那不是失败）。
  两座车（Mi-28）与多座车（AC-130H）**同一条路**，不存在两人抢座的中间态。

**② 同乘直升机仍缓慢上升 → 悬停基准从"她自己"改成"载具"。**
738 改成"机上有玩家就悬停"，但基准取的是**她自己的位置**——她是乘客，座位比机身高出约 1.5 格，
于是 `driveFlight` 收到的 `dy` 恒为 +1.4 左右，"还没到目标高度"每拍都成立 → **永久缓慢爬升**。
现在 `MaidAirCombat.holdHere(maid, mount)` 返回**载具**的位置（`dy = 0`、`horiz = 0`），
才是真正的原地悬停。

**③ 直升机用不上炮塔炮弹 → "瞄的炮"与"打的炮"统一到一个炮名。**
反编译实证：`getShootPos/getShootVec/getProjectileVelocity/getProjectileGravity` 的 **Entity 重载**
内部一律走 `getGunData(getSeatIndex(entity))`——**按"她坐哪个座"解析武器**。而 Mi-28 的
`TurretControllerIndex = 1`（30mm 机炮在**座 1**），她（驾驶员）在**座 0**，座 0 的武器是火箭弹。
于是 740 那一版**瞄准算的是机炮、扣扳机打的却是火箭弹**（反编译还发现：按乘客的
`vehicleShoot(LivingEntity,UUID,Vec3)` 对 AC-130H 会直接抛 `IllegalStateException`，因为座 0 没 gun）。
- 新增 `MaidMountCompat.gunNameFor`：**全车唯一的那次选炮**。优先级 = 炮塔（玩家没占着该位）
  → 她自己那座 → 武器站 → 第一门有武器的座。
- 新增按**炮名**的四个重载 + `fireAt` 改走 `vehicleShoot(LivingEntity,String,UUID,Vec3)`：
  "瞄哪门打哪门"从此由同一个名字保证。旧的"按乘客/按座位"那一组删除（口径只有一处）。
- 顺带：开火判据从"只要它**是飞行载具**"放宽到**"这车有炮"**——**地面炮塔坦克**
  （M1A2/T-90A/PLZ-05）也一并走上弹道瞄准（以前只有飞行载具走）。
- 新增 `isControllerEntity`（比引擎那道"控制位坐着 Mob"宽一档：位存在、索引有效、
  **没有玩家在手动瞄**）——多座载具的炮塔位常常空着，照引擎那道具塔永远没人瞄。
- **不抢引擎已经能用的那档**：该用的炮就在她坐的那一座时（prism_tank 座 0 的激光、M1A2 座 0 的
  120mm、AH-6 座 0 的机炮），引擎那条"Mob 乘客自动开火"打的正是这门炮、与我们瞄的是同一门，
  我们**不接管、也不重复开火**（玩家反馈"地上的光棱坦克倒是可以"就是这一档）。

**④ 炮艇（AC-130H）不装弹也不开火 → 选炮落到有武器的那一座 + 装弹放宽到弹匣型火炮。**
它的**座 0（驾驶位）一门武器都没有**（三门炮在座 1/2/3）：
- `getGunData(0) == null` → 引擎那条"Mob 乘客自动开火"直接跳过；我们旧版也**问不出该装什么弹**。
  选炮那一档（`gunNameFor` 的兜底）让"该用哪门炮"落到**座 1 的 M61**，于是装弹与开火都成立。
- 装弹范围放宽：反编译 `GunData.countBackupAmmo` / `reloadAmmo` 实证**两类枪的弹都来自车容器**——
  "背包弹型"（`MAGAZINE <= 0`）每发直接数容器；"**弹匣型**"（Bofors 弹匣 12、M102 弹匣 1）
  打完由 `reloadAmmo` 从**同一个容器**扣物品填进弹匣。旧版只搬前一类，
  她的 `medium_shell_he` / `large_shell_he` **永远进不了车** → 现在两类都收。
- 炮艇既没有炮塔也没有武器站，而且它三门炮的 `Directions = ["Passenger"]` 按**炮名**解析时
  落到 `vectorTransform` 表的 `"Default"` = **载具自己的机头方向**（反编译 `registerTransforms` /
  `getVectorFromString` 实证）——也就是说**炮艇的炮弹沿机头飞**，想打中只能把机头对准敌人。
  新增 `pointBarrelAt` / `applyNoseAim`：登记"该朝哪"，由飞行档在**它自己写完机头之后**落地
  （顺序即优先级），并清掉鼠标通道免得对冲；**时效仅 3 拍**，脱战后立刻回到原来的跟随/盘旋行为。
  同一条判据也覆盖 AH-6 / A-10 这类"机炮挂在车体"的固定炮。

**⑤ 她自己坐上车导致"绑不上、卡在车上" → 采纳现状 + 召唤绕过骑乘模式。**
根因（javap 反编译原版 `Entity.startRiding(Entity,boolean)` 实证）：它**第一句**就是
`if (this.vehicle == vehicle) return false;`——她已经在这辆车上时**恒返回 false**。
旧版就把绑定当成失败（"没能坐上去……再试一次？"）、**不写链路表** → 她卡在车上不动，
而且 `isOurRider` 认不出她，连"右击让她下来"都做不到。
- `RideBindManager.bind`：她已在这辆车上 → **直接采纳现状**当作绑定成功（不重坐），继续走
  "坐姿 + 挪到驾驶位 + 写表 + 打标记"；若她坐在**别的**车上，先请下来。
- 新增 `isSelfBoardedModMount` / `dismountSelfBoarded`，并在**排班表的手动传送**
  （`summonOne` / `summonMaidTo`）与**一键集合**（`summonAll` / `tickPending`）里放行：
  与 726 的"特殊载具（棍子绑的）"故意不同——她只是**恰好坐在上面**，所以只需**让她下来 +
  单独传人**，不碰任何我们自己的标记（本来就没有）。玩家原话「直接把女仆自己召唤过来」。

### 三、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` mixin 注入点审计 **PASS=181 SKIP=10 UNRES=0 FAIL=0**。
- 只读核对 `_f741_vt.py`：四条改动逐项静态核对（两树同 71 项）**FAILS=0**。
- 打包闸门全过：Forge **620 个 class**（927 entries）、Neo **631 个 class**（938 entries），
  `verify_jar_classes` 全过、`lang json: OK`、`MISSING: none`。
- 部署三处（1.20.1 实例 / 1.21.1 实例 / 服务器包），jar 哈希一致。

---

## 实测七百三十九【悬停机头锁死 + 载具弹道瞄准（炮弹打得准了）+ 地面接敌绕圈根治】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同名 jar 覆盖；**未发平台**。

### 一、玩家原话（三条）

1. 「女仆驾驶直升机在空中悬停的时候，总是在原地进行不停的旋转。没有什么实际影响，就是观感不好。
   能不能让它的头在悬停期间朝向不要发生变化呢？」
2. 「女仆驾驶直升机在空中的时候，发射的炮弹准度约等于 0。因为前几个版本打的还挺准的，
   能不能走后门让那个炮弹强制射向敌方？」
3. 「女仆骑车对敌人的绕圈堪称灾难。基本上就是往敌人身上一撞就完事了，根本绕不起来。
   也发挥不出车辆的速度」

### 二、根因与改法（三条）

**① 悬停时机头原地打转 → 进档锁死机头。**
悬停档（跟随、且已贴到目标点）时，跟随目标点就压在她**自己脚下**，水平分量≈0。于是
`driveVehicle` 算出来的 `err` 是一个**退化方位**——`atan2(Δx, Δz)` 的两个参数都是座位与目标点
之间的浮点抖动，每拍符号/量级都在变；鼠标 X 通道与「直写机头」两条路都跟着它改朝向 → 机头
慢慢转。「把噪声当指令」。
- 新增 `MaidMountCompat.hoverYawHold/releaseHoverYaw`：进悬停档那一刻**采样一次机头**存起来，
  此后每拍写回（`setYRot` + `setServerYaw`，后者不写会被 `handleClientSync` 的 lerp 拽走），
  鼠标 X 通道归零；`driveFlight` 里在**所有会改朝向的写法之后**再覆盖一次（顺序即优先级）。
- 退出悬停档（接敌绕圈 / 要移动 / 下鞍）立刻解冻、下次进档重新采样——**接敌盘旋完全不受影响**。

**② 空中炮弹准度≈0 → 换成游戏自己的弹道解算器。**
七百三十一 那一版是**自己算角度**写进 `setTurretYRot/setTurretXRot`（`yaw=atan2(dz,dx)-90`、
`pitch=-atan2(dy,flat)`）。反编译（`VehicleEntity` / `VehicleWeaponUtils` / `RangeTool`）实证两处
根本错误：
- **炮塔角是「相对车身」的**：引擎的 `getBarrelVector` 是 `车身变换 × turretYRot/turretXRot`。
  我们写进去的却是**世界绝对方位**——等于把相对角当绝对角用，车一转炮口就乱指。
- **完全没算弹道**：引擎那条链走 `RangeTool.calculateFiringSolution`（解**重力下坠 + 目标提前量**，
  牛顿迭代），我们只对目标画直线。炮弹是纯抛物线的（`CannonShellEntity`，无制导、无追踪）。
- 另外实证：`vehicleShoot` 的 `targetPos` 参数**只有制导导弹（`MissileProjectile`）读**，
  炮弹一律不读、只按 `getShootVec()`（炮管朝向）飞——所以「让炮弹射向敌方」**只能靠发射前把炮管摆对**。

改法（**逐字照搬引擎自己的链**，口径只有一处）：新增 `MaidMountCompat.aimBallistic` ——
① 按引擎公式取发射点（`getShootPos − getShootVec × |getShootPos − 她的位置|`，那个"反投影"是引擎
原文，不是笔误）；② `RangeTool.calculateFiringSolution`（目标速度 + 目标重力作提前量，玩家 ×(2,1,2)）；
③ 交给引擎的 `turretAutoAimFromVector` / `passengerWeaponAutoAimFormVector` 转炮塔（它们负责相对角
换算、转速限幅、俯仰/偏航限位）；④ **等炮口与解算方向夹角 ≤3° 才开火**（炮塔是逐拍转的：
M1A2 3.3°/拍、Mi-28 15°/拍）。`vehicleShoot` 的 `targetPos` 改传 `null`（对炮弹无意义，
UUID 仍传，制导导弹会因此进入制导）。旧的 `forceTurretOntoTarget`/`forceFireAt` 已删；
`ensureAmmo` 保留"弹匣空了自己上弹"。

**③ 地面接敌「一撞就完事」→ 半径自适应 + 绕圈不刹车。**
七百三十八 补了地面绕圈，但两处没跟上：
- **半径给小了**（固定 2.5 格）：车的转弯能力有限（机头兜底每拍上限 10°），车速 1 格/拍时
  "能绕住"的半径下限就有 ~5.7 格，给 2.5 格 → **拐不过来 → 直直撞进敌人怀里**。
- **每拍都被停车带刹一下**：`stopBand` 含**车速前瞻**（`车体半宽+1 + |Δ|×10`），车速 1 格/拍时
  高达 **13 格**，而绕圈的"胡萝卜"只挂在前方 4~6 格 → **每拍判"到了"、每拍真刹车** → 车永远起不来速。

改法：新增 `MaidMountCompat.groundOrbitRadius`（半径 = `max(6, 配置, 车速×11, 车体半宽×3)`，
上限 28）与 `driveOrbit`（绕圈专用驱动档：**不刹车**、只要没偏得太离谱就**一直给油**）。
`RideBindManager` 地面接敌分支改调 `driveOrbit`（反射拿不到就退回通用档）。
撞墙/卡住自动反向（`groundReverse`，738 已有）保持不变。

### 三、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` **PASS=181 SKIP=10 UNRES=0 FAIL=0**。
- 只读核对 `_f740_vt.py`：三条改动逐项静态核对（两树各 20 项）全过。
- SWB 反射面**两版本逐一核对**（javap）：`turretAutoAimFromVector` / `passengerWeaponAutoAimFormVector`
  / `getShootVec(Entity,float)` / `getShootPos(Entity,float)` / `getProjectileVelocity/Gravity` /
  `RangeTool.calculateFiringSolution` 在 1.20.1 与 1.21.1 上**同名同描述符**。
- 打包闸门全过（jar 内 class 与源一一对应、mixin 包登记、lang json 合法）；部署三处哈希一致。

---

## 实测七百三十八【开陆地载具接敌绕圈 + 玩家上机就悬停 + 指挥棒绝不登乘】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同名 jar 覆盖；**未发平台**。

### 一、玩家原话（三条）

1. 「女仆在骑乘陆地载具的时候，走位的方向仍然是朝着主人方向。如果主人坐上了车，那么女仆的
   车子就会一点都不动了。这个倒是无所谓。主要是在面对敌人的时候，主人坐上车以后，车还是一动
   不动，就很难绷了。能不能在接敌后也采用直升机/扫帚那种绕圈的方式呢？撞墙以后自动反方向。」
2. 「如果女仆乘坐的是直升机且玩家坐副驾驶，那结果就更糟糕了，因为会跟之前的代码冲突（女仆开的
   直升机必须要在玩家的上面），导致女仆必须要一直往上飞。建议改为玩家乘坐以后就悬停。
   锁敌的时候正常。」
3. 「我们的骑乘指挥棒有最高的优先级，如果拿骑乘指挥棒进行右击，那么就不会坐上副驾驶，
   只会有他的解绑功能，绝对不会乘坐。」

### 二、根因与改法（三条）

**① 地面载具接敌一动不动 → 补上绕圈 + 撞墙反向。**
接敌机动那一整套（`MaidAirCombat.combatTarget`）此前**只挂在「飞行载具」这一支**上
（`RideBindManager.drive` 里 `isFlyingVehicle(mount)` 那个 if），地面载具（车/坦克）永远走
「跟着主人走」的兜底——主人一上车，她与主人的水平距离≈0，`stopBand` 立刻判「到了」→ 停车，
敌人再近也不动（这就是「车还是一动不动」）。
- 新增 `MaidAirCombat.groundOrbitTarget(maid, mount, target)`：与 `orbitPoint` **同一套机制**
  （同一段 `CombatOrbit` 随机旋向/快慢、同一个「追逐式胡萝卜」抗滞后），只把高度从「敌上
  `fightAlt` 格」换成**敌人脚下的地面高度**（地面载具本来就不该飞，`GROUND_ORBIT_MIN = 2.5`）。
- 新增 `MaidMountCompat.groundReverse(mount)`：**撞墙反向**。引擎自己不会掉头，只会照着输入位
  死顶 → 卡墙边原地磨。这里按「位置几乎没动」判卡住：连续 `GROUND_STALL_TICKS = 10` 拍
  （0.5 秒）位移小于 `GROUND_STALL_EPS = 0.02` 格就翻一次号，绕圈方向随之调头。
  用位移而不是引擎的 `horizontalCollision`：后者被 SWB 自己的 `travel` 改写、又被我们每拍的
  `setDeltaMovement` 干扰，判不稳；位移是最终事实，且对「顶墙 / 顶实体 / 被地形卡住」一视同仁。
- `RideBindManager.drive` 新增地面载具接敌分支，与飞行档共用同一道「主人不在场就收手」的闸
  （`FlightTargeting.RANGE`）。
- `FlightTargeting` 新增 `isVehicleMountRider`：地面载具也并入「发现 50 格」这套索敌，
  否则绕圈半径才 2.5 格却看不见几格外的目标（旧口径只有 TLM 那条 16/8 格援护半径）。

**② 玩家坐上副驾后直升机一直往上飞 → 机上有玩家就原地悬停。**
跟随档的高度基准是**主人的 Y**（`hoverAt(…, owner.getY())`，七百三十三 定的口径）。玩家一旦
坐上这架直升机，`owner.getY()` 就等于**机身自己的 Y** → 目标高度 = 她自己的高度 + `followAlt`，
她每贴一次就被抬高，形成**每拍 +N 的棘轮**，永远往上飞（七百三十四 修的是「拿她自己的 Y 当
基准」，没覆盖「主人与她在同一台机器上」这一档）。
- 新增 `MaidMountCompat.hasPlayerAboard(mount)`（只看机上有无玩家乘客，不问是哪位玩家）。
- 新增 `MaidAirCombat.holdHere(maid)`：返回她自己当前的位置。
- `RideBindManager.drive` 跟随档里：**机上有玩家 → 原地悬停**（水平竖直都保持，不追任何基准）。
  接敌那一支在它上面，完全不受影响——正是玩家要的「锁敌的时候正常」。

**③ 指挥棒绝不登乘 → 拆掉 737 那道「放行主人坐副驾」的豁口。**
玩家原话「骑乘指挥棒有最高的优先级……绝对不会乘坐」。737 为「主人坐副驾」加的三处豁口
一处不剩地收掉：
- `handle()` 里删掉「先试坐上副驾」那个分支——拿指挥棒右击自己女仆开的车只有解绑一个结果；
- `denyMountForBatonHolder()` 删掉 `allowsOwnerPassengerSeat` 放行；
- `onMount()` 删掉同款放行。
**空手右击坐副驾那条路完全不变**（`handlePassengerSeat` 不查手里拿什么），
`allowsOwnerPassengerSeat` / `boardOwnerAsPassenger` 仍由它使用。

### 三、验证与交付

- 两树 `javac` **0 错误**；`_mixchk.py` mixin 注入点审计 **PASS=181 SKIP=10 UNRES=0 FAIL=0**。
- 只读核对 `_f739_vt.py`：三条改动逐项静态核对（两树各 13 项）全过。
- 打包闸门全过：Forge **619 个 class**（926 entries）、Neo **630 个 class**（937 entries），
  `verify_jar_classes` 全过、`lang json: OK`、`MISSING: none`。
- 部署三处（1.20.1 实例 / 1.21.1 实例 / 服务器包），jar 哈希一致。

---

## 实测七百三十七：守家不吃绳子的力 + 车/坦克机头兜底 + 双人座副驾 + 语音补漏 12 条

> 版本号不变，仍是 v1.3.0(beta)，同名 jar 覆盖；**未发平台**。**两树同步**。

### 〇、玩家原话

1. 「home 模式下空袭牵引绳不再生效。」（同一条 bug 的说法：「如果给 home 模式的女仆设置空袭模式的话女仆就会一直被牵引绳往玩家那拉」）
2. 「女仆在开车的时候，那个车头的转向方面没有那么智能，能不能也加上像直升机这样子的转向兜底呢？」
3. 「我考虑到卓越前线有一些载具是分为双人座的，能否考虑在女仆乘坐后主人右击的时候登上副驾驶座呢？」
4. 「缺少的是那一句拿骑乘指挥棒右击女仆（而先前没有右击载具的语音）……可能还有其他遗漏，反正一并补上吧。」

### 一、守家不再吃绳子的力

两条"往主人那边拉"的链路原来**都不看 home**：武装拴绳的每拍拉力（`GunnerTetherManager.pullTick`）
与空袭牵引绳的远距召回（`MaidFlightRecall`）。守家的语义是"把她停在岗位上"，她自己那条守家回圈
（`SchedulePos.tick`）才是"拉她回家"的正解——绳子再插一脚就是两股力打架，就是玩家说的"一直被拉"。
现在两处各加一道 `isHomeModeEnable()` 早退：**home 模式下这两条绳整条不生效**（不再写位移、不再传送；
挂载状态与渲染照旧）。非 home 的女仆一个字不变。

### 二、车/坦克的机头兜底

以前开车只发左右输入位，转向全交给引擎自己那条链；而那条链按 `holdTick` 慢累积、转率又正比车速、
`rudderRot` 夹在 ±0.8 → 起步转不动、快起来追不上。现在新增直写机头的兜底（`forceCarHeading`）：
25° 死区 + 每拍步长随车速缩放 + 只写 yaw 不碰 deltaRot，并一起写 `serverYaw`（否则客户端每拍
lerp 会把机头拽回）。只接轮式/履带两台引擎。

### 三、双人座副驾

卓越前线自己那条右击链见"第一乘客不是玩家"就把女仆踢下车、主人坐驾驶位。现在由我们接管：
主人右击自己女仆开的车 → **坐进空着的副驾**（先上车、再用车自己的 `changeSeat` 钉到副驾），
女仆稳在 0 号驾驶位。空手右击与拿指挥棒右击都接；两道登乘闸也放行这一档。

### 四、语音补漏 12 条

按"女仆会说但没有语音"审计全树，补 12 条（内置语音包 161 → 173 条），含玩家点名的
「先用骑乘棒右击坐骑，再来右击我～」、副驾无位、四条新工具的应答、送水、无水域、建造缺料。
复跑审计 gap = 0。

## 实测七百三十六【同步到 1.20.1】二十四期至三十四期全部同步到 Forge 1.20.1

> 版本号不变，仍是 v1.3.0(beta)，同名 jar 覆盖；**未发平台**。本次把 724～734 那十一个「仅 1.21.1」版本的全部改动补齐到 1.20.1（Forge 47.x）侧，两树行为一致。

### 一、同步内容（玩家可见部分）

- **骑乘指挥棒·不再抢死链**：旧版「一位玩家名下只留一条骑乘链路」会让甲+载具A 在给乙+载具B 配对时被静默放掉；现在两组互不干扰。
- **右击载具不再转玩家视角**：SWB 会在登乘前把玩家转向车头；现在右击后 4 拍内把重叠方向复述回快照。
- **真刹车 / 不撞主人**：停车改为「总跑前松油 + 轮死刹车」，停车距离按车体半宽放大（坦克不再把主人撞倒）。
- **坦克真开炮**：把她的目标 UUID 写进炮塔/武器位的 AI 目标，并在弹匣空时自己装弹；她还会主动搭载具搭上的炮口开火。
- **直升机真悬停与直行**：骑上飞行载具就进入悬停，高度基准只认主人的 Y（不再每拍 +3 棘轮），接敌升到敌上 N 格盘旋；长距离直写机头、不飞偏。
- **每 0.5 秒定期急停**：把「用力过猛」封顶，水平速度呈有界锯齿。
- **骑龙（悬空鞍位）**：每拍摆位 + 冻结她自己的导航 + 不再因距离断开；配对 S2C 同步到客户端，龙一飞不再错位。
- **龙骑位传送豁免**：守家、危险逃离、主人死亡传送、过热逃离、自保策略、区块加载 六处均把「骑龙鞍位」视为停放豁免对象。
- **新声音**：骑乘/骑龙相关 9 条语音（占位、无座、骑龙就绪等）。

### 二、两树差异（故意保留）

- 1.20.1 没有 {@code EntityTickEvent}（它到 1.20.2 才进原版），所以龙每拍摆位改用本工程同款的混入点（{@code EntityTickPostMixin} 挂 {@code Entity.tick} RETURN）；
- 网络包改用 Forge 的 {@code SimpleChannel}（与既有各包逐字同款）；
- 客户端 tick 改用 {@code TickEvent.ClientTickEvent}（{@code Phase.END} 同口径）。

## 实测七百三十四【同步到 1.20.1】跟随/悬停高度的「棘轮」修正——基准只能有一个来源

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上 733，同名 jar 覆盖；**未发平台**。

### 〇、玩家原话

「现在唯一的问题就是悬停在玩家上方的这个高度似乎仍然有问题，很多次都是比玩家高出了十几二十格。」

### 一、根因：七百三十三 自己引入的「每拍 +3」棘轮

七百三十三 把跟随高度基准从"她脚下的地面"改成"主人的 Y"是对的，但**同一版里那一档"够近就原地悬停"错误地把基准传成了她自己的 Y**：

```java
// 七百三十三（有 bug）
if (水平距离 > followDist) air = hoverAt(maid, owner.X, owner.Z, owner.Y);   // 对
else                         air = followTarget(maid, maid.getY());          // 错：基准是她自己
```

于是近距时的目标点 = **她现在的高度 + 3**。她朝这个点爬上去、贴住之后，下一拍目标又变成"新的高度 + 3"——**每贴到一次就被抬高 3 格，目标跟着水涨船高**，形成棘轮。几秒就窜到主人上方十几二十格，正是玩家看到的现象。

### 二、修法：高度基准只保留一个入口，且只认主人的 Y

- `RideBindManager` 近距分支改为 `hoverAt(maid, maid.getX(), maid.getZ(), owner.getY())`——两档的差别**只在水平位置**（够远 → 飞到主人正上方；够近 → 守住她当前水平位置），**竖直基准一律 `owner.getY()`**。
- 删掉 `MaidAirCombat.followTarget(...)`（已无调用者）——它正是被误传"她自己的 Y"的那个入口。现在只有 `hoverAt(maid, x, z, baseY)` 一个入口，`baseY` 的语义就是"参照物高度"，调用方一律传主人的 Y。
- 顺手删掉 733 起就已无用的 `groundY` / `GROUND_SCAN`（地面探测那套），并修正类注释里"离地 airAlt"的旧口径。
- 固定翼分支的日志档名也一并从 `跟随档(离地N格)` 改成 `跟随档(比主人高N格)`，与直升机同口径。

### 三、验收

搜「飞行档」看 `跟随档(比主人高3格)`；`高差` 应当稳定收敛到 0 附近（不再单向往上爬）。若仍然偏高，先确认面板「悬停高度」是不是被调大了——它是这一档唯一的数值输入。

## 实测七百三十三【同步到 1.20.1】长距离不飞偏（直写机头）+ 跟随高度改为「比主人高三格」+ 全程每 0.5 秒脉冲急停

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上 732，同名 jar 覆盖；**未发平台**。

### 〇、玩家原话

1. 「女仆控制不好方向感的方向，短距离还行，但是长距离如果要往主人的方向飞很容易飞偏。」
2. 「如果主人飞到了高处，那么开直升机也不会追过来，只会在水平方向上追，竖直方向上，一点都不改。我之前提到过，是离地面上高三格，但这个表述并不明确，应该是**比主人的高度高三格**。（就跟扫帚模式一样）」
3. 「就算加了限制，但是仍然会出现中途失速的情况。我需要的就是像接近主人进行时那样的效果，并且最好是**每 10 tick 就触发一次**这样的停止，否则真的太不稳定了。」

### 一、长距离飞偏的根因——偏航通道权限不足两成，且与滚转耦合

引擎 `helicopterEngine:613`：

```
setYRot(yRot + yawSpeed * clamp(2.0 * mouseX * propeller, -10, 10))
```

`propeller` 稳态 ≈ `power` ≈ 0.09，所以实际权限只有 **0.18 倍**（不到玩家推鼠标的两成）。更糟的是同一条 `mouseX` 在 `:612` **还驱动滚转**——她为了纠航向持续压 `mouseX`（实机日志 `鼠标X=-6` 长期打满），机体一路滚转，升力矢量 `getUpVec` 歪向侧方：水平推力被掰成横向分量，既飞偏、又抵消前推。

还有一条自己写出来的坑：旧版 `if (|err| >= 60°) desired = 0`（"转向优先、没对准不给速度"）。日志 `鼠标X=-6 期望=0.00 水平速度=0.00` 反复出现——偏航转不过来 → 长期卡在"不给速度"里**原地打转、一点不前进**，正是「中途失速」。

**本版**：新增后门 `forceHeadingOnto`，**直接写 `setYRot`**（每拍上限 10°≈200°/秒，比那条通道快十倍以上），并连 `serverYaw` 一起写——反编译 `handleClientSync:5443-5445` 服务端每拍会用 `serverYaw` 把 `yRot` 往回 lerp，不写它就被拽回去。通道里只留 `FLIGHT_YAW_TRIM = 1.5` 做微调，滚转耦合随之可忽略；**删掉** `|err|>=60 → 速度=0` 那个分支。

### 二、跟随高度：从「离地三格」改成「比主人高三格」

旧口径 `groundY(她脚下) + 3`，于是主人飞高后日志是 `跟随档(离地3格) 高差=0 距目标=101格`——高度差恒为 0，**竖着一格都不追**，只在水平面追，永远追不上。

扫帚是 `owner.getY() + FOLLOW_HOVER`（`MaidBroomDrive.followPoint`）。本版与它同口径：`hoverAt(maid, x, z, baseY)` 的 `baseY` 传**主人的 Y**；主人就在旁边时传她自己的 Y（等价"保持当前高度"）。日志档名改为 `跟随档(比主人高N格)`。

### 三、急停脉冲：去掉 24 格门槛，全程每 10 tick 一次

732 的脉冲只在离目标 24 格以内生效，长途中段不打 → 玩家看到的"中途失速"仍在。按原话「每 10 tick 就触发一次」改为**不设距离门槛**：无论多远，一律每 10 拍（0.5 秒）把水平分量收一截（跟随/接近档 `0.25`、接敌盘旋档 `0.55`），竖直分量不动。

### 四、验收日志（进游戏后搜「飞行档」）

| 标记 | 含义 |
|---|---|
| `直写机头` | 航向走后门直写（长距离不再依赖弱偏航通道） |
| `跟随档(比主人高3格)` | 高度基准已是"主人 Y"，主人飞高她会跟着爬 |
| `周期急停(0.5s)` | 那一拍打了脉冲；`水平速度` 应呈有界锯齿 |
| `硬刹(水平清零)` | 已到点、正在停死 |

## 实测七百三十二【同步到 1.20.1】直升机周期急停脉冲（每 0.5 秒收一次，把"用力过猛"封顶）

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上 731，同名 jar 覆盖；**未发平台**。

### 〇、玩家原话

1. 「卓有成效，现在在接近玩家的时候就有了一个不错的急停效果。」
2. 「但是我发现像这个女仆操纵的时候，总是容易用力过猛，所以这边最好是每 0.5 秒就急停下来一次。这样子可以显著增加飞机的稳定性。」

### 一、把 731 那记"到位急停"变成**周期脉冲**

731 的硬刹只在 `horiz <= FLIGHT_ARRIVE(3.0)` 时触发——所以只有"已经贴到点"才收得住，途中冲起来的那段动量没人管。本版把它扩成**每 10 拍（= 0.5 秒）一次**的脉冲：在离目标点 24 格以内的"操纵带"里，周期性把 `deltaMovement` 的水平分量打掉一截，竖直分量不动。

| 档 | 每 0.5 秒保留 | 说明 |
|---|---|---|
| 跟随 / 接近 | `0.25` | 照玩家原话"急停下来一次"；实机她 1 秒能从 0 冲到 1.83，砍完形成 **0.3~1.1** 的有界锯齿、均值 ≈ 0.7，正好压在速度环 0.75 的包络内 |
| 接敌盘旋 | `0.55` | 温和版：绕圈靠水平速度维持，砍到 0.25 会把圈顿成一步一停 |

到位那一档（`horiz <= 3`）仍是**每拍**砍 `0.25`、要她真停；脉冲与它互斥（同一拍不重复刹）。操纵带外（>24 格）不打脉冲——否则一趟长途会被周期性砍成半速，那是没要求的副作用。

### 二、日志怎么看

`飞行档` 那行新增 `周期急停(0.5s)` 标记：出现它的那一拍就是脉冲打下去的一拍。配合 `水平速度` 看应当呈**有界锯齿**（不再一路冲到 1.8），`期望` 不变。`硬刹(水平清零)` 仍是"已到点、正在停死"。

## 实测七百三十一【同步到 1.20.1】暴力后门三连：强制刹车 + 先爬后绕 + 直升机真开炮

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版，同名 jar 覆盖；**未发平台**。

### 〇、玩家原话

1. 「现在女仆完全就停不下来了。1 点刹车的能力都没有，我们这边走个后门吧，给女仆一个强制刹车。比如悬停完之后就强制给它水平方向的速度停下来。」
2. 「遇到敌人之后，先上升，然后再盘旋。」
3. 「好好看一下刚刚游戏运行的日志，女仆也不会使用直升机上的炮弹。」
4. 「说实话，我们为了达成这个目的已经花了太多时间，现在我需要用一些暴力的手段，多走一些后门，来强制稳定他的位移。」

### 一、三条实证（全部来自 `promaid.log` 05:49~05:53 那次实机）

| 现象 | 日志原文 | 根因（反编译定位） |
|---|---|---|
| 停不下来 | 到点后 `期望=0.00` 而 `水平速度=0.83`，下一拍 `1.82` | 引擎的惯性远大于俯仰权限能提供的反向推力；`setHoverMode` 只衰减 5%/拍 |
| 一边爬一边绕 | `接敌档 高差=11 → 0 → -11` 每 5 秒横跳 | `orbitPoint` 直接返回"敌上 15 格 + 绕圈"的点，高度环与水平环**同时**抢输入 |
| 一炮不放 | `炮塔目标=warden` 明明写进去了 | ① 内置开火闸要求"炮口与目标夹角 < 4°"（`VehicleEntity:4013-4024`），绕圈时永远进不去；② 弹匣空时 `canShoot` 直接 false，而"从容器补弹"是**玩家开火键**那条链在调，女仆没有开火键 |

### 二、三个后门（不再依赖引擎自己的判定）

**① 强制刹车** — `MaidMountCompat.killHorizontal(mount, retain)`

直接改 `deltaMovement`：`setDeltaMovement(dm.x*retain, dm.y, dm.z*retain)`。
安全性有反编译依据：`VehicleEntity.setDeltaMovement`（:5474-5487）**只在加速时限幅，减速直通**，所以把水平分量乘小是安全的；**竖直分量原样保留**（空中不能失去升力）。
条件 = `horiz <= FLIGHT_ARRIVE(3.0)` 且**不在接敌盘旋中**（盘旋里刹会把圈顿成碎步）；到位时同时把期望速度归零、让内环把机头摆平，两者配合才真停得住。日志里会标 `硬刹(水平清零)`。

**② 先爬后绕** — `MaidAirCombat.altitudeReady / climbPoint`

在 `combatTarget` 前加**硬高度闸**：她离"敌上 N 格"还差超过 2.5 格时，水平目标点直接取**她自己当前位置的正上方**——先原地把高度爬到，再交回盘旋。这样高度环独占输入，不会再和水平环互相抢。日志搜「空战」会看到两行分明：`接敌 → 先爬到敌上 15 格（到位后再开始盘旋）` → `接敌 → 爬到敌上 15.0 格、绕着敌人盘旋（半径 4.0 格…）`。

**③ 直升机真开炮** — `MaidMountCompat.forceTurretOntoTarget` + `forceFireAt` + `ensureMagazineLoaded`

- `forceTurretOntoTarget`：把炮塔方位/俯仰（`turretYRot/turretXRot`）**写死**到目标；驾驶位机炮那对（`gunYRot/gunXRot`）一并写。跳过引擎的转向速度限制。
- `ensureMagazineLoaded`：弹匣空且车容器里有对得上的弹 → 自己调 `GunData.reloadAmmo`。
- `forceFireAt`：直接调 `vehicleShoot(LivingEntity, UUID, Vec3)`——**它不做角度判定**，`targetPos` 会交给弹道计算（`GunItem.shootBullet` 实证读到 `ShootParameters.targetPos`）。每 5 拍一发。日志搜「模组坐骑·开火」看 `直连开火 → warden`。

> 注意：这条路走的是**她所在的座位武器**。座位里没弹且车容器也没弹时，`GunData.canShoot` 仍会拦下——所以「模组坐骑·装弹」那一档（把子弹搬进车容器）依然是前提。

### 三、验收日志怎么看（进游戏后）

| 日志 | 看什么 |
|---|---|
| `模组坐骑 … 飞行档 …` | 到点后应有 `硬刹(水平清零)`，且 `水平速度` 从 0.8/1.8 掉到接近 0 |
| `空战 …` | 接敌时**先**出 `先爬到敌上 15 格`，高差收敛后才出 `绕着敌人盘旋` |
| `模组坐骑·开火 …` | 出现 `直连开火 → warden/husk/...` = 炮弹真的打出去了 |
| `模组坐骑·炮位 …` | 仍是"她把目标交给炮塔"那一行，可作为"有没有目标"的旁证 |

## 实测七百三十【同步到 1.20.1】直升机真会飞了（速度环 + 俯仰量级）+ 飞行坐骑锁敌并入扫帚口径

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版，同名 jar 覆盖；**未发平台**。

### 〇、玩家原话

1. 「为什么不能强行修改直升机的飞行链路，改成跟扫帚模式一样的移动方式和逻辑呢？现在这样子女仆又完全不会使用直升机了。」
2. 「我要的效果：女仆骑上直升机，启动，随后女仆启动直升机飞到比主人高三格的地方悬停。随后在一定范围内跟随主人。如果遇到敌人，且自己处于攻击状态，对他有敌意。则飞到离敌人高15格的位置，随后绕着敌人进行盘旋。并且使用飞机上的武器进行攻击。打完了，或者发现攻击范围内没有主人，则放弃攻击敌人，转而去追主人。」
3. 「骑上飞行载具后它的锁敌范围应该跟扫帚模式是一样的。」

### 一、直升机「完全不会用」的真正根因——俯仰输入要乘一个极小的 power

反编译 `VehicleEngineUtils.helicopterEngine` 逐行核出来（`mi_28.json`：PitchSpeed=0.75、lift=1）：

```
:611  setXRot(xRot + (onGround?0:1.5) * pitchSpeed(0.75) * mouseY * propeller)
:686  deltaMovement.add(getUpVec().scale(propeller * lift * 0.66))   ← 唯一的水平推进
:638-641  power 上限只有 0.12；悬停所需 power ≈ 0.091
```

`propeller` 稳态 ≈ `power`，**悬停时只有 0.1**。也就是说俯仰通道的实际增益是
`1.125 × mouseY × 0.1`：旧版写满 0.6 也只换来 **0.067°/拍 ≈ 1.3°/秒**，机头根本压不下去。
而直升机的水平推进**只有**"机头下压 → 升力分出水平分量"这一条——机头不动 ⇒ 水平推力恒为零
⇒ 玩家看到的「彻底失去前后左右移动能力，只会上下飞行」。**这不是调参问题，是量级差了十几倍。**

### 二、本版怎么改（两点1）

| 项 | 旧版 | 本版 |
|---|---|---|
| 俯仰 | 写 `mouseY = 距离×0.04`（上限 0.6） | **串级速度环**：外环 距离→期望速度（上限**照搬扫帚 0.75 格/拍**）→目标前倾角；内环 姿态误差→`mouseY`（上限 **8**，与玩家推鼠标同档） |
| 升降 | 按位掩码 0x004/0x008（引擎里是很慢的积分器，≈54 拍才到位）→ **极限环** | **直接写 `setPower`** 的 PD：`power = 0.091 + (高差 − 竖直速度×10)×k`，钳在 [0.045, 0.12]（下限不能低于 0.04，否则引擎熄火） |
| 位掩码 | 用它当"前进推力" | 直升机档**清空位掩码**，只走鼠标通道 + power |

数值仿真（照 mi_28 方程跑，脚本 `_f730_sim.py`）：高度从低 3 格收敛**过冲仅 0.20 格**（旧版实机日志是
`高差=3 → -4 → -1 → 2 → 0 → -4` 的 ±4 极限环）；巡航速度爬到 **0.33 格/拍** 后稳住；到点后
水平速度**自己回落到 0** 并进入悬停档。

### 三、锁敌并入射帚口径（点3）+ 主人牵引绳（点2）

- `FlightTargeting` 的准入从"空袭任务 / 扫帚任务"扩到 **`被指挥棒绑在飞行坐骑上的女仆`**：
  **发现 50 格 / 维持 128 格 / 无视排班盒子**——与扫帚/空袭同一套数字、同一套判据。
  `RideBindManager.drive` 的飞行档改用 `FlightTargeting.resolve`（问不到才退回 brain）。
- **主人牵引绳**：她离主人**超过 50 格**（复用上面的发现半径，不新增数字）就**放弃当前敌人、转去追主人**，
  日志「主人超出接敌半径 → 放弃敌人、转去追主人」；主人回到范围内自动复位。

### 四、没有动的

固定翼那一档**逐字未改**（它前/后位是推力、高度靠俯仰）；地面载具（坦克/装甲车）、
龙的悬空鞍位、光标清理、龙背插值全部沿用七百二十九。飞艇仍走位掩码（它有自己的升降轴）。

### 五、验收怎么看

日志搜「飞行档」，新格式：

```
模组坐骑 卓越前线载具(HELICOPTER) 飞行档 引擎=HELICOPTER 跟随档(离地3格)   鼠标X=6 鼠标Y=3.20 水平速度=0.24 期望=0.75 竖直速度=0.02 power=0.091 距目标=9格 高差=1
```

- 要挪窝时 `水平速度` 应往上走、`鼠标Y` 明显不为 0；
- 到点后 `水平速度` 回落到 0、并标 `悬停档(停住)`；
- `power` 应稳在 0.09 上下（0.045~0.12 之间）；若恒为 -1 说明反射拿不到 `setPower`。
## 实测七百二十九【同步到 1.20.1】直升机真悬停与真能平移 + 坐骑「光标」不再解不掉 + 龙背漂移根治

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版，同名 jar 覆盖；**未发平台**。

### 〇、玩家三条原话

1. 「现版本采用武装直升机的时候，还是做不到悬停。喜欢先往上飞三格，然后再慢慢掉下来。而且这个版本比较差劲的就是女仆彻底失去了前后左右移动能力，只会上下飞行。」
2. 「如果一只龙被绑定了以后并且上了光标，那就再也没有办法解除他身上的光标了。这边建议在解除女仆乘坐在坐骑上的情况以后就顺便解除龙身上的光标。」
3. 「女仆的位置还是在发生漂移。能不能强行绑定女仆坐在龙上龙的某块建模，然后每 tick 锁定那个位置呢？这样子也比在空中乱飘强得多。」

### 一、直升机「做不到悬停 + 只会上下飞行」——把「悬停档」从常开改成**停稳才开**，升降从开关改成 **PD**

**根因（反编译 `VehicleEngineUtils.helicopterEngine` + 实机日志双证）**

727 把悬停档设成了「只要是直升机且开关开着 = **常开**」。而引擎的悬停档每拍做三件事（`helicopterEngine:590-599`）：

- `pitchSpeed *= 0.2`——俯仰权限只剩两成；
- `setXRot(xRot * 0.97)` 且再减 `0.5 * Δ·viewVec`——**主动把机头拉回水平**；
- `setDeltaMovement(Δ.multiply(0.95, 1, 0.95))`——水平速度每拍打 0.95 折。

而直升机**唯一**的水平推进手段就是「低头 → 升力沿机体上方向量分出水平分量」（`add(getUpVec().scale(propeller*lift*0.66))`，`:686`）。上面三条合起来：压不下去（0.2）、压下去每拍又被拉回（0.97 + 减项）、推出来的速度再被指数衰减（半衰期约 13 拍）——**水平方向被彻底锁死**。这正是玩家说的「彻底失去了前后左右移动能力，只会上下飞行」。

「先往上飞三格、然后慢慢掉下来」是第二个独立缺陷：旧版升降是**纯死区开关**（差 > 0.75 就一直按抬总距、差 < -0.75 就一直按压总距），没有任何阻尼，而总距一按就一路加到 0.12 上限（`:638-641`）→ 升力过剩 → 冲过头 → 反过来急压 → 又冲过头，形成**极限环**。实机日志正是这一串（每 5 秒一条）：

```
高差=3 → -4 → -1 → 2 → 0 → -4 → -3 → 0
```

**修法**

1. **悬停档改成「停稳才开」**：判据是「水平距离进 `FLIGHT_ARRIVE`（3 格）**且**水平速度低于 `FLIGHT_PARK_SPEED`（0.05 格/拍）」。停稳时悬停档给的两件东西（姿态自稳 + 按竖直速度反调总距、系数 0.01 比非悬停档的 0.002 大 5 倍）正是「稳稳悬停」要的；**一旦要挪窝（跟随主人 / 盘旋 / 接敌）立刻退出**，把俯仰权限与水平速度还给引擎。用「速度也停了」而不只看距离，是为了避免盘旋时目标点在死区边缘反复穿越导致悬停档每几拍开关一次（抖）。
2. **升降改成 PD 控制**：用**实测竖直速度**把目标点提前——`预测落点 = 高差 - 竖直速度 × 提前量`（提前量 `FLIGHT_VY_LEAD = 10` 拍，覆盖 `power → propellerRot` 那一层约 5.5 拍的一阶滞后再留一档余量）。正在上升就把「还差多少」算少（提前松油、不冲过）；正在下坠就把「还差多少」算多（提前加油、接住）。死区 0.75 格不变，但它现在只是**容差**、不再是开关。
3. **删掉「高度已对齐但水平还远就给前进位」那条分支**：直升机的 `0x004` = `forwardInputDown` = **抬总距**（`setPower(min(power + 7e-4*powerAdd*..., 0.12f))`，`:628-641`），跟高度绑死，根本不是「前进推力」。旧版拿它顶平飞 = 一边说高度够了、一边继续爬。现在**水平移动只走俯仰、高度只走总距**，两条路彻底分开。
4. 前飞俯仰下限从 0.05 抬到 **0.08**（悬停档退出后俯仰权限是满的，这个量级才推得动她）。
5. 飞行日志新增 **`竖直速度=`** 与 **`悬停档(停住)`**：`高差` 与 `竖直速度` 同侧且高差在缩小 = 正在正常收敛。

**面板不变**（悬停高度 / 接敌高度 / 盘旋半径三项照旧），**地面载具与固定翼的既有行为不变**。

### 二、龙背上的「光标」再也解不掉——补上坐骑侧留痕 + 待选超时 + 兜底扫描

**根因（javap 实证 + 代码走查）**

原版发光标记**会写进存档**（`Entity.saveWithoutId` 把 `hasGlowingTag` 写进 `"Glowing"` 键，`Entity.load` 再读回来调 `setGlowingTag`），所以它跨存档、跨重启都在。而我们的清理只挂在「链路表里那条 Link 还在」上，于是有三条泄漏路径：

- **待选光标没有超时**：棍子点一下坐骑、然后走开，那条龙就永久发光（最常发生）；
- **`takePending` 在目标消失 / 换维度时直接 return `null`**，没撤标记；
- **`TAG_RIDE_MAID`（坐骑侧留痕）全树只有 `remove`、从来没有 `put`**——后果不只是「留痕白写」：重启后 `restore` 只认「她还在坐骑上」（`maid.getVehicle() != null`），而**龙那条她不是乘客、`getVehicle()` 恒为 `null`** → 那条龙重建不出链路，也就永远没人去撤它的发光标记（这就是玩家说的「重启后也解不掉」的根因）。

**修法（三重）**

1. **绑定成功时补写坐骑侧留痕**（`TAG_RIDE_MAID`），让「这只坐骑曾被我们标记过」有凭据可查。
2. **待选光标 30 秒超时**：`PENDING_AT` 记下点选时刻，每 2 tick 扫一遍，到点熄灯并写日志「待选光标超时」。同时在 `takePending` 里补上「目标没了 → 清标记」、在 `forgetPlayer` 里清时刻表。
3. **每 5 秒一次兜底扫描**：遍历所有实体，只看**我们自己的两个私有标记**（`maid_smart_ride_pending` / `maid_smart_ride_maid`）——
   - 带「待选」标记但没有任何玩家把它当作待配对目标 → 熄灯；
   - 带「坐骑侧留痕」但链路表里查无归属 → 熄灯 + 清留痕。

**绝不误伤**：判据是「带我们的私有键」，不是「在发光」——她自己中光灵箭、别的模组让谁发光，都不会带这两个键，一次都不会被碰。

**解绑那条主路照旧**（`releaseMaidImpl` / `releaseMaidQuiet` / `forgetMaid` 三处都会 `unmark(坐骑)`），本节补的是「主路之外漏掉的那些」。

### 三、龙一飞她就漂——把她的**渲染插值两端点**都由龙的端点推出来

**根因（javap `ClientLevel.tickEntities` / `EntityRenderer.render` 实证）**

实体的**画面位置不是「当前位置」**，而是 `getPosition(partialTick) = Mth.lerp(partialTick, xo, getX())`——`xo/yo/zo` 是「上一拍位置」，在每只实体自己 tick 的开头由 `setOldPosAndRot()` 从当时的当前位置拍下。于是：

- **龙**：`xo` = 龙上一拍位置、`getX` = 龙这一拍位置 → 模型平滑滑动；
- **她**：`xo` 是「上一拍 `setOldPosAndRot` 那一刻她在哪」——在**客户端**，她在那一拍之前刚被 `lerpTo` 的位置包**限流平滑**（原版位置包每 2 tick 才发一次），所以 `xo` 拿到的是「平滑到一半」的位置；而 `getX` 又被我们每拍硬写成鞍位。**两个端点一个来自插值中间态、一个来自最终态** → 她的曲线每拍往回缩一下，观感就是相对龙体持续漂移。

**修法（就是玩家说的「每 tick 锁定那个位置」）**

鞍位相对龙体的**偏移量在一拍之内几乎不变**，所以直接由龙的两个端点推出她的两个端点：

```
她的这一拍端点 = 龙的这一拍位置 + 偏移
她的上一拍端点 = 龙的上一拍位置 + 偏移
```

两条曲线**两端点逐拍同源**，插值中间态自然重合成同一条线，漂移消失。这个算法是**无状态的**（偏移当场算、不缓存任何「上一拍」，`xo/yOld/zOld` 一并对齐），因此与本方法在一拍里被调几次无关——她自己的 tick 之后调一次、龙 tick 之后又调一次（谁后 tick 谁说了算），两次结果完全一样，不会「越推越远」。

朝向也一并镜像（`yRot` / `yRotO` / `yBodyRot` / `yBodyRotO` / `yHeadRot`）：龙转身时她的模型跟着转，且转的插值曲线与龙同源（旧版压根不设她的朝向，龙一转她就是「坐在原地不动」，看起来又像错位）。插值字段的写入**严格限定在客户端**（服务端写它没有收益，而 `xOld/yOld/zOld` 还兼作服务端碰撞扫掠的「上一拍位置」，服务端乱写反而有风险）。

### 四、日志

| 搜什么 | 看什么 |
|---|---|
| `飞行档` | `竖直速度=` 与 `高差=` 同侧且高差缩小 = 收敛中；`悬停档(停住)` = 已停稳；`跟随档(离地N格)` / `接敌档(敌上N格)` = 当前档位 |
| `空战` | 接敌起手那一行「爬到敌上 N 格、绕着敌人盘旋（半径 M 格）」 |
| `骑乘指挥棒` | 「待选光标超时」「残留「待选光标」→ 已熄灯」「坐骑侧残留光标 → 已熄灯」 |
| `骑行同步` | 龙那条配对有没有同步给客户端 |

## 实测七百二十八【同步到 1.20.1】飞行载具高度分两档：跟随离地、接敌敌上 + 收紧盘旋半径

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版，同名 jar 覆盖；**未发平台**。

### 零、玩家原话（对 727 的补正）

「有一个地方搞错了呀，我是说跟随的时候保持离地三格的电路。但是遇到敌人还是要升高到比敌人高 15 格的位置的呀，同时缩小绕圈的半径。」

727 把「离地三格」套到了**所有**情形（连接敌也一起改成离地三格），本版把两档拆开。

### 一、高度分两档：**跟随 = 离地 3 格**、**接敌 = 敌上 15 格**

- **跟随档**（没有敌人：巡逻 / 跟着你）：目标高度 = 她**脚下的地面** + `combat.ride.airAlt`（默认 **3**）。这一项只管跟随。
- **接敌档**（有活的攻击目标）：目标高度 = **敌人所在位置** + `combat.ride.fightAlt`（新增，默认 **15**），并在这个高度上绕着敌人盘旋俯射。

**为什么这样就不会像 726 那样越飞越低**：高度进的是**目标点 Y**、由**总距**升降，姿态（俯仰）**不再决定高度**。所以不管敌人比她高还是低，机头都不会被压向地面。

### 二、盘旋半径再收（6 → 4）+ 根治「圈特别大」的真因

- `combat.ride.orbitRadius` 默认从 **6 收到 4**（范围 2~24）；老档里还是 6 的会一次性迁移到 4（独立标记 `orbitRadiusMigrated`，玩家自己调过的值不碰）。
- **真因**：727 的盘旋点每拍只沿切向前进 **0.14 格**，而 `driveFlight` 的距离死区是 **3.0 格**（目标点跟离 < 3 格就**不给俯仰**）→ 稳态滞后 ≈ 3~6 格，**实际半径 = 配置半径 + 滞后** → 能到 10 格以上（玩家两次都在说的那件事）。
- **修法**：改成**追逐式胡萝卜**——胡萝卜永远挂在她**当前实际方位角**再往前 `LEAD` 格（`LEAD = max(4.0, 0.85r × 快慢倍率)`，恒 > 死区）。滞后被 LEAD 吸收、不再叠加到半径上；她若飞出圈外 / 圈内，胡萝卜在半径 r 的圆上会把她径向拉回（自纯正）。旋向 / 快慢仍复用 `CombatOrbit`（随机性同源）。

### 三、飞行档的隐藏返修：悬停档下不再用**前进位**顶平飞

反编译实证：直升机的 `0x004` = `forwardInputDown` → `setPower(min(power + 7e-4*powerAdd*min(holdPowerTick,10), 0.12f))`——**它就是「抬总距」**，跟高度绑死。旧版在「高度已对齐但水平还远」时也送 `0x004`，等于一边说「高度够了」一边继续加总距 → 她会**一路爬过头**。现在悬停档下横向移动**只靠俯仰**（升力沿机体上方分出水平分量），松开 `0x004` 后引擎在悬停档按**竖直速度**反调总距。

### 四、面板 / 文本

面板（移动与行为 → 骑乘）现在是四行：**骑飞行载具·悬停与盘旋 / 悬停高度（跟随·离地格数）/ 接敌高度（比敌人高几格）/ 盘旋半径（格）**。日志搜「空战」看接敌那一行「爬到敌上 N 格、绕着敌人盘旋（半径 M 格）」；搜「飞行档」看当前是「跟随档（离地N格）」还是「接敌档（敌上N格）」。

> 【与 727 的关系】727 那一版有两处要回正：① `airAlt`（现叫「悬停高度」）**只管跟随**（离地 3），不再管接敌；② 新增 `fightAlt`（「接敌高度」，默认 15）专管接敌。旧档里 `airAlt` 还是 15的会按旧迁移规则搬到 3（已在 727 执行）；`orbitRadius` 还是 6 的本版搬到 4。

## 实测七百二十七【直升机悬停与同高空盘旋 / 骑龙直接下鞍 / 装弹返修 / 龙飞行错位 / 载具开火口径】

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版（726），同名 jar 覆盖；**未发平台**。

### 〇、玩家五条原话

1. 「女仆似乎不会让直升机悬停。而且在打精英敌人进行绕圈的时候，总是范围绕的特别大，而且高度很低。导致实际的命中率非常堪忧。最好是采用跟扫帚一样的机制，骑上直升机之后就进入悬停状态，离地三格左右。随后的盘旋也是悬停在同一高度盘旋。」
2. 「女仆如果坐到了龙上，虽然明面上系统消息写的是shift+右击让女仆下来。但实际上没用，导致除非把龙收起来，否则女仆是下不来的。这边建议将它改成跟载具一样的，直接拿着骑乘指挥棒右击就可以让他下来。」
3. 「扫描女仆的背包换弹机制似乎没能实现。」
4. 「如果龙是静止的时候还好。女仆可以很好待在龙背上，但是一旦龙开始飞行和跟随主人的时候，女仆的位置就会发生严重的改变和错乱。」
5. 「这边就有一点需要注意的，到底什么时候女仆才会让自己的载具发起攻击？我认为是，只要女仆进入了 I attack 状态下，那么就会让他的载具一起攻击了。」

### 一、直升机「不会悬停、绕得又大又低」——上机即悬停、盘旋锁定同一高度

**根因（反编译 `VehicleEngineUtils.helicopterEngine` + 实机日志双证）**

直升机的**高度只有两条路**：总距（前进/后退位）与悬停开关（上键位切）；航向/俯仰全走**鼠标通道**，位掩码里根本没有对应位。而 726 那一版写的是「先爬到敌人**上方** 15 格再盘旋」：

- 敌人一旦在她**脚下**（实机日志出现 `高差=-24`），俯仰就把机头压向地面 → 越飞越低、贴地乱窜；
- 盘旋半径又是**直接借扫帚那套**（`CombatOrbit.radius` 的区间是 `range(8)×0.75 ~ orbitMax(10)`，再乘 `CombatManeuver.radiusScale` 还会被放大）→ 圈特别大；
- 「悬停」开关只在**到达目标点那一拍**才开，而她的目标点在敌人上方 15 格、**永远到不了** → 悬停从头到尾没开过（玩家说的「不会让直升机悬停」就是这个）。

**修法**

1. **上机即悬停**：只要她骑着飞行载具（开关打开），`driveFlight` **常驻悬停档**——引擎在悬停档会把俯仰系数压到 0.2、滚转 0.05、偏航 0.5（姿态稳、不再点头画龙），并每拍把水平速度乘 0.95（不会冲过头）。
2. **高度锁「离地」而不是「离敌」**：目标高度 = 她**脚下的地面** + `combat.ride.airAlt`（默认改为 **3**，玩家原话「离地三格左右」）。地面高度是逐格往下扫第一个**碰撞形状非空**的方块顶面——她在洞里按洞底算、在桥上按桥面算（用 `level.getHeight` 会把她往天花板里推）。高度死区从 2 格收到 **0.75** 格，总距会持续微调、把机身钉在那一格上。
3. **盘旋保持同一高度**：接敌盘旋点的高度就是上面那个绝对 Y，**不再随敌人上下浮动**（旧版是"敌人脚底 + N"，敌人一跑高跑低她就跟着上下扎——正是玩家说的"在地上和高空5个左右跳动"）。
4. **半径独立旋钮**：新增 `combat.ride.orbitRadius`（默认 **6** 格），不再乘 `CombatManeuver.radiusScale`（那一层是扫帚的"蛇形/脱离再进/8字横切"，每一项都要求机身有真实的换向加速能力；直升机是悬停+侧移，套上去只会让圈忽大忽小、高度忽高忽低）。旋向与快慢**仍然复用 `CombatOrbit`**（与扫帚同一段状态机）。
5. **没有敌人时低空跟随**：主人离得够远就把目标点放在主人正上方、高度仍锁离地 3 格；就在旁边则原地悬停。

面板新增三项：**移动与行为 → 骑乘 → 骑飞行载具·悬停与低空盘旋 / 悬停高度（离地格数）/ 盘旋半径（格）**。日志搜「空战」看每场遭遇那一行「接敌 → 绕着敌人盘旋（半径 N 格、锁定离地 M 格）」；搜「飞行档」看有没有「悬停档」三个字。

> 【与 726 的语义变化】`combat.ride.airAlt` 由「爬到敌人**上方**多少格（默认 15）」改成「**离地**多少格（默认 3）」——老存档里存着 15 的会按新语义生效（离地 15 格悬停），想低飞请手动调到 3。

### 二、坐在龙上「下不来」——改成与载具完全同口径

**根因**：旧版这条链路每一处都只认 `MaidRideKit.isRideRider`，而它要求 `getVehicle() != null`；悬空鞍位方案里她**根本不是乘客**（723 起就不 `startRiding` 了）→ 对龙**恒为 false**。于是右击她 / 右击龙都只回一句「潜行右击可以让我下来」，而那条路实际走不通，玩家只能把龙收起来。

**修法**：新增 `isOurRider(maid)`（= 乘客档 `isRideRider` **或** 悬空鞍位档 `isDragonChairRider`），三处统一改口径：

- 右击**她自己** → 是棍子绑的就直接下鞍；
- 右击**那条龙** → 是自己的女仆就直接下鞍，不是才提示「已经有女仆了」；
- 潜行右击（女仆 / 坐骑 / 龙）→ 兜底路径也补上 `chairRiderOf`。

玩家原话「直接拿着骑乘指挥棒右击就可以让他下来」——现在就是这样。

### 三、装弹「似乎没能实现」——改用卓越前线自己的判据

**根因（反编译 `ItemAmmoStrategy` / `PlayerAmmoStrategy` 实证）**

卓越前线车的武器分**两条完全不同的吃弹路**：

- **物品型**（`ItemAmmoStrategy`）：配置写 `"AmmoType": "superbwarfare:small_shell_he"` 这种**普通物品 id**（AH-6 的机炮 / 火箭弹都是这一类）。它的 `getPlayerAmmoType()` **恒为 null**，弹就是那个**物品**本身。
- **枚举型**（`PlayerAmmoStrategy`）：配置写 `"@rifle"` 这种，才有 `getPlayerAmmoType()`。

726 那一版是**按 `Ammo` 枚举**去匹配的：读到物品型的 null → 直接 `return 0`；而且判物品用的是 `AmmoSupplierItem`（只有步枪弹那种"弹药补给物品"才是这个类），对物品型的武器**永远不匹配**。两道都错，所以**一颗都没搬过**（实机日志里一条「模组载具·装弹」都没有）。

**修法**：不问"这是什么类型"，直接问卓越前线自己——`AmmoConsumer.isAmmoItem(stack)`（= `MinecraftUtil.isSameItemStack(stack, consumer.stack())`）。它对**物品型与枚举型两种都成立**，还顺带把 NBT/组件差异也判掉。把车上**每一门 `useBackpackAmmo()` 的武器**的 consumer 都收进来，她背包里**命中任意一门**的子弹就搬进去（对不上的一件不动，每 0.5 秒检查一次）。

### 四、龙一飞起来她就「位置严重错乱」——客户端与龙同帧摆位

**根因（反编译 `ServerEntity` + `ClientLevel.tickNonPassenger` 实证）**

摆位**只在服务端做**（客户端那一支被 `isClientSide()` 挡掉了，因为链路表 `LINKS` 只活在服务端）。于是客户端的她「**不是乘客 + 又没人摆位**」，位置完全由两样东西决定：

- 原版**限流的位置包**（`ServerEntity` 每 2 tick 一次位置包，角度另算）；
- 客户端自己的 `lerpSteps` 插值。

而龙是**每帧插值渲染**的原版实体——两条插值曲线节拍不同步，误差就累积成玩家看到的"位置严重改变和错乱"。龙**静止**时两者都不动，所以看不出来；一飞起来就明显。

**修法**：新增 S2C 单包 `MaidSeatNetworking`（`maid_smart:maid_seat`），把「她挂在哪条龙上」由服务端同步给客户端（`RideBindManager.SYNCED_CHAIRS`）。客户端在**自己那一拍实体 tick 之后**，用**与服务端逐字相同的** `seatOnDragon` 从龙的当前（插值后）位置重算鞍位——客户端不再依赖限流位置包，**她与龙永远同帧**。绑定 / 解除 / 自动解除都会重发一次；新玩家开始追踪她时（`StartTracking`）补发；退出世界清空。日志搜「骑行同步」。

### 五、载具「到底什么时候开火」——收敛成一条：她进攻击状态就打

玩家口径：「只要女仆进入了 I attack 状态下，那么就会让他的载具一起攻击了。」

旧版只在**有目标时**才写：`maid.setTarget` 与炮塔/武器位的 AI 目标 UUID。于是**她一脱战，车还对着上一个目标继续开火**——"什么时候才开火"就不确定了。

现在判据收敛成**一条**：她此刻有没有活的攻击目标（`brain.ATTACK_TARGET` 优先，退回实体层 `getTarget`）。

- **有** → 目标同时写进①她自己的 `getTarget`（卓越前线内置的「Mob 乘客自动开火」链路读的就是它，反编译 `VehicleEntity:4014`）与②车上的炮塔 / 武器位 AI 目标 UUID（坦克那种走它）；
- **没有** → **两处都显式清空**（`null` 也照写）。

### 六、验证与范围

- 两树同步：**本版只有 `promaid_src_neo`（1.21.1）被动过**，`promaid_src`（Forge 1.20.1）一个字节未改；`git status` 显示 `promaid_src/**` 与 `patched/promaid-1.3.0-forge-1.20.1.jar` 无变化。
- neo 树 `javac` **0 错误**；`_mixchk` **180 PASS / 0 FAIL / 0 UNRES**；`build_promaid_neo.py` 出包 **925 条目 / 11,951,003 字节**；jar 内容核对（新类 `MaidAirCombat` / `MaidSeatNetworking` 在、四个新配置键在、两份 lang 在、本段 changelog 在）。
- 只部署 1.21.1 实例；平台一律未动。

## 实测七百二十六【特殊载具传送规矩 / 龙骑位错位 / 直升机空战 / 载具装弹 / 绑定顺序】

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版（725），同名 jar 覆盖；**未发平台**。

### 〇、玩家六条原话

1. 「对于卓越前线以及龙这两个特殊的载具，我觉得女仆的一些性质需要发生改变。……不会触发大部分传送，只有玩家手动使用排班表进行的传送，可以将她们传送过来。不会连着载具一起传送。只会把人传送过来。并且一旦进行传送了，那么就会立刻进行一次清除标记和解绑。也就是说，相比于原版的坐骑女仆并不会去主动产生传送。所有的传送方面的行为必须由玩家来。」
2. 「目前女仆跟龙之间还是有一点点小小的错位，就是女仆换位置的速度赶不上龙飞行的速度。」
3. 「女仆在驾驶武装直升机的时候总算可以跟着主人进行飞行了，但是在战斗的时候显得过于笨逼，发挥不出武装直升机的优势，应该要套用扫帚模式运动代码和逻辑，体现出空战的优势。至少离敌人要高出15格左右吧。现在基本上就是绕着敌人盘旋，但是总是在地上和高空5个左右跳动，敌人很容易就能打到。」
4. 「冰火传说的龙也是如此，当然，那个是龙自己AI的问题。……可以让龙在天上盘旋，并且发射龙息来对付敌人。除非空间过于狭小，否则不采用近战肉搏。」（**本版只做考察，未改**）
5. 「实际测试发现，如果先用骑乘棒绑定女仆再绑定载具是无效的。必须要先绑定载具再绑定女仆。也罢，咱们干脆就堵死了。描述以及代码上都要求必须要先瞄载具再瞄女仆。」
6. 「卓越前线，如果女仆身上有这个载具对应的子弹。能不能让女仆自己把弹扔进装弹区里面呢？」

### 一、① 特殊载具（卓越前线载具 / 冰火传说龙）：一切自动传送停用，只有玩家手动能传

**口径**：这两个"特殊载具"上的女仆**不再产生任何自动传送**——跨维度跟随、同维远距拉回、危险方块撤离、主人死亡归位、以及原版 TLM 的"离主人太远就瞬移回来"，**全部让位**。**只有玩家手动**用排班表（「传送到我身边」/「一键集合」）才能把她叫过来，而且**只传人、不传载具**，传送的同时**自动解除绑定 + 清掉金色标记 + 清 persistentData 痕迹**。

**为什么与"普通原版坐骑"分家**：普通坐骑（马/猪/骆驼）保留 716 的口径——自动/手动都"连人带坐骑一起搬"。这两类特殊载具按玩家这次的原话改成"她被动、你主动"。

**落点**：新增 `RideBindManager.isSpecialMountRider(maid)`（= 悬空鞍位链路 或 骑卓越前线载具）与 `detachForSpecialTeleport(maid)`（手动传送前先干净解除）。自动链路（`MaidChunkLoadManager.followIfCrossDimension` / `trySameDimPull`、`MaidTeleportPreserveMixin`、`SelfPreservationBehavior.shouldSkipTeleportForParked`、`DangerEscapeHandler`、`MasterDeathTeleportHandler`、`MaidHeatEscape`）一律改用这一个判据；手动链路（`summonOne` / `summonMaidTo` / `summonAll` / `tickPending`）改走"解除绑定 + 只传人"。

### 二、② 龙骑位错位：摆位补在"龙自己 tick 之后"

**根因（服务端 tick 顺序，javap 实证）**：`ServerLevel.tickNonPassenger` 的顺序是 `setOldPosAndRot → Pre → e.tick() → Post → 摆乘客`，而**同一 tick 内龙与女仆谁先 tick 由实体 ID 决定**。她的摆位挂在 `MaidTickEvent`（在她**自己的** `tick()` 里）：她先 tick 的帧里按龙的**旧位置**摆好，龙随后才飞到新位置——整整落后龙一帧（飞起来就是那个"错位"）。

**修法**：把摆位**同时补在龙自己的 `EntityTickEvent.Post`**上（谁后 tick，谁的收尾就是这一帧的最后一次摆位），她的位置收敛到龙这一帧的最终鞍位。两道最便宜的守卫排在最前（她 / 龙），其余实体一次方法调用即返回。

### 三、③ 武装直升机空战：套用扫帚模式的"爬到敌上、绕圈打"

**根因**：`MaidMountCompat.driveFlight` 收的"目标点"来自 `RideBindManager.drive`——算的是"她的走位记忆 / 主人跟随点"。**一有敌人它还在追主人**，于是低空乱飞、在低地与高空之间跳动。

**修法（新增 `MaidAirCombat`）**：飞行载具（直升机 / 固定翼）**一旦有攻击目标** → 改走**与扫帚模式同一套形状**：① **接敌爬升**先爬到敌上 `combat.ride.airAlt` 格（默认 **15**，玩家点名）；② **战斗盘旋**绕着敌人转圈打，半径 / 旋向 / 快慢 / 接敌机动**直接复用 `CombatOrbit` + `CombatManeuver`（同一段抽签与缓动，不是另写近似物）**。高度在面板「移动与行为 → 骑乘 → 空战·离敌高度」可调（5~40）。地面载具（坦克/装甲车）不受影响。

### 四、④ 冰火传说龙爱下地近战（**仅考察**）

**结论**：这是**龙自己 AI** 的行为，不是我们这条链路的问题。实证：`DragonAIAttackMeleeGoal.canUse()` 要求 `!isFlying() && !isHovering()` —— **近战目标只在它落地后才启动**；而"要不要起飞"由 `IafDragonLogic` 每拍一个很低的随机数（`getFlightChancePerTick`）决定，它还会因为 `doesWantToLand()`（飞久了 / 想降落）主动落地。它自带的空中攻击是**有的**（`IafDragonAttacks.Air`：`HOVER_BLAST` 悬停轰击 + `breathAttack` 吐息、`SCORCH_STREAM`、`TACKLE`），只是**不保证被选中**（`randomizeAttacks()` 三个里随机挑一个）。

**可行的修法（成本中等，风险高于本版其余各项）**：在"指挥棒绑的龙 + 有目标"这条链上，每拍强制 `setHovering(true)/setFlying(false)`、把 `airAttack` 钉成 `HOVER_BLAST`、并且不满足"空间狭小"时才压住 `doesWantToLand`。**要干预它自己的飞行/攻击状态机**（每拍跟 `IafDragonLogic` 抢状态），实机行为要反复调；且它会影响**所有**骑着这条龙的观感。**本版只做考察、不动它**——等你点头再单开一批。

### 五、⑤ 绑定顺序固定为"先瞄载具、再瞄女仆"

**修法**：`RideBindManager.handle` 里删掉"先给女仆挂待选"那条路——右击女仆且手里没有已选中的坐骑时，**直接拒绝**并回一句「先用骑乘棒右击坐骑，再来右击我」。物品说明（`item.maid_smart.ride_baton.desc1/2`）同步改成"必须**先瞄坐骑、再瞄女仆**"。

### 六、⑥ 女仆自己往载具装弹

**根因（反编译实证）**：车的枪弹从 `GunData.countBackupAmmo(getAmmoSupplier())` / `withdrawAmmo` 取，而 `VehicleEntity.getAmmoSupplier()` 返回的是**车自己**；车注册了 `Capabilities.ItemHandler.ENTITY → getInventory()`（`VehicleContainerHandler`，`ModCapabilities$registerCapabilities$9` 反编译实证）——车的备用弹药**只在车自己的容器里找**，她背包里的子弹**车看不见**。

**修法**：她骑在卓越前线载具上时，从车里那门枪的 `selectedAmmoConsumer().getPlayerAmmoType()` 读出**这车当前武器吃的那一种**子弹，把她背包里**属于这一种**的弹药搬进车的容器（只搬对得上的，每 0.5 秒检查一次、按需搬）。日志搜「模组坐骑·装弹」。开关 `combat.ride.ammoFeed`（默认开）。

### 七、本版范围与安全边界

- **只动 1.21.1（NeoForge）**；1.20.1（Forge）一个字节都不改，jar 维持上一版。
- 整条链路**全程反射**——没装这两个模组 / 换了版本 / 改了包名，一律不激活，一个字节都不碰原版；而且**只认指挥棒绑的那条关系**，原版/别的模组让她坐上去的场合一次都不会碰。
- 新增面板项：骑飞行载具·空战 / 空战·离敌高度 / 骑载具·替她装弹（「移动与行为 → 骑乘」）。


## 实测七百二十五【骑乘坐姿 / 龙骑位观感 / 直升机驾驶 / 语音补录】

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版（724），同名 jar 覆盖；**未发平台**。

### 〇、玩家四条原话

1. 「女仆骑乘的时候应该是坐姿。」
2. 「现在女仆跟龙之间还是有较大的分离关系。看着很不自然。为什么玩家坐在龙上面就不会出现这种情况呢？」
3. 「女仆驾驶直升机的时候堪称灾难，完全不会左右移动和直上直下。」
4. 「新的语音也需要训练一下。」

### 一、①骑乘时她应该是坐姿（顺带治②"跟龙分离"）

**根因（反编译实证）**：女仆的**可见坐姿**只有一个来源——实体自己的坐姿标志 `EntityMaid.isMaidInSittingPose()`（= `TamableAnimal.isInSittingPose()`，读 `DATA_FLAGS_ID` 第 0 位）。TLM 两条渲染路径都问它：Gecko 模型把 molang 变量 `tlm.is_sitting` 绑到它（`TLMBinding` 反编译实证），Bedrock(JS) 模型走 `EntityMaidWrapper.isSitting()` 返回同一个方法（反编译实证）。而**骑乘这条路上她从来不是坐姿**——原版只有 `AbstractHorse` 那一系（马/骆驼/驴）在玩家骑上去时置坐姿；卓越前线的载具与冰火传说的龙都不是 `TamableAnimal` 的坐骑语义，**没人替她置这个位**，所以她一直用站立模型。

**修法**：绑上（载具/龙/原版兽皆可）→ `setInSittingPose(true)`；解绑 → 还原成绑前的值（你的原版"坐下"指令、手动让她坐下，一个字节不丢）。全走 TLM 自己的公开方法 `isMaidInSittingPose()/setInSittingPose(boolean)`。链路**每拍复述一次**坐姿（TLM 的几个任务会把它清回站姿），重载后重建链路同样补上。

### 二、②骑龙"有明显的空档、看着不自然"

**根因**：龙的鞍位公式 `positionRider` 摆的是「`getRiderPosition()` + 乘客身高」这个点。**你**坐在那个点上用的是**坐姿模型**，所以贴鞍；而女仆旧版用**站姿模型**落同一个点——看起来就是"站在鞍位上方一整格、跟龙有段空档"。**所以本档只动坐姿标志、不动落点公式**：摆位与你逐字相同、只把模型换成坐姿，观感即与玩家一致。这正是"为什么玩家坐着就不会分离"的答案。

### 三、③直升机：左右不动、直上直下不动（堪称灾难）

**根因（反编译 `VehicleEngineUtils.helicopterEngine` 实证）**：直升机的**航向/俯仰/滚转**三项全都写在**鼠标通道**上，位掩码里根本没有对应位：

```
setXRot(xRot + (onGround?0:1.5) * pitchSpeed * getMouseMoveSpeedY() * propeller)   // 俯仰
setYRot(yRot + yawSpeed * clamp(… * getMouseMoveSpeedX() * propeller, -10, 10))    // 偏航
setZRot(roll - rollSpeed * (deltaRot + (onGround?0:0.25) * getMouseMoveSpeedX()…)) // 滚转
```

左右位（0x001/0x002）在直升机里**只**影响 `deltaRot`（滚转），**不改偏航**——旧版"送左右位"= 机头不转 = 原地打转/不动。升降同理：引擎没有竖直轴，高度全靠**总距**（前位 0x004 加、后位/下位减）。而旧版一到目标附近就 `brakeVehicle` 把 power 归零（那是地面车的刹车）→ 空中直接掉高度、反复"飞出去再被拉回来"（实机日志：`位掩码=5 引擎=HELICOPTER 距目标=11格 → 38格 → 42格`，然后几秒一条"距离超 48 格，已连人带坐骑传送回主人身边"）。

**修法（换一整套飞行驾驶）**：
- **偏航/俯仰写鼠标通道**（`setMouseMoveSpeedX/Y`，与玩家推鼠标同一条路径）；
- **升降走总距**（目标更高抬总距 + 抬头，更低压总距 + 低头，高差进死区就保持当前总距）；
- **平飞**够远就低头前倾 + 抬总距；
- **贴到目标开悬停**（`setHoverMode`，引擎自己摆平姿态、衰减速度）；
- **绝不对飞行载具用地面刹车**：空中把 power 归零就是掉高度。
- 档位判据只含**直升机与固定翼**（它们的航向/俯仰在鼠标通道）；**飞艇不进这一档**——它的偏航走左右位、升降走上下位（`setLiftSpeed`），地面那套位掩码本来就够。
- 反射探测失败 → 退回地面位掩码，一个字节不变。

### 四、④新语音（骑乘 / 龙 / 载具 9 条）

给 719~724 引入的、尚未登记的骑乘/龙/载具气泡补了日语语音（**内置日语语音包 152 → 161 条**）：`ride_seat_fail`（没能坐上它的鞍位）/ `ride_dragon_ok`（我坐它背上啦）/ `ride_dragon_here`（我已经在这条龙背上啦）/ `ride_occupied`（这只坐骑已经有女仆了）/ `ride_occupied_mine` / `ride_dragon_busy` / `ride_wreck`（这辆载具已经报废了）/ `ride_no_seat` / `ride_too_small`。清单（manifest）与日语台词表、情绪档（cheer_ride / plead_ride）三处同步登记，音频已归一化（峰值 -1.5 dB）并打进 jar。

### 五、安全边界（与上一版同口径）

整条链路**全程反射**：没装这两个模组 / 换了版本 / 改了包名 → 一律不激活，一个字节都不碰原版；且**只认指挥棒绑的那条关系**——原版 / 别的模组让她坐上去的场合一次都不会碰。坐姿只在链路存活期间生效，解绑即还原。

### 六、排查

- 搜 `模组坐骑`：被她接管的那只坐骑这一档的动作；**飞行载具另搜 `飞行档`**，会写出鼠标通道值、总距位、距目标与高差。
- `她站着骑 / 跟龙有空档` → 确认「模组坐骑兼容」开着；本版起绑上即坐姿。
- `直升机乱飞 / 不动 / 掉高度被反复拉回` → 搜 `飞行档`；她开直升机时**偏航俯仰走鼠标通道、升降走总距、贴住目标会悬停**，与地面车完全不同。

**完整逐条更新日志**见仓库 `CHANGELOG.md`。


# 实测七百二十四【卓越前线载具四期 / 冰火传说龙骑位五期】

> 版本号不变，仍是 v1.3.0(beta)。**本版已同步到 1.20.1（Forge）与 1.21.1（NeoForge）两侧**；1.20.1（Forge）已跟上上一版（723），同名 jar 覆盖；**未发平台**。

### 〇、玩家六条原话

1. 「女仆骑着卓越前线载具的时候没有刹车机制。导致会不断的漂移。而且坐上去绑定之后会被其他的女仆抢走坐骑。」
2. 「然后就是像坦克这种超大型载具，应该要保证不撞到主人，现在就是虽然会跟着主人移动，但是会直接把主人撞倒。应与主人拉开距离才对。」
3. 「而且有一个语义方面的问题你似乎搞错了。如果一个女仆与一个载具达成了绑定。那么之后玩家再绑定第2个女仆和载具的时候，并不会取消第1个已经达成的乘坐。」（补充澄清：「不是说同一个载具可以绑多个女仆，而是说已经达成用绑定的方式乘坐载具A的甲女仆，并不会因为玩家去绑定乙女仆乘坐B载具而解绑。」）
4. 「转向问题还是没解决，每次拿骑乘棒右击一下载具，玩家的视角都会转一下，这很明显不符合直觉。」
5. 「而且骑坦克的时候攻击欲望太低了，也不会使用坦克上的炮弹。逻辑应该是和骑马远程攻击的运动逻辑一样。」
6. 「如果是在骑龙的时候，女仆乘坐的位置还是会发生错误，并且会时不时的传送和断开乘坐。」

### 一、①卓越前线载具：真刹车（不再一路漂移）

**根因（反编译实证）**：旧版"停车"只发 `processInput(0)`（清输入）。而 SWB 引擎里油门只是**松手**：`setPower(power * 0.96)`、地面摩擦系数才 `0.54~0.79`（`VehicleEngineUtils.wheelEngine:250-257`、`wheelChairEngine:996-999`）——power 从 1 衰减到 0.1 要 ~55 拍，`deltaMovement` 一路拖着走。我们**从没写过刹车量**。

修法：新增反射 `setPower(float)` / `setDeltaRot(float)` / `setTargetSpeed(double)`（javap 实证 1.20.1 hotfix 与 1.21.1 同名同描述符）；`stopVehicle` 改成 `processInput(0)` + **三件一起归零** + 按拍给当前动量打 0.45 阻尼（SWB 的 `setDeltaMovement` 覆写体只在**加速**时限幅，减速直通，安全）。停车带随车速前瞻（`band = 停车距离 + |Δ|×10`）。

### 二、②大车不再撞主人（停车距离按车体尺寸）

旧的停车判据是全局固定 `STOP_SLACK = 1.5` 格——对半宽 2 格上下的坦克等于"顶到人身上才停"。现在 `stopSlackFor(mount) = max(1.5, getBbWidth() + 1.0)`，并与主人自己设的 `followDist()` 取大：坦克自动拉到 ~3~4 格，小载具仍 1.5 格。**代价**：大车跟随主人时会停得比以前远。

### 三、③绑定语义修正（甲不再被乙挤掉）

旧版 `bind()` 第一句是 `releaseOtherLinks(player, maid)`——"一位玩家名下只留一条骑乘链路"，于是给甲+载具A 绑好后再配乙+载具B，**甲会被静默解绑**（实机日志实证：22:08:48 绑甲TRACK → 22:08:57 绑乙同一辆 TRACK，中间没有任何"换绑"日志）。`releaseOtherLinks` / `releaseMountLinks` 两个方法**整个删除**。现在只保留"她自己再绑一次 = 换绑"；同一辆坐骑已有**另一位**女仆时**拒绝**乙（气泡「这只坐骑已经有女仆了～」）并**绝不碰甲**。

### 四、④右击载具不再转玩家视角

**根因（反编译实证）**：SWB `VehicleVecUtils.setDriverAngle(vehicle, player)` 是全 jar **唯一**改玩家 yRot/xRot/yHeadRot 的地方，而它在 `VehicleEntity.interact` 里 `player.startRiding` **之前**就被调用（:3172 / :3184）——我们随后拦下登乘也拦不回已经转过的视角。

修法：右击那一下前后做**视角快照 / 还原**；客户端另打一个"视角钉子"（快照 + 4 拍时效），由客户端专属类 `com.maidsmart.client.RideBatonViewClamp` 每客户端 tick 复述回去（放客户端专类是为了不让 `net.minecraft.client.*` 进两侧都会加载的 `RideBindManager`，避免专用服务器崩）。玩家自己主动转视角不受影响。

### 五、⑤坦克用自己的炮弹（炮塔 AI 目标）

**根因（反编译实证）**：`VehicleEntity.baseTick:3751-3768` 里，Mob 乘客的自动开火读的是炮塔/武器位的 **AI 目标 UUID**（`turretAutoAimFromUuid(getAiTurretTargetUUID(), …)` / `passengerWeaponAutoAimFormUuid(getAiPassengerWeaponTargetUUID(), …)`）。旧版只写 `maid.setTarget`，**从没写那两个 UUID** ⇒ 炮塔恒无目标 ⇒ 从不算弹道、从不开炮。

修法：新增 `applyVehicleAiTargets`，把她的目标 UUID 同时写进**炮塔 AI 目标**与**武器位 AI 目标**（无人位写 `""` 清掉）；`tickAttack` 的 VEHICLE 分支接线。另：骑载具时她没目标会节流地 `AutoCombatSwitch.tryEngagePublic(maid)` **主动索敌**（对齐"骑马远程"的接敌积极性）。日志新增「模组坐骑·炮位」。

### 六、⑥冰火传说龙：每拍摆位 + 冻结她自己 + 不再因距离断开

两个 bug：
- **位置错 / 抖**：摆位挂在每 2 tick 的 `tick` 里，跟不上每拍都在动的龙；且她是**普通实体**（不是乘客），大脑/寻路每拍都可能把她往外挪。现在接 TLM 的 `MaidTickEvent`（每 tick，与服务端门控，同 `GunnerTetherManager.onMaidTick` 范式）做 `setGravity(false)` + `freezeOnSeat`（停导航 + 清速度 + 清 `WALK_TARGET`）+ `seatOnDragon`；`tick` 里那条 2 拍摆位留作兜底。
- **时不时传送 / 断开**：她不是乘客 ⇒ `MaidTeleportPreserveMixin`、`SelfPreservationBehavior.shouldSkipTeleportForParked`、`DangerEscapeHandler`、`MasterDeathTeleportHandler.shouldStayPut`、`MaidHeatEscape` 那些"停放豁免"**全都不认她** ⇒ TLM 原生 `teleportToOwner` / 救援传送把她从龙背拽走 ⇒ 下一拍距离 > `CHAIR_MAX_GAP=8` ⇒ **解除链路**；而解除走的 `releaseMaidQuiet` **不还原重力、不还原龙的行动档** ⇒ 她永久无重力、龙永久卡跟随档。修法：① 给以上所有豁免补 `isDragonChairRider` 判据；② 距离改**拉回鞍位**而非解除（只有她/龙/主人真没了或跨维度才解除）；③ `releaseMaidQuiet` 补上 `setGravity(maid,true)` + `restoreDragonCommand`；④ neo 的 `recallFromFlight` / `recallFromFlightTo` 补"只传她 + 干净解除"分支（与 `followIfCrossDimension` 同口径）。

### 七、验证

两树其中 **neo 树**：`javac` 0 错误；`_mixchk.py` PASS=180 SKIP=10 UNRES=0 FAIL=0；`build_promaid_neo.py` 打包通过（`verify_jar_classes.py`）；只读核对 `_vt724.py`、jar 内容核对 `_jarchk724.py`；**只部署 1.21.1 实例**。**Forge 侧零改动**（`git status` 可验：`promaid_src/**` 与 forge jar 未变）。

# 实测七百二十三【冰火传说龙骑位四期：降级为"悬空鞍位" / 卓越前线轮椅转向 / 指挥棒右击二期】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），同名 jar 覆盖；**未发平台**。

### 〇、玩家三条原话

1. 「关于龙方面的问题，还是没能解决。我采用降级方案，骑龙的话，那就仅仅是把女仆挂在玩家的骑乘位上，但并没有真正的骑在龙上。随后龙的行动逻辑自动转变为跟随玩家。玩家手动使用日程表进行传送那也仅仅是传送女仆不传送龙。也就是说移动的逻辑仍然是龙在进行移动。女仆相当于仅仅是坐在一把悬空的同位置的椅子上而已。这个处于是开后门的无奈之举。先将原有的代码删除掉，然后替换成这个就行。此时玩家对龙进行右击会显示已经占用了。阻止一下右击骑龙的行为。」
2. 「关于卓越前线的载具，拿指挥棒右击之后，玩家的确没有坐上去，但是右击完之后玩家的位置发生了改变。似乎是坐上去秒坐下来的结果。」
3. 「现在在进行一番操作之后，的确可以把女仆绑在轮椅上了，但是女仆移动的路径完全就跟女仆应有的路径不符。大部分情况是坐上轮椅之后，朝轮椅面朝的方向移动个几步，然后就停在那边了。为什么骑马就不会出现这种情况呢？」

### 一、①冰火传说龙：**整个"真骑"方案删掉，换成悬空鞍位**（玩家点名的降级方案）

**为什么真骑走不通（留档，722 的结论）**：龙**覆写了** `positionRider(乘客, 回调)`，覆写体是「先调 `super`、**返回之后**再判一次乘客身份」，判据是 `getControllingPassenger()`——它只认"主人"（1.21.1）或"玩家"（1.20.1）。女仆永远不满足 ⇒ 龙把她当**嘴里的猎物**：摆到嘴边 + 切成 `ANIMATION_SHAKEPREY`（甩咬骨骼动画，把整个模型带跑偏＝"建模只剩一块"）+ 55 刻后咬一口（伤害×2）再 `stopRiding()`（＝"被咬死"）。`ci.cancel()` 只能取消**基类那一份**，覆写体在 `super` 返回之后照样写；722 试着在乘客自己每 tick 末尾再抢回鞍位，但龙的状态机每拍都在跟她抢——横跳／模型／被咬三个症状轮着来。所以按玩家的话**删掉整套旧代码**（`EntityDragonMaidSeatMixin` 整文件删除、`shouldSeatMaidOnDragon` / `enforceDragonSeat` / `stopPreyShake` / `keepDragonAirborne` / `driveDragon` / `stopDragon` 全部移除），换成：

- **不真骑**：`maid.startRiding(龙)` 这一步**取消**。她不是乘客 ⇒ 龙那条猎物分支**永远不会被走到**（那是 `positionRider` 里的代码，只有乘客才进）⇒ 横跳／模型只剩一块／被咬死**从根上没有了**。
- **挂在骑乘位上**：每 2 刻由我们 `setPos` 到 `getRiderPosition()`（龙给**玩家**算的那个鞍位，含俯仰/飞行补偿）+ 她的身高，再过一道 `freeSeatY`（别把她按进方块里）。位置与玩家骑龙时**逐字相同**——就是玩家说的"坐在一把悬空的同位置的椅子上"。
- **不会掉下来**：绑上时 `setNoGravity(true)`，解绑时还原。她不是乘客，原版没有东西托着她。
- **移动仍然由龙进行**：我们**不驱动龙**（旧的 `driveDragon` 已删），龙自己那套飞行物理照常跑。
- **龙的行动自动转为跟随玩家**：绑上时把龙置 `setCommand(2)`（原版语义：`DragonAIEscortGoal.canUse()` 要求 `getCommand()==2` 才跟着主人走）；解绑时还原成绑定前的值。
- **传送只传女仆**：传送链路（跨维度 / 一键集合 / 同维远距拉回）对龙**不再走"连人带坐骑一起搬"**，改为只传她一个人（判据 `isDragonChairRider`，放在 `isRideRider` 之前）。跨维度时先干净解除这条链路（还原她的重力与龙的行动档）再传。
- **右击龙显示"已被占用"、并且骑不上**：指挥棒右击被占的龙会明确回一句（自己的＝"我已经在这条龙背上啦～（潜行右击可以让我下来）"，别人的＝"这条龙的背上已经有女仆了～"）；同时 `denyMountForBatonHolder` 把**玩家登龙**那一下堵死（不看手里拿什么——他再骑上去会跟她重叠、龙也会立刻把她当非控制乘客走猎物分支）。
- **偏离即解除**：她离龙超过 8 格（被别的模组拉走／传送走／龙换维度）就解除链路，不让她在远处凭空飘着。

### 二、②指挥棒右击载具后"玩家位置变了"＝**客户端那一份从来没被拦**

玩家原话「玩家的确没有坐上去，但是右击完之后玩家的位置发生了改变。似乎是坐上去秒坐下来的结果」。根因（反编译 + 类结构实证）：我们的右击闸 `onInteract` / `onInteractSpecific` 判据写的是 `instanceof ServerPlayer`——而这个事件**两侧都发**（服务端 `Player.interactOn`；客户端 `MultiPlayerGameMode` → `LocalPlayer.interactOn` 同一条链），所以**客户端那一份从来没被取消**。而卓越前线的 `VehicleEntity.interact` 在**客户端本地**也跑，它里面有 `VehicleVecUtils.setDriverAngle(this, player)`（把玩家转向车头 ⇒ 你看到"位置/朝向变了"）和"第一个非玩家乘客一律 `stopRiding()`"（⇒ 女仆被踢下去 ⇒ 再落座 ⇒ "秒坐秒下"）。修：两条闸都放宽到 `Player`；客户端那一份**只取消事件、不执行业务逻辑**（绑定/解除只由服务端做一次，否则同一个右击会在两侧各绑一次）——本地预测也不会再进 SWB 那段。

### 三、③轮椅走几步就停＝**轮椅引擎的转向只读乘客的头，不读左右位**

玩家原话「为什么骑马就不会出现这种情况呢？」——实机日志就是证据（19:15:14 起每 5 秒一行）：`位掩码=6 … 距目标=8格` → `位掩码=1 … 距目标=15格` → `位掩码=2 … 距目标=23格`——**我们一直在送左右位，它却原地打转**。反编译 `VehicleEngineUtils.wheelChairEngine` 实证，它跟方向有关的只有这一句：

```
diffY = clamp(-90, 90, wrapDegrees(passenger0.getYHeadRot() - this.getYRot()));
this.setYRot(this.getYRot() + clamp(0.4f * diffY, -5*steeringSpeed, 5*steeringSpeed));
```

也就是说：**它每刻把车头拉向"第一位乘客的头朝向"**，而左右位只在 `wheelEngine`（普通轮式车）里被读——轮椅这台引擎**根本没有那段**。女仆是 Mob，头朝向由她自己那套决定；她一坐上车就不再走路（乘客位移被 `rideTick` 吃掉），头常常停在原地不动 ⇒ 车头永远对着同一个方向 ⇒ 走几步就顶住。**骑马不会出现**正是因为它走的是 `GroundPathNavigation`（喂地面寻路那条渠道），压根不经过这套引擎。修（只针对引擎名 `WHEELCHAIR`，其余引擎一字不改）：① 把目标方位角写进乘客的 `setYHeadRot`（引擎唯一读的那个）；② **同时按有限速率（每拍 ≤12°）把车头 yaw 拽向目标**——引擎那一项被 `5*steeringSpeed` 卡住（轮椅默认 0.1 ⇒ 只有 0.5°/拍 ≈ 10°/秒，太慢就是"走几步就顶住"）；③ 这一档**不再等转向**，够远就踩油门（引擎每拍都在把车头拉过来，站着不冲正是"顶住"）。

### 四、边界（一个字节都不碰的场合）

- 龙：只有**有主女仆 + 冰火传说的龙 + 这一对在链路表里**三者同时成立才介入。玩家本人骑龙、别的模组的生物当乘客、无主女仆——一次都不会被调到。
- 轮椅：只有引擎名恰为 `WHEELCHAIR` 才走头朝向档；其余引擎（普通轮式/履带/船/直升机/固定翼/飞艇）**照旧左右位**，一字不改。引擎名拿不到（没装/换版本）→ 照旧左右位。
- 交互闸：`ride.batonExclusive` 关掉 = 与上一版一字不差。

### 五、排查

- 搜「骑乘指挥棒」：绑定那一行在悬空鞍位档下会多出「（悬空鞍位降级方案：不真骑，龙置跟随档，传送只传她）」。
- 搜「模组坐骑」：轮椅档会多写「头朝向=NN」；其余引擎照旧只写位掩码。
- 搜「不登乘」：被占的龙、以及手持指挥棒的玩家登乘，都会留一行。


# 实测七百二十二【冰火传说龙骑位三期：横跳 / 模型只剩一块 / 被咬死全治 + 骑乘指挥棒独占右击三期】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），同一份 jar 覆盖发布；**未发平台**。

### 〇、玩家两条原话

1. 「先使用指挥棒右击龙，然后再右击女仆。女仆会显示坐上去，坐上去之后，龙的整个建模只剩下一块。其他的全部被卡掉，然后女仆就在龙的身上反复横跳。」
2. 「卓越前线，我测试的载具是轮椅……玩家使用指挥棒右击，会自己坐上去以后立刻坐下来（而不是像右击其他生物一样绑定）」

### 一、龙：横跳 + 模型只剩一块 + 被咬死——根因（反编译实证）

**三个症状是同一个根因的三面。** 冰火传说的龙**覆写了**两参 `positionRider`（社区版 1.21.1 `DragonBaseEntity.positionRider` / 1.20.1 `EntityDragonBase.m_19956_`），覆写体是：

```
positionRider(passenger, callback) {
    super.positionRider(passenger, callback);        // ← 720/721 拦的就是这里
    if (hasPassenger(passenger)) {
        if (getControllingPassenger() == null
                || !getControllingPassenger().getUUID().equals(passenger.getUUID())) {
            updatePreyInMouth(passenger);            // ← 女仆永远走这一支
        } else { ...玩家鞍位... }
    }
}
```

- 而 `getControllingPassenger()` 两头**都只认主人**（`passenger instanceof Player` + 是主人）——女仆两边都不满足 → 永远走猎物支。
- 那一支做三件事：把她按到**嘴边 / 脚下**（每拍左右摆动）、把龙自己的动画切成 `ANIMATION_SHAKEPREY`（“甩猎物”循环动画）、并在 `animationTick > 55` 时**咬她一口（伤害×2）再 `stopRiding()`**。
- 所以三个症状对应：摆动 = **横跳**；鞍位落在方块里 = **窒息**；SHAKEPREY 那套骨骼动画把整个模型姿态带跑偏 = **“建模只剩一块”**；55 刻后那一口 = **致命**。

**实机日志实证**（`promaid.log`）：13:12:20 绑定成功 + 同秒“累计吃到 1.0 点窒息伤害”；13:12:24 连人带坐骑传送；13:12:31 她**死亡并触发自动复活** —— 上龙到死 11 秒。

**为什么 720/721 那两道拦不住**：它们拦的是**原版那一份**位置写入（`Entity.positionRider`），而覆写体是在 `super` **返回之后**再写一遍 —— `ci.cancel()` 取消不了那一遍。

**修法**：在**她自己每 tick 的最后一步**（原版顺序：`rideTick() { tick(); … vehicle.positionRider(this); }` 的 **TAIL**）再抢一次鞍位 —— 那一刻坐骑所有动作都已做完，我们最后落笔谁也挪不走她；同时：① 把 `ANIMATION_SHAKEPREY` **复位**（否则模型还是那副撕咬姿态）；② 把龙**钉在“飞行”这一档**（`getRiderPosition()` 里“飞/悬停”与“站在地上”两档差约 4 格——两边互翻就是横跳的另一半）；③ 位置照旧过 `freeSeatY`（抬出方块，最多 6 格）。

### 二、骑乘指挥棒独占右击：闸门下沉到“登乘”这个动作本身

**玩家原话**：「拿着骑乘棒右击卓越前线的载具会直接坐上去，而不是绑定」。

**721 那道 `EntityMountEvent` 闸为什么还不够**（日志实证）：2026-09-29 16:07 那次测试里这两行**同时**打出：

```
16:07:10.572  [骑乘指挥棒] 绑定：主人=Khragg 女仆=圣女酒狐 坐骑=卓越前线载具(WHEELCHAIR)
16:07:10.573  [骑乘指挥棒] Khragg 手里拿着指挥棒 → 不登乘（独占右击：这一下只归棒子）
```

绑定成功、闸也拦下了，玩家眼前却仍然是“自己坐上去了”。原因在**拦的层次**：`EntityMountEvent` 是在 `startRiding` **内部**发的，取消它等于“先同意上车、再撤销”（原版撤销路径本身就是一次可见的上车+下车）；而卓越前线的载具是**客户端本地**上车的（反编译实证：它自己的客户端 mixin 在收到挂载包时本地调 `startRiding`——`ClientPacketListenerMixin.vehicleEntityUpdate`）——客户端那一份预测根本不看服务端的事件结果。

**修法**：新增 `EntityBatonMountGateMixin`，拦**原版登乘入口本身**（`Entity.startRiding(Entity, boolean)` / 1.20.1 `Entity.m_7998_`）的 **HEAD**：那一刻**什么状态都还没改**，直接返回 false（与“这只坐骑不让你骑”同一条原版返回）——没有“上车再撤销”，也就没有可见的坐上去。两侧同款（客户端也拦，不会出现“本地先坐上、服务端再把你拉下来”的抖）。

**只拦“拿着指挥棒的玩家骑别的坐骑”**：女仆自己坐上去（棒子绑的那条）与武装栓绳（把你自己挂到女仆/扫帚上，判据里直接放行）一个字节都不受影响。关掉独占档 = 与上一版一字不差。

### 三、验证

- 两树 `javac` **0 错误**；注入点审计 `_mixchk.py` **PASS=185 FAIL=0 UNRES=0**（新增两个注入点后从 181 升到 185）。
- 打包后只读核对：`_vt722.py` PASS=72/0（新代码与镜像）、`_reg722.py` PASS=36/0（边界：只拦玩家+手持指挥棒；女仆与栓绳不受影响；抬升仍≤ 6 且失败回退；只复位 SHAKEPREY 一条动画）、`_jarchk722.py` PASS=44/0（两个 jar 内实体与注册）。
- 部署三处（两个客户端 + 服务端测试包）两两 `match=True`。


# 实测七百二十一【冰火传说龙骑位二期（不再横跳 / 不再陷进方块窒息）+ 骑乘指挥棒独占右击补闸】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 〇、玩家两条原话（上一版之后）

1. 「现在女仆大概可以骑上龙了，但是会在背上反复横跳，甚至直接陷到地里面窒息。而且龙的建模会被
   直接卡掉。」
2. 「右击问题还是没有解决，拿着骑乘棒右击卓越前线的载具会直接坐上去，而不是绑定。」

### 一、龙背横跳 / 陷地窒息——三个根因（都有字节码或实机日志实证）

**① 传送那条路绕过了上一版的漏斗。** 上一版只拦了**一参**漏斗（1.20.1 `Entity.m_7332_` /
1.21.1 `positionRider(Entity)`，两者都是 `public final`）。但原版里还有第二个入口直接调
**两参**的那个：`Entity.m_276804_()`（= `teleportPassengers`）：
`getSelfAndPassengers().forEach(p -> { for (e : p.passengers) p.m_19956_(e, Entity::m_6027_); })`，
javap 实证。也就是说**每一次传送**（`m_6021_` / `m_264318_`，含本模组「连人带坐骑送回主人身边」
那条链路）都绕过一参漏斗、直接进龙覆写的两参漏斗 → 女仆又被当成嘴里的猎物摆到嘴边、
55 刻后又被咬被甩。**修**：两个漏斗都拦；两参那一档用原版给的 `MoveFunction` 落座
（传送时它是 `Entity::moveTo`，保留传送语义），再 cancel。

**② 上一版硬把龙置成「飞行」，与龙自己的状态机每 tick 互翻。** `MaidMountCompat.driveDragon`
原来写「只要 `isFlying()` 为假就置真」，而龙自己那套（`flightManager.update()` 的悬停分支 +
`logic.updateDragonServer()` 的落地判定）会置回 hovering/false。两边互翻的后果不是"飞不起来"，
而是**高度每 tick 跳一次**：`getRiderPosition()` 的竖直项里「悬停/飞行」那一档比「站在地上」
那一档高整整 `1.1*linearFactor + getRideHeightBase()*0.6`（阶段 5 约 **4 格**）。
**修**：只在「它还站在地上（`onGround()`）」时推一把（这是 `useFlyingPathFinder()` 为真的前提），
已经离地/悬停就交给它自己那套飞行物理，不再覆写。

**③ 龙在密闭空间里时鞍位落在方块里 → 每 tick 窒息 → 自保把她传送出去。** 实机日志实证：
绑定后立刻出现一串 `[maidhurt] 圣女酒狐: 类型=inWall 位置=(…,-32,…)`（每秒 1 点、
随后 `self preserve exit teleport`）——龙在洞里 / 天花板很低的大厅里时，它算出来的鞍位**在方块里**，
而女仆没有把方块挤开的体格（原版只有玩家/大体积生物会被推出去），于是每 tick 吃 `in_wall` 伤害，
接着自保把她 teleport 走（`teleportTo` 内部先 `unRide()`）＝「陷进去 / 被甩」的另一半。
**修**：落座前过一道 `MaidMountCompat.freeSeatY`——从鞍位往上找第一格「她放得下」的位置
（最多 6 格；判据与该模组其余落点判定同源：该格碰撞形状为空），找不到就用原位（绝不因此不落座）。

**④ 自保传送不再把她从坐骑上踹下来。** 与 718 #31（坐/蹲 = 玩家明确停放）同一条口径、
与 656（骑扫帚时自保让位）同一个理由：她此刻是**乘客**，位置由坐骑每 tick 覆写，去向归骑乘链路。
`SelfPreservationBehavior.shouldSkipTeleportForParked` 加上「被骑乘指挥棒绑上的骑乘女仆」这一档
（两条自保传送路径共用那道闸）。

### 二、拿着棍子还是能坐上卓越前线的载具——独占档补在「登乘那一道」上

上一版拦的是 `PlayerInteractEvent.EntityInteract( Specific )`——它们只由「原版那条右击链路」发出
（服务端 `Player.m_36157_` = `interactOn`、客户端 `MultiPlayerGameMode.m_105230_/m_105226_`）。
而**坐上载具**在更下面一层：**任何** `startRiding` 都要走 `Entity.m_7998_` →
`ForgeEventFactory.canMountEntity` / `EventHooks.canMountEntity` → 发 `EntityMountEvent`
（javap 实证：`Entity.m_7998_` 偏移 49 就是这次调用）。卓越前线的载具/轮椅走的是它自己那条
客户端本地链路（`WheelChairEntity.attractEntity` 每 tick 把附近**非玩家**生物 `startRiding` 上来；
玩家那条走 `VehicleEntity.interact` → `player.startRiding`）——**客户端本地那一下**根本不过我们
那两个事件，所以旧闸拦不住。

**修**：独占档改挂 `EntityMountEvent`（**所有**登乘路径的共同必经点，客户端与服务端各发一次）：
手里拿着骑乘指挥棒的玩家**一律不通过任何方式登乘**；**只拦玩家本人**——女仆自己坐上去
（棍子绑的那条 `maid.startRiding(mount, true)`）与武装拴绳（它不用 `startRiding`）完全不受影响。
关掉 `ride.batonExclusive` = 与上一版一字不差。日志搜「不登乘」（节流 3 秒）。

### 三、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=181 FAIL=0 UNRES=0**（两个漏斗都逐字证明
  由目标类自己声明）；打包 `verify_jar_classes` 两树全过（Forge 899 条目 / Neo 911 条目）。
- 未改：龙对「谁是控制乘客」的判定、驾驶 / 开火链路、绑定与传送链路、面板与其余默认值。

# 实测七百二十【冰火传说龙的骑位修正（女仆不再被含在嘴里 / 不再被甩下）+ 骑乘指挥棒独占右击】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、女仆骑冰火传说的龙：位置不对 + 被甩下来

「女仆坐在龙身上的位置跟玩家不一样。而且龙会直接把女仆从背上甩下来。」——两条是同一个根因的两半。

龙的乘客分两类，判据是它的「控制乘客」（`getControllingPassenger`）：1.20.1 上**只认玩家**，
1.21.1 上只认「主人」。女仆两边都不满足 → 判据恒为 `null` → 龙走**另一个分支**：
把她当成**嘴里的猎物**，摆在**嘴边**（位置不对），并在 55 刻后**咬她一口**再把她**甩下来**
（`updatePreyInMouth`：`animationTick > 55` → `hurt(伤害×2)` + `stopRiding()`）。

修法是只修**定位这一处**：乘客位置每 tick 由原版算一遍，而其中的漏斗（1.20.1 `Entity.m_7332_`、
1.21.1 一参的 `positionRider(Entity)`）是 **`public final`**——龙不可能覆写它。所以新增一个 mixin
**注入原版 `Entity` 的这个漏斗**：乘客若是【有主女仆】且载具是【冰火传说的龙】，就取龙自己的公开方法
`getRiderPosition()`（它给玩家算的鞍位）按玩家同款落座并 `cancel`，整段跳过龙的覆写。
**不改龙对「谁是控制乘客」的判定**（那条同时管着驱动与开火，动了会和飞行驱动路线打架）。

【没装冰火传说不会崩】Mixin 对「目标类不存在」只记一条 WARN 然后跳过（仅 `strict` 才抛），
而且这条注入的是**原版 `Entity`**——与那两个可选模组是否安装无关。

【客户端同样生效】乘客位置不随包同步：客户端每 tick 用载具的权威位置自己算一遍，所以这条两棵树都写了。
客户端不认 `persistentData`，但认得**同步的主人 UUID**——判据就是「有主女仆 + 龙」；
而登龙那条路**只对玩家开放**，女仆没有别的办法爬上龙，所以这个组合只会由骑乘指挥棒产生。

### 二、新配置：骑乘指挥棒·独占右击（默认开）

「加一个新设定，骑乘指挥棒在使用的时候不会触发原本的右击效果。只会触发骑乘棒自己的右击效果，
也就是说你拿骑乘棒是骑不上龙或者车子的。」

【为什么旧版能骑上龙】龙的翅膀/尾巴/头是**独立的小实体**，它们会把右击**转发给龙本体**
（反编译实证）——旧版只认「目标本身是不是坐骑」，看到一块「部位」就放行 → 转发 → 骑上去了。
现在两件一起做：① 认得出本体（按类名把部位还原成龙），所以「对着翅膀挥棍子」照样能选中龙；
② 独占档：手持指挥棒时**任何**实体右击一律由棍子吃掉，连 `interactAt` 那条也一起拦
（客户端先发 `interactAt`、没被消费才发 `interact`）。那个入口只当闸门不做事（否则绑定会执行两遍）。
关掉 = 只有我们认得出的目标才由棍子接管（与上一版一字不差）。

### 三、验证

两树 `javac` 0 错误；mixin 注入点审计 PASS=180 FAIL=0；打包闸门全过；
新配置在面板有行、中英 lang 都有键。**尚未进游戏实测，尚未发版。**

# 实测七百一十九【骑乘指挥棒·模组坐骑通解通法 + 通用攻击档（任何 Mob 坐骑无条件服从她的目标）+ 指挥棒气泡改口 + 13 条新语音】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、指挥棒气泡改口

手持骑乘指挥棒点扫帚模式下的女仆，旧文案是「她正在骑扫帚，先让她收好扫帚～」→
改为 **「我处于扫帚模式」**。判据一字未动，只改文本（两树镜像）。

### 二、模组坐骑通解通法

除了原版兽（马/猪/骆驼…），现在还能让女仆骑并**驾驶**两类模组坐骑：

- **卓越前线的载具**（坦克 / 装甲车 / 直升机 / 固定翼 / 飞艇…）
- **冰火传说的龙**（**普通版与社区版共用一条路径**）

**为什么不能用一条通用判据**：这两类**不走原版那套**——卓越前线的座驾不是「能上鞍」的生物、
也没有寻路，它自己有一套**位掩码驾驶模型**（前/后/左/右/上/下 + 分引擎：地面/履带/船/直升机/固定翼/飞艇）；
冰火传说的龙**连鞍都没有**，靠一个独立的**飞行管理器**驱动。

**通解 = 可插拔驱动**：探测到是哪一类载具 → 派给对应驱动，每个驱动只把同一个「意图」
（去哪 / 停下 / 打谁）翻译成那个模组自己的 API。

- **她会开吗**：会。卓越前线的载具只要她坐在**驾驶位（座位 0）**就能开
  （它的引擎认「第一乘客」，不要求是玩家）——所以**别自己占驾驶位**，
  占了她在副驾就只能开火不能开（模组会自动把她挪回座位 0）。
  冰火传说的龙由它自己的飞行管理器带着飞，高度/俯仰全归它自己的飞行物理（和玩家骑龙同一套）。
- **代她开火**（默认开）：卓越前线的载具**本来就支持「乘客是生物、有自己的目标就自动开火」**，
  女仆坐进武器位就会替她打；冰火传说的龙由我们显式触发吐息。
- **怎么绑**：和原版坐骑一样——手持指挥棒点载具/龙选中，再点自己的女仆配对。
  载具要**有座位、没报废**；龙要**已驯服、阶段 ≥ 2**（1 阶段小龙骑不了）。
- **安全边界**：整条链路**全程反射**——没装这两个模组 / 换了版本 / 改了包名，**一律不激活**；
  且**只认指挥棒绑的那条关系**，原版/别的模组让她坐上去的场合一次都不会碰。

### 三、新语音 13 条

骑乘指挥棒（实测七百一十四 新增）的 13 条女仆气泡**从来没有配音**（manifest 里一条都没有）——
本批补齐：配对/出发走俏皮档、解除走关心档、其余 10 条「现在还不行」走为难档。
manifest 139 → **152 条**（两树逐字节一致，13 个 ogg 两树都在），命中零遮蔽。

### 四、新增配置（面板：移动与行为 → 骑乘指挥棒，默认都开）

- **模组坐骑·兼容（卓越前线 / 冰火传说）**：关掉 = 只认原版 Saddleable 兽。
- **模组坐骑·代她开火**：关掉 = 她照常驾驶但坐骑不开火（你自己开）。

日志搜「模组坐骑」（节流 5 秒一只）看兼容层有没有真的在驾驶。

**通用攻击档（新）**：不只是上面那两家——**任何“用正常优先级判定”的可骑乘生物**，座上她之后都会**无条件服从她的攻击目标**：我们只把目标告诉坐骑，瞄准 / 接近 / 开火全用它**自己那套攻击方式**。她没目标时坐骑也没目标。原版马 / 猪 / 骆驼没有攻击能力，写了也不会乱跑。兼容不了的（卓越前线载具 / 冰火传说龙）才走专用驱动。

## 实测七百一十八【#30 窒息播报改常驻检查 + 骑乘链路四收紧 + 坐姿免自保传送 + 收编社区 PR #26 的 Goety 半截】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 〇、玩家四条原话

「1.有个问题，issue30 反馈者压根就没有提到岩浆，那为什么你的修法里面会扯到岩浆呢？
2.PR 上面的问题我们本地修复，然后回复一下他。
3.issue31 你觉得是否有去修改的必要？
4.还有，我觉得应该要收紧一下判定条件，只有拿骑乘棒让女仆进入骑乘状态才走我们的骑乘链路，
且骑乘棒无法选中坐垫和扫帚。」

---

### 一、【#30：被自己垫的方块闷住却始终不提示——根因与修法】

玩家（#30）复现的是「垫脚方块把自己卡住、一直吃窒息伤害、又掉血又吃东西、大于 5 秒也不提示」。
判据本身没问题：**不是**在比对"血量 5 秒前 vs 现在"，而是**累计真的吃到的窒息伤害**
（`in_wall` 那一种，按伤害结算记账）——所以"边掉血边吃东西回血"**并不会**把它冲掉。

**真正的结构性问题是这句播报长错了地方**——它写在自保会话的**第 4 档（卡墙）**里，
于是同时被三件与"卡住"无关的事摆布：

1. 自保会话得先激活（要 `headInSolid` 命中，而它是方块形状判定 `isRedstoneConductor`，
   与"真吃到 in_wall 伤害"不完全同一件事）；
2. 得轮到第 4 档——前面岩浆 / 着火 / 溺水任何一档 `return` 掉就轮不到它
   （尤其"附近有岩浆就绕开"那一档，只要半径 3 内有岩浆就抢先喊一句并 `return`）；
3. 会话进过让位窗口（`envGiveUpTick`）时，那 5 秒整条环境逃生链路都不跑。

**修法（两树）**：把这句播报从环境逃生链路里**搬出来，提成常驻轻量检查**
（`checkSuffocateAnnounce`，与"负面效果自清"同一层，会话内外都跑），判据只剩一个——
**最近 5 秒累计吃到的 in_wall 伤害过没过 4 点**。与岩浆无关、与会话无关、与让位窗口无关。
顺带把旧门槛 `> 4.0` 的 off-by-one 一并收了（口径本就是"4 点以上"，旧写法实际要 5 点才喊）。
一阵不卡了（台账清零）就复位，下一回被卡还能再喊；持续被闷着只说一次、不刷屏。
保命动作（挪位置 / 顶出）仍旧留在逃生链路里，一个字没改。

---

### 二、【点4：骑乘链路收紧——只有棍子绑的才算，棍子也选中不了坐垫/扫帚】

玩家原话：「只有拿骑乘棒让女仆进入骑乘状态才走我们的骑乘链路，且骑乘棒无法选中坐垫和扫帚。」

**收紧一：进入条件必须"我们绑的"。** 新增判据 `MaidRideKit.isBatonBound(maid)`——
她自己身上带着 `maid_smart_ride_mount` 这道痕迹（绑定成功那一刻写、解绑/自然脱落时清、随存档持久化）。
以下三处从"只要她在当乘客"改成"必须是我们绑的"：

- `isRideRider`（传送"连坐骑一起搬"的判据）→ 原版/别的模组让她坐船、坐矿车、被拽上别人的坐骑，
  一律不再走本链路的搬运，完全还原版乘客规则；
- `isDriven`（坐骑"别自己乱逛"的判据）→ 不是我们绑的，载具的随机溜达一个字不碰；
- `RideBindManager` 的"再选一次 = 下鞍"与"潜行右击 = 下鞍"→ 原版让她坐上去的，指挥棒不去把她拽下来。

**收紧二：扫帚不是坐骑。** 新增 `MaidRideKit.isBroom(Entity)`（`instanceof EntityBroom`，类型判据、
TLM 改 id 也不会静默失效）。扫帚有它自己那一整条飞行链路，被当成坐骑会打架（点她会把她从扫帚上
拽下来、驱动会去掐扫帚的行为、传送会想连扫帚一起搬）。`isRideableMount` / `denyReason` /
`ridingMount` / `isDriven` 四处一并排除。

**收紧三：选中不了、也给得出理由。** `RideBindManager.onInteract` 里，家具（椅子/坐垫）与扫帚
**不是 `Mob`**，旧写法会掉进"对着别的实体挥棍子没效果"直接 `return`——玩家看不到任何提示。
现在把它们收进来，走 `denyReason` 给出明白话：「这是家具，不是坐骑～」/「扫帚有它自己的飞法，
不用棍子管～」。

---

### 三、【你的第 3 问：#31 值不值得改——值得改一半】

#31 反馈者提了两件事：①"坐下时被怪打两下会自己站起来"；②"残血时坐/蹲着也会被瞬移到主人身边"。

**① 不动。** TLM 原版坐下确实"打死也不起身"，但那是原版的一个缺陷（`943` 那套坐下锁定），
玩家上一轮已经跟你确认过"设计上就是这样"——受伤起身（v1.5.213）是**我们主动加的保命语义**，
与"坐下固定位置"不冲突：想固定就用**蹲下/坐垫**，而不是靠"坐着挨打也不动"。

**② 改。** 这一条是**我们自己的 bug**，不是语义分歧：本模组对外宣称"玩家明确停放她时不打扰"
——`MaidTeleportPreserveMixin` 里早就豁免了 TLM 原版的 `teleportToOwner`（判据含
`isMaidInSittingPose() || isShiftKeyDown()`），但**我们自己**那两条自保归位传送
（`teleportHome` / `teleportHomeOnExit`）没查，于是她会被自己人拽走——**声明与实现不一致**。

修法：新增开关 `combat.teleportExemptSitting`（默认**开**），两条自保传送共用一个
`shouldSkipTeleportForParked`：坐姿 / 蹲下时不传。受伤起身（①）后判据自然为假、传送恢复——
"停下就别动"与"挨打要能逃"两者不冲突。关掉 = 回到旧行为。

---

### 四、【点2：PR #26——Goety 半截本地收编，BeyondDimensions 半截退回】

PR #26（作者 `rodericksthescriptkid`）一个提交塞了两件互相独立的事：

| 半截 | 内容 | 处理 |
|---|---|---|
| Goety（诡厄巫法） | 位移聚晶当第三种飞行「外部持续推进」+ 加力 + 自动触发（12 文件中的 9 个） | **本地收编**（见下，缺陷已修） |
| BeyondDimensions | 一个从未在 PR 描述里申报的 `com/maidsmart/bd/` 包：存入 / 溢出入库 | **整包退回**（见下） |

**Goety 半截为什么收**：它按本模组既定架构写的（软兼容反射、能调就调），复用了公开的
`MaidBroomDrive.combatPoint`（接敌机动几何层，五种机动白拿），只动了 `ProMaidExtension`
三行注册——与"我们已有的两种飞行"并列成第三种，架构上是它该在的位置。

**收编时修掉的四处缺陷**（都是我在审查里指出、这次落地一并修的）：

1. **反射包根写死**：`thrustPower` 里解析 `WandUtil` 硬写 `com.Polarice3.Goety`——
   另一套发行 `za.co.infernos.goety` 会 `ClassNotFound`，威力加成永远当 0，且异常被吞、
   现场看不出原因。**改成用 init 时命中的那个包根。**
2. **静态表无上限**：`TASKS` / `LAST_BOOST` / `BOOST_UNTIL` 三张表只在显式 `stop` 时清——
   女仆被魂收/卸载/删除时 `stop` 永不调用，条目永久驻留。**按本模组铁律接上 `StateTables.cap`**
   （其余链路一律这么做），并在 tick 里每 200 tick 兜一次底。
3. **每 tick 整条堆栈进日志**：`castDiag` 把完整 `printStackTrace` 塞进返回值，而调用方在失败时
   **每 tick** 写一行——一场飞行几万行堆栈。**压成"异常类: 首行消息"**（限 200 字），
   同时把"施放失败"与状态行的日志都改成**时间限频**（2 秒一行）。原来状态行是
   `DBG % 20 == 0 || onGround || horizontalCollision`——贴地/蹭墙时每 tick 都写，正好是最需要看日志的卡住场景刷屏。
4. **`isActive` 卡死隐患**：`MaidGoetyMoveSuppressMixin` 每 tick 问 `isActive` 来决定要不要
   **取消她的移动 sink**。原版 `isActive` 只看表里有没有键，一旦 `tick` 没跑到（重载/事件被吞），
   一条陈旧条目就会让她**永久不能走**。**`isActive` 补上"过期即失效"**（超过该档时长上限 + 200 tick
   即视为失效并清掉），移动立刻恢复。

**BeyondDimensions 半截为什么退回**：① 它在 PR 描述里**一个字都没提**，是"夹带"进来的；
② 它会**破坏性地搬动物品**（把产物从背包/掉落物里真实扣除、插进维度网络），却**没有任何配置文件
开关**，也不复用本模组既有的 `MaidExtraContainer` / `MISC_BACKPACK_OVERFLOW` 那道闸——
一个玩家没开过的第三方存储联动，不该默认生效；③ 里面还有无上限静态表、反射失败即整体不可用等问题。
这一条我**不合并、也不关 PR**——在 PR 下面回复说明，请作者把 bd 那半截拆成独立 PR（并补上配置开关），
Goety 这半截我们已按自己的架构落地（1.20.1 那棵树也归我们，稍后对齐）。

**说明**：Goety 半截**只落 NeoForge（1.21.1）树这一版**——作者的分支也声明"1.20.1 树我没动，
按上一轮分工归你们"。1.20.1 树的 Goety 适配不在本批，需要时另起一批。

---

### 五、验证与交付

- 两树 `javac` **0 错误**（只剩既有的过时 API 警告）。
- `_mixchk.py` mixin 注入点审计：**PASS=178 FAIL=0**（含本批新增的 `MaidGoetyMoveSuppressMixin`）。
- 打包闸门全过：Forge **609 个 class**（82 mixin 登记）、Neo **621 个 class**（83 mixin 登记），
  `verify_jar_classes` 全过、`lang json: OK`、`MISSING: none`。
- 只读核对 `_vt718.py`：本批每条改动逐项静态核对（两树镜像）。
- 部署三处（1.20.1 客户端 / 1.21.1 客户端 / pack1201 服务端）。
- **本批尚未进游戏实测**——静态核对 + 编译 + 打包门禁过了，等你实测。

**本条尚未发版**——等实测。

## 实测七百一十七【六个 issues 修复 + 家具类坐骑黑名单：XP 暴涨 / 窒息提示 / 连锁耐久 / home 死亡传送 / 传送后血上限 / 搭路缓慢泄漏】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

「1.https://github.com/fadersketch/Promaid-mod/issues那边积累了六个issues，帮我诊断一下这些
问题都出在哪里，并且将它们修复。……除了我已经回复的内容以外，你都给他回复一下。先修复bug。
2.https://github.com/fadersketch/Promaid-mod/pulls提供者更新了有关诡异巫法模组的适配。帮我
看一下有没有什么问题，能否直接合并？
3.我们需要给原版tlm的椅子这些道具开一个后门，他们虽然是家具类物品，但是从某种意义上，他们
也算坐骑，需要开一个额外的黑名单，保证女仆坐在这个上面的时候，我们所做的所有骑乘更改全都
不生效。」

### 二、issue #32：女仆击杀后玩家经验暴涨（一次 150 级）——根因与修法

**根因**：TLM 的女仆是**永远掉经验**实体（`EntityMaid.isAlwaysExperienceDropper()` 恒 true、
`getBaseExperienceReward()` 直接返回她累积的**全部女仆经验**）。所以她一死，原版
`LivingEntity.dropExperience` 就把这一整笔按 2477/球拆成一堆经验球撒在倒地处；而我们的
**自动复活**紧接着用死亡快照（含 `MaidExperience`）把她原样复活——**她的经验一点没少，地上
那堆球还在**，谁站旁边谁就白捡一份。玩家口径完全对上：「每次女仆死了重生之后就会出现一次
经验暴涨」「像是和某个数值成比例」——比例正是她累积的女仆经验。

**修法**：自动复活开着时（我们本来就会把经验原样还她），让她这一死**根本不掉经验球**——用
原版现成的闸 `LivingEntity.skipDropExperience()`（1.20.1 SRG `m_217045_`，javap 实证；
`dropExperience` 第一句就是 `!wasExperienceConsumed()`）。时机安全：TLM 的 `EntityMaid.die`
先 post `MaidDeathEvent`（我们此刻取快照），之后才走 `super.die → dropAllDeathLoot →
dropExperience`。**关掉自动复活时一个字不动**（走 TLM 原版：掉球、不复活）。

### 三、issue #30：被自己垫的方块闷住，却始终不提示「喘不过气」

**根因：那句播报长错了地方。** 判据本身没问题——是**累计真的吃到的窒息伤害**（`in_wall`，
按伤害结算记账），不是在比对"血量 5 秒前 vs 现在"。真正的问题是它写在自保会话的
**第 4 档（卡墙）**里，于是被三件与"卡住"无关的事摆布：① 会话得先激活（`headInSolid`
方块形状判定）；② 得轮到第 4 档——前面岩浆 / 着火 / 溺水任何一档 `return` 掉就轮不到它
（"附近有岩浆就绕开"那一档只要半径 3 内有岩浆就抢先 `return`）；③ 会话进过让位窗口
（`envGiveUpTick`）时那 5 秒整条环境逃生链路都不跑。门槛还带一个 `> 4.0` 的 off-by-one
（口径是"4 点以上"，旧写法要 5 点才喊）。

**修法**：七百一十八 已把这句播报从逃生链路里**搬出来做成常驻轻量检查**
`checkSuffocateAnnounce`（判据只剩"最近 5 秒累计 in_wall 伤害到没到 4 点"，门槛改 `>= 4.0`），
与岩浆 / 会话 / 让位窗口全部解耦；保命动作（挪位置 / 顶出）仍留在逃生链路里。详见上一节。

### 四、issue #29：连锁采集耐久只扣一次 —— 按你的回话加开关

历史设计就是"连锁那最多 16 块只由目标矿扣 1 点耐久"（`chainBreakAll` 原注释明写）。按你
上一条回复的口径「下个版本加个开关」新增配置 **`mine.chainFullDurability`（连锁每块耗耐久，
默认关）**：打开后连锁破坏的**每一块**都扣 1 点（含障碍物连锁），并沿用单块那套"碎裂清主手槽、
下一 tick 自动换装备用镐"的口径；镐碎了就地停止本次连锁。关闭 = 与今天一字不差。

### 五、issue #28：home 工作女仆被死亡传送拽走后「卡」在重生点

**根因**：`MasterDeathTeleportHandler.shouldStayPut` 是**唯一**没有豁免 home 模式的传送链路
（`MaidChunkLoadManager` 的 summon / pull / rescue 早就统一豁免 `isHomeModeEnable() && !建造`）。
她被拽到 home 圈外之后：TLM 的回家走位被 `SchedulePosTickMixin` 对"干活中女仆"整段 cancel，
我们自己的 home 巡逻/工作驱动又明确跳过非战斗工作（农场）→ **既回不去、也够不到圈外的作物**，
原地呆站。对照：钓鱼女仆坐在坐垫上（`isMaidInSittingPose()`）本来就免传，所以只有农场那种
"站着干活"的会中招。

**修法**：`shouldStayPut` 对齐其它传送链路的同一条口径 —— home（非建造）女仆 = 玩家明确停放，
死亡也不拽；建造女仆仍照旧可召。

### 六、issue #27：远距 / 跨界传送后血上限掉回 20

**根因**：客户端可见的 MAX_HEALTH 只来自 `ClientboundUpdateAttributesPacket`，原版只在
①开始追踪、②属性变脏时发。而同维远距传送是纯 `moveTo`（不重追踪）、跨维传送是"删+生成"
（新区块没载完时新实体停在 HIDDEN、追踪起不来）——**两条路都可能一包都不发**，客户端就一直
显示 TLM 默认的 20。魂符那条路有自动复活的强制补包兜着，所以只有传送这条路中招 —— 与玩家
「只有远距离传送和跨界传送会出现，收放魂符似乎不会」完全吻合。

**修法**：新增 `MaidResyncCommand.scheduleAttributeResend`（**只补属性表那一包**，不重建实体），
接进**每一条**传送链路：`teleportCoreTo`（同维/跨维兜底）、跨维跟随、死亡传送、烫伤逃生、
自保归位（两处）。下一 tick 补一枪，轻、幂等、随原版口径一字不差。

### 七、issue #19：搭路方块长期"缓慢"泄漏 —— 取材侧补一道真实扣减核对

上一版（实测六百三十六一带）已修掉「副手展示件被当材料」那条主因（改善明显），但你最新报告
"长期使用仍缓慢增加"指向另一条独立的缝：**某些 `IItemHandler` 包装层的 `extractItem` 会返回
一份副本却不真正扣减底层库存**（本工程自己的注释早记过这条坑，见 `MaidTorchPlacerBehavior`：
「handler 返回副本时扣不掉（无限插火把刷方块）」）。此时搭路照常放置、到期照常归还 1 件
—— 一进一出就是净 +1，且只在恰好选中那个坏槽位时发生（所以"缓慢"）。

**修法**：取材改走 `extractOneHonest` —— 扣 1 件后**核对槽位总数真的减 1**（只用"总数变化"
这一个绝对事实，不猜实现）。真扣 → 照常；返回副本没扣 → 判"没取到"，试下一槽；全都不真扣 →
取材失败。**宁可这次不搭，也不凭空造料**。口径统一在 `MaidBuildBlockFilter` 那一个 choke point，
搭路/挖矿/伐木/索引石支撑/战斗放置全部受益。

### 八、点3：家具类坐骑黑名单后门（椅子 / 坐垫）

TLM 的椅子（`EntityChair`）/坐垫（`EntitySit`）都让女仆变成"乘客"，于是它们天然落进本链路
的几个"乘客"判据里（指挥棒把她当成"骑在坐骑上"、闲逛抑制会去管载具、传送会想"连坐骑一起搬"）。
新增配置 **`ride.furnitureBlacklist`（家具类坐骑黑名单）**，默认两项
`touhou_little_maid:chair` / `touhou_little_maid:sit`。**她坐在黑名单家具上时，本模组所有骑乘
改动一律不生效**：`isRideableMount` / `denyReason` / `ridingMount` / `isRideableMount` 一律
排除、`isDriven` 不抑制闲逛、传送走原版乘客规则（不"连坐骑一起搬"）。判据是**实体类型注册名**，
别的模组的可坐家具填一行 id 即可，代码不用动；两树共用同一份 id。

### 九、PR #26（诡异巫法 / Goety 位移聚晶）审查结论：**不直接合并**

PR 本身 `mergeable_state=clean`，Goety 那半边工程质量不错（分侧守卫、命令权限、mixin 作用域
都合规），但**不能原样合**，三条硬伤：

1. **未声明的第二块功能**：diff 里除了描述的 `goety/`，还带一整套没写进描述的 `bd/` 包
   （`MaidBdCompat` / `MaidBdDeposit` / `MaidBdOverflow` / `MaidBdProbeCommand`，+1030 行）
   —— 超越维度（BeyondDimensions）的**从女仆背包往主人网络搬东西 / 把地上掉落塞进主人网络**。
   它**没有任何配置文件开关**（只有 per-maid 的 persistentData 布尔），且**没有复用**仓库既有
   的 `MaidExtraContainer` + 开关 `misc.backpackOverflow` 那条受门控的溢出链，属于"第三个溢出层
   且绕过门控"。这一条**单凭"未声明范围"就足以请他拆成独立 PR** 再谈。
2. **静态表不清理**：`MaidBdDeposit.COUNTER` / `MaidGoetyAuto.COUNTER` 只增不减；`MaidGoetyFlight`
   的 `TASKS` 在 maid 中途卸载/死亡时不落清扫，`stop()` 不清 `BOOST_UNTIL` —— 与仓库既有的
   "5 分钟 `getGameTime()` 清扫"惯例不一致，长时间开服会缓慢泄漏。
3. **抑制可被卡死 + 逐 tick 打全栈**：`MaidGoetyFlight.onMaidTick` 吞掉所有异常，若 `tick()` 在
   `stop()` 之前抛，`isActive` 恒真、`MaidGoetyMoveSuppressMixin` 会**无限期取消她的
   `MoveToTargetSink`**（她彻底走不动），而 `MAX_TICKS` 超时只在 tick 正常跑时生效；另有
   `castDiag` 把**完整堆栈**逐 tick 写日志。两条都要收。

另有两条次要：`MaidBdCompat.init` 是"全有全无"解析（包含一个**从未使用**的 `mExtractTag`，
缺它就把整包 compat 关掉）、`MaidGoetyCompat.thrustPower` 里硬编码了另一套根包名。**建议**：
请他把 `bd/` 拆出单独 PR（并补配置文件开关 + 复用 `MaidExtraContainer`），Goety 那半边按上面
五条修一轮；本轮我们**不动这个 PR**（不关、不合，等他回复）。

### 十、验证与交付（沿用本项目铁律）

两树 `javac` **0 错误** → `_mixchk.py` 注入点审计 **PASS=177 FAIL=0** → 打包
（Forge 609 class / Neo 611 class，mixin 82 全登记，lang OK，MISSING: none）→ 只读核对
`_vt717.py` **FAIL=0 / 86 项**（覆盖六条修复的源码点 + 包内常量 + 点3 黑名单 + 两树镜像）→
部署三处 **match=True**（NeoForge 1.21.1 / Forge 1.20.1 客户端 / pack1201 服务端）。
**未发任何平台**（沿用约定）。辅助件一律 `_*.py` / `_*.txt`（gitignore 内）。


## 实测七百一十六【骑乘指挥棒·四改：独占换绑 / 1:1 还原走位 / 选中即亮光标 / 连坐骑传送】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

「1.这边还是要采用跟武装栓绳一样的策略，如果绑定了一个女仆再绑定另一个，那么第1个会解绑，
坐骑同理。
2.移动逻辑方面，女仆在骑了马以后原本的那些近战和远程走位方面就失效了。这个我觉得应该是要
还原一下的。至少对于像马这种只会跑步的应该要1:1还原女仆原有的走路逻辑，其他模组的生物同理。
如果他不只是会跑路，那么再另开一个渠道。
3.如果玩家用这根棒子选中了那个女仆/可骑乘坐骑，那就应该立刻显示光标，而不是坐上坐骑以后再显示。
4.这种骑上坐骑的女仆需要额外打一个标记，她们也可以被传送过来，具体的操作可以直接照搬扫帚模式
下也可以把女仆连人带扫帚一起传送过来。」

### 二、① 独占换绑（照武装拴绳的策略）

- 绑定前先**松开这位玩家名下别的**骑乘链路（口径照抄武装拴绳的 `releaseOtherLinks`：只认
  "链路里的主人 == 这位玩家"，先收集再解除——边走边删会炸迭代器）。
- "**坐骑同理**"再补一道：这只坐骑上若已驮着**另一位**女仆的链路，一并松开——一只坐骑同时
  只服务一位女仆。
- 两处都写一行日志（`换绑` / `坐骑换主`），谁被换下来了查得到。

### 三、② 1:1 还原她的走位（本轮的重点）

**根因（TLM 字节码实证）**：她骑上坐骑后，TLM 把她的 brain 从 WORK 活动切到 **RIDE_WORK**，
而 `MaidBrain.registerRideWorkGoals` 里注册的任务行为来自 `IMaidTask.createRideBrainTasks`；
TLM 的 `TaskAttack.createRideBrainTasks` 只注册了
`StartAttacking / StopAttackingIfTargetInvalid / MaidMeleeAttack / MaidUseShield`
—— **没有任何"走向目标"的行为**（对比同一份 jar 里 `createBrainTasks` 是有的）。所以她
一旦成为乘客，近战/远程的**移动走位**就整条消失，看起来就是"走位全废"。

**修法（套她自己的移动链路，不另造一套）**：她的走位意图其实没消失——我们的单兵战术
（core 优先级 230）与 TLM 跟随（core 3）照常在她**自己的** `PathNavigation` 上写目的地，
只是位移被"她是乘客、位置由载具决定"这一条吃掉。所以驱动层改成读
`MaidRideKit.ownNavigationTarget`（她自己的导航目标点，`getTargetPos`），原样转达给坐骑：

- **渠道一：只会跑的坐骑**（马/驴/骡/骆驼/猪…）—— 判据 `GroundPathNavigation`。喂它自己的
  **地面寻路**：它按自己的寻路跑，上下坡/绕障/跳跃交给它，**完全复现她两条腿的走位**。
- **渠道二：不只是会跑路的坐骑**（飞行/两栖/别的模组坐骑）—— 另一个渠道：**直连操纵**
  （`MoveControl.setWantedPosition`），不重新规划路径，直接朝目标点给操纵意图。

找不到她自己的走位目标时，依次退回：她脑里的 `WALK_TARGET` → 跟主人走（离主人超过
`ride.followDist` 才喂）。速度仍是"坐骑与女仆取最大值"（见上一版的换算）。

### 四、③ 选中即亮"光标"

- 打标记的时机从"绑定成功"提前到"**被选中**"：棍子选中女仆或坐骑的那一刻就
  `setGlowingTag(true)`（同光灵箭那条渲染、金色描边），并写独立的 `maid_smart_ride_pending` 留痕。
- 撤标记的四个出口都接上：**换选**（先撤上一只）、**配对成功**、**超时/实体消失**（每 200 tick
  的清理）、**玩家下线**。`clearPendingMark` 只撤"我们打的那一下"，且她已有正式链路标记时不熄灯
  ——不会误清光灵箭等别的发光来源。
- 解绑时顺手清 pending 痕迹（双保险）。

### 五、④ 连坐骑一起传送（照搬扫帚那条）

- 新判据 `MaidRideKit.isRideRider`：正骑着一只**已上鞍的 `Saddleable` 生物**、且身上带着
  绑定痕迹（`maid_smart_ride_mount`）——自洽判据，不查链路表，服务端重启后也认得出。
- 新搬运链路 `MaidChunkLoadManager.recallRideRider` / `recallRideRiderTo`：结构照搬
  `broomRiderTo`，**四步在同一个 tick 内做完**——解开乘客关系 → 搬坐骑 → 搬她 → 重新落座
  （跨维度按 UUID 取回坐骑，`startRiding(force)` 落座）。原因与扫帚同一条：原版 `teleportTo`
  第一件事就是 `unRide()`，直接传她 = 坐骑扔在原地、人掉到主人身边。
- 五个传送入口全部接上这一档（她不再被"骑乘中"顶回去）：`summonAll`（一键集合）、
  `tickPending`（未加载区块强载召回）、`summonOne`（排班表召她过来）、`summonMaidTo`、
  以及 `followIfCrossDimension`（跨维度跟随）与 `trySameDimPull`（同维度远距拉回）。
- 原版 TLM 的 `teleportToOwner` 那条**不必**另补一道闸：`isRideRider` 要求 `getVehicle()` 非空，
  她一定是乘客，既有的 `isPassenger()` 豁免已经拦住（`MaidTeleportPreserveMixin` 里写明了这一点）。

### 六、动到哪几处

- `combat/MaidRideKit.java`（两树）：`isRideRider`、`ownNavigationTarget`、双渠道
  `feedNavigation` / `stopNavigation`。
- `combat/RideBindManager.java`（两树）：`TAG_PENDING_MARK`、`releaseOtherLinks` /
  `releaseMountLinks`、`markPending` / `clearPendingMark`、`drive()` 改读她自己的导航目标、
  `isRideRider` 转发、`forgetPlayer` 清 pending。
- `follow/MaidChunkLoadManager.java`（两树）：`recallRideRider` / `recallRideRiderTo` /
  `rideRiderTo` / `entityAt` + 五个入口的接入。
- `mixin/MaidTeleportPreserveMixin.java`（两树）：注释写明"为何不必再补一道"。
- `guide/GuideChaptersCombat.java`（两树）：骑乘那一章补"独占换绑 / 1:1 走位（两渠道）/
  选中即亮 / 连坐骑传送"四段。

### 七、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=177 / FAIL=0**；打包门禁
  （`verify_jar_classes.py`、mixin 注册、lang）通过；部署三处目标 `match=True`。
- 只读核对脚本 `_vt716.py`：两树镜像、四个点的关键代码都在、旧的"只读 WALK_TARGET"写法已消失。

## 实测七百一十五【记忆优化·第一档：移植 Sphantosis 的事件元数据（未完成事件 + 注意力等级）】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

「关于女仆大语言模型方面的功能已经是很老版本之前做的了，并且除了调用也没有做过相关的优化
（比如主动会话、记忆填写等方面都是很老的代码了）。你帮忙优化一下。」并给了 Sphantosis 的两个
分支链接，说「我了解到的一个非常强大的 AI 情感对话强化项目，包含了相当多的方面，你看看能不能
对我们这边的记忆内容进行一个改写。」

### 二、先说清现状（避免重复劳动）

**上一批（实测七百一十三 之后）已经把 Sphantosis 的大部分主干移植进来了**——多级记忆索引
（日/3日/周/月）+ 归档器 + 五路 RRF 检索 + 记忆日记注入 + PAD 情绪层 + 七阶段主动对话，
代码里都标着「移植自 Sphantosis」。本次重新对着**远程分支**核了一遍（`main` 与
`experimental/advanced-features-experiment`，后者比前者新 83 个提交），确认还差的是
**事件级元数据**这一层——也就是 Sphantosis `event_extractor.txt` 里那套
「情感八维向量 / 注意力等级 A-D / 冲击性因子 / 终止标志」。本次先取其中最有用、最省 token 的
两条落地，其余留待后续批次。

### 三、本次落地的两条（都来自 Sphantosis 的事件元数据）

#### 3.1 未完成事件（Sphantosis 的「终止:0」）

- **提取侧**：`EVENT` 行的格式从 `salience|content` 扩成 `salience|attention|open|content`——
  `open=1` 表示「明显还没完」（主人说要去做某事 / 在等一个结果 / 计划尚未落地 / 悬念未揭开），
  拿不准一律 0（宁缺毋滥，与 Sphantosis「无法确定优先标 0」同口径）。
- **注入侧**：带 `open:1` 的事件会被单独拎出来，渲染成对话上下文里的
  \u00a7e「她记挂着的事」\u00a7r 那一段（只取最近 2 条）。效果：**她能在下次见面时主动接上
  「上次那件事怎么样了」**，而不是干等你再提。开关 `memory.openEvents`（默认开）。
- **兼容**：解析器**两种格式都认**——老模型/老配置仍回两段式时按「attention=C、open=0」处理，
  绝不因为模型没听话就丢掉整条事件。

#### 3.2 注意力等级（Sphantosis 的「注意力等级 A/B/C/D」）

- `attention` 字段随事件一起入库（段落 tags 加 `attention:X`），供后续批次在检索/画像里
  按「主人当时投入了多少注意力」加权。本次先**只入库、不改变现有检索排序**
  （零回归风险）——排序加权留到后续与「事件图谱因果边」一起做。

### 四、改动清单（两树镜像）

| 文件 | 改了什么 |
|---|---|
| `memory/AiMemoryExtractor.java` | 提取 prompt 的 EVENT 行扩成四段式（含 attention/open 的说明）；`writeEvent` 容错解析（四段/两段都认）+ 打 `attention:X` / `open:1` 标记 |
| `memory/AiMemoryContext.java` | 新增「她记挂着的事」段（带 `open:1` 的最近 2 条） |
| `config/MaidSmartConfig.java` | 新开关 `MEMORY_OPEN_EVENTS`（`memory.openEvents`，默认开） |
| `assets/.../lang/{zh_cn,en_us}.json` | 新开关的中英文案 |

### 五、本批**没做**的（说清楚边界）

- **情感八维向量 / 冲击性因子**：我们的情绪层走的是 PAD（valence/arousal/dominance，实测
  七百一十三 已有的 `AffectManager`），与 Sphantosis 的八维离散情感是两套体系；
  合并需要一次「两套情绪模型怎么并」的设计决策，不塞进本批。
- **事件图谱的因果边 + belong 树形结构**：Sphantosis 的事件是**图**（节点=事件、边=因果/belong），
  我们的事件是**扁平段落**。改成图是一次结构性重写（要动存储、检索、注入三处），
  风险与工作量都远超本批，需要单独一批做。
- **DSU 实体消解 / 向量检索 / mood EMA**：同属结构级改动，继续留待后续。

### 六、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=177 SKIP=10 UNRES=0 FAIL=0**
  （本批不动 mixin）。
- 打包门禁全过（`verify_jar_classes.py` + mixin 包登记 + lang json）：forge 609 / neo 611 个 class，
  `MISSING: none`。
- 只读核对脚本 `_vt715.py`：确认「四段式 EVENT prompt 在场、两段兼容分支在场、
  `open:1` / `attention:` 标记在场、`她记挂着的事` 段在场、开关默认开、两树镜像」。

## 实测七百一十四【两件：① 鞘翅外观给 YSM 让位（加开关）② 原版生物骑乘——骑乘指挥棒】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

1. 「那我们先从最简单的事情来。ysm 方面就按照你的步骤来进行落实。加个开关就行。」
2. 「先做原版生物乘坐坐骑吧。直接套僵尸的骑乘代码，速度上是坐骑的最大速度与女仆的最大速度之间
   取最大值。乘上坐骑的女仆作为骑乘状态，我们也不做额外的豁免，同时加一个 shift 加右击可以把
   女仆从坐骑上弄下来（跟坐在扫帚上一样）。关键就在于骑乘状态下，女仆的移动 AI 该怎么搞，
   我觉得大概就是套我们这边已有的移动链路。引入一个新物品，骑乘指挥棒，可以将可骑乘坐骑与
   女仆绑定起来，被绑定以后会出现光标。（类似于前段时间的武装拴绳）不管是先绑女仆还是先绑
   可骑乘坐骑都没问题。绑定之后，女仆就会坐到那个坐骑上。」

### 二、① 鞘翅外观·YSM 让位（新开关，默认开）

**先说清事实（javap / 反编译实证，不是推测）**：

- YSM 自己那一层鞘翅图层（`textures/entity/elytra` 那一支）把实体强转成 `Player`
  （`Player player = (Player) ...OO00OOOOo0Ooo0oo0o0Oo0OO();`），而且**只挂在 YSM 的玩家渲染器上**
  ——它根本不理会女仆。所以「YSM 的鞘翅图层和我们的图层打架」这件事**不存在**。
- 真正会重叠的只有一件事：**YSM 模型自己在背上烘了一对翅膀**，而它也认原版鞘翅
  （`has_elytra` = `EquipmentUtil.getEquippedElytraItem(...)` 非空；`getEquippedElytra` 只认
  `Items.f_42741_` = `minecraft:elytra`，外加 `elytraslot` 那个模组）。实测扫过 YSM 内置 27 件模型：
  - `21_saint`：`wings` 骨的 scale 挂着 `ysm.has_elytra + v.roaming.Wings == 1`（滑翔时张开）；
  - `09_hailuo`：`Elytra` 骨平时 `scale: 0` 藏着，滑翔时露出来；
  - 其余 25 件**没有**烘翅膀（`18_wedding` 那对 `Wing/Wing2` 是婚礼装饰、与鞘翅无关）。

**做法（就是"加个开关"）**：装了 YSM、且这只女仆走的是 YSM 模型渲染器、且她**正在滑翔**时，
我们那一层**让位**（不画）。因为上面两件模型的翅膀**都只在滑翔时露出来**——站着/走路时 YSM
模型背上没有翅膀，那一档照旧由我们画（不然 YSM 模型下站着就没翅膀了），只有滑翔那一段会两对
叠在一起。

- 新开关 `鞘翅外观·YSM 让位`（`combat.wingYsmYield`，默认开）；关闭 = 旧行为（滑翔时两对叠画）。
- **只对原版鞘翅有意义**：我们支持的模组滑翔装备（伊卡洛斯之翼 / 神秘遗物+ / 自带外观的鞘翅胸甲）
  YSM 一律不认（它只认 `minecraft:elytra`）——那些滑翔时照旧由我们画，本开关不介入。
- 两树各只改一个文件的一处 + 一个开关 + 一行日志（搜「鞘翅渲染」能看到是哪一档）。

### 三、② 原版生物骑乘（骑乘指挥棒）

#### 3.1 先钉死三条关键字节码事实（整条链路都建在这上面）

1. **载具只在"玩家骑"时才走玩家驾驶那条路**：`LivingEntity.aiStep` 里是
   `getControllingPassenger() instanceof Player ? travelRidden(player, ...) : travel(...)`
   ——**二选一**。而 `AbstractHorse` / `Pig` / `Strider` 的 `getControllingPassenger()` 字节码
   都要求「**第一乘客 `instanceof Player`**」（猪/炽足兽还额外要胡萝卜钓竿/诡异菌钓竿），
   否则一路 `super` 回 `Entity` **恒返回 `null`**。
   ⇒ **女仆当乘客时它返回 null**：载具走的是**普通 `travel()`**，它自己的 `PathNavigation`
   照常把它推着走。**这就是"降级偷懒"的落点**——我们只要把目的地喂给它自己的导航即可。
2. **它自己的随机闲逛会跟我们抢方向**：`RandomStrollGoal.canUse()` 第一句是
   `if (mob.hasControllingPassenger()) return false;`（两版逐字相同）——原版靠它保证"玩家骑着马
   马不自己溜达"。而按第 1 条，女仆当乘客时 `hasControllingPassenger()` 是 **false**，
   所以载具自己的 `WaterAvoidingRandomStrollGoal` 仍会跑。
3. **上鞍要 force**：`Entity.startRiding(target, true)` 的第二参为真时直接跳过
   `canRide` / `canAddPassenger` 两道门（javap 实证 `iload_2 ifne` 跳过）。

#### 3.2 落地方案（逐条对着玩家那几句话）

- **上鞍 = 套僵尸骑鸡**：`maid.startRiding(mount, true)`（原版 `Zombie.finalizeSpawn` 里就是
  `startRiding(chicken)` 这一记）。她成为乘客后 TLM **自己**把大脑切到 `RIDE_IDLE`/`RIDE_WORK`/
  `RIDE_REST`（`MaidUpdateActivityFromSchedule` 判据是 `isMaidInSittingPose() || isPassenger()`），
  我们一行都不碰。
- **速度取最大值**：`max(坐骑 MOVEMENT_SPEED, 女仆 MOVEMENT_SPEED) × 倍率`，再换算成喂导航的
  **倍率**（`MoveControl` 内部是 `speedModifier × 自己的 MOVEMENT_SPEED`，javap 实证），
  即 `目标 / 坐骑速度`，夹在 0.3~4.0。玩家原话"取最大值"照办，另给一个
  `骑乘·速度总倍率`（默认 1.0）供手感微调。
- **移动 AI = 套已有链路**：每 2 tick 读**她脑里的 `WALK_TARGET`**（跟随/走位/任务写的都是这里），
  把那个点转达给**坐骑自己的 `PathNavigation.moveTo`**；读不到就退回"跟主人走"（超过
  5 格才喂主人的位置）。上下坡/绕障/跳跃/原版动画全归坐骑自己的寻路。**目的地的计算完全复用
  现有系统**，我们只把"她走"换成"坐骑走"。
  - 唯一动原版的一处：`RandomStrollGoalRiddenMixin` 把上面第 2 条补上——判据 =
    `MaidRideKit.isDriven(mount)`（乘客里有**本模组有主**的、且不是扫帚模式的**女仆**）。
    不满足就是一个字节不动（原版玩家的马、别人的坐骑、无主女仆骑的生物全部照旧）。
- **不做额外豁免**：她的战斗/自保/落地水/险境脱困**一条都没改**（按原话）。
- **shift + 右击下坐骑**（"跟坐在扫帚上一样"）：潜行时右击**女仆或坐骑**都行 → 解除 + `stopRiding()`，
  她自己走回你身边。此外"绑定态再右击一次"也是解除（与武装拴绳同款手感）。
- **新物品 = 骑乘指挥棒**（`maid_smart:ride_baton`，合成：拴绳 + 木棍）：
  - 右击一只**已上鞍**的坐骑 → 选中它；右击自己的女仆 → 选中她；两边都有 → **当场配对**。
  - **顺序随意**（先女仆后坐骑、先坐骑后女仆都对）——这也是原话点名的要求。
  - **"会出现光标"**：选中的一方（以及配对后的女仆 + 坐骑双方）打**原版发光描边**，
    描边颜色沿用武装拴绳那套客户端 `MaidGlowGoldMixin`（**金色**）。
  - 物品**不带状态**（"已选中谁"记在服务端按玩家的瞬时表里），同一根棍子反复用。

### 四、改动清单（两树镜像）

| 文件 | 改了什么 |
|---|---|
| `client/LayerMaidElytraGecko.java` | 非 Gecko 渲染器（= YSM 那条路）**且正在滑翔**时让位；新增 `ysmYield()`（只报一次日志） |
| `config/MaidSmartConfig.java` | `COMBAT_WING_YSM_YIELD` + 骑乘三键（`enable` / `followDist` / `speedScale`） |
| `config/PromaidConfigScreen.java` | 鞘翅行后新增「鞘翅外观·YSM 让位」；新板块 **`RIDE`「骑乘指挥棒」**（移动与行为，紧跟武装拴绳） |
| `combat/MaidRideKit.java`（新） | 能力探测（`Saddleable && isSaddled`）、速度换算、骑乘关系/驱动小工具 |
| `combat/RideBatonItem.java`（新） | 物品本体（悬停说明） |
| `combat/RideBindManager.java`（新） | 选中/配对/解除、每 2 tick 驱动与自愈、金色标记、持久化恢复 |
| `mixin/RandomStrollGoalRiddenMixin.java`（新） | 「本模组女仆在骑 → 坐骑别自己闲逛」 |
| `ProMaidMod.java` / `CreativeTabHandler.java` / `ProMaidExtension.java` / `mixins.promaid.json` | 注册物品 / 创造栏 / tick 挂载 / mixin 登记 |
| `assets` + `data`（各 4 件） | 模型 json、16×16 贴图（新画）、合成配方（shapeless：lead + stick）、中英 lang |

### 五、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=177 SKIP=10 UNRES=0 FAIL=0**
  （本批新增 2 个注入点，都由目标类自己声明）。
- 打包门禁全过（`verify_jar_classes.py` + mixin 包登记 82 个类 + lang json）：
  forge 609 个 class / neo 611 个 class，`MISSING: none`。
- jar 内容核对：新类、新 mixin、模型/贴图/配方、两语言新键、mixin 登记 **全部在包里**。
- 部署三处（两个客户端 `versions` 的 `mods` + 服务端 `pack1201`）并逐处核对尺寸：**match=True**。
- 只读核对脚本 `_vt714.py`：确认「YSM 让位门在、开关默认开、两树镜像、骑乘三键在场、
  速度取最大值算式在、闲逛抑制判据在、旧行为未被打断」。

## 实测七百一十三【鞘翅外观做成"通解通法"：三段式解析 + 删掉"护甲型滑翔装备不画"这条例外】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

1. 「像鞘翅胸甲这种物品现在可以渲染出来自己的鞘翅纹理以及附魔效果吗？我希望这种渲染是一种**通解通法**，
   而不是一些专门的适配。尽可能规避去专门适配的情况。」
2. （更早之前）「目前鞘翅的渲染只在空袭模式下会被渲染出来。而且渲染出来的全都是原版鞘翅，
   能不能调用那个鞘翅自己的外观呢？同时在所有模式下渲染。」

### 二、结论先说

- **附魔光泽本来就是通用的**——图层传的是 `ItemStack.hasFoil`（= 原版 `ElytraLayer` 同款第 4 参），
  从实测五百零七 起就是"真附魔才发光"，与是哪件鞘翅无关。这一半不用改。
- **翅膀贴图原来不是通用的**——靠 `MaidWingSkins` 里一张"物品 id → 贴图路径"的硬编码表（13 条），
  认不出就退回原版贴图。**这次改成了三段式解析**（见下）。
- **"能滑翔就画翅膀"这个闸门本来就是通用的**（判据 = 原版滑翔闸门 `ItemStack.canElytraFly`），
  但原来多了一条"是护甲就不画"的例外，**把鞘翅胸甲这类整类排除了**。这次**删掉了这条例外**
  （理由见第五节：那条例外建立在一个错误前提上）。

### 三、三段式解析（`MaidWingSkins` 重写）

```
① 先问模组自己（反射它已经公开的取值口）
② 问不到再查静态表（降级为覆盖/兜底）
③ 仍认不出退回原版 textures/entity/elytra.png
```

- **① 反射**用**鸭子类型**，**不写死任何类名**：只要这件装备有一个无参 `getType()`，
  且其返回对象有一个返回 `ResourceLocation` 的无参 `getTexture()`（伊卡洛斯之翼就是这样：
  `AbstractWings.getType()` → `IWingsType/WingsType.getTexture()` / `getTextureReversed()`，
  javap 实证两树都是 public），就直接采信；另探测装备自身公开的
  `getElytraTexture(ItemStack[, LivingEntity])`。
  - **好处**：该模组**日后新增翅膀物品，我们零改动即可画对**（静态表不用动）。
  - 反射结果**不按类缓存**（`getElytraTexture` 允许按 ItemStack 的 NBT 给不同贴图，按类缓存会画错）；
    只缓存"这个类有没有取值口"这一次判断。
- **② 静态表**只保留"贴图藏在模组私有图层里、没有公开取值口"的：神秘遗物系
  （`EnigmaticElytraLayer.TEXTURE_MAP` 是 private static，反射它等于抄实现，太脆）。
- **③ 退回原版**——绝不画错，只是外观是原版的；留一行痕（日志搜「鞘翅渲染」）。

**为什么不能做到"完全不认识任何模组也画对"**：Forge/NeoForge 的 `IClientItemExtensions` 里
**根本没有**"告诉我你的鞘翅贴图"这个钩子（javap 实证：1.20.1 只有 getFont/getArmPose/
applyForgeHandTransform/getHumanoidArmorModel/getGenericArmorModel/renderHelmetOverlay/
getCustomRenderer；1.21.1 加了 setupModelAnimations/getArmorLayerTintColor/getScopeOverlayTexture/
shouldBobAsEntity，仍然没有）。原版 1.20.1 的 `ElytraLayer` 更是把 `Items.ELYTRA` 与贴图常量写死，
直到 1.21.1 才加了可覆写的 `getElytraTexture`——但那是**图层自己的**方法，模组物品无法从外部被问到。
没有共享 API 就没有万能解，三段式是能达到的最通用形态。

### 四、静态表顺手修正了一处真错（两树命名空间本来就不同）

原静态表两树共用 `enigmaticlegacyplus:...` 的 id，但 **1.20.1 那边根本没有这个 namespace**：
1.20.1 的神秘遗物是 `enigmaticlegacy`（壮丽鞘翅 → `textures/models/misc/elytra.png`）+
  扩展 `enigmaticaddons`（混沌之傲 → `textures/item/3d/chaos_elytra.png`）；
  1.21.1 才统一成 `enigmaticlegacyplus`（`textures/models/misc/majestic_elytra.png` / `chaos_elytra.png`）。
  现在两树各写各的（逐条 javap + 字符串实证）。

### 五、删掉"护甲型滑翔装备一律不画"这条例外（实测六百八十四 的规则反转）

- **旧规则**：「能滑翔、但本身是护甲」的装备（鞘翅胸甲这类）**不叠**我们的翅膀，
  理由是"它自己就有护甲外观，叠一层会穿模"。
- **实测推翻了那个前提**：javap + jar 全量扫描 **1.20.1 与 1.21.1 两个 TLM 版本**，
  `EntityMaidRenderer` 只挂了 Held / Head / Backpack / BackItem / Banner **五层**，
  全 jar 对 `HumanoidArmorLayer` / `models/armor` / `textures/models/armor` 的引用数**都是 0**，
  也没有任何护甲贴图资源（`textures/models/armor` 0 个 PNG）。→ **TLM 根本不给女仆画任何护甲。**
- **所以**：鞘翅胸甲这类穿在女仆身上**本来什么都不显示**，把它排除在翅膀渲染之外 = 穿了个寂寞；
  叠一层翅膀既不会穿模，还正好补上"她穿着滑翔胸甲"应有的观感。原版 `ElytraLayer` 只认
  `Items.ELYTRA` 是因为**原版玩家会画护甲**、那件护甲自己就有外观；女仆这边没有这个前提。
- 现在 `MaidFlightKit.isWingRenderable` = `isElytraLike`（能滑翔就画），不再看是不是护甲。
  `isArmorElytra` 仍保留（**背包取用时优先穿带护甲那件**，那是另一件事，未动）。
- 日志从"能滑翔、但本身是护甲 → 不叠翅膀"改成"能滑翔、但没能查到它自己的贴图 → 用原版贴图"。

### 六、改动清单

- `client/MaidWingSkins.java`（两树）：三段式解析器（反射鸭子类型 + 静态表 + 原版兜底）、
  `hasOwnTexture` / `isVanillaElytra` 两个给图层用的小判据、静态表按树分命名空间。
- `combat/MaidFlightKit.java`（两树）：`isWingRenderable` 去掉护甲例外；
  `noteWingSkipped` → `noteWingVanillaFallback`（文案与新口径一致）。
- `client/LayerMaidElytra.java` / `LayerMaidElytraGecko.java`（两树共 4 个）：闸门改为"能滑翔就画"，
  并在"模组滑翔装备但查不到自有贴图"时留痕。
- `config/MaidSmartConfig.java`（两树）：`COMBAT_WING_RENDER` 注释改成三段式口径。
- `guide/GuideChaptersFlight.java`（两树）：第二节①（鞘翅）与第八节（鞘翅外观）文案同步。
- 开关 `鞘翅外观`（默认开）行为不变，仍是"关掉 = 旧行为"。

### 七、验证

- 两树 `javac` **0 错误**（Forge 373 源 / Neo 375 源）。
- `_mixchk.py` 注入点审计 **0 FAIL**（175 项）。
- 反射目标逐条 javap 实证：伊卡洛斯 `getType()/getTexture()/getTextureReversed()` 两树 public；
  `IClientItemExtensions` 两版本都无鞘翅贴图钩子。
- TLM 护甲渲染实证：两版本 jar 对护甲图层/资源的引用数均为 0。

## 实测七百一十二【卓越前线能量枪打空后"一直在天上飞" + 手中枪没子弹时先翻背包换一把能接着打的】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

1. 「关于扫帚模式和空袭卓越前线的能量枪判定有一些问题，如果枪内的能量打完了，那么女仆又不会下鞍，
   又不会报缺，只会在天上一直飞。」
2. 「如果女仆发现手中的枪已经没有了对应子弹，第一步不应该是报错。应该是继续检查一下包内有没有其他的
   物品符合继续战斗的需求（这个好像就是直接套用的自主切换武器吧，同时优先模组武器。不知道落实了没有）」

### 二、① 能量枪打空 → 门禁必须真的关掉（不再"一律放行"）

**根因在 GunCompat.canReload 那条"能量武器直接 return true"的短路。** 模式门禁是
`canFeed || canReload` 的**或门**（`MaidFlightKit.hasAmmoForWeapon`），而卓越前线的能量枪
（二次灾变 / 超级星星炮 / ql_1031 / sentinel / taser 这一类，弹量就是枪自带的能量池）：

- 纯能量枪（弹药 = 它的 ForgeEnergy 池）：`canFeed` = `hasEnoughAmmoToShoot` 算的是**能量够不够**
  → 能量打空后为**假**；但 `canReload` 被那条短路**恒真** → 或门恒真 → 门禁永不关闭；
- 混合枪（二次灾变：弹匣 + 能量池）：弹匣打空后 `canFeed` 也为假，同样被短路顶住。

于是"没有弹药"这件事永远传不到缺件判定那里 —— 她**不报缺、不下鞍，在天上一直飞**
（扫帚模式同理：`canStayMounted` 不含弹药，但她连"缺件气泡"都不会响）。

**修法：删掉短路，把"能不能打"交还给枪械 mod 自己回答。** 反射可用时走各自的正路
（TACZ `AbstractGunItem.canReload` / 卓越前线 `GunData.shouldStartReloading`，与实测六百九十四 /
六百九十五 同一口径）：

- 纯能量枪 `useBackpackAmmo()` 恒真（无弹匣）→ `shouldStartReloading` 恒假（无从换弹），
  而 `canFeed` 算的就是能量池 → **能量打空即门禁关闭**：气泡报缺件、扫帚模式按既有分档处理；
- 混合枪 → `canFeed` 看弹匣、`canReload` 看备弹，弹匣打空且无备弹即门禁关闭。

**反射不可用**（没装 SBW / 版本改名）时仍退回旧的宽松兜底 `looseAmmoScan`——**那一条里保留
"能量武器免检"的老口径**，绝不在兼容层异常时把她误判成缺弹药（两树镜像）。

### 三、② 手中枪没子弹 → 先翻背包换一把能接着打的（不是先报错）

**门禁原先只看"她主手那一把"**（`resolveRangedWeapon` 主手优先）：主手打不响就直接判缺弹药、
把模式关掉，**根本不给她翻背包的机会**（空袭那边 `isModeActive` 在 `equip` 之前判假就收翅落地）。
玩家说的"应该继续检查包内有没有其他符合继续战斗需求的东西"正是这一环。

**修法：门禁与换装都改成"她身上有没有一把能接着打的"。**

- 新增 `MaidFlightKit.hasUsableRangedWeaponAny` = 主手或背包里**任意一把**"远程武器且打得响"
  （`hasAmmoForWeapon` 逐把问，含能量枪）；
- 门禁三处接入它：`hasAmmoForRanged`、`airAssaultReady`、`MaidBroomKit.ammoOk`；
- 新增 `MaidFlightKit.ensureUsableRangedWeapon`：**只在主手那把打不响时**才动手，从背包挑一把
  "打得响"的换到主手，**模组武器优先于原版武器**（同档取背包槽序靠前的）；找不到能用的就
  **保持现状**，绝不把玩家给的武器无故换掉；
- 换装两处接入它：空袭 `equip()`（远战那一档）与扫帚模式（`MaidToolAutoEquipBehavior` 的扫帚分支）。
  **门禁与换装共用同一份实现**（本模组"同一个口径只有一处"）。

换手成功会写一行运行日志（「空袭装备」类，搜「主手换武器」，40 tick 节流）：现场"她怎么突然换了把枪"
一看就知道是这一条干的。

## 实测七百一十一【烫伤脱困（真的被烫到就立刻传送出去）+ 鞘翅外观（所有模式都画、且用那件鞘翅自己的样子）】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

1. 「扫帚和空袭模式下有关岩浆的寻路还是不太聪明。好像是跟之前那个窒息的问题同一个问题，
   用同样的方法解决就行。同时再加一个如果被烫到了立刻传送到最近的空气方块。」
2. 「目前鞘翅的渲染只在空袭模式下会被渲染出来。而且渲染出来的全都是原版鞘翅，
   能不能调用那个鞘翅自己的外观呢？同时在所有模式下渲染。」

### 二、① 烫伤脱困：飞行中真的被烫到 → 立刻传送出去

**确实与窒息同一个根**：地面的危险方块处理（险境脱离 `DangerEscapeHandler`）写明了
豁免「乘客」——`if (isMaidInSittingPose() || isPassenger()) continue;`，理由是「坐姿由椅子管、
骑乘由载具负责」。而**扫帚模式的女仆永远是她那把扫帚的乘客**，空袭 / 飞行跟随又整天在空中：
于是这三个模式里**危险方块一条逃生都没有**，只剩「预测式避让」
（`MaidFlightHazardGuard` 的 detour / antiSink：只在她**还没进去**时绕开 / 抬平）。
一旦真贴上去（被击退、被地形挤、烟花推偏），没有任何「已经在里面了就出来」的机制——
这就是玩家说的「跟窒息同一个问题」：**豁免挂错了对象**。

- 新增 `com.maidsmart.combat.MaidHeatEscape`：她**真的**泡进岩浆（`isInLava`）或被点着
  （`isOnFire`）时，**本 tick 内立刻传送**到最近的空气格。
- **判据就用原版那一个**（与窒息那档用 `isInWall()` 同源）：不自己写几何判定，
  就不会出现「看着泡在岩浆里、其实没掉血」的误触发。泡在水里不算（水会把火浇灭）；
  **烫不疼的不算**——抗火药水（剩余 ≥30 秒）/ TLM 火焰保护饰品，那两样本就是泡岩浆不掉血，
  不该惊慌（判据收口成 `SelfPreservationBehavior.fireImmune`，与自保那边逐字一致）。
- **为什么是传送而不是飘过去**：岩浆每秒 4 点、她只有 20 血——等不起扫帚那种有时长的转向脱困。
  直接走原版 `Entity.teleportTo`（清摔落 / 清速度 + 末影人音效）；
  **骑扫帚时连人带扫帚一起搬**（走 `MaidChunkLoadManager.relocateBroomRiderToCell`，
  与「扫帚牵引绳」同一个四步搬运 `broomRiderTo`——直接传她会被 `unRide` 从扫帚上踹下来）。
- **落点**：最近的、她放得下的空气格（站立格 + 头顶格都空气，且自身 / 脚下不是危险方块，
  免得下一拍又踩回去）；一圈都找不到「脚下安全」的就退一步只要求两格空气——先脱离流体最重要。
  水平 ±3、竖直 -2~+3，取最近的；1 秒冷却防抖。
- **调用点**：三个飞行行为各自 tick 的**最前面**（`MaidBroomBehavior` / `MaidFlightCombatBehavior` /
  `MaidFlightFollowBehavior`），返回 true 时调用方就此打住——刚传送完再写推进意图会把她从落点上推走
  （与「扫帚牵引绳」同一条约定）。日志搜「烫伤脱困」。
- **开关**「烫伤脱困」（`combat.heatEscape`，默认开）。与「飞行危险环境避让」
  （`combat.flightDangerAvoid`）是**两层**：那条预测式，这条已经在里面了就出来。

### 三、② 鞘翅外观：所有模式下渲染 + 用那件鞘翅自己的样子

**原来「全是原版鞘翅」的原因**：我们的图层（Bedrock / Gecko 两套，共四个文件）一直用原版
`ElytraModel` + 原版贴图——它只管「给她画一对翅膀」，完全不知道模组鞘翅长什么样。
实测六百八十四 当时写「模组装备没有通用 API 告诉别人我的翅膀长什么样」——那句话对**通用**判据成立，
但**已知的那几件**不成立：它们的贴图路径就写在那几个模组的图层类里，逐条反编译抄过来即可。

- **① 所有模式下渲染**：图层的闸门从 `MaidFlightKit.isFlightVisual`（飞行任务 **或** 正在滑翔）
  放宽为「**胸甲槽穿着可渲染的鞘翅**」——她站着挖矿 / 走路 / 跟随 / 空闲时背上也有一对**折叠**的翅膀，
  **飞起来（滑翔）才张开**（展翅仍由 `isFlightVisual && !onGround` 决定）。
  与原版玩家「穿着鞘翅背上就有翅膀」完全同款。四个图层文件（forge / neo 的 Bedrock + Gecko）同改。
- **② 用那件鞘翅自己的外观**：新增 `com.maidsmart.client.MaidWingSkins`，按物品 id 给出贴图——
  伊卡洛斯之翼（`locusazzurro_icaruswings`，`textures/entity/<名字>_wings.png`）：
  羽毛系 `feather` / `colored_feather` / `golden_feather_wings`、纸翼 `paper_wings`、魔法翼 `magic_wings`、
  贤者之石翼 `flandre_magic_wings`（→ `philosopher_stone_wings.png`），与**空域系 6 件**
  `ikaros` / `nymph` / `astraea` / `chaos` / `hiyori` / `melan_wings`（它们**滑翔时另有 `_reversed` 反向贴图**，
  模组 `WingsLayer.getElytraTexture` 本来就切，我们照抄这条）；
  神秘遗物+（`enigmaticlegacyplus`，`textures/models/misc/`）：`majestic_elytra.png` / `chaos_elytra.png`
  （模组自己的 `EnigmaticElytraLayer.TEXTURE_MAP` 就是这两条）。**逐条反编译核实**。
- **认不出型号的**（其它模组的滑翔装备）退回原版 `textures/entity/elytra.png`——不会画错，
  只是外观还是原版的；要它自己的样子往 `MaidWingSkins.TEXTURE` 补一行 id → 贴图即可。
- **与「自推鞘翅」是两件事**：能不能自己飞由「自推鞘翅·资格物品表」管；长什么样由这张贴图表管——
  一件普通鞘翅（羽毛系）也能有自己的外观，只是它不会自推。
- **开关**「鞘翅外观」（`combat.wingRender`，默认开）；关掉 = 旧行为（只在飞行 / 滑翔时画、
  且一律原版贴图）。

### 四、改动文件（两树镜像）

- 新增：`combat/MaidHeatEscape.java`、`client/MaidWingSkins.java`；
- `combat/SelfPreservationBehavior.java`（`fireImmune` 对外判据）、`combat/MaidBroomBehavior.java`、
  `combat/MaidFlightCombatBehavior.java`、`combat/MaidFlightFollowBehavior.java`（烫伤脱困调用点）、
  `follow/MaidChunkLoadManager.java`（`relocateBroomRiderToCell`）、
- `client/LayerMaidElytra.java`、`client/LayerMaidElytraGecko.java`（两树共四个图层：闸门放宽 + 贴图改查表）、
- `config/MaidSmartConfig.java`（+`combat.heatEscape`、+`combat.wingRender`）、
  `config/PromaidConfigScreen.java`（+两行）、四份 `lang`（+两键）、两树手册 `GuideChaptersFlight.java`。

### 五、验证

两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=175 SKIP=10 UNRES=0 FAIL=0；
打包 `verify_jar_classes.py` 全通过（forge 871 项 / neo 873 项）；新类与两键均在 jar 内；
部署三处（1.20.1 Forge 主整合 / 1.20.1 服务器 / 1.21.1 NeoForge）。

---
## 实测七百一十【自推鞘翅：不靠烟花也能飞的那一类模组鞘翅（伊卡洛斯之翼 / 神秘遗物+）】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

> 「我现在又安装了伊卡洛斯之翼和神秘遗物+，加完模组之后有两种鞘翅不需要烟花也可以起飞。
> 我觉得需要做相关的兼容，如果装配了这些物品，相当于同时满足了烟花以及推进物品的要求。
> 然后同时也需要考虑一下他们的运动逻辑是怎么样的，看看怎么安排在女仆身上。
> 之后其他的类似物品看有没有一个通解通法。」

### 二、先摸清这两家加了什么（反编译实证）

**伊卡洛斯之翼**（1.21.1，`locusazzurro_icaruswings-1.21-0.7.0`）：
- `feather_wings` / `colored_feather_wings` / `golden_feather_wings` / `paper_wings` /
  `magic_wings` / `flandre_magic_wings` —— 普通 `ElytraItem`，**没有任何自推**，只是耐久不同；
- `ikaros_wings` / `nymph_wings` / `astraea_wings` / `chaos_wings` / `hiyori_wings` /
  `melan_wings` —— `SynapseWings`，**自推**：`FlyingEventsHandler.onPlayerTick`
  （`PlayerTickEvent.Pre`）里只要她在滑翔就每 tick 加一份推力
  `v += (look·d + (look·i − v)·t) · c`，**不需要按跳跃键**。

**神秘遗物 / 神秘遗物+**：
- 1.21.1（`enigmaticlegacyplus-1.21.1-1.1.2`）：`majestic_elytra`（壮丽鞘翅）与
  `chaos_elytra`（混沌之傲）继承 `BaseElytraItem`，它把 `canElytraFly` 直接写成 `true`
  （**不看是不是玩家**）→ 女仆穿得上、滑得起来。自推在 `flyingBoost(player)`：
  **按住跳跃键**时 `v = v×0.48 + look×0.64`（壮丽）/ `v = v×0.5 + look×k`（混沌，默认 0.8）。
- 1.20.1（`EnigmaticLegacy-2.30.1` / `enigmaticaddons-1.2.6`）：**不可用**——
  两件的 `canElytraFly` 都写死 `entity instanceof Player && ...`
  （`EnigmaticElytra.java:204`、`ChaosElytra.java:312`），女仆**连滑翔都做不到**，
  穿上去就是件普通胸甲。

### 三、关键障碍：三家的自推**全都只推玩家**

上面所有自推入口都挂在 `PlayerTickEvent` / `instanceof ServerPlayer` / 客户端跳跃键包上。
**女仆不是 Player，一点推力都收不到。** 所以只"认物品"的结果是——她跳起来、展开滑翔、
然后**一路往下沉**。这次真正的活是**由本模组替她施加推力**。

### 四、通解通法：同一个形状、各自的小数

三家的推力公式其实是**同一个形状**（每 tick 把速度往视线方向拉一档）：

```
v ← v×gain + look×add          // 原版挂载烟花也是这一条：v ← v×0.5 + look×0.85
```

所以通解 = **一张表**（物品 id → 这条公式的两个系数）+ **一个窗口**（每 tick 推一下、到期交还滑翔）
+ **一条接入链**（空袭与飞行跟随各一处调用）。以后再有同类模组，只要往
`combat.selfWingsItems` 里加一行 id —— 认不出型号就走通用兜底模型
（`v ← v×0.5 + look×0.5`），**不需要改代码**。

### 五、特性怎么展现：逐条照抄各自数值

| 鞘翅 | 公式（gain / add） | 收敛巡航速度 |
|---|---|---|
| 伊卡洛斯·双重可变翼 ikaros | 0.5 / 0.35 | 0.70 格/tick |
| 伊卡洛斯·反逆可变翼 melan | 0.5 / 0.35 | 0.70 |
| 伊卡洛斯·缓滞时空羽翼 hiyori | 0.5 / 0.63 | 1.26 |
| 伊卡洛斯·混沌进化之翼 chaos | 0.5 / 0.64 | 1.28 |
| 伊卡洛斯·电子战用隐秘翼 nymph | 0.5 / 0.68 | 1.36 |
| 伊卡洛斯·超加速型羽翼 astraea | 0.5 / 1.10 | **2.20（最快）** |
| 神秘遗物+·壮丽鞘翅 | 0.48 / 0.64 | 1.23 |
| 神秘遗物+·混沌之傲 | 0.5 / 0.80 | 1.60 |

**不统一成一个数**——不同鞘翅的特性就是它自己的特性（astraea 起步猛、nymph/hiyori 较缓、
混沌之傲上限高）。数值硬编码而非反射（不怕对方改名、1.20.1 树不必 import 1.21.1 的类）；
全局倍数旋钮 `combat.selfWingsScale`（默认 1.0）可整体缩放。

### 六、改动清单（两树镜像）

| 文件 | 改动 |
|---|---|
| `combat/MaidSelfPropelledWings.java`（两树，**新增**） | 资格判定（只看配置表 + `#标签`）、内置参数表、推力窗口 `ignite/tick/clear`、诊断 `diag`、手册文本 |
| `config/MaidSmartConfig.java`（两树） | 新增 `combat.selfWings` / `selfWingsItems` / `selfWingsScale` 三项 |
| `config/PromaidConfigScreen.java`（两树） | 面板加三行 |
| `combat/MaidFlightKit.java`（两树） | `hasFlightPropellant` 加第五条腿；`equip()` 末尾就绪判据放宽 |
| `combat/MaidFlightCombatBehavior.java`（两树） | `canLaunch`/`canJumpToLaunch` 放行（**不吃烟花冷却**）、`canTakeOff` 不必重写、`tryLaunch` 末尾加第五条腿、每 tick `tick()`、`endFlightSafely` 收窗、缺件诊断与文案 |
| `combat/MaidFlightFollowBehavior.java`（两树） | 每 tick `tick()`、启动门禁（`hasFlightPropellant` 自动跟随）、补推链第⑤条腿、`fuelLabel`、`releaseThrust` 与收手清理、跳过文案 |
| `guide/GuideChaptersFlight.java`（两树） | 空袭章节第三节由「四选一」改「五选一」+ 新增自推鞘翅说明；飞行跟随节选择清单同步 |
| `assets/maid_smart/lang/zh_cn.json` / `en_us.json`（两树） | 各 +3 键 |

### 七、刻意做的三件事（边界）

1. **不消耗、不扣额外耐久**：模组对玩家自推是**额外扣耐久**的（伊卡洛斯每 20 tick、
   神秘遗物每 5 tick），女仆这边**只按原版滑翔的节奏扣**（`updateFallFlying` 每 20 tick 扣 1）
   ——前四条腿是"借它的动作"，这一条只借它的**物理**，不该替它扣钱。
2. **排在补推链最末**：扇子 → 烟花 → 法术 → 激流 → **自推鞘翅**。前四样存在的存档手感一字不变。
3. **窗口收敛、不会飞走**：公式本身收敛，窗口开着时速度往不动点收敛；40 tick 到期交还滑翔，
   掉高了按同一个判据（速度 < 0.35）再补一次。

### 八、1.20.1 侧的说明（写进手册与面板）

那边的神秘遗物把「能不能滑翔」写死了 `instanceof Player`，女仆连滑翔都做不到，
所以本功能**实际在 1.21.1 生效**；1.20.1 树的代码照镜像、表里留着 id，
等那边装上同类模组即用（**不做 hack 去强改别人的类**）。

### 九、与「仿创造飞行」（实测七百〇二）的分工

那个是另一个功能（给女仆创造模式飞行：悬浮 + 自由升降，不需要鞘翅、也不是滑翔）。
本类走的是**鞘翅滑翔**这条物理（原版滑翔位 + 滑翔阻力），两者互不干涉，
资格物品表也各自独立，可以同时开。

## 实测七百〇九【扫帚模式两修：① 真正被方块闷住时立刻脱离（不再等"卡住"0.6 秒、选格规则为保命让位）② 起飞判定与远程空袭完全对齐（补上"弹药"这一条）】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话两条

> ①「扫把模式的窒息判定还是有问题。仍然会导致女仆窒息。被方块挡住的只能是扫帚。而女仆在
> 扫帚上似乎又没有碰撞箱，导致会陷进去窒息。」
> ②「扫帚的启动链路还是比较落后，目前也只判了远程武器，没有判弹药。应该改成跟远程空袭
> 一样的起飞判定。」

### 二、① 的根因：脱困只看"位移"，而贴墙滑行时位移永远不为零

实测六百九十一 那套脱困（`nearestEscapeCell` + `bodyFits` + `isPocket` + 5 秒名单）解决的是
"撞上去不动了"这一档，判据是**连续 12 tick 位移 < 0.06 格**（`STUCK_TICKS` / `STILL_EPS`）。
但玩家报的这一档是**扫帚贴着墙"滑"**：每一 tick 都在动（沿墙面滑动），`still` 计数永远攒不满，
**脱困相位一次都进不去**。

真正被闷住的是**她**、不是扫帚——这正是玩家那句"被方块挡住的只能是扫帚"：
扫帚有自己的碰撞箱（1.375 × 0.5625），靠 `Entity.move` 的碰撞解算停在墙外；而她是**乘客**，
座位在扫帚**朝向后方 0.5 格**（原版 `EntityBroom.m_19956_` 字节码实证，见 `SEAT_BACK`），
原版 `positionRider` 是**直接摆位（`setPos`）、不做任何碰撞解算**——没人替她挡那半格，
于是扫帚安全停在墙外，她被按进墙的边角，每 tick 扣一点 `in_wall`。

**修法（判据用原版那一个，不自造几何）**：脱困之前先问一次 `LivingEntity.isInWall()`——
它**恰好**就是"这一 tick 原版要不要扣她 `in_wall` 伤害"的那道判据
（`LivingEntity.m_6075_` 字节码：`if (isInWall()) hurt(damageSources().inWall(), 1.0F)`），
按眼位 + 0.8×宽度去撞方块的 `isSuffocating` 形状。为真 → **立刻**退到能容下她的空气格。

**选格规则同时为"保命"让位**（旧规则会把唯一可行的那格否掉、返回 null，她原地继续闷着）：

| 条件 | 平时（躲建筑/绕路） | 保命档（正被闷住） |
|---|---|---|
| 必须"离目标更近" | 是（防原地打转） | **否**——从方块里退出来本身就是正确方向 |
| 不许超过"目标高度 +3 格" | 是（防越躲越高，实测六百九十二） | **否**——墙角外侧那格常比目标点高 |
| 5 秒内刚去过的不再选 | 是（防两格间横跳） | 第一趟仍尊重；**一趟选不出来就放开再来一趟** |
| 一格死洞不进 | 是 | 同上（desperate 档放开） |

仍然**取最近的合格格**，所以放松不等于"往上爬"或"乱飞"。
日志新增一行可核对：`扫帚卡墙 … 人正卡在方块里（isInWall，正在挨窒息伤害）→ 立刻退到能容下她的空气格 (x, y, z)`。

### 三、② 的根因：起飞只判了"有没有远程武器"，没判"打不打得响"

进到起飞相位（`takeoffTarget`）的判据是 实测六百九十二 那条 `canStayMounted`
（= 扫帚 + 远程武器，**刻意不含弹药**）。那条口径本身没错——它是为"弹匣打空别把她从空中摔下来"
定的，管的是**要不要求下鞍**。但**起飞**与"留在鞍上"是两件事：远程空袭那边
（`MaidFlightKit.isModeActive`）是**起飞前就要求三件齐 + 弹药**（实测四百九十五：
"没弹药就没必要起飞"）；扫帚这边只有"有武器、没弹药"的她照样进入起飞相位、腾空一次，
再靠开火链路自己换弹。

**修法（两条链路各补一处，口径都复用既有判据，不新写第三份）**：

1. **"去找扫帚"那一档先判武装**：她身上没有扫帚时会先去找一把（实测六百五十九），
   旧版**什么都不判**——附近有扫帚就过去骑上并立刻起飞。现在先问新增的
   `MaidBroomKit.armed(maid)`（= 远程武器 + 弹药）；不武装就**不出门找**、原地待命报缺件。
   她一旦被补上武器/弹药，下一 tick 就照旧去找——不会把她锁死。
2. **起飞相位本身改判 `isModeActive`**（= 扫帚 + 远程武器 + 弹药）：判假就**原地悬停**、
   报缺件，**不抬那 1 格**。闸放在 `takeoffTarget` **之前**——起飞相位是 `CLIMB` 状态表，
   其收尾判据之一是"连续几拍没长高 = 头顶被顶住"；若先消费相位再悬停，那几拍会被当成
   "顶住了"从而**结束相位**，下一 tick 她就直接落进盘旋档照飞不误，闸等于没关。

**与 实测六百九十二 不打架**：那条改的是**下鞍**判据（`canStayMounted`），本批只决定
"要不要抬这 1 格"；判据为假时她**留在扫帚上原地待命**（不下鞍、不丢高度）。而且弹药那条走
`ammoOk → canFeed || canReload`（实测六百九十四 / 六百九十五）："弹匣空着但背包里有对得上的
弹药"算齐备——冲锋枪/狙击枪那种"这一拍弹=无"不会把她钉在地面，真正被挡住的只有
**确实一点弹药都没有**这一种。

### 四、改动清单（两树镜像）

| 文件 | 改动 |
|---|---|
| `combat/MaidBroomDrive.java`（两树） | 新增 `maidSuffocating`（= 原版 `isInWall`）、`enterEscape`（脱困入口 + 分档日志）；`nearestEscapeCell` 增两参 `suffocating` / `desperate`；`unstick` 新增 ①.5 窒息即时脱困档，卡住档改走 `enterEscape` |
| `combat/MaidBroomKit.java`（两树） | 新增 `armed`（= 远程武器 + 弹药；"去找扫帚"档的起飞前置判据） |
| `combat/MaidBroomBehavior.java`（两树） | ② 档"去找扫帚"前加 `armed` 闸；④.5 起飞相位前加 `isModeActive` 闸（判假 → `hoverInPlace` + `notifyNotReady`） |
| `guide/GuideChaptersFlight.java`（两树） | 扫帚专章补两段：起飞判定对齐、窒息即时脱离（含 `isInWall` 因果与放松后的选格规则表） |
## 实测七百〇八【修"同区块邻居撤票，把她的区块加载一起撤掉" + 同区块共享票不重复挂载】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

> 「之前排班表设计的时候，如果重生之后的女仆离玩家太远，那么玩家无法通过排班表将他召回
> 过来，排班表上也不会显示他的名字，我就在想是不是区块加载失效了。隔的太远的时候也没有
> 办法用排班表内的女仆配置远程打开女仆的背包。上次我已经让你修复过了，但是我不知道有没有
> 成功生效。」

### 二、先确认"上次那个修复"在不在（在，且已进包）

上次（实测六百九十九）那套修复**源码与已安装 jar 里都在**，本轮逐条核实：
`ScheduleNetworking.openFor` 名单会补「✚ 复活中」与「⚑ 未加载」两类；
`SchedulePacketsMaid` 单点查询会回"最后见到她的位置 ·未加载"；
`RemoteMaidGui` 走强制配对（**距离上限已取消**，半径概念不存在）；
`MaidAutoResurrect` 复活时**先把重生点区块同步加载到 FULL**再 `addFreshEntity`。
两树构建产物与实际部署 jar **SHA1 完全一致**——修复确实在玩家正在用的包里。

### 三、但这里有一个真 bug：**同区块的邻居撤票，会把她的加载一起撤掉**

原版 `addRegionTicket` 对"同类型 + 同等级"的票会**去重**：`Ticket.compareTo` 只比
等级/类型/比较器，而本模组的 `MAID_TICKET` 比较器恒为 0 —— 两只女仆落在**同一区块**时，
实际只有**一张**票。而旧代码撤票是**按女仆逐个撤**：撤掉 A 的那张 = 撤掉共用的那张。

于是链条是：A 死亡/被收回 → A 不在 wanted → 撤票 → **B 的区块当场卸载** → 下一轮 5 秒
扫描里 B 已不在实体表 → B 也被判"不要票" → **她从此没有任何东西在加载她**：排班表看不见、
一键集合召不回。触发条件是"同区块的邻居先走一步"而**不是距离**，所以症状"时好时坏、
找不到规律"。

### 四、修法：票的挂/撤统一过一层**按区块的引用计数**（`CHUNK_WANTERS`）

- 新增 `acquireTicket` / `releaseTicket`：**只有某区块的最后一位持有者释放时才真正
  `removeRegionTicket`**；还有别人要它，票就留下。全部挂/撤点（会话票、换区块、换维度、
  待召回强载票、复活补票）**一律改走这一层**，不再直接 add/remove。
- 持有者用前缀区分（`m:` 跟随会话票 / `s:` 待召回强载票）——两者共用同一张票网格，
  分开计数才不会互相撤票。
- 票龄护栏（"刚挂的票 <100 tick 不撤"）从"按女仆记"改成**"按区块记"**：票是区块级资源，
  撤不撤要看这张票自己挂了多久。
- `ServerStopping` / 关开关时的 `releaseAll` 仍整盘清场（那时整张网格一律作废）。

### 五、顺带就是你要的那个优化：**覆盖重叠不再重复挂载**

引用计数同时把"多只女仆覆盖到同一区块"自动合并成**一张票**——开销是覆盖范围的**并集**，
不是"每只女仆各开一片"。十只女仆挤在一起，票开销等于一只；只有真的散开才线性增长。
（`CHUNK_WANTERS` 的 key 就是 `(维度, 区块)`，天然就是这个语义。）

### 六、改动清单（两树镜像）

| 文件 | 改动 |
|---|---|
| `follow/MaidChunkLoadManager.java`（两树） | 新增 `CHUNK_WANTERS` / `CHUNK_TICKET_BORN` / `acquireTicket` / `releaseTicket` / `ticketFresh` / `ownerKey`；全部挂撤票点改走引用计数；删除已被取代的 `removeTicket` 与 `TICKET_BORN`；移除不再使用的 `ServerChunkCache` import |
## 实测七百〇七【起飞链路"一定要成功"：不再原地蹦着飞不起来】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

> 「偶尔女仆会出现在地上跳了好几次却起飞不了的情况，被敌人打中了以后反而可以起飞了
> （这里并不是说被打完才能起飞，偶尔敌人离得近了也会起飞，可能是在某个链路反复尝试，
> 最终只成功了一次）。……最终还是要求女仆一旦执行这个起飞的链路，一定要成功执行，
> 而不是在原地浪费。」

### 二、根因（空袭的起跳那一口被同 tick 的移动源摊平）

起跳滑翔（`jumpForLaunch` / 第 0 步补跳）只做了一件事：`setDeltaMovement(x, 0.42, z)`。
但**同一 tick 的 brain 阶段里，还有好几路会重写她的位移**，而且都在本行为之后：

- TLM `MaidMoveControl` 在"想游泳/水里"时**直接施加速度矢量**（绕过 WALK_TARGET）；
- 本模组自己的战术行为（`MaidCombatTacticsBehavior.meleeTick` 的跳劈/贴身绕走）与
  自保（`SelfPreservationBehavior` 的垫高/逃跑）会再写一次位移；
- TLM 跟随/战斗走位写下的 `WALK_TARGET` 会被 `MoveToTargetSink` 变成寻路。

0.42 这一口被摊平 ⇒ 她**原地蹦好几下却离不了地**（`updateFallFlying` 里"落地即清滑翔位"
是硬条件，离不了地就永远点不着烟花）。而**挨打那一下不属于"女仆方的移动意图"**、
不会被上面任何一路覆盖，反而干净地把她顶离了地——这正是"被敌人打中以后反而可以起飞"。

### 三、修法：起跳那一 tick 的竖直运动"独占"（`kickOffGround`）

与 `MaidMoveSuppressMixin` 同一套"清移动意图"的口径，在起跳点就地做一遍：

1. 清 `WALK_TARGET` + 停导航——同 tick 不再有人把她往地面拽；
2. 重给竖直冲量 `0.42`（与 `jumpForLaunch` 同一数值）；
3. 置 `hasImpulse`（SRG `m_245125_` / 1.21.1 `hasImpulse = true`）——原版对"有冲量"的实体
   重发位置包，否则客户端本地还会按旧速度把她画在原地。

`jumpForLaunch`（地面起跳的入口）与第 0 步补跳**共用**这一个 `kickOffGround`，两处口径完全一致。
全程 `try/catch`：任何意外都退回"只写速度"的老行为，绝不拖垮 brain tick。

### 四、留痕

新增一行「起飞受阻」（节流 5 秒）：「起跳窗走完仍未离地（低矮空间 / 被同 tick 移动源吃掉），
本轮起跳放弃」。**正常情况一次都不该打出来**——打出来就说明那一跳被吃掉了（多为低矮空间，
那是真的飞不起来，属于预期内的放弃）。

### 五、改动清单（两树镜像）

| 文件 | 改动 |
|---|---|
| `combat/MaidFlightCombatBehavior.java`（两树） | 新增 `kickOffGround`（清 WALK_TARGET + 停导航 + 重给 0.42 + 置 hasImpulse）；`jumpForLaunch` 与第 0 步补跳改走它；新增 `logLaunchStuck` / `LAUNCH_STUCK_LOG`，"起飞受阻"日志；`forget` 清该表 |

## 实测七百〇六【近战空袭"拉开距离"把空袭打废的 bug + 远程空袭高度硬地板】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话（两条）

> 「相比于上一个版本，这一个版本的近战空袭基本上就失效了，可能是上一次出现的离敌人最小距离
> 在近战空袭中也发力了。导致女仆基本上碰不到敌人。
> 远程空袭的时候，不管是处于哪一种飞行状态，那么至少那个比敌人高上 8 格不能有太大的偏差，
> 而不是飞着飞着又只比敌人高一点点了。」

玩家判断得**完全正确**：就是 实测七百〇四 新加的「离敌最近距离」在近战空袭上发力过猛。

### 二、先查日志（1.21.1 客户端 `latest.log` + `promaid.log`）：两个 bug 都在

- `近战空袭 / 拉开距离：水平 X 格 < airRaid.minStandoff 6.00 格` 出现 **25 次**（一场遭遇 166 秒），
  距离在 `0.78 ~ 5.99` 格之间来回——**全程没有一次压下去**。
- 同一场里 `猛击` **0 行**、`空袭·俯冲` 只有 2 行（且都是"距敌 39 格"的远距离俯冲加速）。
  **俯冲/猛击根本没发生过**，这正是玩家说的"基本上碰不到敌人"。

### 三、近战空袭失效的两个根因（都在 实测七百〇四 新加的那一段里）

**根因①：两条判据用了两把不同的尺子，夹出一条"永远俯不下去"的死区。**
新加的"拉开"判据用**水平距离**（`distH < minStandoff`），而"进入俯冲"的闸门用 **3D 距离**
（`dist <= smashRange()`，`dist` 取自 `maid.distanceTo/ m_20270_`）。因为恒有 `3D ≥ 水平`，
所以 `3D ≤ 3.5` 必然推出 `水平 ≤ 3.5 < 6`——**每一次想俯冲的那一 tick，都会被"拉开"判据先拦下
并 `return`**。俯冲在几何上永远不可能发生。
- 修：两条判据**统一成 3D 距离**。贴脸只发生在同一高度层；她高悬于敌人头顶时水平近是正常进场姿态。

**根因②：拉开预算是"自复位"的，于是她在 6 格线上无限抖动。**
`pulled` 计满 `STANDOFF_MAX_TICKS`(40) 后，下一 tick 被归零 → "拉开 40 tick、放开 1 tick、
再拉开 40 tick"。就算尺子统一了，6 格 → 收翅线 3.5 格那一段进场路也永远走不完。
- 修：**一轮只拉开一次**。本轮一旦"够远"或"预算用完"，就置位 `STANDOFF_DONE`，
  此后本轮一律放行俯冲；`endSmash`（一轮收尾）摘掉标记，**下一轮**照样能重新拉开。

**根因③（顺带）**：猛击收尾后的"等待再起飞"（`WAIT_LAUNCH`）期间一律不拉开，
让"再起飞"不被横飞打断。

俯冲的瞄准与命中判定（`sweepWithin` / `smashHit`）**一个字未改**——修的是"进不进得去"，
不是"打不打得中"。日志新增一行「放弃拉开」（预算用完仍拉不开时说明本轮直接俯冲）。

### 四、远程空袭"飞着飞着只剩一点高度"：新配置「离敌最低高度」（默认 8，0 = 关）

旧版只有一条**软约束**：`期望盘旋高度 10 格 + 按高度误差给俯仰 + 掉高补推（5 秒冷却）`。
俯仰有死区、滑翔每 tick 都在掉高——三段叠加让高度在带内持续锯齿，偶尔探到 8 格以下、
甚至只剩一两格（那时俯仰才刚开始往回抬，但要好几秒才补得回来）。

新增 `airRaid.minAboveHeight`（面板「空袭数值 → ③ 盘旋与补高」里的**「离敌最低高度（格）」**，
默认 **8.0**，0~64，0 = 关闭）——与「期望盘旋高度 10」配成"目标带 10、地板 8"：
- **硬地板**：高度差低于本值就把盘旋俯仰**直接钉成最大抬头**（不再走高度误差增益——增益在死区
  附近给的角太小、救不回高度），她立刻转入爬升、绝不会继续往下滑；
- 同时把"掉高补推"的**触发门槛提到本值**（不必等掉出 10 格带，冷却一好就补推）。

日志新增一行「高度地板」（节流 5 秒）：「只比敌人高 X 格 < airRaid.minAboveHeight 8.00 格」。

### 五、改动清单（两树镜像）

| 文件 | 改动 |
|---|---|
| `combat/MaidFlightCombatBehavior.java`（两树） | 近战"拉开"改用 3D 距离 + 一轮只拉开一次（`STANDOFF_DONE`）+ WAIT_LAUNCH 短路；新增 `logStandoffGiveUp`；盘旋俯仰加"低于地板即钉最大抬头"；`belowFloor` 同时抬高"掉高补推"触发门槛；新增 `logHeightFloor`/`FLOOR_LAST_LOG`；`airMinAboveHeightCfg()` 访问器 |
| `config/MaidSmartConfig.java`（两树） | 新增 `AIR_RAID_MIN_ABOVE_HEIGHT`（`airRaid.minAboveHeight`，默认 8.0，0~64） |
| `config/PromaidConfigScreen.java`（两树） | 「③ 盘旋与补高」新增一行「离敌最低高度（格）」；「离敌最近距离」说明改写（标注 七百〇六 修正） |
| 两份 `lang` JSON（两树，zh/en） | +1 键 `config.promaid.airRaid.minAboveHeight` |
| `guide/GuideChaptersFlight.java`（两树） | 「空袭也拉开距离」改写（并入七百〇六 修正说明）；新增一段「远程空袭的离敌最低高度」 |
| 四处 changelog + `deploy_all.py` docstring | 本条目 |

### 六、边界（这次刻意不动的）

- **俯冲的瞄准与命中判定**（`sweepWithin` / `smashHit` / `forcedHitRadius`）：一个字未改。
- **远程空袭盘旋半径的那条硬下限**（`airRaid.minStandoff` 对盘旋半径的作用）：未改，仍生效——
  只有**近战**那一支的判据从水平改成了 3D。
- **"期望盘旋高度 10"本身的默认值**：未改，新增的是它下面那条地板。
- 版本号仍是 `v1.3.0(beta)`，同名覆盖，**未发平台**。

## 实测七百〇五【接敌机动不再一场定一种 + 环绕每 8 秒随机掉头 + 手册补写】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话（三条）

> 「1、你的改动似乎还是没啥用。女仆似乎扫帚模式还是只会环绕。我怀疑对于战斗结束这个判定可能
> 有一些问题，反正就是还没做到位。上一版本的问题还是都没解决。
> 2、而且我希望女仆环绕飞行不要一直进行顺时针飞或者逆时针飞。可以在中途突然顺时针飞转变为
> 逆时针飞。差不多 8 秒钟一个周期吧。随机选择继续顺时针或者逆时针。
> 3.补充手册内容」
>
> （第二条补充）「2.看一下刚才运行的游戏日志，是我的错觉吗？我怎么感觉打了那么多场都一直
> 在用环绕啊。到底是调用上出了问题还是说就是战术设计的不明显？
> 3.其实近远程空袭的女仆也是需要有一个拉开距离的」

### 二、先查日志：不是"错觉"，但也不是"调用坏了"

翻了 2026-09-28 的实机日志（1.21.1 客户端，`latest.log`，5502 行），结论分两半：

- **调用链是通的**：`接敌机动` 那行日志整场出现 8 次，五种机动里 **蛇形 / 高悠悠都真抽到过**
  （`大正女仆酒狐` 抽到过蛇形，`圣女酒狐` 抽到过高悠悠）。所以 实测七百〇四 那两处收尾
  （`clearClimb` / `endFlightSafely` 里 `CombatManeuver.forget`）**确实生效了**，
  不再是"一次抽签用一整天"。
- **但"一直在环绕"的观感是真的**，两个原因叠在一起：
  1. **一场遭遇太长、而机动一场只抽一次**。日志里那一仗从 `00:00:28` 打到 `00:03:11` 还没停，
     **163 秒**；`CombatManeuver.begin` 幂等 ⇒ 这 163 秒里她抽到环绕就一直是环绕。
     旧设计假设"一场遭遇十几秒"，实战里一个 boss 能打两分钟以上——**假设不成立**。
  2. **环绕权重最高（0.34）**，且另外四种机动的几何幅度偏小（蛇形只有 ±15% 半径），远看确实
     分不出跟匀速绕圈的区别。

### 三、改了什么（两树镜像）

**① 接敌机动改成"每 20 秒换一种"（`CombatManeuver`）**
- 新增段上限 `SEGMENT_MAX_TICKS = 400`（20 秒）：同一场遭遇内每满 20 秒**重抽一次**，
  且**保证与上一段不同**（`pick(id, salt, avoid)`：抽到同一种就换盐重抽，仍同就顺次取下一个）。
  所以一场两分钟的仗至少换 5~8 种打法，而不是一种到底。
- 权重重新分配：环绕 `0.34 → 0.24`（与蛇形并列第一，它仍是基线打法，但不再占三分之一强）；
  蛇形 `0.22 → 0.24`、高悠悠 `0.18`、脱离再进 `0.14 → 0.17`、8 字横切 `0.12 → 0.17`
  （五个权重之和恰为 1.00，抽签仍由 UUID+遭遇序+段号派生，可复现）。
- 蛇形径向幅度 `±15% → ±22%`（8~10 格的圈上是 ±1.8~2.2 格，远看才分得出来"她这一拍更远"）。
- 波形自变量 `ticks` 换段时归零 ⇒ 每个机动都是**完整的一段**，不是从半路接上。
- 新读数：`segment(id)` / `entering(id)` / `segmentTicks()`；日志改成
  「本场遭遇开场…」/「本场遭遇第 N 段·换打法…」。

**② 环绕旋向每 8 秒随机掉头（`CombatOrbit`）**
- 玩家原话：「不要一直顺时针或者逆时针……差不多 8 秒钟一个周期吧。随机选择继续顺时针或者逆时针。」
- 新增 `flipSign(id)`：每 `DIR_FLIP_TICKS = 160`（8 秒）**对半概率**决定"继续原方向 / 翻过来"；
  乘在原有的 `direction(id)`（UUID 派生，一半逆一半顺）上。
- **位置连续**：掉头只改角速度符号（`ang` 本身连续），目标点仍是圆上的连续点——是"她在圆上掉头"，
  不是"瞬移到对面"，所以不需要任何插值。`flipped(id)` / `flipSignNow(id)` / `flipTicks()` 供调用方。
- 两条链路共用：扫帚 `MaidBroomDrive.combatPoint`、远程空袭 `MaidFlightCombatBehavior.faceOrbit`。
  真翻那一刻各写一行「反向环绕：旋向翻转为 …（每 8 秒对半概率决定是否掉头）」。
- `forget` / `clearAll` 里一并清 `DIR` 表（与 STATE / SPEED 同款）。

**③ 手册补写（两树 `GuideChaptersFlight.java`）**
- 五种机动那一段：权重改 24/24/18/17/17、蛇形 ±22%，新增「每 20 秒换一种」与
  「环绕旋向每 8 秒随机掉头」两段，写明日志关键字（`接敌机动` / `反向环绕`）。

**④ 配置面板 + 配置注释**
- 面板「战斗与自保 → 接敌机动」：`接敌机动 · 是什么` 改成"且每 20 秒换一种"，新增一行
  `环绕 · 方向会掉头`；总开关那行补"每 20 秒换一段"。两树面板同步。
- `MaidSmartConfig.COMBAT_MANEUVER_ENABLE` 的注释同步权重与两条新机制。

### 四、没做的事（如实写）

- **没动**"战斗结束判定"本身。日志显示收尾链条（`clearClimb` / `endFlightSafely`）已经在生效
  （五种机动都抽到过），所以本轮没有去改"什么算战斗结束"——真正让观感是"一直在环绕"的是
  "一场遭遇太长而机动一场只抽一次"，这一条已由 ① 的段上限解决。
- **没动**空袭"拉开距离"（那是 实测七百〇四 的 `airRaid.minStandoff` + `faceAwayFlat`，
  日志里 `近战空袭 … 拉开距离：水平 4.59 格 < airRaid.minStandoff 6.00 格` 已经在打）。
- **没动**几何骨架 / 高度基准 / 硬上界（半径仍夹在「离敌最近距离」与「离敌最远距离」之间；
  高度仍只加不减）。

### 五、怎么验证

- 日志搜「接敌机动」：一场超过 20 秒的仗里，同一只女仆应当出现**多次**、且相邻两次的名字不同
  （换段那几次带「本场遭遇第 N 段·换打法」）。
- 日志搜「反向环绕」：每约 8 秒一次判定，真翻才有那一行；相邻两行的顺/逆应当来回变。
- 两个 jar 同名覆盖（`promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`），
  部署到三个本地目标（两个客户端 + 服务端 pack1201）。

## 实测七百〇四【接敌机动真的每场换 + 岩浆判定按她的身位 + 空袭也拉开距离】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话（四条）

> 「1. 就是对于岩浆这种危险环境的寻路还是差点意思，导致女仆有的时候还是会飞进岩浆里面
> （在空袭和扫帚里面都有这种问题），自己寻路时，对于危险环境的判定也有点问题，导致有的时候
> 还是会踩上去。
> 2. 看一下刚才运行的游戏日志，是我的错觉吗？我怎么感觉打了那么多场都一直在用环绕啊。
> 到底是调用上出了问题还是说就是战术设计的不明显？
> 3. 其实近远程空袭的女仆也是需要有一个拉开距离的
> 4. 我在 1.20.1 部署了一个等价交换。里面有一些物品可以帮助玩家飞行，你看看能不能那个 PR 里
> 做的防创造飞行能不能适配？」
>
> 「对于岩浆这种危险环境的判定，可能需要把女仆自身的碰撞伤害算进去。尽可能的让她规避岩浆。」

### 二、第 2 条：不是错觉，是两个真 bug（已实测日志实证）

**证据**：`D:\.minecraft\versions\1.21.1-NeoForge_21.1.250\logs\promaid.log` 整场
21554 行里，`接敌机动` 只出现 **1 次**（21:54:32 远程空袭·高悠悠），而 `随机环绕` 出现 6 次、
`扫帚模式 接敌 → 先爬到它上方` 出现 20+ 次。扫帚那条链路 `接敌机动` **一次都没打出来**。

两个独立缺陷，缺一个都会造成"永远在环绕"：

**缺陷 A：抽签状态没随「遭遇」重置（真正的根因）**
`CombatManeuver.begin()` 是幂等的（有状态就原样返回），而"一场遭遇结束"（丢目标、下鞍、
玩家接管）走的是 `MaidBroomDrive.clearClimb` / `MaidFlightCombatBehavior.endFlightSafely`，
**这两处原先都不清 `CombatManeuver` / `CombatOrbit` 的状态**——只有换任务/下线（`forgetMaid` /
`forget`）才清。于是"每场遭遇抽一种"变成"一次抽签用一整天"：第一场抽到环绕（34% 概率，不低），
之后每一场都还是环绕。**修法**：在这两个"遭遇收尾点"各加一行
`CombatOrbit.forget(id)` + `CombatManeuver.forget(id)`，让下一场真的重抽
（`forget` 刻意不清 `SERIAL`，所以是"换一种"而不是"抽第一签"）。

**缺陷 B：扫帚的日志被节流吃掉（为什么日志里看不到）**
扫帚那条链路用 `mountLog()` 写日志，而它是"同一只女仆 5 秒最多一条"的**节流**——
`随机环绕` 与 `接敌机动` 在**同一 tick** 起手，先进去的 `随机环绕` 占掉名额，`接敌机动` 被静默丢弃。
空袭那条用的是直写日志，所以只有它打得出来（这也是日志里唯一那 1 条是远程空袭的原因）。
**修法**：扫帚的 `接敌机动` 改成直写 `PromaidLog.log`（它本来就是每场遭遇一行，频次同"爬升到位"档）。

> 一句话：**不是战术设计不明显，是它真的几乎没被抽到、而且打出来的那一行还被日志节流吞了。**

### 三、第 1 条：岩浆判定按「她的身位」算（`DangerBlocks.cellDangerousBoxed`）

**玩家原话**：「可能需要把女仆自身的碰撞伤害算进去」。旧判据 `cellDangerous` 只看
**一个格**（站立格 / 脚下 / 头顶灼烧型），而女仆碰撞箱是 `0.6 × 1.8`——她**横跨两格宽**。
"她自己那一格安全、隔壁就是岩浆"时判**安全**，于是她贴着岩浆边缘走/飞，被自己的碰撞箱推进去。

新增 `DangerBlocks.cellDangerousBoxed(level,x,y,z,radius)`：把判据从「脚底那一格」扩到
「她的整个身位」（水平 ±`ceil(宽/2)` 格，默认 0.6 宽 → ±1 格 / 3×3）。
**只加宽不加高**（竖直仍只算站立格/脚下/头顶灼烧型）——把整个 1.8 格高都算上会让"站在深坑边"
也误判，那不是"她会掉进去"。

同时修了两个**真 bug**（都是"她真的会走/飞进岩浆"的直接来源）：

1. **地面「附近岩浆避让」探的是眼高那一格**（`floor(m_20188_)` = 脚底往上 1.62 格）。
   而岩浆是流体、贴着地面那一层：她站在地面上时脚下那格才是岩浆面，眼高那格是**空气**
   → 这个避让**恒判干净**，对"贴着地走/走进浅岩浆"几乎完全失效。
   现在改成探**脚底那一层**（`floor(m_20186_)`），并按碰撞箱横扩。
2. **扫帚「待命降回地面」不认岩浆**（`parkIdle` → `groundYOrNaN` 只问"这格是不是空气"，
   而岩浆**不是空气**）——"岩浆湖面"在她眼里与"地面"一模一样，待命降落会直直落到岩浆面上。
   现在落点是危险格就照旧**原地悬停**、绝不落过去（宁可挂在半空）。

配套：飞行跟随那一档原先只有 `detour`（"要飞向的点"会穿进危险格才让开）、**没有** `antiSink`
——它在岩浆湖上方平飞时视线是水平的（detour 判干净），可滑翔的重力还在把她往下拽。
现在 `MaidFlightFollowBehavior` 的空中那一支末尾也调 `MaidFlightHazardGuard.antiSink`，
与远程空袭 `tickRangedAir` 同款（只抬竖直分量、不碰朝向；地面起跳与"进收手半径"都在它之前 return，
落地下降不受影响）。

### 四、第 3 条：空袭也「拉开距离」（`airRaid.minStandoff`，默认 6.0，0 = 关）

**玩家原话**：「其实近远程空袭的女仆也是需要有一个拉开距离的」。新配置管三件事：

1. **远程空袭盘旋半径的硬下限**：半径区间近端取 `max(盘旋半径 × 0.75, 本值)`。
   旧版近端是"盘旋半径 × 0.75"现算的——把「离敌最远距离」调小时近端会跟着塌，
   她可能绕到 3 格、正好进近战怪的攻击范围。
2. **近战空袭「先把距离拉开再俯冲」**：已占好高度但水平距离没到这条线时，
   她先**背离敌人平飞**一小段（新增 `faceAwayFlat`：俯仰 0，不抬头——高度已占好，
   抬头会掉水平速度还把她顶更高），拉开了才压低机头。
3. 贴身怪把她推到这条线以内时，她**不继续压着敌人绕**。

**近战那一档不会因此打不中**：这条线只作用在「俯冲之前」；俯冲一旦开始，
瞄准（`faceTarget`）与命中判定（`sweepWithin` / `smashHit`）**一个字不改**
（这个模组在命中率上专门修过两轮）。日志搜「拉开距离」看每次触发时的水平距离。

### 五、第 4 条：等价交换（ProjectE）能不能适配「仿创造飞行」

**能，但只能走「资格物品表」那一路，不能走「能力」那一路。** 实证（`javap` 反编译 ProjectE 1.0.1）：

- ProjectE 的飞行是一个 **`ServerPlayer` 绑定的 capability**（`IFlightProvider.canProvideFlight(
  ItemStack, ServerPlayer)`，调用方 `InternalAbilities.shouldPlayerFly(ServerPlayer)`）——
  **签名就要求玩家**，女仆没有 `Player` 身份、也没有 `Abilities`，这条路对女仆**天然不适用**。
- 真正提供飞行的物品只有三件（`IFlightProvider` 的实现者）：
  - `projecte:gem_boots`（Gem Boots）——`canProvideFlight` = "这双鞋穿在脚上"（`FEET` 槽 == 该栈）；
  - `projecte:arcana_ring`（Ring of Arcana，**mode = Zero 时**）——`canProvideFlight` 恒 `true`
    （是否真飞由 Zero 模式门控）；
  - `projecte:swiftwolf_rending_gale`（SWRG）——本版本 `canProvideFlight` 恒 `false`（**不给飞**）。
- 而我们的「仿创造飞行」**本来就不复刻物品物理**：它是"她有资格（物品表命中）我们就托住她"
  （`setNoGravity` + 每 tick 直接给速度）。所以**只要把物品 id 填进 `misc.freeFlightItems`
  就能直接生效**，不需要写任何适配代码：

  ```
  freeFlight = true
  freeFlightItems = ["projecte:gem_boots", "projecte:arcana_ring"]
  ```

  （1.20.1 那个实例的配置就是 `D:\.minecraft\versions\1.20.1-Forge_47.4.21\config\promaid-common.toml`。）

  说明：SWRG **别填**（本版本它自己不给飞，填了等于凭空给飞）；`gem_boots` 在本机制下
  按"身上有这件装备"认（我们的物品表扫护甲槽，不要求她真的穿在脚上——但为了观感一致，
  建议就让她穿着）。

### 六、改动清单（两树镜像）

| 文件 | 改什么 |
|---|---|
| `combat/MaidBroomDrive.java` | `clearClimb` 清两套随机（缺陷 A）；`接敌机动` 日志改直写（缺陷 B）；`parkIdle` 落点危险则不落 |
| `combat/MaidFlightCombatBehavior.java` | `endFlightSafely` 清两套随机（缺陷 A）；远程盘旋半径硬下限；近战 `faceAwayFlat` 拉开门槛；新增 `airMinStandoffCfg`/`logStandoff`/`STANDOFF_LAST_LOG` |
| `combat/MaidFlightFollowBehavior.java` | 空中那一支末尾补 `antiSink` |
| `combat/SelfPreservationBehavior.java` | `avoidLavaMovement` 改探脚底那一层 + 按碰撞箱横扩 |
| `combat/MaidFlightHazardGuard.java` | `dangerousAt` 加碰撞箱半径重载；`detour`/`up`/`pathBlocked`/`antiSink` 全部带上 |
| `tool/DangerBlocks.java` | 新增 `cellDangerousBoxed` + `boxRadius` |
| `config/MaidSmartConfig.java` | 新增 `AIR_RAID_MIN_STANDOFF`（`airRaid.minStandoff`，默认 6.0） |
| `config/PromaidConfigScreen.java` | 空袭那一板新增「离敌最近距离（格）」一行 |
| `assets/maid_smart/lang/{zh_cn,en_us}.json` | 新键 `config.promaid.airRaid.minStandoff` |
| `guide/GuideChaptersSystem.java` | 岩浆判定那一节扩写（她的身位 / 两个真 bug） |
| `guide/GuideChaptersFlight.java` | 新增「空袭也拉开距离」一节 |

### 七、验证

- 两树 `javac` **0 错误**（仅既有 deprecation 警告）。
- 两树 `GuideChaptersSystem.java` 逐字节一致；`GuideChaptersFlight.java` 新增块逐字节一致。
- 四份 lang JSON 解析通过（键数 360/213/366/224）。
- jar 内 `CombatManeuver` / 新日志关键字 / 新配置键核对通过；`_mixchk.py` 注入点审计通过。
- 部署三处（两个客户端 `versions\*\mods` + 服务端 `pack1201\mods`）逐处 md5 一致。
## 实测七百〇三【接敌机动：五种飞行方式 + 空袭空中防叠罗汉】

> 版本号不变，仍是 v1.3.0(beta)。**两条链路都有**（1.20.1 Forge + 1.21.1 NeoForge，两树镜像），
> 同一份 jar 覆盖发布；**未发平台**。

### 一、玩家原话

> 「在女仆扫帚模式接敌的情况下随机性能不能稍微高一点？我是说现在全都是保持盘旋状态的，
> 战斗方式有些过于单一了。首先先研究多种飞行方式（要求适配远程武器，可以参考一下现代空军
> 战术里面都有哪些飞行方式），然后女仆接敌之后，会从这多种飞行方式中选择一个进行执行。
> 而不全是统一绕圈。即使真的选到了绕圈，那么每次的盘旋方向、速度等，各方面都要有一定的
> 随机性，当然不要太大。而且基础比敌人高多少格这一点还是要的。」
>
> 「扫帚模式控制多女仆，让我想到了另一个担忧的情况，使用鞘翅时的近战空袭和远程空袭会不会
> 也出现先前那种多种女仆都在使用同一种飞行方式，导致叠罗汉的情况？」

### 二、为什么"全都是盘旋"（现状定位，不是猜测）

实测六百九十三 / 七百〇一 已经把**同一张圆**拆开了：半径各抽各的（`CombatOrbit.radius`）、
旋向对半（`CombatOrbit.direction`）、绕圈快慢也在飘（`CombatOrbit.speedScale`）。
但**"打法"本身仍然只有一种——绕圈**。所以多只女仆观感上还是"一群人在同一个圆上转"，
只是圆的半径不同、转的方向不同。玩家要的是"换打法"，不是"同一个圆上换参数"。

### 三、新增：接敌机动（`com.maidsmart.combat.CombatManeuver`，两树字节一致）

在"圆"这个骨架上叠一层**波形调制**，五种机动，每只女仆**每场遭遇各抽一种**：

| 机动 | 权重 | 空战原型 | 干什么 |
|---|---|---|---|
| 环绕 ORBIT | 34% | 基线 | 什么都不加——半径/旋向/快慢照旧随机（玩家说的"选到绕圈也要有随机性"就落在这档） |
| 蛇形 WEAVE | 22% | defensive weave | 一边绕一边**径向进出 ±15%**，距离持续滑动，敌人算不准她下一拍在几格外 |
| 高悠悠 YOYO | 18% | yo-yo | 高度做 **8 秒慢波**（基准高度**之上** 0~幅度格），且**高处转得慢、低处转得快**——真实 yo-yo 拿速度换高度 |
| 脱离再进 EXTEND | 14% | extend / disengage | 周期性**拉到最外圈再切回来**："输出"与"退出去喘一口"交替，最省血 |
| 8 字横切 FIGURE8 | 12% | cross-turn / 剪刀机动 | 每 **4 秒翻一次旋向**，在敌人正面来回横穿，而不是匀速绕单侧的圈 |

**抽签是稳定的**：由「女仆 UUID + 这是她第几场遭遇」派生（`CombatManeuver.pick`/`rand01`）。
- 同一份存档重放出来还是同一种（可复现，日志对得上）；
- 但她**下一场会换一种**（`SERIAL` 计数逐场递增，`forget` 刻意不清它）——不是"一辈子只打一种"；
- 同场多只女仆各抽各的 → 不会再出现"一群人在同一个圆上转"（玩家原话的直接解）。

### 四、两条硬边界（为什么这样叠是安全的）

1. **高度只加不减**：`heightAdd` 返回 `0 ~ amp`（`amp = combat.maneuver.yoyoAmp`，默认 4），
   是**加**在各方自己那条基准高度上（扫帚 = `COMBAT_ALT`，空袭 = `holdY`）。
   玩家那条「基础比敌人高多少格这一点还是要的」**在任何机动下都成立**（只会更高，不会更低）。
2. **几何骨架一个字不改**：机动层只输出**乘数/加数**（半径倍率、角速度倍率、高度增量、旋向翻转），
   最终半径仍由调用方夹进 `[离敌最近距离, 离敌最远距离]`（扫帚 `[lo, hi]` / 空袭 `[orbLo, orbMax]`）
   ——六百九十三 立的那条"随机绝不会变成越飞越远或贴脸"规矩原样成立。
   幅度也都在小范围：半径倍率 `[0.82, 1.15]`、角速度倍率 `[0.75, 1.15]`。

### 五、新增：空袭的空中防叠罗汉（`combat.airSeparation`，默认开）

**这是玩家第二个担忧的答案，也是实证出来的真实缺口**：扫帚链路**本来就有**一套反叠罗汉
（实测六百九十一：相位错开 `phaseOf` + 邻近互斥 `separate`），但**鞘翅空袭这两条链路一条都没有**
（全树 grep `separate`/`SEP_R`/`叠罗汉` 在 `MaidFlightCombatBehavior` 里零命中）。具体缺在两处：

- **远程空袭**：半径虽已各自随机，但**起点没有错开**，且重掷节拍会让她们偶尔凑到一起。
  现在给每只一份 **UUID 派生的稳定半径偏置**（`separationRadius`，±`combat.airSeparationRadius`，
  默认 2.0 格），最终仍夹进 `[orbLo, orbMax]`。
- **近战空袭**：更直接——"背离敌人抬头爬升"那 30 tick（1.5 秒）她们飞的是**同一条直线**。
  现在给每只一个 UUID 派生的稳定方位偏置（`CombatManeuver.azimuthSpread`，**±12°**）。
  **只在近战那一档偏**（远程爬升期同时也在开火，偏了影响输出），而且**只偏爬升**：
  俯冲那一记的朝向仍由 `faceTarget` 给（瞄准方向），一个字节不改——这个模组在
  实测四百七十三 / 四百八十三 上专门修过两轮命中率，不会为了好看毁掉它。
  偏置同时写进 `LookControl.setLookAt` 的期望角度，否则每 tick 会被拉回 0。

### 六、配置（两树同步）

新配置段 `[maneuver]`（面板：**战斗与自保 → 接敌机动**，单独成板——它管两条链路共有的
一件事，塞进 [扫帚] 或 [空袭数值] 都会让另一边的人找不到）：

| 键 | 默认 | 范围 | 说明 |
|---|---|---|---|
| `maneuver.enable` | true | 开关 | 总开关；关掉 = 永远环绕（半径/旋向/快慢的随机照旧保留） |
| `maneuver.yoyoAmp` | 4.0 | 0~16 | 高悠悠的高度幅度（格，**只加不减**）；0 = 这一档退化成普通环绕 |
| `airSeparation` | true | 开关 | 空袭空中防叠罗汉（半径偏置 + 近战爬升方位偏置） |
| `airSeparationRadius` | 2.0 | 0~8 | 空袭半径偏置强度（格）；0 = 只保留近战爬升方位的错开 |

语言键两树各 +5（zh_cn / en_us 各 5 条）。

### 七、日志

- 扫帚：搜「**接敌机动**」看每只女仆抽到哪一种（每场遭遇一行）；搜「随机环绕」看半径/旋向/速度。
- 空袭：搜「**接敌机动**」（`远程空袭` 前缀）；搜「随机环绕」看半径/旋向。

### 八、验收（本项目铁律，逐条留痕）

- 两树 `javac` **0 错误**（Forge 370 源 / Neo 372 源）。
- `_mixchk.py` 注入点审计 **PASS=175 SKIP=10 UNRES=0 FAIL=0**（本批未动 mixin，符合预期）。
- 只读核对 `_p_verify703.py`：**OK=51 FAIL=0**（两树落点、镜像、配置键、面板、四份语言文件 BOM/EOL、硬边界）。
- jar 内容核对 `_p_jarchk703.py`：**OK=38 FAIL=0**（新类 + 五个枚举常量 + 关键方法 + 5 条语言键 + JSON 合法）。
- jar：`promaid-1.3.0-forge-1.20.1.jar` **10,995,109 B** / `promaid-1.3.0-neoforge-1.21.1.jar` **11,024,956 B**，
  同名覆盖，版本号仍是 1.3.0。
- 部署三处（两个客户端 versions 的 mods + 服务端 pack1201）**逐字节 match=True**；部署前确认无 java/javaw 在跑。

### 九、没做的事

- 未发平台（CurseForge / Modrinth / MC百科 一个字节没动）。
- 未改俯冲瞄准、未改爬升仰角、未改开火链路、未改任何"去哪"的骨架（只加波形与偏置）。
- 未新增物品 / 命令 / 按键。

## 实测七百〇二【仿创造飞行移植到 1.20.1（精简版）+ 贡献者调研点名】

> 版本号不变，仍是 v1.3.0(beta)。这一批是**把 1.21.1 树的「仿创造飞行」按既定边界移植到 1.20.1（Forge）**——
> 方向是上一条 PR #24 跟帖里定下的：**做，做成精简版，由我们这边落**（不让贡献者为此单开分支）。
> 同时把贡献者这段调研（资格三路 + 两条版本边界的核实）在 CHANGELOG 里点名。

### 一、资格探测：1.20.1 只留两路（这是"精简"的全部含义）

`javap` 对着两份 SRG jar（`client-1.20.1-20230612.114412-srg` 与 1.21.1 那份）逐条核过，砍掉的是**在 1.20.1 物理上没有落点的两路**：

- **重力归零启发式（砍）**：`Attributes.GRAVITY` 在 1.20.1 的 `Attributes` 里**根本没有这个字段**（1.21.1 有）。
- **数据组件两路 `@组件` / `@组件~文本`（砍）**：数据组件（`DataComponentType` / `stack.getComponents()`）是 1.20.5+ 的机制；1.20.1 的 `ItemStack` 还是 NBT。勉强改写成 NBT 判据不划算（NBT 里"哪个键代表能力"毫无统一约定）。
- **保留的两路**：①资格物品表（双手 / 护甲 / 背包 / TLM 饰品栏 / 额外容器），支持 `modid:item` 与 `#命名空间:标签` 两种写法；②资格效果表（药水效果挂在实体上，这一路对女仆天然有效）。
- 顺带一条**好消息**：动画注册**一行都不用改**——`AnimationRegister.register` 的两个重载签名在两版 TLM jar 里**逐字相同**，`@Invoker` 暴露 `register` + 客户端 mixin 在 `registerAnimationState()` 尾部挂 `fly` / `elytra_fly` 这一套可以原样搬。

### 二、搬过来的整条链路（与 1.21.1 树逐字一致的部分）

状态机（飞行中 / 软着陆 / 落地待命）、一阶飞行力学（速度直接由距离算，绝不累加——增量式推进在接管 travel 后会变成无阻尼谐振子）、朝向 QoL（倒退着飞 + 延迟转身 + 逐 tick 平滑转体）、智能待命（主人停下 3 秒后落到他脚边站好）、战斗档（有敌人就飞去打，飞行是"移动层"跟着她的意图走）、赶路档（本来要走过去的活也交给飞）、"在主人身边干活"不做跟随起飞、状态表 5 分钟寿命回收、遗留无重力交还（`persistentData` 标记）。这些全是纯数学与事件驱动，不碰版本差异。

### 三、1.20.1 侧按仓库现有范式补的部分

- **网络层**：1.21.1 用 NeoForge 的 `CustomPacketPayload` + `PayloadRegistrar`；1.20.1/Forge 是 `SimpleChannel`（本树已有 8 处同样写法）——照抄其中之一，语义一字不改。
- **配置面板那一行开关**：`MaidConfigContainerGui.initAdditionWidgets()` 两版都是 `protected`、连 lambda 编号都对得上，挂法原样搬。
- **快捷键**：照 `EmotionKeysClient` / `BuildKeysClient` 的写法（`RegisterKeyMappingsEvent` + 客户端 tick 轮询 `consumeClick`）。默认**不绑定**，玩家自己在按键设置里绑。
- **命令**：`/maid_smart freeflight_goto` / `freeflight_follow` / `freeflight_enemy`（仅 OP；后两条是专用服务器验收入口——挂"替代主人 / 替代敌人"，因为 TLM 的 `getOwner()` 在专用服务器上恒为 null）。
- **给"走位型"链路让位**：搭路（`BridgeUpBehavior`）在"她具备飞行能力"时整段让位（`bridgeDisabled`）；`MaidMoveSuppressMixin` 补上 `isControlling` 那一档（飞行速度写在 brain 之前，而 brain 随后写的 `WALK_TARGET` 会被 `MoveToTargetSink` 变成寻路、把她的速度拽飘——必须在 brain 阶段源头取消）。

### 四、SRG 命名的实证（本树手工编译、无 refmap）

原版成员一律用运行时名（`m_xxxxx_` / `f_xxxxx_`），TLM 自己的方法保持可读名（如 `isMaidInSittingPose`）。本轮新用到的名字全部 `javap` 实证过，包括几处**反直觉**的：`isAir` = `m_60795_`（不是 `m_60713_`，后者是 `is(Block)`）、`getCollisionShape` 在 `BlockStateBase` 上是 `m_60742_` 且**要传 `CollisionContext`**（没有两参重载）、`Vec3` 的分量字段是 `f_82479_/f_82480_/f_82481_`、`setDeltaMovement(DDD)` 是 `m_20334_`（`m_20256_` 只接 Vec3）、`FriendlyByteBuf` 的 UTF 是 `m_130070_`/`m_130277_`、`Entity.hasPermissions` 是 `m_20310_`。

### 五、刻意不做的事

不识别 Iron Jetpacks / 柴油喷气背包这类**自带燃料推进**的装备：它们的推力逻辑绑在玩家身上，我们"仿创造飞行"等于凭空绕过它的燃料，算作弊。想用请自己往物品表里加，并明白这一点。

---

## 实测七百零一【扫帚接敌随机机动二期 + 复活女仆"身体停在原地、扫帚照飞"根治】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话三件：①「重生以后的女仆在坐上扫帚以后有的时候会做着做着自己身体就停了下来，停在原地，但是扫帚在进行飞行。」②「目前在扫帚模式下的参战以后随机运动随机性不够强。应该要保证至少与怪物拉开多少距离，然后剩下的运动的绕圈速度以及半径要每隔一小段时间就变化一下。同时与其他的女仆拉开距离（这些机制仅在扫帚模式接敌以后才启用）」③（粉丝日志答疑，见文末）。

### 一、①的根因：复活女仆在客户端是一具"没有载具的孤儿实体"（原版字节码实证）

- 复活的链路是 实测六百九十七 那条"删客户端实体 → 重新生成（同 UUID、新实体 id）→ 补三枪"。而本模组的补包方法 `MaidResyncCommand.resyncTo` **只补了"她载着谁"这一方向**（`if (!maid.getPassengers().isEmpty()) → SetPassengersPacket(maid)`）。
- 反编译 1.21.1 原版 `ServerEntity.sendPairingData`（1.20.1 SRG `m_9824_`，同形）用的是**两个方向**：
  `if (!entity.getPassengers().isEmpty()) accept(new ClientboundSetPassengersPacket(entity));`
  `if (entity.isPassenger()) accept(new ClientboundSetPassengersPacket(entity.getVehicle()));`
  ——**后者（她骑着谁）我们从来没发过**。
- 而原版对"乘客"**不发它自己的位置包**（反编译 `ServerEntity.sendChanges`：`if (entity.isPassenger())` 那一支只用载具坐标、并 `sendDirtyEntityData` 后就 `wasRiding=true` 返回）。于是客户端那只女仆没有载具可跟，被钉死在 `AddEntity` 那一拍的落点上不动；服务端的扫帚照常被我们驱动飞行 —— 正是玩家说的"她的身体停在原地，但扫帚在飞"。
- 为什么只有**复活过的**女仆会中招：入世界自动补包对"新实体"是刻意放过的（实测五百九十六：新 id → 原版追踪会自己发全套包），复活走的正是"同 UUID、新 id"，那一路不发；只剩 实测六百九十七 那三枪——而**载具那侧的 `lastPassengers` 早已记着"她在车上"，`sendChanges` 只在乘客表"变化"时才重发**，所以补包之后谁都不会再把她挂回扫帚上。

### 二、②的改法（扫帚接敌档三件事；两树镜像）

1. **独立的最小离敌距离**：新增配置 `combat.broom.minStandoff`（默认 6，1~32，面板「移动与行为 → 扫帚模式 → 离敌最近距离（格）」）。盘旋半径的近端由"跟着盘旋距离现算"改成 `max(盘旋距离 × 0.75, 本值)` —— 你把「离敌最远距离」调到 4，旧版近端会跟着塌到 3 格、她正好飘进近战范围；现在下限独立存在。区间退化（下限越过上限）时以上限为准，绝不倒挂。
2. **绕圈速度也缓慢游走**：`CombatOrbit` 新增 `speedScale(UUID)`（0.6~1.5 倍，每 3 秒重掷、每拍滑 0.6% 量程；与半径的 4 秒节拍**刻意错开**，否则同拍既换半径又换速度像打摆子）。角速度这一项从此不再确定，敌人算不出提前量。随机盐与半径那一路分表派生，两条链互不相关。
3. **接敌期间与同伴拉开距离**：`MaidBroomDrive.separate` 的"最小水平间距"在接敌档由 2 格放宽到 4 格（推力封顶同步 1.5 → 2.5 格，否则推不动）。`steerTo` 新增 `combat` 形参，只有 `MaidBroomBehavior` 的接敌分支传 `true`——平时跟随/守家仍走原来的 2 格。三层一起（半径随机 6~10 + 旋向对半 + 接敌间距 4 格）保证她与同伴不在同一条射线上。

**只动这三件**：接敌爬升、盘旋高度、开火链路一个字没动；平时跟随/守家巡逻照旧。

### 三、③粉丝日志答疑（写在 changelog 里备查）

粉丝贴的最后三行是 `FML: 47.4` / `Forge: net.minecraftforge:47.4.22` / `Flywheel Backend: flywheel:instancing` —— 这是**加载器与前置模组的正常版本行**，不是报错：Forge 47.x = 1.20.1 的正确版本档，Flywheel（Create 系模组用的渲染后端）打印它的后端模式。**"手机版不支持"这个方向是错的**：本模组是 **Java 版**模组，手机（基岩版/PE）从架构上就装不了；要玩只能在**电脑 Java 版**上装 Forge 1.20.1（或 NeoForge 1.21.1）+ 对应版本的 TLM（touhou_little_maid ≥ 1.5.0）。若他确实是在电脑上进的游戏，那要看日志里**更靠前**的第一条 `Caused by` / `Failed to load mod`。

### 四、验证

两树 `javac` **0 错误**；`_mixchk` 注入点审计 **0 FAIL**（本批不新增注入点）；打包门禁全过（jar 内 class 与源一一对应、mixin 包登记、lang json 合法）；只读核对脚本确认新值/新档在场、区间不倒挂逻辑在场、两树镜像一致；jar 内核对 `getVehicle` 补包与 `minStandoff` 档在场；部署三处一致。

## 实测七百【仿创造飞行·跟随时速度被拽的源头抑制补档】（版本号不变，仍是 v1.3.0 beta）

> 背景：PR #24（仿创造飞行）复核中贡献者指出——他在 PR 里给 `MaidMoveSuppressMixin` 加的「飞行接管」档没有随合入进 main，并担心「跟随时 TLM 跟随写下的 WALK_TARGET 会把飞行速度拽下去」。反编译定论后确认缺口真实，本批在 main 侧补上（PR 侧无需再改）。

### 一、缺口为什么是真的（原版字节码实证）

- 反编译 1.21.1 原版 `MoveToTargetSink`：`start()` 调的是 `getNavigation().moveTo(Path, double)`，路径由 `tryComputePath` 里的 `createPath` 造——**两条都不在 `FreeFlightWalkGuardMixin`（只拦 `moveTo(x,y,z,speed)` 直连档）的射程里**。
- 我们的飞行速度写在 `MaidTickEvent`（brain tick 之前），控制器每 tick 的 `suppressWalk` 清 WALK_TARGET 也跑在 brain 之前——但 TLM 跟随在**同一个 tick 的 brain 阶段**重新写入 WALK_TARGET → `MoveToTargetSink` 造路径 → `MaidMoveControl` 在「我们写速度之后」执行 → 她被自己的寻路拽着、速度发飘。

### 二、改法（neo 树；1.20.1 树没有仿创造飞行，不动）

`MaidMoveSuppressMixin`（同一个注入点 `MoveToTargetSink.tick` HEAD，**不新增注入点**）加一档：`MaidFreeFlightController.isControlling(maid)`（= 飞行中 / 软着陆中）→ 清 `WALK_TARGET` + 停导航 + 取消本 tick。**落地待命（ST_STANDBY）不拦**——她落地站着，TLM 跟随照常工作（与搭路让位的口径同源）。

### 三、验证

neo `javac` **0 错误**；mixchk **PASS=171 SKIP=9 FAIL=0**（注入点计数不变——加的是同一注入点内的分支）；打包门禁全过；jar 内核对 `isControlling` 档在场；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。

## 实测六百九十九【排班表全量显示 + 一键召回真正可用 + 复活落点隐身根治】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话：「排班表无法显示所有的女仆，如果一个玩家的女仆死亡，回到了玩家的重生点，但是玩家的重生点离玩家当前位置又太远，那么排班表上就不会显示这个女仆的资料和信息。会造成缺失。此时如果使用一键召回会显示无法召回。」

### 一、根因：复活到远处的女仆根本进不了实体表（原版字节码实证）

- 反编译 1.21.1（1.20.1 同款语义）`PersistentEntitySectionManager.addEntity`：往一个**未加载区块**里 `addFreshEntity` 的实体【不在实体表里】——区块可见性默认 `HIDDEN`，"不可达就不 startTracking"；要等区块异步载完、可见性升档的那一刻才"现身"。
- 而实测六百九十八的持票对账每 5 秒跑一次，扫的恰恰是实体表：她还没现身 → 对账表里"没人要" → 把 `ensureTicketNow` 刚挂的票撤掉 → 区块放弃加载 → 她**永远**进不了实体表。
- 结果就是玩家看到的：排班表不列她（打开包扫的是实体表）、按 UUID 找不到她、一键集合只能对着**死亡点**挂 15 秒强载票然后报「没能等到」——"一键召回显示无法召回"。
- 死亡→复活的 60 秒窗口里她更不是实体：列表缺一行、召回只会"没找到/没能等到"。

### 二、改法（两树镜像）

1. **复活前先把重生点区块同步加载到 FULL**（`MaidAutoResurrect.resurrect`）：解析出重生点后先 `getChunk(cx, cz)` 同步加载，再算落点、再 `addFreshEntity`——她当场进实体表、票也稳了；落点计算从此看得到真实地形。这也是 697/698 那串幽灵问题的又一个根除点。
2. **票龄护栏**：`MaidChunkLoadManager` 新增 `TICKET_BORN`——刚挂的票（<100 tick）在 5 秒对账里**不撤**，"实体还在异步加载路上"的窗口彻底关死。
3. **登记表落盘**：`LAST_SEEN` 从会话内存升格为 SavedData（`maid_smart_maid_seen`，存主世界；每 5 秒写穿落盘、启动灌回；server 停止只复位内存）——重启/重进后，远处未加载的女仆依然有迹可循；条目顺带记下**名字**。
4. **排班表全量显示**（`openFor`）：实体表扫完后再补两类行——① 已阵亡、等待自动复活的 → 任务列「✚ 复活中」；② 不在已加载区块的（登记表）→ 任务列「⚑ 未加载」（跨维度照旧标维度）。详情页位置栏照常轮询：复活中的显示「复活中·剩 N 秒（将落在重生点/你身边）」，未加载的显示最后出现位置（维度后缀「·未加载」）；中英 lang 补 `task.maid_smart.away / reviving` 两键。
5. **一键召回真正可用**：单独召回找不到实体时——先查待复活表（回「她已阵亡，还有约 N 秒复活」），再按登记位置**当场强载区块 + 进待召回队列**（回「她一出现就自动传回你身边」）；一键集合新增 reviving 报数（「另有 N 名女仆已阵亡——正在自动复活」）；死亡瞬间登记作废（`forgetMaid`），不再对着死亡点白挂 15 秒票。

### 三、验证与边界

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批不新增 mixin 注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过，forge 576 / neo 593 个 class（比上批 +2 = `MaidSeen` 记录 + `MaidSeenStore` 两张卡）；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。;jar 内容核对（新增类/常量/话术/预载调用）**0 FAIL**。
- 边界：被魂符收回的女仆会留一条「⚑ 未加载」的旧行——对它点一键召回会强载一次、15 秒等不到就报「没能等到」并自动清掉该行（自愈）；排班（持久票）那条路一字没动。

## 实测六百九十八【建造女仆不再被打死 + 幽灵建模三道门 + 扫帚待命落地】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话：「1.问题还是未能解决。女仆的建模仍然会会被卡掉，而且还会出现女仆骑上扫帚，结果在空中悬空的状态。具体可以看精灵酒狐的游戏日志。2.之前提到过，建造模式下的女仆应该是有5级的抗性的，但她却仍然被打死了。」

### 一、建造护盾为什么会掉：凶手是我们自己的「主动参战」（日志实证）

2026-09-27 那场（1.21.1 实例）森近霖之助两次出事，形状一模一样：

```
04:35:25.021  [promaid/战斗] 森近霖之助 参战：maid_smart:build -> touhou_little_maid:attack
04:35:25.056  [maidhurt] 类型=genericKill 伤害=3.4e38      ← 护盾已经没了，这次靠魂符收走保命
04:38:24.856  [promaid/战斗] 森近霖之助 参战：maid_smart:build -> touhou_little_maid:attack
04:38:26.762  森近霖之助被下界合金巨兽杀死了                ← 2 秒后这次真死了
```

- 建造护盾（实测二百七十三：无限抗性 V + 受击取消 + 怪物不锁她）的判据是「**当前**任务是不是建造」。
- 本模组自己的「主动参战」会把建造女仆切成 `touhou_little_maid:attack`——切换的那一拍 `isBuildingTask` 变 false，`tickShield` 走 else 支把抗性 V 摘掉、受击取消也不再拦：她成了一个穿着建筑工衣服的普通战斗女仆。

### 二、改法（两树镜像）

1. **建造女仆默认不参战**：`AutoCombatTargeting.tryEngageMaid` 加一道门——当前任务是建造、且「战斗模式分类表」没有点名 `maid_smart:build`，就不把她拖进战斗（想让她照旧参战：表里写 `maid_smart:build=近战/远程`，表优先，与所有其它任务同一条口径）。
2. **护盾跟「建造女仆」走，不跟"这一拍的任务字段"走**：`BuildShieldGuard.isBuilding` 现在也认「本模组主动参战会话中（COMBAT_ACTIVE_TAG）+ 战前任务（PREV_TASK_TAG）= 建造」——即便玩家点名让她参战，整场战斗护盾也不掉；战斗结束还原后标记被清，自动回到常规判定。两处互不依赖。

### 三、幽灵建模的真正机制（这次是从原版字节码读出来的）

- NeoForge 的 `EntityLeaveLevelEvent` 只在 `ServerLevel$EntityCallbacks.onTrackingEnd` 里发；而 `onTrackingEnd` 由 `PersistentEntitySectionManager.updateChunkStatus` 在**区块视线档位降到 HIDDEN**（区块卸载 / 掉出 entity-ticking）时对区块里所有实体调一遍——这一刻 `setRemoved` 还没执行，所以「离场」日志一直是 `reason=null`（与死亡无关）。
- 它的副作用是 `ChunkMap.removeEntity`：把 `TrackedEntity` 从实体表摘掉，并给**所有看过她的玩家**发一次 `ClientboundRemoveEntitiesPacket`——客户端当场把她删掉。
- 区块重新 ticking 时原版会重新同步她，但"新实体不补包"（实测五百九十六）拦掉了我们的补包，而实测六百九十七 那三枪只覆盖复活后头 10 秒——幽灵就是这么留下来的。

### 四、三道门（两树镜像）

1. **持票即时跟人**：`MaidChunkLoadManager.followTickets` 每 tick 拿我们自己持票表里的 UUID 查一次"她还在不在这个区块"，换了就当场撤旧票、挂新票。旧版每 5 秒才对账一次，中间那几秒她落在"没票的区块"里：服务端不 tick 她、原版也不同步她（这也是"骑在扫帚上定在空中不动"的另一半成因）。
2. **复活落点立刻补票**：`MaidAutoResurrect` 在 `addFreshEntity` 成功之后当场 `ensureTicketNow`——重生点多半不在主人当前的 tick 范围里，等下个 5 秒窗口的代价就是"她在外面那几秒是个没票的女仆"。
3. **可见性自愈看门狗**：挂在 `ChunkMap.tick` 尾巴上（与远程界面同步泵同一个注入点，不新增注入点），只扫**本模组持票的**女仆——① 她在实体表里但**主人没配对**（seenBy 里没有主人的连接）→ 当场重建一次（5 秒冷却）；② 她**根本不在实体表里**（区块没在 ticking）→ 立刻把票挪到她脚下。「主人不在 96 格内」一律不管：原版在 160 格内本来就该同步过她，96 格内没有她一定是异常；更远是正常看不见，不做无谓补包。日志搜「可见性自愈」。
4. 存活的女仆离场（`reason=null` 那一档）也登记一次**强制补包**（与复活同一条三枪队列，队列自己会顺延到"她又能被找到 + 主人同维度"那一拍再打）。

### 五、扫帚待命落地（治"骑上扫帚在空中悬空"）

- ⑥.③「没有敌人、也没在跟主人这一趟」那一档原先一律 `hoverInPlace`（目标点=她现在的坐标，速度收干）——而她很可能正悬在半空：起飞相位本来就要"原地往上抬 1 格"（实测六百七十七 的玩家要求）、打完一仗停在高处、主人在别的维度/离得远时更没人管她。于是"骑上扫帚就浮在那儿"成了常态。
- 现在改成**降回地面待命**：顺着她脚下探地（最多 24 格），找到就降到「地面之上 1.6 格」；探不到地（虚空 / 她比地形高出 24 格以上）照旧原地悬停，**绝不往下扎**；已经贴地时也照旧悬停（不再上下抖）。主人自己飞在天上/在水里/在坐骑上时不落地——那时贴着他悬停才是对的。
- 新配置键 `combat.broom.idleLand`（默认开）+ 面板「移动与行为 → 扫帚模式 → 扫帚·待命落地」+ 中英 lang；日志搜「待命 → 降回地面」。**接敌与跟随一个字不受影响**（那两档在它前面）。

### 六、验证与边界

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增 mixin 注入点，只是往既有 `ChunkMap.tick` TAIL 注入点里加了一行）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
- 边界：她**没被本模组保载**（无主野女仆 / 关掉 maidChunkLoad）时三道门都不动她——自愈只兜"我们自己答应过要保载的"那批；排班（持久票）那条路仍由 `tick()` 的 3b 段管，一字没动。



## 实测六百九十七【自动复活之后自己补三枪实体包：治"复活后建模被卡掉、变成幽灵状态"】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话：「刚刚在游玩的时候有一个女仆在复活之后，建模直接被卡掉了。变成了幽灵状态。」

### 一、日志实证：那一次自动复活留下的完整形状

2026-09-27 那场（1.21.1 实例）一共四次自动复活，每一次都是同一个形状：

```
03:57:43.510  法术模组 Creating new SpellBookManager for maid 2c8b…   ← 新实体入世界
03:57:43.511  [promaid/重同步] 补包跳过：她是新实体（id 6→10260）       ← 实测五百九十六 放行
03:57:43.513  [promaid/自动复活] 圣女酒狐 已在主人重生点复活
03:57:43.589  法术模组 Removed SpellBookManager for maid 2c8b…        ← 刚加进来那只又离场了
03:57:43.590  [promaid/离场] 圣女酒狐 离开世界（reason=null）
```

### 二、为什么会变成"服务端还有她、客户端已经没有她"

- 复活用的是**同一个 UUID、全新的实体 id**（复用 UUID 是刻意的：记忆/灵魂目录按 UUID 索引）。
- 那条「离场」正是本项目注释里早写明的那条链的另一半：**法术模组（万法皆通）对没带「锚核」的女仆在离场时会让客户端把她的实体删掉**。这些女仆确实没有锚核——法术模组自己在同一秒的 debug 日志里打了 `Maid 21b5146f-… does not have anchor_core, allowing removal`。
- 而 实测五百九十六 刻意规定「新实体不补包」（原意：魂符放出的新实体，原版追踪会自己发全套）——于是**没有任何一方**在那个删包之后把她补回来：客户端把她删了、服务端她还在照常开火。玩家看到的就是"建模被卡掉、变成幽灵状态"。

### 三、改法（两树镜像）：复活成功后自己补三枪，绕开"新实体不补包"那道分流

- `MaidAutoResurrect.resurrect` 成功 `addFreshEntity` 之后调用新增的 `MaidResyncCommand.scheduleForcedResync(maid)`：**+2 秒 / 再 +5 秒 / 再 +5 秒**共三枪，每枪就是既有的 `resyncTo`（删 → 生成 → 全量同步数据 → 属性 → 装备 → 饰品）。
- **绕开 实测五百九十六 的 id 分流**：那条规矩是为"魂符放出的新实体"写的，与"复活后客户端可能已被拆掉"是两回事——这里不看 id 变没变，一律补。
- 她正开着物品栏/装备栏时（`guiOpening`）那一枪**顺延**（同 实测五百九十五 的理由：重建客户端实体会让 TLM 的容器槽位抓着一只已被删掉的实体）。
- 客户端本来就有她时，删+加只是一次看不见的瞬时重建（既有注释即如此），不会多出实体。

### 四、顺带的诊断（下次一眼定位）

- 「离场」日志与「自动复活」日志都**带上实体网络 id**（`id 6` / `新实体 id 10260`），「复活后补包」每一枪各一行——三行一 grep 就能看出"离场的是刚复活进来的那一只，还是旧的那一只"。

### 五、验证与边界

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增 mixin 注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
- 只在**自动复活**这条链上生效：手动魂符收放、普通区块重载仍走 实测五百九十六 的 id 分流（原版追踪会自己发全套，我们不掺和）。



## 实测六百九十六【飞行也要把岩浆这些当"不可靠近"：扫帚/空袭/飞行跟随的航段会主动绕开危险方块，滑翔下沉到危险方块上方时不再往下沉】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话：「女仆在飞行的时候应该要尝试避开危险的环境，比如岩浆这些，将这些视为不可靠近的地区。」

### 一、缺口在哪：危险方块这一套地面有、空中一个字都没有

地面的那套（`misc.dangerBlocks` 表 + 寻路层把节点改判 BLOCKED + 险境脱离 + 自保的"附近岩浆感知"）**全部挂在走路/寻路上**；`SelfPreservationBehavior` 里那句注释写得很直白：「放在岩浆扫描之前：**空中不需要岩浆避让**，顺带省掉方块扫描」。于是她一旦离地（扫帚模式自己驾扫帚、空袭/飞行跟随在滑翔），危险表整个失效——她会贴着岩浆湖面盘旋、一头扎进去。

### 二、改法（两树镜像）：新类 MaidFlightHazardGuard + 三处接入

- **危险判据复用地面那一份**：`DangerBlocks.cellDangerous`（站立格本体 / 脚下一格 / 头顶灼烧型），玩家在 `misc.dangerBlocks` 里加一项，飞行这一侧立刻跟着生效。
- ① **扫帚模式**：`MaidBroomDrive.steerTo` 在"卡墙脱困"之后、算速度之前，把 `aim` 过一次 `detour`。
- ② **飞行跟随 / 坐标飞行**：`MaidFlightFollowBehavior.faceToward` 里喂给机头的那个点先过 `detour`（滑翔的操纵杆就是视线，改视线方向就是避让）；LookControl 仍指向原目标。
- ③ **远程空袭的盘旋/掉高**：`MaidFlightCombatBehavior.tickRangedAir` 末尾加一句 `antiSink`——只压竖直分量，不改朝向。
- 绕行算法：沿航段采样（步长 0.75 格、最多 4.5 格），命中危险格就按 ±35°/±70°/±110° 找方向（**带 3 秒粘滞**，防左右横跳），全被堵死就向上抬 2 格爬过去；**已经身处危险格里时原样放行**（那一档交给自保的岩浆逃生/卡墙顶出，不抢活）。
- 新增配置键 `combat.flightDangerAvoid`（默认开）+ 面板行「飞行危险环境避让」+ 中英 lang。

### 三、边界（写清楚，不改的东西）

- **攻击动作不避让**：近战空袭的收翅俯冲、远程空袭的俯冲助推是"朝目标去"的设计动作，照旧冲（这些分支各自 return，走不到 `antiSink`）。避让只管**飞行途中**的巡航/盘旋/跟随/掉高。
- 只在"她自己在飞"时生效：拴绳操控（玩家的绳子就是操纵杆）不干预；已在危险格里不干预。
- 开关关掉 = 飞行完全不看危险方块（旧行为一字不差）。

### 四、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增 mixin 注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
- 落地验收：让她骑扫帚/空袭掠过岩浆湖——旧版会贴上去甚至扎进去，现在会侧向绕开或抬升；在岩浆湖正上方掉高时不再往下沉。



## 实测六百九十五【卓越前线的「有弹药」这一条也交给它自己：远程空袭 / 扫帚的换弹门现在两家枪各问各的（TACZ 的 AbstractGunItem.canReload + 卓越前线的 GunData.shouldStartReloading——与 TLM 自己的卓越前线换弹链路用的是同一个方法）；顺带把「激活条件」那几处文案改成新口径】（版本号不变，仍是 v1.3.0 beta）
> 玩家原话：「现在我把卓越前线也下过来了，你也改一改吧。顺便改一下激活状态对应文本。」
### 一、还有一半没交出去（实测六百九十四 的尾巴）
实测六百九十四 只把 **TACZ** 那一半的「换得上弹」交给了它自己（`AbstractGunItem.canReload`）。卓越前线（SBW）当时本机未装、无法 javap 实证，所以它对 SBW 枪仍走老的 `looseAmmoScan`——**「任意 5 类 SBW 弹药之一都算」、同样不看口径**。同一个缺口，只是换了一家枪。
现在两台实例的卓越前线 0.8.9.1 都装上了（1.20.1 `hotfix-993063bed-all` / 1.21.1 `final-5b92ebe1`），`javap` 实证到位，这一半也交出去。
### 二、改法（两树镜像）：卓越前线的「换得上弹」
`GunCompat.canReload` 现在这样分流：
- **卓越前线枪** → 原样调用 `GunData.shouldStartReloading(Entity)`；
- **TACZ 枪** → 原样调用 `AbstractGunItem.canReload(shooter, gun)`（实测六百九十四，一字未改）；
- **能量武器**（二次灾变 / 超级星星炮，不吃常规弹药、内部充能）→ 照旧**直接放行**；
- 两家都**反射不可用**（没装 / 版本改名）才回退 `looseAmmoScan`（老口径，行为与旧版一字不差）。
`javap` 实证（1.20.1 与 1.21.1 两版签名一致、字节码逐句相同）：
```
GunData.shouldStartReloading(entity)
= !reloading() && !useBackpackAmmo() && !hasEnoughAmmoToShoot(entity)
&& hasBackupAmmo(entity)
GunData.hasBackupAmmo(entity) = countBackupAmmo(entity) > 0
GunData.countBackupAmmo(entity):
创造玩家 / 创造弹药箱  → Integer.MAX_VALUE（无限，永远换得上）
否则                  → countBackupAmmoItem(entity) × AmmoConsumer.getLoadAmount()
+ virtualAmmo
其中 countBackupAmmoItem → 该枪 selectedAmmoConsumer().count(gunData, entity)：
**只数这把枪自己认的那一类弹药**（口径必须对得上），纯只读计数、不扣物品。
```
**为什么用它、而不是裸的 `hasBackupAmmo`**：它与 TACZ 侧的 `AbstractGunItem.canReload` 是同一个角色——**开火链路真正会去用的那个判定**。TLM 自己的卓越前线换弹链路 `SWarfareCompatInner.doGunReload(女仆, gunData)` 查的就是 `shouldStartReloading`（真 → `startReload()`；否则 `shouldStartBolt()` → `startBolt()`）。「门禁说能换弹」与「TLM 真的会去换弹」必须是同一句话，否则又会出现「判她齐备、她却永远在重装」。
新增反射句柄 `SBW_RELOAD_API` / `sbwReloadApi()` / `sbwCanReload()`；`looseAmmoScan` 保留为兜底。
**为什么不回退 实测六百九十二 / 六百九十四**：① `useBackpackAmmo()`（弹匣容量 ≤ 0、直接从背包吃弹的枪）那一支 `shouldStartReloading` 返回 false，但那些枪的「打得响」本来就把背包弹算进去（`currentAvailableAmmo` = `countBackupAmmo`），`canFeed` 已覆盖；② 弹匣满时它返回 false，而那一拍 `canFeed` 为真，「打得响 **或** 换得上」的或门照旧过；③ 创造弹药箱在 `countBackupAmmo` 里读作 `MAX_VALUE`，照旧无限。
### 三、顺带改的「激活条件」文案（两树镜像）
这次把**描述激活条件的地方**统一到新口径——原文案还写着「TACZ 自己判」，现在两家枪各问各的：
- **语言文件的任务说明**（中 / 英，两树各两份）：`task.maid_smart.broom.desc`（扫帚模式）与 `task.maid_smart.flight_ranged.desc`（远程空袭）——点明「枪械的子弹要**喂得上手里那把枪**，口径由枪械 mod 自己判」。
- **手册**（两树 `GuideChaptersFlight`）四处：「远程空袭还额外需要弹药」、「怎么算『齐备』」（扫帚那三条）、「『有弹药』这一条现在由枪械 mod 自己判」（标题与正文，并新增「卓越前线也照办」一条）、「双就绪」那一条的 ②。
- **手册**（两树 `GuideChaptersCombat`）一处：枪械弹药箱那一条的「六百九十四 起……交给 TACZ 自己判」→「交给枪械 mod 自己判（TACZ 与卓越前线各问各的）」。
### 四、验证与边界
- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增 mixin 注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar`/ `promaid-1.3.0-neoforge-1.21.1.jar`，**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
- `_vt695.py` 只读核对：两树 `GunCompat` 镜像（`sbwCanReload` / `SBW_RELOAD_API` / `shouldStartReloading` 都在、`canReload` 已按 SBW/TACZ 分流、能量枪先放行、旧的「`if (!isSbwGun(gun))` 只问 TACZ」写法已消失）；两树 `MaidFlightKit` 注释同步；lang 四条（两树 × 中英）都含新文案；手册两树 4+1 处都改到；旧的「由 TACZ 自己判」标题已不存在。
- `_jarchk695.py` jar 内容核对：两个 jar 的 `.class` 里都能搜到 `shouldStartReloading`、`com/atsuishio/superbwarfare/data/gun/GunData`，且旧行为不会出现。
- **边界如实写**：① 这一批只改「换得上弹」这一半，`canFeed`（打得响那一半）**一个字没动**；② 照旧是**只读查询**——SBW 自己给 `countBackupAmmo` 带了 10 tick 的记忆缓存，每 tick 问它不贵；③ 能量武器照旧免检，绝不因为换 API 把她们判成缺弹药；④ 卓越前线没有 TACZ 那种「散装弹药箱」物品，它的「无限」只有创造弹药箱一条，读作 `MAX_VALUE`。
- **最终仍要凭实测验收**：手里是卓越前线的步枪 / 狙击枪、背包里只有对不上口径的弹药时，远程空袭 / 扫帚模式**不再激活**并报缺弹药；背包里有对得上的散装弹（或创造弹药箱）时照旧激活。

## 实测六百九十四【模式门禁的「有弹药」这一条改成问 TACZ 自己：远程空袭 / 扫帚的「有弹药」不再只是“有远程武器 + 任意弹药”，而是原样调用 TACZ 的 AbstractGunItem.canReload（口径必须对上这把枪、弹药箱照认、坏枪与满匣顺带否掉）】（版本号不变，仍是 v1.3.0 beta）

> 玩家原话：「现在关于远程空袭模式和扫帚模式枪械的判定还是tacz原版能够开火的判定吗？我想的是现在对于允许激活的判定，不应该是单纯的远程武器加弹药（很简单，因为像冲锋枪跟狙击枪并不是只要有远程武器和弹药就行的），最好是走TACZ的API，发现手持武器对应的TACC端允许开火，那么就判定为武器和弹药方面为可通过的。」

### 一、问的就是这个缺口，而且它确实存在

「允许激活的判定」就是两模式共用的那道弹药门：`MaidFlightKit.hasAmmoForWeapon`（远程空袭走 `hasAmmoForRanged`、扫帚走 `MaidBroomKit.ammoOk`，两边都汇到这一处）。它是两半的或门：

- **已经是 TACZ 的那一半**：`GunCompat.canFeed`（实测六百六十六）——逐句拄 TACZ `LivingEntityShoot.shoot` 的 NO_AMMO 门（`useInventoryAmmo` / `hasInventoryAmmo(…, true)` / `getCurrentAmmoCount` / `hasBulletInBarrel`），问的是「**这一拍**扣执机打得响吗」。
- **还不是的那一半**：`GunCompat.canReload`（实测六百九十二 为了“弹匣空了别把她摇下扫帚”而加的）——它用的是 `looseAmmoScan`：主手 / 背包里有**任意** `tacz:ammo`（或对得上的弹药箱）就算，**不看口径**。这正是玩家说的「只要有远程武器和弹药就行」。
- **后果**：她背着一包步枪弹、手里却是狙击枪 / 重机枪 / 蓄力枪 → 判「备弹通过」→ 空袭 / 扫帚模式激活、气泡不报缺件、而**她一直绕圈不开火**（与 666 修过的「一直绕圈但不射击」同一类，只是从另一道门进来）。

### 二、改法（两树镜像）：把「换得上弹」交给 TACZ

- `GunCompat.canReload` 改成：TACZ 枪 → 原样调用 `AbstractGunItem.canReload(shooter, gun)`；只有**反射不可用**（返回 null）才回退 `looseAmmoScan`。TACZ 明确回答 false（口径对不上）时**必须**算 false，绝不回退。
- javap 实证（1.20.1 与 1.21.1 两版 1.1.8，签名一致、字节码逐句相同）它查的是：① `getCommonGunIndex(getGunId(gun))` 为空 → false（坏枪 / 枪包数据缺失）；② 弹匣已满（`getCurrentAmmoCount >= 附件加成后的上限`）→ false（那一拍 canFeed 已为真）；③ `useInventoryAmmo` → false（那类枪归 canFeed 的 hasInventoryAmmo 管）；④ `reloadData.isInfinite()` → true；⑤ 假弹 `getDummyAmmoAmount > 0`；⑥ 否则扫 shooter 的 ITEM_HANDLER（女仆 = 护甲 + 双手 + 背包 + 饰品 四合一）：`IAmmo.isAmmoOfGun(gun, stack)` **或** `IAmmoBox.isAmmoBoxOfGun(gun, stack)`。
- 第 ⑥ 步的 lambda（`lambda$canReload$1`）与 `hasInventoryAmmo` 的扫法**逐句相同**，是纯只读的 `getStackInSlot` 循环（不 extract、不改 NBT）——**每 tick 调用无副作用**（javap 实证）。
- 新增反射句柄 `TACZ_RELOAD_API` / `taczReloadApi()` / `taczCanReload()`；`looseAmmoScan` 保留为兜底（没装 TACZ / 版本改名时行为与旧版一字不差）。SBW 没有对应的公开查询方法（本机未装、无法 javap 实证）→ 仍走 looseAmmoScan，其「打得响」那一半仍由 666 的 `hasEnoughAmmoToShoot` 管。

### 三、为什么这不回退 实测六百九十二

692 的诉求是「弹匣打空的那一拍别把她从扫帚上摔下来」。`canReload` 第 ⑥ 步**同时认弹药箱**（`tacz:ammo_box`，玩家后勤主流形态），且弹匣满时那一拍 `canFeed` 为真——「打得响 **或** 换得上」的或门照旧过。被新口径挡掉的只有「带的是对不上口径的弹药」，那本来就不该让她起飞（起了也打不响）。

### 四、验证与边界

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`，**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
- `_vt694.py` 只读核对：新增反射段两树**逐字镜像**；`canReload` 真的调 `taczCanReload`，且「TACZ 明确 false 不回退」；neo 新块零 SRG 残留、forge 新块零官方名；javap 复验两版 tacz jar 里 `canReload(LE,IS)` 都在；门禁调用点仍是 `canFeed || canReload`；此外 CJK 注释行两树逐行一致（GunCompat 172/172）。
- `_jarchk694.py` jar 内容核对：两个 jar 里都能搜到新符号与新文案（`AbstractGunItem` / `TACZ_RELOAD_API` / `taczCanReload` / `canReload`），且旧行为残留（`canReload` 函数体里只剩一处 `looseAmmoScan` 兜底）。
- **边界如实写**：① 这一批只改「有弹药」这一条判据，弓 / 鸢（数箭 / 烟花）与御币 / 三叉戟（免检）一个字没动；② 「这一拍打得响」那半（`canFeed`）仍是 666 的口径，未动；③ 自主战斗切枪械池的闸（`hasGunAndAmmo`）**故意仍只认 canFeed**（进池 = 当场打得响），未纳入本批；④ 没装 TACZ / 反射取不到 `canReload` 时整条回退旧口径，不会因为兼容层异常把模式踢掉。
- **最终仍要凭实测验收**：她手里是冲锋枪 / 狙击枪、背包里只有「对不上口径」的弹药时，模式应当**不再激活**并报缺弹药；背包里有对得上口径的弹药或弹药箱时，照旧激活。

## 实测六百九十三【锁敌后的「随机环绕路径」：盘旋半径改成每只女仆各自随机缓动 + 旋向按 UUID 对半（一半顺一半逆）+ 新增「离敌最远距离」硬上界——扫帚模式与远程空袭两条环绕链路同时改（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话：「可以把环绕型攻击方式更改一下，这样环绕的话会增加被击中的概率，会导致消耗量增加，敌人如果攻击的是第一只的话，会攻击到后面的，锁敌之后攻击敌人的飞行路径改成随机吧，然后设一个锁敌之后离敌的最远距离，狐狐被击中的概率或许就降低不少。」

### 一、根因是几何，不是运气

「被击中的概率」里有一份不来自「她贴得近」，而来自「她和同伴在同一条线上」：

- **旧版她绕的是一个固定圆**：半径写死取配置（扫帚 `combat.broom.range` 默认 8 格 / 远程空袭 `airRaid.orbitRadius` 默认 10 格），切向又是**同一个固定方向**——`MaidBroomDrive.combatPoint` 的相位每 tick 恒 `+ ORBIT_SPEED / r`，`MaidFlightCombatBehavior.faceOrbit` 的切向恒 `(-uz, ux)`。
- **691 的相位错开只解决了「起点」**：`phaseOf` 让同一批女仆不必从 0 相位起手，但跑起来仍然落在**同一个圆、同一个高度**上、朝**同一个方向**转。
- 于是敌人朝最近那只射一箭，**射线穿过这个圆还会打到对面那只**；近战横扫同理。这正是玩家说的「攻击的是第一只的话，会攻击到后面的」。

### 二、改法（两树镜像）

新增 `com.maidsmart.combat.CombatOrbit`（纯数学、无 Minecraft 依赖，两树**逐字节相同**，md5 `91074505b658eb9db848d6fcdc17dac6`）：

- **半径**（`CombatOrbit.radius`）：每只女仆在区间 `[基础盘旋距离 × 0.75, 离敌最远距离]` 里取一个由 **UUID 派生**的比例，每 80 tick（4 秒）重掷一次、每次只挪 0.35 量程以内、每 tick 最多滑 0.004——路径是**飘**的，不是每 tick 抖一次（后者会把枪口甩散、看着像故障）。
- **旋向**（`CombatOrbit.direction`）：由 UUID 派生，`+1` 逆时针 / `-1` 顺时针**对半分**。半径随机只解决「不在同一个圆上」，旋向对半才让两只女仆是**对穿**而不是首尾相接。
- **最远距离 = 硬上界**：`hi` 既是随机区间的顶点，也是硬牵引的界。空袭侧 `faceOrbit` 在 `r > hi` 时把径向增益从 0.5 提到 1.0（加倍往回带）；扫帚侧目标点半径恒 ≤ `hi`，由 `steerTo` 拉回。
- **不碰高度**：扫帚的盘旋高度仍由 `COMBAT_ALT`（爬升相位定下的「这一场遭遇的高度」）全权决定，空袭仍由 `rangedHoldHeight` + 掉高补推决定——把高度也随机化会与那两套「只升不降 / 掉高才补」的口径打架。
- **接入点**：扫帚 `MaidBroomDrive.combatPoint`（半径与 `dir`）、远程空袭 `MaidFlightCombatBehavior.faceOrbit`（`tx/tz` 乘 `dir`、`wantR` 替 `orbitRadius()`）；状态清理三处齐全（`MaidBroomDrive.forgetMaid/clearAll`、`MaidFlightCombatBehavior.forget/clearAll`）。

### 三、配置与文案

新增两个数值键（两树 × 配置 / 面板 / lang 中英，`defineInRange` 与默认值）：

- `combat.broom.orbitMax`「离敌最远距离（格）」，默认 **10**（= 盘旋距离 8 的 1.25 倍，均值仍落在 8 上；区间默认 6~10），范围 1~48。
- `airRaid.orbitMax`「离敌最远距离（格）」，默认 **12**（= 盘旋半径 10 的 1.2 倍；区间默认 7.5~12），范围 2~64。
- 面板：空袭数值 → ③ 盘旋与补高 多一行「离敌最远距离（格）」；扫帚模式 多一行「扫帚·离敌最远距离（格）」（该页由十一项变十二项）。
- 手册（两树 `GuideChaptersFlight`）：远程空袭完整链路 ② 改写 + 新增「为什么半径要随机」一条；扫帚章新增一节「锁敌后的环绕路径：半径随机 + 旋向对半」；两处面板清单同步。
- **日志留痕**：日志搜「随机环绕」——每一轮起手写一行，带这一轮抽到的半径、区间与旋向（`MaidBroomDrive` 走「扫帚模式」分类，`MaidFlightCombatBehavior` 走「远程空袭」分类）。

### 四、验证与边界

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点，`CombatOrbit` 不是 mixin）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar`/ `promaid-1.3.0-neoforge-1.21.1.jar`，**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
- `_vt693.py` 只读核对：两树 `CombatOrbit.java` md5 相等；两个驱动都已接进随机半径 / 旋向 / 上界；两个配置键两树都在（声明 + defineInRange + 面板行 + lang 四条）；旧写法（`+ ORBIT_SPEED / r` 不带 `dir`、`(r - orbitRadius()) * 0.5` 无上界）**已不存在**。
- `_jarchk693.py` jar 内容核对：两个 jar 里都能搜到 `CombatOrbit`、`随机环绕`、`离敌最远距离`、`orbitMax`，且旧文案不在。
- **边界如实写**：① 这一批只动「绕多远、往哪边绕」，**高度一个字没动**（理由见上）；② 半径随机是**缓动**不是瞬移——观感上应是「她在换位置」，若看着像抽搐就是 `DRIFT_STEP` 的问题，报个数我调；③ 均值刻意与旧版对齐（扫帚 8 / 空袭 10），所以「平均距离」没变，变的是「分布与不可预测性」；④ 默认值下随机区间的近端是基础距离的 0.75（扫帚 6 / 空袭 7.5），若不想要「偶尔贴得更近」，把「离敌最远距离」调到基础距离以上、再把「盘旋距离」本身调大即可。
- **最终仍要凭实测验收**：多只女仆（尤其狐狐）同时接敌时，还串不串、被击中的频率降没降。

## 实测六百九十二【三条反馈：① 守家时两条牵引绳拉回的是「家」不是主人（空袭/扫帚都一样）② 冲锋枪/狙击枪弹匣一空就把她从扫帚上摔下来（弹药不再算下鞍理由 + 换弹口径拆两档）③ 守家盘旋高度会被「躲建筑」一路抬上去（找不到地只降不升 + 脱困不许爬过目标高度）（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话三条：①「Home模式下，空袭牵引绳还在发力。女仆离了主人100格之后，还是会被传送回来。」②「扫帚模式如果女仆持有的是冲锋枪或者狙击枪，这两类枪械很容易飞着飞着就突然从扫帚上掉下来。可能是被敌人打下来的，可以查一下游戏日志，其他的枪械种类似乎就不会出现这种情况，非常的诡异。」③「女仆在处于home模式的状态下最好还是要尝试让自己的高度尽可能保持启动盘旋的高度（躲建筑只是暂时调整高度）。防止女仆在躲避其他建筑物的时候越飞越高。如果女仆飞得太高了，那么虽然会在那个位置上盘旋，但是之后的攻击、链路等方面就都不会触发了。」

### 一、三条各自的根因（**这一批三条全部有你自己的游戏日志逐行实证**，不是推测）

游戏日志（`1.21.1-NeoForge` 实例，2026-09-27 01:32–01:39 那一局，她手里是 `tacz:modern_kinetic_gun`）：

- **① 守家时被拽回主人身边**：`[2026-09-27 01:32:22] [扫帚牵引绳] 圣女酒狐 距主人 100 格（> 100 格），已连人带扫帚传送回主人身边` —— 而同一分钟里她身上挂的是 `[扫帚守家] … 沿工作范围盘旋 || home=true 活动=工作 圈心=(211,-61,475) 半径=15.0`。**她正在守家巡逻，主人跑出 100 格，这条绳就把她和扫帚一起搬到主人那边 100 格外**；随后 5 分钟她都在主人基地里 `被方块顶住 → 先飘到最近的空气格`，再也回不到圈里（日志里那串 Y 从 15 一路被垫到 40 多）。
  **根因是旧版刻意「不看 home 模式」**：那两条牵引绳（空袭 / 扫帚）的距离基准与落点都写死成"主人"，理由是"排班的自动锚定 home，查 home 会变成排班女仆永远召不回"。那条担心是真的，但解法不该是"无视 home"——应该是**守家时把参照点换成她的工作区**。
- **② 弹匣一空就从天上掉下来**：`[01:38:09] [远程开火] 精灵酒狐 主手=tacz:modern_kinetic_gun 距敌 14.93 格 开镜=true TLM档=true 弹=无` 紧接着 `[01:38:09] [扫帚模式] 精灵酒狐 下扫帚（理由=缺远程武器/弹药，这把是世界里那把，原地留着）`——**同一 tick、因果相邻**。同一分钟里「骑上 → 下」循环了 7 次（每次落地后 TLM 的枪械链路给她换弹 → 判定又齐备 → 再骑上，约 5 秒一轮）。
  **根因**：旧版拿 `MaidBroomKit.isModeActive`（扫帚 + 远程武器 + **弹药**）当**逐 tick 的下鞍判据**，而弹药那一条对 TACZ 枪问的是"**这一拍弹匣里还有没有弹**"（`GunCompat.canFeed` → `getCurrentAmmoCount>0 || hasBulletInBarrel`；因为 `useInventoryAmmo=false`）。冲锋枪（射速高、一梭子秒空）与狙击枪（弹匣小 + 栓动）最先撞上那一拍；**她的背包里明明有 `tacz:ammo_box`**（同一份日志的背包行写着），只是弹匣空了。而她是扫帚的乘客，下鞍 = 从空中掉下去。
- **③ 守家高度被"躲建筑"抬上去**：日志里 `[扫帚卡墙] … 被方块顶住不动了 → 先飘到最近的空气格 (210,15,525)` → `(202,19,522)` → `(213,25,515)` → `(202,27,513)` → `(210,29,511)` → `(212,31,509)` → `(202,44,498)` ……**Y 一次比一次高**（01:32:26 → 01:32:58 那半分钟）。
  **根因有两处，都在 691 新写的那几行里**：① `homeOrbitPoint` 的高度是 `groundY(...) + homeAltCfg()`，而 `groundY` **找不到地面时返回的正是"她当前的高度"** → 她比地形高出 24 格以上（或悬在虚空上）时，目标高度 = **她自己 + 8 格**，每拍往上加一次 = 无上限爬升；② 脱困取格的"离目标更近"按 3D 距离比、而 `goal` 的 Y 取的是**她当前那一格**，所以"往上挪"与"往旁边挪"等价——楼里横向被堵死时只剩"往上"可选，一次 +2 格、卡一次长一次。玩家点名的后果也成立：飞太高之后目标在下方出了索敌球、离参照点又超了牵引绳距离，**攻击与链路一起失效**。

### 二、改了什么（两树镜像，SRG / 官方名各写各的）

- **① 守家时的参照点与落点 = 她的工作区圈心**（`com.maidsmart.follow.WorkAreaClamp.homeAnchor`，口径只此一处）：
  - `MaidBroomRecall.tick` / `MaidFlightRecall.tick`：`homeAnchor(maid) != null` 时，**距离基准换成圈心**（"离**家**超过 N 格才拉"）、**落点也换成圈心**（"拉也是拉回家"），并且**这一档不再要求主人存活**（圈心是实体数据里的坐标，主人死了/下线了守家的她也该回岗位）；非守家（跟随）时一字未改，仍按主人算。
  - 传送入口新增两条（与旧路**同一段搬运代码**，只有落点算的锚点不同）：`MaidChunkLoadManager.recallFromFlightTo(maid, anchor)` / `recallBroomRiderTo(maid, anchor)`；落点算法抽出 `standSpotAt(dest, anchor, force)`（`standSpot` 现在委托给它），"解开乘客关系 → 搬扫帚 → 搬她 → 重新落座"抽出 `broomRiderTo(...)`。
  - 日志与聊天消息跟着分档：`距工作区 N 格（> N 格），已连人带扫帚传送回工作岗位` / `已传送回工作岗位`，系统消息改成「骑着扫帚飞离工作区 N 格…已连人带扫帚回到岗位上」。
  - 旧注释里那段"不看 home 模式"的理由整段改写（保留"排班自动锚定 home"这条事实，说明新解法是换参照点而不是无视 home）。
- **② 弹药不再是下鞍理由；"喂得上弹"拆成两档**：
  - `MaidBroomKit.canStayMounted(maid)` = 扫帚（物品或已骑）+ 远程武器，**不含弹药**；`isModeActive`（495 的"没弹药不必起飞"）一个字没动，继续当**起飞**门禁。
  - `MaidBroomBehavior` ③ 分成三档：**真的没有远程武器** → 下鞍（但要**连续 2 秒**都缺，`weaponlessLongEnough`，手上的武器被换走一两拍不该把她摔下去）；**还没骑上 + 缺弹药** → 照旧"没弹药不必起飞"；**已经骑在扫帚上 + 缺弹药** → **留在扫帚上**，只留一行限频日志（`noteOutOfAmmo`，日志搜「弹匣里没弹」）。
  - `GunCompat.canReload(maid, gun)` = "现在打不响但**换得上弹**"（背包里有这把枪认的弹药 / 对得上的弹药箱；口径就是实测五百六十八 那条 `looseAmmoScan`，不做第三份扫描）；`MaidFlightKit.hasAmmoForWeapon` 的枪械那一支改成 **`canFeed || canReload`**——**这一拍打得响 或 背包里有对得上的弹药**都算齐备。日志里那个 `弹=有/无` 仍用 `canFeed`（它回答的是"这一拍打不响吗"，两件事分开）。
  - 依据：TLM 的 `GunCommonUtil.performGunAttack` **自己就有** `ShootResult.NO_AMMO → IGunOperator.reload()` 那一支（javap 实证，那正是我们开火链路每发都在调的方法），所以留在扫帚上时换弹会自己发生。
- **③ 守家高度只降不升 + 脱困不许爬过目标高度**：
  - `MaidBroomDrive.groundYOrNaN(...)`：**找不到地面返回 `NaN`**（旧版返回"她当前高度"，调用方紧接着 `+ homeAlt` 就是这么变成每拍 +8 的）。
  - 新增 `HOME_ALT`（每条女仆"**这场守家巡逻的高度**"记忆 + 当时的圈心，`PatrolAlt` 小类；换任务/换圈心/服务端停止都会清）。`homeOrbitPoint`：找得到地 → `地 + homeAlt`（并刷新记忆）；**找不到地 → `min(她当前高度, 记忆)`，只准降回那个高度，绝不再往上加**（没有记忆就停在现在的高度）。
  - `nearestEscapeCell` 新增第 ⑤ 条判据：候选格的 Y **不许高过目标高度 + `ESCAPE_UP_MAX`（3 格）**——翻一道坎够用，但永远爬不过"该在的高度"。
- **配置注释 / 面板 / 手册三处口径同步**（lang 这一批**没有新增键**，所以四份 lang 一个字节没动）：两条牵引绳的配置注释与面板说明都改成"参照点=非守家主人 / 守家工作区圈心"；「扫帚·守家盘旋高度」的注释与面板补上"这个数是巡逻高度，躲建筑只是暂时偏离"与"找不到地只降不升、脱困不许超过 +3 格"；手册新增一节「空中高度：不越躲越高、不贴地乱窜」，并改写守家那一节、牵引绳那一节、排查清单加第 ⑥ 条（「她飞着飞着从扫帚上掉下来」怎么读日志），空袭那一节的牵引绳说明也改成参照点口径。

### 三、没做什么（如实写边界）

- **没动接敌高度与战斗盘旋**：`combat.broom.climb`（敌上 12 格、"本场只升不降"）一个字没改——那是**接敌**的高度意图，玩家这次的第三条说的是**守家巡逻**。
- **没做"弹尽自动降落"**：弹匣空了她会留在扫帚上（换弹由开火链路自己触发）；真的连背包都空了，她就继续飞、继续不开火（日志里有「弹匣里没弹」那行），**没有**新增"慢慢落回地面"的着陆链路——那是一条新行为，不在本批。
- **没给守家牵引绳新增配置**：参照点从"主人"换成"工作区"是同一条绳的语义修正，不新增开关；想彻底关掉仍是把 `combat.broom.recallDistance` / `combat.flightRecallDistance` 设 0。
- **没改 `isModeActive` 的起飞口径**、没改原版座位偏移、没动索敌与炸弹链路。
- **玩家手上那份 1.2.3 / 1.2.4b 不含本批修复**：要拿到得等 1.3.0 发出去（发布这件事我没有做）。

### 四、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 77/81 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar`/ `promaid-1.3.0-neoforge-1.21.1.jar`，**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对（match=True）。
- `_vt692.py` 只读核对 **PASS=143 FAIL=0**：15 段新代码/新文案**两树逐字镜像**（forge 过一遍名映射后与 neo 相等；两条牵引绳的 "displayClientMessage/sendSystemMessage"、"Component.literal" 那几处两树本就各写各的，核对时按同义归一处理），neo 侧代码里零 SRG 残留、forge 侧代码里零官方名；口径侧：`isModeActive` 不再出现在下鞍判据里、`canStayMounted` 不含弹药、`groundYOrNaN` 返回 NaN、`HOME_ALT` 三处清理齐全、脱困高度上限在、非守家回退分支与主人存活门都还在；文件卫生：12 个文件两树都无 BOM、全 LF（顺带把 3 份历史遗留的 CRLF forge 源码统一成 LF）。
- `_jarchk692.py` jar 内容核对 **PASS=60 FAIL=0**：两个 jar 里都能搜到这一批的新文案与符号（`距工作区 `/`工作岗位`/`回到岗位上`/`飞离工作区`/`recallFromFlightTo`/`recallBroomRiderTo`/`standSpotAt`/`broomRiderTo`/`homeAnchor`/`canStayMounted`/`canReload`/`弹匣里没弹`/`groundYOrNaN`/`PatrolAlt`/`HOME_ALT`/手册那一节 …），旧文案（`已连人带扫帚传送回主人身边` / `已传送回主人身边` / 手册里"飞太高"那两句旧说法）**已从包里消失**。
- 两个专用服（1.20.1 Forge / 1.21.1 NeoForge）启动实测 **PASS**（`Done` 后再盯 30 秒无异常）。
- **这三条最终仍要凭实测手感验收**：守家时你走远她还跟不跟得来（不跟才是对的）、冲锋枪/狙击枪打空弹匣还掉不掉、守家巡逻还越不越飞越高。代码侧能保证的是"判据与证据链成立，且你日志里那三条都逐行对上了"。


## 实测六百九十一【扫帚模式三修：空中寻路不再「钻一格窒息」（改按她身体的碰撞箱逐格判 + 死洞不选 + 5 秒内不重复登入）、多女仆不再叠罗汉（绕圈相位按 UUID 错开 + 邻近互斥）、守家盘旋不再贴地（新增「守家盘旋高度」默认离地 8 格）（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话三条：①「女仆的扫帚模式对于空气的寻路不行，很多时候只往一格里面钻，导致女仆窒息。应该要加一些额外条件的，而不是单纯的拿一格来进行判定，就算要拿一格判定，也是拿那个窒息的那个格子来判定。而且防止女仆卡死，所以刚刚寻路并且到达过的空气方块在5秒之内就不应该再次登进去了。防止女仆在两个空气格子之间来回位于反复横跳。」②「观感方面不太好，多个女仆乘着扫帚飞行的时候，很容易出现叠罗汉的情况。」③「我之前提到过，home模式下女仆会进行飞行盘旋巡逻对吧？但是女仆很喜欢贴地飞行。这个观感太差了。」

### 一、三条各自的根因（javap 反编译实证，不是推测）

- **① 脱困"钻一格窒息"：旧判据判的是「扫帚那一格」，而窒息的是「她」**。旧 `nearestAirAhead` 只有两个条件：候选格是空气、**它上面一格**也是空气。可她是**乘客**：原版 `positionRider` 把女仆的座位摆在扫帚**朝向的后方 0.5 格**（字节码实证：1.20.1 `EntityBroom.m_19956_` 与 1.21.1 `EntityBroom.getPassengerAttachmentPoint` 两份都是 `-0.5` 再按扫帚 yaw 旋转；竖直方向 1.21.1 是 `-0.3125`，1.20.1 取 `m_6048_() + 乘客.m_6049_()`——而扫帚自己的 `m_6048_()` 反汇编就是 `return 0.0`、女仆也没覆写 `m_6049_()`，所以 1.20.1 是 0.0。**两个版本都落在同一格**，差 0.31 格不会跨格）。她的碰撞箱是 `EntityType.sized(0.6f, 1.5f)`（宽 0.6 / 高 1.5）。
- **为什么"扫帚放得下"不等于"她放得下"**：乘客的位置是原版 `positionRider` **直接摆上去的**（默认走 `Entity::setPos`，**不做碰撞解算**）——扫帚本体靠 `move()` 的碰撞停在墙外，而挂在她朝向后方半格的**她**可以正好被按进墙里，每 tick 摆一次、每 tick 扣一次窒息伤害。**一格宽的墙洞 / 单格空腔最容易出事**：她离格边界正好半格，身体会**同时压住候选格与来路那一格**，而旧判据只看候选格那两格。
- **② 叠罗汉：两个盘旋相位的起点都写死 0.0**。`ORBIT.getOrDefault(id, 0.0)`（绕敌人）与 `HOME_ORBIT.getOrDefault(id, 0.0)`（绕工作范围）起手相位都是 0，而角速度又是同一个 `ORBIT_SPEED / 半径`——同一时刻进场、半径相同的两只女仆**目标点逐 tick 完全重合**：绕同一个圆心、永远是同一个点；而且因为角速度一样，她们会**一直**粘在一起飞（不是"偶尔碰上"）。
- **③ 守家贴地：这一档压根没人管高度**。`homeOrbitPoint` 的目标高度取的是**扫帚当前高度**（`p.f_82480_`），而起飞相位只抬 `RISE_BLOCKS = 1` 格——她整场守家巡逻就贴在离地一格飞。旧注释里那句「【高度不动】…她没有纵向语义」正是这条的来历：工作范围确实只是个水平圆，但"多高"这件事因此完全空着。

### 二、改了什么（两树镜像，SRG / 官方名各写各的）

- **① 脱困选格从"两条件"扩到"四条件"**（方法改名为 `nearestEscapeCell`）：① 扫帚那一格两格空气（照旧）；② **她身体的每一格都是空气**——新增 `bodyFits()`：按上面那条座位偏移（后方 0.5 / 下沉 0.3125）把她 0.6×1.5 的碰撞箱**铺进方块网格逐格判**，这就是玩家点名的"拿那个窒息的那个格子来判定"；③ 不是"一格死洞"——新增 `isPocket()`（候选格与她身体那两层、东南西北四邻全是方块 → 进去了出不来，不选）；④ **5 秒内没去过**——`Stuck` 里新增"刚去过"名单（`visited`，按方块坐标打包成 long 做键，到 `gameTime + 100` 过期）。**一条都不合格就原地不动**并留一行日志（日志搜「扫帚卡墙」），绝不再往死洞里按她。"去过"的记账分成两种都记：**到达**（玩家点名的那条规则）与**超时没到**（"这一格她压根到不了"，下一拍也不该原样再撞一次）。
- **② 别叠罗汉**：新增 `phaseOf(maid)`——每只女仆一个**稳定**的盘旋相位（从 UUID 派生、同一只永远同一个值，不会每次进世界跳一下），两个盘旋相位的起点都换成它，同一批女仆天然错开一整圈；再叠一层 `separate(...)` **邻近互斥**写在 `steerTo` 里（"别叠罗汉"是横跨跟随 / 战斗盘旋 / 守家盘旋 / 原地悬停四种目标点的同一条口径，只此一处）：目标点附近有别的**载着女仆的扫帚**时把目标点朝远离她的方向推出去（默认间距 2 格、一次最多挪 1.5 格、只推水平——竖直那一份交给两个爬升相位；**只避让"女仆骑着的扫帚"**：空着的扫帚是她的目标、玩家自己骑的那把也不是她要叠的东西）。互斥作用在**夹进工作范围之前**，所以不会被推出圈外。
- **③ 守家盘旋高度**：新增配置 `combat.broom.homeAlt`（面板「移动与行为 → 扫帚模式 → 扫帚·守家盘旋高度（格）」，默认 **8**、可调 1~32，lang 四份 + 配置注释 + 手册同步）。目标高度 = `groundY(...)`（**她脚下那块地**：从她当前位置往下最多 24 格找第一块非空气方块，之上那一格就是"地面"；找不到 = 悬在虚空上 → 不动，绝不能给一个"往下几十格"的目标）+ `homeAltCfg()`，然后过 `safeY(...)`——从"想要的高度"往下找**她真的放得下**的最高一格（`y` 与 `y+1` 都是空气），一格都放不下就留在原高度（**绝不硬顶**）。为什么不复用「接敌爬升高度」那个数：那个是"**相对敌人**"（没有敌人时根本不存在那个参照物），这个是"**离地**"，两个参照系不同，合成一个数只会让其中一边永远不对。
- **顺带**（同一病根，一并收掉）：`followPoint`（跟主人）的高度也过 `safeY`——主人头顶有天花板时不再把"主人上方 2 格"那个**在方块里**的点塞给她。
- **手册**：「新建"空中避障：不钻一格、不叠罗汉、不贴地"一节」+ 守家那一节改成新口径 + 参数列表「共十项 → **共十一项**」并**顺手修掉那处过时数字**（接敌爬升高度那里还写着"默认 10"，实测六百八十六 起就是 12）+ 排查清单加第 ⑤ 条（「扫帚卡墙」那两行日志怎么读）。

### 三、没做什么（如实写边界）

- **没动原版座位偏移**（`positionRider` / `getPassengerAttachmentPoint`）：那是 TLM 的行为，本批只在"选落点"时把那个偏移**算进去**，一个字节都没改它。
- **没给多女仆加"编队"**：只做"别叠在同一点"（相位错开 + 互斥），不排阵型——玩家当初明确排除过编队（成本太高），这一批也不越界。
- **没动接敌高度、开火链路、索敌、牵引绳**：本批只碰"平时（没有敌人）"这三件事与"脱困选格"。
- **上一批那条边界照旧**：`followIfCrossDimension` 里的兜底 `catch (Exception ignored)` 仍然没动（那是另一件事）。
- **玩家手上那份 1.2.3 不含本批修复**：要拿到得等 1.3.0 发出去（发布这件事我没有做）。

### 四、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 77/81 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` 与 `promaid-1.3.0-neoforge-1.21.1.jar`，均**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对（match=True）（**精确字节数见仓库根的 CHANGELOG.md——故意不写进游戏里的这份**）。
- `_vt691.py` 只读核对 **PASS=116 FAIL=0**：三段新代码（别叠罗汉+高度兜底 / 脱困选格 / Stuck 名单）**两树逐字镜像**（forge 过一遍名映射后与 neo 相等，且 neo 段内零 SRG 残留名、forge 段内零官方名）；旧 `nearestAirAhead` 与写死的 `0.0` 起点都已消失；四条选格判据、八个新常量、三处接入点（steerTo→separate、守家高度→groundY+homeAlt+safeY、followPoint→safeY）全部在位；配置/面板/lang/手册四处口径齐全（含"共十项"已消失）。
- `_jarchk691.py` jar 内容核对 **PASS=64 FAIL=0**：两个 jar 里都有 `MaidBroomDrive.class` 与 `MaidBroomDrive$Stuck.class`；**`javap` 直看包内字节码**——13 个新方法都在、`nearestAirAhead` 不在，且**调用链在字节码里**（`steerTo`→`separate`、`unstick`→`nearestEscapeCell`、`homeOrbitPoint`→`groundY`+`safeY`、`followPoint`→`safeY`、`combatPoint`→`phaseOf`）；`Stuck` 有 `recentlyVisited`/`remember`；中文文案直接查 class 常量池字节（面板新行在、旧文案「（高度保持不变）」没了、手册有「空中避障」/「共十一项」且「共十项」没了）；包内 `lang` json 有新键；包内 changelog 与源码那份逐字节一致。
- 两个专用服（1.20.1 Forge / 1.21.1 NeoForge）启动实测 **PASS**（`Done` 后再盯 30 秒无异常）。
- **这次三条只能靠实测手感验收**（她还会不会往一格洞里钻 / 多只女仆还叠不叠 / 守家还贴不贴地）——代码侧能保证的是"判据与证据链成立"，手感请以你实测为准。

## 实测六百九十【玩家崩溃报告修复：跨维度跟随扫描「边遍历原版实体表、边把女仆跨维度传送」= fastutil 迭代器槽位越界 → 整合服务端「Exception in server tick loop」整个崩回桌面（版本号不变，仍是 v1.3.0 beta）】

> 玩家发来的崩溃报告（`crash-2026-09-26_23.00.29-server.txt`，2026-09-26 23:00:29，跑的是已发布的 **1.2.3**）：
> `java.lang.ArrayIndexOutOfBoundsException: Index 877 out of bounds for length 513` → `it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap$MapIterator.nextEntry` → `…$ValueIterator.next` → `com.google.common.collect.Iterators$1.next` → `com.maidsmart.ProMaidExtension.onServerTick(ProMaidExtension.java:248)` → 「Exception in server tick loop」。同一 tick 里还有一条 `Index 544 out of bounds for length 513`，来自第三方模组 maidbeacon 挂在同名事件上的处理器（下面第三节说清它为什么没崩）。

### 一、根因（javap 反编译实证，不是推测）

- **那一层 for 遍历的是一张"活视图"，不是拷贝**：`onServerTick` 里 `for (Entity en : lvl.m_8583_())`（1.21.1 侧 `getAllEntities()`）——`ServerLevel.m_8583_()` 的字节码是 `LevelEntityGetterAdapter.getAll()` → `EntityLookup.getAllEntities()` = **`Iterables.unmodifiableIterable(byId.values())`**，而那个 `byId` 就是 `it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap`。**1.20.1 与 1.21.1 两版实现逐句一致（两版都核过 javap）**：也就是说，遍历期间原版实体表里加一个/删一个实体，手上那个迭代器就与表对不上了。
- **为什么会"越界"而不是"少看几个"**：fastutil 链式表的迭代器不按下标数组走，而是按**槽位号**在 `long[] link` 上跳——反编译 `MapIterator.nextEntry()` 的第一句就是 `next = (int) link[curr]`。实体表被结构性改动（增实体 / 删实体 / 换维度）之后，迭代器手里那个槽位与 link 里的链指针就跟当前数组对不上，下一跳直接落到数组长度之外：玩家这份报的是 `link[877]`，而当时那张表只有 513 个槽（`877 > 512`，是另一套表状态里的编号）。
- **触发它的是我们自己**：那一层循环体里调用的 `MaidChunkLoadManager.followIfCrossDimension(fm)` 在主人换了维度时会对女仆做**跨维度 `teleportTo`**——传送会把她从**本维度**的 `byId` 里删掉（日志同一毫秒就有「[promaid/离场] … reason=CHANGED_DIMENSION」），并顺带触发 `EntityLeaveLevelEvent` 事件链。**同一个迭代器紧接着往下读 → 越界 → 异常从 `onServerTick` 抛出去（那一层循环外面没有任何 try）→ 服务器 tick 循环整个崩掉**。
- **为什么以前没炸**：这条扫描每 5 秒才跑一次，而且只有「主人与女仆确实不在同一维度 + 主人身边有可站立点」时才真的传送。玩家那局一直在跨维度：22:59:54 还在刷「跨维度跟随：主人身边 16 格内无可站立点（高空/虚空）——等落地后再传」，23:00:29 落点一出现，传送立刻发生——正好落在遍历中间。
- **一个实证细节**：同一份日志里，事件总线把 `Index 877` 那条也记了一遍「Exception caught during firing event」；而 `Index 544` 那条（第三方模组的）之所以没带走服务器，是因为它被 `followIfCrossDimension` 自己的兜底 `catch (Exception ignored)` 吞掉了——**这恰好说明当时那次传送内部已经出过一次同样的越界**，我们只是"运气好"没抛出去。

### 二、改了什么（两树镜像；一处工具类 + 66 处调用）

- **新增 `com.maidsmart.tool.EntitySnapshot`（两树各一份，逐字一致，只差访问器名）**：`EntitySnapshot.of(ServerLevel)` 先把实体表**拷一份**再交给调用方遍历。语义是"这一趟看到的是这一趟开始时就在场的那批实体"——这正是本模组所有全量扫描循环原本的假设（下一趟扫描自然看到新出现的实体）；代价是一次 O(实体数) 的引用复制（微秒级，而这些循环本来就是 O(实体数) 的全量扫描，大多还是几十 tick 才跑一次）。工具类的 javadoc 里写全了上面第一节那套证据链。
- **两树各 33 处直接遍历全部换成快照（共 66 处）**：本模组**不再有任何一处**直接 `for (…)` 遍历原版实体活视图——覆盖 `ProMaidExtension`（3 处，含崩溃点）、`MaidChunkLoadManager`（4 处）、`AutoCombatSwitch`（302/314 行的大循环）、`GunnerTetherManager`、`FishingChairService`、`SchedulePacketsPlan`、`MaidPlanting`、`MaidHeldLight` 等 26 个文件。**不只修"看起来会增删实体"的那几处**：循环体里调用的任何代码都可能顺手改动实体表（我们自己的跨维传送 / 一键集合召回 / 掉物品清理，车万女仆的任务与 AI，其他模组挂在事件上的回调），靠逐处判断"我这条安不安全"是判不完的——所以口径统一成一句：**遍历实体表 = 先取快照**。
- **崩溃点①**（`ProMaidExtension.onServerTick` 的跨维跟随扫描）与**崩溃点②**（`MaidChunkLoadManager.tick` 的受困救援扫描，那里同样在循环里 `teleportCore` 跨维度搬人）都在代码里写明原因与本批编号，免得以后有人"顺手改回直接遍历"。
- **没有做**（如实写边界）：没动 `followIfCrossDimension` 里那个兜底 `catch (Exception ignored)`（吞异常不好，但改成不吞会让跨维跟随的每一种失败都变成崩服；那是另一件事，不在本批）；没动排班/召回/区块加载的触发时机与判据（本批只改"怎么遍历"，不改"什么时候传送"）；也没给第三方模组打补丁。

### 三、第三方那一半（maidbeacon），以及玩家可能还会看到的日志

- `Index 544 out of bounds for length 513` 的栈是 `maidbeacon@1.0.1-bugfix` → `com.mastermarisa.maidbeacon.event.MaidTracker.onEntityLeave` → `Iterables$UnmodifiableIterable.forEach` → 同一个 fastutil `nextEntry`：**它也在"实体离开维度"事件里遍历同一张活视图**，而那一刻正是我们这次传送把它删出本维度的时候。它的异常被 Forge 事件总线接住、只打了一条 ERROR（然后又落进我们的兜底 catch），**没有带走服务器**。
- **本批不改它、也不绕它**：那是它自己仓库里同一类问题的修法（同样"先取快照"就行）。修完本批之后玩家日志里**可能仍会看到那一条 maidbeacon 的 ERROR**——它只打日志、不会崩游戏；要不要报给那个模组的作者，由你决定。

### 四、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；出 `promaid-1.3.0-forge-1.20.1.jar` 与 `promaid-1.3.0-neoforge-1.21.1.jar` 均**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对（**精确字节数见仓库根的 CHANGELOG.md——故意不写进游戏里的这份**）。
- `_vt690.py` 只读核对 **PASS=21 FAIL=0**：两树工具类逐字一致、两树除工具类外**零处**直接遍历实体活视图、两树各 33 处快照、两个崩溃点都走快照且写明原因、`out_*` 里有 `EntitySnapshot.class`。
- `_jarchk690.py` jar 内容核对 **PASS=16 FAIL=0**：两个 jar 里都有 `EntitySnapshot.class`；**`javap` 直接看进包字节码**——`ProMaidExtension.onServerTick` 里有 `EntitySnapshot.of` 调用、**且不再直接调 `m_8583_`/`getAllEntities`**，`MaidChunkLoadManager.tick` 同样；26 个调用类（含内部类）都引用了新工具类；jar 内 changelog 与源码那份逐字节一致。
- 两个专用服（1.20.1 Forge / 1.21.1 NeoForge）启动实测 **PASS**（`Done` 后再盯 30 秒无异常）。

## 实测六百八十九【面板排版三修：武装拴绳那条 1000+ 字介绍拆成 4 条短行（旧版窄窗口下比一页还高，把第 1 页挤成只剩板块标题、正文压住翻页/保存按钮）+ 注释绘制加下界兜底 + markdown 星号不再原样画在界面上（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话：「现在已经相当完善了，唯一一个不好的就是手册的这几个页面的排版不行，首先一个是介绍过多，直接撑满了整个屏幕，需要精简化，然后就是一个屏幕太空了，里面啥东西都没有。」

### 一、两个症状是同一个根因（先说清楚，再改）

- **面板把「一行」当原子**：分页在 `sectionButtons` 里逐行累加真实高度，`yAcc + rh > contentBottom` 就整行挪到下一页；而画注释的 `drawComment` 一路画到底、**没有任何下界**。于是只要有一行的注释比**一页可用高**（`h - 76 - 52`）还高，就同时得到玩家截图里的两件事：① 它被整行挪到下一页，第 1 页只剩一条板块标题（「一个屏幕太空了」）；② 它独占的那一页从 `CONTENT_TOP` 往下画，越过 `contentBottom` 盖到翻页按钮（`h-68`）与保存按钮（`h-34`）上，再往下直接出屏（「撑满了整个屏幕」）。
- **只有这一行会这样**：把面板全部 485（1.20.1）/ 500（1.21.1）行的注释按面板自己的口径重算了一遍，只有「武装拴绳 · 功能详解」这一条超过一页（1.21.1 侧 1068 字 ≈ 22 行 ≈ 254px，而那种分辨率一页只有 165px）。它**只在窄窗口**下暴露（GUI scale 4，或窗口 GUI 高 < 352）；宽屏下正好放得下——这就是它一直没被发现的原因（宽屏/低 GUI scale 测不出来）。
- 代码里那句「逐页装填后每页都保证最后一行不超 contentBottom」的注释，正好被「单行比一页还高」这种情况打破：让超高行独占新页的那道判断是有意的，但它独占之后没有任何东西再管它。

### 二、改了什么（两树镜像）

- **介绍拆条（治本）**：那条 1068 字（1.20.1 侧 984 字）的长注释按主题拆成 **4 条独立行** —— `武装拴绳 · 是什么`（2 行注释）/ `什么时候能绑`（3 行）/ `怎么解除`（3 行）/ `画面上的三件事`（3 行），合计 **518 字**，每条注释 ≤3 行、行高 ≤64px，**谁都不可能再高过一页**。面板只留「看一行就够用」的部分：合成与日志关键词、绑/解规则、绳子不会自己断与免伤、画面三件事；开发史（「从一根直线换过来」「除数为 0」「隔远不再消失」那些）与实现细节留在手册那一章（那一章本来就比面板细，末尾还有跳回本页的入口）。
- **绘制兜底（治标）**：`drawComment` 增加下界 `h-76`（与分页同一个口径），真被截断就在末尾补一行「……（余下见手册）」。以后谁再写下长注释，最坏是被截断 + 指路，**不会再压按钮、不会再出屏**。
- **顺带的观感修复**：面板注释里的 markdown 强调标记 `**…**` 过去是**原样画出来的**（玩家截图里能看到「**已经骑在扫帚上**」），全面板 24（1.20.1）/ 25（1.21.1）行注释带这个标记。新增 `emphasis()`：成对标记就地转成金色（`§e…§r`），落单的星号照原样保留（否则一个 `§e` 会把后面整段染黄）。它只加在 `wrapComment` 一处，而高度计算与绘制都走这一处，口径不会脱节。
- **没有做**（如实写边界）：没有改分页模型本身（「让分页能拆行」要动 5~6k 行界面的核心分页、得整轮面板回归，收益只是「超长文本还能完整读到」——现在由「文案拆条 + 绘制截断指路」覆盖）；也没有动其它行的文案（第二长的是 1.21.1 侧那条「二号位重锤猛击」设置行，9 行 ≈ 114px，仍在一页之内）。

### 三、顺带修掉一处历史遗留（四份日志一起）

- **「实测六百八十六」的标题被顶在四份日志的第一行、正文却留在 687 之后**（六百八十八 那批插新条目时插到了标题与正文之间）：读起来是「第一条只有标题、686 的正文没有归属」，而且 687 的末行直接粘着 686 的正文。现在把标题移回它自己正文之前、并补上条目之间的空行；根 `CHANGELOG.md`、`store/version_changelog.md`、两份 jar 内 `changelog.txt` 四份同步。

### 四、验证

- 两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（本批没有新增注入点）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；`promaid-1.3.0-forge-1.20.1.jar` 与 `promaid-1.3.0-neoforge-1.21.1.jar` 均**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对尺寸（match=True）；`_vt689.py` 只读核对 **FAIL=0**（**精确字节数见仓库根的 CHANGELOG.md——故意不写进游戏里的这份**）。
- **排版回归（按面板口径模拟分页）**：「武装拴绳」板块在窄窗口（内容宽 448 / 一页 165px）从「第 1 页只有 1 行 18px」变成「第 1 页 3 行 146px」；全表**最高一行 104px（1.20.1）/ 114px（1.21.1）**，都低于一页可用高——空页与溢出这两个症状在测试的两种分辨率（448×293 / 544×360）下都不再出现。

## 实测六百八十八【撤掉「空袭爬升封顶」与「拴绳上飘 0.10 封顶」（需求方否决）+「本来要走过去的活」也交给创造飞行（PR #24 设计问题的答复落地）（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话三条：①「首先我没让你加爬升封顶，把这个给删了。」②「本来就走过去应该交给创造，我回复你的就是那个。（……既然引入了创造飞行这个概念，那么它无疑是搭方块的上位替代，有创造飞行能力，那就禁用搭路），反倒是你理解错了。」③「速度限制删掉（没在操控时上飘速度封顶 0.10 格/tick，这个不要）。」

### 一、撤掉「空袭爬升封顶」（实测六百八十七 刚加的那一条，整条移除）

- **移除清单**：配置 `airRaid.maxAlt`（原来默认 48）、面板「空袭 → 爬升上限（格，0=不限）」那一行、`MaidFlightCombatBehavior` 里的 `maxAlt()` / `overCeiling()` / `applyCeiling()` 三个成员与 `CEILING_AT` 限频表、`needClimb` 上那道 `!overCeiling(maid, target)` 条件、以及日志「顶到爬升上限」。一句话：这一条从代码、配置、面板、lang、手册里都不存在了。
- **如实写的边界（根因没有消失）**：玩家报的"一口气飞到 300 多格"的**根因一个字都没改**——空袭的高度判据仍然只有 `onTargetAltitude`（相对**目标**的 Y，没有任何绝对上限），目标自己会飞 / 会上升时她就跟着一路爬，烟花 / 激流三叉戟 / 孔雀羽扇 / 位移法术几路推力也照旧叠加。这批撤掉的只是"封顶"这个**手段**；留下的兜底是**拴绳操控**（实测六百八十七 那条，保留）：她丢了锁敌之后你把绳子举起来，方向就归你——低头 = 下降，一低头就能把她压回来。

### 二、撤掉「没在操控时的上飘封顶 0.10 格/tick」（拴绳悬挂档）

- **移除清单**：`MaidFlightFollowBehavior` 里的 `TETHER_MAX_RISE` 常量，与悬挂档那句 `Math.min(竖直速度, TETHER_MAX_RISE)`。
- **保留的两件**（它们是另一回事，不是速度封顶）：水平速度按 0.72 收干、俯仰摆平（低头滑翔会被原版滑翔物理一路往地里拽）。**竖直速度现在原样保留**——没在操控时她该滑翔就滑翔、该掉就掉，不再被我们削。

### 三、「本来要走过去的活」也交给创造飞行（PR #24 那个设计问题的答复：a 档）

- **需求方口径**：「本来就走过去应该交给创造。」上一批（六百八十七）已经按同一句话把**搭路**（垫方块过沟 / 上坡）整段让位了；这一条是它的**对偶面**——**不搭路，也不绕路**：要过去就直接飞过去。
- **落在哪一层**：她的移动有三条来源——①大脑的 `WALK_TARGET`（由 `MoveToTargetSink` 消费，最终走 `PathNavigation.moveTo(Path, double)`）；②**直连寻路**（挖矿 / 伐木 / 农活这些驱动每 tick 自己调 `moveTo(x, y, z, speed)`）；③别处直接写速度。"本来要走路过去的活"主要落在 ② 上（挖矿早在 v1.5.127 就改成不写 WALK_TARGET、改直连寻路），所以新注入点 `FreeFlightWalkGuardMixin` **只掐 ② 这一个重载**（`moveTo(DDDD)Z` 的 HEAD，返回 false = 这一发地面寻路不生效）——① 一个字都不碰（`MoveToTargetSink` 走的是 `moveTo(Path, double)`）。
- **接管条件（四条全中）**：①她有飞行资格（总闸 + 没被单独关掉 + 资格物品/效果/重力其一，且没坐着 / 没被骑 / 没在守家 / 不是空袭或扫帚任务）；②开关 `misc.freeFlightTravel` 开着（默认**开**）且她在**非战斗的工作任务**上；③那一格确实"费劲"：水平超过 `misc.freeFlightTravelDist`（默认 **8 格**）**或**高差超过 **2 格**（与跟随那条"高差 > 2 就起飞"刻意同口径）；④那一格**底下落得下去**（沟上 / 虚空上 / 水面上方**不接管**——那种路她原来怎么走现在还怎么走，别把"她自己会想办法"变成"我们把她悬在半空"）。
- **一趟飞到底**：接管看"够不够远"（8 格）、结束看"到没到"（1.5 格），中间那段靠"这趟已经在赶路"的标记继续接管——她不会飞 8 格再走最后一段，也不会半路被跟随逻辑拽回主人身边。降落到地方 → **软着陆**（保持无重力 0.12 格/tick）→ 落地即收工，把重力与控制权交还给她自己的任务（挖矿 / 伐木那一套照旧，她人已经站在工位上了）。
- **不接管的情况**（各有各的语义，一律不碰）：自保逃跑（落点是"贴着地面的安全点"）、战斗类任务的战术走位、刷怪笼插火把（那条链自己拥有走位，实测六百六十四）、站桩工作（建筑 / 烹饪 / 酿造）、idle / 跟随（跟随有它自己那套飞行跟随；散步 / 闲逛更不该变成飞）。
- **顺带修掉的一个往复**：「**在主人身边干活**」时不再做**跟随**起飞。不修的话会出现：落到工位 → 主人走两步她起飞去追 → 挖矿驱动发现她不在工位、又发一发远距离寻路 → 我们又把她飞回工位……来回摆。判据：她在非战斗工作任务上、主人水平 16 格内且高差 8 格内 ⇔ "她该在这儿干活"；主人真走远（> 16 格）或上天（高差 > 8 格）照旧跟上去——那也正是 TLM 换任务的时机。
- **配置 / 面板 / 日志**：`仿创造飞行·走路的活也交给飞`（BoolRow，默认开）+ `仿创造飞行·超过多远就改飞（格）`（NumRow，默认 8，范围 0~64）；日志搜「赶路」（同一只 5 秒最多一条）。
- **边界**：**只有 1.21.1 树有这一条**（仿创造飞行本身只有那边的实现，理由见 六百八十七 第一节）。

### 四、手册与验证

- **手册**：1.21.1 树的飞行章「仿创造飞行」一节补第六条「本来要走过去的活，也交给飞」（原第六条「排错与验收入口」顺延为七）；战斗章里 六百八十七 加的「空袭爬升上限」那一段**整段删除**。
- **验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=171 SKIP=9 FAIL=0**（新增注入点 `FreeFlightWalkGuardMixin` 的 `PathNavigation.moveTo(DDDD)Z` 由目标类自己声明）；打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；`promaid-1.3.0-forge-1.20.1.jar` 与
`promaid-1.3.0-neoforge-1.21.1.jar` 均**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对尺寸；
两个专用服务器（1.20.1 Forge 47.4.23 / 1.21.1 NeoForge 21.1.250）启动实测 **PASS**；`_vt688.py` 只读核对 **FAIL=0**
（**精确字节数见仓库根的 CHANGELOG.md——故意不写进游戏里的这份**）。

## 实测六百八十七【仿创造飞行并入（PR #24 本地修复版）+「有创造飞行能力就禁用搭路」+ 「丢锁敌后拴绳当操纵杆」+ 绑定 HUD 写细 + 服务端隐患一轮审计（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话四条：①「绑定空袭状态进行接敌的时候，有的时候玩家行为再加上女仆自身的冲锋行为等各方面叠加，
> 会导致女仆一口气直接飞到天上300多格。然后导致女仆飞在空中直接失去索敌，而玩家只能任由其在空中自由滑行，
> 一点办法都没有。扫帚模式可以因为玩家可以接管扫帚而进行补救。这边可能要给战术拴绳加一些特殊的效果。
> （仅限空袭模式）在手持此拴绳且处于绑定状态下的时候，如果女仆这个时候失去了锁敌。可以控制女仆飞行的方向。」
> ②「扫帚模式玩家绑定时HUD写详细一点。现在的就仅仅告诉你，右击之后可以进行切换，但是没有强调，
> 是拿了武装拴绳右击之后才行。这个会让玩家产生误解。」
> ③「PR和issues的问题，首先，既然引入了创造飞行这个概念，那么它无疑是搭方块的上位替代，
> 有创造飞行能力，那就禁用搭路。他的问题我们就在本地修复并部署，然后回答一下他的issues。
> 最后在手册内补充一下这个功能的使用教程及相关配置。」
> ④「这个模组有关服务端的优化真的太少了……等你把上面的功能做好之后，再想想服务端方面有什么隐患，并修复一下。」

### 一、仿创造飞行：把 PR #24 在本地修好之后并入 neo 树

贡献者（`rodericksthescriptkid`，分支 `feat/free-flight`，head `f1e92c1`）的功能本身是完整的：**给女仆一套「仿创造飞行」**——整合包里那些「创造飞行 / 悬浮」物品几乎只对玩家生效（写死 `instanceof Player`），本功能不去复刻每个物品的物理，而是「她有资格我们就托住她」：`setNoGravity(true)` + 每 tick 直接写速度。并入范围：12 个新文件 + 9 个既有文件的改动（**只有 1.21.1 树**，理由见本节末尾）+ 8 条配置 + 面板 8 行 + 两条命令 + per-maid 开关（配置界面一行 + 快捷键）+ 模型包 `fly` / `elytra_fly` 两个动画状态的注册。

**合入前我修掉的三处阻断项**（都是"会真的出事"那一类，不是风格问题）：

1. **客户端没有守卫**：`MaidTickEvent` **两侧都会发**（`EntityMaid.tick()` 在客户端也跑），而控制器里全是服务端写操作（`setNoGravity`、每 tick 写速度、清 brain 记忆、停导航）。旧写法会在客户端每 tick 抢写速度与朝向、与同步下来的位置打架。现在分侧闸在 `MaidFreeFlightController.tick()` 的第一行（单一口径，与 `GunnerTetherManager.onMaidTick` 的老注释同款）。
2. **存档里遗留的无重力没人交还**：`Entity#noGravity` **跟着实体 NBT 存盘**，而状态表只活在内存里。她若在飞行中存档退出 / 被卸载，下次进来 `STATE` 是空的、谁也不认领她 ⇒ **她带着 NoGravity 永远飘在那儿，叫不下来**（单机退出重进就能复现）。现在起飞时在 `persistentData` 里写一枚自己的标记，每 tick 第一件事做一次"对账"：没在管的她、却带着我们那枚标记 ⇒ 交还重力并记一行日志。只管得住**我们**留下的无重力，绝不误伤空袭 / 扫帚设的那些。
3. **软着陆超时那一下是"放手让她掉"**：旧版注释写"最长 120 tick"、代码却是 120 **秒**，而且到点**直接交还重力**——从她当时所在的任意高度自由落体、没有任何防摔兜底。现在超时只**提速**（20 秒后 0.12 → 0.48 格/tick），真正放手只在**她已经掉到世界底部以下**（虚空，那儿松开重力也摔不死）；`release()` 一律清一次坠落距离。

**顺带修掉的应修项**：命令补 `.requires(src -> src.hasPermission(2))`（仓库另两条命令同款——没有它，任何玩家都能把 64 格内任意女仆开到任意坐标）；`trackOwnerMoving` 改成只在"她真的可能飞"时才记（默认关的服务器上不再给全服女仆各记一份坐标）；控制器那十一张 UUID 表加**定期清扫**（5 分钟没露面就整键回收），`release()` 补清原先漏掉的 4 张表；网络层「离她太远」的文案与判据（64.0 = 8 格）对齐；mixin 清单三条新登记的缩进按本文件口径。

**如实写的两条边界**：①贡献者自带的 `CHANGELOG_FREE_FLIGHT.md` 没有并进来（它的编号与我们的 673~677 撞号，本仓库的更新记录只有这一套四份的写法），记录在本条里；②**1.20.1 Forge 树没有移植**——这份实现的三路资格里有两路在 1.20.1 根本不存在（`Attributes.GRAVITY` 与数据组件 `DataComponentType` 都是 1.20.5 之后才有的），网络层与动画注册 API 也不同（Forge `SimpleChannel` / 旧版 geckolib），所以这不是"照抄改个名"能完成的活，留作下一步等需求方定方向。

### 二、「有创造飞行能力，那就禁用搭路」

- `BridgeUpBehavior` 的两处让位点（`checkExtraStartConditions` 与 `canStillUse`）新增判据 `MaidFreeFlightController.bridgeDisabled(maid)`：**总开关开 + 这只没被单独关掉 + 资格物品/效果/重力其一** ⇒ 搭路整段让位。
- 与贡献者原判据的区别（他写的是 `isControlling`，只管"正在飞 / 正在软着陆"）：需求方口径是"**有能力**就别搭"，所以**落地待命档同样不搭**——她一动就会起飞，垫方块既慢又跟飞行抢移动。
- 这一条只在 1.21.1 树上有意义（仿创造飞行本身只有那边的实现）。

### 三、空袭：「丢锁敌之后，绳子就是操纵杆」

- **拴绳操控**（新配置 `combat.tetherLeashSteer`，默认开，面板「武装拴绳 → 悬挂时用武装拴绳操控方向」）：挂在她下方、**她此刻没有目标**、且**你手里拿着武装拴绳**（主手副手都行）时——**你的视线就是她的机头**：平视 = 平飞不掉高（每 tick 直接写速度，原版滑翔物理只负责这一 tick 的位移）、抬头 = 爬升、低头 = 下降，俯仰 ±60° 封顶，速度 0.30 格/tick（≈6 格/秒）。
- **为什么"没有目标"这个判据是免费的**：`tick()` 第一件事就是"挂着 → 悬停链路然后 return"，而 `canStillUse` 里 `threatNearby` / `ownFlightBusy` 都排在前面——**有敌人时跟随行为整个不跑**（控制权交回空袭链路）。所以她走到悬停链路 ⇔ 她此刻没有目标，正是需求方说的"失去锁敌"；不需要再去问一次目标（问了要动 brain 记忆，而结构上已经保证了）。

### 四、绑定 HUD 写细（两树）

- 「绑定中」下面从一行变两行：`§7手持【武装拴绳】右击女仆 = 绑定 / 解除（空手右击无效）` 与 `§8她骑扫帚时那一击 = 坐回驾驶位（主动骑乘）；空袭二号位时 = 解除`。
- 旧版只有一句「右击她：解除；她骑扫帚时改为主动骑乘」——**没写手里得拿着那根武装拴绳**，玩家空手右击发现没反应、或以为随便什么物品都行。第一行把「手持武装拴绳」摆在开头；第二行放不下时只留第一行，绝不顶出屏幕。

### 五、服务端一轮审计与修复

- **审计（对编译产物，两树全量 567 / 583 个类）**：`客户端类型出现在类的成员描述符里` = **0 个**——这正是 1.1.0 真炸过专用服务器的那一类（主类 lambda 的描述符里带 `Screen`，`RuntimeDistCleaner` 直接拒绝，`dist.isClient()` 也救不了）；含 `<clinit>` 的类（200 个）里没有一个静态初始化会带出客户端类型；`@SubscribeEvent` 的参数类型全是具体事件类（NeoForge 拒绝注册抽象事件类）；新加的两个网络包方向正确（C2S 用 `playToServer`、S2C 用 `playToClient`）。
- **修复**：仿创造飞行那套状态表的寿命问题（定期清扫 + 遗留无重力交还 + 轨迹只在该记的时候记，见第一节）；新增 `com.maidsmart.tool.StateTables` 护栏——**只增不减的 UUID 表**（那些"只在 tick 到当事人才会清"的日志限频 / 启发式计时 / 短暂宽限表）超过 2048 条就整表清空并记一行日志，挂在**武装拴绳 10 张 + 扫帚驱动 3 张 + 排班气泡 / 散步检查 / 烹饪 / 钓鱼 3 张**上。清空是安全的：这些表里没有一张存"行为必须一致"的状态（限频时间戳清掉 = 下次多打一条日志，退避计时清掉 = 重新起算）。必须精确的 `LINKS` 没走这条路——它本来就有 `detach` / `onMaidJoin` 两条清理链路。
- **如实写的边界（查出来但本次没动的）**：`getAllEntities()` 全图扫描有三处是**每 3 tick / 每 5 秒**一次（钓鱼保持座位、跨维度跟随、启动清标记）。前者在"女仆多 + 实体多"的服务器上是实打实的开销，但改动面大（要给它建"活跃钓鱼女仆"索引）、收益无实测数据支撑，本批不动，记在这里。

**手册**（两树）同步：战斗章「武装拴绳」补拴绳操控一段；1.21.1 树的飞行章补一节「仿创造飞行」使用教程（这是什么 / 资格三路与五种写法 / 智能待命 / per-maid 开关与快捷键 / 有飞行能力就不搭路 / 排错与两个专用服务器验收入口）。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=170 SKIP=9 FAIL=0**；服务端审计 dist **0 命中**；
打包门禁（`verify_jar_classes.py` + mixin 包登记 + lang json）全过；`promaid-1.3.0-forge-1.20.1.jar` 与
`promaid-1.3.0-neoforge-1.21.1.jar` 均**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods +
服务端 pack1201）并逐处核对 md5（**精确字节数见仓库根的 CHANGELOG.md——故意不写进游戏里的这份**）。

## 实测六百八十六【扫帚盘旋高度：默认 15 → 12，而且「只升不降」——平地也终于会爬满；重锤后门只留空袭、扫帚不开（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话两条：①「把扫帚盘旋的默认配置高度改为12格，而且我当时测试的环境是平地，但是女仆依然只是在比敌人稍微高一点点的地方进行盘旋，就是不升高自己的高度。」②「我说的重锤是指女仆在使用近战空袭的时候，同时带着玩家，玩家这个时候拿重锤进行砸击。这个时候会不会吃到重锤的加成效果以及附魔呢？不行的话，把这边的后门开了，扫帚模式不要开。」

### 一、默认 15 → 12（含一次性迁移）

- 配置项 `broom.climb`（面板「移动与行为 → 扫帚模式 → 接敌爬升高度」）默认 **15 → 12**，范围仍是 2~32。
- **旧档里写着 15 的会自动搬到 12**：新的一次性标记 `broomClimb12Migrated`，判据与 682 那次（10 → 15）同款——**只有值恰好等于 15 才搬**，玩家自己调过的其它值一律不碰。（为什么必须换一个新标记：682 那个标记在老档里早已落盘为 true，复用它等于「已经迁过了」，一个字节都不会动。）
- 代码里的兜底值同步改成 12（本项目的老规矩：兜底值必须跟着默认值走）。

### 二、「就是不升高自己的高度」的第三处根因（前两处在 实测六百八十四）

玩家说「平地也一样」，而 2026-09-26 那一局的日志给的数是 **2.00 / 2.93 / 3.66 格**——正好就是「悬停高度」那条兜底值附近。根因**不在配置**（配置读出来一直是 15.0，开阔处也确实爬到过 15.00），而在**「顶头」这个判定太敏感、而且一顶就把高度钉死**：

- **判据太敏感**：旧版连着 10 拍（0.5 秒）没长高就算「头顶被顶住」。被 boss 撞一下、被风弹吹一下、客户端/服务端位置同步抖一下，都够触发——她明明在开阔地，却被判成「顶着天花板」，当场把这一场的盘旋高度写成「此刻相对敌人的高度」。现在改成**两个条件一起看**：连着 20 拍（1 秒）没长高 **且** 整段净涨不到 0.5 格（真被天花板压着时净位移就是 0；正常爬升 20 拍该涨 6 格）。
- **一顶就钉死整场**：被顶住那一刻写进 `COMBAT_ALT` 的数就是这一整场遭遇的高度（旧日志原话「以后一直保持这个高度」）。现在这个数**只升不降**：一次顶头抬不高，但绝不会把已经飞到的高度拽下来。
- **自愈更快**：头顶一开阔就重试爬升的等待从「5 秒起、翻倍到 60 秒一次」缩到「**2 秒起、最久 30 秒一次**」（「真被天花板压着」那一档仍然由头顶探针挡住，房间里一次都不试）。
- 怎么验：`promaid.log` 搜「本场盘旋高度」（现在多写一句「本场只升不降」）与「头顶被顶住」（带起止高度、整段抬了几格、头顶三格是什么方块——分清真天花板与驱动故障）。

### 三、重锤后门：只留空袭那一档，扫帚不开

- **先回答玩家问的「会不会吃到」：会。** 挂在**近战空袭**的女仆下方、你手里拿着重锤挥出去时，增伤与附魔都按「你跟着她这一段俯冲下落了多少格」生效——重锤的下落三段加成（4 / 12+2(f-3) / 22+(f-8)）与 **密度 / 破甲 / 风爆** 读的都是同一个 `fallDistance`，我们只是在攻击那一刻把它写回去（`PlayerMaceRideFallMixin`）。为什么原来吃不到：乘客不走 `Entity.move`，所以吊在她下面的你那个字段恒为 0。
- **按玩家要求收窄**：旧版扫帚档也记账，本批**只对空袭那一档记账**（判据 `isBroomRelated`），扫帚档一个字都不记 → 混入不写 `fallDistance` → **扫帚档完全恢复原版**（挂着扫帚平飞慢慢降两三格不再换来一记猛击）；扫帚档也不会再打「重锤门 玩家=」那一行。
- 女仆自己那一侧不受影响：她近战空袭收翅俯冲时挥重锤那条链本来就只在空袭里、且只在主手真是重锤时结算。
- 怎么验：`promaid.log` 搜「重锤门」——空袭档挂着时会有 `玩家=…` 一行，写明「写回 fallDistance=N 格」还是「不到门槛」；扫帚档不该出现这一行。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十五【扫帚模式不再轻易丢锁敌：她与两种空袭改用同一个索敌器（发现 50 格 / 维持 128 格，不再按 TLM 援护半径 16-8 格丢）+ 「援护·跑远就松手」对扫帚让位（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话：「目前的女仆不像空袭状态下不那么容易丢失锁敌。处于扫帚模式（下面还挂着主人）状态下的女仆容易出现丢失锁敌的情况。大致就是飞到了空中，然后boss也飞上来，boss掉下去。然后女仆就会骑着扫帚悬停在那边，一动不动。」

### 一、根因：空袭有自己的索敌器，扫帚没有——差别就这一条

- **空袭为什么丢不了**：空袭（近战/远程）每一拍都由本模组自己的索敌器 `FlightTargeting` 选目标——以自身为圆心 **50 格球发现**、锁定后 **128 格内维持**、**维持期间不查视线**（实测五百二十一 / 五百二十四为"飞太高就丢目标、顺惯性飘出战场"写的那一套）。
- **扫帚为什么丢**：扫帚读的是车万女仆那条链（brain 的 `ATTACK_TARGET`），而 TLM 的"维持"判据是**援护半径**（工作时段 16 格 / 其余 8 格）+ **按排班分流的传感器盒**（非工作时段垂直硬编码 4 格）。玩家的场面——她飞到空中、boss 飞上来、boss 掉下去——竖直一拉开就同时踩中两条：距离过线 + 掉出 4 格高的盒子 → 目标作废；TLM 每 tick 重挑时既没有 128 格兜底、盒子也够不着 → **再也拿不回来**。
- **"悬停一动不动"是丢目标之后的出口**：目标一没，扫帚行为落进「没目标 → 平时」那一档，而挂着主人（武装拴绳）时那一档的出口正是**原地悬停**——她一直在跑，只是每一拍都走悬停那一支。**空袭不会僵在原地**是因为它的启动/继续条件要求"有目标"，目标一丢它自己就停下来收尾——这也是"空袭没事、扫帚有事"的另一半原因。

### 二、改法：扫帚并入空袭那套索敌器（同一套数字、同一套判据，不是另写一套）

- `FlightTargeting` 的入口守卫从"只有两个空袭任务"放宽到"**空袭任务 或 扫帚任务**"；扫帚侧新增两个接入点：`MaidBroomBehavior.aimTarget()`（**先问索敌器、问不到才退回 TLM 那条链**）与它 `stop()` 里的 `FlightTargeting.forget`（换任务/她没了时把锁定一起清，与空袭 forget 同一分工）。
- **不是"直接换掉"**：退路留着，所以今天能锁到的目标一个都不会因为这次改动而丢（索敌器第②步本身也会接管 brain 里已有的合法目标）；它多给的正是原来没有的两样——**128 格维持半径**（不再按 16/8 丢）与**无视排班盒子的 50 格球**（boss 掉到下方、竖直拉开也照样看得见）。
- **代价与边界（如实说明）**：索敌器的判据里有 `isWithinRestriction`——她不会为了追敌越出守家/工作范围（与空袭同一条红线）。越界的目标本来也打不着，丢掉是对的。
- **顺手让位一处**：「援护·目标跑出 N 格就松手」（`NeutralThreatDriver.abandonFarTarget`，默认 16 格）对**扫帚空中**不再生效——它的前提是"她自己跑远了、主人还在原地"，而扫帚上主人就挂在**她下面**，前提根本不成立。旧版的表现是：她一边正确地追着 boss，日志一边每 10 tick 刷一行「援护 目标跑出 16 格（她与主人都超）→ 松手」并且真的把 brain 里的目标擦掉。
- **诊断跟着放开**：索敌探针（日志搜 `空袭索敌`，逐环给出 候选 / 可攻击 / 活动范围内 / 50 格球内 / 有视线 / 选中）原先只对空袭任务生效，现在**扫帚也打**——两边用的是同一个 `scan`，所以这一串计数对扫帚就是她真正走的那条链。下次再出现"悬停不动"，日志一眼分清是"50 格球里真的没有可打的"（`选中=-` 且前面各环都有数）还是"看见了被某一环滤掉"。
- 手册同步（两树 `GuideChaptersFlight` 扫帚那一节新增「锁敌」一段）。
- **边界（如实说明）**：这条修的是"**该看见、却因为旧判据丢了**"的目标。如果日志里 `空袭索敌` 显示 50 格球内一个可攻击目标都没有，那她悬停是**正确的**——附近确实没有敌人。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十四【三件：① 扫帚盘旋「只比敌人高三格」的两个来源（换目标就重爬 + 顶住就再也不试）+ ② 挂在女仆下面时的重锤现在真能砸出猛击（门槛 0.5 → 0.1 格/tick，并给玩家那一侧补留痕）+ ③ 鞘翅在 YSM 模型下也画了、其他模组的鞘翅装备走通用判据（版本号不变，仍是 v1.3.0 beta）】

> 玩家原话三条：①「目前扫帚模式绕着敌人盘旋射击似乎默认高出的高度仍然不够，实际测试下来差不多只比敌人高了三格左右的样子那样子把主人挂在那边几乎跟平地没啥区别，没有战术价值。可能是某些配置没有生效。」②「我希望玩家在挂在女仆下面的时候重锤以及类似可以触发它对应的附魔。而且你可以扫一下刚刚的游戏日志，有没有触发重锤的增伤效果。」③「装了YSM后三叉戟的旋转效果已经很好的渲染出来了。那么能不能更进一步把鞘翅的渲染也做好呢但是这个要根据成本来定，实在不行就拉倒。还有一点，目前只有鞘翅会渲染出来，那其他模组的鞘翅装备能不能渲染出来呢？有没有一个通用代码可以写出来？？」

### 一、扫帚盘旋高度：配置没失效，是「高度被钉死了」+「换目标就重爬」

- **配置一直是生效的**（日志实证，2026-09-26 那一局）：`[配置迁移] 扫帚接敌爬升高度不需要迁移（现值 15.0）` → `接敌 → 先爬到它上方 15.0 格` → 开阔处 `爬升到位（y -58.81 → -44.93，想抬 14.16 格）` + `本场盘旋高度 = 敌人上方 15.00 格`。所以"某些配置没生效"这一条可以排除。
- **那"三格"从哪来**：同一局 16:46:15 与 16:46:33 各一次 `头顶被顶住 → 就地悬停（整段只抬了 -0.24 格）→ 本场盘旋高度 = 敌人上方 3.66 / 2.00 格`。她当时在低矮房间里（基地 y≈-60，那一层天花板≈-40），15 格物理上到不了。
- **真正的两个缺陷**（都在 `MaidBroomDrive.combatClimbTarget` 里，两树同步）：
  - ① **换目标就重爬**：爬升相位的 key 就是敌人 UUID，而 TLM 每 tick 重挑 `ATTACK_TARGET`——两个敌人轮流被选中时，高度就在"爬到 15 格"与"爬到 3.66 格"之间来回切（实测 16:46:15 → 16:46:18 各一次，只隔 3 秒）。
  - ② **顶住就再也不试**：旧版是"一场遭遇只爬一次"，那个 3.66 就是 `COMBAT_ALT` 的终值（日志原话「以后一直保持这个高度」）——她后来走进开阔地也升不回去。
- **改法**：本场遭遇只爬一次，**换目标只把 key 改写成新敌人**（高度一个字节都不动）；被顶住的高度照旧先按它飞，但**每 5 秒重试一次**（连续失败退避翻倍、60 秒封顶），且必须过一道"头顶探针"（她脚底往上 1~3 格现在是空气）——低矮房间里头顶一直是方块，一次都不会试，她不会为了够不到的高度原地一抽；等她自己飞到高处/走出房间，下一拍就重爬、把高度补满。
- **判据放宽**：`STALL_TICKS` 4 → 10。「4 tick = 0.2 秒」比"起手加速"还短（阻尼下从零起步第一拍只涨 0.075 格），一次方块角/叶子的擦碰就足以把本场高度钉在地板上；0.5 秒的确认对"真升不上去"零损失（正常爬满 15 格要走 3 秒）。
- **诊断（关键）**：`头顶被顶住` 那一行现在把**头顶三格到底是什么**写出来——`头顶：+1=minecraft:stone / +2=空气 / +3=空气`。这样"抬了 0 格 + 头顶全是空气"= 驱动故障、"头顶是方块"= 真撞天花板，下次从日志一眼分清（这条正是玩家猜的"配置没生效"该被排除的那一半）。
- 手册同步（两树 `GuideChaptersFlight` 扫帚那一节）。

### 二、挂在女仆下面时的重锤：门槛放宽 + 给玩家那一侧补留痕

- **旧日志查不到（照玩家要求扫了那一局）**：`promaid.log` 里 `[重锤门]` 共 1295 行、`触发` 出现 1295 次**全部**是 `不触发`；而且那些行**全是女仆那一侧**的判定（`主手=…` 指她手里的东西——那一局她拿着 `minecraft:mace` 但`没有风弹`，所以女仆那一侧也从没触发过）。**玩家这一侧旧版一个字节都不打**，"有没有吃到猛击"在日志里无从查起 → 现在补上。
- **新增留痕**：玩家挥重锤、且确实挂在女仆下面时写一行（搜 `重锤门`）：`玩家=…（挂在女仆下面）主手=重锤 这一段下落 N 格（原版猛击门槛 1.50）→ 写回 fallDistance=N 格…` 或 `→ 不到门槛 → 这一锤只是普通挥击`。他在平地上挥锤不会打这行（免得看着像故障）。
- **顺带修掉一个"永远吃不到"的门槛**：`RIDE_SLOW_MIN` 原本是 1.0 格 / 2 tick（= 0.5 格/tick，照抄原版 `checkSlowFallDistance`）。但**扫帚的竖直速度上限只有 0.30 格/tick**（`MaidBroomDrive.MAX_V_SPEED`），一次采样最多 0.6 格 → **永远**被判成"慢降"、累计被钳在 1.0（低于 1.5 的猛击门槛）→ **挂在扫帚下面一锤猛击都砸不出来**。现在门槛改成 0.2 格 / 2 tick（= 0.1 格/tick）：扫帚一次正经下降稳稳过线，而"悬停时那点漂移"（到点后速度每 tick ×0.75 收干，实际 dy≈0）照样被滤掉。
- **增伤与附魔是同一道门**：`MaceItem.canSmashAttack = fallDistance > 1.5 && !isFallFlying()`——原版的伤害加成与**密度**（`modifyFallBasedDamage × fallDistance`）、**破甲**、**风爆**全读这个数，所以把 fallDistance 写对，玩家要的"以及类似可以触发它对应的附魔"一起生效。
- **顺手收窄**：`consumeRideFall` 原先无条件写 `RIDE_LAST_Y`，一个在地面上挥锤的普通玩家会凭空被记成"正挂着"；现在只在"本来就在记"的时候续基准。
- 手册同步（neon 树 `GuideChaptersCombat` 二号位那一节；1.20.1 没有重锤这一项）。
- **边界（如实说明）**：威力终究取决于她真的往下走——挂在扫帚上**平飞**时砸不出猛击是设计如此（原版同样要求下落），要猛击就让她俯冲/下降一段。

### 三、鞘翅：YSM 模型下也画了；其他模组的鞘翅装备走通用判据

- **YSM**：旧版整层跳过，理由与 683 撤掉的那条同源（挂点要查 Gecko 骨骼表）。现在分两档：Gecko 渲染器照旧走骨骼挂点；**非 Gecko（YSM 等）改走固定身体偏移**——肩线 +1.86 格、+0.20 格（取自 实测四百八十 那批 28 个模型的统计值，就是 `Root` 那一档的补偿量）。依据是 683 的实证（YSM 调图层的那一段与 TLM `GeoReplacedEntityRenderer` 同构），与激流那层同一套坐标；日志写一行说明走了哪条路（搜 `鞘翅渲染`）。
- **其他模组的鞘翅装备（通用代码）**：判据换成 **`ItemStack.canElytraFly`——"这件装备对玩家能不能滑翔"**，与原版滑翔的闸门同源，所以模组**不需要为本模组做任何适配**。唯一例外：**它本身是护甲**（鞘翅胸甲这类，自带护甲外观，叠一层翅膀会穿模）——原版 `ElytraLayer` 同样只服务原版鞘翅，而原版鞘翅恰好是世上唯一一件"能滑翔但不是护甲"的装备，所以这不是新规矩、只是把原版那条隐含规则写明。被跳过时写一行日志（搜 `鞘翅渲染`），免得被当成渲染故障。贴图这一类统一用原版 `textures/entity/elytra.png`（模组没有通用 API 告诉别人自己的翅膀长什么样）。
- **边界**：YSM 各模型比例不一，固定偏移取的是"平均最像"那一档——若某个模型上明显不贴合，说一声调那一个数即可（与 683 激流那层同一个套路：先按证据上，实测再微调）。
- 文档同步：README「与其他模组共存」、`PUBLISH_GUIDE.md`「已知情况」，两树手册 `GuideChaptersFlight` 三件套那一节。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十三【YSM 模型的女仆重新有那圈激流旋转特效：把 548 顺手写下的「本层只在 Gecko 渲染器下绘制」撤掉（那句限制只对鞘翅成立）+ 顺带查清"烈焰冲锋时的那个模型"是谁的（版本号不变，仍是 v1.3.0 beta）】

> 起因：实测五百四十八（修 YSM 黑屏 CCE 那一批）在文档与 README 里留下一句「鞘翅**与激流旋转特效**在 YSM 模型上不会绘制」。玩家这轮想起它，并提了个替代方案：「我们可以直接套用万法皆通的女仆使用烈焰冲锋时的模型。」两边的源码都翻过之后的结论是：**那句限制对鞘翅成立、对激流特效却是我们自己多关的一道门**（撤掉即可）；而"烈焰冲锋时那个模型"并不属于万法皆通，也搬不过来（见第二节）。

### 一、"YSM 下不绘制"是我们的守卫，不是 YSM 的限制（撤掉这一句）

- **链路**：那圈特效是我们的图层 `LayerMaidSpinAttackGecko`（几何逐行照抄原版 `SpinAttackEffectLayer`，收在 `MaidSpinAttackEffect`）。它在 `MaidElytraLayerGeckoMixin` 里挂到 `GeckoEntityMaidRenderer`，而 TLM 的 `EntityMaidRenderer.initYsmModelRenderer` 会把这些 Geo 图层逐个 `copy` 到 YSM 渲染器——**YSM 下图层照样会被调用**（548 修的 CCE 就发生在这一步）。
- **卡在哪**：`render()` 第一句 `if (!(getRenderer() instanceof GeckoEntityMaidRenderer)) return;`——走 YSM 时自己返回。而门槛 `MaidSpinAttackEffect.shouldRender` = `maid.isAutoSpinAttack()`（我们置的活体标志位 4），与渲染器类型无关。
- **为什么这句多余**：548 写下它的理由是"本层挂点依赖 Gecko 骨骼表（ElytraLocator / Elytra / UpperBody / Root）"——那是**鞘翅图层** `LayerMaidElytraGecko` 的情况，**鞘翅那条守卫一字未动**；激流层一根骨骼都不用，只在图层入口那个 poseStack 上按固定偏移画三层方块。
- **YSM 那边的坐标系与 TLM 同构（反编译实证）**：YSM 的女仆渲染器（混淆包 `com.elfmcys.*`，实现 TLM 的 `IGeoEntityRenderer`）继承的基类里，流程是 `push → setupRotations → translate(0, 0.01, 0) → 画模型 → 图层循环 → pop`，与 TLM 的 `GeoReplacedEntityRenderer` 同一段代码形状——图层拿到的仍是"格、Y 朝上、原点在脚下"那一套（且仍在 `setupRotations` 之后），下面 `scale(-1,-1,1)` 的换算照样成立。
- **改法（两树）**：守卫整句删掉（连 `GeckoEntityMaidRenderer` 的 import），激流特效对任何 `IGeoEntityRenderer` 都画。**鞘翅那条不动**（它真的要骨骼；YSM 链上女仆的背部物品/鞘翅由 TLM 自己的图层负责）。

### 二、顺带查清："烈焰冲锋时的那个模型"是谁的（结论：不是万法皆通的，也借不过来）

- **烈焰冲锋 = 铁魔法（ISS）的 `irons_spellbooks:burning_dash`**。它的 `onCast` 只做一件事：`setSpinAttackType(SpinAttackType.FIRE)`（外加位移 / 20 tick 无敌 / 燃烧效果）。
- **那圈火色激流是 ISS 自己画的**：它用 mixin 换掉原版 `SpinAttackEffectLayer` 的贴图（`fire_riptide.png`，全亮）——也就是说"玩家版的蓝激流"与"烈焰冲锋的火色版"是**同一个几何、换一张贴图**；对**它自家的法师怪**才另走一套：`GeoSpinAttackLayer` + `irons_spellbooks:geo/spin_attack_model.geo.json`（三层堆叠的独立 geo 模型，不挂骨骼）。
- **万法皆通的职责只有一条**：让女仆能施 ISS 的法术（`IMagicCastingAnimationProvider` 把 ISS 的 `cast_start` / `cast_finish` 动画名映射成模型里的 `iss:xxx` 动画）。
- **你在大正女仆身上看到的那套施法动作，来源是 YSM 模型包**：YSM 内置 `default` 包带一份 `iss.animation.json`（19 个 `iss:` 动画）；1.20.1 那份万法皆通 1.8.2 里根本没有模型包（带 `winefox_*.iss.animation.json` 的是 1.21.1 的 1.8.4）。**"从外面给 YSM 模型补动画"我们做不到**——动画归模型包的 `animations/*.json` 与 YSM 的 controller。
- **顺带一条对你有用的**：YSM 把同一个旋转标志位暴露成 molang 变量 `is_riptide`，内置 `default` 包的 `main.animation.json` 里就有一个 `riptide`（0.25 秒里 `AllBody` 自转 0° → -360° 再躺平）——"她身体转不转"归模型包管，标志位我们早就置上了。你的「大正女仆酒狐」（YSM 内置 `wine_fox/01_taisho_maid`，lang 里就是这个名字）自己那 49 个动画里**没有** `riptide`（同族的 `08_sta / 09_hailuo / 10_zhiban / 17_mini` 有）；`default` 那份会不会兜底合并进它，混淆代码里定不下来。

### 边界（如实说明）

- 这次动的是"我们自己那道守卫"，**画不画得出来、落点对不对必须进游戏看**：装 YSM → 给女仆套一个 YSM 模型（如「大正女仆酒狐」）→ 主手给带激流附魔的三叉戟 → 攻击模式 → 看那一秒里有没有那圈白色斜纹特效。若真错位（说明 YSM 图层空间与我们的推断不符），退路是 ISS 那套做法：自带一份三层堆叠的独立 geo 模型、走渲染器自己的模型管线画，彻底不依赖骨骼与图层空间。
- 文档同步：README「与其他模组共存」与 `PUBLISH_GUIDE.md` 里那句改成只留鞘翅。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十二【扫帚模式「坐着扫帚掉在地上动也不动」修复（撤掉 679 那道「没拿绳子就不驱动」的闸）+ 接敌爬升默认 10→15 + 绑定的女仆换模式时自动解除一次 + 远程空袭开枪频率与超远距离准度 + 绑定 HUD 加一行操作提示（版本号不变，仍是 v1.3.0 beta）】

> 四条：① 「如果玩家绑定了一个空袭状态的女仆。这个时候再把女仆切换到扫帚模式，那个标记仍然在，但是却是显示一个无效的效果……设定成女仆，在切换模式的时候会自动将标记和绑定消除一次就行了。……简单来说，一个玩家一次只能绑定一个女仆，绑定了下一个就会把上一个清掉。（无论是空袭还是扫帚。）」② 「现版本女仆在扫帚模式下很奇怪，有的时候会正常起飞和跟随主人，但有的时候就会直接坐着扫帚掉在地上，动也不动。而且就算真的飞起来了打敌人，飞起来的高度仍然很低，起不到实战效果。目前大概要在原有的基础上至少再往上飞5格左右。默认值上调5格。顺便帮我检查一下刚刚游戏的运行，看看为什么会出现这样的问题。」③ 「远程空袭状态下，可能是因为我们把视线判定关了的问题，导致女仆在此状态下飞的太远后打枪的准度特别的低。而且似乎某些行为会阻止女仆开枪。女仆开枪的频率相比于正常的枪械模式要低了很多。」④ 「目前在那个处于绑定状态下的HUD再加一句提示，提醒一下，玩家可以通过右击战术拴绳的方式切换为主动骑乘。」

### 一、扫帚模式「坐着扫帚掉在地上，动也不动」——根因就是 679 那道闸（撤掉）

- **根因**：679 给扫帚驱动加了一道接管门槛「武装拴绳在用」（主人手上握着武装拴绳，或她正被拴着），不满足就 `travel` 走原版。而**原版对"女仆单独骑扫帚"根本没有驱动**——TLM 的 `EntityBroom.getControllingPassenger()` 对女仆恒为 `null`，原版那一支就是自由落体（这也是本模组当初必须自己接管的原因）。于是不拿着绳子时她必然"坐着扫帚掉在地上、动也不动"；拿着绳子（或正吊在她下面）就正常——正好是玩家说的"有的时候……有的时候……"。
- **日志实证**（1.21.1，2026-09-26 那一局）：`扫帚让位` 两行分别落在 14:39:27 与 14:50:37，两处之后各有 40 秒以上**没有任何「扫帚模式」的飞行/爬升日志**。
- **改法**：`EntityBroomMaidTravelMixin` 里那道闸整段删掉（连同它的 `扫帚让位` 留痕，那条日志从此打不出来），恢复 679 之前的口径——接管条件回到三条：第一乘客是她 / 她的任务就是扫帚模式 / 没有玩家在驾驶。玩家当初那句"不拿则走原版"的本意是**不要改写 TLM 的扫帚链路**（防别的 mod 冲突），这一条一直由那三道条件严格保证：不满足就一个字节都不动。
- 顺手把 `GunnerTetherManager.leashChainActive()` 也删了（它唯一的消费方就是那道闸；没人调又名字很像"该怎么飞"的方法留着，只会给下一个人准备第二份口径——与 672 删 `tetherHoldPos` 同一条理由）。

### 二、扫帚接敌爬升高度默认 10 → 15（+ 一次性迁移）

- **配置项** `combat.broom.climb`（面板「移动与行为 → 扫帚模式 → 接敌爬升高度」，范围仍是 2~32）默认 **10 → 15**，即玩家要的"在原有基础上再往上飞 5 格"。这一个数字同时决定**这一场遭遇的盘旋高度**（爬完就一直保持在那个高度打）。
- **旧存档里的 10 会自己变**：新增一次性迁移标记 `broomClimbMigrated`（值 == 10 才搬到 15；玩家自己调过的其它值一律不碰，标记落盘后想写回 10 也随便）。三个实例的 toml 另外直接改了一遍（两个客户端 config 目录只有管理员可写，走部署同款提权通道；改前各留 `*.p682bak`）。
- 说明：这道数字"以前看着没用"是因为上面那道闸把整条飞行链路掐了——链路通了之后，它才真的等于她飞多高。地下天花板低的地方仍按**实际抬到的高度**记（绝不低于「悬停高度」），不会为了够 15 格一直往上顶。

### 三、武装拴绳：她换模式就自动松开一次（+ 一对一绑定核对）

- **新增**：挂载记录里多存一格"挂上那一刻她的战斗模式档"（`broom` / `flight` / 其它）。`tick` 里每拍比一次，**新模式连续站满 1 秒**（去抖，防 TLM 换任务那一两拍的瞬时读空）就自动走一次完整解除：撤金色描边标记 + 清 persistentData + 清相位包 + 把他从她身上放下来（空中给 5 秒摔伤豁免），日志写 `自动解除(切模式)`，气泡复用既有的"绳子收好啦，我在这儿等着～"（台词包按"包含"匹配，复用才有语音）。
- **"一个玩家一次只能绑定一个女仆"**：这条 675 就做进了 `attach`（绑新的之前先把这位玩家已有的链路整条松开，日志写 `换绑`），本批核对过源码与日志——2026-09-26 14:51:00 那一局里 `换绑：先松开女仆=稀神探女 → 挂载：女仆=大正女仆酒狐` 就是它，空袭档与扫帚档都走同一条路，本批**未改**。
- **顺手修一处隐患**：落水自动解除那一支旧版直接调 `detach()`，而 `detach()` 会改 `LINKS`——在迭代器里改 map 会让下一拍 `it.next()` 抛 `ConcurrentModificationException`，整段 `tick` 被最外层 `catch` 吞掉，于是**本 tick 剩下的女仆全部漏检**（无声无息）。现在需要解除的都先攒起来，等这一轮走完再逐个解。

### 四、远程空袭：开枪频率与超远距离的准度（三条）

- **①枪械冷却不再被"超射程/视线被挡"冻住（这是"频率比正常枪械模式低"的固定一截）**：冷却的递减原先写在 `tickGunFire` 里，也就是**在射程门与视线门之后**——她飞得太远或视线被挡的那几拍，冷却**冻住不动**；而地面枪械模式（TLM 自己的任务）冷却每 tick 都走。现在递减搬进 `tickGunState`（在 `fireRanged` 的**所有门之前**、每 tick 恰好一次、幂等，与 666 把 `GunCommonUtil.tick` 搬过去是同一条理由）。
- **②起飞段（阶段一）也开火**：远战那一整段（20 tick = 1 秒）旧版一枪不放，每一轮"落地 → 重新起飞"都白丢 1 秒输出。现在这一段也调 `fireRanged`，**顺序是"先开火、后摆朝向"**（枪械开火会自己把身体拧向目标，紧随其后的 `faceLaunchDirection` 把朝向摆回去，烟花的推力方向取的是当拍视线、每 tick 现算，所以推力方向一个字没变）。
- **③新增「空袭·有效开火距离」`airRaid.rangedFireRange`（默认 24 格，0 = 不限）**：枪械模组自己的射程（实测现场 48 格）比弓弩的 24 大一倍，旧版于是会在 40 格开外一路点射——子弹是有飞行时间的实体，打一直在动的敌人基本打不中（实测日志里那一段全是"距敌 39.14 / 43.89 格"）。超出这条距离**不扣扳机**，改为继续盘旋（盘旋的径向修正会把她拉回半径 10 的圈上）再打。**锁敌那条 50 格没动**（那是"看不看得见该打的怪"，这条只管"打得到才算数"）。默认 24 对弓弩一字未改（它本来就只有 24）。
- **关于"视线判定关了"**：查过了，**开火这一路一直有视线门**（`fireRanged` 里 `SelfPreservationBehavior.hasSight` 不过就不出手，日志会写 `但视线被挡 → 不出手`）。**被关掉的只有"维持已锁定目标"那一道**（`FlightTargeting` 的 `HOLD_RANGE`/维持锁定刻意不查视线，524 定的口径）——那正是"她不会因为她飞高、视线被树叶挡一下就丢掉目标"的实现，不是准度问题的来源。上一局 249 秒的空袭段里，"不出手"的门只有 7 行（射程 3 行 + 视线 4 行，节流 2 秒一条），所以你说的"某些行为阻止开枪"主要是两处**整段不开火**：起飞段（本批已补）与激流突进窗口（十几 tick，属既有让位设计）。
- 顺带量了一下上一局同一只女仆、同一把枪的开火密度：**扫帚段 4.51 发/秒 → 空袭段 2.72 发/秒**（后段 3.28），也就是你感觉到的"慢了很多"确实存在。

### 五、绑定态 HUD 多一行操作提示

- 「绑定中」那条下面再画一行灰字、缩进 4 像素：`右击她：解除；她骑扫帚时改为主动骑乘`。文案与右击的真实行为对齐（`toggle` / `seatBackOnBroom`：她骑着扫帚时那一下右击 = 你坐回扫帚驾驶位；她没骑扫帚（空袭）时是纯解除）。仍然受"放不下就不画"的边界保护，不会顶出屏幕。

### 附：你让我查的"刚刚那一局"（1.21.1，2026-09-26 14:37~14:51）

- **扫帚不起飞**：`扫帚让位`（679 那道闸）——见第一节，已修。
- **飞得低**：那一局她的基地在 y≈-60（地下），日志里大片 `头顶被顶住 → 就地悬停（整段只抬了 0.00 格）`，也就是**真的被天花板挡住了**（同一批日志里 `扫帚卡墙` 在 14 分钟里出现 29 次，那局基本都在窄隧道里打）。开阔地形那一档是能爬满的（01:27 那一局：`爬升到位 y -56.94 → -49.29，想抬 7.86 格`）。默认已按你的要求升到 15，洞穴里仍会按实际高度收尾。
- **开火频率**：见第四节 ③ 与最后那组数字。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）并逐处核对 md5；
三个实例 toml 回读 `climb=15`。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十一【把 679 那批「按玩家实例同步默认值」整体撤回：12 项声明默认值恢复真默认 + 撤掉两条与声明值打架的强制迁移 + 你三个实例的配置一并改回默认（版本号不变，仍是 v1.3.0 beta）】

> 一条（你指出我理解反了）：「有一点你理解错了，我要的是，我现在不想运行我游戏里面那个值了。我想运行默认配置的值。而不是改默认值」

### 一、撤回 12 项：声明默认值 = 真默认（改）

- **上一批做反了**：679 你说「将我现在修改的值改成默认值」，我读成了"把你改过的值变成默认值"——
  于是把 12 个键的**声明**默认值改成了你 1.21.1 实例里的值。你要的是反过来的那一半：
  **让你游戏里的值回到默认值**。本批把声明值全部恢复成真默认，改成"游戏跟着默认走"。
- 12 项一览（本批恢复的「真默认」← 679 曾写成的值）：

| 配置键 | 真默认（本批恢复） | 679 曾写成 |
|---|---|---|
| `mine.breakBudget` 穿透预算 | **22** | 6 |
| `combat.soulSpellCooldownSeconds` 收符冷却（秒） | **60** | 1 |
| `combat.waterLandingScan` 落地水下探格数 | **2** | 3 |
| `combat.playerDamageMode` 玩家对女仆伤害模式 | **4（仅一点伤害）** | 2（无限制） |
| `combat.autoResurrectDelaySeconds` 复活延迟（秒） | **60** | 10 |
| `combat.aidThirstThreshold` 投喂触发口渴度 | **15** | 20 |
| `combat.combatWorkRange` 战斗时临时扩圈（格） | **15** | 32 |
| `misc.scheduleAvailabilityCheck` 排班切换前可用性检测 | **开** | 关 |
| `flightFollow.enabled` 飞行跟随 | **关** | 开 |
| `flightFollow.firework` 飞行跟随·消耗烟花 | **开** | 关 |
| `flightFollow.elytra` 飞行跟随·消耗鞘翅耐久 | **开** | 关 |
| `flightFollow.trident` 飞行跟随·消耗三叉戟耐久 | **开** | 关 |

- 顺带把被 679 一起改过的"旧默认"文案全部改回来：配置面板 9 行（穿透预算 / 收符冷却 / 复活延迟 / 投喂触发口渴度 /
  战斗时临时扩圈 / 飞行跟随那四条开关）、手册 6 处（被动技能章、空袭那一章两处、飞行章三处、系统章一处）、
  两个类的注释各 1 处（`CombatSenseCheck` 的取样点说明、`MaidAutoResurrect` 的流程说明）。

### 二、撤掉两条与声明值打架的强制迁移（改）

- `runConfigMigration` 里原有两条**强制**改值：`mine.breakBudget` 22 → 6、`misc.scheduleAvailabilityCheck` **无条件** → false。
- 声明值既然表态为 22 / 开，留着它们就等于"默认值永远拿不到"（679 那条注释自己也承认了这一点）。本批一并删除。
- 其余迁移**一条没动**——它们搬的都是"还停在旧默认上"的键，声明值本来就等于迁移目标，不冲突。

### 三、你三个实例的配置已经真的改回默认（改）

- 三个 toml 全部改完（各留一份 `promaid-common.toml.p681bak` 备份）：
  `D:\.minecraft\versions\1.20.1-Forge_47.4.21` / `D:\.minecraft\versions\1.21.1-NeoForge_21.1.250` / `C:\Users\Sketch\mc_server_test\pack1201`。
- **服务端那份**直接覆盖；**两个客户端那份**走的是部署同款的管理员通道——这两个实例的 `config` 目录在本机只给了
  `Users:(RX)`（连写都得提权），普通拷贝会被拒。改完两份都**回读校验**过：12 项逐个对，0 不符。
- 另外加了一条**一次性**迁移 `DEFAULT_REPAIR_MIGRATED`（内部标记，请勿手改）当兜底：万一还有别的实例 /
  从旧档恢复的 toml 里仍带着 679 那批值，游戏下次启动会把它搬回真默认——**只搬"值恰好还停在 679 那批值上"的键**，
  你自己调成别的值的一律不动，标记落盘后永不再碰。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201），
三处与自己 `patched/` 里的 md5 一致；三个实例的 12 个键**回读校验 0 不符**。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百八十【搭方块禁用名单面板（默认只许原版天然方块、模组方块默认全禁）+ 饰品栏里的鞘翅也算空袭三件套（版本号不变，仍是 v1.3.0 beta）】

> 两条：① 「在搭路那个界面加一个额外的配置界面（类似于挖矿的配置面板），是一个黑名单面板，选择方即让女仆禁止使用哪个东西来搭方块（此配置对于自保搭高、挖矿、伐木、搭路都生效），默认禁止搭建的为所有非的原版自然生成方块（当然现有的那些在黑名单里的仍然是不允许的，比如沙子），不包含模组方块。但是玩家如果想要让他用模组方块搭，那还是可以的。只要在这个配置面板里面把模组物品取消掉就行了。」② 「我现在了解到翘翅是可以放在饰品栏的，那么在饰品栏里面的鞘翅应该也可以作为启动我们的空袭的激活条件之一，并且等效消耗耐久。」

### 一、新面板：搭方块禁用名单（在「移动与行为 → 搭路」里，默认只许原版天然方块）

- **入口**：「移动与行为 → 搭路」小节最后一行 `搭方块禁用名单　管理 →（禁 N · 放 M）`，点开是一整页全方块面板——搜索框 + 16×3 图标网格（分页）+ 底部"我改过的条目"列表 + 两个按钮（`默认只许原版天然方块` 开关 / `清空名单`）。交互照搬挖矿那张障碍物表与投喂食物勾选页（同一套网格点击 + 翻页 + 悬停显示注册名与中文名）。
- **点一下切换**：`红框✖ = 禁止`、`绿框✔ = 允许`。悬停格子右上角会把它的注册名与 `(原版天然)` / `(非天然)` 一起写出来，点下去就切。
- **默认规则 = 只许「原版天然方块」**（默认开）：表在 `com.maidsmart.tool.NaturalBlocks`，**113 项**——石头 / 圆石 / 苔石 / 深板岩 / 泥土 / 粗泥 / 灰化土 / 菌丝 / 草方块 / 泥巴 / 沙子 / 红沙 / 砂岩 / 沙砾 / 黏土 / 雪块 / 冰 / 浮冰 / 蓝冰 / 花岗岩闪长岩安山岩 / 凝灰岩 / 方解石 / 滴水石块 / 黑曜石 / 16 色陶瓦 / 全部矿石与深板岩矿石 / 下界石英矿 / 下界金矿 / 远古残骸 / 全部原木与"木"（含菌柄菌核）/ 下界岩 / 灵魂沙土 / 玄武岩 / 黑石 / 镶金黑石 / 岩浆块 / 萤石 / 菌光体 / 下界疣块 / 绯红与诡异菌岩 / 末地石 / 紫水晶块与芽床 / 幽匿 / 南瓜 / 西瓜 / 三种蘑菇块 / 蜂巢。
  - 因此**合成品（木板 / 玻璃 / 石砖 / 羊毛 / 混凝土 / 铜块 / 紫珀 / 海晶…）与全部模组方块默认都不许用**——这正是玩家要的"默认禁止所有非原版自然生成"。
  - **为什么是手写表、而不是去查世界生成数据**：原版没有"这方块会不会自然生成"的 API。实测把 `data/minecraft/worldgen/**` 反推一遍，得到的集合既缺项又串味——缺 `cobblestone` / `cobbled_deepslate` / `obsidian` / `bedrock`，却混进了 `oak_planks` / `gold_block` / `glass`（那些是**结构处理器**的输出，不是地形）。表一旦"看着没道理"，玩家就再也不敢信默认值，所以改成一张人工审定的短表，判定口径一句话能讲清：**原版方块、能在世界里自然捡到/挖到、放下去能站人**。
  - **想让她用模组方块搭**（玩家原话）：在面板里把它点成绿框✔就行（写进 `bridge.buildWhitelist`），**不需要改配置文件**。
- **三个配置键**（TOML 都在 `[bridge]` 小节）：`buildOnlyNatural`（默认 `true` = 上面的默认规则）、`buildBlacklist`（默认空 = 面板上点成红框✖ 的）、`buildWhitelist`（默认空 = 面板上点成绿框✔ 的例外，含模组方块）。中英翻译键三条已加（`config.promaid.bridge.buildOnlyNatural` / `buildBlacklist` / `buildWhitelist`）。
- **单一口径**：全部判定只在 `MaidBuildBlockFilter.isBlacklistedBuildBlock`（优先级：放宽名单 → 禁用名单 → 「只许原版天然方块」→ 放行）。**面板画的状态**和**女仆取材时判的状态**调的是同一个方法，所以不可能出现"面板说允许、她仍然不搭"这种口径分裂。
- **生效范围**：自保搭高 / 挖矿 / 伐木 / 搭路——玩家点名的那四条。它们本来就都走 `MaidBuildBlockFilter.isUsableBuildBlock` 这一个入口，所以规则挂在那一处就自然全覆盖（**没有**给四个模块各写一遍）。
- **与既有黑名单的关系**：沙子/沙砾这类**下落方块**、仙人掌/岩浆块这类**伤害方块**、以及 `misc.dangerBlocks` 配置表，照旧由原来那几条判定拦住，**不受本名单影响**——玩家原话「现有的那些在黑名单里的仍然是不允许的，比如沙子」正好自洽：沙子在天然表里，照样不许搭，这恰好证明两层判定各自独立。
- 手册两处同步：工作章「拿什么方块垫脚」（挖矿/伐木读者入口）+ 飞行章的配置总览一节（搭路那一段）。

### 二、饰品栏（Curios）里的鞘翅也算空袭三件套，并且等效扣耐久（改）

- 新增软兼容层 `com.maidsmart.combat.CuriosElytraCompat`——**全反射、零硬依赖**（与 `GunCompat` / `MaidSpellCompat` / `SlashBladeCompat` 同一套路：Curios 不在编译期 classpath 上，也不给玩家加前置）。两版 Curios 的 API 形态（1.20.1 `LazyOptional` / 1.21.1 `Optional`、`getCuriosInventory` → `getCurios()` → `getStacks()` → `getSlots/getStackInSlot/setStackInSlot`）都用**两版同名的那几个方法**，所以本类在 SRG 树与 Mojmap 树里**逐字相同**。
- 接进 `MaidFlightKit` 四处：① `hasElytra`（激活条件多一条"饰品栏里有"）；② `equip`（胸甲槽没鞘翅时，把她饰品栏那件**取出来穿到胸甲槽**）；③ `takeElytra`（飞行跟随那条"临时穿戴、收手还回去"的链路也认）；④ `elytraDiagnostic`（诊断多一段 `饰品栏=有/空/未装 Curios`，方便对着日志排查"我放她饰品栏了，怎么还说缺鞘翅"）。
- **"等效消耗耐久"是怎么成立的**：原版滑翔的闸门 `LivingEntity.updateFallFlying` **只认胸甲槽**的鞘翅——所以这件必须穿到胸甲槽去。穿上之后耐久由原版那套"滑翔中每 20 tick 扣 1 点"照常扣，也就是说**不是另写一套扣耐久**，而是与"她自己从背包里翻出一件鞘翅穿上"完全同一条路。要"不啃耐久"也只有原来那一个开关（飞行跟随·消耗鞘翅耐久）管得着它，与旧口径一致。
- **取舍次序**：背包（先带护甲的"鞘翅胸甲"、再普通鞘翅）→ 双手 → 饰品栏。胸甲槽原本那件照旧还进背包（与旧行为一致，不另立新规矩）。
- **边界**：不装 Curios / 她没有饰品栏槽位 / Curios 版本不符 / 反射被拒 → 四个方法一律返回"没有"，行为与旧版**一字不差**（诊断行会写"未装 Curios"）。
- 顺带说清一件事：「她能不能把鞘翅放进饰品栏」是 Curios 与整合包的事（本批实测她的饰品栏槽位由 TLM 注册给 Curios：`back` / `body` / `head` / `hands` … 十三个槽类型）；「放进去之后 promiad 认不认、会不会穿、会不会扣耐久」才是本批解决的问题。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
两个包**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）。
（精确字节数见仓库根的 `CHANGELOG.md`——**故意不写进游戏里的这份**：jar 里写着 jar 自己的
大小会互相影响，永远对不上。）

## 实测六百七十九【骑扫帚链路收窄成「武装拴绳在用才接管」+ 把玩家当前配置值同步成默认值（版本号不变，仍是 v1.3.0 beta）】

> 五条：① 「最好不要整体改骑扫帚的链路，防止其他mod对骑扫帚进行改动导致冲突。而是对物品进行判定（即只有玩家手持武装拴绳才走此链路，不拿则走原版）」；② 调研空袭施法链路（结论见下，**无代码改动**）；③ 调研空袭开枪判定（结论见下，**无代码改动**）；④ 「将我现在修改的值改成默认值」——按他 1.21.1 实例的当前配置同步 **12 项**默认值；⑤ 取消三平台状态定时检查（已删）。

### 一、扫帚驱动接管：只有「武装拴绳在用」才走自研链路（改）

- 玩家原话：「最好不要整体改骑扫帚的链路，防止其他mod对骑扫帚进行改动导致冲突。而是对物品进行判定（即只有玩家手持武装拴绳才走此链路，不拿则走原版）」
- 本模组改动 TLM 扫帚飞行的**唯一**地方是 `EntityBroomMaidTravelMixin`：它在 `EntityBroom.travel` 的
  `HEAD` 上直接 `cancel` 掉原版、再写自己的速度与朝向。它不是"加一层"，而是**整条接管**——别的 mod
  对扫帚骑乘 / 飞行的改动会在这一档被整体绕开。玩家的担心是对的，所以这一批把它收窄。
- 新增唯一判据 `GunnerTetherManager.leashChainActive(maid)`（本项目"口径只有一处"），满足其一才接管：
  ① 她的主人手上正握着武装拴绳（主手 / 副手任一）；② 她此刻正被武装拴绳拴着（牵绳档 / 二号位）。
- **为什么 ② 也算「在用」**：二号位里玩家吊在她下方，手上拿的是枪或重锤（实测六百七十六 的重锤猛击
  就是这么用的），"不拿绳子"是常态——只认 ① 的话，他一放下绳子就断掉她的飞行，两个人一起掉下去。
- 不满足时**一个字都不改**：这一 tick 起 `travel` 走原版（TLM 与别的 mod 说了算），并顺手清掉这一 tick
  没人消费的推进意图（免得下次接管时突然来一记陈旧的推力）。日志搜「扫帚让位」能看到第一次让位。
- **副作用说明（重要）**：没有玩家驾驶的扫帚在 TLM 原版里是**自由落体**。所以"没在用拴绳"时她在空中
  会掉下去——这正是"走原版"的字面含义。要收绳子，请先让她落地。

### 二、配置默认值同步 12 项（改；按玩家 1.21.1 实例的当前配置）

| 配置键 | 旧默认 | 新默认 |
|---|---|---|
| `mine.breakBudget` 穿透预算 | 22 | **6** |
| `combat.soulSpellCooldownSeconds` 收符冷却（秒） | 60 | **1** |
| `combat.waterLandingScan` 落地水下探格数 | 2 | **3** |
| `combat.playerDamageMode` 玩家对女仆伤害模式 | 4（仅一点伤害） | **2（无限制）** |
| `combat.autoResurrectDelaySeconds` 复活延迟（秒） | 60 | **10** |
| `combat.aidThirstThreshold` 投喂触发口渴度 | 15 | **20** |
| `combat.combatWorkRange` 战斗时临时扩圈（格） | 15 | **32** |
| `misc.scheduleAvailabilityCheck` 排班切换前可用性检测 | 开 | **关** |
| `flightFollow.enabled` 飞行跟随 | 关 | **开** |
| `flightFollow.firework` 飞行跟随·消耗烟花 | 开 | **关** |
| `flightFollow.elytra` 飞行跟随·消耗鞘翅耐久 | 开 | **关** |
| `flightFollow.trident` 飞行跟随·消耗三叉戟耐久 | 开 | **关** |

- 只改**声明默认值**：Forge / NeoForge 都不会替已有 toml 改值，**老存档里的现有配置一个字节都不动**
  （想用新默认请删掉那一行，或整个文件删掉重开）。
- `mine.breakBudget` 与 `misc.scheduleAvailabilityCheck` 两项本来就由 `ProMaidMod.runConfigMigration`
  在每次启动时强制改（22→6、true→false）——声明值与实际行为现在终于一致了；迁移段照旧保留（它管老档）。
- 面板描述与手册里写死旧默认的数字文案一并改掉了（穿透预算 / 复活延迟 / 投喂触发口渴度 /
  战斗时临时扩圈 / 回魂符冷却 / 飞行跟随那三条省料开关）。

### 三、两条调研结论（**无代码改动**，供决策）

- **② 空袭施法**：不是独立自研链路，也**不是**"搬的万法皆通"。实际是**两层**：
  ① 通用的「顺带施法」确实**复用万法皆通**（`touhou_little_maid_spell`）的公开 API
     （`SpellBookManager.getOrCreateManager` → `ISpellBookProvider.castSpell`），promaid 只负责
     "何时发起 + 目标同步 + 距离 / 间隔闸门"；
  ② 位移法术（起飞 / 补高 / 俯冲冲刺那三处"指定法术"）是 promaid 自己写的反射包**直连 Iron's
     Spells**（`SpellRegistry.getSpell` → `MagicData.initiateCast` → `AbstractSpell.onCast`），
     因为万法皆通**没有公开的"指定法术施法"API**（它自己那套在同 jar 的私有
     `MaidSpellCommand$IronSpellHelper` 里，类 package-private、方法 private static）。书单 / 等级 /
     冷却仍读万法皆通的 `IMaidSpellData`。两条链路的判定都在 `MaidSpellCastCompat`。
- **③ 空袭开枪**：是**接的原版（TLM 自家）枪械通道**。开火这一下走
  `GunCommonUtil.performGunAttack(maid, target, gun)`，TLM 内部再转 TACZ 的
  `IGunOperator.fromLivingEntity(maid).shoot(...)`；promaid 自己只写了外层三道门
  （视线 `SelfPreservationBehavior.hasSight`、冷却 `RANGED_GUN_CD`、弹药 `GunCompat.canFeed`），
  **没有任何** `IGun.shoot`、也没有任何碰枪械类的 mixin。空袭与地面远程共用同一份
  `fireRanged` / `tickGunFire`。

**验证**：两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
`promaid-1.3.0-forge-1.20.1.jar`（10,813,668 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,837,727 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）。

## 实测六百七十八【换座不再把她摔下去 + 扫帚接敌爬升 8 → 10 + 左上角「绑定中」（版本号不变，仍是 v1.3.0 beta）】

> 三条实测反馈：① 「当玩家坐在扫帚上，女仆处于扫帚模式时，玩家拿着武装拴绳对着女仆进行右击的时候，不应该把女仆的骑乘状态也解除掉。这样子可能会导致失控。」② 「考虑到现在加入了这个模式，那么女仆需要飞的再高一点，默认应该是10格的高度。（之前的扫帚模式盘旋是8格）」③ 「在进入绑定状态下，最好是在左上角用蓝色字体显示一下玩家现在处于绑定状态。（渲染机制同冷却计时）」

### 一、换座（绑定态右击 = 坐回扫帚驾驶位）全程不让她下鞍（修）

- 玩家原话：「当玩家坐在扫帚上，女仆处于扫帚模式时，玩家拿着武装拴绳对着女仆进行右击的时候，不应该把女仆的骑乘状态也解除掉。这样子可能会导致失控。」
- **旧版是怎么做的**：六百七十二 起，"绑定态右击她 = 解除并坐回扫帚驾驶位"的实现是**先把扫帚上所有人请下鞍**
  （对每个乘客调 `stopRiding()`）→ 玩家上鞍坐第一乘客 → 再把她 `startRiding` 放回第二乘客。
  也就是说她**真的被下过一次鞍**；而且万一第二下没放上去（`canAddPassenger` 没过多余的乘客位、
  那把扫帚那一瞬间不可用、别的东西插了一脚），她就**留在扫帚外面**、从空中掉下去——她只有 20 血。
- **根因**：不需要那段"请所有人下鞍"的舞蹈。**原版 `Entity.addPassenger` 自己就有"玩家插队"这条规则**
  （javap 实证，1.20.1 `Entity.m_20348_` 与 1.21.1 `Entity.addPassenger` 字节码逐条同形）：
  ```java
  // 新乘客是 Player、且【第一乘客不是】Player → 插到第 0 位；否则才 append
  if (!level.isClientSide && passenger instanceof Player
          && !(this.getFirstPassenger() instanceof Player)) {
      passengers.add(0, passenger);
  } else {
      passengers.add(passenger);
  }
  ```
  她的扫帚上第一乘客正是她自己（女仆）→ 玩家一上鞍就**自动坐进驾驶位（第 0 位）**，
  她的座次原地不动（还是原来那个位置，只是往后挪一格变成第二乘客），
  **骑乘关系、载具引用、碰撞位置全程一个字节都没变**。
- **现在怎么改**：`GunnerTetherManager.seatBackOnBroom` 去掉"逐个 `stopRiding()` + 逐个放回"那一整段，
  只留 `player.startRiding(broom, true)` 一句；座位顺序交给原版那条规则。
  仍然**显式**换座（六百七十二 留下的正解：靠副作用撞运气那条路继续不用）。
- **顺带修好的一处**：玩家上不去扫帚时，旧版会把两个人一起丢在空中；现在只是换座没成功
  （日志写「换座失败：玩家上不了扫帚（…仍安全骑在扫帚上）→ 绳子已解除，她继续自己飞」），
  她照旧好好骑在扫帚上。
- **留痕**：换座成功那一行日志现在带上**乘客顺序**（如 `乘客顺序=玩家,女仆`）——一眼能看出
  "玩家确实坐在第 0 位的驾驶座上、她在第二乘客"，这正是 TLM `getControllingPassenger()`
  （"玩家驾驶"整条链路）唯一的判据。
- 日志搜**「换座」**。

### 二、扫帚接敌爬升高度：8 → 10，并且变成可调的配置项（新）

- 玩家原话：「考虑到现在加入了这个模式，那么女仆需要飞的再高一点，默认应该是10格的高度。
  （之前的扫帚模式盘旋是8格）」。这里的"这个模式"指武装拴绳二号位——你吊在她下方 2.6~2.9 格，
  她飞高一点你脚下才有余量，不会一路蹭着树冠和地面。
- **旧版**：那个 8 是代码里的常量 `MaidBroomDrive.CLIMB_BLOCKS`，面板上调不了。
- **现在**：新配置键 **`combat.broom.climb`**（"扫帚·接敌爬升高度（格）"，默认 **10**，2~32），
  面板「移动与行为 → 扫帚模式 → 接敌爬升高度（格）」可调；常量连同它的注释一起删掉
  （本项目"口径只有一处"的规矩：不许留第二份高度数字）。
- **它同时决定盘旋高度**：这一个数字既是"爬多高"也是"在敌上多高打"——爬升相位收尾
  （到位 / 头顶被方块顶住）那一刻把**这一场遭遇的盘旋高度**定下来，之后一直保持到打完 / 丢目标。
  头顶被顶住时按**实际抬到的高度**记（但绝不低于"悬停高度"），所以低天花板地形不会为了够 10 格
  一直往上顶。
- 面板那一行脚下的说明、`MaidSmartConfig` 的注释、手册「扫帚模式」章「她怎么飞、怎么打」那一段
  （连"参数在哪调"那一句的项数：九项 → **十项**）都跟着改了。
- **旧存档里的 8 不会自动变**（配置文件里写死的值优先），想跟着新默认走就把那一行改掉或删掉重开。
- 日志搜**「接敌 → 先爬到它上方」**与**「本场盘旋高度」**。

### 三、左上角一行蓝色「绑定中」（新）

- 玩家原话：「在进入绑定状态下，最好是在左上角用蓝色字体显示一下玩家现在处于绑定状态。
  （渲染机制同冷却计时）」
- **渲染机制真的是同一套**：没有新开一条 HUD 链路，而是把这一行塞进**冷却 HUD 那个包**
  （`CooldownHudPacket` → `com.maidsmart.client.CooldownHudRenderer`）。于是
  左上角的位置（就在建造 HUD 之下、冷却计时上面）、字体、行高、带阴影、**打开界面 / F3 时整块隐藏**、
  **几秒没数据自动清空**——全套行为一行都不用新写，且天然是**只给当事人看**的（只发给该玩家）。
- **两处不同**：① 那一段的字是**蓝色**（`§9`），别的行照旧黄 / 紫；② 它没有倒计时，
  永远排在**最上面**一行（排序键给 `Long.MIN_VALUE`）。
- **文案带档位**：`◆ 绑定中 <她的名字> 牵绳` / `◆ 绑定中 <她的名字> 二号位`——
  **牵绳**= 她还没起飞、你自由活动她跟着走（空袭档的牵绳相位）；**二号位**= 你正吊在她下方。
  解绑、她没了、跨维度、你潜跳下鞍——这一行自己就消失（数据源只有 `GunnerTetherManager.LINKS` 一处）。
- **它不受"冷却 HUD"开关影响**：这几行是"状态指示"，与"要不要看复活 / 回魂符倒计时"是两件事。
  所以服务端那个"关掉就整段 return"的闸从函数开头挪到了冷却那三段自己头上
  （新增 `CooldownHudTracker.soulScanWanted()`，顺带连全图扫描一起省掉）。
- 手册「武装拴绳」章「三、挂在下面看得见吗」那一段末尾补了「④ 左上角的蓝色『绑定中』」。
- 日志搜**「冷却HUD」**（下发详情里会出现 `bound:名字:leash/` 这样一条）。

### 四、本批改动的文件（两树镜像，SRG / 官方名各写各的）

- `com/maidsmart/combat/GunnerTetherManager.java`：`seatBackOnBroom` 重写（去下鞍舞蹈 + 乘客顺序留痕 +
  失败分支不再连累她）、新增 `entityKind`、新增 `hudBound(player)`（HUD 数据源）。
- `com/maidsmart/combat/MaidBroomDrive.java`：删常量 `CLIMB_BLOCKS`，新增 `climbCfg()`，
  `combatClimbTarget` 全程改用配置值（含日志文案）。
- `com/maidsmart/combat/CooldownHudTracker.java`：`broadcast` 的"关掉"闸挪进 `soulScanWanted()`、
  绑定指示入队（`kind = "bound"`）、日志详情照旧。
- `com/maidsmart/client/CooldownHudRenderer.java`：`isBound` / `remainOf` 的 `Long.MIN_VALUE` 档 /
  `format` 的蓝色分支。
- `com/maidsmart/config/MaidSmartConfig.java` + `PromaidConfigScreen.java`：新键 `combat.broom.climb`
  + 面板「扫帚·接敌爬升高度（格）」一行。
- `assets/maid_smart/lang/{zh_cn,en_us}.json`：`config.promaid.broom.climb`。
- `com/maidsmart/guide/GuideChaptersCombat.java` + `GuideChaptersFlight.java`：手册那三处。

**验证**：三处判据都对着字节码核过（1.20.1 `Entity.m_20348_` / 1.21.1 `Entity.addPassenger` 的
"玩家插队"分支、`getPassengers()` 返回的是 `ImmutableList` 因而只能靠原版那条规则换座）；
两树 `javac` **0 错误**；`_mixchk.py` 注入点审计 **PASS=167 SKIP=8 FAIL=0**；打包门禁
（`verify_jar_classes.py` + `mixin 包登记` + `lang json`）全过；出
`promaid-1.3.0-forge-1.20.1.jar`（10,810,631 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,834,698 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端 versions 的 mods + 服务端 pack1201）。
## 实测六百七十七【两套都齐说一句「我已经准备好了」+ 拴绳的拉扯做成真的（版本号不变，仍是 v1.3.0 beta）】

> 两条实测补充：① 空袭模式激活（三件套齐）+ 扫帚模式条件满足（她已经坐上扫帚、手里还拿着远程武器）时，她要主动说一句「我已经准备好了」（附带语音），**只说一次**，除非再被解除、CD 才刷新；② 「我要的拉扯感是可以拉着女仆走，像原版拴绳一样」——六百七十六 那一层其实只是**画面上的弯曲**，物理上并没有任何东西在拉她。

### 一、两套都齐了，她会说一句「我已经准备好了」（新）

- 玩家原话：「当空袭模式处于激活状态（集齐三件套或者其他的，已达成激活的条件），扫帚模式条件满足（自己已经坐到了扫帚上，并且手中拿有远程武器）的时候，会说一句，自己已经准备好了。（附带语音）这个只说一次。除非再次被解除，此文本CD才会刷新。」
- **触发条件（两条同时成立）**：① 空袭三件套齐（鞘翅 + 远程武器 + 推进剂 + 弹药）；② 扫帚齐备
  （她已经骑在扫帚上 + 有远程武器 + 有弹药）。两条的判据**都是复用既有的那一份**（扫帚那条就是
  `MaidBroomKit.isModeActive`，空袭那条是新建的 `MaidFlightKit.airAssaultReady`）。
- **【为什么"空袭那一半"要新写一个判据】** 现成的 `MaidFlightKit.isModeActive` 里的"武器位"是
  **按当前任务分流**的：远程任务数远程武器、其余任务数近战武器。而这一句的触发场合是**扫帚模式**
  ——她的任务不是飞行任务，于是同一个背包会被它判成"缺武器"（它转头去数近战武器了），
  这一句就永远说不出口。所以 `airAssaultReady` 按**任务无关**口径把四件重新组合一遍
  （鞘翅 / 远程武器 / 推进剂 / 弹药），四件**全部调用既有判据**，没有写第二份"什么算鞘翅"。
- **「只说一次」是一道闩，不是冷却计时**：说出口就闩住，**三种**情况会拔闩（拔掉之后她再满足条件
  就重新说一遍）：① 扫帚那一半不成立（下扫帚 / 缺件 / 总开关关掉——`notifyNotReady` 那条路上
  无条件拔）；② 空袭那一半断开（鞘翅飞坏、烟花烧完、弹药打光）**连续 2 秒**；③ 退出扫帚任务。
  齐备与断开都要求"连续站稳 2 秒"，与本文件既有的缺件播报同一套防抖口径。
- 台词走的是**语音链路**：气泡文本「空袭和扫帚都齐了，我已经准备好了！」里**包含**语音包
  manifest 的新 key「我已经准备好了」，所以这句是真有语音的（新 synth 的 `ready_dual.ogg`）。
- 日志搜**「双就绪」**：播报一行、`解除（原因）`一行——排查"她为什么又说了一遍"时能直接看出
  是哪一处解除把 CD 刷掉的。

### 二、拉扯：真的能「拉着女仆走」了（新）

- 玩家原话：「我要的拉扯感是可以拉着女仆走，像原版拴绳一样。」
- **【先纠正六百七十六 的口径】** 那一批加的是"按她的速度把绳子中段拖出去"——**只有观感**，
  物理上没有任何力作用在她身上。这一批把力补上（同时把那一层弯曲改成只在**绳松**时出现，见第三条）。
- **【力学是照抄原版的，不是自己编的】** 原版拴绳的力学只有三档，两版字面量**一模一样**
  （1.21.1 `Leashable.tickLeash` → `elasticRangeLeashBehaviour` / `closeRangeLeashBehaviour`；
  1.20.1 `PathfinderMob.customServerAiStep` 里那一段 if-else，两版都对着反编译源码核过）：
  - **距离 > 10 格**：原版**直接撒手掉拴绳**（`LEASH_TOO_FAR_DIST`）。我们的绳子是玩家自己要求的
    「不会断的绳」，所以**这一档不撒手**——照旧按弹性档拉，再远由她自己的牵引绳连人带扫帚传回来；
    这种时候每 5 秒写一行日志「拉扯：她离主人 N 格」，说明原版到这儿就撒手了。
  - **距离 > 6 格**（`LEASH_ELASTIC_DIST`）：每轴 `copySign(d² × 0.4, d)` 的冲量 + `hasImpulse`
    ——**逐字照抄**原版那一记"拽"。
  - **距离 2~6 格**：原版是 `navigation.moveTo(离持有者 2 格处, followLeashSpeed)`，也就是**每 tick
    重新算一条 A\* 路径**（被拴的牛就是这么走的）。女仆一多，每 tick 一次寻路就是服务器灾难，
    所以这一档我们**只补速度、不碰导航**：朝主人的水平速度不足"走到离你 2 格"所需时补到上限
    （0.215 格/tick ≈ 与玩家的行走速度同档），够快就一个字不改（不去跟她的跟随链路打架）。
- **【挂在 `MaidTickEvent` 上，而不是原来那个每 2 tick 的校验】** 两个理由都是字节码实证：
  ① **时机**——TLM 的 `EntityMaid.tick()` 第一件事就是 post 这个事件、然后才 `super.tick()`
  （两版 javap 都是这个顺序），原版拴绳的力也正是"她自己的这一拍里、`travel()` 之前"给的；
  写在服务端 tick 末那只会在下一 tick 被她自己的飞行控制覆盖掉。② **频率**——原版那记冲量是
  每 tick 一次的量级，2 tick 一次等于把拉力砍半。
- **【只对「牵绳档」使劲】** 悬挂档（你已经吊在她身下）那个位置是骑乘定位每拍刚性摆出来的，
  对绳子使劲只会跟它打架（而且悬挂距离最大可配到 6.3 格，一过 6 格就会变成"每 tick 朝自己乘客
  加速"的荒唐场景）——所以判据是"**你此刻没有骑着她**"。也就是说：受力的是玩家自己要的那一档
  （「玩家牵着女仆走、不影响自己的活动」）。
- **开关**：配置面板「移动与行为 → 武装拴绳（二号位）」新增一行 `武装拴绳·拉扯（像原版拴绳一样）`
  （默认**开**）+ 中英翻译键；关掉 = 绳子只画不使劲（她自己的跟随链路照旧工作）。

### 三、绳子：绷紧时是**直线**（改）

- 六百七十六 那层"拖曳弯曲"这一批改了两处：
  ① 拖曳的来源从**她的绝对速度**改成**她相对你的速度**——你俩一起飞的时候相对速度为 0，
  绳子本来就不该弯（旧版那一条物理上是错的）；
  ② 再乘一个**松弛度**：两端实际距离 ≥ 绳长（牵绳档按原版的 2 格余量算、悬挂档就是悬挂距离）
  = **绷紧 = 零弧度（直线）**，只有距离短于绳长（绳真的松了）才按松了多少给弧度。
  **玩家要的"拉扯感"本来就是"绳子绷直了在拽她"**，所以这一层现在只在绳松的时候出现。

### 四、语音与手册

- 内置语音包 manifest 138 → **139** 条（两树字节一致）：新增 `ready_dual.ogg`
  （日语「空襲も箒も準備できたよ、いつでもいけるよ！」，情绪档与 `leash_liftoff` 同档），
  33,903 字节、两树逐字节一致。
- 手册两处：`武装拴绳` 章的四、数值补「拉扯」整段（分档 + 开关），五、排查补那行"拉扯"日志，
  并把面板行清单补上拉扯那一行；`扫帚模式` 章新增一段「两套都齐了她会说一句「我已经准备好了」」
  （条件、只说一次的三种拔法、日志搜什么）。

### 五、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,810,631 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,834,698 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=167 / SKIP=8 / FAIL=0；
`_vt677.py` 只读核对（新判据 / 闩的三处拔法 / 原版四个字面量 / 两树镜像 / 旧写法是否已消失）全过；
两份 guide changelog 字节一致；jar 内容核对（新语音 ogg、manifest 139 条、新配置键与翻译键、
手册新段落进包）。

**上游戏怎么验（四条）**
- **双就绪**：给她鞘翅 + 远程武器 + 烟花（三件套齐），再切到扫帚模式、让她骑上扫帚 →
  约 2 秒后头顶气泡 + 语音「空袭和扫帚都齐了，我已经准备好了！」；日志搜「双就绪」。
  再把她扫帚拿走（或缺件）→ 日志出现「双就绪解除（…）」；放回来 → 她**会再说一遍**。
- **拉扯**：牵绳档（空袭女仆还没起飞、你牵着走）里**跑起来**——她不再只是"自己按跟随的节奏走"，
  而是像被拴绳拽着一样贴上来；跑远到 6 格以上能明显感到那记"拽"（原版那一记 0.4·方向² 的冲量）。
  注意悬挂档（你已经吊在她身下）是**不拉**的——那一档由骑乘定位刚性控制，绳子不受力。
- **绳子**：牵着她走的时候绳子是**绷直的直线**（不再是波浪线）；她贴到你身边（2 格内）绳子才松下来弯。
- **面板**：手册 → ⚙ 模组详细配置 → 「移动与行为」→「武装拴绳（二号位）」最后一行就是新的拉扯开关。
## 实测六百七十六【武装拴绳六期：绳子隔远不再消失 / 不再是一根竖线（原版公式在"直上直下"时是 0/0）/ 两端按帧插值 / 加一层拖曳弯曲；悬挂档右击坐回扫帚不再靠瞄准；二号位重锤猛击（版本号不变，仍是 v1.3.0 beta）】

> 三条实测反馈：①绳子"隔得太远，渲染模型就消失了，而且它没有拴绳特有的物理拉扯效果"；②上个版本修好的"绑定态右击回座位"又没了；③玩家手里拿着重锤、让女仆近战空袭时，能不能吃到重锤的增伤——"我更希望玩家可以吃到这些效果。而不受坐下这个状态影响。"

### 一、绳子"隔远就消失"：是我们自己那道 8 格闸门（修）

- 玩家原话："拴绳隔得太远，渲染模型就消失了……"
- **根因**：绳子渲染里有一道 `MAX_ROPE_DISTANCE_SQR = 64.0`（= 8 格）的跳过判据，本意是"挂载状态过期了别硬画"。
  但它把**牵绳档**一起挡掉了——那一档她（空袭模式还没起飞）跟在你身后走，跟随距离 6 格，加上高度差、
  加上她绕过障碍时的一顿，很容易就过 8 格，而那正是最该看到绳子的时候。
- **现在**：放宽到 64 格（`STALE_STATE_SQR`），闸门只管"这对挂载是不是已经过期"，不再参与"多近才画"。

### 二、"还是一根竖线"：原版那套几何在"直上直下"时是 0/0（修）

- **根因（反编译实证）**：原版丝带的**宽度方向**是 `(dz, dx)` 除以**水平投影长度**（`Mth.invSqrt(dx²+dz²)`），
  而那个除法与"单位化"正好抵消——所以原版丝带的宽度恒为 0.025 格、方向恒为"绳子水平投影的垂线"。
  可我们的绳子是**玩家正吊在她正下方**：两端 X/Z 逐字相等（悬挂档的水平偏移为 0）→ 水平投影 = 0 →
  方向算不出来、宽度也摊不出去 → 24 段全叠成一根竖线。原版拴绳永远不会遇到这一档（被拴的生物总在你旁边晃），
  所以那套几何对我们这个用法是**退化的**——六百七十五 把它逐字搬过来，看着仍然不像绳子，原因就在这里。
- **现在**：近垂直（水平投影 < 0.15 格）时改用**三片 0°/60°/120° 围着绳轴一圈**的带子——任何水平角度看
  都至少有一片是侧对着你的，看上去就是一条圆绳；水平投影够长（牵绳档那种斜着的绳）仍然逐字走原版那一片。

### 三、绳子两端"各跳各的"：没有按帧插值（修）

- 原版两端都走 `Mth.lerp(partialTick, xo, getX())`；旧版直接读 `getX()`，于是绳子只有 20 Hz 的台阶，
  而她本人是按帧插值渲染的——绳子会被"钉"在她上一 tick 的位置上一格格地抽，手感就是一根硬棍。
- **现在**：两端都按帧插值，跟着她的模型走。
- 顺带核对并修正了原版 `addVertexPair` 里两个顶点的竖直错开顺序（旧版第一趟对、第二趟反了，丝带偏了半格厚）。

### 四、"没有物理拉扯效果"：加了一层拖曳弯曲（新）

- 原版那条垂坠（`dy>0 ? dy*f² : dy - dy*(1-f)²`）**只在 Y 上弯**，而"直上直下"的绳子里 Y 就是绳自己的方向
  ——弯了也看不出来；再加上两端是**刚性连接**（她飞、你跟着她，相对位置每 tick 由服务端钉死），
  这根绳子本身确实**不会有任何形变**。
- **现在**：多加一层**拖曳弯曲**——按她本帧的水平位移把绳子中段朝反方向拽出去（峰值落在中点，
  用 `4f(1-f)` 包络；两端仍严格钉在两个锚点上），并做帧间平滑与限幅（上限 0.8 格）。
  效果：她加速 / 转向时绳子被拖在后面，悬停时自己收直。**这不是原版的东西**，是为了"像绳子"刻意加的，
  独立一层、一眼能认出来。

### 五、"绑定态右击坐回扫帚"又没了：那一下右击压根没跑到我们这儿（修）

- 玩家原话："明明上个版本修好的，在扫帚模式的绑定状态下右击可以直接回到座位上，那个效果又消失了。"
- **实测日志给出了确凿证据**：`挂载（悬挂 2.9 格）` 之后十几秒就是 `解除(玩家离鞍)`，中间
  **既没有「换座」也没有「解除(右击)」**——说明那几下右击我们的 handler 一次都没跑到（最后是潜跳下鞍下来的）。
- **根因**：悬挂档里她就在你**头顶上方** 2.6~2.9 格，你的视线基本是水平的（还常在看地形）——
  准星射线扫不到她的碰撞箱，客户端发的是 `ServerboundUseItemPacket`（落到 `RightClickItem`），
  而不是打到实体的 `EntityInteract`。旧版这一档只认"骑着载着她女仆的扫帚"，于是悬挂档右击是**空的**。
  六百七十二 那条"坐回扫帚"本身没错，问题是**它有时候根本不会被触发**（旧版偶尔灵，是射线恰好擦到她）。
- **现在**：`RightClickItem` 这一档补一条认人规则——**你骑着的正好是挂载表里那只女仆**时，等价于右击她
  （走同一个切换：她在扫帚上就"坐回驾驶位"，否则照旧解除）。日志里会写一行「右击（悬挂档，射线没打到她）」，
  下次再"不灵"一眼就能看出走到哪一步。
- 射线打中方块时走的是 `RightClickBlock` 那一档，本项目**没接**（那一档要动方块交互，风险比收益大）。

### 六、二号位重锤猛击：玩家自己手里的重锤也吃下落加成（新，1.21.1 专属）

- 玩家原话："我刚刚在运行游戏的时候，让女仆进行了近战空袭，然后我手里面也拿了个重锤。那么我可以正常触发这个重锤的增伤等效果吗？我更希望玩家可以吃到这些效果。而不受坐下这个状态影响。"
- **答案是"原来吃不到"**（反编译实证，1.21.1）：重锤的下落加成只读 `fallDistance` 一个字段——
  `MaceItem.canSmashAttack` = `fallDistance > 1.5 && !isFallFlying()`，伤害再按 4f / 12+2(f-3) / 22+(f-8) 三段算；
  `Player.attack` 里 `getAttackDamageBonus` 是**唯一**那次读取。而这个字段只在 `Entity.move` →
  `Entity.checkFallDamage` 里累加/清零，**乘客不走 `move`**（`rideTick` 先把速度清零，再由载具的
  `positionRider` 直接 `setPos`，我们的悬挂定位正是这么做的）→ 吊在她下面的你 `fallDistance` 恒为 0，
  一锤都砸不出猛击（暴击也一样：原版暴击判据本身就要求 `!isPassenger()`）。
- **现在**：既然真正在动的是她，就按**你的实际下降**替你记一份，并在 `Player.attack` 的 HEAD 写回
  `fallDistance`（新增混入 `PlayerMaceRideFallMixin`）——之后原版那整套自己就跑通了：伤害加成、
  猛击音效、周围 3.5 格击退、"猛击成功不再吃那一下摔伤"。
- **三条口径**：① **一次下落只换一锤**（取用即清零，同原版 `postHurtEnemy` 的 `resetFallDistance`）；
  ② **悬停 / 慢慢飘着不算**（照原版 `checkSlowFallDistance` 的 0.5 格/tick 口径把累计钳回 1.0，
  低于 1.5 门槛 = 原版就不给猛击），想砸重锤就得真跟着她俯冲一次；③ **她落地 = 清零**。
- 开关：面板「武装拴绳·二号位重锤猛击」（默认开，关掉 = 完全恢复原版）。**1.20.1 没有重锤**，
  那一棵树既没有这个配置项、也不带这个混入（指南里只有一行指向）。
- 手册本章、配置面板详解、中英翻译键都已补上。

### 七、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,763,388 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,787,242 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=167 / SKIP=8 / FAIL=0（新增的那条
`Player.attack` 注入点也由目标类自己声明）；两份 guide changelog 字节一致；`_vt676.py` / `_jarchk676.py`
只读核对（新几何常数与新文案进包、旧写法消失、两树镜像）。

**上游戏怎么验（三条）**
- **绳子**：牵着她在后面走——走到 8 格以外绳子**还在**（以前会消失）；吊在她正下方时绳子是一条**有宽度、
  有编织明暗**的绳（不是一根竖线）；她在加速时绳子中段被**拖在后面**、悬停后自己收直。
- **右击**：绑定态（悬挂档）拿着绳子右击 → 日志出现「右击（悬挂档，射线没打到她）」+「换座：…坐回扫帚驾驶位」
  （扫帚档）/「解除(右击)」（非扫帚档）；不必再把准星对准她。
- **重锤**（仅 1.21.1）：跟她俯冲一次（她下落够 1.5 格以上）→ 在空中左键砸怪 → 有猛击音效 + 范围击退 +
  明显更高的伤害；慢慢飘着砸则没有猛击（与原版口径一致）。
## 实测六百七十五【武装拴绳五期：换绑先松开上一只、魂符残留金边清干净、配置面板改革 + 手册新章节、扫帚跟随 6 格、扫帚档再下沉 0.3、绳子换成原版拴绳（版本号不变，仍是 v1.3.0 beta）】

> 六条实测反馈：①同时绑第二只女仆要把第一只的标记先清掉；②绑定后收进魂符再放出来，"光灵箭那种边框还在、效果却没了"；③最近新增的功能没有配置面板与详解面板、位点反人类；④扫帚跟随要走出去 20 多格才跟，默认该改成 6；⑤扫帚飞行时玩家还要再往下 0.3 格（还有少量重叠）；⑥绳子的视觉效果"根本就不像个绳子，你用的是一根线"，应该套用原版拴绳模型、配色改白。

### 一、一只绳子只牵一只：绑第二只自动松开第一只（新）

- 玩家原话："如果同时用武装拴绳绑定了多个女仆，怎么办？那不炸了吗？这边采用一个机制，如果标记了第2个女仆。那么会自动把第1个女仆相关的标记先清掉。"
- **为什么真的能同时绑两只**：牵绳档（空袭的女仆还没起飞）玩家**不是**她的乘客——既不占座位、也不互相顶开，
  于是旧版会同时存在两条链路：两根绳子一起画，她俩一起飞的时候抢座位（谁先 startRiding 谁赢，另一条下一拍才被"玩家离鞍"判掉）。
- **现在**：绑第二只之前先把第一只**完整松开**——走的是正常解除流程（撤金色描边、清 persistentData、清相位包、
  他要是还吊在她下面就把人放下来、空中继续给 5 秒摔伤豁免），日志里写一行「换绑」。
- 只认"链路里那位玩家 == 你"：别人牵着的女仆，一根都不动。

### 二、魂符收放后残留的金色描边：在"入世界最后一步"清掉（修）

- 玩家原话："如果成功用武装拴绳绑定了一个女仆，然后再将这个女仆收进魂符再放出来，那么那个像光灵箭一样的效果会仍然存在，但是效果却没了。在把他从魂符放出来之后的**最后一步**应该就是将她身上已有的此类边框先清掉。"
- **根因**：金色描边用的是原版发光位（`Glowing` 是**写进女仆 NBT** 的），收进魂符会一起被保存、放出时一起带回来；
  而拴绳链路（LINKS）只活在内存里，收符那一刻就没了。旧版只有"每 30 秒一次的恢复扫描"兜底，而且它要求
  **主人此刻在线**——所以经常看到"边框还在、效果却没了"挂很久。
- **现在**：挂在**入世界事件**上（对魂符放出而言就是最后一步；魂符本身的流程一个字没动）：
  ① 骑乘关系还在（区块重载 / 跨维度）→ 当场把悬挂档链路重建（绳子不会有一段时间消失）；
  ② 其余情况（魂符收放 / 真解除了）→ 把金色标记与残留键一起清掉。
  清理只在"她还带着我们的标记位"时才动手，所以绝不会去动一只"单纯中了光灵箭"的女仆的发光状态。

### 三、配置面板改革 + 手册新章节（新）

- 玩家原话："关于最近新添加的这些功能，都没有配置面板以及详细介绍的相关面板。配置面板你好像做了吧，但是我没看到在哪，可能现在安排的这些位点都比较反人类。玩家很难精准地定位到哪个功能在哪配置。"
- **武装拴绳独立成板块**：「移动与行为 → 武装拴绳（二号位）」——旧版它只能寄居在「扫帚模式」那一页的**末尾**
  （要先点进扫帚、再往下滚才看得到）。现在与扫帚平级、紧跟在它后面，而且**第一条就是"这个功能是什么"的详解**
  （是什么 / 什么时候能绑 / 怎么解 / 画面上的三件事 / 面板在哪），下面每一行脚下还有各自的详细说明。
- **面板顺序按"同一族的挨着摆"重排**：「移动与行为」里现在是 飞行跟随 → 扫帚模式 → 武装拴绳（二号位）
  → 移动与跟随 → 空闲流畅 → 搭路（搭路从这一段开头挪到了末尾）；首页那句提示改成了
  「最近新增（飞行跟随 / 扫帚模式 / 武装拴绳（二号位））都在「移动与行为」」。
- **两个新开关**：`武装拴绳·金色描边标记`（默认开，关掉 = 完全不发光）、`武装拴绳·扫帚额外下沉（格）`（默认 0.3，见第五条）。
- **手册补一整章**「武装拴绳（二号位 · 挂在飞行女仆身下开火）」：怎么绑 / 怎么解 / 画面上的三件事 / 数值 / 排查，
  章末自动带「配置入口」一跳直达面板那一行。（从 667 起这功能只活在更新日志里，正文一次都没提过。）
- 顺带补齐配置文件的**中文 / 英文翻译键**（`config.promaid.tether.*` 以前一个都没有，Forge 的配置界面里显示的是裸键名）。

### 四、扫帚跟随：起手 25 → 6、收手 5 → 3（改）

- 玩家原话："当前扫帚模式必须要在玩家走出超过20格之后才会跟过来。这边最好还是改成默认为6（因为扫帚的容错比鞘翅高太多了。不需要隔那么多，隔近距离跟随即可。同时跟随期间仍然是采用之前的被挡住优先钻空气方块的效果。至于需不需要像鞘翅跟随那样子具有方块视线阻挡判定这一点我不好定夺，你来帮我判断）"
- 起手 **25 → 6**、收手 **5 → 3**（这一对的本意是"一直贴着你飞"：她的跟随点在你身后 3.5 格、高 2 格，
  3D 距离约 4.0 格，所以进不到 3 格以内 = 一直跟着；你停下来她才悬停在你旁边）。
- **只动默认值**：旧存档配置文件里写死的数字不会自动变（面板里改，或删掉那两行重开）。
- **"要不要像鞘翅那样加方块视线阻挡判定"——我的判断是不加**（理由写进了配置项注释与手册）：
  ① 鞘翅那条是"与主人之间被方块挡住就不起飞"，它的退路是**改走路**；扫帚链路没有这条退路，加了她只会在墙后
  原地悬停、而主人越走越远（本项目最忌讳的假死）；② "被挡住"这件事**已经有解**——「卡墙脱困」会先飘到最近的
  空气格再续链路（就是你要保留的那条效果）；③ 3D 视线是每 tick 一次的 raycast，而扫帚链路本身每 tick 都在跑。
- 页面上那一对现在也是**带范围**的输入（2~128 / 1~64，越界当场红字）。

### 五、扫帚飞行时玩家再往下 0.3 格（新）

- 玩家原话："扫帚飞行的时候再把玩家的高度再往下调个0.3格左右吧。现在还是会有少量的重叠。"
- 扫帚模式下她是**骑在扫帚上**的，而扫帚模型比她的脚底还低——同一个「悬挂距离」在扫帚档会显得偏浅。
  新增 `武装拴绳·扫帚额外下沉（格）`（默认 **0.3**，0~2.0 可调）：只在扫帚档（任务在扫帚模式、
  或她此刻正骑着世界里的扫帚）加到悬挂距离上。
- 定向口径仍然只有一处（`GunnerTetherManager.hangFor`），悬挂定位的 mixin 不再自己拼算式。

### 六、绳子：从"一根线"换成原版拴绳同款的丝带（改）

- 玩家原话："当前的武装拴绳连接视觉效果做的根本就不像个绳子，你用的是一根线。应该套用原版的拴绳模型的。也就改个配色为白。"
- **根因**：旧版走的是原版 `RenderType.LINES`（1px 宽的 GL 线、不带明暗），所以永远是一根没有体积的直线。
- **现在整段几何照抄原版拴绳**：宿主类 1.20.1 是 `MobRenderer.m_115461_ / m_174307_`（javap 实证：1.20.1 的
  `EntityRenderer` 里根本没有 renderLeash，绳子是 MobRenderer 画的）、1.21.1 是 `EntityRenderer.renderLeash / addVertexPair`；
  管线换成原版自己的拴绳渲染类型（`RenderType.m_110475_()` / `RenderType.leash()`）；
  24 段三角带 + 两趟错开，围成一条 0.025 格宽、沿长度**扭着**的丝带；竖直方向用原版那条二次曲线
  （`dy>0 ? dy*f² : dy - dy*(1-f)²`）；每个顶点按两端各自的方块光 / 天空光插值。
- **配色按你说的只改这一项**：原版把颜色写死在顶点里——`(0.5, 0.4, 0.3) × (隔一段 0.7 / 1.0)`（棕绳），
  现在三个通道都换成 1.0（**白**），那层 0.7/1.0 的编织明暗原样保留——绳子看着像绳子的关键就在它。

### 七、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,757,419 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,778,190 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=166 / SKIP=8 / FAIL=0；
`_vt675.py` 只读核对（默认值、新方法、旧写法是否消失、两树镜像）全过；两份 guide changelog 字节一致；
jar 内容核对（新文案 / 新配置键 / 新章节进包）。**另外**：1.21.1 那个实例的 `config/promaid-common.toml` 里
`followStart / followEnd / hang / ghostAlpha` 是旧版本写死的值（25 / 5 / 1.8 / 0.35），本次把它们改成新默认
（6 / 3 / 2.6 / 0.1）——否则"改默认值"对这份配置一点作用都没有。

**上游戏怎么验（六条）**
- **换绑**：牵着女仆 A（空袭档、她还没起飞）→ 再对女仆 B 用绳子 → 日志出现「换绑：先松开女仆=A」，
  A 身上的金色描边消失，绳子只剩 B 那一根。
- **魂符**：绑定（牵绳档，有金边）→ 收进魂符 → 放出来 → 她身上**不应该**再有金色边框（日志「入世界清理：」）。
- **面板**：手册 → ⚙ 模组详细配置 → 「移动与行为」→ 新板块「武装拴绳（二号位）」（就在「扫帚模式」后面），
  第一条是功能详解；手册目录里也多了一章「武装拴绳（二号位 · 挂在飞行女仆身下开火）」。
- **扫帚跟随**：让她进扫帚模式、你走开——走出**6 格**她就开始跟（以前要 20 多格）；日志/手册里都写着新默认。
- **下沉**：扫帚模式绑上绳子 → 你和她（以及扫帚）之间不再有重叠，比空袭档低 0.3 格。
- **绳子**：绳子应该是**白色**、有编织明暗、贴着两端光照（阴影里发暗）的丝带，而不是一根直线。

## 实测六百七十四【武装拴绳：半透明加深 + 扫帚也透明、空袭档不再坐到她头上、金边标记、新台词补语音（版本号不变，仍是 v1.3.0 beta）】

> 四条实测反馈：①半透明不够透（默认要 0.1）、而且扫帚也该透明；②空袭档绑完玩家不是吊在身下，而是**直接坐在她头上**；③被拴绳选中的女仆要一层标记（效果同光灵箭射中敌人，光边改成金色），正式起飞时解除；④用 API.bat 把新加的台词也配上语音。

### 一、半透明：默认 0.35 → 0.1，而且**扫帚本体**也一起透明（改）

- 玩家原话："半透明程度不够，默认应该为0.1，而且扫帚也应该是半透明的。"
- 默认值 0.35 → **0.1**；面板那行改名「武装拴绳·第一人称半透明（女仆+扫帚）」，仍然 0.0~1.0（**填 1.0 = 关掉**）。
- 生效范围从"挂在她下面那一档"扩到**两档**：① 你挂着 / 骑着的**那只女仆**（空袭档、扫帚档都算；再加上
  六百七十二 那种"你驾驶扫帚、她坐同一把"的换座态——那一档没有拴绳链路，判据落在"同一把扫帚 + 她是我的人"）；
  ② 你骑的**那把扫帚本体**（自己开扫帚时扫帚模型就在视野里；吊在骑扫帚的女仆下面时也算——扫帚就在你头顶）。
- 仍然**只对你 + 第一人称**生效：第三人称、别的玩家、别人骑别的女仆 / 扫帚，一律照旧不透明。纯客户端。

### 二、修：空袭档绑完"坐到她头上"（根因是少发了一个包）

- 玩家原话："此机制没有引用到空袭状态，而且现在玩家会直接坐到空袭女仆的头上。明明说好的是挂在身下的。"
- **根因**：「牵绳 → 起飞挂载」是在每 tick 校验里把她挂上的，而当时**发的还是绑定那一刻的同步包**——那一刻
  玩家还不是她的乘客，包里 riderId 记的是 -1，客户端收到就把这一对**清掉**了。于是客户端不认"他是她的枪手" →
  悬挂定位的 mixin 整个不生效 → 玩家被原版摆在**她头顶**；牵绳档的绳子也因此画不出来。
- **现在**：同步包多带一个**相位**（0 没挂 / 1 牵绳档 / 2 悬挂档），而且 riderId 从**链路**取（不再问"第一个乘客"）；
  每一次相位变化都重发——绑定、起飞翻档、落地回牵绳、解除、存档恢复、新玩家开始追踪，一个都不漏。
- 顺带把 1.21.1 树漏掉的"牵绳档也要画绳子"补齐（forge 树六百七十三 已改，neo 树还留着"必须是她乘客"那道门）。

### 三、被拴绳选中的女仆：金色描边标记（新）

- 玩家原话："对被武装拴绳选中的女仆加一层标记效果。效果同光灵箭射中敌人时的渲染，但是将光边改为金色。
  正式起飞时解除该标记效果。"
- 用的是**原版自己的发光标记**（`setGlowingTag(true)`：写共享标志位 6、自动同步给所有玩家）——渲染管线与
  "光灵箭射中的敌人"完全同一条：穿墙可见的描边。描边颜色原版取的是"队伍颜色"（没有队伍 = 白），
  这里由一个小 mixin 改成**金色**（0xFFD700）。
- **牵绳档打上、正式起飞（翻悬挂档）的那一刻解除**；解绑、她没了、存档重载后链路重建不出来，这几种情况都会撤掉
  （发光会写进她的 NBT，所以另外留了一个我们自己的 NBT 键，崩溃 / 重载后能认出来清干净）。
- 有意为之的边界：判据是"**发光的女仆**"，所以万一有别的来源（比如她自己中了光灵箭）让女仆发光，描边也会是金色。
  总开关跟着武装拴绳走：功能关掉就一个字都不改，照原版白色。

### 四、新加的台词补上语音（GPT-SoVITS / API.bat）

- 玩家原话："用这个训练新加入的语音。"
- 把六百七十二 / 六百七十三 加进来、但一直没配音的 **5 条**拴绳气泡补了语音（manifest 文本 + 日语台词 + 情绪档）：
  「先跟着我走，等你起飞我再挂上去～」「起飞啦！抓好绳子～」「先落地歇会儿，你牵着绳子等我起飞～」
  「回扫帚上坐好，我接着飞～」「绳子收好啦，我在这儿等着～」。
- 语音条目 133 → **138**；两树 manifest 字节一致，5 个 ogg 两树都在。

### 五、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,744,412 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,765,455 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=166 / SKIP=8 / FAIL=0（两个新 mixin 的注入点也过了）；
`_vt674.py` 只读核对 91/91；两份 guide changelog 字节一致。

**上游戏怎么验（四条）**
- **半透明**：挂在下面时按 F5 切第一人称 → 她的模型**更透**了（默认 0.1）；骑扫帚（含换座共乘）时**扫帚本体**也半透明；
  切第三人称 → 恢复不透明。深浅 / 开关用面板「武装拴绳·第一人称半透明（女仆+扫帚）」，1.0 = 关。
- **空袭档**：空袭模式的女仆站在地上时右击绑定 → 日志「牵绳：」→ 她放烟花起飞 → 日志「起飞挂载：」，
  **这时你应该吊在她身下**（不是坐在她头上）；她落地满 2 秒 → 「落地放人：」。
- **金边标记**：牵绳档（她还没正式起飞）时她身上应该有**金色描边**（穿墙可见）；正式起飞后描边消失。
- **语音**：上面那 5 条气泡现在应该都有声音了。

## 实测六百七十三【武装拴绳：坐扫帚能立刻再绑 + 第一人称半透明 + 空袭改「先牵绳，起飞才挂人」（版本号不变，仍是 v1.3.0 beta）】

> 三条实测反馈，都是"操作上的优化"：①坐在扫帚上右击切不到绑定模式；②绑定飞行时被女仆模型挡视野（但继续降低高度会更容易被打中）；③空袭模式绑定后玩家立刻被挂住、完全没有可操作性。

### 一、坐在扫帚上右击 = 立刻切回绑定模式（修）

- **根因：这一次右击压根没变成"右击实体"**。换座之后玩家在扫帚**驾驶位**、女仆在**第二乘客**（他身后），
  准星前方一个实体都没有 → 客户端射线结果是 MISS → 客户端发的是"使用物品"包，事件落在 Forge 的
  `RightClickItem` 上；而旧版只监听 `EntityInteract`，于是这一下**什么都不会发生**——"右击不灵"就是这么来的。
- **现在补上这个入口**：手持武装拴绳、这次右击**没打到任何实体**、而玩家正骑着一把**载着他女仆**的扫帚时，
  等价于右击那只女仆（走同一个开关：没绑就绑上、绑着就换座/解除）。只认"自己扫帚上的那只女仆"，
  对着空气挥绳子不会触发任何东西；原来那条"射线打到扫帚/女仆"的入口一字不改。

### 二、第一人称下她半透明（新）

- 玩家原话："有的时候还是会被女仆的模型挡到视野。而如果继续降低模型高度会导致玩家的高度太低容易被打中。
  渲染的话最好是改成半透明状态。而且仅限绑定玩家的第 1 视角会展示半透明状态。"
- **为什么只压 alpha 没用（javap 实证）**：女仆模型走 `entityCutoutNoCull`，它的透明态是 `NO_TRANSPARENCY`
  ——**混合根本没开**，顶点 alpha 压到 0.35 也照样不透明。所以改成原版"幽灵渲染"那一档
  （`itemEntityTranslucentCull`，**同一张贴图**，透明态才是 TRANSLUCENT）+ 顶点 alpha 乘系数——正是原版
  隐形实体对旁观者可见时的画法（那一档的 alpha 是 0.15）。
- **只影响她、只影响你**：拦在 `EntityMaidRenderer` 的唯一入口（YSM / Gecko / Bedrock 三条渲染分支都从它进，
  javap 实证），判定 = 总开关开 + 相机第一人称 + 本地玩家正骑着她 + 而且是**拴绳挂上去的**（客户端读 S2C 挂载表）。
  第三人称、别的玩家、别人骑别的女仆一律不动；**服务端一行都不用改**。
- **透明度可调**：面板「武装拴绳·第一人称半透明」默认 0.35（0.0~1.0），**填 1.0 = 关掉**。她的模型本体与
  各图层（鞘翅翅膀、手持物）一起变半透明，绳子照旧画。
- 【实现】包一层 `MultiBufferSource` + 顶点消费者改 alpha；渲染类型靠反射从 RenderType 里读贴图、换成同贴图的
  半透明孪生（**按字段类型找、不写死名字**，两版通用）。取不到就退化成"只换她当前这张贴图那一档"；再不行就
  只压 alpha；全部包在 try 里，异常时按不透明走——**绝不因为渲染崩游戏**。
- 【已知边界】YSM（装了 Yes Steve Model 并用它的模型）走另一套贴图来源，那一档可能吃不到半透明；
  TLM 自带的 Gecko / Bedrock 两套模型都覆盖到了。

### 三、空袭模式：绑定后先「牵着她走」，她起飞才挂人（改）

- 玩家原话："现在的空袭模式是绑定完之后，玩家会立刻处于骑乘状态，只能被女仆任由着拉着走。我希望在绑定完成
  之后是原版的拴绳逻辑是玩家牵着女仆走（不影响自己的活动）。直到女仆起飞。"
- 空袭档多一档**牵绳**：绑定她时如果**她还没起飞**（不在空中，或没在滑翔/出空袭手）→ **不挂人**——
  你照常走、照常打，她跟着你走、绳子照画（绳端在你手上，观感就是牵着她）。
- **她真起飞才把你挂到二号位**；她**落地满 2 秒**再把你放下来、恢复牵绳（放下来那一下照旧给 5 秒摔伤豁免）。
- "起飞"的判据不是 `!onGround()`，而是**空中 +（在滑翔 或 本轮空袭出手进行中）**：普通起跳、从坎上掉下来都不算
  ——否则一挂上去她又落地，就变成两档之间来回翻（671 那轮"不断攀升"的同类病）。
- "拴着就该原地悬停、别再追主人"那五条判据（`MaidFlightFollowBehavior`）改成只认**悬挂档**（`isHanging`）；
  否则牵绳期间她会被按在原地不动，而玩家要的正是"牵着她走"。扫帚那一档一个字没改（永远保持悬挂）。
- 日志新标签：`牵绳：`（绑定成功但还没起飞）、`起飞挂载：`（翻成二号位）、`落地放人：`（恢复牵绳）、
  `解除(牵绳)：`（牵绳档右击收绳）。

### 四、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,744,412 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,765,455 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=162 / SKIP=8 / FAIL=0（新增的渲染 mixin 也过了）；
`_vt673.py` 只读核对 93/93（两树各一遍，含"空袭链路五处 isTethered 全部改成 isHanging"这条）；两份 guide changelog 字节一致。

**上游戏怎么验（三条）**
- **坐扫帚右击可绑**：先绑定一次 → 再右击 = 坐回扫帚驾驶位（日志「换座」）→ **再右击** = 立刻回到二号位
  （这一下以前是不灵的；日志里应该出现「挂载：」）。
- **第一人称半透明**：挂在她下面时按 F5 切第一人称 → 她的模型应该半透明（能看见后面的东西）、绳子还在；
  切第三人称 → 恢复不透明。深浅用面板「武装拴绳·第一人称半透明」（1.0 = 关）。
- **空袭先牵绳**：空袭模式的女仆**站在地上**时右击绑定 → 你应该**还站在地上**、她能跟着你走（日志「牵绳：」）；
  等她起飞（去接敌/自己飞起来）→ 你会被挂到二号位（日志「起飞挂载：」）；她落地 2 秒后你会被放下来（日志「落地放人：」）。

## 实测六百七十二【武装拴绳三期：扫帚上的「换座」两半 + 拴绳不再替你定高度/定座位 + 上扫帚去抖（版本号不变，仍是 v1.3.0 beta）】

> 四条实测反馈：两条是"上一版改错了 / 还没修好"（绑定态右击掉下去、扫帚模式仍攀升），一条是设计口径（乘坐扫帚时右击不能绑定），一条是观感（地面待命时和女仆建模完全重叠）。

### 一、绑定态右击 = 坐回扫帚（把"上一版是对的"接回来，并且做成显式行为）

旧版能"坐回扫帚"靠的其实是一个**副作用**：`resolveMaid` 只看第一个乘客 → 解析不出女仆 → 这次右击**没被取消** → 落到 TLM 自己的
`EntityBroom.interact` 上把玩家接上扫帚（javap 实证它的门是 `!isDiscrete && !isPassenger && !(getControllingPassenger instanceof Player)` 且乘客数 ≤1 → `player.startRiding(this)`），
我们随后的 tick 看到"玩家离鞍"才把挂载表清掉。671 把 `resolveMaid` 改成"扫全部乘客"之后，右击扫帚**总能**解析到女仆，于是稳定走解除分支 = 人直接掉下去
（实测日志 13:48:55 / 13:49:09 的「解除(右击)」）。靠"别取消事件"换座位是**撞运气**（同一版里的 13:49:21 又变成了"接上扫帚"），所以这一版写成**显式**换座：

- **绑定态右击** → 解除拴绳 + 把她请下扫帚、玩家坐进**驾驶位**（第一乘客），再把她放回第二乘客。座位顺序是有意义的：TLM 的
  `getControllingPassenger()` 只在**第一乘客是玩家**时非空，而"玩家驾驶"（`PlayerBroomControl`）与我们 mixin 的"有玩家驾驶就一个字不改"都看它
  ——玩家坐第一 = 他开、她开火，与"玩家自己放一把扫帚再让她上"的天然顺序一致。
- **她不在扫帚上**时右击仍然只是解除（没地方"坐回去"）。整段包在 try 里：任何意外都退回普通解除，绝不让一次换座把绳子卡住。

### 二、乘坐扫帚时右击 = 换到二号位（第 3 条）

`attach` 里那道"玩家正在骑东西就拒绝"（气泡「主人先从坐骑上下来再抓绳子」）671 只放宽了**女仆**骑着扫帚那一档，没放宽**玩家**骑着扫帚这一档
——所以这个场景必然被拒。现在：玩家骑的正好是**这只女仆的扫帚**时放行（先下扫帚、再挂到她身上），其它坐骑（船 / 矿车 / 别人的扫帚 / 别人的女仆）
照旧拒绝。它和第一条正好是一对**互逆**动作。

### 三、拴绳不再替你定高度，也不再让你和她建模重叠（第 1、2 条）

**"女仆不断攀升"是个棘轮**：`MaidBroomBehavior` ② 只要"没扫帚物品且没骑着"就会 `dismount()` 再 `seekBroom()`→`rideWorldBroom()`，
而 `rideWorldBroom` **没有冷却**、**每次骑上都重开一次起飞相位**（目标 = 她当前高度 + 1.35）。实测日志里「骑上了世界里放着的那把扫帚」
在 13:48:55 出现 6 次、13:49:09 十次、13:49:21~22 共 18 次（5~11 次/秒），而「爬升到位」记的 y 是 −60.00 → −58.94 → −9.14 → −53.60 → +3.67：
**每转一圈抬一格，这就是那个"不断攀升"**。三处一起治：

- 起飞相位**去抖**：同一只女仆 5 秒内只登记一次（`startTakeoff`）——棘轮断掉；
- "没扫帚"**去抖**：连续 1 秒真的没扫帚才去找 / 才骑（`broomlessLongEnough`）——不会"上一秒骑上、下一秒又下"；
- 上扫帚 / 起飞这类可能高频的日志**节流**成 5 秒一条（`mountLog`）；下扫帚**补上理由**（`dismount(maid, why)`：总开关关 / 手上没扫帚 /
  缺件 / 行为结束换任务）——旧日志里这四个来源长得一模一样，"她为什么下的扫帚"一个字都查不到，这一版起一眼可见。

**高度口径统一**：671 把空袭那条改成"只保持她自己当前高度"，扫帚那条仍会把她拉到"脚下地面 +3 格"。这一版把扫帚也改成同一个口径
（`MaidBroomDrive.hoverInPlace` = 目标点就是她自己的位置 → 原地悬停），并把 `tetherHoldPos` / `tetherHover` 两个方法**整段删掉**
（空袭那边最后一处消费也改成"她自己现在的位置"）。"她该多高"从此只有一个答案：**她原来多高就多高**；去哪个高度由她自己的战斗链路决定
（接敌那一档排在拴绳之前，一字未动）。

**地面不再重叠（第 2 条）**：悬挂定位现在分两档，判据是**下方有没有空间**（不再看 `onGround`——几何自己就说明了状态，"贴着地面滑飞"也不会漏）：
她在飞 = 吊在下方 hang 格（滑变）；她站在地上 / 贴着地形 = **水平拉开**并排站着（拉开的距离就是绳长，夹在 1.2~3.0；方向沿"她 → 玩家上一拍位置"，
所以是她走你在后头跟着，而不是她一转身你就绕着她荡半圈）。两档之间**有粘滞**（进"并肩"要真的没空间、回"悬挂"要有 0.5 格余量），
中间那条带就是"反复横跳"的解药。

### 四、出包与核对

出 `promaid-1.3.0-forge-1.20.1.jar`（10,744,412 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,765,455 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**尚未发版**。

**本批的核对**：两树 `javac` 0 错误；`_mixchk.py` 注入点审计 PASS=160 / SKIP=8 / FAIL=0；打包前"源码↔class 时间戳"闸门通过；
`_vt672.py`（本批只读核对 104 项）全绿——含"两树新代码翻译后**逐字相同**"与"全树已无 `tetherHoldPos`/`tetherHover` 代码引用"。

**上游戏怎么验（四条）**
- **坐回扫帚**：扫帚模式里先让她飞起来 → 右击绑定（二号位）→ **再右击**应该回到扫帚驾驶位（日志里是「换座」，而且这时你自己能开扫帚）；
  再右击她 = 又回到二号位，来回都要顺。
- **乘坐扫帚时绑定**：你自己坐在她那把扫帚上右击她 → 应该换成二号位（不再冒「主人先从坐骑上下来」）。
- **不再攀升**：绑定后放着不动半分钟，高度应该**一点不变**；日志里不该再出现每秒好几条「骑上了…」。
- **地面并肩**：让她落地待命再绑定，两个建模应该并排站着、拉开 1.8~2.6 格；起飞后自动回到"吊在下方"（这一条的悬挂距离按你配置文件里的值，见下面那条提示）。

## 实测六百七十一【武装拴绳二期：扫帚可靠绑定 + 悬挂不再横跳/爬升 + 空袭改「不会断的绳」（版本号不变，仍是 v1.3.0 beta）】

> 四条实测反馈：两条是 bug（玩家上下横跳、女仆不断上升），两条是手感与规则（视线被挡、空袭的绑定方式）。

### 一、扫帚模式下右击绑定：修可靠性

- **门槛放宽**：她**已经骑在扫帚上**（`MaidBroomKit.isRidingBroom`）就放行，不再要求"此刻不在她面上"。
  低空掠地 / 刚起飞时 `onGround()` 为真，旧版会被「等我飞起来再右击我」挡在门外——这是"右击不灵"最可能的来源。
  她骑着**世界里的扫帚**（任务不是扫帚模式）也算。仍然拒绝：不是她的主人 / 已有人挂着 / 玩家自己骑着别的坐骑 /
  扫帚模式但还没骑上又站在地上。
- **认人**：她坐在扫帚上时客户端射线命中的是**载具（扫帚实体）**，`resolveMaid` 现在扫**全部的**乘客取那只女仆，
  不再只看第一个（玩家同乘 / 别的模组往车里塞了乘客时，第一个未必是她）。
- **可诊断**：拒绝原因现在也写日志（`武装拴绳 … 拒绝：… （女仆=…）`）。气泡只显示几秒、还会被 4 秒节流吞掉，
  光靠它查不出是被哪一条判据拦的。

### 二、玩家上下横跳 + 女仆不断上升：两个各自独立的根因

**横跳** = 悬挂定位是**二值判定且无插值**：下方那一格没空间就"这一拍不改定位"、退回原版"站在她身上"，
下一拍地形变了又吊回 hang 格下面——两个相差两格以上的位置之间每 tick 直跳。现在拆成两步：
① `hangTarget` 挑这一拍最合适的偏移（有空间就用满 hang；没空间就沿她身体往上收，**永不超过她脚底**）；
② `hangCurrent` 把当前偏移**滑变**过去（**上升 1.5 格/tick、下降 0.35 格/tick**——往回收得快，免得把人按进方块；
往下放得慢，这半条就是"别横跳"）。地形起伏时看到的是平滑升降，像船随浪。

**上升** = `tetherHoldPos` 的兜底是**自相对**的：往下 24 格找不到地面时 `ground` 还等于**锚点自己的 Y**，
于是它返回"她自己 Y + 3 格"——目标永远是"比她现在高 3 格"，且跟着她一起抬、永远追不上：
空袭那边每 tick 抬 0.30 格（vy 上限）、扫帚那边 `steerTo` 的到达判定永远不成立，两条链路一起无限爬升。
现在扫描放到 64 格，**找不到地面就保持她自己现在的高度**（高空 / 虚空 / 末地上空不再爬）。

**顺便排除了一条**：javap 实证原版 `Entity.push` 对"载具与其自身乘客"**本来就互推豁免**
（1.20.1 走 `m_20365_` = `getRootVehicle()` 同一性判定，1.21.1 走 `isPassengerOfSameVehicle`），
所以上面这两条都不是碰撞互推造成的。

### 三、视线被女仆的建模挡住 → 悬挂距离默认 1.8 → **2.6**（可调 0.5~6.0）

2.6 让她的脚底**高过你的视线**（玩家眼高约 1.62 格），前方视野让开。
你第 4 条说的"绳长两格"和第 3 条选的"默认 2.6"其实是**同一个旋钮**（= 你挂在她下方多远），
按一个值做：默认 2.6，面板里可以调到 2.0。定位现在是滑变的，调大调小都不会横跳。

### 四、鞘翅空袭的绑定：改成「不会断的绳」

- **不再悬空起程**（你点名的"空袭就注定了不适合这样"）：空袭那条链路**不再写任何高度**——只把俯仰摆平
  （低头滑翔会被原版物理一路往地里拽）+ 水平速度按 0.72 收干，竖直速度原样留着；她爬升/俯冲由她自己的
  空袭链路决定，绳子只负责"把主人吊在下面"。扫帚那条按你的要求**保留**"离地 3 格原地悬停"。
- **不会自己断**：删掉"她落地累计 0.6 秒就自动放人"那一档（这条正是"绳子会断"的来源）。
  **只留入水**：你还吊在她下方 2.6 格，落水会跟着进水，溺水是致命的（这一档留着救命）。
  主动解绑照旧：**再右击一次 / 潜跳下鞍**。
- **玩家同款免摔**（你第 4 条的最后一句）：挂着期间除了原有的卡墙(`inWall`)/挤墙(`cramming`)，
  再免**摔落(`fall`)**与**撞墙飞(`flyIntoWall`)**——与女仆那边 `MaidFlightWallGuard` 的两条**同一个 msgId**，
  口径只有一处（那里是 `FLY_INTO_WALL` / `FALL` 两个常量）。解绑后原有的 5 秒空中豁免不变。

### 本次没动的

- **手册**：`GuideChaptersCombat.java` 里**没有拴绳那一节**（全文搜过），所以本次没有手册改动可做。
- **接敌与开火链路**：一个字没动（扫帚/空袭的索敌、开火、盘旋全部照旧）。
- 物品名与物品本身没动：你说的「战术拴绳」就是现在的**武装拴绳**（`maid_smart:combat_leash`，合成：拴绳 + 铁锭×2）。

### 验证

两树 `javac` **0 错误**；注入点审计 **0 FAIL**（160 项）；打包门禁全过；jar 内复核 0 FAIL；
出 `promaid-1.3.0-forge-1.20.1.jar`（10,744,412 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`（10,765,455 B），
**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。**本条尚未发版**——等你实测。

> 【测这个】
> ① **扫帚**：让她起扫帚（**低空掠地也算**），手持武装拴绳右击她/扫帚 → 应当挂上；日志搜「武装拴绳」看`挂载`。
>    **如果还不灵**：日志搜「武装拴绳 … 拒绝」——那一行会写明卡在哪一道门（这次专门为这个加的）。
> ② **横跳与爬升**：挂上后飞高、专门掠起伏地形/树林，你应当**平滑升降**；她**不再一路爬升**（悬停/接敌照旧）。
> ③ **空袭**：切空袭模式再挂 → 她照常自己爬升俯冲（不再被按到"离地 3 格"），绳子不会自己断；
>    从高空松手/落地，**你不掉血**（同款免摔）。
> ④ 高度觉得不合适：面板「武装拴绳·悬挂距离」直接调（0.5~6.0）。

## 实测六百七十【魂符文案纠正 + 末地水晶底座回收"飞远了也收得回来"（版本号不变，仍是 v1.3.0 beta）】

> 两条都是小改动：一条是旧文案残留，一条是回收链路的健壮性。

### 一、魂符那句子：`生命值过低` → `受到致命伤害`

「低血量自动回魂符」在**实测四百零三**就被收紧了：只有**致死伤害**（伤害 ≥ 当前血量）且身上
没有保命物品时才收符，低血量**不再**触发（低血量交给自保：喝药 / 搭高 / 逃跑）。但**提示语还留着
旧版措辞**——她明明是被一击致命的，你看到的却是「你的女仆生命值过低，已回到魂符中」，
读起来像"血少了一点就收符"。现在两处提示（普通那句 + 带 CD 那句）与手册里引用的那一句统一改成
**「你的女仆受到致命伤害，已回到魂符中。」**，与实际触发口径对齐。

### 二、末地水晶炸完那块黑曜石：**飞远了也要收得回来**

现象（你的原话）："飞得太远之后黑曜石就收不回来了。"

**根因是"回收条目先删、后干活"**，而那件活会碰"那块黑曜石所在的区块"：

1. 她飞远了，**底座那块区块早已卸载**（她的区块票跟着她本人走，不会替几十格外的一片地面续命）；
2. 到期回收要读那一格方块，`level.getBlockState(pos)` 于是走 `Level.getChunkAt(pos)` →
   `getChunk(x, z, FULL, requireChunk=true)`——**javap 实证**：`LevelReader.m_46819_` 里就是
   `iconst_1`，也就是"没加载就**同步强制加载**"。轻则在服务端 tick 里**同步加载一片地形**（卡帧），
   重则加载失败**直接抛 `IllegalStateException`**（失败分支见 `ServerChunkCache.m_8421_` 与
   "No chunk holder after ticket has been added"）；
3. 旧版 `ri.remove()` **写在** `removePlaced` **之前**，整段又只有一个 try/catch——异常一抛，
   条目已经没了：**这一块黑曜石从此既不被撤掉、也不还她背包**；同一次异常还会连带跳过
   **同一个 tick 后面的起爆处理**（后面那一发也跟着卡一拍）。

顺手一起治掉的三处同源问题：

| 问题 | 旧版 | 现在 |
|---|---|---|
| 区块没加载 | 在 tick 里同步强制加载 / 抛异常丢条目 | **整条留着，下一 tick 再试**（先用 `Level.isLoaded` 判，绝不动没加载的区块） |
| 她本人不在世界里 | 抓住 10 秒前那个实体引用硬塞背包（魂符收起 / 区块重载后是**另一个对象** → 物品塞进死对象 = 凭空消失） | 每次回收**按 UUID 重新解析**（口径同 `PlacedBlockTracker.findMaid`，跨维度也算）；找不到人就**留着等她回来** |
| 这一发中途作废 | 她死了 / 水晶实体没了 / 迟迟炸不了 → 只丢炸弹，**那块黑曜石永远留在地上** | 三条路都走同一个 `settleBase`：水晶链路登记到期回收还她，重生锚 / 床照旧直接撤掉（它们那一炸自己就把方块消耗了） |
| 回收表满 | 与待起爆共用 256 个名额，满了**静默丢弃** | 单列 1024 个名额，满了**在日志里说话** |

**可诊断**：日志搜「回收底座」看成功回收（还了谁）、搜「底座回收再等等」看为什么还在等
（区块没加载 / 她不在世界里；同一块底座只报一次，不刷屏）。

**验证**：两树 `javac` **0 错误**；注入点审计 **0 FAIL**（160 项，那道闸门已接进打包脚本）；
打包门禁全过；jar 内复核 **0 FAIL**（新文案在、旧文案已清除、`removePlaced` 的新签名与三个
新方法都在 jar 里）；出 `promaid-1.3.0-forge-1.20.1.jar`（10,532,026 B）/
`promaid-1.3.0-neoforge-1.21.1.jar`（10,553,245 B），**同名覆盖**，版本号仍是 1.3.0；
部署三处（两个客户端实例 + 服务器整合包）。**本条尚未发版**——等你实测。

> 【测这个】拿黑曜石 + 末地水晶去打怪（近战空袭 / 远程空袭都行），**放完立刻飞远 50 格以上**，
> 过 10 秒回来看：那块黑曜石应当**已经不在原地**，东西回到她背包；`latest.log` 里会有
> 「回收底座 … 还回 …」。如果走的是"等区块"那条，会先出现
> 「底座回收再等等：那块底座所在的区块还没加载 → 留着…」——两种日志随便发我哪一种都能定位。

> 【本次没动的】"她把底座放下去、然后自己死了"那条**相位回滚**（`tickPhases` 里那个 `rollback`）
> 仍是旧口径（撤掉、不回背包）。它和本次的现象不是同一条路，要一起改成"还她/落地"说一声。

## 实测六百六十九【武装拴绳五条实测反馈（门槛/右击优先级/绑定后悬停）+ 新语音 + 白贴图（版本号不变，仍是 v1.3.0 beta）】

> 先谢实测。上一批把游戏搞开不起来是我的错（注入点选错了类），这一批先把"能不能用"补齐。

### 一、"必须飞在空中才能绑"太窄了 → 扫帚照旧要求空中，**空袭模式随时可绑**

你说得对：鞘翅飞行时玩家根本追不上，这条门槛等于把功能锁死在扫帚上。现在的门槛分两档：

| 她的状态 | 能不能挂 |
|---|---|
| **扫帚模式**（`maid_smart:broom`） | **必须已经飞在空中**（原样保留——地面上挂上去她会把人拖进地里） |
| **空袭模式**（`flight_combat` / `flight_ranged`） | **随时能挂**，站在地上也行 |
| 其它模式 | 不挂，她会提醒你「扫帚或空袭模式时再抓绳子吧～」 |

**"在地面里窒息"这一条是单独治的**：悬挂点（你脚底那一格 + 头顶那一格）如果**不是空气**，
这一拍就**不把你按到她下面**，而是照原版站在她身上——等她自己升起来（见第三条）再自然吊下去。
判空间的代码就在那个 mixin 里，两格都查、异常时按"照旧吊着"处理（绝不因为一次判定失败把机制卡死）。

> 【我替你做的解释，错了就一句话改】你写的"直接套用拴绳的代码"，我理解成
> **复用现有这套拴绳/悬挂机制**（而不是给空袭模式另写一套原版拴绳拉力）。所以空袭模式绑上以后
> 你仍然是挂在**她身上**、只是不会再被按进地面。如果你要的是"空袭模式下她像被拴绳牵着的宠物那样
> 跟着你走、你并不挂在下面"，那是一条**新机制**（要自己做拉力物理），说一声我再做。

### 二、手持拴绳右击骑扫帚的女仆 = 骑上扫帚了 → 现在**绑定优先**

**根因不是"事件优先级不够"**（我原以为是）：我们的交互处理其实**跑在** TLM 的
`EntityBroom.interact` 之前，cancel 掉就能挡住那次 `startRiding`。真正的问题是**目标认错了人**——
她骑着扫帚时，客户端射线命中的是**载具（扫帚实体）**，事件里的"目标"是**扫帚**而不是女仆，
旧版 `instanceof 女仆` 直接不成立就 return，交互于是落到 TLM 那边去骑乘了。

现在：目标是扫帚 → 自动解析成**它背上的那只女仆**，然后照常走绑定/解除。
手持武装拴绳右击 = 绑定优先于骑乘（想骑扫帚就换手，或者别拿拴绳）。

### 三、"绑定后她一直在空中转圈圈" → **升到离地 3 格原地悬停，接敌不变**

转圈的成因：绑定后她的"跟随主人"逻辑照旧在跑，而主人（你）现在就在她身下——
她每 tick 都在追一个永远差一点的点，绕着你自己转。现在**被拴绳绑住时**：

- **扫帚模式**：不再跟随、不再绕工作范围巡逻 → 目标点改成「她脚下**地面 + 3 格**」那个**定点**，
  停在那儿。锚点取**扫帚**的坐标（她是乘客、座位在朝向后方半格，拿她的坐标当定点会永远差半格
  ——那个"空中不停旋转"的老回路，注释里早就写过）。
- **空袭模式**：同一套口径（共用同一个定位函数），她不再去追主人；空袭是滑翔物理、没有扫帚那种
  意图队列，所以是每 tick 写速度（水平收干、竖直朝目标高度修正、俯仰摆平），**地面上则先起跳**。
- **接敌一个字没动**：有敌人时，控制权照旧交回她自己的空袭/开火链路（她该绕该打都照旧）。
  悬停高度 3 格现在是一个**常量**（`GunnerTetherManager.tetherHover()`），想调只改那一行；
  要变成面板可调项也是小改，你说要我再加。

> 【空袭那一支是**第一版**，我测不了】滑翔物理下的"悬停"本来就是逆着原版物理做的，
> 观感（会不会有轻微起伏/下沉）要你实测。扫帚那一支是确定性的（跟原版"没有输入"同一套收干逻辑），
> 应该很稳。哪一支不对就告诉我是"扫帚"还是"空袭"。

### 四、新文本的语音：**已训练 9 条**；有语音的文本从**蓝色改成粉色**

- **语音**：武装拴绳的 9 条气泡全部进语音包（挂上/到站/以及七条"现在还不行"的提醒），
  走的是原来那套流程（GPT-SoVITS 合成 → 峰值归一化 → 装进两棵树 → 打进 jar）。
  manifest 从 124 条变 **133 条**（两棵树逐字节一致）。现在挂她、松她、以及"这不是我的主人"
  这类提醒都会**出声**了。
- **颜色**：你要求"有语音的文本别用蓝色、改粉色"——会送去 TTS 的正是**系统对话气泡**这条通道，
  所以把它的青色 `§b`/`AQUA` 全改成粉色 `§d`/`LIGHT_PURPLE`：世界内气泡、聊天框里
  `[名字] 文本` 的前缀、以及投喂蛋糕那条（它单独走一条链路、原来是深蓝 `§9`）。
  **敌袭警示的红色 `§c` 保留**（那是危险提示，不是"有语音"）。手册/配置界面里那些青色是 UI
  配色、跟语音无关，**没动**。

### 五、贴图：用**原版拴绳**，重上色为**白色**

不再是像素手绘的那张：直接取原版 `minecraft:item/lead` 的 16×16 图，把颜色整体拉成白
（保留绳子内部的明暗与轮廓线，最亮处是纯白）。两棵树贴图逐字节一致。

**验证**：两树 `javac` **0 错误**；注入点审计 **0 FAIL**（160 项，那道闸门已经接进打包脚本）；
本批核对脚本 **0 FAIL**；打包门禁全过；jar 内复核 **0 FAIL**（9 条语音 ogg、133 条 manifest、
白色贴图、五个新方法都在 jar 里，且**顺带回归确认**那个 mixin 目标仍是 `Entity`）；
出 `promaid-1.3.0-forge-1.20.1.jar`（10,526,032 B）/ `promaid-1.3.0-neoforge-1.21.1.jar`
（10,547,270 B），**同名覆盖**，版本号仍是 1.3.0；部署三处（两个客户端实例 + 服务器整合包）。
**本条尚未发版**——等你实测。

## 实测六百六十八【紧急修复：实测六百六十七 的「武装拴绳」会让游戏开不起来（版本号不变，仍是 v1.3.0 beta）】

### 一、症状：装上 667 的包，游戏在 Bootstrap 阶段就崩，连主菜单都到不了

NeoForge 1.21.1 的日志原文（玩家反馈）：

```
Mixin apply for mod promaid failed mixins.promaid.json:EntityMaidGunnerMixin from mod promaid
  -> com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid:
org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException Critical injection failure:
@Inject annotation on maidsmart$hangGunnerBelow could not find any targets matching 'positionRider'
in com/github/tartaricacid/touhoulittlemaid/entity/passive/EntityMaid. No refMap loaded.
```

**这个错误是我们的，而且 1.20.1 那一份有同一个毛病**（只是你先在 1.21.1 上撞到）。

### 二、根因：mixin 的注入点写在了"继承来的方法"上

「武装拴绳」要把挂着的玩家摆在女仆**下方**，做法是拦原版那个"把乘客摆到载具身上"的方法
（`positionRider`）。问题出在**我们把这个注入点写在了女仆身上**：

- Mixin 的 `@Inject` **只在目标类自己声明的方法里找注入点**，继承来的方法一律找不到；
- `positionRider` 只在原版 `Entity`（一切实体的基类）里声明，TLM 的女仆
  （`EntityMaid extends TamableAnimal`，整条继承链 javap 逐个查过）**没有任何一处覆写它**；
- 于是 Mixin 在女仆身上找不到 `positionRider` → 判定这份 mixin 配置失败 → **启动期直接崩**。

说白了：**能编译 ≠ 能加载**。javac 只检查你写的注解字符串是不是合法 Java，不会去问
"目标类到底有没有这个方法"；这个检查是 Mixin 在**游戏启动时**做的，做不过就是崩，
而且崩在 Bootstrap——比崩在游戏里更糟，玩家连界面都看不到。

### 三、改法：注入到"真正声明它的那个类"，再加一句守卫

改成 `@Mixin(Entity.class)`（`positionRider` 真正的声明处），处理器第一行加
「这只载具必须是女仆」的判断——世界上**别的所有载具**（船 / 矿车 / 马 / 别的模组的坐骑）
走完原版逻辑，我们一个字都不碰；只有"拴绳挂载的那位玩家 + 女仆当载具"这一种组合会被改定位。

顺带把两树的**运行时名字口径**写死并加注（这是本项目手工编译、没有 refmap 的必然要求——
日志里那句 `No refMap loaded` 就是这个意思，注解里的方法名会**逐字**拿去匹配运行时名字）：

| | 1.20.1 Forge | 1.21.1 NeoForge |
|---|---|---|
| 运行时方法名 | SRG 名 `m_19956_` | 官方名 `positionRider` |
| 摆位置的回调 | `MoveFunction.m_20372_` | `MoveFunction.accept` |

（两树的名字各自都对，这次崩的不是名字，是"找错了类"。另外 1.21.1 的 `positionRider`
有 1 参/2 参两个重载，所以现在连**完整方法描述符**一起写上，不给歧义留空间。
这条实证也写进了 mixin 的类注释，免得以后有人"顺手改回女仆"。）

### 四、防复发：新增一道**注入点审计**闸门（这次就靠它查干净了）

既然 javac 管不了这件事，我们补了一个只读检查脚本，把**两树 74 个 mixin 的每一个注入点**
都拿去对照 javap：**目标类是不是真的自己声明了这个方法**（拿两个版本的真实客户端 jar +
TLM jar 当依据，名字口径也按各自的运行时名核对）。

本次全量结果：**158 项注入点全部通过，唯一的两个失败就是崩掉的那个 mixin**（两树各一份）——
也就是说这次事故之外，**没有别的埋雷**（同一类问题不会在下一个版本再炸一次）。
这个脚本以后每次打包前都会跑。

### 五、拴绳功能本身的口径没变（等你测）

- 手持**武装拴绳**右击**正在飞行**的女仆 = 挂上去（她必须已经在空中，地上她会提醒你）；
- 再右击 = 解除；你潜跳自行下鞍、或她落地/入水超过 0.6 秒，也会自动把你稳稳放下；
- 悬挂距离默认 **1.8 格**，面板里可调 0.5~4.0；
- 挂着的时候卡墙/挤墙伤害全免（贴着树冠飞是常态）；空中松手有 **5 秒摔伤豁免**；
- 她照常飞、照常开火（她不会因为背上驮了人就被抢走驾驶权）；绳子是我们自己画的，
  女仆腰部到你手之间那条（不会扯断，也不用真的物理绳）。

**验证**：两树 `javac` 0 错误；注入点审计 0 FAIL；打包门禁全过；出
`promaid-1.3.0-forge-1.20.1.jar` / `promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，
版本号仍是 1.3.0）；部署三处（两个客户端实例 + 服务器整合包）。
**本条尚未发版**——等你实测。

## 实测六百六十七【武装拴绳：挂在飞行女仆下方开火（版本号不变，仍是 v1.3.0 beta）】

### 粉丝点单：「酒狐在空中飞的时候，可以让玩家挂在酒狐下方吗？」——武装直升机二号位

粉丝原话：「众所周知酒狐会魔法，酒狐在空中飞的时候，是否可以让玩家挂在酒狐下方？让玩家能够
与酒狐一同攻击，八宝粥行动那样武装直升机，玩家挂在2号位开火。这边要引入一个新道具，应该类似
于一个不会扯断，而且长度只有一格的拴绳。可以将玩家和女仆锁在一起。使用方法跟拴绳一样，右击
女仆就行。解除也是同样的方式。」

**新道具「武装拴绳」**（合成：拴绳 + 铁锭×2，也进了创造物品栏「工具与实用品」）：

- **右击飞行中的女仆** = 把自己挂到她下方（默认 1.8 格，正是"长度只有一格"的拴绳 + 玩家身高）；
  **再右击一次或按潜跳** = 解除。只认主人、一只女仆同时只挂一人；
- **她照常飞、照常开火**：字节码实证 TLM 的女仆没有覆写"驾驶权"那个方法——你挂上去不会
  抢走她的控制权，她的远程空袭 / 扫帚模式照旧跑（一号位她开火，二号位你开火，各打各的）；
- **为什么是"真挂载"而不是绳子拉力模拟**：乘客跟着载具走是原版机制，俯冲/爬升/烟花加速
  /多人同步全部自动正确；自己写弹簧拉力会跟玩家的本地操作打架（橡皮筋+抖动）；
- **安全网**：她落地/入水超过 0.6 秒自动把你放下（挂着拖地/闷水里是不可能了）；解除瞬间
  人在空中给 5 秒摔伤豁免；挂着时卡墙/挤墙伤全免；她本人对你的伤害本来就被「主人/友军
  免伤总闸」三层拦着，挂多紧都不会被自己人打中；
- **可见**：服务端把"谁是她的二号位"同步给客户端，女仆腰部到你的手之间画一根带下垂的麻绳
  （与轰炸标记同一套渲染管线）；
- **存档安全**：挂载标记写在女仆身上，存档重载后自动恢复；配置面板「战斗与自保 → 扫帚模式」
  板块末尾新增「武装拴绳」三行（总开关 / 悬挂距离 0.5~4.0 可调，越界红字）。

**想用**：给她配好远程武器切到飞行任务 → 等她飞起来 → 手持武装拴绳右击她。

**验证**：两树 `javac` 0 错误；骑乘链路逐条字节码实证（force 骑乘的门槛、乘客定位入口、
驾驶权归属）；打包门禁全过；出 `promaid-1.3.0-forge-1.20.1.jar` /
`promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
**本条尚未发版**——等你实测。

## 实测六百六十六【枪械"打个几发就哑火" + 面板「散步间隔填了没反应」（版本号不变，仍是 v1.3.0 beta）】

### 一、粉丝留言：远程鞘翅模式下蓄力枪械 / 重型武器"哑火"——两个成因都在我们这边

粉丝原话：「远程鞘翅模式下对蓄力枪械和重型武器的判定有点问题，通常打个几发就会"哑火"，
女仆会一直绕圈但不射击，如果在地面则会挨揍。」

**成因 1：弹药判据比枪械 mod 自己的松（"弹匣那几发打完就再也不会响"）**

旧版散装弹药那条是"背包里**任意** `tacz:ammo` 都算"（卓越前线那边"5 类弹药任意一类都算"），
而两家的开火门都不是这么判的（反编译实证，TACZ 1.1.8-hotfix / SBW 0.8.9.1）：

- TACZ：`IGun.hasInventoryAmmo(女仆, 枪, true)` 扫她整个背包，逐个槽位问的是
  `IAmmo.isAmmoOfGun(枪, 弹药)`——**口径必须对得上这把枪**（另有弹药箱 `isAmmoBoxOfGun`）；
- 卓越前线：`GunItem.canShoot` → `GunData.hasEnoughAmmoToShoot(女仆)`
  = 弹匣里（用背包弹的枪则是背包里**这把枪认得的**那类弹）够不够打一发。

于是「背着步枪弹、手里却是重机枪 / 火箭筒 / 蓄力枪」这种口径对不上的局面下：我们判"有弹药"
→ 飞行远战 / 扫帚模式照旧激活、气泡不报缺件、**她一直绕圈**；而枪那边每一发都是
NO_AMMO（TLM 收到只会去换弹，喂不上就永远在重装），或 canShoot=false（卓越前线那一支直接
返回 50，只扔一颗手雷）。**枪里预装的那几发打完，这把枪就再也不会响**——正是"打个几发就会
哑火"；而飞行远战的地面支路只有开火、不近战，所以"在地面则会挨揍"。

现在：新增 `GunCompat.canFeed(女仆, 枪)`，把整道判定交给枪械 mod 自己（反射调上面那两个
方法，与它们字节码逐句对齐；**反射不可用时退回旧口径**，绝不因为兼容层异常就判她"没弹"）。
模式门禁也改成**按手上这把枪**判（旧版是"身上任意一把枪有弹就算"）。

**成因 2：卓越前线的枪械状态机被我们"节流"了（蓄力枪的充能永远走不完）**

`GunCommonUtil.tick` 在 TACZ 那一支是空操作，在卓越前线那一支是 `GunData.tick(女仆, true)`
——**充能 / 换弹 / 拉栓 / 散热计时器的唯一推进者**。旧版这一句写在开火方法的射程 / 视线 /
冷却**三道门之后**：只有"轮到她开火的那一拍"状态机才走一步。于是冷却 40 tick 的重武器装一发
弹要 80 秒，蓄力枪更直接——**充能永远走不完 → canShoot 恒假 → 永不开火**。

现在：新增 `tickGunState`，每 tick **恰好一次**（同一 tick 幂等，多路调用不会加速计时器），
并在**所有门之前**推进。日志「远程开火」那一行也加了 `弹=有/无`，返回值读法按平台分开印
（卓越前线的 50 = canShoot 假、20 = 开镜或慢射速）。

### 二、面板「散步间隔填了没反应 / 是不是不可调」——越界值被静默吞了

**证据（不是猜）**：你两处实例的 `config/promaid-common.toml` 以及它旁边 5 个 `.bak` 里，
`strollInterval` **一直是 200**、`strollRadius` **一直是 16**——你填的 4000000 / 3 一个都没落盘。
所以她仍按默认 **200 tick = 10 秒** 在散步（"一直乱跑"）。

而这两行的合法范围是 间隔 **20~24000**、半径 **4~128**，你填的两个数**都越界**；更糟的是
越界**没有任何提示**：输入框只校验"是不是数字"（越界照样白字），保存时 setter 的返回值被直接
忽略，于是一声不响就丢了（Forge 的 `ConfigValue.set` 也不校验范围——反编译实证它只往文件里写，
范围要等**下次加载**才被钳），观感就是"这个数调不动"。

改了三处：

- 「散步间隔」上限 20 分钟 → **1728000 tick（24 小时）**：想让她基本不散步就填大值；
  面板该行说明与配置文件注释里都写上了范围（旧版一个字都没写，玩家当然不知道有上限）；
- 面板输入行**支持声明范围**（`NumRow` 新增可选的 min/max）：越界**当场红字**，保存时
  **钳到边界并明确告诉你按多少生效**（不再静默）；没写成功的行会列名提示 + **不关面板**，
  让你当场改；
- 面板内跳转（目录 / 板块 / 手册链接）离开前**先保存一次**——旧版只有「保存并返回」/ Esc
  会写配置，用跳转按钮离开 = 输入框里刚填的数整段丢掉。

**想立刻让她别乱跑**：把「空闲散步」开关**关掉**（开关是即时生效的），或把「散步间隔」填成
范围内的值（现在最大 1728000 = 24 小时）。

**验证**：两树 `javac` 0 错误；打包门禁全过；出 `promaid-1.3.0-forge-1.20.1.jar` /
`promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
**本条尚未发版**——等你实测。

## 实测六百六十五【烧制面板「看不到食物」——加分类入口与一键填入（版本号不变，仍是 v1.3.0 beta）】

玩家反馈：「我在设置面板，确实看不到食物。」

**先说是怎么回事（日志实证，不是猜）**：他自己的 `promaid.log` 里那条面板诊断写着——

```
[烧制清单] 候选网格 148 件 | 档位=烧制物品 | 世界=有 | 代表物品=
    minecraft:beef=真 porkchop=真 chicken=真 potato=真 kelp=真 iron_ore=真 sand=真 bread=假
```

也就是说**判定没错、食物就在那 148 个候选里**（beef / 猪排 / 鸡 / 土豆 / 海带都过判据；`bread=假`
也是对的——面包没有炉子配方）。看不到的原因是**这一格太大又没分类**：一格 16×3 = 48 件、
148 件要翻 4 页，食物散在中间；而另一档「燃料」本来就该没有食物（食物不是燃料）。

**这一版做的（只动面板 + 一处只读数据源，行为判定一个字不改）**

- 「烧制物品」这一档顶部新增三个筛选按钮：**全部 / 只看食物 / 只看矿物**——点一下「只看食物」
  就只剩那十来件，一页看完；
- 同一行再加一个 **「填入原版食物」** 一键：把内置食材清单（当前世界真有炉子配方的那几件）
  填进「烧制物品」名单 = **只烧食物**。名单留空时本来就是自动判定（先食材后矿物），所以这一步
  是"钉死"，不是"否则烧不了"；想回到自动就把名单清空；
- 判据只有一处：`MaidCookBehavior` 新开三个**只读**口（`builtinFoodIds` / `isBuiltinFood` /
  `blastableForPicker`），面板不再自己抄一张表——避免"面板说能烧、她却不烧"；
- 燃料那一档不动（候选语义不同，不挂分类按钮）。

**验证**：两树 `javac` 0 错误；打包门禁全过；出 `promaid-1.3.0-forge-1.20.1.jar` /
`promaid-1.3.0-neoforge-1.21.1.jar`（**同名覆盖**，版本号仍是 1.3.0）；部署三处。
**本条尚未发版**——等你实测。

## 实测六百六十四【七条反馈落地：扫帚与守家、卡墙脱困、三条新档、圈心兜底、火把优先、食物清单；版本号回到 v1.3.0(beta)】

> 玩家原话：「你版本号更新的太快了。我们现在其实更多像是一个 beta。而不是任何一个正式的
> 版本号。现在最多算 v1.3.0 版。」——所以本版把版本号从 1.3.8 **收回 1.3.0**（beta 口径，
> 之后只在这个号上迭代；平台上一个正式附件仍是 v1.2.3，未动）。

### ① 扫帚 + home：不再响应排班表的传送

玩家原话：「开 home 的扫帚模式，女仆不应该响应排班表的传送。」

事实是：她**骑在扫帚上**时 TLM 的传送本来就抓不住她（`SchedulePos.tick` 那句
`if (!canBrainMoving()) return;`——乘客/坐着/睡觉时 `canBrainMoving()` 为 false），
但**下扫帚那几拍**（缺件待命、刚落地）会照常被「出圈就传送回工位」抓走；而她要飞来飞去，
被拽回圈心一次就得重新起飞。

- `SchedulePosTickMixin`（每 2 秒的周期拉回）与 `SchedulePullbackMixin`（切班那一拍）的
  「干活不打断」豁免各加一条 **`MaidBroomKit.isBroomTask(maid)`**：扫帚模式整段按干活处理
  ——圈心照常刷新，传送/走回一律跳过（口径收在 `maidsmart$exempt` 一处）；
- `MaidTeleportPreserveMixin` 补上**计划里漏掉的那一根线**：滑翔那条早就豁免了
  `teleportToOwner`（原版走 `Entity.teleportTo`，它第一件事是 `unRide()`，会把她从扫帚上
  踹下来），扫帚模式漏了——现在 `isBroomAirborne` 一起豁免；
- 其余 home 口径本来就与其他模式同一处（目标点夹取走 `WorkAreaClamp` 同一对字段、
  半径吃 `combatWorkRange` 的同一次放大、接敌优先于守家），没有第二套。

### ② 扫帚撞墙：先飘到最近的空气格，再续原链路

玩家原话：「如果他在飞行中撞到了阻挡的方块，那么应该先尝试往最近的空气方块进行移动。
然后再继续执行原有的扫帚链路。」

根因：她的位移是「目标速度包络 + 阻尼」算出来的一阶矢量，撞上方块时
`Entity.move(MoverType.SELF, …)` 只在碰撞面上滑一下，**速度意图一个字不变**，下一 tick 照旧
朝那个点推——表现就是「盯着那块方块飞」。

- `MaidBroomDrive.unstick`：连续 12 tick（0.6 秒）位移 < 0.06 格且还没到点 → 判被顶住 →
  用 `nearestAirAhead` 在她身边 ±3 格里找**最近的空气格**，三条件：本体是空气、
  **上面一格也是空气**（她连扫帚差不多两格高）、**离目标比现在更近**（防原地打转）；
  走到它（1 格内）或 40 tick 用完就清除脱困状态、回到原链路——**只是绕一步，不改变去哪**；
- 新配置 **`combat.broom.unstick`（卡墙脱困，默认开）**；日志搜「扫帚卡墙」；
- 悬停/到点/玩家驾驶都不会误判（到点那一支 `dist < ARRIVE` 不进计数器）。

### ③ 扫帚模式自己的调试档（起手/收手距离）

玩家原话：「它没有一个像飞行跟随一样的调试跟随启动半径的选项（飞行跟随默认主人飞出了
20 格且没有视线阻拦以后再进行飞行。扫帚模式好像照搬了这个，但是没有任何程度上的调试面板。）」

- 新增 **`combat.broom.followStart`（默认 25）** 与 **`combat.broom.followEnd`（默认 5）**：
  扫帚模式不再借「飞行跟随」那一对数字，面板「移动与行为 → 扫帚模式」里三行新档
  （起手距离 / 收手距离 / 卡墙脱困），与「飞行跟随」那一对互不影响；
- 迟滞带照旧：主人远过起手距离才飞过去跟，进到收手距离内停下悬停，且自动把收手压到
  「起手 − 1」以内（防止「一起飞就收手」）。

### ④ 守家判定排查（河童的罗盘画的圈）

玩家原话：「我刚刚在游戏里用河童的罗盘画了一个圈。但是女仆却还是不照着那个飞，而是绕着
一个我根本就不知道的范围在飞行。」

先说清这个「圈」是什么：**圈心 = `getRestrictCenter()`、半径 = `getRestrictRadius()`**，
而圈心由 `SchedulePos.restrictTo` **按当前活动档**从河童罗盘配的三个锚点（工位/休闲/睡眠）
里取、半径取 TLM 配置（本模组只抬高下限到「排班活动半径」）。所以「圈在哪儿」取决于**时刻 +
活动档 + 锚点**，光看游戏里那一圈看不出是哪一档。

本版做了两件事：

1. **圈心兜底**：圈心还是零（`restrictTo` 没跑过：home 模式刚开、或当前活动档不在
   work/idle/rest 三档里）而**玩家已经配过锚点**时，退回「她排班锚点里最近的那个」——
   否则夹取与盘旋双双失效，扫帚模式只剩跟主人；
2. **一行诊断日志**（搜「扫帚守家」，状态变了马上打、否则 20 秒一条）：
   `home= / 活动= / 圈心= / 圈心来源=（实体数据｜排班锚点兜底｜无）/ 半径= / 锚点已配置= / 锚点=工位…休闲…睡眠…`
   ——下一次实测把这一行发过来，就能当场看出「她绕的到底是不是你画的那个圈」。

### ⑤ 刷怪笼插火把的「优先」：走位所有权

玩家原话：「女仆优先往刷怪笼上插一根火把这个操作没能实现。」

根因（1.20.1 的 Brain 事实）：**CORE 与当前活动（WORK）是两套并发的行为表、互不阻断**。
她去插火把的同时，挖矿/伐木/农活那几条驱动照样每 tick 发自己的寻路（**直连 `moveTo`**，
不走 WALK_TARGET，所以已有的 `MaidMoveSuppressMixin` 拦不住），谁最后写谁赢 → 她被一路按在
工位上，「优先」名存实亡。

- 新标记 `MaidWorkTags.SPAWNER_TORCH_TAG`（只在「有目标、正在走过去」这段时间置上，
  置/清都由 `MaidSpawnerTorchBehavior` 维护，行为停下时交还）；
- `MaidMoveSuppressMixin` 加一支：清 `WALK_TARGET` + 取消 `MoveToTargetSink`
  （TLM 原生任务/跟随/远程走位写的走位目标一律不执行）；
- **新 mixin `SpawnerTorchNavGuardMixin`**：掐掉**别的驱动**发起的直连 `moveTo`
  （她自己的那一发走 `selfNav()` 自标记放行的窄门）；挖矿/伐木/农活驱动**不必逐个改**；
- **战斗永远优先**：攻击类任务、被骑、坐着时判据恒 false，且行为在「脑里出现攻击目标」的
  那一 tick 就清标记；
- 新配置 **`combat.spawnerTorch.priority`（优先去插，默认开）**，面板「防刷怪」板块一行；
- 顺带补上两条**静默 return 的留痕**（搜「刷怪笼」）：「附近没扫到刷怪笼」「一件能当灯的
  东西都没有」——此前只有「工作区里不碰」会打日志，其余情况玩家只能看到「她没去插」。

### ⑥ 烧制清单：食物和矿物在同一张清单里

玩家原话：「烧制物品面板为什么只有矿物没有食物呢？那我应该怎么配置怎么烧食物呢？」

事实：**没有两张表**。「烧制物品」这一格就是**原料白名单**（`combat.cook.smeltAllow`），
食物（beef / porkchop / potato / kelp…）本来就在里面，只是那一格**只列「当前世界真有炉子
配方」的东西**（客户端拿不到配方表时会整格为空，退回全物品）。怎么烧食物两种做法：

1. **什么都不用填**（推荐）：清单留空 = 自动判定，而自动判定**先食材（内置白名单，要求真有
   配方）→ 再矿物标签 → 再任何非装备可烧制物**——她就是先烧食物；
2. 想钉死：在那一格里点图标把食物勾成「可以」，或在底部输入框填 `minecraft:beef` 之类加进
   名单（清单只缩小范围，不会让烧不动的东西变成可烧）。

本版把这段话写进了面板标题（那一格现在就写着「食物与矿物都在这一格：搜 beef / potato /
iron_ore；留空 = 自动，先食材后矿物」），并保留原有的「烧制清单」诊断日志（换档位时记一条，
带一组代表物品的真/假判定，可直接定位「为什么这格里看不到食物」）。

### 验证

两树 `javac` **0 错误**；只读核对 `_vt664.py` **114/114 全过**（配置声明与默认值 / 面板四行
与顺序 / 两树方法落地 / mixin 登记 / 四份 lang）；打包门禁全过（含 mixin 包登记那一栏：
forge 73 个类、neo 72 个类全部登记）；出 `promaid-1.3.0-forge-1.20.1.jar` /
`promaid-1.3.0-neoforge-1.21.1.jar`；部署三处（旧 1.3.8 附件已清）。

**本条尚未发版**——按约定等你实测。不想要哪一条就关哪一条：援护半径设 0（上一版那条）、
卡墙脱困关、优先去插关、守家盘旋关；扫帚起手/收手距离在「移动与行为 → 扫帚模式」里调。

## 实测六百六十三【从别人改过的 TLM 里借两条：援护主人的仇人 + 目标跑远就撒手（新配置「援护半径」）】

### ① 这份东西是什么

有人给了一份**改过的 TLM 1.5.3**（`touhoulittlemaid-1.5.3-modified-all.jar`）。拿它跟官方 1.5.3
逐类比对：**4522 → 4524 条目，只动了 21 个类、加了 2 个、删了 0 个，且全部是战斗相关**——
`IAttackTask` / `IRangedAttackTask`（新增一批静态索敌判据）、新增 `MaidStartAttacking` 与
`MaidClearStaleAttackTarget`、`TaskAttack` 与五个远程任务、`MaidBrain`、`MaidFollowOwnerTask`、
`DefaultMonsterType`、`MaidConfig`。改的是同一件事：**女仆打谁、追多远、什么时候放手**。

### ② 他那边「大小圈」是怎么做的（字节码解码）

- **小圈 = 她自己的活动圈**：近战目标必须过 `maid.isWithinRestriction(目标位置)`
  （home / 排班模式那个工作圈）；
- **大圈 = 一个独立的「援护半径」**：`IAttackTask.getAssistRadius(maid)`——守家 → 取工作圈半径；
  非守家 → 平时 `MAID_NON_HOME_RANGE`(8)，**一旦判定「在战斗中」就换成新配置
  `MAID_COMBAT_RANGE`（默认 16，范围 8~48）**，注释原文 "Temporary expanded assist radius
  when combat is detected in Non-Home mode"；
- **「在战斗中」怎么判**（`isInCombatMode`）：有攻击目标 **或** 主人最近被谁打了
  （`owner.getLastHurtByMob`）**或** 主人最近打了谁（`owner.getLastHurtMob`）**或** 她自己最近被谁打；
- **目标优先级**（`findPriorityAttackTarget`）：主人的仇人 → 当前目标（还合法就留着）→ 最近的可见敌人；
- **放手**（`shouldAbandonTarget`）：目标离**她**和**主人**「都」超过援护半径——与
  `isWithinAttackRange` 的「两者取近」互为反面。

### ③ 对比：我们本来有什么

| 能力 | 他那边 | 我们 |
| --- | --- | --- |
| 战斗时扩圈 | 只放大他自己的「援护半径」读数 | ✅ 更彻底：`combatWorkRange`（默认 15）直接放大**真实的圈**——连寻路与 TLM「出圈就传送回工位」那条阈值一起，战斗结束自动落回 |
| 目标必须在圈里 | ✅ 近战目标判据 | 只有空袭链路有（`FlightTargeting` 那两处 `isWithinRestriction`） |
| 打不到就撒手 | ✅ 两条（跑远了 / 走不到） | ❌ 一条都没有 |
| 主人打了谁 → 她去帮 | ✅ 主人的仇人优先级最高 | ⚠️ 只有「主人被打/打人 → 切战斗任务」（`AutoCombatSwitch`），**选目标**那一步仍由 TLM 的「最近的怪」决定；只有手工工具 `SmartAttackTool` 用到 `owner.getLastHurtMob()` |

### ④ 照搬了什么（本版新增）

**新配置「援护半径」（`combat.assistRadius`，默认 16 格，0 = 关；面板：战斗与自保 → 主动参战）**，
一条配置管两件事，落点在 `NeutralThreatDriver`——本模组本来就是「往 `ATTACK_TARGET` 写目标」的那一处：

1. **援护主人的仇人**：她（战斗任务）没有目标时，优先把「主人最近的仇人」当目标——最近打主人的人
   优先、其次主人最近打的人；然后才退回原来的「谁锁定了主人/女仆」扫描。
2. **目标跑远就撒手**：她的目标离她**和**主人都超过这个半径 → 清目标（`ATTACK_TARGET` /
   `LOOK_TARGET` / `CANT_REACH_WALK_TARGET_SINCE` / `WALK_TARGET`）。追一个已经跑掉的怪，正是
   「追出去 → 被圈拽回来」的循环源头之一。

两条都**逐条换成本模组现成的实现**（一条判据一处）：合法性用 `BombThrow.legalThrowTarget`
（「她的任务认的敌人 + 非友军」）、视线用 `NeutralThreatDriver.perceivable`（实测六百一十九 的
隔墙门，看不见就不写目标）、圈用 `WorkAreaClamp.allows`、时间窗沿用 `CombatWorkRange` 的 100 tick 口径。
「撒手」只做减法（松手）不做加法，所以刻意放在「主动参战」总开关之前——整条想关就把援护半径设 0。

### ⑤ 照搬时改掉的两点（照搬不照抄）

- 他读主人的 `getLastHurtByMob` / `getLastHurtMob` **不看时间**：原版这两个字段一旦写上就不再清空，
  于是「主人十分钟前打过的那只怪」永远算仇人、永远被优先。我们加了 5 秒窗口（时间戳初值 0 的头
  5 秒也一并挡掉，与扩圈那条护栏同款）。
- 他还写了一条「够不到就放弃」（`MaidClearStaleAttackTarget`，读 `CANT_REACH_WALK_TARGET_SINCE`）。
  **这条没有照搬**：他的实现是 `记忆值 > 100`，而记忆里存的是「走不到那一刻的 gameTime 绝对值」，
  拿它跟常量 100 比，在任何跑了 5 秒以上的世界里恒真——实际效果是「只要出现过一次走不到就立刻
  放弃」，与他自己那个 `CANT_REACH_GIVE_UP_TICKS = 100` 的意图不符。要照搬得先改成
  `now - since > 100`，那是一条独立行为，等你实测反馈（本模组 ③ 的兜底导航/挥砍与战术行为
  本来就兜着这种情况）。

### ⑥ 验证

两树 `javac` **0 错误**；只读核对 `_vt663.py` **39/39 全过**（配置 / 面板 / 接线 / **两树新增块
归一化后 130 行逐行一致** / SRG 与官方名双向无残留 / 四份 lang）；打包闸门全过（含 mixin 包登记
那一栏）；出 `promaid-1.3.8-forge-1.20.1.jar` / `promaid-1.3.8-neoforge-1.21.1.jar`；部署三处。

**本条尚未发版**——与上一版的崩修复一起测。援护半径不想要就把「援护半径（格）」设 0。

## 实测六百六十二【修死机：玩家一进世界服务端就崩——mixin 包里的「鸭子接口」没登记（v1.3.5 起就带着）】

### ① 现象：v1.3.6 进世界必崩（不是概率，是必现）

实测日志（`latest.log` + `crash-reports/crash-…-server.txt`）：

> `org.spongepowered.asm.mixin.transformer.throwables.IllegalClassLoadError:`
> `com.maidsmart.mixin.RemoteTrackBridge is in a defined mixin package com.maidsmart.mixin.*`
> `owned by mixins.promaid.json and cannot be referenced directly`

Description = `Exception in server tick loop`。触发链很短：玩家登录 → `ServerLevel.addPlayer`
→ `ChunkMap.addEntity` 造 `TrackedEntity` → JVM 解析它新挂上的接口 → 加载本接口 → 抛。

### ② 根因：Mixin 对「mixin 包」的规矩，我们踩了

`mixins.promaid.json` 里的 `"package": "com.maidsmart.mixin"` 等于**把整个包声明成 mixin 包**：
这个包里**没有登记在配置清单里**的类，只要被普通代码加载（JVM 解析某个类的接口表也算），Mixin 就拒绝。

`RemoteTrackBridge` 是 v1.3.5 引入的「鸭子接口」——它**不是** mixin，只是让普通代码能 cast 到
`ChunkMap$TrackedEntity`（那个包私有内部类）的一座桥，所以它本来就不该、也不能登记进 config；
唯一正确的做法是**别让它住在 mixin 包**。它偏偏住在那里，于是：

- 编译期 javac 一点事没有（javac 不知道 Mixin 的规矩）；
- 打包闸门也没事（类都在 jar 里、数量对得上）；
- 只有**真进游戏、真的解析到这个接口**时才炸——所以 v1.3.5 起它一直随包发布，直到这次实测。

对照（同一只 jar 里的活证据）：`EntityFlagInvoker` / `LivingEntitySpinAccessor` 也住那个包、
也被包外的普通代码 cast，但它们**登记在清单里**（是 accessor / invoker mixin），所以从来没出过事。
「登记与否」就是这条规矩的全部。

### ③ 修法

- 搬家：`com.maidsmart.mixin.RemoteTrackBridge` → **`com.maidsmart.schedule.RemoteTrackBridge`**
  （与唯一使用者 `RemoteMaidGui` 同包）。两树都搬，正文除包名与本段说明外逐字节相同。
- `ChunkMapTrackRemoteMixin` 补一条 import（它 `implements` 这座桥）；`RemoteMaidGui` 删掉那条
  import（已同包，不再需要）。
- **顺手把这条规矩做成打包闸门**：`verify_jar_classes.py` 新增一节——读 jar 里的
  `mixins.promaid.json`，把 `com/maidsmart/mixin/` 下每个顶层 class（不算 `$` 内部类）与清单比对，
  **有未登记的类就 FATAL、打包失败**。以后这类错误在 `python build_promaid.py` 那一步就被挡住，
  不会再到玩家手里才炸。

### ④ 边界

- 只动了这一个类的位置与两条 import，**没有任何行为变化**：远程开界面的「强制同步」逻辑一个字没改。
- 两树编译输出目录里那份**旧的** `com/maidsmart/mixin/RemoteTrackBridge.class` 已删——它就躺在
  编译输出目录里，不删会被原样打进 jar（打包闸门里的 `STALE_IN_JAR` 那一栏盯的就是这种残留）。
- 这个坑与 Windows/Linux、forge/Neo 都无关，是 Mixin 的通用规矩；两树同样处理。

### ⑤ 验证

两树 `javac` **0 错误**（60 个警告全是原有的过时 API 提醒）；只读核对 `_vt662.py` **50/50 全过**
（搬动落地 / 编译清单跟随 / 两处引用 / **mixin 包登记审计** / 两树 import 区镜像 / 版本号）；
打包闸门全过并**新增**「mixin 包登记 OK」一栏（forge 72 个类、neo 71 个，全部登记）；
出 `promaid-1.3.7-forge-1.20.1.jar` / `promaid-1.3.7-neoforge-1.21.1.jar`；部署三处（旧 1.3.6 件已清）。

**本条尚未发版**——仍等你实测（这次请**先看一眼能不能进世界**）。

## 实测六百六十一【扫帚模式收尾：牵引绳「连人带扫帚」、守家时绕工作范围盘旋、独占配置板块、骑乘只留主手武器、烹饪清单收成一个勾】

### ① 玩家这一批提了五件事

> 1.在模组详细配置飞行跟随中后面加入对于扫帚模式的各项数值调整面板。
> 2.这个东西应该也要有一个飞行牵引绳。如果隔的太远的话会连人带扫帚一起传送送回来。但是如果在代码上
> 碰到了什么硬骨头。我们在触发此传送的时候，就先将女仆从扫帚模式切换到空闲模式，然后将扫帚和女仆传送
> 过来，然后再切换成扫帚模式。排班表内的一键传送同理，如果能一起传送就一起传送。不能一起传送，就用
> 上面这个来。而不是直接反馈，没有办法传送。（否则隔的太远女仆就找不回来了）
> 3.烹饪和酿造那个配置面板不好用。明明只需要改的像喂食面板一样，在那个面板勾选了就能烧，没勾选就不能烧
> 就行了，现在偏要把烧跟没烧分别做成两个面板分开搞反而麻烦了很多。
> 4.我问一下，在骑乘模式下女仆可以使用魔法吗？如果有的话，在扫帚模式里面同步一下。如果没有就更新手册
> 中的内容。然后声明一下，在扫帚模式下因为是骑乘状态所以没有办法使用魔法以及放置TNT/末地水晶/重生锚/床
> 的操作。只能使用主手武器攻击。
> 5.home对扫把模式完全没用，这边改为，如果我在扫把模式下开启鸿蒙，那个女仆正常就会在工作范围内对着
> 工作范围那个圈进行盘旋，直到接敌。

### ② 第 4 条先回答：**不能施法**，而且这不是我们"懒得做"，是骑乘本身

三处证据（两棵树都读过源码）：

1. `MaidFlightCombatBehavior.checkExtraStartConditions` 与 `canStillUse` 开头就是
   `if (isSleeping() || isPassenger()) return false;`（forge 树 = `m_5803_() || m_20159_()`）——
   **门是我们自己的行为架的**：法术兼容层 `MaidSpellCastCompat` 里反倒写着「空中无门槛，整个施法链路
   没有 isPassenger 判定」，也就是"法术本身允许飞着放"（鞘翅滑翔中照放）。
2. `MaidFlightFollowBehavior` 同样拒（还多拒一个坐姿）。
3. 扫帚模式的 ride brain（`MaidBroomTask.createRideBrainTasks` → `buildBrain()`）里**根本没有施法行为**
   ——只有「攻击目标 / 目标失效停止 / 扫帚行为」三件。她骑着扫帚时连"想施法"这一步都走不到。

**放置类战术（TNT / 末地水晶 / 重生锚 / 床）原来**没有**这层保护**：`MaidBombing` 的相位机与
`BombTntTick.tickCombatTnt` 只看「任务是不是战斗类」，全程没有 `isPassenger` 判定——她骑在扫帚上照样
会起手放水晶。所以你要的"声明"要成立，就必须**真拦一道**（见 ③-4）。

### ③ 五件事各自怎么做的

**1. 扫帚模式自己的配置板块**（`Section.BROOM`，挂在「移动与行为」下、紧跟「飞行跟随」）

原先那五行散在「战斗与自保 → 单兵战术」的尾部，现在整块搬过去，并补上本批新增的「扫帚·牵引绳距离」，
共六行：总开关 / 盘旋距离 / 悬停高度 / 牵引绳距离 / 平时跟随主人 / 守家时绕工作范围盘旋。
单兵战术里那五行删掉（引用只保留一处）。

**2. 扫帚牵引绳：连人带扫帚一起传送**（新类 `MaidBroomRecall` + `MaidChunkLoadManager.recallBroomRider`）

- 判据与「空袭牵引绳」同源：离主人超过 `combat.broom.recallDistance`（**默认 100 格**，按 3D 距离算，
  所以"飞太高"本身也会触发；0 = 关闭）→ 立刻召回，并在聊天栏给她主人一条系统消息（10 秒最多一条）。
  挂在 `MaidBroomBehavior.tick` 的**第一行**（⓪ 相位），拉回来的那一 tick 不再写推进意图。
- **那条硬骨头**：`Entity.teleportTo(ServerLevel, …)` 内部第一件事就是 `unRide()`——直接传送她 =
  当场被从扫帚上踹下来，扫帚留在原地漂着。所以顺序改成四步、同一个 tick 内做完：
  **解开乘客关系 → 搬扫帚 → 搬她 → 重新落座**（落座走 `startRiding(broom, true)`，与
  `MaidBroomDrive.ensureMounted` 同一条 force 口径）。传送落地后清掉那 tick 的推进意图
  （`MaidBroomDrive.clearIntent`，**不碰欠账表**，收工时扫帚照旧精确回背包）。
- **你提的那条兜底没有白留着**：跨维度（`teleportTo` 会换实体）或落座失败时，走
  "就地收工 → 传送她 → 下一 tick 她自己从背包里取出来再骑上"。任务**始终不换**——所以
  "先切空闲再切回来"这一步不需要：换任务会重建 brain、还会把扫帚收进背包，而这条兜底路径
  已经天然做出了那件事的结果。
- **排班表的「召她过来 / 一键集合」四处入口**（`summonOne` / `summonAll` / `tickPending` /
  `summonMaidTo`）都改了：骑扫帚的女仆**不再按"骑乘中"豁免**（旧口径会把她永远留在原地——
  她本来就是乘客）。现在能一起搬就一起搬，搬不动就上面那条兜底，**绝不回一句"没办法传送"**。

**3. 烹饪清单面板：四张名单收成两个科目、每个物品一个勾**（`PromaidConfigScreen`）

- 面板上原来是四个模式按钮（只烧这些 / 禁止烧制 / 只用这些燃料 / 禁用燃料）= 四张表分开填。
  现在收成**两个科目**（「烧制物品」/「燃料」），每个科目一页、网格上**一个勾**：
  **绿 ✔ = 可以、红 ✖ = 不可以**，点图标即切换——与「投喂食物勾选」那张面板完全同款。
- **底层存储一个字没换**：仍然是原来那四个配置键，老存档里填好的名单不会丢。折算规则：
  「允许清单」为空（默认）→ 这一页编辑的是**禁止清单**（勾 = 不在禁止清单里）；
  「允许清单」非空（旧版"只烧这些"模式）→ 这一页编辑的是**允许清单**。页顶那句话会写明当前是
  黑名单还是白名单，底部名单的按钮也跟着变（「改为可以」/「移出」）。
- 判定口径与 `MaidCookBehavior.smeltListed / fuelListed` **逐字一致**（禁止优先；允许清单非空则只认清单），
  所以"面板上看到的勾"与"她真的会照做的"不会变成两套东西。候选网格仍按科目过滤
  （烧制只列真有炉子配方的、燃料只列原版能当柴烧的）。

**4. 扫帚模式禁掉放置类战术（TNT / 末地水晶 / 重生锚 / 床）**

判据收在 `MaidBroomKit.forbidsBombing`（= 任务是扫帚模式，**不看有没有正骑着**：骑不骑都是这一种打法），
三处一起拦：起手（`MaidBombing.tryStartMelee`）、投掷扫描（`BombTntTick.tickCombatTnt`，所有战斗任务的
入口）、相位推进（`MaidBombing.tick(Phase)`：**已经在飞的那一段当场作废**，已放下的方块回滚进她背包）。
所以这个模式下她的输出只有**主手武器**（开火链路照旧走远程空袭那一套）。

**5. 守家（工作范围）时沿着工作范围那个圈盘旋，直到接敌**

（你说的「鸿蒙」我按 **home / 工作范围**理解——上一句正是"home 对扫把模式完全没用"。如果你指的是
别的东西，说一声我改。）

- 旧版 home 对扫帚模式只剩「把飞行目标点夹进圈里」，所以开着 home 她也只是跟着主人悬停，看不出守家。
  现在没目标那一档变成三选一：**① 守家生效 → 沿工作范围那个圈的边缘慢慢盘旋巡逻**（半径取
  "限制半径 − 1.5 格"，留出座位的半格偏移；**高度不动**，只改水平——工作范围在 TLM 里本来就是个水平圆）
  → ② 否则跟主人 → ③ 都没有则原地悬停。接敌时照旧优先（先爬到目标上方 8 格再绕圈开火）。
- 盘旋相位 **单独一张表**（`HOME_ORBIT`），与"绕着敌人转"的 `ORBIT` 分开存，免得刚打完那一刻跳半个圈。
- 开关就是原来那一项（面板上叫「扫帚·守家时绕工作范围盘旋」，配置键 `combat.broom.clampHome`）：
  **关掉 = 自由飞**（平时跟主人、追怪也能越界）。

### ④ 边界（说清楚，免得踩坑）

- **牵引绳不管 home**：与「空袭牵引绳」同一取舍——骑着扫帚本身就是"离开原地"的状态，而排班表会自动给
  女仆锚定 home，照抄那道门的结果就是"用了排班的女仆永远拉不回来"。所以**守家女仆 + 主人跑出 100 格以外**
  会被牵引绳拉走；不想这样就把「扫帚·牵引绳距离」设 0，或者关掉她排班里的 home。
- 牵引绳只在她**骑着扫帚**时生效；已经落地的那段距离交给「同维度远距拉回」（它查 home / 坐姿 / 干活豁免）。
- 牵引绳**跨维度不抢**（那一路归跨维度跟随）；跨维度要叫她回来，用排班表的「召她过来」——那条路已经
  认识扫帚了。
- 烹饪面板只改了"怎么填"；酿造那边本来就只有一个搜索半径，没有名单，没动。

### ⑤ 验证

两树 `javac` **0 错误**；只读核对 `_vt661.py` **80/80 全过**（含两树归一化镜像比对：`MaidBroomRecall` /
`MaidBroomKit` / `MaidBroomDrive` / `MaidBroomBehavior` 代码行逐行一致）；mixin 目标名审计
`_mixin706.py` **305 项 0 FAIL**（本批没动 mixin，回归跑一遍）；打包闸门全过（forge 内 549 个 class、
neo 550 个，多出来的那个正是新的 `MaidBroomRecall.class`）；出
`promaid-1.3.6-forge-1.20.1.jar` / `promaid-1.3.6-neoforge-1.21.1.jar`；部署三处（旧 1.3.5 件已清）。

**本条尚未发版**——等实测。

## 实测六百六十【女仆配置：远程开界面改「强制同步」——区块票据救不了这一条，但同步做得出来】

### ① 玩家问的那句

> 之前不是有过一个女仆所在的区块强制加载吗？难道解决不了这样的问题吗？

问的是上一条（六百五十九）里我写的「几百格外开店没做——那得先解决『客户端从没同步过她』，
需要就说一声」。**区块票据救不了这一条**，因为它跟「客户端认不认得她」是两条独立管线；
但「强制同步」本身做得到。这一条就是把它做出来：**距离上限整个取消**。

### ② 结论先说

| # | 上一条（六百五十九） | 这一条（六百六十） |
|---|---|---|
| 2 | 远程开界面**能开到 48 格**，超出就回一句「客户端看不到她，那种距离开出来也是残的」，建议走近或用「召她过来」 | **取消距离上限**：同一维度内，几十格、几百格、几千格都是同一套流程。做法是**把同步真做出来**（替这一对强制配对），不再依赖「她本来就在同步范围内」这个前提 |

### ③ 为什么「女仆区块强制加载」解决不了这一条

两条管线，各管各的：

- **区块票据**（本模组的 `MaidChunkLoadManager`，票级 2 = 实体正常 ticking）管的是「她在服务端还在不在、动不动」——她在，AI 照跑，跨维度跟随扫得到她。票据里**没有一个字**是关于客户端知不知道她的。
- **实体同步**管的是「客户端认不认得这个实体」，判据在 `ChunkMap$TrackedEntity.updatePlayer` 里（1.20.1 = SRG `m_140497_`）。javap 逐条读下来是：

  ```
  水平距离² ≤ min(getEffectiveRange(), 视距×16)² 且 broadcastToPlayer(player)   // 1.21.1 还多一条 isChunkTracked
  ```

  其中 `getEffectiveRange` 的起点是 `ChunkMap.addEntity` 里写进去的 `EntityType.clientTrackingRange × 16`（字节码里 `bipush 16 / imul` 紧跟在它后面，单位是**区块 × 16 = 方块**）。TLM 给女仆声明的是 10 区块 → **160 格**，再被「视距 × 16」卡一刀。

**结论**：票据把她的区块载着，与「客户端认不认得她」毫无关系——**所以区块加载解决不了这一条**。
（顺带纠正上一条里的一个数：真正「自然能同步到多远」是 `min(160, 视距×16)`，玩家视距 8 区块时 128 格、
16 区块时 160 格封顶。上一条我封的 48 格是为了「稳妥」自己拍的，本来就保守了近三倍。）

### ④ 做法：强制同步 + 每 tick 同步泵

- **新增 `ChunkMapTrackRemoteMixin`**（`@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity")`，包私有内部类只能用字符串写法）：
  - `promaid$forcePair`：把这一对塞进原版自己的 `seenBy`，并调**原版同一个方法** `ServerEntity.addPairing`（它把 spawn 包 + 元数据 + 属性一次性打包发出去）。所以客户端拿到的她，与「玩家自己走近、原版自己配上的」**完全一致**，不是另抄一份发包。
  - 已经在 `seenBy` 里的不重复发（`add` 的返回值就是「这次新加进去没有」）——不会出现同一实体发两遍 spawn。
  - 原版还有**两条路会把她从客户端拆掉**（`updatePlayer` 按距离拆、`removePlayer` 在她区块滑出视野时拆），会话期间一并拦下，拦的时候顺手把配对补上。
  - **不该配就一个字不改**：玩家自己右键开的界面（没登记）、她死了/被魂收走、维度对不上——三种都返回 false，交回原版按距离处理。
- **新增 `ChunkMapRemotePumpMixin`**（`@Mixin(ChunkMap.class)`，注入 `tick`/`m_140421_` 的**尾巴**）：每 tick 跑一次 `RemoteMaidGui.pump`。挂这里的两个理由：① `entityMap` 是 ChunkMap 的私有字段，只有混进它的 mixin 碰得到；② **「先配对、再开界面」必须在同一次调用里按序发出**——两条包走同一条连接、TCP 有序，客户端必然先认识她、再打开她的界面（反过来就是在包处理线程里先开、tick 里再补同步，那是明摆着的抢跑）。
- **`RemoteMaidGui` 改成「登记 + 泵」**：排班表点「女仆配置」只登记一笔；下一 tick 泵里强制配对成功后再替玩家开界面。`remoteRange` / `MAX_RANGE` / `MARGIN` 三个距离常量**全部删除**——半径这个概念不存在了。没登记过会话时泵第一行就返回（平时零开销）。
- **1.21.1 多一道 `isChunkTracked`**：neo 树那个 `updatePlayer` 还要求「客户端已经把她的区块收下来了」。我们在方法**入口**接管，这道额外的 gate 一并绕开。
- **界面关掉自动拆**：`AbstractMaidContainer.removed` 那条注入收回登记 → 下一个 tick 原版自己按距离把她从客户端拆回原样。**不会有长期驻留的强制同步**。

### ⑤ 口径变化

- 排班表点「女仆配置」→ 登记 →（下一 tick，≤100 毫秒）同步落地 + 界面弹出。玩家点下去到界面出现最多晚 1~2 tick，肉眼无感。
- `openBlockedReason` 只剩三条**「开了也会被原版立刻关掉」**的：她已经不在了 / 她正在睡 / **她在别的维度**（跨维度时玩家的客户端在另一个维度，`AbstractMaidContainer` 构造里那个 `level.getEntity(int)` 找的是**玩家所在维度**，永远找不到她；而且那一维度上的强制同步也做不了——她在那一维度的实体跟踪表里，而玩家不在）。跨维度这条要给「召她过来 / 传送到我身边」，与前一条口径一致（`ScheduleNetworking.findMaid` 是**遍历所有维度**找她的，所以这条必须我们自己拦）。
- 同步没落地（她/他不在了、区块没加载出来）→ 2 秒后回一句明白话，不会让玩家对着「点了没反应」发呆。
- **玩家自己右键女仆开的界面一个字没变**：不登记 → 距离照旧由 TLM 管（1.20.1 = 3 格，1.21.1 = 4 格）。

### ⑥ 没做的

- **跨维度开界面**：要做就得让客户端在她那个维度上解析她（换容器/换界面那套），是另一件事——现在给的是明确拒绝 + 一句怎么办。
- 上一条的两处（骑世界里的扫帚、容器 stillValid 放行距离）**原样保留**，这一条只是把「距离」这个上限拿掉。
- 编队（玩家已排除）。

### ⑦ 验证与交付

- 两树 `javac` **0 错误**（只剩既有的过时 API 警告）。
- 只读核对 `_vt660.py`：**55/55 全过**；其中含「两树新类代码行（归一后）逐行一致」。
- **新增 `_mixin706.py`：mixin 目标名审计**——把两树**每一支** mixin 的 `@Mixin` 目标、`@Inject` 的方法名与参数个数、`@Shadow` 的字段名与类型，逐个拿到**真实（含加载器补丁的）jar** 里用 javap 核对，并反查「源码里有 `@Mixin` 却没登记进 `mixins.promaid.json`」。**305 项 0 FAIL**。本模组是手工编译、没有 refmap，注解里写的就是运行期真名，名字写错**启动即崩**——这道闸专门挡这个，这一批的两支新 mixin 在里面逐个方法核对过。
- 打包闸门全过 → `promaid-1.3.5-forge-1.20.1.jar` / `promaid-1.3.5-neoforge-1.21.1.jar`；部署三处（1.20.1 客户端 / 1.21.1 客户端 / pack1201 服务端）。
- 版本 **1.3.5**：v1.3.4 从未发版，这一批又动了同一件事（把 48 格上限整个拿掉），按老口径进一位尾数，便于你自己对齐「测的是哪一版」。

**本条尚未发版**——等实测。

## 实测六百五十九【找扫帚：先去骑世界里放着的扫帚实体 / 女仆配置：真正的远程开界面】

### ① 反馈原文

> 「让她去骑世界里已经放着的那把扫帚实体」，我想要真正的远程开界面

这两句改的都是**上一条（六百五十八）里我做的两处**：⑤ 的「找扫帚」当时只认「地上掉的物品」，
② 的「打开即关」当时只是「不开 + 回一句明白话」。这一条把它们换成真正要的东西。

### ② 结论先说

| # | 上一条（六百五十八） | 这一条（六百五十九） |
|---|---|---|
| 5 | 「找扫帚」只认 **地上掉的扫帚物品**：走过去收进背包 | 首选 **世界里已经放着的那把扫帚实体**：走过去**骑上它**。收工只下鞍、扫帚**留在原地**（不是我们放的，不进「待归还」那笔账）。没有实体可骑，才退一步捡地上掉的那件 |
| 2 | 离得远点「女仆配置」→ **不开**，回一句「她离你 X 格……先走近她」 | **真的开**：从排班表点的那次会登记成一次「远程会话」，TLM 容器每 tick 那条「距她 ≤ 3 格」判据对这一对（玩家, 她）**整条放行**；界面关掉时授权自动收回。能开的距离因此从 3 格 → **48 格**（边界仍取原版实体同步距离，见 §⑤） |

### ③ 第 5 条：扫帚优先 = 去骑世界里放着的那把

`MaidBroomDrive.seekBroom` 的两档，现在是这个顺序：

1. **世界里放着的扫帚实体**（`EntityBroom`，16 格内最近的一把、**空着的**）→ 每 tick 重写寻路目标走过去 → **2.5 格内直接上鞍**（`startRiding(..., force)`，与「取出自己那把」同一条通道）→ 骑上即起「起飞相位」（原地抬 1 格，与老规矩一致）。
   - **不抢有人骑的**：判据就是原版自己的 `getPassengers().isEmpty()`——玩家正骑着的那把、别的女仆已经骑上的那把，都不是「没人用的那把」。
   - **不是我们放的就不动它**：收工时 `dismount` 只管我们放出去的那些（`DEBT` 表里记着的，收工原物归还）；骑世界里的那把时只下鞍，扫帚**留在世界里**。
2. 没有实体可骑，才退一步走上一版的老路：**地上掉的扫帚物品** → 走过去 → 2 格内收进背包（收进来之后由 `ensureMounted` 放出来骑，与 v1.3.0 同款）。

两档都仍然排在「骑上 / 接敌 / 跟随主人」**全部之前**——没扫帚时她不会先去跟主人。

### ④ 第 2 条：真正的远程开界面

上一版是「止血」：开门前先跑一遍 TLM 的判据，不满足就**不开**。这一版是真的开。

- **根因（字节码实证）**：`AbstractMaidContainer.stillValid` 每 tick 被服务端问一遍「这个界面还能不能留着」，链子是 `maid != null → isOwnedBy(player) → isAlive() && !isSleeping() → canReach(maid, 3.0)`（1.20.1）/ `canInteractWithEntity(maid, 4.0)`（1.21.1）。最后那条一挂，服务端下一 tick 就 `closeContainer()`——客户端看到的就是「点进去一下、立刻闪退」。
- **做法**：从排班表点「女仆配置」时先登记一对（玩家, 她）→ **只把「距离」这一条翻过来**变成 true → 界面关掉（`removed`）时收回登记。玩家**自己右键**开的界面不登记，距离照旧由 TLM 管（原版手感一点没动）。
- **死 / 睡 / 换主人 照旧关**：这三条是 stillValid 里「距离之前」的条件，注入处自己再问一遍，三条都过才翻。
- **为什么不用 @Redirect 改那条调用**：TLM 这一版在 1.20.1 里调的偏偏是**官方名** `Player.canReach`，而同一只 jar 里其它类调的是 SRG `m_19950_`——两种写法混着，mixin 的 refmap 一改就错位，错位的结果是**启动即崩**。改成在**方法出口**（`@At("RETURN")`）上翻返回值：只认「出口」这个位置，与被调方叫什么名字完全无关。

### ⑤ 为什么远程仍然有边界（48 格）

TLM 的容器是**按实体的网络 id 在客户端找她**的（`AbstractMaidContainer` 构造里 `level.getEntity(int)`），而「玩家背包那一片槽位」只在 `maid != null` 时才建——客户端没同步到这个实体时，打开的是一个**残界面**。所以边界取**原版自己的实体同步距离**：`EntityType` 的「客户端同步距离」读数（单位=区块）× 16，留 8 格余量，再封顶 48 格（玩家视距常见的低档是 4 区块 = 64 格，封 48 稳稳落在里面）。TLM 给女仆声明的是 CREATURE 类实体的默认值（10 区块 = 160 格），所以 48 是「取个稳妥值」，不是同步距离本身。

### ⑥ 没做的

- **几百格外开店**：那一档要先把「客户端从没同步过她」这件事解决（强制同步 / 自建数据包），本版没做——真需要的话说一声。
- **距离之外的判据一个字没放松**（死 / 睡 / 换主人照旧关）。
- 编队（玩家已排除）。

### ⑦ 验证与交付

- 两树 `javac` **0 错误**（只剩既有的过时 API 警告）。
- 只读核对 `_vt659.py`：**37/37 全过**；其中含「两树新类代码行（归一后）逐行一致」。
- 打包闸门全过 → `promaid-1.3.4-forge-1.20.1.jar` / `promaid-1.3.4-neoforge-1.21.1.jar`；部署三处（1.20.1 客户端 / 1.21.1 客户端 / pack1201 服务端）三个 `match=True`，旧的 1.3.3 包已删。
- 版本 **1.3.4**：v1.3.3 从未发版，但这一批把上一批的两处做法换掉了，按老口径进一位尾数，便于你自己对齐「测的是哪一版」。

**本条尚未发版**——等实测。

## 实测六百五十八【排班表列表行改显示状态 / 女仆配置「打开即关」/ 骑扫帚原地打转 / 爬到 8 格又掉下来 / 找扫帚优先 / 发现刷怪笼插火把】

### ① 反馈原文

> 1.排班表如图所示的这个界面内最好可以显示女仆的一些基本状态，比如目前处于什么模式，是否是在家状态，是否排班，去掉原来的血量百分比显示。
> 2.如果离得太远。再点击女仆配置的时候会点进去以后立刻闪退出去。
> 3.现在女仆在骑着扫帚的时候，如果主人就在旁边呢，它会在空中不停的旋转。
> 4.遇到敌人的时候，会先上升8个，但是随后又慢慢掉下来了。那这样子的意义在哪里？
> 5.扫帚模式应该优先找扫帚，而不是优先跟随主人。
> 6.有玩家建议女仆在发现刷怪笼以后如果手上有火把会优先在刷怪笼上插火把（用来防止刷怪，当然可能涉及我的知识盲区，如果有和火把一样功效的东西，那么一样可以接受判定。）。如果这一块区域被判定为Home模式工作区域（河童的罗盘标记的那一块）则不执行这个链路。

### ② 结论先说

| # | 现象 | 结论 |
|---|---|---|
| 1 | 列表行显示的是「血量%」 | 改版：行内改成【当前任务 + 工作模式（早班/晚班/全天）+ 排班(段数) + 维度 + 在家】，血量百分比整条去掉。搜索框顺带也能搜任务名（「她叫什么」和「她在干什么」都搜得到） |
| 2 | 离得远时点「女仆配置」→ **进去一下立刻闪退** | 根因在 TLM：女仆容器**每 tick** 都要过一遍 `AbstractMaidContainer.stillValid`，其中一条是「玩家到她 ≤ 一个很近的常数」，不满足时**服务端下一 tick 就关界面**。修法：开门前先跑**同一套判据**，不满足就**不开**、并回一句明白话（她离你多远、该怎么办）——宁可一句明白话，也不要一闪而过的空界面 |
| 3 | 主人在旁边时她在空中**不停打转** | 两个成因叠在一起（详见 §④）：①她是**乘客**，原版 `positionRider` 把她的座位摆在她朝向**后方 0.5 格**，而「原地悬停」的目标点写的是**她的**坐标 → 与扫帚永远差半格、永远到不了点、每 tick 被推一把；②朝向写在 `steerTo` **之前**，被「朝向 = 速度方向」整条盖掉，而那个方向恰好是**背对主人** → 每 tick 翻 180°，客户端对 yRot 插值 → 看起来就是在转圈 |
| 4 | 遇敌先升 8 格，随后**慢慢掉下来** | 两处高度**不是同一个参照系**：爬升是「她自己脚下 + 8」，盘旋是「敌人脚底 + 悬停高度(默认 2)」——升完立刻按另一套高度往回落，爬升白爬。现在爬升的终点改成「**敌人上方 8 格**」，并在爬升收尾那一刻把**这一场遭遇的盘旋高度**定下来（顶头就按实际抬到的高度，下限 = 悬停高度），盘旋照它飞。「升 8 格」从此不是一段动画，而是**决定这一场打多高** |
| 5 | 扫帚模式没优先找扫帚 | 新增**第一优先级**：身上没扫帚也没骑着时，**先去找**——她 16 格内掉在地上的扫帚物品 → 每 tick 重写寻路目标走过去 → 2 格内收进背包；找不到、或 20 秒还够不着就报缺件待命。这一档排在「骑上 / 接敌 / 跟随主人」**全部之前** |
| 6 | 玩家建议：发现刷怪笼就插火把 | 新增 core 行为（优先级 186，默认开）：扫到附近有刷怪笼就走过去把它哑掉。判据**不是认「火把」这个名字，而是认「放下去方块自身发光 ≥ 8」**（详见 §⑦）；刷怪笼落在她的「在家模式/工作区」圈里（河童的罗盘标记的那一片）**一律不碰**——那可能是你故意留的刷怪塔 |

### ③ 第 1 条：列表行改版（顺手把字段 6 停用）

排班表 → 选女仆 → 列表行，现在是：

```
§e<名字> §f<当前任务> §b<工作模式> §9<维度(跨维度才显示)> §a排班(N)/§8排班关 §d在家
```

- 任务名走客户端翻译（`task.<命名空间>.<路径>`，就是详情页那套 `taskCn`），服务端**不需要**多发字段。
- 服务端原来往字段 6 填「血量百分比」——现在**没有任何消费方**（列表行不用了），所以不再计算、只发空串。**字段位保留**：协议形状一个字不动，客户端仍按 0..8 读下标，少一处「服务端发 9 个、客户端读 8 个」的错位风险。v1.2.0 那条「血量公式取反」的账随字段一起作废。
- 搜索框也匹配任务名：输入「挖矿」「扫帚」同样能筛出来。

### ④ 第 3 条：那个「打转」的两半因果（0.5 格座位偏移 + 朝向自引用）

**第一半：0.5 格。** 她是乘客，原版 `EntityBroom` 的 `positionRider` 是这么摆座位：

```
offset = Vec3(-0.5, 骑乘高度, 0).yRot(-载具yaw - 90°)     // 女仆乘客的 xOffset = -0.5
```

也就是**座位永远在扫帚朝向的后方 0.5 格**。旧版「原地悬停」把目标点写成 `new Vec3( maid.getX(), …, maid.getZ() )`——**她的**坐标；而 `steerTo` 里「到点了吗」比的是**扫帚**到目标点的距离。两者恒差那 0.5 格（再加上骑乘高度差），于是：

- 永远到不了「到点」（`< ARRIVE = 0.35`）那一支 → 永远走「按方向给速度」那一支 → 她一直被推着走；
- 被推的方向正好是那段偏移的方向 = **背对朝向**那一侧。

**第二半：朝向自引用。** 朝向是这么算的（旧版）：`-atan2(主人 - 她)`——用的是**她的**位置；而她的位置又随**朝向**动（座位绕着她转）。再加上调用顺序反了——`faceYaw` 写在 `steerTo` **之前**，被 steerTo 里的「朝向 = 速度方向」整条覆盖——于是每一 tick 写进去的朝向都是「背后那 0.5 格的方向」，也就是**与「面向主人」正好相反**；下一 tick 重算又翻回来。每 tick 翻 180°，客户端对 yRot 做插值，看到的就是「在空中不停地旋转」。

**修法（两处，都在 MaidBroomDrive 一处定义）：**

1. `hoverInPlace(maid)`：悬停的目标点取**扫帚自己**的位置（新增 `broomPos`）——「原地」终于真的是原地（走到「到点」那一支、速度乘 0.75 收干）；起飞与接敌爬升也改走 `steerVerticalTo`（只改 y、水平用扫帚当前的 x/z）。
2. `faceYawTo(broom, target)`：偏航**从扫帚自己的位置**算（不是她的位置），自引用回路断掉；并且行为里三处朝向调用**一律挪到 `steerTo` 之后**（跟随那一支原来摆反了，注释里写明这个顺序是硬要求）。

### ⑤ 第 4 条：爬升与盘旋的高度口径统一

```
旧：爬到「她自己脚下 + 8」→ 相位结束 → 盘旋高度「敌人脚底 + 2」   ← 升完立刻掉 6 格
新：爬到「敌人上方 + 8」   → 相位收尾时把本场盘旋高度记进 COMBAT_ALT（下限 = 悬停高度、上限 = 8）
                            → 盘旋高度 = 敌人脚底 + COMBAT_ALT      ← 升到哪就一直在哪打
```

- 「顶头」（头顶被方块挡住）仍然照旧用「连续 4 tick 没长高」判：顶到 3 格就按 3 格飞，**不会**为了够一个够不到的高度一直往上顶。
- 换敌人（新的目标 UUID）会重新爬一次、重新定高度；丢目标时 `clearClimb` 把高度一起作废，回到平时的跟随/悬停高度。
- 日志（搜「扫帚模式」）：`接敌 → 先爬到它上方 8 格（高度从这以后一直保持）` 与 `本场盘旋高度 = 敌人上方 X 格（想 8，实际 Y 格）`。

### ⑥ 第 5 条：什么算「找扫帚」

- **「取」**（主手 → 副手 → 背包 → 精妙背包/旅行者背包）由原来的 `ensureMounted` 负责，不重复。
- **「找」**兑现的是玩家把扫帚**丢在地上**之后她自己过去捡：16 格内最近的扫帚物品 → 走过去（每 tick 重写寻路目标，真正走路的是 TLM 的导航）→ 2 格内用 `insertItem` 收进背包（背包满就**原样留在地上**，绝不吞掉玩家的东西）→ 收成后 `kill()` 掉物品实体（不走原版掉落，否则会掉两份）。
- 世界里**已经放出来的扫帚实体**不归这里管：原版 `EntityBroom.m_6138_` 自己会把走到旁边的主人女仆拽上鞍位——我们要的是「她走过去」，不是「替她把玩家放的扫帚骑走」。
- 20 秒还没够到（卡墙/在水里）→ 放弃这一轮、进 60 秒冷却（同一把别每 tick 重试），日志写「那把扫帚走了 20 秒也没够到 → 先放下」。

### ⑦ 第 6 条：防刷怪插火把（新链路）

**为什么「光」就够**（这是本条的立身之本）：原版刷怪笼每次尝试生成都要过 `Monster.isDarkEnoughToSpawn`——**目标位置亮度 ≤ 7** 才放行；而它的候选生成区是以自己为中心的 **8×3×8**，光照每格 -1。所以只要刷怪笼身上/身边有一个**发光 14** 的火把，整个生成区最远那一格也还有 10，全部在门限之上——它直接哑掉。

**因此判据不是「火把」这个名字，而是那件事：放下去方块自身发光 ≥ 8。** 火把 14 / 灵魂火把 10 / 灯笼 15 / 灵魂灯笼 10 / 萤石 15 / 菌光体 15 / 海晶灯 15 / 三种蛙明灯 15 / 南瓜灯 15 / 末地烛 14 / 篝火 15 都行；**红石火把只有 7，不够**，所以清单里刻意没有它（与被动插火把那边的取舍同源）——玩家说的「有和火把一样功效的东西一样可以接受判定」，「功效」就是这一条数字。模组物品兜底：注册名以 `torch` / `lantern` 结尾的也认（照样要过发光 ≥ 8 那道闸）。

**放哪儿（按优先级，九个候选位全在刷怪笼紧挨着的格子里）：** ①**刷怪笼顶上**（原话「在刷怪笼上插火把」最字面的那一处）→ ②同层东南西北四邻（火把立在地板上）→ ③四邻的上一层（立在墙头上）。第一个「是空气 + 原版 `canSurvive` 站得住」的位置就是落点，九个都放不了才放过它（并记日志）。用 `canSurvive` 而不是自己拼「脚下是不是实心」：半砖/楼梯/栅栏各自有各自的碰撞形状，交给原版判最稳。

**去重不靠记忆**：火把在那儿（候选位里有发光 ≥ 8 的方块）就认为这只刷怪笼处理过了；火把被人拆了/被水冲了，下一轮扫描自然再插一次。

**不碰的情况：** ①刷怪笼落在她的「在家模式/工作区」圈里（河童的罗盘 —— 读 TLM 的 `getRestrictCenter/getRestrictRadius`，与扫帚模式那把夹取同一对字段）；判不出来时**按「在里面」处理**——宁可少插一根，也不要拆掉你的刷怪塔；②她手上一件灯都没有（原话是「如果手上有火把」）；③她正在打架（有攻击目标）/自保逃跑/骑乘/坐着/睡觉。

**节奏与开销：** 每 4 秒扫一次，水平半径可配（默认 12 格）、上下各 4 格；一次只处理最近的那一个。走过去的耐心 20 秒，放弃后 60 秒冷却。

**配置（战斗与自保 → 防刷怪插火把）：** `combat.spawnerTorch.enable`（默认开）、`combat.spawnerTorch.radius`（默认 12，4~32）。**日志**搜「刷怪笼」：发现 / 放下 / 在工作区里不插 / 走了 20 秒没够到 / 九个位置都放不了。

### ⑧ 验证与交付

- 两树 `javac` **0 错误**；只读核对 `_vt658.py` **40/40 全过**（含两树逐项对齐、forge/neo 各自的「靠近判据」、语言文件本批 3 键逐字一致）。
- 打包闸门全过：`out_promaid` 542 个 class、`out_promaid_neo` 543 个 class 全部在 jar 里且 jar 无多余 class；lang json OK；MISSING none。
- 出包：`promaid-1.3.3-forge-1.20.1.jar`、`promaid-1.3.3-neoforge-1.21.1.jar`；部署三处（1.20.1 客户端 / 1.21.1 客户端 / pack1201 服务端）三个 `match=True`，旧的 1.3.2 包已删。
- 版本 **1.3.3**：v1.3.2 从未发版，但这一批是四条真实修复 + 一条新链路，按老口径进一位尾数，便于你自己对齐「测的是哪一版」。
- **本条尚未发版**——等实测。

## 实测六百五十七【骑扫帚原地乱晃/被反复拉回/不升空 → 只让服务端驱动 / 上不了扫帚 → force 骑乘 / 烧制清单"没有食物" → 面板诊断】

### ① 反馈原文

> 女仆在坐上扫帚之后，在原地进行左右鬼畜晃动以及被反复拉回。也没有有关的上升高度等操作。烧制清单那里面为什么没有食物呢？

### ② 结论先说

| # | 现象 | 结论 |
|---|---|---|
| 1 | 坐上扫帚后**原地左右鬼畜晃动 + 被反复拉回 + 完全不上升** | 真根因找到了：**客户端也在驱动同一把扫帚**。推进意图是一份"取走即清"的队列，客户端与服务端各读一次——谁先读到谁动，另一侧读到空就归零悬停。服务端不动 → 她永远不升空；客户端动完又被服务端的权威位置拽回 → 左右抖。现在跟原版同款口径：**只让服务端驱动** |
| 1b | 顺带修掉另一条"根本骑不上"的路 | `startRiding(...)` 不加 force 时，原版要求她**此刻不是任何载具的乘客**（`canRide` = `!isPassenger() && …`）。她会坐在椅子（我们自己的钓鱼椅就是这样坐上去的）或别的载具上 → 这里恒返回 false → 每 tick「取出一把扫帚 → 骑不上 → 还回背包」，而**面板上什么都不会报**（缺件判据看到"扫帚物品在"就放行）。现在走 **force 骑乘**把她换过来，换过来这件事也写日志 |
| 2 | 烧制清单里**没有食物** | **这一条我还没能确认原因**（见 §⑤）。按代码食物应该在：生肉/猪排/鸡肉/土豆/海带在原版都有熔炉 + 烟熏配方，走的是同一条 `getRecipeFor`。所以这一条先加**面板诊断**：进这一页时在日志里打一行事实（网格件数 + 9 个代表物品的真/假判定），下次实测把那一行发我就能一次定位 |

### ③ 第 1 条单独说：为什么"客户端也驱动"就等于"谁都动不了"

`EntityBroom` 的飞行由我们的 mixin 在 `travel` 的 HEAD 接管，而 **`aiStep` 里那次 `travel` 调用是客户端也会走到**的（字节码实证）：

```
LivingEntity.aiStep():
     getControllingPassenger()                    → astore 11
     instanceof Player ? travelRidden(p, vec3)
                       : travel(vec3)              ← 这一句【没有】isControlledByLocalInstance 门槛
对比原版 travelRidden(...) 内部：
     tickRidden(player, vec3)
     if (isControlledByLocalInstance()) { setSpeed(...); travel(...); }   ← 原版刻意只让一侧真正施加位移
```

也就是说："玩家驾驶"那条路上原版**专门加了这道闸**，我们仿的那条路漏了。于是同一 tick 里客户端与服务端各驱动一次，而两者共享同一份意图（`THRUST`：mixin 取走即清）——两边抢，服务器读空 → `setDeltaMovement(ZERO)` → 悬停。玩家看到的就是：她在原地左右抖（客户端那份动了、随后被服务端位置拽回）、永远不升空、起飞/爬升两个相位都在 4 tick 内判"没长高"而结束。

**修法**（一处）：mixin 里跟着原版同款口径——客户端一律不驱动，位置等服务端权威同步；服务端照旧用我们写的推进意图飞。顺带把"接管"这件事留痕（`[promaid/扫帚接管] 服务端开始接管扫帚驱动`），下次一眼就知道链路通没通。

### ④ 这一批把"静默失败"全部改成"会说话"

出问题的那次实测里，日志只有「接敌 → 先向上爬 8 格」和「头顶被顶住」两句，而**这两句都无法区分**"真被方块顶住"和"驱动根本没生效"——诊断因此绕了远路。现在这四种情况各有一条日志：

| 日志（搜「扫帚模式 / 扫帚接管」） | 含义 |
|---|---|
| `取出扫帚骑上（消耗 …）→ 原地抬起 1 格` | 这把扫帚是我们放出来的（不是玩家放的） |
| `她已经在一把扫帚上了（不是本模组放出去的）→ 直接接管驱动` | 没看到"取出扫帚骑上"不等于没骑上——旧版这两种情况日志一模一样，现在能分辨 |
| `她本来在 <载具> 上 → 强制换到扫帚` / `骑不上扫帚：startRiding(force) 返回 false（当前载具=…）` | 上座这件事的过程与失败原因 |
| `爬升到位（y 0.00 → 1.35，想抬 1.35 格）` / `头顶被顶住 → 就地悬停（y 0.00 → 0.00，4 tick 没长高、整段只抬了 0.00 格）` | 带起止高度：**抬了 0 格 = 驱动没生效；抬了一半停住 = 真的撞到方块**（后者是正常行为） |
| `想飞却没骑上扫帚（ridingBroom=null；当前载具=…）→ 这一拍不动` | 5 秒一条上限。只要出现，就说明有东西挡着上座 |
| `玩家在驾驶这把扫帚 → 扫帚模式只负责开火、不接管飞行` | 你自己骑在这把扫帚上时她**不会自己飞**是设计：驾驶权在玩家（原版 `IBroomControl` 是给"玩家驾驶、女仆搭乘"设计的）。想让飞她就把玩家下扫帚 |

另外行为侧多了一道 **②.4「玩家在驾驶 → 让位」**：玩家在开的时候不再写推进意图、也不开爬升相位（旧版会每 tick 判一次"顶头"，白刷日志）。

### ⑤ 第 2 条单独说：烧制清单为什么可能"没有食物"

v1.3.2 起候选网格只列**当前世界真有炉子配方**的物品（熔炉/烟熏炉/高炉任一）。按代码，食物应该在里面；所以"没有食物"只可能是下面三种之一，而它们在游戏里**长得一模一样**：

1. **客户端配方表拿不到配方**（`level.getRecipeManager()` 在客户端为空/不全）——那网格会普遍变少，不只是食物；
2. **你这个整合包把食物的炉子配方改掉了**（很多包为了让玩家用炒锅/烤架，会把原版食物熔炼删掉）——那网格会**正确**地不列它们；
3. **你看的那一档是燃料档**（"只用这些燃料 / 禁用燃料"只列能当柴烧的东西，食物本来就不该在）。

所以这一条先不猜、也不改判据，而是**把事实打进日志**：每进一个档位记一条

```
[promaid/烧制清单] 候选网格 61 件 | 档位=只烧这些 | 世界=有 | 代表物品= minecraft:beef=真 minecraft:porkchop=真 minecraft:chicken=真 minecraft:potato=真 minecraft:kelp=真 minecraft:iron_ore=真 minecraft:sand=真 minecraft:cobblestone=真 minecraft:bread=假
```

- 若 `beef=假`：说明这套判据在当前环境里拿不到配方 → 我们改成"拿不到配方表就退回不过滤"（并顺手查为什么拿不到）；
- 若 `beef=真` 而网格里仍然找不到：是**显示/搜索**那一侧的问题，我照这一行继续查；
- 若 `beef=真` 且网格里有、只是你翻不到：那就是第 3 条（燃料档）或搜索词的问题。

（`bread=假` 是**正确**的：面包在原版没有熔炼/烟熏配方，它只是产物。）

### ⑥ 验证与版本

- 两树 `javac` **0 错误**；`_vt657.py` 只读核对 **125 项全过，FAILS = 0**（含两树改动区间的中文注释逐行一致、数值字面量一致；含扫帚 mixin 的客户端闸必须在 `takeThrust` 之前）。
- 打包：`patched/promaid-1.3.2-forge-1.20.1.jar`（10083647 字节）、`patched/promaid-1.3.2-neoforge-1.21.1.jar`（10105259 字节）；打包闸门全过，并逐个在 class 常量池里确认了新日志字符串（确认装进去的是新字节码）。
- 部署三处 `match=True`（1.21.1 NeoForge / 1.20.1 Forge / 服务器包 pack1201），桌面发布素材里的两个 jar 同步刷新。
- 版本**仍是 v1.3.2**：这一批修的是 v1.3.2 里那个"根本没飞起来"的同一件事，而 v1.3.2 从未发过平台，按约定原地覆盖、不空跳版本号。
- **本条仍未发版**——等你测。测的时候看三处：①她骑上扫帚后是否**平稳抬起约 1 格**（不再原地抖、不再被拽回）；②遇到敌人是否**先升 8 格**再绕圈（日志会写"爬升到位（y …）"或"头顶被顶住（…整段只抬了几格）"）；③打开「烧制清单」子页后，把 `[promaid/烧制清单] 候选网格 …` 那一行发我。
## 实测六百五十六【烧制清单"乱填" / 骑扫帚反复被拉回 / 移动照搬原版 / 起飞 1 格 + 接敌升 8 格 / 跟随同款】

### ① 反馈原文

> 1.只烧什么这这张表感觉是乱填的。那些不可烧东西都进这张表了。原本可以烧制的食物和物品什么的反而不在里面。这个不乱写吗？
> 2.女仆在乘坐扫帚的时候会反复被拉回。具体可以看一下日志。我觉得应该这样。如果拿到了扫帚，原地往上飞1格悬停（头顶如果被顶住了那就悬停在此处。），然后引用飞行跟随的逻辑以及同款机制来决定是否启动跟随。（这个是默认开启的，因为扫帚没有耐久。），遇到敌人之后，先向上飞8格（头顶如果被顶住了，那就悬停在此处），然后绕着敌人盘旋。其实空袭的机制里面也大概可以进行借鉴，只是少了烟花火箭那一步。同时为了体现鞘翅的高机动性，扫帚的移动速度肯定是只能照搬原版，不能有所加速的。

### ② 逐条处理

| # | 问题 | 处理 |
|---|---|---|
| 1 | 烧制清单的候选网格"乱填"：不可烧的都在、能烧的不在 | 网格按当前档位过滤：①②「只烧这些/禁止烧制」只列**当前世界真有炉子配方**的物品（熔炉/烟熏炉/高炉任一，且排除工具盔甲三叉戟盾与附魔/用旧物品）；③④「只用这些燃料/禁用燃料」只列**原版能当柴烧**的物品。标题也写明"只列……"。判据放在 `MaidCookBehavior`（与女仆真正取料时同一个），所以"面板上看得见的"和"她真的会烧的"不会是两套东西 |
| 2 | 骑扫帚时反复被拉回 | 根因是 vanilla 的乘客时序（见下 §③）：**每 tick 先让她按自己的逻辑走一步、再由扫帚 `positionRider` 把她按回鞍位**。本模组那三套"地面走位"（战斗战术的 kite/绕圈、自保的垫高/逃跑/瞬移、威胁驱动的直连导航）正好在这半拍里把她推走 → 被拉回 → 再推走。现在**她骑扫帚飞行期间这三处全体让位**（与"飞行作战期间不触发自保"同一条闸、同一个写法）；往哪飞由扫帚驱动全权决定 |
| 2a | 移动速度照搬原版、不加速 | 速度不再是本模组拍的数字：水平上限 `0.75` 格/tick、竖直 `0.30`（原版跳跃键那一档是 0.5，这里刻意收一半）、阻尼 `lerp 0.25`、无输入衰减 `×0.75`——**全部逐字取自 TLM 给玩家驾驶写的 `PlayerBroomControl.travel`**。区别只是"目标速度由距离算出来"而不是"按满 W"，因为 AI 没有键盘 |
| 2b | 拿到扫帚先原地往上飞 1 格悬停 | 新增**起飞相位**：骑上那一刻登记"她脚下 +1 格"，先垂直抬到位才开始"去哪"；抬不动（头顶被顶住）就地悬停 |
| 2c | 遇敌先向上飞 8 格再盘旋 | 新增**接敌爬升相位**：以敌人为单位，每次接敌先向上爬 8 格（顶头即就地悬停），爬到再进盘旋。**同一场遭遇只爬一次**（相位做完打 `done` 标记而不是删掉，否则下一 tick 会当新相位从头爬 = 无限上蹿） |
| 2d | 平时引用飞行跟随的逻辑与同款机制来决定是否跟随 | 直接复用飞行跟随那一对**起手/收手距离**（默认 25 / 5）：主人远过起手距离才飞过去、进到收手距离内就停下悬停，中间是迟滞带。口径只有一处（`FlightFollow` 那两个配置项同时管两条链路）。开关仍是「平时跟随主人」（默认开，扫帚没耐久） |

**"头顶被顶住就悬停在此处"怎么判**：不查方块，看**她自己有没有长高**——原版 `Entity.move` 撞到方块就动不了，所以"想往上却没上去"本身就是被顶住的证据（连续 4 tick 没长高即认定）。这比查碰撞箱更稳：半砖、楼梯、火把、水各自有各自的碰撞形状，而"没长高"是事实。

### ③ 第 2 条单独说：为什么"反复被拉回"（这次是从 vanilla 源码读出来的，不是猜）

```
ServerLevel.tickNonPassenger(载具)  → 扫帚先 tick：自己移动 + positionRider 把她摆上鞍位
ServerLevel.tickPassenger(载具, 乘客) → 她的 rideTick():
     Entity.rideTick() {
         this.setDeltaMovement(Vec3.ZERO);
         this.tick();                            // ← 她照样跑自己的整条 aiStep（含她的走位）
         getVehicle().positionRider(this);       // ← 紧接着再被按回鞍位
     }
```
`_revmc/net/minecraft/world/entity/LivingEntity.java` 里 `aiStep()` 是**无条件**调 `this.travel(...)` 的，乘客也不例外。所以那不是"她偶尔被拽一下"，而是**每 tick 必然重演一次**：地面走位把她推走 → 扫帚把她拉回。另外自保里的 `teleportTo(...)` 更狠——它会直接 `unRide()` 把她从扫帚上踹下来。

顺带纠正上一版（实测六百五十五）我在注释里写错的一句：`SchedulePos.tick` 里先挡住的其实是 `isWithinRestriction()`（圈内就不动），**不是** `isPassenger()`；乘客那条是紧随其后的 `if (!canBrainMoving()) return;`。结论没变（TLM 不会把乘客传送回圈心），但注释已按字节码改正。

### ④ 验证与版本

- 两树 `javac` **0 错误**（forge 333 源 / neo 334 源）；`_vt656.py` 只读核对 **137 项全过，FAILS = 0**（含两树改动区间的中文注释逐行一致与数值字面量一致）。
- 打包：`patched/promaid-1.3.2-forge-1.20.1.jar`（10076767 字节）、`patched/promaid-1.3.2-neoforge-1.21.1.jar`（10098316 字节）；打包闸门（jar/out 一致、lang JSON、mixin class 齐全）全过。
- 这一批改了多处真实 bug 与手感，按版本约定**最后一个数字 +1 → v1.3.2**。

**本条尚未发版**——等你实测。测的时候看四处：①骑上扫帚是否"原地抬起约 1 格 → 平稳飞过去"（不再原地抖动/被拽回）；②遇敌是否先升 8 格再绕圈；③主人走远（超过起手距离 25 格）她是否会跟过来、靠近后悬停；④烧制清单页的网格里是不是只剩真能烧的东西（燃料档只剩能当柴的）。
## 实测六百五十五【扫帚模式实测六条：面板死按键 / 骑上后换不了武器 / 空中上下摆动 / 两条语音 / 任务名英文 / 排班表加「女仆配置」】

### ① 反馈原文

（用户）「简单测试下来，就发现了不少问题。

1. 烧制功能的配置面板的按键是死按键，按了根本就没用。
2. 女仆虽然可以骑上扫帚，但是没有办法给他更换武器，毕竟一个右击就坐上去了，这个不管。不过走跟远程空袭一样能自己换武器
3. 女仆坐上去之后，虽然会往上飞一点，但是是在一个高的空中上下摆动的一个鬼畜状态。面会朝着主人，但是不会朝主人飞行。遇敌也一样
4. 模组内给女仆右击投喂蛋糕的时候没有语音。同样的，新任务也没有语音。这个不是bug，你只要把API打开就训练就行了。
5. 任务名显示的时候显示的是英文。
6. 排班表快捷模式里加一个功能。可以直接打开这个女仆内部的配置页面。（就跟正常右击女仆打开的效果一样）」

### ② 逐条：症状 → 根因 → 修法

| # | 症状 | 根因 | 修法 |
|---|---|---|---|
| 1 | 烧制面板上的按键按了没用 | 烧制清单**子页**的 `mouseClicked` 里写的是 `return this.clickCookGrid(...)`。`clickCookGrid` 只在命中物品格时返回 true，其余一律 false——而这个 false 被**直接 return 出去**，`super.mouseClicked` 永远不会执行。于是子页上每一个子控件（四张名单的模式按钮、搜索框、「加进当前名单」、名单行里的「移出」、`← 返回参数`）全部收不到点击，整个子页只能按 ESC 退出。对照同机制能用的几个子页（挖矿/伐木、投喂、喂水、替代品）：它们都是「命中网格就 `return true`，否则**落到** `super.mouseClicked`」——只有烧制这一处是特例 | 改成与其余四个子页同构的写法（两树） |
| 2 | 骑上扫帚后没法给她换武器 | 两条路同时断了：① 她骑在扫帚上时玩家右键是「上/下扫帚」，打不开她的背包界面；② v1.3.0 我给扫帚模式写的是「**不自动换装**」早退（怕并进飞行分支会让她长出鞘翅），于是自动换装这条路也是关着的 | 扫帚模式改为调用 `MaidToolAutoEquip.ensureForTask`——扫帚任务的 UID 不在它的特殊表里，于是落到末尾那条「实现了 `IAttackTask` 的模组战斗任务 → 用任务自带的 `isWeapon` 匹配武器」的**通用分支**，而 `MaidBroomTask.isWeapon` 正是 `MaidFlightKit.isRangedWeapon`：即「从背包里挑一把远程武器换到主手」，与远程空袭**同一口径、同一份实现**。**仍不并进飞行分支**（那个分支会给她穿鞘翅、跑空袭索敌探针与牵引绳） |
| 3 | 会往上飞一点，但在空中上下摆动（鬼畜），脸朝着主人却不朝主人飞；遇敌一样 | 我上一版把凋灵那套**增量式**推进直接搬了过来，见 ③ | 速度改为**由距离直接决定**（不再累加）+ 起飞相位 + 盘旋改成固定**线速度**，见 ③ |
| 4 | 投喂蛋糕没有语音；新任务也没有语音 | 投喂那条**刻意**走直发 `addChatBubble`（为了保住蓝色气泡、跳过 5 秒限频，见该类的注释），而全模组只有两个语音入口，一个挂在 `addTextChatBubble` 的 mixin 上——它从来没进过语音链路；扫帚缺件那条虽然进了链路，但内置 `manifest.json` 里没有对应条目，于是落到「TLM 语音站点」那条路（站点没配就静默） | 投喂处**显式补一次 `speak()`**（气泡颜色与限频行为一个字不动）；同时给 manifest 补两条、写日语台词、用 GPT-SoVITS 合成 ogg，见 ④ |
| 5 | 任务名显示英文 | TLM 的任务名走 lang 键 `task.<命名空间>.<路径>`（`IMaidTask.getName()` 的默认实现）。本模组每条任务都有键，唯独新加的 `task.maid_smart.broom` **没写** → GUI 拿不到译文、退化成显示原始键；顺带查出英文 lang 里 `task.maid_smart.woodcut`（伐木）**从有这一条起就一直缺** | 四份 lang 补齐扫帚的中英文键；英文补上伐木那条 |
| 6 | 排班表快捷模式里要能直接打开女仆配置 | —（新功能） | 快捷设置页底部新增「`⚙ 女仆配置`」按钮 + 新 C2S 包；服务端按 UUID 找她 + 校验归属后调 `EntityMaid.openMaidGui(player)`（**与右键女仆同一个入口**：背包 / 外观 / 行为设置），见 ⑤ |

### ③ 第 3 条单独说：这是我上一版写错了

凋灵的移动在它的内部类 `WitherBoss$WitherMoveControl.tick()`，核心那一行是

```
wither.setDeltaMovement(delta.add(d.scale(speed * 0.05 / len)));
```

——**每一拍在上一拍的速度上再加一点**（增量式）。它在凋灵身上稳定，是因为凋灵走原版 `travel`：那里每 tick 会给 `deltaMovement` 乘一次空气阻力（0.91），「加一点 + 乘 0.91」配起来正好是一个一阶收敛系统，速度∝距离、到点自然收干。

而扫帚模式的扫帚是**由 mixin 在 `travel` 的 HEAD 处 `cancel` 掉**原版位移的（没有玩家驾驶时我们接管）——**阻力那一层没有了**。于是同一行公式变成一个**无阻尼的谐振子**：她冲向目标点、速度不收、冲过去、反向加速、再冲回来……**振幅 = 初始偏差，且永不衰减**。

玩家看到的两件事都能从这一个原因解释：

- **「在高的空中上下摆动」**：跟随位的高度是「主人脚下 +2 格」。她一起飞就与目标点差了 2 格，这 2 格偏差被喂成了一个周期约 1.4 秒（`ω=√0.05`）的上下振荡，振幅 ±2 格——也就是她会在主人上方 0~4 格之间来回窜，永远不会停下来。
- **「脸朝着主人但不会朝主人飞」**：水平方向她本来就站在跟随圈上（水平偏差≈0），所以只有垂直那一路在振；朝向是每 tick 明确设成「看着主人」的，于是"看着主人原地上下跳"。

**修法**：速度**直接由距离决定**，不做任何累加——

```
速度 = 单位方向 × 限速 × min(1, 距离 / 1.5)
```

远处全速、近了按距离线性收干（`dist → 0` 时速度 → 0），到 0.35 格内直接清零原地悬停。这样「位移 → 速度 → 位移」是**一阶系统，数学上不可能振荡**；同时保留了玩家要的凋灵观感：朝目标点飞、到点停住、朝向 = 速度方向。水平/垂直分别限速（0.35 / 0.22 格每 tick）。

另外两处一并改了：

- **起飞相位**（玩家原话「女仆会立刻用扫帚飞起来 1 格」）：骑上那一刻记下「她脚下 +1 格」，先垂直抬到位、再开始"去哪"。旧版是直接塞一个向上的瞬时速度——同一 tick 里紧跟着的 `steerTo` 会把它覆盖掉，而且没有阻尼也根本停不在 1 格处。
- **盘旋改成固定"线速度"**：旧版是固定角速度 0.09 rad/tick，在 8 格半径上等于圆周线速度 0.72 格/tick——是她限速（0.35）的两倍多，**她永远追不上那个点**，表现成"绕不动、只在原地抖"。现在按半径换算（`step = 0.14 / 半径`），任何半径下她都能稳稳跟上。

### ④ 第 4 条：两条语音怎么补的

台词（日语，走 GPT-SoVITS）：

| 文件 | 情绪档 | 台词 |
|---|---|---|
| `cake_eat.ogg` | care（温柔·放慢） | `ケーキ、すごくおいしい…ご主人様の気持ち、ちゃんと届いたよ〜` |
| `broom_not_ready.ogg` | plead（为难·撒娇） | `箒モード、まだ足りない物があるみたい…ちょっと待っててね` |

- 内置 manifest 加两条（`contains` 前缀匹配）：`蛋糕真好吃` → `cake_eat.ogg`、`扫帚模式还差` → `broom_not_ready.ogg`。两树 manifest **字节一致**，条目数 122 → **124**。
- 生成管线照旧：`gen_voice_pack.py`（给 `run` 加了「只补合成指定文件」的参数，补一两句不必把 124 条全量重跑）→ GPT-SoVITS API（`127.0.0.1:9880`，`API.bat`）→ `norm_voice_pack.py`（峰值归一化到 -1.5 dB + libvorbis q4）→ `install_voice_pack.py`（两树各装一份）。
- 玩家说的「你只要把API打开就训练就行了」——按此执行：这两条是**真跑过 API 生成**的，不是占位。

### ⑤ 第 6 条：新网络包

- 客户端：快捷设置页底部右侧新增「`⚙ 女仆配置`」按钮（放在**底部按钮行**，不占正文行——正文第 5 行已经排到 y=182，而 240 高最小窗口的内容下沿是 184，再插一行会压住底部提示），只在这一页、且已选中女仆时可点。
- 服务端：新包 `SchedulePacketsMaid.OpenMaidConfigPacket`（C2S，只带女仆 UUID）→ `findMaid`（全维度按 UUID 找）+ `allowed`（主人或 OP）→ 调 `EntityMaid.openMaidGui(player)`。
- **为什么必须服务端开**：`openMaidGui` 内部只对 `ServerPlayer` 生效（字节码实证：走 `NetworkHooks.openScreen(serverPlayer, provider, buf)`），客户端单方面调用没有任何效果。
- 注册槽位：forge 树 `SimpleChannel` 索引用 **19**（13 依旧空着不补位，理由见该处注释）；neo 树走 `playToServer`。

### ⑥ 这一批没做的（明确留着）

- **编队**（玩家已排除：成本太高）。
- 缺件时**不**退回地面近战（原地待命 + 气泡报缺件）——第一批的取舍，没变。
- 只认她**背包里**的扫帚，不骑世界里现成的扫帚实体。
- **有玩家在驾驶时**原版行为一字不改（驾驶权仍归玩家与 TLM 的 `IBroomControl`）。
- 游戏内手册**还没加**「扫帚模式」这一页（上一批就留着）。
- 「女仆配置」按钮只加在**快捷设置页**（玩家点名的就是这一页），排班页没加。

### ⑦ 验证与版本

- 两树 `javac` **0 错误**（forge 333 个源 / neo 334 个源；只有既有的 deprecation / unchecked 注记）。
- 只读核对脚本 `_vt655.py`：**78 项检查 / FAILS = 0**——版本号、jar 内新类与新语音、两树 `manifest.json` 与两个 ogg **字节一致**、四份 lang 的键、六处代码点，以及两树改动区间里「中文注释逐行一致 + 数值字面量一致」。
- 打包 gate（`verify_jar_classes.py`，jar 与 out 目录一致性）+ lang JSON 校验：通过。
- 两个 jar 已部署到三处（1.21.1 NeoForge、1.20.1 Forge、`pack1201` 服务端）并刷新桌面素材。

- **版本 v1.3.1**：按你定的规矩——这一批是「修复了较多的 bug」，所以动的是版本号**最后一位**（不是覆盖 v1.3.0）。两个 jar 都重新打包，所以 v1.3.0 的扫帚模式与本批修复都在里面。
- **本条尚未发版**。测试按约定留给你：给她**扫帚 + 远程武器** → 切「扫帚模式」，看她是不是「立刻抬起 1 格 → 朝主人/敌人平稳飞过去 → 绕着开火」，以及骑乘期间**武器能不能自己换**。
## 实测六百五十四【新功能·扫帚模式：女仆自己骑扫帚飞起来打——改造 TLM 的扫帚让它听女仆的】

### ① 需求原文

（粉丝建议）「原版女仆可以骑扫把但是必须玩家驾驶，模组里可以设置女仆无需玩家独立骑扫把并且和多个女仆一起形成飞行编队，可以设定跟随玩家飞行，可以编辑编队形状。可以远程操作编队成员的战斗职能。」

（用户取舍）「设计编队就算了，成本太高了。但是别的东西值得采纳。加入一个新功能叫做扫帚模式。在此模式下女仆会去寻找扫帚坐下，然后检查有没有远程武器，没有的话就报（同空袭）。如果有，那就是成功激活了此模式。然后女仆会立刻用扫帚飞起来1格，随后战斗时运动的逻辑就同凋灵，手上武器的运作直接照搬远程空袭模式。平时会跟随主人……」

### ② 为什么必须动 TLM 的扫帚（字节码实证）

TLM 的扫帚是**真载具**（`EntityBroom extends AbstractEntityFromItem extends LivingEntity`），也确实留了扩展点 `IBroomControl` + `BroomControlManager`——但它的每个回调都**要求一个 Player 参数**：

```
EntityBroom.getControllingPassenger()  →  只有【第一乘客是 Player】时才返回非空
EntityBroom.travel(Vec3)               →  只有「控制乘客是 Player 且第二乘客是 EntityMaid」
                                          才遍历 broomControls 调 IBroomControl；否则走重力下坠
```

也就是说这套 API 是给「玩家驾驶、女仆搭乘」设计的：女仆一个人骑上去时 `getControllingPassenger()` 是 null，扫帚**直接自由落体**。所以「女仆独立骑扫帚」只能在一个地方接管——新增 `EntityBroomMaidTravelMixin` 注入 `EntityBroom.travel` 的 HEAD（forge 树为 `m_7023_`），三条全中才接管（第一乘客是**本模组所有的、任务为扫帚模式**的女仆，且**没有玩家在驾驶**），否则原版一字不改。

### ③ 这一版做了什么

| 件 | 说明 |
|---|---|
| 新任务 `maid_smart:broom`（扫帚模式） | 图标 = TLM 的扫帚；实现 `IRangedAttackTask`（拿到「用手上的远程武器开火」这条通道）；**不参与自主切换**（与两种空袭同款） |
| 骑上 / 下来 | 从她背包（或精妙背包 / 旅行者背包）取 1 把扫帚物品，在**她脚下**放出一把扫帚并骑上去（与玩家手持右键放置同款、同样消耗那件物品）；收工时按**当初那一件**精确还回背包（连自定义名字一起）——扫帚不损耗、不掉在地上 |
| 立刻飞起来 1 格 | 骑上来的第一 tick 给 0.42 的向上速度（与飞行跟随的起跳同一档） |
| 战斗移动（**凋灵式**） | 照搬原版凋灵内部类 `WitherBoss$WitherMoveControl.tick` 的推进公式：`v += dir ×(speed×0.05 / 距离)`、到点 `v × 0.5` 阻尼、朝向 `-atan2(dx,dz) × 57.295776`。只改一处：站立距离从凋灵的「碰撞箱大小」（近战贴脸）换成可配的 `combat.broomRange`（默认 8 格）——她拿的是远程武器，必须把距离拉开；另加「方位角缓慢旋转」（绕着她转圈打，一圈约 3.5 秒）与水平 / 垂直限速 |
| 开火 | **整条复用远程空袭**：`MaidFlightRangedTask` 的开火实现（枪械排除 / 三叉戟 / 御币弹幕 / 弩射烟花 / 弩装配 / 弓造箭）暴露成公共静态入口，扫帚任务委托**同一个方法**；开火调度（射程 / 视线 / 冷却 / 枪械换弹瞄准）走 `MaidFlightCombatBehavior.fireRanged` **同一个方法**。口径只有一处，不抄第二份 |
| 缺件报告 | 形状与空袭 `notifyNotReady` 完全一致（入世界宽限 0.5 秒 → 缺件要连续站稳 2 秒才报 → 15 秒冷却）：气泡 + 运行日志报「扫帚 / 远程武器 / 弹药」 |
| 平时跟随 | 无敌人时悬停在主人身边（水平约 3.5 格、高 2 格）跟着飞；`combat.broomFollow` 关掉则原地悬停待命 |
| 配置（面板：战斗与自保） | `combat.broom`（总开关）、`broomRange`（默认 8）、`broomHover`（默认 2）、`broomFollow`（默认开）、`broomClampHome`（默认开）——面板里有对应 5 行 |

### ④ 与 home 模式的关系（你说最难的那条）

根因（字节码实证）：她一旦骑上扫帚就是**乘客**，`EntityMaid.canBrainMoving()` 在 `isPassenger()` 时为 false → TLM 自身那套范围约束（寻路节点评估、任务选点过滤）**整条失效**，往哪飞只听我们的。

好消息：TLM 的硬约束 `SchedulePos.tick` 里紧跟着 `if (!canBrainMoving()) return;` → **不会被传送拉回**（不会出现「飞出去→被拽回来→再飞出去」，也不会被拽下扫帚）。

坏消息：既然没人管「守家」了，就得我们自己管。所以所有飞行目标点（战斗盘旋位、跟随位、待机位）统一过 `MaidBroomKit.clampToHome`：**只夹水平**（活动范围圈本来就是水平圆，纵向没有半径这一说）、圈心没配好就不夹（免得把她拴到世界原点）、半径读的是 `getRestrictRadius()` 的**放大后**读数（战斗任务本来就有的 `CombatWorkRange`，默认 15）——与近战 / 空袭「接战时可以追远一点」同一口径。敌人在圈外就不追（仍可原地开火）。

### ⑤ 怎么用

1. 给她一把扫帚（TLM 的扫帚物品）+ **任意远程武器**（弓 / 弩 / 御币 / 三叉戟 / 枪械）；
2. 把她的任务切成「扫帚模式」（**只有你手动指定才会进**——被袭击时她不会自己换上扫帚）；
3. 她会取出扫帚、在脚下放一把、骑上去先抬升 1 格；遇敌绕圈开火，没敌人就悬停跟着你。

### ⑥ 这一批**没做**的（明确留着）

- **编队**（你已排除：成本太高）——一个字都没做；
- **未激活时的地面近战回退**：缺件时她是「原地待命 + 气泡报缺」，**不会**自己冲上去挥拳头（空袭那边是退回地面战斗；扫帚这批刻意没做，免得把两条战斗链路的交互一起搅进来）。所以缺件气泡里也**没有**写「先按普通战斗来」——那句话在这里会是假的；
- **骑世界里现成的扫帚**：这批只认她背包里的（我问过的那道选择题的推荐项）；
- **玩家同乘时的驾驶权**：有玩家在驾驶时原版行为一字不改（想「女仆抢驾驶权」要再接 `IBroomControl`）；
- **游戏内手册章节**：手册还没加这一页（changelog 先写全）。

### ⑦ 验证

两树 `javac` **0 错误**（forge 333 个源文件 / neo 334）；`_vt654.py` 只读核对 **FAILS=0**（新文件两树镜像、任务与 mixin 注册、5 项配置的声明 + 定义、4 份 lang 的 6 个键与 BOM、面板 5 行、「唯一的移动执行者是 mixin」不变量、编译产物）；打包 gate（`verify_jar_classes.py`）通过。

- **本条尚未发版**。测试按约定留给用户。
- 本条与 实测六百五十三（烧制卡死）**一起进 v1.3.0**——两个 jar 都重新打包，所以 v1.2.5 那批修复也在这两个包里。

## 实测六百五十三【「烧制时站在炉子前卡死」的真相：炉子里躺着一件永远烧不动的东西 + 三条根治】

### ① 反馈与排查

（用户原话）「将女仆调整成烧制模式的状态下，女仆会直接站在原地卡死不动。这是我在刚刚启动1.21.1版本的时候发现的问题。上次测试1.20.1的时候是可以运行的。你可以再看看日志，细细排查一下。」

### ② 日志实证：她喂了一根烧不动的胡萝卜，从此槽 0 被占死

`D:\.minecraft\versions\1.21.1-NeoForge_21.1.250\logs\latest.log`（2026-09-24 18:57:44 ~ 18:58:04）：

```
18:57:44.857 cook start: maid=大正女仆酒狐 furnace=BlockPos{x=-156, y=-60, z=186}
18:57:44.868 cook round: maid=大正女仆酒狐 be=FurnaceBlockEntity fedSlot0=1 minecraft:carrot
18:58:04.498 cook start: maid=大正女仆酒狐 furnace=BlockPos{x=-156, y=-60, z=186}   ← 重新绑上同一口炉子
（此后直到 18:58:11 退出游戏，日志里再没有出现过任何一轮 cook round）
```

原因链：

1. 那口熔炉的槽 0 被**喂进了一根胡萝卜**（旧版硬编码白名单把胡萝卜当食材，实测六百五十一已从白名单里删掉）；
2. 胡萝卜在原版 1.21.1 **没有任何炉子配方**（本条把 1.21.1 的 1291 条配方逐个查过：其中炉子类 112 条 = smelting / smoking / blasting / campfire，胡萝卜与腐肉一条都不在）→ 炉子永远不会消耗它 → **槽 0 永远非空**；
3. 旧代码在「槽 0 非空」时**整段跳过喂料**（收完成品就什么都不做），既没有「零进展就换炉子」的退路，绑定匹配又只按旧的 FOODS 判 → 她抱着炉子坐下、永久不动。玩家看到的就是「站在原地卡死」。

**不是 1.21.1 独有**：1.20.1 目录里躺着的是**同一个构建**（`promaid-1.2.4-*`，同日 12:56，两侧 sha256 已核对），这段逻辑两版一字不差。「1.20.1 能跑」只是因为那次测试的炉子/背包状态不同（槽 0 里没有烧不动的东西）。顺带查明：那次 1.21.1 会话加载的仍是**旧 jar**——实测六百五十一/六百五十二 的代码当时只编译到 `out_promaid*`，从未打进过 jar，所以那两轮的修复还没到过玩家手上。

### ③ 三条根治

| # | 病 | 药 |
|---|---|---|
| 1 | 槽 0 躺着「这型炉子根本烧不动」的东西 → 之后每一轮都跳过喂料 | 新增 `canSmeltHere()`：这类东西**取回她背包**（背包满则原地落地）。老存档里那口「毒炉子」自动解毒。判据用 `hasRecipeRaw`，**不看**玩家那四张清单——玩家自己放进去正常烧的矿石不会被误取回 |
| 2 | 「先喂料、后补柴」：她有料却一根柴都没有 → 料卡槽 0、柴补不上、炉子点不着 | 调换顺序：**先补柴、再喂料**；没柴可添就**不喂**（料留在背包里，不留在炉子里当路障） |
| 3 | 以上都躲过去时仍有别的死角（模组炉、开关组合等） | **看门狗**：绑定 + 站桩，但 60 秒（1200 tick）里一次有效动作都没有（不收成品 / 不喂料 / 不补柴 / 不取回）→ 解绑走人换炉子，并打一条 `cook watchdog:` 日志。原版最长的炉子配方只有 200 tick（10 秒），留了 6 倍余量，不会误触发 |

### ④ 顺带对齐：选炉/匹配探针与喂料门同口径

「熔炉烧矿物」关掉时高炉的喂料门本来就是关的（`extractForFurnaceType` 里那道门），可选炉探针还在按高炉配方算「有活干」→ 绑上高炉干坐；「烧任何可烧制物」关掉时同理（探针算它可烧，`extractAnySmeltable` 却返回空）。现在探针与喂料门一致：两个开关都关 = 她不会去绑一口「喂不进」的炉子。

### ⑤ 这口炉子现在为什么「无事可做」（给你一个预期）

把她那次 18:57 的背包（`latest.log` 里装备离开那行的 dump：胡萝卜 63 / 腐肉 49 / 黑曜石 51 / 樱花木板 3 / 钻石 4 / 铁锭 2 / 三叉戟 / 重锤 / 金胡萝卜 / TNT / 末影水晶 / 荧石 / 重生锚 …）逐个对过 1.21.1 原版配方（tag 也展开算过）：**没有一件能进炉子**——腐肉在原版也烧不成皮革。所以修好之后她**不会**再去坐那口炉子，而是四处闲逛等料（这才是「没料可烧」的正确表现，不是卡死）。想看她真烧，往背包里放生牛排/生猪排/生鸡肉、铁矿石/粗铁、圆石、沙子、海带这类真有炉子配方的东西。

### ⑥ 验证

- 两树 `javac` **0 错误**（Forge 58 警告 / NeoForge 20 警告，均为既有的 deprecation / unchecked）；
- 两树 `MaidCookBehavior` 归一化结构镜像（去注释、SRG↔Mojmap 归一）逐行对齐；
- 只读核对脚本 `_vt653.py`：看门狗字段与阈值、`canSmeltHere`/`smeltAnyAllowed`、换序后的分支顺序、镜像比对全部通过。

- **本条尚未发版**。测试按约定留给用户。
## 实测六百五十二【烧制清单：把「燃料」和「可烧制物品」搬进配置面板——四张名单自己说了算】

### ① 需求（用户原话）

「能不能将烧制物品的燃料以及可烧制物品的配置也换成配置面板，就像挖矿，伐木，喂食那样子呢。那个配置面板真的太好用了。」

### ② 以前是什么样

烧制那条链路"该烧什么、该拿什么当柴"**全写死在代码里**：

- **原料**：食材白名单（`FOODS`，硬编码 9 项）+ 矿物标签 + 通用回退，分别挂在「熔炉烧矿物」「烧任何可烧制物」两个开关上；
- **燃料**：**完全自动**——按"燃烧时长 ×100000 + 数量"评分，纯燃料优先、没有纯燃料才退而用可烧制燃料；**一个开关都没有**。

而挖矿 / 伐木 / 喂食早就有面板可编辑名单（点物品图标加入/再点移出 + 中英文搜索 + 翻页），烧制独缺一套。

### ③ 现在：四张面板名单

入口在「生产与工作 → 烹饪与酿造 → **烧制清单**」，页内四个模式按钮切表：

| 名单 | 语义 |
|---|---|
| **只烧这些** | 非空时只把清单里的物品当**原料**（仍要求真有炉子配方）；留空 = 自动判定（食材白名单 + 矿物标签 + 通用回退） |
| **禁止烧制** | 清单里的物品**永不放炉子**，优先级最高 |
| **只用这些燃料** | 非空时只从清单里按燃烧时长挑；留空 = 自动评分 |
| **禁用燃料** | 清单里的物品**永不当柴烧**，优先级最高 |

一条总语义：**禁止永远优先；允许清单非空 = 只在这些里挑，留空 = 自动（旧行为）**。
**四张表默认全空 = 装上就是老手感，行为一字不变。**

### ④ 一条硬约束：允许清单只"缩小"，绝不"放开"

清单判定挂在**配方判定之上**（`hasRecipe` = 清单 + 原始配方），所以就算往清单里写一个原版根本烧不动的物品，也不会让她把炉子占住——那正是 **实测六百五十一** 刚修掉的毛病（"抱着一个烧不动的炉子卡死不动"）。清单改的是"挑哪些"，不是"能不能烧"。模组/数据包给物品补了配方时清单照样认（走的是同一个配方管理器）。

### ⑤ 顺手补的两处

- **「烧木材」开关补进面板**：它一直只写在 toml 里，面板上根本没这一行（面板漏项）；
- **燃料那条路的"这是不是可烧制燃料"改走不看清单的原始判定**：否则"把木材列进禁止烧制"会被算成"纯燃料"反而优先烧掉，与玩家意图正好相反。

### ⑥ 交互（与矿表 / 替代品 / 喂食子页完全同款）

顶部四个模式按钮（当前档黄色 ●）→ 搜索框（中英文皆可）→ 物品网格（点图标加入、再点移出，已加入的格子有 ✔）→ ◀▶ 翻页 → 手填注册名「加进当前名单」→ 底部当前那张名单（每行物品图标 + 中文名 + 「移出」）。改动即时写进内存配置，**关闭面板时统一落盘**（与挖矿矿表同口径）。

### ⑦ 验证

- 两树 `javac` **0 错误**；
- 1.21.1 那侧的整段新代码由 1.20.1 侧**逐条替换生成**，替换后强校验"不许残留 Forge 注册表名 / SRG 名"，两树结构镜像；
- 4 份 lang json 各加 4 个 `config.promaid.misc.cook*` 键（保持 BOM + CRLF，带 JSON 语法闸门）；
- 手册「烧制与酿造」章补第 ③ 条（并把手写死的"注意三点"改成"注意四点"）；
- **本条尚未发版**。测试按约定留给用户。
## 实测六百五十一【女仆「会烧胡萝卜」+ 抱着一个烧不动的炉子卡死不动——同一张硬编码白名单，既当"该烧什么"又当"能不能烧"】

### ① 反馈（用户原话）

「坏了，又出bug了。上次修好了1.20.1版本女仆烧制功能卡死不动的问题，但是1.21.1版本又坏掉了。而且有玩家反馈女仆会烧胡萝卜，明明这个玩意根本就不能烧」

### ② 先说结论：这两个现象是**同一个根因**，而且不是 1.21.1 独有的新故障

烧制代码里有一张硬编码白名单 `FOODS`（注释写的是"原版熔炉可烧食物"），
**它是"她有没有可烧的东西"的全部判据**——`pickFurnaceKind`（选炉型）、
`furnaceMatchesInv`（绑定还成不成立）、`processFurnace`（往炉子里塞什么）
三处都只查 `FOODS.contains(item)`，**一次配方都不查**。

而这张表里混进了五个**原版根本没有任何炉子配方**的条目：

| 白名单条目 | 原版炉子配方 |
|---|---|
| `minecraft:carrot`（胡萝卜） | **没有** |
| `minecraft:beetroot` | **没有** |
| `minecraft:brown_mushroom` | **没有** |
| `minecraft:cactus` | **没有** |
| `minecraft:dried_kelp` | **没有** |
| 同表里的 potato / kelp / 生肉 / 生鱼 | 有（smelting 或 smoking） |

（证据：把两份客户端 jar 的 `data/minecraft/recipes` 全量解出来逐个匹配 ——
smelting / smoking / blasting 三类都查遍，上面五个一条都没有。1.20.1 与 1.21.1 两个版本结论一致。）

于是只要她背包里有胡萝卜：

1. `pickFurnaceKind` 见到胡萝卜就判成 **FOOD** → 她去占一个烟熏炉/熔炉；
2. `processFurnace` 把胡萝卜塞进原料槽（提取路径同样不查配方）；
3. `furnaceMatchesInv` 用的是**同一张白名单** → 恒为真 → **她永远不解绑**，
   抱着一个永远烧不动的炉子坐到底 —— 玩家看到的就是"卡死不动"。

**实测六百三十七 只治了"附近找不到炉子"那一条冻结路径**；这一条是
"炉子找到了、但里面根本没有能烧的东西"，是另一条独立路径，两条都会冻人。
这也是为什么 1.20.1 看着"修好了"、1.21.1 又"坏掉"了：两棵树的这段代码本来就
逐行一致（本次实测做完结构比对，只有 Forge/NeoForge 的 API 名差异），
所以这个毛病**两个版本都有**，只是踩中它的条件不同。

### ③ 改法：让白名单说真话 + 让"能不能烧"去问配方

- **删掉那五个假条目**（`carrot / beetroot / brown_mushroom / cactus / dried_kelp`），
  并在表头写明证据与理由。删条目**不等于功能变少**：模组或数据包自己给这些物品
  补了配方时照样能烧 —— 那条路走的是 `extractAnySmeltable` 与 `pickFurnaceKind`
  的**通用分支**，它们查的是**真实配方**而不是这张表；
- **新增 `isCookFood(level, stack)` = 「在白名单里 且 有烟熏或熔炉配方」**，
  把处置侧四处"是食物就认"的判据（选炉型 ×2、绑定成立性 ×2）和取料那一处
  统一换成它。白名单从此只当**优先项**，不当**事实断言**——万一以后又有人往表里
  加错条目、或者某个数据包把原版配方删了，也不会再出现"抱着烧不动的炉子不放"。

### ④ 顺带修掉同一文件里两处"写了但从来没生效"的判据

这两处不是本次反馈的目标，是在读这段代码时发现并逐个用官方映射
（obf→Mojmap）+ tsrg 链式实证确认的，都在**两棵树里各修一份**：

- **`isSafeToFeed` 的"用旧的耐久物品不熔"闸门从来没生效过**：旧代码是
  `m_41753_() && m_41773_() > 0`，而实证 `m_41753_ = isStackable`（**不是**注释里
  说的 isDamaged）、`m_41773_ = getDamageValue`（**不是** getMaxDamage）。
  `能堆叠 && 有损伤值` 在原版几乎恒为假（能堆叠的不吃耐久、吃耐久的都是单件），
  于是这道闸等于不存在：**她会把自己用过一阵的铁剑之类直接丢进炉子**。
  实测三百四十九 报的是"附魔物品被拿去烧"，那半条由 `isEnchanted` 挡住了，
  这半条一直漏着。现按注释原意写成 `isDamaged() && getMaxDamage() > 0`
  （1.20.1 树：`m_41768_` / `m_41776_`）；
- **`hasOreTag` 一直查错字段**：`m_135827_ = getNamespace`、`m_135815_ = getPath`，
  而 "ores"/"raw_materials" 住在**路径**里（`forge:ores/iron` 的 path 是
  `ores/iron`）。旧代码取的是**命名空间**（永远不含这两个词）→ 本方法**恒返回 false**、
  矿物永远判不出来。后果只是"矿物优先"失效（回退到通用可烧制物那条路照样烧得了矿石），
  所以一直没人发现；既然在改这个文件就一并修回来。

### ⑤ 影响面与验收

- **修好的行为**：女仆不再把胡萝卜/甜菜根/蘑菇/仙人掌/干海带当成"可烧食材"，
  不会再为它们占炉子、也不会再抱着炉子不动；用旧的耐久工具/武器不再被熔掉；
  "矿物优先"开始真正生效；
- **完全不变**：生肉/鱼/土豆/海带的烹饪、燃料挑选、烟熏炉与高炉的分工、
  多女仆分散占炉、绑定后坐下站桩、实测三百四十/六百三十七 的闲逛口径 —— 一个字没动；
- **验收**（测试留给用户）：给她一背包胡萝卜 + 一个空熔炉，她**不该**去占那个炉子，
  该照常闲逛找别的活；给她生牛排，照旧进炉子烤熟；给她一把用过的铁剑，
  不该被丢进炉子。

### ⑥ 验证

- 两树 `javac` **0 错误**；
- 两份客户端 jar 的配方数据全量比对（胡萝卜等五项：两个版本都无炉子配方）；
- 官方映射 + tsrg 链式核对 `m_41753_/m_41773_/m_41768_/m_41776_/m_135827_/m_135815_`
  六个 SRG 名的真实 Mojmap 身份；
- 归一化结构比对：改完两棵树这段代码仍逐行镜像（只剩 Forge/NeoForge 的 API 名差异）；
- **本条尚未发版**。测试按约定留给用户。
## 【实测六百五十】v1.2.4 附件重传（同名覆盖）：女仆放床爆炸后**留下一格水**而不是火——1.20.1 树拆床时把 SRG 名写错了

### ① 反馈（用户原话）

「我发现一个1.20.1的bug，女仆放床爆炸不产生火而是水」

### ② 根因：一行 SRG 名写错，`f_49990_` 是**水**不是空气

空袭链路的床，起爆那一刻会先把自己放下去的那两格床拆掉（`BombPlacement.removePlaced`），
床是两格方块，所以那一支用的是"带抑制形状更新标志的 `setBlock`"而不是 `removeBlock`。
问题就出在这一句的目标方块上——写的是 `Blocks.f_49990_`：

- **`Blocks.f_49990_` = `minecraft:water`**（1.20.1 SRG 实证：`Blocks.f_49990_ = register("water", new LiquidBlock(Fluids.WATER, ...))`。
  本工程 `SelfPreservationBehavior` 的落地水/岩浆放水**也正是拿它当水用**——四处都把 `f_49990_` 赋给变量 `water`）；
- **空气的 SRG 名是 `f_50016_`**（同一份 `Blocks` 实证；本工程其余三十余处"放空气"全都写的它）。

于是这一炸拆完床，原地留下的是一格**水**。而原版爆炸点火那一步的判据恰好是
「那一格是**空气**」——被水占住自然点不着，所以看起来就是"不产生火而是水"。

### ③ 改法：一个字（并把两树对齐）

- `promaid_src/com/maidsmart/combat/BombPlacement.java`：`Blocks.f_49990_` → **`Blocks.f_50016_`**，
  并补一段注释把这两个 SRG 名钉死（含"本工程其它地方拿 f_49990_ 当水用"这条旁证）；
- **1.21.1 / NeoForge 树同一行本来就是对的**（Mojmap 写的是 `Blocks.AIR.defaultBlockState()`），
  本次不动它——两树语义本来就该一致，是 1.20.1 这一边跟丢了。

### ④ 影响面与验收

- 受影响：**只有 1.20.1 的床链路**（重生锚那一支走的是另一个分支 `removeBlock`，不受影响；
  末地水晶底座是"留着 → 到期回收"，也不走这一支）；
- 验收：1.20.1 里让女仆用床炸一次（下界/末地，主世界床不炸），**原地不该留下水**，
  该点着的地方照常是粉色火；开 `combat.pinkFire` 关掉时则是原版橙色火。

（本次为**同名附件覆盖**，版本号仍是 v1.2.4，文件名不变：`promaid-1.2.4.jar` /
`promaid-1.2.4-neoforge-1.21.1.jar`；装之前请删掉 mods 里的旧包。）
## 【实测六百四十九】【issue #22 第三条：农场「收获物自动收集」写的是关、收割产物却直接进背包——查明是 TLM 本体行为，本开关只是措辞误导，改名为「农场掉落物立即拾取」并如实描述】


【①】 反馈与需求（用户原话）

「promaid手册里的配置，农场模式的掉落物自动进背包显示的是关，但是实际上连锁收割之后就是直接进背包了，除非背包满了才会掉在外面。（背包里没有压缩盒和其他额外的东西）」


【②】 排查：不是我们的开关失灵，是"产物本来就进背包"

- 我们的三个收割入口（原版目标格 / 3x3 整片 / BFS 连锁）在调 `collectDrops` 前**都已经用 `MISC_AUTO_COLLECT` 门控**；
- 线上 v1.2.4 的两个 jar 里 `FarmSweepMixin.class` 常量池确有 `MISC_AUTO_COLLECT` 字段引用（**门控确实在**，不是发错包、也不是开关没接上）；
- 真正的原因在**前置模组本体**：javap 反汇编 TLM 1.5.3 的 `TaskNormalFarm.harvest`，
  第 115 条字节码是 `invokevirtual EntityMaid.dropResourcesToMaidInv(...)`——**车万女仆收割时就是把产物直接放进女仆背包**（作物/种子这类不做特殊掉落的作物都走这条路）。issue #20 反馈者描述的也正是这个机制："农场模式会连锁收割农作物。然后掉落物放到女仆背包里。而车万女仆本体支持当女仆背包满了之后会自动放进精妙背包里"。


【③】 结论与改法：改名字、改描述，不改行为

- 面板行名：`收获物自动收集` → **`农场掉落物立即拾取`**；
- 说明改写为："我们做连锁收割/整片收割时，顺手把收割点附近的掉落物直接拾取进她背包（默认关闭）。【注意】车万女仆本体收割时本来就把产物直接放进女仆背包（产物不落地），所以这一行看不出差别；它真正的作用是把「散落在收割点旁的掉落物」也立即捡掉，而不是等她路过再捡；"
- 配置注释与内置手册同口径更新（修复前那条手册只写"产物直接进背包不落地"，正是这句让人以为"关掉 = 产物落地"）；
- **行为一个字没改**（默认仍是关；关了 = 产物照旧进本体背包，散落的掉落物等她路过再捡）。改的是"说法"与"本体行为对不上"这件事。


【④】 为什么不改成"关掉就不进背包"

那就等于要求玩家"收割产物必须落地"——作物/种子属于不做特殊掉落的普通作物，TLM 给它们的处理就是直接入包（这是本体的设计，不是我们的链路）；我们要拦它就得去拦截本体的收割回调并自己把产物吐回地上，属于**为了关键词去改本体行为**，收益为负（会凭空造出"女仆收完地上一堆东西反而要玩家去捡"的新抱怨）。所以本条目只做措辞纠正。

【实测六百四十七】「我卡住了，快喘不过气了」：改成真吃到 >4 点窒息伤害才说（几何命中不再算数）

· **反馈与需求（用户原话）**

「最近我在游玩游戏的过程中发现女仆老不停的说自己窒息了，喘不过气。帮我看一下这句话它的触发逻辑是什么？我觉得触发这句话，至少要真受到超过4点的窒息伤害才可以说。」

· **触发逻辑（改之前的链条，反编译 / 字节码实证）**

那句话是自保行为「环境逃生阶梯」第 4 档（岩浆 → 着火 → 溺水 → **卡墙窒息** → 岩浆避让）里的一句播报，链条是：

1. `envDangerSoft` 里包含 `headInSolid(maid)`：**头部那一格不是空气，且该方块的形状判定为"实"**；
2. 该判定为真 → 自保会话的"环境危险上升沿" → `sessionEnter`（**顺手把 `announcedEnv` 重置为 false**）；
3. `environmentalEscape` 走到第 4 档 → `if (!lavaContext && !announcedEnv)` 成立就 `addTextChatBubble("我卡住了，快喘不过气了！")`。

也就是说旧版**只按几何判定播报，一点伤害都不核实**：她挤过窄缝、被自己/别人放的方块顶一下、上下坡抬头、垫块瞬间位移滞后，都能瞬时命中第 1 条；再加上第 2 条"每次会话说一次"的标记会被不断重置，就成了「老不停说自己窒息了」。（顺带记一笔：`headInSolid` 用的形状谓词是 `m_60796_`，反编译核对下来是"红石导通/满块"那一档，不是原版窒息判定用的 `isSuffocating`（1.20.1 = `m_60828_`）——这属于同一个"几何 ≠ 受伤"的问题域；本次按用户口径先把**播报**改成看伤害，物理判定一个字没动。）

· **改法：先记账、再开口**

· 新增**窒息伤害台账**：`SelfPreservationBehavior` 里 `SUFFOCATE_DAMAGE`（UUID → 累计伤害 + 最后一次入账 tick），在既有的受伤事件里只收 `in_wall`（原版窒息伤害；`data/minecraft/damage_type/in_wall.json` 的 `message_id` 在 1.20.1 与 1.21.1 都是 `inWall`，代码里顺带兼容 `in_wall` 写法）。
· **门槛 = 真吃到超过 4 点**（`SUFFOCATE_ANNOUNCE_MIN = 4.0f`，用户口径"超过 4 点"），累计窗口 **5 秒**（超过 5 秒没有新的窒息伤害就当清零）。原版活体是"卡在可窒息方块里每 tick 吃 1 点"（`LivingEntity.m_6075_` 字节码实证：`if (isInWall()) hurt(damageSources().inWall(), 1.0F)`），所以真被闷住 0.25 秒就能到门槛。
· **只改"说不说"，不改"做什么"**：找不到空气格时的挪位置、`antiSuffocate` 强制顶出、岩浆上下文保护（5 秒窗口）全部原样保留——没掉血时她照样自己挣脱，只是不出声。
· **可对账**：说出口时写一行「窒息」日志（`… 卡在方块里且已真吃到 5.0 点窒息伤害 → 播报`）；几何命中但没到门槛时也写一行（限频 30 秒/女仆）`… 头部几何命中实心方块，但累计窒息伤害 0.0 点（门槛 >4.0）→ 不播报，只挪位置顶出`。以后想确认"她为什么又说 / 为什么这次没说"，看 `logs/promaid.log` 的「窒息」分类即可。

· **手册同步（两树）**

· 「战斗与自保」章逃生手段 ③ 那条补上 v1.2.4 口径（只有真吃到超过 4 点才喊；几何挤进方块不掉血就不吭声；挪位置/顶出照旧；运行日志可查）。
· 「这是什么」章 v1.2.4 速览补一条。

· **验证**

· 两树 `javac` 0 错误；`_vt647.py` 跨树校验 **FAILS=0**（9 项 × 2 树）：台账、门槛常量、播报被门槛门住、只收 `in_wall`、msgId 归一化比较、两行日志、累计窗口 5 秒、播报门槛之后仍照常找空气格/顶出。
· 本条与 六百四十六 同批进 **v1.2.4**（两树同改、jar 重打、部署本地三处）。
【实测六百四十六】issue #22：驻守（home）农场女仆「到处乱转」+ 打完怪「驻守变跟随」

· **反馈（issue #22 原话，MingDeee · MC 1.21.1 · v1.2.3 第三次版）**

「1、Home模式下，农场工作选项，女仆会快速地到处乱转，检测耕地作物是否成熟时，不是去看而是走遍每一块耕地。相对的，跟随模式就没有这个问题，平常就是跟着走或者站着，只有作物成熟了才跑过去收割。车万女仆本体对农场工作选项的支持，正常来说就是上面说的跟随模式的状态。所以，我推测应该是Home模式的农场工作这部分，应该是没改好。」

「2、农场工作选项，Home模式下，如果遇到怪物，会变为攻击选项且有概率变成跟随模式，结束后会切换回来工作状态，但是跟随模式仍然保留，没有恢复回Home。测试的时候，遇见第一个怪，在Home模式下打死了，没变跟随。来第二个怪的时候，去打它，就变跟随了，然后就一直跟随。」

· **第一条的根因：我们自己给驻守女仆写的「随机巡逻」在覆盖原版农场的寻路**

· 现场机制（`_rev22` CFR 反编译 TLM 1.5.3 实证）：TLM 的 `MaidFarmMoveTask` 要求 **WALK_TARGET 为空**才会去搜下一块地，搜到就自己 `setWalkTarget` 走过去；`MaidArriveAtBlockTask` 一到站就把 WALK_TARGET / TARGET_POS 两份记忆清掉——「到站 → 清空 → 再搜」之间天然有空档。而**一次搜不到活**（地里没有可收/可种的）时，`MaidCheckRateTask` 会静默等 `maxCheckRate(120) + rand(120)` = **6~12 秒**才再搜一次。
· 我们的 `HomeWorkMovementDriver`（实测三百三十三 写的）恰好在「WALK_TARGET 为空」时每 **5 tick（0.25 秒）**用直连导航（`PathNavigation.moveTo`，**会直接换掉当前路径**）把她送去 home 锚点附近的一个**随机点**——上面那 6~12 秒的空档正好被随机点铺满，看起来就是「在田里快速地到处乱转、像在一格格检查地」。
· 跟随模式没有这份驱动（它只服务 home 模式）——所以这个问题只在 home 出现，与反馈的观察完全一致。
· **为什么当年需要它**：三百三十三 那会儿我们把「需要锄的泥土」塞进了 TLM 的移动扫描（`FarmMoveTillMixin`），农场任务因此搜不到活；实测二百九十八 已按用户要求把农场运作**完全回原版**，原版自己会走去收成熟作物——这份「替她找活」的随机巡逻早就没有存在理由了。

· **第一条的改法：农场移出该驱动，锄地也不再抢路**

· `HomeWorkMovementDriver` 现在只管**宰杀**（`maid_smart:slaughter`）——TLM 那边没有「自己找活」的能力，没有牲畜可杀时它会永久站桩，必须由我们兜着；农场 UID 从判据里删掉（`isFarmOrSlaughter` → `isSlaughter`）。
· 驻守农场女仆的运动回到原版：闲着站着、或按「空闲散步」的节奏走走（`MaidStrollBehavior` 自带「已有移动目标就不打扰」的门禁，**不会抢 TLM 的路径**），作物成熟了才走过去收/种——与反馈里对「正常」的定义一致。
· `FarmTillDriver` 的锄地索敌同样收口：女仆脑内**已有 WALK_TARGET**（任务正带她去收/种某一格）时不再直连导航插目标（每 0.5 秒硬插一次同样会互相覆盖路径），只在她站定/没有目标的间隙锄——锄地频次略降，轨迹干净。
· 顺带给该驱动加一行限频（30 秒/女仆）日志（分类「驻守」）：`home 工作移动：宰杀任务当前无牲畜目标 → 去 home 附近闲逛点 …`——「她什么时候是被我们带着走的」从此可对账。

· **第二条的根因：战后还原把「战斗前模式快照」删在了读取之前**

· 参战时 `AutoCombatTargeting` 会把战斗前的 home 模式与作息写进 persistentData（`COMBAT_PREV_HOME_TAG` / `COMBAT_PREV_SCHEDULE_TAG`），战后由 `AutoCombatPools.restorePrevMode` 还原。
· 但五处还原调用点全都是**先 `clearMarkers`（它会把这两份快照一起删掉）、再 `restorePrevMode`**——`CompoundTag.getBoolean` 对不存在的键返回 **false**，于是「还原」实际执行的是 `setHomeModeEnable(false)`：**每次战斗结束都把驻守写成跟随**。排班开着的女仆不走这条（作息与模式由日程表管），所以现象是「有概率」。
· 这条与 实测三十九 修的是同一个坑的另一半：那次修的是「清标记」的顺序，这次是「清快照」的顺序。

· **第二条的改法：先还原后清标记 + 没快照就不动她**

· 五处调用点（`AutoCombatPools.clearMarkersForExternal`、`AutoCombatSwitch` ×3、`AutoCombatTargeting` ×1）统一改成 **`restorePrevMode` → `clearMarkers`**——快照属于 `clearMarkers` 的清理对象，顺序反了就是「删在读取之前」。
· `restorePrevMode` 加门：**没有快照就什么都不做**。旧版无条件 `setHomeModeEnable(getBoolean(快照))`，对老版本升级上来的残留标记、以及没走过参战快照的清理路径，等于「没记录也照样把 home 关掉」——把玩家的驻守设置吃掉了。
· 还原成功时落一行日志：`战斗前模式已还原：驻守（home）=开/跟随`（分类「战斗」）——home 到底回没回来，latest.log 与 `logs/promaid.log` 里都能直接对账。

· **手册同步（两树）**

· 「战斗与自保」章还原那一段补一句：还原时连「驻守 / 跟随」与她当时的作息一起还原；**没记录过快照就完全不动她的设置**；还原后日志里那一行可对账。
· 「农场与杂项功能」章：「原生任务流畅化」补一句「农场这条链路的运动完全交回原版」，并新增两条——「驻守（home）模式的农场（v1.2.4）：女仆不再到处乱转」（含旧版为什么会乱转的一句话根因）与「锄地不抢路（v1.2.4）」。
· 「这是什么」章的 v1.2.4 速览补上本批两条修复，与本条 changelog 同口径。

· **验证**

· 两树 `javac` 0 错误；跨树校验 `_vt646.py` **FAILS=0**（12 项 × 2 树）：五处「先还原后清标记」配对、`contains` 门、无残留的无条件读快照写法、农场 UID 已出驱动、`isSlaughter` 判据、锄地导航已让路、还原日志一行。
· 本条与 六百四十五 同批进 **v1.2.4**（两树同改、jar 重打、部署本地三处）。

· **边界（先写清楚，免得当成「没生效」）**

· 驻守农场女仆现在**闲下来就是站着**（或按「空闲散步」走走）——这正是反馈里要的「跟随模式那种正常状态」；若希望她在没活时多走动，参数在「移动与行为 → 空闲散步」那一节的间隔 / 半径 / 速度。
· 锄地让路只影响「她正走去收/种」的那一小段；站定、没有目标时照旧锄，锄地诊断行（`till diag` / `till ok`）一字未动。
· 第二条的修复对**排班中的女仆**不生效（也不该生效）：她们的作息与模式本就由日程表管理，那两处快照还原一直是有意让位的。
## 实测六百四十五【压缩盒「附魔物品一律禁入」：开关与自定义清单都删掉（用户要求写死口径）+ 手册同步 + 版本号升 v1.2.4 并发版】

### ① 需求（用户原话）

「Bug修好了，现在把压缩盒禁止加入附魔物品的开关和选项关掉。我们始终不允许附魔物品装进压缩盒。随后是自定义哪个不允许装进去这个功能给删掉了。然后手册内容描述进行修改。随后提交github v1.2.4，对比changelog写release声明」

### ② 改法：判据写死两条，配置项连同面板行一起删除

`CompressionBoxFilter` 原来有三个判据（压缩盒本身 / 带附魔的物品 / **配置里的自定义禁入清单**），后两条各自挂着一个配置项。这一版收敛成**写死的两条**：

| 原判据 | 现在的处理 |
| --- | --- |
| 压缩盒本身（盒子不装盒子） | 原样保留（六百一十七 用户报过的那个 bug：能把自己装进去 = 整个盒子连里面的东西一起消失） |
| 带附魔的物品（附魔书 / 附魔武器 / 附魔工具与盔甲） | **保留并写死**——不再有开关 |
| 配置里的自定义禁入清单（`compressionBox.refuseList`） | **整条删除**（判据、配置项、面板行一起去掉） |

删掉的东西（两树各一份）：`CompressionBoxFilter` 的 `MSG_LIST` / `inRefuseList` / `refuseSet` / `idOf` / `blockEnchanted`；配置字段 `COMPRESSION_BOX_REFUSE_ENCHANTED` 与 `COMPRESSION_BOX_REFUSE_LIST`（连同 `[compressionBox]` 里两处 `.define(...)`）；配置面板上的「禁入带附魔的物品」开关行与「禁入清单」字符串行。**语言文件不用改**——那两个配置项的说明一直是写在代码里的 `comment(...)`，`zh_cn.json` / `en_us.json` 里从来没有对应的键。

**为什么不再给开关**（用户那句「我们始终不允许」的理由，也是我们认同的口径）：把开关关掉之后，当初写下的两条理由**一个字都没有变**——①盒子只有 5 格，而附魔物品不可堆叠（不同附魔组合互相不是同一件东西，一格只能放一种组合），很快就满，它的定位是「大批材料的压缩仓」而不是装备库；②六十百一十八 之前用户报的「附魔类物品存进去会消失」正出在这一类上（盒子的自定义数量与原版 {@code getMaxStackSize()=1} 对不上，多出来的那份被当返回值丢掉，而 TLM 那几十处调用点大多不看返回值）。**关掉开关 = 把这些坑重新打开**——那不是可选项，是坑。而自定义清单要求玩家手写**完整注册名**（`minecraft:gunpowder` 这种），效果又只是「让女仆看不见某样东西」；玩家真想挡的东西（附魔物）本来就写死了，留一张要手写注册名的表只会变成「填了没生效」的咨询来源。

### ③ 面板不留空洞：原位置改成一条只读说明

配置面板「系统与杂项 → 压缩盒」原来四行（女仆背包延伸 / 每格上限 / 禁入带附魔的物品 / 禁入清单），现在去掉两个可点的，原位补一条**只读**的「禁入规则」说明行：红字写明「压缩盒本身 与 带附魔的物品 一律不能放入」，说明里交代为什么不给开关、以及老配置里那两行不再被读取。老玩家打开面板看到的不是「功能不见了」，而是「这一条改成了规则」。

### ④ 老存档 / 老配置怎么办

老存档的 `config/promaid-common.toml` 里可能还留着 `refuseEnchanted = true` 与 `refuseList = []` 两行：**它们不再被读取**（判据已写死，配置不参与），**也不影响启动**，不用手动去删。压缩盒这一节现在只剩「女仆背包延伸」与「每格上限」两条可调。

### ⑤ 手册同步（两树）

- 「压缩盒」章的禁入那一条改成「六百二十；六百四十五 起**口径写死**」，并交代为什么不给开关、自定义清单为什么删掉；
- 「总开关与上限」那一段改成「**可调的只有两条**」+ 那条只读说明行的位置；
- 自检那一行的措辞：`禁入清单`（四种拒绝 + 配置清单加了立刻生效）→ `禁入判据`（两类拒绝 + 石头放行 + 那两个配置项确实已经删掉）；
- 「不可堆叠的东西」那条例子里原来举的第一个例子是附魔书——它现在根本进不去盒子了，例子换成药水 / 工具 / 盔甲 / 马鞍；
- 「这是什么」章把 v1.2.3 那一行的「（本版）」去掉，新增 **v1.2.4 起新增功能速览（本版）**（激流三叉戟当飞行推进剂、精妙背包 / 旅行者背包适配、骑乘或坐下不搭方块、烧制酿造不再冻住、远程空袭开火修复、右键蛋糕崩服修复、压缩盒禁入写死）。

### ⑥ 游戏内自检同步（`/maid_smart box check [女仆]`）

- 段落名「禁入清单」→「**禁入判据**」；
- **「不可堆叠」那两条回归的探针从附魔书换成一把没附魔的钻石剑**：六百二十 ~ 六百四十四 之间它们靠一个临时开关把附魔书放进盒子跑，开关删掉之后附魔书再也进不去盒子，探针必须换——验的事情一件没变（盒子里一格存了 2 个不可堆叠的东西时，**交给原版的堆必须合法**、过一圈盒子 NBT 原样还在）；
- **④ 从「配置清单当场加一条 → 立刻生效」换成「口径写死」**：用**反射**去取那两个配置字段，取不到才算过——用户要的就是「这两个选项不存在」，那就直接验它们不存在；
- 其余（① 两种拒绝 + 石头放行对照、② 数据层 `mergeInto` / `merge` 原样退回、③ 界面那条路 Shift+左键被拒、⑤ 女仆那一侧 `isItemValid` 也判 false）原样保留。

### ⑦ 版本号升 v1.2.4 并发版

- **`META-INF/mods.toml` ×3**（两树的 `mods.toml` + neo 树的 `neoforge.mods.toml`——真正被打包的是 forge 树的 `mods.toml` 与 neo 树的 `neoforge.mods.toml`，neo 树那份 `mods.toml` 不参与打包，历史上一直跟着同步版本号、只为避免日后混淆）版本 `1.2.3` → `1.2.4`，两个打包用的那份在**模组描述**里补一句本版摘要（游戏内模组列表可见）；
- 构建 / 部署 / 校验 / 测试 / 发布脚本里的 jar 名与版本串一并改（共 **34 个脚本文件**）：`promaid-1.2.4.jar`（Forge 1.20.1）与 `promaid-1.2.4-neoforge-1.21.1.jar`（NeoForge 1.21.1）；
- 两个 jar 重打并逐项校验，部署本地三处（1.20.1 主实例 / 1.21.1 实例 / 1.20.1 测试服）；
- GitHub 发**两个 release**：tag `v1.2.4`（1.20.1 Forge）与 `v1.2.4-neoforge-1.21.1`（1.21.1 NeoForge），各带对应附件；说明按「v1.2.3 → v1.2.4 差异」改写（不再是重传，而是新版本）。

### ⑧ 这一版都更新了什么（v1.2.3 → v1.2.4：实测六百三十三 ~ 六百四十五）

- **激流三叉戟当飞行推进剂**（六百三十三 / 六百三十四 / 六百三十八 / 六百四十 / 六百四十一 / 六百四十二）：起飞、空袭的盘旋抬升与俯冲下扎、飞行跟随四条链路都能烧；力度照「玩家在水里的那一记」并按附魔等级放大（形状 / 水阻力 / 收手阈值 / 旋转时长照原版），六百四十二 整体 ×1.3；取材范围最终是**主手 → 副手 → 背包**三处都找（六百三十八 一度收成「只认背包」，六百四十 按玩家要求改回——耐久就扣在真正用到的那一件上）；飞行跟随新增「消耗三叉戟耐久」开关（`flightFollow.trident`）；
- **精妙背包 / 旅行者背包适配**（六百三十六 / 六百三十九）：她自己的背包满了，东西就装进她身上那件额外容器；反过来取料时也先去那里面找（先判自己背包，再判额外容器）；
- **远程空袭「拿着枪一枪不放」**（六百四十三）：① 空中开火曾被 TLM 的「枪型交战距离带」卡死（那是它**挑目标**用的档位，不是开火判据，TLM 自家开火只看视线 + seeTime）；② 地面远战分支压根不调开火那一句。两条都修，并新增「远程开火」诊断行（节流 2 秒）——为什么不开火从此一眼可查；
- **右键蛋糕崩服（1.21.1 专属）**（六百四十四）：neo 树移植时把「蛋糕可食用」注错了方法（注入 `Item.use` 却把返回值写成 `Boolean`，真实返回是 `InteractionResultHolder`），改用 1.21.1 的数据组件在加载期给 `minecraft:cake` 挂 `FOOD`；
- **其它**：骑乘 / 坐下中禁止搭方块（六百三十五）、烧制 / 酿造没有目标时不再「把自己冻在原地」（六百三十七）、压缩盒禁入口径写死（本条）、`_mixaudit.py` 注入点全量体检（六百四十四 顺带，两树 144 处 `@Inject` 只有那两条是错的，已随修复清掉）。

### ⑨ 验证

两树 `javac` **0 错误**；两个 jar 重新打包并逐项校验（必需类齐全 / 两份语言 JSON 合法 / mixins 清单与包里类一一对应 / 手册 changelog 已是最新）；`deploy_all.py` 部署本地三处 **match=True**；跨树脚本核对（配置字段、面板行、语言键、手册文本、自检文案、jar 名、行尾与 BOM）**FAILS=0**。**未跑游戏内实测**——按约定测试留给用户。

### ⑩ 边界与遗留

- 老配置里残留的 `refuseEnchanted` / `refuseList` 两行**不再被读取**（不删也不影响启动）；
- 「不可堆叠探针」换成钻石剑只是**自检里的事**：玩家侧可见行为与 v1.2.3 相同（带附魔的一律进不去）；
- 从此**没有任何入口**能把附魔物品放进压缩盒。以后若真有人需要「把附魔书塞进盒子」（比如高阶附魔书仓），那是新需求、要单独讨论口径，**不是把开关加回来**（加回来就等于把那两个坑重新打开）。
## 实测六百四十四【右键蛋糕崩服修复（1.21.1 / NeoForge）：neo 树移植时把「蛋糕可食用」注错了方法——注入 Item.use 却把返回值写成 Boolean】

### ① 反馈（粉丝原话）

「1.2.0 版本右键蛋糕试一下能不能吃游戏突然炸了……重新进世界找女仆过来右键可以喂她吃，看 git 发现更新到 1.2.3 版本了，更新一下再进存档，右键蛋糕又把我踢出世界了……不过目前给女仆投喂蛋糕时女仆会在前方随机快速小范围移动，一旦点歪就会被踢」

粉丝附的报错核心：`java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class net.minecraft.world.InteractionResultHolder`，栈顶明确指向 `promaid` 的 `MaidCakeEdibleMixin`，环境 `promaid v1.2.0-neoforge-1.21.1`（1.2.3 一样——这条从移植那天起就没动过）。

### ② 病根：方法注错，handler 泛型跟着错

neo 树那条 mixin 长这样：

```java
@Mixin(Item.class)
public abstract class MaidCakeEdibleMixin {
    @Inject(method = "use", at = @At("HEAD"), cancellable = true)
    private void maidsmart$cakeIsEdible(CallbackInfoReturnable<Boolean> cir) {
        if (isCake((Item) (Object) this)) {
            cir.setReturnValue(true);              // ← 塞进去的是布尔值
        }
    }

    @Inject(method = "finishUsingItem", at = @At("HEAD"), cancellable = true)
    private void maidsmart$cakeFoodProperties(CallbackInfoReturnable<FoodProperties> cir) { … }
}
```

javap 实证（1.21.1）：`Item.use` 的真实返回值是 `InteractionResultHolder<ItemStack>`、`Item.finishUsingItem` 是 `ItemStack`——两处 handler 的泛型（`Boolean` / `FoodProperties`）**都跟目标方法对不上**。注入后的方法拿到的是 `(InteractionResultHolder) cir.getReturnValue()`，而里面装的是 `Boolean` → 强转当场抛 `ClassCastException`，服务端直接踢人。

**触发路径（字节码实证）**：`ServerPlayerGameMode.useItem` → `ItemStack.use` → `Item.use`——也就是**右键那只蛋糕、而这一下没有落在任何可交互方块上（对着空气/远处点一下）**时必炸。这正是粉丝的两种情形：「试试能不能吃」（对空右键）与「喂女仆时点歪了」（没点到女仆身上）。对着方块右键不走这条路（`useItemOn` 只依次试 `BlockState.useItemOn` / `useWithoutItem` / `ItemStack.useOn`，一次都不碰 `Item.use`），所以现象看起来像「只有某些点击会炸」。

**为什么 1.20.1 树一直没事**：那边注入的是 `Item.m_41472_` / `m_41473_`，javap 实证返回值就是 `boolean` / `FoodProperties`，与 handler 泛型**逐一对得上**。同一条崩服只在 neo 树出现，根因就在这儿。

### ③ 修法：1.21.1 不该注入，改用数据组件

javap 实证 1.21.1 的食物**已经全面数据组件化**：`Item` / `ItemStack` 上根本没有 isEdible / getFoodProperties 了，一切改读 `DataComponents.FOOD`——

- `Item.use`：读 FOOD，有就 `startUsingItem` + `consume`（吃不下给 `fail`）；
- `Item.getUseAnimation` = `has(FOOD) ? EAT : NONE`；
- `Item.getUseDuration` = `food.eatDurationTicks()`；
- TLM 的工作餐判定 `DefaultMaidWorkMeal.isWorkMeal` 也是在调 `ItemStack.getFoodProperties(LivingEntity)`。

所以 neo 树改成：**模组加载时用 NeoForge 的 `ModifyDefaultComponentsEvent` 给 `minecraft:cake` 挂上 FOOD 组件**（`MaidCakeEatHandler.CAKE_FOOD`，营养 14 / 饱和 0.6——与 1.20.1 同口径；`eatSeconds` 留默认 1.6 秒）——上面那一串派生行为全部自动成立，**一条 mixin 都不需要**。

- 删掉 `promaid_src_neo/com/maidsmart/mixin/MaidCakeEdibleMixin.java`，并从 `mixins.promaid.json` 摘掉该条目（打包脚本会校验「列出的 mixin 必须在 jar 里有对应 class」，两处必须一起改）；
- 监听器挂 **mod 总线**（`container.getEventBus()`）——`ModifyDefaultComponentsEvent` 是 `IModBusEvent`；
- 挂载时印一行 `[promaid/蛋糕可食用] 已给 minecraft:cake 挂上 FOOD 组件（营养 14 / 饱和 0.6 / 1.6 秒）`，当场可确认；
- `misc.cakeEdible` 仍然管这一段（关掉 = 组件不挂、蛋糕恢复原版不可食）。差别是它现在**加载期一次性**生效：关掉后投喂立刻停，「女仆把蛋糕当食物」要重启游戏才回到原版——配置注释与面板提示都写明了。

### ④ 顺带：两树全部 mixin 注入点做了一遍「返回类型体检」

这种「移植时方法名写错」的 bug 靠人眼看不住，于是写了个静态核查器（仓库根的本地脚本 `_mixaudit.py`，随 `_*` 一并忽略、不进发布包）：解析每个 `@Mixin` / `@Inject(method=…)`，用 javap 取目标方法的真实返回类型，与 handler 的 `CallbackInfo` / `CallbackInfoReturnable<T>` 逐条比（含自动装箱、嵌套 `@Mixin` 的归属判定）。两树合计 144 个注入组合跑下来，**真正的类型不符只有上面那两条**（都在已删的蛋糕 mixin 里）；其余报警逐条核对后都是核查器自身的解析限制（加载器新增的方法如 `ElytraItem.elytraFlightTick`、`<init>` 形式的注入、渲染器构造器），不是代码问题。

### ⑤ 1.20.1 树：代码不动，只更正一句错注释

forge 树那条 mixin 本身是对的，留着。但它的类注释写着「玩家右键蛋糕仍是放方块（BlockItem.use 未改），不受影响」——javap 实证 `BlockItem` 只覆写 `m_6225_`（useOn）、**没有**覆写 `m_7203_`（use）：放置路径确实没变（方块放置先于物品使用），但「右键空气」会走到 `Item.m_7203_`，它读的正是 `getFoodProperties` → 现在会开始吃手上那块蛋糕。这句已在注释里更正。

### ⑥ 边界与遗留

- 本条只改「蛋糕怎么变成可食用物品」，**营养 / 回血量 / 好感加成一律没动**（整块 14 点生命、+10 好感）。
- 粉丝提到的「投喂时女仆在前方随机快速小范围移动」：与本条无关，也不是本条改动引起的（投喂那条路只做「消耗 1 个蛋糕 + 音效粒子 + 好感」，一行位移都不写）。崩服修好之后，点歪最多是没喂上；她为什么在旁边小幅抖动，还需要一份现场记录才能定性——已请对方下次复现时抓 `latest.log` 片段。
- 本条尚未发版。

## 实测六百四十三【远程空袭「拿着枪一枪不放」的两条病根：① 空中开火被 TLM 的"枪型距离档"卡死 ② 地面分支压根不调开火；顺带补上「远程开火」诊断行】

### ① 需求（用户原话）

「远程空袭当女仆只持有激流三叉戟用于飞行时，虽然可以起飞，也不会报缺，但是起飞之后女仆根本就不会攻击。只会在天上飞。可以看一下刚刚运行的游戏，虽然拿着枪，但是根本就不射。」

### ② 实测日志实证（2026-09-23 17:15，1.21.1 实例 / 监守者）

- 她**在飞**、模式是激活的（`isModeActive` 已过），盘旋相位在跑：每 2 秒一条「空袭索敌 … 我方有视线=1 我方选中=minecraft:warden@11.4~20.3」，每 5 秒一条「掉高挥激流三叉戟」——**后一条就写在调用开火的同一个方法（`tickRangedAir`）里**，说明开火那一句每 tick 都在被调、只是被闸门挡回去了；
- 主手 = `tacz:modern_kinetic_gun`；背包里有 `tacz:ammo_box`（弹药箱）、`minecraft:trident`（激流，飞行燃料）、鞘翅；
- **26 秒里一枪未放**（日志里没有任何开火痕迹）。
- 另一处：17:15:17 `ranged kite: main=tacz:modern_kinetic_gun dist=2.6 ideal=28.8`——她**站在地上**、距敌 2.6 格、举着枪，同样一枪不放，随后被监守者打死。
- 同场更早那条「空战装备不齐，没有弹药」的气泡，是她**还没拿到弹药箱**时的判定，与本条这两个病根不是一回事。

### ③ 病根一（空中）：把 TLM 的"交战距离带"当成了开火门禁

`tickGunFire` 旧版的开火条件是 `cd <= 0 && canSeeGunTarget(maid, target)`，而 `canSeeGunTarget` 调的是 TLM 的 `GunCommonUtil.canSee`。javap 实证（TLM 1.5.3-neoforge+mc1.21.1）它在 TACZ 侧落到 `TacInnerCompat.canSee`：

```
IGun gun = IGun.getIGunOrNull(stack);
if (gun == null) return BehaviorUtils.canSee(maid, target);
return TimelessAPI.getCommonGunIndex(gun.getGunId(stack)).map(index -> {
    String type = index.getType();
    if ("sniper".equals(type))                    return targetConditionsTest(maid, target, MAID_GUN_LONG_DISTANCE);
    if ("shotgun"|"pistol"|"smg")                 return targetConditionsTest(maid, target, MAID_GUN_NEAR_DISTANCE);
    return targetConditionsTest(maid, target, MAID_GUN_MEDIUM_DISTANCE);
}).orElse(BehaviorUtils.canSee(maid, target));
```

而 `IRangedAttackTask.targetConditionsTest(maid, target, cfg)` 只是"把 `TargetingConditions.range(cfg.get())` 设成那一档、再做一次 `test`"（同一份 javap）——**那是 TLM 地面枪械任务用来"挑/留目标"的交战距离带**（TLM 自己的配置注释：near = "Suitable for pistols and shotguns"），**不是**"这一发能不能打出去"的合法性判据。TLM 自家 `GunShootTargetTask.tick` 里，开火前用的**只有** `maid.canSee(target)`（普通射线视线）＋ seeTime 迟滞，那道档位判据一次都没出现在开火条件里（而本方法的注释一直写着"照搬 TLM"，实际却多了一道门）。

**于是**：空袭是"绕目标盘旋（半径 `airRaid.orbitRadius` 默认 10 格）**且停在她上方**"（高度带 `airRaid.rangedHoldHeight`），3D 距离天生落在那些带外（手枪/霰弹/冲锋枪那档更近）→ `canSee` 每 tick 都是 false → `performGunAttack` **一次都没被调到** → 天上绕圈、一枪不放。

**现在**：与 TLM 自家逐条对齐——视线由上层 `fireRanged` 统一判（`SelfPreservationBehavior.hasSight`，弓弩与枪械同一个口径），`tickGunFire` 只看冷却。射程仍由 `fireRanged` 把着（枪械 = TLM 枪械中距离配置，默认 16 格，弓弩 = 24 格）。

### ④ 病根二（地面）：远战的地面分支一路 return，从不调开火

`MaidFlightCombatBehavior.tick` 的地面远战分支只做两件事——"够近就起跳/甩位移法术起飞"，其余情况 `setGliding(false)` 后直接 `return`；而**开火那一句只写在 `tickRangedAir`（空中盘旋相位）里**。于是"落地 → 等重新起飞"的那几秒她举着枪一直不放（上面 17:15:17 那条就是它，最后被打死）。

**现在**：地面分支在 `return` 之前补一次同样的 `fireRanged`（弓弩走本任务的 `performRangedAttack`、枪械走 TLM 枪械通道），节奏仍由内部的两张冷却表（`RANGED_NEXT_SHOT` / `RANGED_GUN_CD`）管，不会连发。

### ⑤ 新增诊断行：`[promaid/远程开火]`（节流 2 秒）

以前"她为什么不射"在日志里**完全查不到**（只有一条"没有弹药"的气泡，真正的闸门一行都不写，现场只能靠猜）。现在每一档都会印出来：

```
[promaid/远程开火] 大正女仆酒狐 主手=tacz:modern_kinetic_gun 距敌 22.40 格 > 射程 16.00 格 → 不出手
[promaid/远程开火] 大正女仆酒狐 主手=tacz:modern_kinetic_gun 距敌 13.70 格 但视线被挡 → 不出手
[promaid/远程开火] 大正女仆酒狐 主手=minecraft:bow 距敌 9.20 格 → 开火
[promaid/远程开火] 大正女仆酒狐 主手=tacz:modern_kinetic_gun 距敌 12.80 格 开镜=false TLM档=false 推进=激流推进中 → 枪械开火返回 2 tick（原始 2；原始<20 = 打出去了）
[promaid/远程开火] 大正女仆酒狐 主手=tacz:modern_kinetic_gun 距敌 15.40 格 → 枪械冷却剩 6 tick
```

返回值的读法（TLM `TacInnerCompat.performGunAttack` 字节码逐支对照）：

| 返回值 | 含义 |
| --- | --- |
| 2 / 10~14 | 这一发**真的打出去了**（连发 / 半自动·点射） |
| >= 20 | 还在开镜 / 抽枪 / 拉栓 / 换弹，**没打出去** |
| 100 | 枪械不可用（`IGun` 或枪包数据缺失），或本模组捕获到异常 |

### ⑥ 同一批内的笔误与修正：第一版把"冷却"写死了（任何飞行道具都开不了火）

第一版（③④⑤ 就是它）在"冷却还没走完"那一支里**直接 return，而没有把递减后的冷却写回表**——于是冷却永远停在 `performGunAttack` 返回的那一刻。玩家实测日志（2026-09-23 17:30，装的就是那一版）：

```
17:30:47.909 [promaid/远程开火] … 距敌 17.66 格 → 枪械开火返回 7 tick
17:31:03.692 [promaid/远程开火] … → 枪械冷却剩 6 tick
17:31:05.692 [promaid/远程开火] … → 枪械冷却剩 6 tick
…（此后每 2 秒一条，全是"冷却剩 6"）
```

第一发之后冷却卡死在 6~7，**换哪种飞行道具都不再开火**（用户原话："现在新版本更糟糕了……不管采用哪种飞行道具，都是开火都开火不了了"）。这条笔误在**同一批内**修掉：无论走哪一支，最后都把"递减后的冷却"写回（与改动前的写法一致）。

### ⑦ 开火诊断再升级（同一批）

既然要回答"为什么偏偏三叉戟不行"，那一行日志现在把现场量一起印（"冷却还没走完"那档仍 2 秒节流；**每次真正尝试开火都不节流**——它的频率本来就由冷却决定）：

| 字段 | 含义 | 用途 |
| --- | --- | --- |
| `距敌` | 3D 距离（弓弩 / 枪械共用射程门禁） | 判"超出射程" |
| `开镜` | TACZ 的 `synIsAiming`（反射读 `IGunOperator`） | TLM 的 `performGunAttack` 有一档"距离 > 她的活动半径且没开镜 → **先开镜**、返回 `aimTime×20+2`"——那个返回值看着像小冷却、**其实没打出去**；有这个字段才能把两者分开 |
| `TLM档` | 旧版那道"枪型距离带"判据（`GunCommonUtil.canSee`） | **现在只记录、不再当门禁**：留着是为了回答"旧版那道门当时开没开"，也就是 §③ 的结论能不能被现场证实 |
| `推进` | 此刻挂着哪一路推进剂（激流推进剂窗口在不在） | 直接回答"三叉戟 vs 烟花"：同一行里就能看出"不射"是不是只发生在某一路推进剂上 |
| `原始 N` | 属性缩放**之前**的返回值 | 判"这一发出去没有"要看原始值：`2 / 10~14` = 打出去了；`≥20` = 开镜 / 抽枪 / 拉栓 / 换弹；`100` = 枪械不可用 |

**另外查清的一件事**：激流推进剂点着时会置活体标志位 4（`isAutoSpinAttack`，`MaidRiptideBoost.ignite`）。javap 扫过 TLM 1.5.3 与 TACZ 两个 jar，**没有任何服务端代码读它**（只有客户端渲染：`YSMBinding` / `GeoReplacedEntityRenderer`），所以它不是"三叉戟不射"的原因——这一点排除了。

### ⑧ 验证与边界

- 两树 `javac` EXITCODE=0；三份 changelog（root `CHANGELOG.md` = BOM+LF，两树 `assets/promaid/guide/changelog.txt` = BOM+CRLF）逐字节一致；jar 内嵌指南与源文件一致。
- **本条未经实机验证**（按约定，测试归玩家；第一版的笔误正是玩家实测抓出来的，已在同一批内修掉）。要看的还是一行：`[promaid/远程开火]` 是不是**反复**出现「→ 枪械开火返回 2 tick（原始 2）」（而不是只有第一发），以及她**天上与地上**都在打枪。
- **边界一**：本条只把"她该不该开火"判对；TACZ 自己的抽枪 / 开镜 / 拉栓 / 换弹节奏**一字未动**（那是 TLM 的 `GunCommonUtil.performGunAttack` 在管）。若日志长期是「≥20」或「100」，那是枪械侧的状态问题（弹药型号与枪对不上 / 枪包数据缺失），下一轮的日志会直接说明是哪一支。
- **边界二**：空袭里枪械的"理想交战距离"仍是 TLM 的枪械中距离配置（默认 16 格）；想让拿手枪/霰弹枪的她贴得更近打，那是**走位**的活（下一步可以按枪型给盘旋半径分档），本条没有动盘旋几何。
- **本条尚未发版**。

## 实测六百四十二【激流三叉戟的推进剂力度整体 ×1.3：六百四十一 那套形状照搬"玩家在水里那一记"，实测偏慢（激流三甚至比俯冲自己飞还慢、缺乏实战价值），于是把**力度**乘 1.3——形状 / 水阻力 / 收手阈值 / 旋转时长一字不动】

### ① 需求（用户原话）

「现在使用激流三叉戟速度矢量又太慢了，激流三甚至还没有俯冲飞行自己飞得快，导致其缺乏实战价值。感觉要在现在的基础上所有数值乘以1.3倍。这个不难，只需要改一改数值就行了。」

### ② 只乘"力度"这一个量（其余常量一个不动）

六百四十一 已经把**形状**（烟花式递推：每 tick 把整个速度矢量往"视线 × 当前力度"上拉）与**阻力**（水里 ×0.80/tick）定下来了，实测下来唯一不够的是**力度**。所以本条只动一个数——`MaidRiptideBoost.PLAY_SCALE = 1.3`：

```
点火：力度 P0 = 原版 3.0×(1+等级)/4 × 1.3 × combat.riptideFlightScale   // I 1.95 / II 2.93 / III 3.90 格/tick
之后：每 tick  P = P0 × 0.80^k、v ← v×0.5 + 视线 × (P/2)               // 与六百四十一 逐字相同
收手：P ≤ 0.35（滑翔常态）或满 40 tick
```

**"所有数值"在这里只能是力度**——下面这几个是"原版 / 滑翔的既有口径"，跟着乘上去会失真，逐条说明白：

| 常量 | 值 | 为什么不乘 1.3 |
| --- | --- | --- |
| 递推系数 `GAIN` | 0.5 | 原版挂载烟花那一句的形状（`v ← v×0.5 + 视线×0.85`），改了就不是"烟花路线" |
| 水阻力 `WATER_DRAG` | 0.80 | 玩家在水里那一记的衰减；×1.3 = 1.04 直接变成"越飞越快" |
| 收手阈值 `DECAY_UNTIL` | 0.35 | 那是**滑翔常态巡航速度**（飞行跟随补推判据用的是同一个数） |
| 窗口上限 `MAX_THRUST_TICKS` | 40 | 只是兜底，正常档根本用不到 |
| 旋转时长 `AIR_SPIN_TICKS` | 20 | 原版 `startAutoSpinAttack(20)`，动作口径 |

### ③ 数值对照（照递推式逐 tick 累加，与日志里印的是同一个算法）

| 等级 | 力度（格/tick） | 峰值速度（格/tick） | 窗口 | 行程 |
| --- | --- | --- | --- | --- |
| 激流 I | 1.50 → **1.95** | 0.98 → **1.27** | 7 → 8 tick | 5.4 → **7.6 格** |
| 激流 II | 2.25 → **2.93** | 1.46 → **1.90** | 9 → 10 tick | 9.2 → **12.5 格** |
| 激流 III | 3.00 → **3.90** | 1.95 → **2.54** | 10 → 11 tick | 12.9 → **17.3 格** |

标尺：滑翔常态巡航 **0.35 格/tick**、俯冲段冲刺一口 **0.55 格/tick**（羽扇那一路补的那一下）。III 级峰值 2.54 已是它们的数倍——"激流三还没有俯冲自己飞得快"这一条不再成立。

### ④ 配置与面板

- `combat.riptideFlightScale` 的语义从"力度折扣"改成**"力度倍数"**，范围从 `0.1~1.0` 放宽到 **`0.1~2.0`**，默认仍是 1.0。它是**在 1.3 这个基线之上**再乘的一层：
  - 想回到六百四十一 的手感 → 调到 `1 ÷ 1.3 ≈ 0.77`；
  - 还想更猛 → 往 2.0 调（等效 = 玩家那一记的 2.6 倍）。
- 手册面板那一行从「激流推进·力度折扣」改成「**激流推进·力度倍数**」，tooltip 与配置注释同步；游戏内手册「激流三叉戟」那条也改成"再整体 ×1.3 / III 级行程约 17 格"。
- 日志那一行现在是「…照玩家在水里那一记 ×1.30；水里那份阻力 ×0.80/tick → 约 N tick、行程约 D 格…」，N / D 与上表逐项对得上（III 级 11 tick / 17.3 格）。
- 配置注释里涉及行进数值的几处（含「俯冲段冲刺·用激流三叉戟」那条原先写的"力度逐字节照原版"）一并改对。

### ⑤ 没动的（说明白）

- 形状照烟花（每 tick 拉向"视线 × 当前力度"）、**竖直分量也归推进管**、整份可删、耐久 −1、撞到途中敌人照原版结算——六百四十一 的全套一字不动。
- 近战那一记「旋转冲击」（她自己朝目标的突进，`MaidTridentSpinBehavior` 的 DASH）**不受本项影响**，仍是原版矢量、原版时长。

### ⑥ 验证与边界

- 两树 `javac` EXITCODE=0；三份 changelog（root `CHANGELOG.md` = BOM+LF，两树 `assets/promaid/guide/changelog.txt` = BOM+CRLF）逐字节一致；jar 内嵌指南与源文件一致。
- **本条未经实机验证**（按约定，测试归玩家）：上表由原版公式 + 递推式算出，不是估的。
- **边界（要留意的一条）**：力度涨了约 1.35 倍，而这一版的速度是**钉在视线方向上**的，所以起飞相位（抬头 62°/45°）那一口的**竖直分量也跟着变大**。若又出现"起飞特别高"，把 `combat.riptideFlightScale` 调到 0.77 即回到六百四十一 的量级，再按手感微调——这一项就是为这种情形留的旋钮。
- **本条尚未发版**。

## 实测六百四十一【激流三叉戟的推进剂改成"拟真烟花"：启动/行为走烟花那条路、数值照玩家在水里那一记（判附魔等级）；同时修四条：起飞窜高 / 未起飞先炸风爆 / 落地愣一下 / 飞行跟随朝向与判定圈删矢量 / 远程空袭认不认它】

### ① 需求（五条反馈 ＋ 一条设计决定，用户原话）

1.「实测下来，起飞的时候女仆还是会飞的特别高。尤其是在配合到重锤的弹射起跳的时候，还是会轻松飞出100格。」
2.「再配合三叉戟和重锤的时候女仆会在还没起飞的时候开始就打出一次风暴。」
3.「单纯使用激流三叉戟进行近战空袭的时候，女仆会在落地之后愣一下再起飞。这一点对于实战是致命的。」
4.「在飞行跟随状态下三叉戟的朝向似乎并不是主人。并且没有像烟花那样子的一旦主人在判定圈内就把矢量删去。」
5.「远程空袭不认这把武器能够起飞。」
6.（设计决定）「综合考虑，我决定还是将激流三叉戟在起飞/俯冲/飞行突进的链路改为拟真烟花，也就是实际上的启动和行为效果走烟花的路线，数值跟玩家在水中使用三叉戟（还要判定附魔等级）一致。但是动作会变成三叉戟的动作，并且对途中敌人造成对应的伤害。」

### ② ① 的病根：不是"力度多大"，是"竖直分量没人管"

六百四十 的 v2 是**一次性冲量 + 只压水平**：点火 `v ← v×0.5 + 视线×3.0`，之后每 tick 只把 **x/z** 乘 0.92、**y 原样写回**。
而鞘翅滑翔对竖直几乎不设防——反编译实证 `LivingEntity.travel` 滑翔分支的收尾句就是 `setDeltaMovement(v.multiply(0.99, 0.98, 0.99))`（竖直只有 ×0.98/tick），抬头时重力还被 `cos²(pitch)×0.75` 削掉一大半。
起飞相位的朝向偏偏是抬头 **62°（近战）/ 45°（远战）**（`climbPitch`，`MaidFlightCombatBehavior`），于是那一口 3.0 的竖直分量（≈2.65 格/tick）**一路留着**；
再叠上重锤风爆"把她自己掀飞"的那一份（`FriendlyWindGuard` 刻意放行"她推自己"，另见 ④ 的闸门），实测就是"起飞特别高、轻松 100 格"。
**结论：竖直分量必须有东西管。** 管它的东西现成就有——**挂载烟花**（它对骑手的推力每 tick 都带着竖直分量一起收敛）。

### ③ 现在的口径：`MaidRiptideBoost` 重写为"拟真烟花"

```
点火：力度 P0 = 3.0×(1+等级)/4 × combat.riptideFlightScale   // I 1.5 / II 2.25 / III 3.0 格/tick（判附魔等级）
每 tick：P = P0 × 0.80^k                                    // 玩家在水里的阻力：速度每 tick ×0.80
        v ← v×0.5 + 视线 × (P/2)                            // 与挂载烟花同形的递推；不动点 = 视线 × P
收手：P ≤ 0.35（滑翔常态）或满 40 tick
行程：I ≈ 5.4 / II ≈ 9.2 / III ≈ 12.9 格                     // 按递推式逐 tick 累加
                                                            （闭式 力度÷(1−0.80) = 7.5/11/15 是理想上限）
                                                            → 实测六百四十二 起力度整体 ×1.3：7.6 / 12.5 / 17.3 格，以那一条为准
```

- **形状照烟花**：原版挂载烟花对骑手就是 `v ← v×0.5 + 视线×0.85`（不动点 1.7×视线；1.20.1 `FireworkRocketEntity.m_8119_` / 1.21.1 `tick` 字节码实证）。本类把那个 0.85 换成 **力度/2**（不动点 = 力度）——于是"每 tick 拉着走、随她视线转、整份可删、烧掉一件"这四件烟花的行为全都继承下来。
- **竖直也归推进管**（① 的解药）：这一句把 **y 一起**拉向"视线 × 当前力度"，视线一平/一低她就不再往上窜；旧版那个"竖直原样写回"的漏洞没有了。
- **数值取"水里那一记"**：用户口径逐字是"玩家在**水中**使用三叉戟"。上一版照"雨里"（空气阻力 0.91）算出来是 17/25/33 格，实测仍嫌远；水里那一记的闭式是 `3.0 ÷ (1 − 0.80) = 15 格`（III）——这才是玩家真正熟悉的那一记（女仆走烟花递推、III 级实到约 12.9 格，同一个量级：差的 2 格是递推式 0.5 收敛那一拍滞后）。
- **动作仍是三叉戟那一记**：照原版 `startAutoSpinAttack(20)` 摆旋转（标志位 4 + 20 tick 旋转计数，画面由 Gecko 去重 mixin 负责）、按等级放 `TRIDENT_RIPTIDE_1/2/3` 音效、耐久 −1（与投掷共用那一条）。
- **途中敌人照原版结算伤害**：本类**不登记** `MaidTridentSpinBehavior` 的 DASH，所以撞到实体时由原版 `checkAutoSpinAttack` 口径走 `MaidSpinAttackTouchMixin` → `onSpinTouch` 结算（攻击力 + 附魔加成 × 命中 → 击退 → 火焰附加 → 荆棘），与玩家"激流冲过去扫到怪"一致；命中后原版自己清标志位（= 旋转收招），但**推力不受影响**。
- 日志换成「…照玩家在水里那一记；水里那份阻力 ×0.80/tick → 约 N tick、行程约 D 格…」；行程按递推式**逐 tick 累加**算，不用闭式——闭式（力度 ÷ (1 − 0.80) = 7.5/11/15）比实际大 15% 左右（III 级 15 → 实到 12.9），日志宁可说实际值。

### ④ ② 还没起飞就先炸一次"风暴"：地面重锤猛击抢跑（1.21.1）

- **根因**：`MaidMaceSmashBehavior` 是**与空袭任务无关的全局 core 行为**（注册优先级 235，远高于空袭那一条 4），它唯一那道空中门只有 `isFallFlying()`——她站在地上、目标进 3 格、主手重锤 + 有风弹时，它会在**同一 tick 抢在空袭起飞之前**起跳（GUST + GUST_EMITTER_SMALL + WIND_CHARGE_BURST），贴地那一帧 `smashHit` 再甩出一发重锤风爆（她自己的风爆刻意不被风免拦），于是"还没起飞就先炸一次风暴"。
- **现在**：`checkExtraStartConditions` 里加一道闸门——**空袭任务 + 三件套齐**（`MaidFlightKit.isFlightTask && isModeActive`）时本行为不抢跑，那一轮的重锤猛击交给空袭自己的收翅俯冲（`hitOne` 会强制写 `fallDistance`，风爆照炸、伤害照算）。三件套不齐（她本来也飞不了）时一切照旧，猛击仍是她在地面上的战斗手段。

### ⑤ ③ 落地后"愣一下"：地面分支根本没有激流起飞腿

- **根因**：`canTakeOff` 里 六百三十三 加的 `hasRiptide` 在**地面上是空判**——激流那条腿写在 `tryLaunch` 里，而 `tryLaunch` 只有**空中**两个入口（离地后第 0 步、猛击收尾后的再点火）会调；地面分支只认 `canLaunch`（烟花/羽扇）。于是"只带激流三叉戟"的她落地后**再等多久都起不来**，只能等地面近战那一记旋转突进把她弹起来——而那条要吃 20 tick 收招硬直（`SPIN.RECOVER_TICKS`）+ 约 20 tick 地面冷却（`GROUND_READY`），实测就是"愣一下"。
- **现在**：新增 `canJumpToLaunch(maid, gameTime) = 冷却已过 &&（烟花/羽扇 或 激流三叉戟）`，**近战与远程两个地面分支**都用它——起跳这一步与烟花**完全同路**，点火仍由空中那一 tick 的 `tryLaunch` 完成（顺序一字不动：扇子 → 烟花 → 激流）。有烟花/羽扇的存档一字不变（它们的判据本来就更宽的那一支先命中）。
- **冷却也一起判（否则会"原地一蹦一蹦"）**：激流那一口复用烟花的冷却表（`FIREWORK_READY`，写回 `fanCooldown` = 20 tick），而 `tryLaunch` 开头的第一件事就是查这张表——起跳判据若不看冷却，她会在"有烟花/羽扇且正在冷却"的档位里**跳起来却点不着任何东西**，落地再跳、再落地。所以判据收成"冷却已过 且（烟花/羽扇 或 激流三叉戟）"，与 `tryLaunch` 的闸门逐条对齐。

### ⑥ ④ 飞行跟随：视线回到主人 ＋ 推进剂与那枚火箭同构

- **「朝向不是主人」**：④ 腿点火前那一句原本是 `faceToward(aim, true)`，而 `true` 会把俯仰**至少抬到 20° 朝上**（那是"起跳那一下先抬头"用的）——主人比她低时这一句就把视线从主人身上顶开了，而推进剂**每 tick 都按视线推**，现场就是"三叉戟朝着天上飞"。改成 `faceToward(aim, false)`（就是 `m_6725_` 每 tick 摆在主人身上的那个角度，与烟花那一枚完全同口径）。
- **「没有像烟花那样把矢量删去」**：推进剂换成"拟真烟花"之后，它与那枚挂载火箭**就是同一个东西**（每 tick 都在拉她的速度），所以"进到主人判定圈内"那一 tick 同样**必须先收掉它再清零速度**（`releaseThrust` 的两句与烟花那一支完全同构，日志改成「解除推进矢量（收掉还挂着的烟花，收掉激流推进剂，水平速度 X → 0）」）。链路以别的理由收手（威胁/超时/缺料）时照旧只丢引用、保持动量。

### ⑦ ⑤ 远程空袭认不认这把武器：两层都要放行，缺一层都"完全不起飞"

- **武器位**：`isModeActive` 走的 `hasWeapon` 对远程任务只认 `isRangedWeapon`，而 `isRangedWeapon` 从 五百三十二 起就把激流三叉戟排除（口径是"它投不出去，按近战算"）→ 模式压根不激活。新增 `MaidFlightKit.hasWeaponForFlightTask`（**远程任务且她没有任何远程武器时**，那把三叉戟顶武器位），`isModeActive` / `missingParts` 改用它。
- **弹药**：`hasAmmoForRanged` 在"背包里没有远程武器"时恒为假 → 新增 `rangedAmmoOk`（有远程武器才按老规矩要弹药；武器位由三叉戟顶着时不吃弹药），`missingParts` 与两个诊断工具（`AirRaidTool` / `ReadinessTool`）同步。
- **地面腿**：与 ⑤ 同一处（`canJumpToLaunch`）；远程地面分支原来在两条腿都不成立时还会**无条件 return**，现在她会起跳。
- **边界（刻意不做的）**：远程空袭的**开火**那一支（`hasUsableRangedWeapon` / `fireRanged`）**一字未动**——她拿不出远程武器时就只是不开火（不做玩家做不到的事）；`isWeaponForTask` / `equip` / `resolveRangedWeapon` 也**刻意不放行**三叉戟，否则会把"远程空袭主手被换成三叉戟"和"三叉戟算远程武器"一起带进来。

### ⑧ 没动的两处（说明白）

- 近战那一记「旋转冲击」（`MaidTridentSpinBehavior` 的 DASH）照旧：原版矢量、16 tick 旋转、20 tick 收招硬直、撞人结算——本批只碰**推进剂**。
- 两个开关保留：`combat.riptideFlightScale`（力度折扣，默认 1.0）与 `flightFollow.trident`（飞行跟随不消耗耐久）。本批只把前者的**说明**改成"照搬玩家在水中那一记"（配置注释、手册面板 tooltip 同步）。
- 顺带把游戏内手册（「鞘翅与飞行作战」章、两小节共 6 处）改对：远程空袭的**武器位例外**（她一把远程武器都没有时，那把带激流的三叉戟可以顶上武器位——只保证**能起飞**、不开火）；「可以飞行的道具」在**两个任务里**都从"三选一"改成**四选一**（补上 六百三十三 起就在的第四条腿：带激流附魔的三叉戟，并写明"拟真烟花 / 判附魔等级 / III 级约 15 格 / 每口扣 1 点耐久 / 顺序 扇子 → 烟花 → 法术 → 激流"）；弹药那条注明**"武器位由三叉戟顶着时不吃这条"**；飞行跟随那一节的省料开关从"两个"补成**三个**（六百四十 加的「消耗三叉戟耐久」当时漏进手册）；"解除推进矢量"那行日志的引用同步成新措辞。

### ⑨ 验证与边界

- 两树 `javac` EXITCODE=0；改动文件 forge 11 个 / neo 12 个（neo 多一个 `MaidMaceSmashBehavior`，重锤只在 1.21.1 有）；手册三份 changelog（root `CHANGELOG.md` = BOM+LF，两树 `assets/promaid/guide/changelog.txt` = BOM+CRLF）逐字节一致。
- **本条未经实机验证**（按约定，测试归玩家）：上面的数字全部由原版公式 + 反编译实证推出；手感要微调时旋钮是 `combat.riptideFlightScale`（调小 = 更短更稳）。
- **边界一**：空袭**爬升相位**（`tickClimbToAltitude`）依旧只认烟花/羽扇/法术（前几批的既定口径，本批没动）。只带激流三叉戟的存档在"敌人高出十几格"时靠起飞那一口 + 后续循环；若实测爬不上去，下一批给爬升相位也补一条腿。
- **边界二**：推进剂窗口按"力度衰减到 0.35"收手（III 级约 12 tick），而**动作**（旋转）照原版放满 20 tick——两者不是一件事，别按"旋转停了"判断推力停了。
- **边界三**：远程空袭带三叉戟时她**会飞但不一定会开火**（见 ⑦ 的边界），那是有意留的；力度折扣是全局的（四条腿共用），不按腿分开。
- **本条尚未发版**。

## 实测六百四十【激流三叉戟当推进剂：照搬"玩家在雨里的那一记"；顺带还回"主手/副手也算"、旅行者背包口径与省料开关】

### ① 需求（四条，用户原话）

1.「女仆使用三叉戟起飞/飞行的时候飞行格数异常，一下子就能飞100多格。激流3」
2.「飞行跟随没有不消耗三叉戟耐久的开关。而且三叉戟没有减少矢量的相关措施。」
3.「使用激流三叉戟替代烟花的时候准度等各方面都出现了异常。产生了极大的违和感。」
4.（第一版改完之后的追问）「一枚"隐形烟花"？不应该照搬玩家在雨中飞行的速度嘛？」
5.（同批）「从"主手 → 副手 → 背包"改成只扫背包，改回去」＋「从旅行者背包里也能拿东西」。

### ② 把账算清楚：①③ 的病根不是"力度太大"，是"她少吃了玩家那份阻力"

玩家在雨里/水里放激流 III：**一口 `push(视线 × 3.0)`**（3.0 格/tick = 60 格/秒），随后被**空气阻力 0.91/tick** 吃掉——总行程 ≈ `3.0 ÷ (1 − 0.91) ≈ 33 格`。
女仆旧版放的是**同一口**（力度一分不差），但她**在滑翔**：鞘翅的阻力只有 **0.99/tick**，于是同一口 3.0 能飘 `3.0 ÷ (1 − 0.99) = 300 格`——实测现场就是"一下子飞出 100 多格"。
另外旧版是"在旧速度上**加**一份视线速度"：转弯时两份速度直接相加、旧的那份既不衰减也不重定向，只能靠滑翔那 10%/tick 的转向力慢慢掰回来——轨迹像醉汉走路、落点算不准，这就是 ③。

### ③ 现在：力度照搬玩家（不打折），补的是"玩家那份空气阻力"

`MaidRiptideBoost` 按这一条重写：

- **点火那一口**：`v ← v×0.5 + 视线 × 原版力度`（原版力度 = `3.0×(1+等级)/4`，I 1.5 / II 2.25 / III 3.0 格/tick）。
  **力度一分不打折**——`combat.riptideFlightScale` 默认 **1.0**（就是玩家那一口；想更省更稳可以往 0.1 调）。
- **之后**：水平速度每 tick **×0.92**（= 0.91 ÷ 0.99，**玩家在雨里的空气阻力**补在滑翔自己的 0.99 之上），直到速度掉回滑翔常态（0.35 格/tick）或满 60 tick。
- **竖直分量不动**：那是鞘翅滑翔自己的物理（升力/俯仰），也是掉高抬升与俯冲冲刺的命根子——本条只补"玩家不是滑翔"这一件事（水平那一份）。
- **行程回到玩家的量级**：I ≈ 17 格 / II ≈ 25 格 / III ≈ 33 格（与玩家在雨里放同一把三叉戟同量级）；飞行跟随进到 5 格收手线照样会被抓住。
- **唯一一处刻意偏离玩家**：点火那一 tick 的 `v×0.5`（玩家是自己瞄着放的，女仆没有"手"，得让她的视线说了算）——这是 ③ 的解法；衰减期间**不再每 tick 重定向**（那会变成持续推力，正是 ① 的病根）。
- 四条腿共用这一份实现：起飞（空袭 + 飞行跟随）、掉高抬升、俯冲冲刺。**近战那一记"旋转冲击"一字未动**（那里要的就是原版那一口的爆发力）；突进起手时会收掉衰减窗口（那十几 tick 的速度归突进）。
- 窗口由空袭与飞行跟随两条链路每 tick 各驱动一次，且只在她滑翔时生效；收手/进半径/突进起手/女仆移除与服务器停止都有显式清理。

### ④ 主手 / 副手重新算数（第五条反馈）

`MaidFlightKit.findRiptide` 还回 **主手 → 副手 → 背包** 三处都找（实测六百三十八 曾收成"只认背包"，理由写在当时的注释里；本条按玩家要求改回）。于是两侧判定同口径：`hasRiptide` = 三处 + 精妙背包/旅行者背包 + 我们自己借走的副手展示件。
**耐久扣在谁身上**：找到哪一件就扣哪一件——拿在手上用的就是手上那把。近战突进仍只看主手（那是攻击起手，与原版一致），一条没动。

### ⑤ 旅行者背包：它和精妙背包本来就是同一条路

用户要求"从旅行者背包里也能拿东西"。javap 实证（两版 TLM 包**签名一致**）：

- TLM 的额外容器体系里，精妙背包 = `SBackpackSlotRef`、旅行者背包 = `TBackpackSlotRef`，两者**都 extends `CuriosSlotRef`**、都按饰品栏里那件背包的物品能力（1.20.1 `ForgeCapabilities.ITEM_HANDLER` / 1.21.1 `Capabilities.ItemHandler`）读写，注册入口分别是 `SBackpackCompat.init()` / `TBackpackCompat.init()`。
- 我们只调 TLM 的公开 API（`MaidContainerCache.getContainers` + `ItemsUtil.findStackSlot` 那条请求链），所以**旅行者背包从 实测六百三十九 起就在同一条路上**，不需要各写一份。
- 生效条件与精妙背包一字不差：Curios 在场 + TLM"女仆饰品"开启 + 背包**真的戴在她饰品栏里**。
- **这次补的是"可验证"**：`MaidExtraContainer.pull` 现在**真的从额外容器取来一件就写一行日志**（限频 2 秒）——「她身上没有 → 从精妙背包/旅行者背包取来 1x…」；飞行缺件诊断那一行也拆成 `身上激流三叉戟=`（主手/副手/背包）与 `额外容器激流三叉戟=`（精妙背包/旅行者背包）两项。运行日志搜「额外容器」即可确认这条链路通没通。
- 顺带：`pull` 改成**先手写扫一遍她自己的背包**（判据与 TLM `findStackSlot` 的第一遍完全相同、但不发请求）——她自己有的常见情形更省，而且"她自己没有、请求之后却有了"才等于"确实是从额外容器搬进来的"（上面那行日志靠的就是这个区分）。

### ⑥ 第二条开关：飞行跟随·消耗三叉戟耐久

- 新配置 `flightFollow.trident`（默认**开** = 照原版每次推进扣 1 点），与 `flightFollow.firework`、`flightFollow.elytra` 凑成同一档三个省料开关。**只管飞行跟随**——空袭的起飞/抬升/俯冲是战斗动作，照旧扣耐久（那边没有、也不该有这个开关）。
- 起飞那行日志多了 `激流耐久=照原版扣/不消耗`；推进明细改成「力度 3.00 格/tick，照玩家雨里那一记；水平衰减约 N tick、行程约 33 格」（打折时还会写「折扣 0.60」，不消耗时写「不消耗耐久」），验收搜 `激流起飞` / `空袭·激流` 照旧。

### ⑦ 顺手补的两处口径

- `fuelLabel`（飞行跟随"这一趟按什么飞"）认出了**第四条腿**：六百三十三 起只带激流三叉戟的女仆会被报成"位移法术"，与实际取用顺序（扇子 → 烟花 → 法术 → 激流）不符。
- "解除推进矢量"那一行现在也会写「收掉激流衰减窗口」（此前只提那枚火箭——两者是同一件事：都在持续改她的速度，不收窗口只清速度等于白清）。

### ⑧ 设计迭代（留个明白账）

第一版把这一口写成了"**隐形烟花**"：照原版挂载烟花的递推式 `v ← v×0.5 + 视线×perTick`、持续 20 tick、固定点 = 原版力度 × 0.6。它把 ①③ 都治住了，但第 4 条反馈点破了要害——**激流不是烟花**：玩家在雨里那一记是"一口 3.0 加 0.91 的阻力"，不是"持续收敛到 1.8"。所以本条的最终口径是"原版力度不打折 + 补上玩家的阻力"，行程也回到玩家那把三叉戟的 17/25/33 格。

### ⑨ 验证与边界

- 两树 `javac` 0 错误；两树逐点位结构校验（标记数量 + 归一化后的代码逐行比对）通过；新配置在配置面板「战斗」与「移动与行为 → 飞行跟随」各占一行。
- **边界一**：近战空袭的**爬升相位**依旧只认烟花/羽扇/法术（前几批的既定口径，本批没动）。只带激流三叉戟的存档在"敌人高出十几格"时能靠起飞那一口 + 后续循环，若实测发现爬不上去，下一批就给爬升相位也补一条腿。
- **边界二**：掉高抬升的抬头窗口是 10 tick、衰减窗口按"速度掉回常态"算（III 级约 24 tick），所以后段那一口会跟着盘旋切线推——与原版烟花同款（它也是活二十来 tick、抬头窗口只开一半）。
- **边界三**：力度折扣是全局的（四条腿共用），不按腿分开。
- **本条尚未发版**。

## 实测六百三十九【先判背包，再判精妙背包：精妙背包/旅行者背包里的东西也进"检索圈"了】



### ① 需求



用户原话：「兼容方面，所有判定的位点再加上一个看看精妙背包里面有没有东西就行了。先判背包，再判精妙背包。」



### ② 做了什么：给 `MaidExtraContainer` 加"取物"那半边，再接到所有检索位点



- 新增 `contains(maid, filter)`：**只读探针**——"额外容器里有没有这个东西"。TLM 的 `ContainerRef` 只给了 `containing(maid, stack)`（要求给一件**具体物品**做同类比较）/ `insert` / `extract`，没有"按谓词看一眼"的口子，所以本方法用**取出 1 个 + 立刻放回**实现；万一放不回去有三级兜底（原容器 → 她自己背包 → 落地并记一条日志），**绝不吞物品**。

- 新增 `pull(maid, filter, maxCount)`：**取物**——她自己的背包里没有时，请 TLM 的额外容器系统把匹配物**搬进她的背包**。走的是 TLM 自己那条路（`ItemsUtil.findStackSlot` → `MaidRequestItemEvent` → `ExtraContainerRequestHandler`）：先扫她自己的背包（找到就返回，零副作用），没找到才发请求。**TLM 本体对弓弩弹药/呼吸药水/自愈/喂主人/工作餐/换装备用的就是这一条**，所以口径与本体完全一致（包括"她的背包满了就把她最后一格非空物品挪进额外容器腾地方"这个动作——那是 TLM 的既定行为，不是我们加的）。

- 接线位点（两树同）：**搭方块**四个模块（自保搭高 / 搭路 / 挖矿 / 伐木）的门禁与取材；**自动选取工具/武器**（`MaidToolAutoEquip`：镐 / 斧 / 锄 / 盾 / 枪械弹药，含 `ensureForTarget` 一类的按需换装）；**烧制**（食材 / 矿物类 / 通用可烧物 / 烟熏炉·高炉按配方 / 燃料 / 炉型判定 / 炉子与背包是否匹配）；**酿造**（材料 / 白名单材料 / 水瓶 / 可续酿的基底药水）；**飞行燃料与弹药**（烟花 / 羽扇 / 激流三叉戟 / 箭矢：判定与取用两侧）；**自保**（回血资源门禁、着火与低血两条金苹果消耗路径、逃生资源门禁）；**插火把**；**种树苗**；**撒骨粉**；**支援主人**（11 条投喂/给药分支 + 那个"她身上到底有没有援助物资"的总门禁）。

- **不加新开关**：TLM 本体对它自己的任务本来就是无条件走这套的，我们跟着它的口径走。装了精妙背包却不想让女仆用里面的东西，那是"别把她打扮成这样"就能解决的事，不是我们该拦的（这一点写在各处注释里）。



### ③ 本次没接的位点（留个明白账）



蓝图材料统计、炸弹/TNT 物资、盾牌共享、蜂蜜点燃、落地水桶、智能放置/合成/给物工具——不是不认，是它们要么属于"只统计不取用"的账本（要接就得连"取用"一起接，否则会出现"算她够料、实际拿不到"），要么牵动整条链路，需要单独一轮。



### ④ 验证与边界



- 两树 `javac` 0 错误；镜像之后逐点位做过结构对照（标记行 + 后续代码骨架比对），**发现并修掉 4 处漏插/错插**（含一处会把金苹果消耗点插到扫描循环之后的位置错误）。

- 生效条件与 实测六百三十六 相同：Curios 在场 + TLM 的「女仆饰品」开启 + 背包**真的戴在她饰品栏里**；一件都不满足时两个方法都直接在入口早退，行为与旧版**一字不差**。

- **本条尚未发版**。



## 实测六百三十八【激流三叉戟只认背包：主手那件是玩家的武器，不是燃料】



### ① 需求



用户原话：「激流三叉戟只作背包判定。不需要放在主手，防止玩家还想要用其他的主手武器进行，仅把激流三叉戟作为推进器。」



### ② 做了什么



- `MaidFlightKit.findRiptide` 从"主手 → 副手 → 背包"改成**只扫背包**：手里的三叉戟是玩家选的武器，不该被我们当成推进剂扣耐久（旧版"举着激流三叉戟 = 默认拿它当燃料"，把玩家的主手选择权吃掉了）。

- 推进剂那几条腿（实测六百三十三 的起飞 / 飞行跟随、实测六百三十四 的空袭抬升与俯冲）改用新增的 `findRiptide` 取用版 `fetchRiptide` = **先判背包，再判精妙背包**（与 实测六百三十九 同一套口径）。

- **近战突进不跟着改**：`MaidTridentSpinBehavior.spinWeapon` 仍要求**主手举着**激流三叉戟——那是攻击起手，与原版语义一致，它本来就不走 `findRiptide`。

- 判定侧（`hasRiptide`）同步改成"背包 + 精妙背包/旅行者背包 + 被我们自己借走的副手那件"，不再看双手。



### ③ 验证



- 两树 `javac` 0 错误。

- **本条尚未发版**。



## 实测六百三十七【烧制/酿造没有目标时不再"把自己冻在原地"】



### ① 反馈与日志证据



用户：「不进入我们自己写的烧制且处于 home 状态这个模式的时候会直接卡在原地不动。」



`latest.log` 实证（2026-09-23 13:54 起）：女仆在 home 模式下反复 `cook start: maid=… furnace=null`，之后再没移动过一步。



根因：烹饪/酿造行为在"找不到工作方块"时 `MaidWorkTags.setStill(true)`，而 WORK_STILL 经 `MaidMoveSuppressMixin` 是**彻底静止**——每 tick 清 WALK_TARGET + **调 stopNavigation** + 取消 MoveToTargetSink，连"直连导航"这种绕开 sink 的通道都会被立刻掐掉。而这时候：本任务的 TLM 随机散步是关的（`enableLookAndRandomWalk=false`）、`HomeWorkMovementDriver` 只认农场/宰杀、`HomePatrolHandler` 又跳过"干活中"的女仆——三条外部驱动全都不管她，于是永久站住。



### ② 做了什么



- 三个"无目标"分支（找不到炉子/台子、炉子被拆掉、背包里没有这型炉子能烧的东西）改为**不站桩 + 边走边找**：放行移动，每 2 秒在周围 8 格内挑一个闲逛点（受 `WorkAreaClamp` 钳制——home 模式不逛出限制圈、跟随模式不逛离主人），扫描照旧每 20 tick 一次（`findFurnace` 以她当前位置为中心），**扫到就绑定、然后走过去坐下干活**。与宰杀那条"没有牲畜就四处闲逛"（实测三百四十）同一套口径。

- 绑定并到达工作方块之后**照旧站桩 + 坐下**——那才是"在干活"，一个字没动它。

- 顺手把踩到的两处历史遗留对齐全：`WorkStatusReporter` 的口径说明、以及 neo 树 `useGoldenApple` 缺的那道金苹果冷却门（本树原先只有着火那条路有门，低血那条敞开吃）。



### ③ 验证



- 两树 `javac` 0 错误。

- **本条尚未发版**。



【实测六百三十六】精妙背包适配：她自己的背包满了，就装进她身上那件"额外容器"





· **需求（issue #20）**





· 反馈人 MingDeee：「农场模式会连锁收割农作物。然后掉落物放到女仆背包里。而车万女仆本体支持当女仆背包满了之后会自动放进精妙背包里。但是这里如果一次性收割的掉落物直接超出女仆背包的容量，是不会把超出来的放进精妙背包，而是以掉落物的形式掉在地上。」用户拍板：「我不打算把压缩盒这个物品删掉。但是对于精妙背包的相关适配确实是要做的。帮我做一下。简单来说就是发现自己背包满了之后，再检索一下有没有精妙背包。有的话就装进去。」





· **做了什么：新增 `MaidExtraContainer`，接到六条"收东西"的链路上**





· 新增 `com.maidsmart.tool.MaidExtraContainer.overflow(maid, rest)`：把**她自己背包塞不下的那一份**再往"额外容器"里塞一遍，返回仍塞不下的余量。


· 接线（两树各六处，全部在"已经确认她自己的背包塞不下"**之后**才调用）：① 所有"还东西"路径的统一出口 `MaidGiveBack.give`；② 砍树收方块 `MaidWoodBehavior`；③ 挖矿收方块 `MaidMineBehavior`；④ 搭路方块回收 `BridgeUpBehavior`；⑤ 追踪方块销毁回收 `PlacedBlockTracker`；⑥ **农场连锁收割的掉落兜底 `FarmSweepMixin.collectDrops`**——issue 报的就是这一处。


· 新开关 `misc.backpackOverflow`（默认开，面板在"农场与杂项"那一页）：关掉 = 只用自己的背包（旧行为）。





· **走 TLM 现成的"额外容器"API，不自己去反射精妙背包**





· javap 实证（1.20.1 的 `original_tlm.jar` 与 1.21.1 的 neoforge 包**签名一致**）：TLM 本体早有一整套额外容器体系——`MaidContainerCache.getContainers(maid)` 返回的列表里，**第 0 项永远是她自己的背包**，之后是按 curios 槽位优先级排好的额外容器（精妙背包 = `SBackpackSlotRef`，坐 `back` 槽；另有旅行者背包），每一项都提供 `insert(maid, stack, simulate)` 与 `containing(maid, stack)`。


· 顺序照抄 TLM 自己的拾取（`ExtraContainerPickupHandler`）：**先放"已经装着同类物品"的容器，再放任何容器**——不然会出现"背包里明明有半组、却新开一格"的分裂观感。


· **为什么不自己找精妙背包**：它对外给的是它自己的能力 `CapabilityBackpackWrapper`，**不是** Forge / NeoForge 的 `ITEM_HANDLER`——自己找就得硬编码它的类名、版本与内部方法名，还得为两版本各写一份。TLM 已经把这层做完了，而且顺带覆盖旅行者背包、以及将来任何注册进它那套体系的容器；我们只调它的公开 API。


· **生效条件**（先说清楚，免得当成"没生效"）：需要 Curios 在场 + TLM 的「女仆饰品」功能开启 + 背包**真的戴在她饰品栏里**。一件都不满足时 `getContainers` 只返回"她自己"那一项 → 本方法原样返回，行为与旧版**一字不差**（拿在手上、或放在别处的背包不算——那两处 TLM 自己也不认）。


· **绝不吞物品**：额外容器再塞不下才落地。最坏情况仍是"掉在地上"，与 实测五百九十五 的口径一致。





· **验证与已知边界**





· 两树 `javac` 0 错误；`MaidExtraContainer` 已成两树编译产物（入包）。


· 日志：真的被额外容器收下时写一行 `额外容器` 类的日志（「自己背包塞不下 → 精妙背包/额外容器收下 N 件」），方便确认"到底认没认出来"。


· **本次只做"装进去"**：issue 里还有一半是"自动选武器/工具只在女仆背包里找、精妙背包里的拿不出来"——那是**取出**方向（要按谓词从容器里抽一件到她手上、用完再放回去），牵动换装与回收链路，本次没动；"工具剩一点耐久就换、有时又用到爆"那条也还没查（需要反馈人说是哪件道具被用爆了）。


· **本条尚未发版**：代码已在两树并部署到本地三个 mods 目录，附件/发布页要不要覆盖等用户发话。





【实测六百三十五】骑乘/坐下中禁止搭方块：对所有搭方块行为生效





· **需求**





· 「「骑乘/坐下中禁止搭方块」对所有搭方块行为生效」。





· **根因：同一件事散在三处、口径还不一样**





· 实测一百五十六 把这条护栏**只做在挖矿里**（`mine.rideNoPillar`），而且只判 `isPassenger`（骑乘）；


· 实测五百九十七 又给**搭路**单独补了一份坐姿判定（`BridgeUpBehavior.isSitting`，骑乘 + TLM 坐下姿态两种形态）；


· **伐木一处都没有**，自保搭高也没有。





· **做了什么：判据下沉到统一闸口 `MaidPlaceGuard`**





· 新增 `MaidPlaceGuard.seatedBlocked(maid)`（开关 + 形态）与 `isSeated(maid)`（纯形态：`isPassenger` **或** `isMaidInSittingPose`）。


· `MaidPlaceGuard.blocked(maid)` 里**先判**这一条、再判「悬空禁搭方块」——两个开关互不影响（一个管"她在空中乱搭"、一个管"她根本挪不动的时候乱搭"）。四个自主搭块模块（自保搭高 / 搭路 / 挖矿垫脚 / 伐木垫脚）全都经这一个闸口，于是**伐木与自保搭高这次一并吃到**。


· 挖矿那四处原有的独立判据改成调用同一个方法（于是它们也认得"坐下"了）；搭路的 `isSitting` 改成转发，不再各写一份坐姿判定。


· 开关**沿用旧键** `mine.rideNoPillar`（默认开，面板与注释的文案一并改宽成「骑乘/坐下中禁止搭方块」）：老存档关掉过它的人设置不变，现在只是"作用范围从挖矿扩到全部"；关掉 = 旧行为（骑乘/坐下也照常搭）。


· **刻意不覆盖**（与 `blocked` 那份"不拦"名单同一口径）：蓝图建造与指标石建造（`MaidBuildBehavior` / `IndexStoneBuildBehavior`——**它们本来就要求她坐下施工**，拦了就是自废武功，这也是本次最容易踩的坑）、插火把、AI 工具 `smart_place`（玩家的显式指令）、落地水/细雪（保命机制）。





· **验证与已知边界**





· 两树 `javac` 0 错误。改动点：`MaidPlaceGuard` + `MaidMineBehavior`×4 + `BridgeUpBehavior`（各树），全部走 `seatedBlocked`。


· 玩家侧可见变化：① 伐木/自保搭高在骑乘或坐下时不再放方块；② 挖矿在"坐下"时也不再放方块（此前只拦骑乘）。


· **本条尚未发版**：做法同 六百三十三。





【实测六百三十四】激流三叉戟进空袭：盘旋"抬升"与俯冲"下扎"（单独一套）





· **需求（上一批我自己写歪的那两处）**





· 六百三十三 那一批的收尾里我写了「两处刻意没动」，用户当场否掉：「这就是你不对了。很多 PVP 玩家都是可以做到用激流进行向上抬升飞行的。这边也要有，不过需要单独写一套。」





· **为什么必须"单独一套"，不能照搬六百三十三那一记**





· 六百三十三 的 `MaidTridentSpinBehavior.boostForFlight` 是**沿她此刻的视线**推一口，朝向由调用方摆好（起飞用 `faceLaunchDirection`、飞行跟随用 `faceToward`）。空袭那两处不能照抄：


· **掉高补推**：盘旋期她的视线是 `faceOrbit` 摆的"绕圈切线"，沿它推只会在圈上窜一下、**抬不起来**——这正是我上一批说"加了反而把盘旋朝向推偏"的真实原因。PvP 玩家能用激流抬升，靠的是**先抬头再放**；所以这一路必须自己把机头抬到补推仰角（`airRaid.rangedBoostPitch`，默认 -45° = 抬头 45°，与烟花/法术同一个窗口口径），**然后**才沿视线推。


· **俯冲段冲刺**：这一段的朝向已被 `faceTarget` 钉在敌人身上（低头朝下），所以沿视线推 = **朝下扎得更快**，与烟花/羽扇那两条同款口径（"方向不变、只加大小"）；它不需要改朝向，反而**绝不能**改。





· **做了什么**





· 新增 `com.maidsmart.combat.MaidRiptideBoost`（两树各一份，与六百三十三那一记完全独立、互不调用）：`liftForRanged(level, maid, target, pitch)`（先抬头再推）与 `diveDash(level, maid)`（沿当前视线推）。


· **矢量逐字节照原版**（"还原该激流等级下的矢量"）：方向 = 视线、力度 = `3.0 × (1 + 激流等级) / 4`（I 1.5 / II 2.25 / III 3.0 格/tick）、`push`（加在速度上）、动作是原版那一记（旋转 20 tick + 标志位 4 + 按等级音效 `TRIDENT_RIPTIDE_1/2/3`）、耐久 −1。**不打折、不额外补竖力**——补高完全靠"抬头"，与玩家一致（换个等级就能看出矢量确实变了）。


· 接线：远程空袭的「按需补推」与俯冲段的「冲刺加速」各加一条腿。**燃料优先级 = 法术 → 激流三叉戟 → 烟花 → 羽扇**：法术不消耗物资，激流只扣耐久（三叉戟可修复、可再生），而烟花是**消耗品**——所以有激流时先用激流，把玩家的烟花省下来。


· 新开关两条（默认都开，面板在"空袭数值"里）：`airRaid.rangedBoostRiptide`（掉高补推·用激流三叉戟）、`airRaid.diveBoostRiptide`（俯冲段冲刺·用激流三叉戟）。总开关沿用 `combat.riptideDash`（关掉 = 激流相关的一切都退回）。


· 与六百三十三 同款：**不登记突进状态**（不写 `MaidTridentSpinBehavior` 的 DASH 表、不接管寻路、不进硬直），所以对空袭状态机是"透明"的，那边不需要为它让位。





· **验证与已知边界**





· 两树 `javac` 0 错误；`MaidRiptideBoost` 已成两树编译产物（入包）。


· 日志：明细写 `空袭·激流` 这一类（「掉高抬升／俯冲下扎（激流 N 级，力度 X，距敌 D 格）」），远程那条还会另写一行「掉高挥激流三叉戟」（与「掉高挥羽扇」「掉高补烟花」同一口径），俯冲那条并入既有的「俯冲加速（激流三叉戟，距敌 D 格）」。


· 已知边界：激流三叉戟按 六百三十三 的口径找（主手 → 副手 → 背包），**不要求举在手里**；俯冲那一段她会带着原版旋转动作往下扎（那是激流本来的动作，与近战突进同款）。


· **本条尚未发版**：做法同 六百三十三。





【实测六百三十三】激流三叉戟也能当"飞行推进剂"：起飞 / 启动飞行跟随（本条尚未发版）





· **需求**





「强抬一下激流三叉戟，这个也可以用来起飞/启动飞行跟随，大致代码和孔雀羽扇差不多（只是需要注意一下动作以及还原该激流等级下的矢量）」。





· **做了什么：能飞的道具从"三选一"变"四选一"**





· 空袭三件套的第三件（"可以飞行的道具"）与飞行跟随的启动门禁**本来就是同一条口径**（`MaidFlightKit.hasFlightPropellant`）。现在再添一件：**带激流附魔的三叉戟**——只带一把激流三叉戟、没有烟花/羽扇/法术的女仆，也能起飞、也能启动飞行跟随。


· 判定与羽扇同款（新增 `MaidFlightKit.findRiptide` / `hasRiptide`）：主手 → 副手 → 背包，并且**含"被我们自己的动作表现借走的那件副手"**（六百〇九 那条教训——借走的十几 tick 里它不在任何槽位）。


· **不新增配置项**：开关沿用既有的 `combat.riptideDash`（激流三叉戟旋转突进，默认开）。关掉它，这一条腿**既不算"能飞的道具"、也不会真去推**——判定与行为同开同关，不会出现"气泡说齐了、她却不飞"那种分裂。


· 顺序是**刻意的**：空袭起飞是 扇子 → 烟花 → **激流**；飞行跟随补推是 扇子 → 烟花 → 法术 → **激流**。放在最后 = **不改变任何"已有烟花 / 羽扇 / 法术"存档的起飞手感与烧料节奏**，只有真拿不出前面那几样时才轮到它。





· **这一口的力度与动作（需求点名的两处）**





· **矢量照原版**：`3.0 × (1 + 激流等级) / 4` —— I 级 1.5、II 级 2.25、III 级 3.0 格/tick，方向 = **她当前的视线**，用 `push`（加在速度上，不是赋值）。这与原版 `TridentItem.releaseUsing` 逐字同式（本模组从 实测五百三十四 起就是这么读的）。


· **动作**：原版那一记**旋转**（旋转计数 + 标志位 4，画面由 Gecko 去重 mixin 负责）、按等级放原版音效（`TRIDENT_RIPTIDE_1/2/3`）、耐久 −1（与投掷共用那一条）。旋转时长取**原版 20 tick**——近战突进那条是 16 tick（刻意收短、为收招快），这一条只是"起飞/补推时亮一下动作"，没必要收短，所以两个常量分开。


· **朝向由调用方摆好**：原版是玩家自己抬头瞄着放；这里起飞用 `faceLaunchDirection`（抬头 62°/45°）、飞行跟随用 `faceToward`（抬头下限 TAKEOFF_PITCH）——本方法**自己不动她的朝向**，否则飞行跟随那一路会被拧偏。


· **为什么不复用近战那条突进**（`tryStartDash`）：那条方向取"她→目标"、速度每 tick 被按比例顶着、还带伤害去重与收招硬直。拿它当推进剂会**接管飞行跟随的速度**（每 tick 被拽到 30%，等于原地转圈）。这一条**只借动作与力度**：速度只 push 这一口，之后全交给滑翔与重力，不登记突进状态、不接管寻路——所以对空袭状态机是"透明"的，不需要那边让位。





· **验证与已知边界**





· 两树 `javac` 0 错误；`javap` 回读两树编译产物，确认 `findRiptide` / `hasRiptide` / `boostForFlight` 三个新方法都在。


· 日志口径：起飞写「激流三叉戟起飞」、飞行跟随补推写「挥激流三叉戟追主人」，明细在 `激流起飞` 这一类里（「激流推进（激流 N 级，力度 X）」）；`燃料判定:` 那行诊断也加了 `背包激流三叉戟=`，缺件时能一眼看出是"没有"还是"没认出来"。


· 气泡尾注同步：缺"可以飞行的道具"时的提示改为「（烟花火箭 / 孔雀羽扇 / 位移类法术 / 激流三叉戟任一）」；飞行跟随的跳过日志改为「四选一」。


· **两处刻意没动**（都有理由）：① 远程空袭的「掉高补推」那一支不加激流——原版激流是"沿视线的一口"，抬头补高交给法术/羽扇/烟花更合适，加了反而会把她盘旋时的朝向推偏；② 俯冲段冲刺（`diveBoost`，其中羽扇那一支默认关）不动——需求点的是"起飞 / 启动飞行跟随"，不是俯冲加速。


· **本条尚未发版**：代码已在两树并部署到本地三个 mods 目录，附件/发布页要不要覆盖等用户发话（做法同 六百二十四 ~ 六百三十二）。


【附件第三次重传 · 2026-09-22】法术任务不再动她的主手（实测六百三十二），仍然覆盖进既有附件





· **为什么**：下面这一条（实测六百三十二）写的时候没有发版。按用户要求，现在把它**直接覆盖进 v1.2.3 的两个下载附件**（文件名不变，仍是 `promaid-1.2.3.jar` / `promaid-1.2.3-neoforge-1.21.1.jar`），不新发版本号；因此 六百三十二 那条里"本条尚未发版"的说法作废，以本条为准。


· **重传后的附件** = v1.2.3 首发（实测五百九十五 ~ 六百二十三）+ 六百二十四 / 六百二十五 / 六百二十六 + 六百二十七 ~ 六百三十一 + **六百三十二**；它的"法术任务"部分与 六百二十五（法术任务闸门）是同一条线，两条都在包里。


· **本次改什么**：法术任务（近战法术 / 远程法术）下**不再自动切换她的主手武器**——此前会把附属认的"法术书"当武器塞进主手，诡厄巫法的**聚晶袋**就是这样顶替掉玩家放的剑（每 tick 换一次）。附属施法本来就不需要手持（它自己扫背包），但主手是她近战那一半的伤害来源，而且拔刀剑那条施法链路要求刀在主手——换掉就等于把玩法拆了。


· **玩家侧可见变化**：法术模式下主手**放什么就是什么**，她不再动；想让她近战有伤害，把武器放她主手即可。没有新功能、没有新配置项。


· **安装方式与两次重传完全一样**：mods 目录里只留这一个同名 jar（覆盖即可）。





【实测六百三十二】法术任务下不再动她的主手：聚晶袋被当成"主武器"，玩家放的剑每 tick 被换走





· **反馈**





「版本1.2.3，法术战斗模式会把诡厄巫法的聚晶袋当作主武器，把剑放主手他会自己换成聚晶袋。这边就存在一个问题，自主切换武器到主手是否干涉了法术战斗本身？如果有的话，就不要再让他干涉了，很容易出问题」。





· **根因：动她主手的是我们，而"法术装备"这个口径把袋子当成了武器**





· 法术任务（`maidspell:spell_combat_melee` / `spell_combat_far`）走 `MaidToolAutoEquip.ensureForTask` 的法术分支：实测五百五十九 收紧之后，它在"她真的带着法术装备"时按**"她认不认这件是法术装备"**换主手。


· 而"法术装备"是附属各 provider 自己认的（`ISpellBookProvider#isSpellBook`）。诡厄巫法那一份（`GoetyProvider`）的判定就是 `item instanceof FocusBag`——**聚晶袋正是它认的"法术书"**（反编译实证）。于是"只换法术装备"落到 Goety 上，等于"每 tick 把玩家的剑换成聚晶袋"。


· 附属自己**从不写她的手**：整个 jar 里 `setItemInHand` 零引用，只有 4 处**读**主手（`SlashBladeProvider` ×3、`SimplifiedSpellCaster` ×1）。"主手拿什么"本来只是玩家与附属的事。





· **这不是"多余动作"，是真干涉（四条实证）**





· **施法根本不需要她主手拿东西**：Goety 走附属自己的数据（`MaidGoetySpellData.getSpellBook()/getCurrentStaff()/getCurrentFocus()`）加 `FocusBagItemHandler` 读袋内容，法杖由 `findSpellStaff` 扫 `maid.getAvailableInv(true)` 得到，附属还用 `WandUtilMixin` 把 Goety 的 `findWand/findFocus` 整个替换成她数据里那两件；ISS 那条我们更早就实证"不看主手"（法术书放饰品栏照样放）。⇒ 把袋子塞进主手是**零收益**动作。


· **主手是"近战那一半"的伤害来源**：`SimplifiedSpellCaster.executeCombat`（近战法术任务每 tick 的入口）在施法之后紧跟着 `doHurtTarget` + `swing(主手)`——主手拿什么就打什么伤害。换成聚晶袋 = 她的近战掉到拳头级，玩家放剑的意图每 tick 被抹掉。


· **主手还是某些 provider 的施法前提**：`SlashBladeProvider.processContinuousCasting` 取 `maid.getMainHandItem()`，不是拔刀剑就直接 return（现场日志 `continuous aborted: mainHand is not slashblade`），`initiateCasting` 也拿主手那把刀做连段。而拔刀剑默认在 `combat.spellGearIgnore` 里 → 不符合我们的"法术装备"词条 → 只要她背包里还有**别的** provider 的装备，我们就会把刀从她手里换走 → 附属吟唱当场断。


· **附属的装备注册表只看背包视图**：`SpellBookManager.initSpellBooks` 扫 `maid.getAvailableInv(true)` 注册法术书，`MaidSpellEventHandler.onMaidEquip` 靠**装备变更事件**增删。我们在主手与背包之间来回搬袋子，等于不断给它的注册表喂"换下 → 装上"。





· **修法：法术任务下完全不动主手**





· `MaidToolAutoEquip.ensureForTask` 的法术分支改为**直接 `return false`**：不换武器、不介入她的换装节奏（五百五十九 那条"得真带着法术装备才开放切换"由"一律不换"覆盖，因此不再需要）。


· **这个显式分支必须留着**：法术任务实现 `IAttackTask`（`IRangedAttackTask`），少了它就会落到下面的"模组战斗任务"分支，走附属**恒真**的 `isWeapon` → 又变成"万物皆武器"（五百五十九 之前的老毛病）。


· `MaidSpellCompat.isSpellWeapon` 从此没有调用方（保留作口径定义与诊断用，注释里写清"别再接回主手"）；`maidHasSpells` 仍由自主参战闸门 `AutoCombatPools` 使用。


· **没有新配置项**；想让她近战有伤害，把武器放她主手即可——她不会再动它。





· **验证与连带**





· 两树 `javac` 0 错误；`javap` 回读两树编译产物：`ensureForTask` 里对 `MaidSpellCompat` 只剩 `isSpellTask` 一次调用，紧接着就是 `iconst_0; ireturn`（`isSpellWeapon` / `maidHasSpells` 已不在其中）。


· 同一次排查里看到、**本次未动**的一条：法术任务也算"战斗任务"（`isAttackTask` 为真），"战斗状态自动装副手盾牌"照旧对法术女仆生效——那是副手，与本次问题无关（Goety/ISS 都不需要手持副手），故保留原行为。


· **本条随「附件第三次重传」进包**：两个附件与发布页已同步更新（说明见上一条）。


【附件二次重传 · 2026-09-22】搭路刷物品（issue #19，生存可刷）+ 一批此前未发布的修复





· **为什么**：下面五条（实测六百二十七 ~ 六百三十一）**写的时候都没有发版**。按用户要求，这次把它们**直接覆盖进 v1.2.3 的两个下载附件**（文件名不变，仍是 `promaid-1.2.3.jar` / `promaid-1.2.3-neoforge-1.21.1.jar`），不新发版本号；因此上一条「附件重传（六百二十四 / 六百二十五 / 六百二十六）」以及那几条里"不重新发布 / 附件仍是 v1.2.3 原样"的说法一并作废，以本条为准。


· **重传后的附件** = v1.2.3 首发（实测五百九十五 ~ 六百二十三）+ 六百二十四 / 六百二十五 / 六百二十六 + **六百二十七 ~ 六百三十一**。


· **最要紧的是六百二十七（刷物品）**：女仆铺桥用的方块"用不完、还会越用越多"——实测给她 1 个铁块，她铺了 128 格并全部回收，净多 127 个。**生存可刷**，建议所有在用 v1.2.3 的存档换包。


· **这批修的是同一类问题**：引擎的输出落到了"玩家看不见、或者本来不该碰"的地方——背包等级之外的隐藏格子（六百二十八）、动作表现临时借走的副手槽（六百二十七），以及两条搭块判定的缺口（六百二十九 / 六百三十一）。


· **没有新功能、没有新配置项**；存档、配置、任务、蓝图一律无需处理。





【实测六百二十七】搭路刷物品：取材把"动作表现"借走的副手当成了真材料（issue #19，生存可刷）





· **反馈**





粉丝在 GitHub **issue #19** 报「搭路的方块在使用时不会消耗，但是回收时会增加背包内存量」（NeoForge 1.21.1 / promaid 1.2.3，搭路材料：云杉木板）；用户侧复现更狠：**给她 1 个铁块用来寻路，她反手交回 128 个**。





· **根因：一边"不付账"，一边"照单归还"**





· **不付账**：动作表现（让她副手举一下这一下真正在用的那件）为了能看见，展示件写进的是**真实副手槽**——一件复制品，但货真价实、能被取材扣走。而搭路取材的口径是"背包 + 双手里数量最多的那种、先扣手后扣背包"（当年为"她手里正拿的就是想用的"特意加的口径）。搭路节奏默认 4 tick、展示 10 tick，于是下一次取材时展示件还在副手上：**扣走的是它，扣完动作表现又补一件新的**——真料从此一动不动。类注释里"展示件不参与任何判定"这句，在"扫双手取材"的链路上并不成立。


· **照单归还**：方块到期回收是**按方块 id 逐格还一件真物品**（这一条本身是对的，她放下去的本来就是物品），于是每铺一格就净产出一件。128 格 = 128 件。


· **顺带**：真料只可能在一开始被扣那一次；理论上一个方块都不给她也能一直铺——"有方块吗"的门禁也被展示件骗过。





· **修法：取材一侧不许碰"被借走的副手"**





· 新增 `BombPose.offhandBorrowed(maid)`（起飞烟花姿势借副手的情形一并算入）；内容物过滤器 `MaidBuildBlockFilter.takeBuildBlock` 加五参重载：借走期间**计数与扣取都跳过副手**。主手不跳（那是她自己真握着的）；没被借走时行为完全不变。


· 搭路 / 挖矿 / 伐木 / 索引石四条取材链全部接上；搭路的"有方块吗"门禁同步修（否则真料耗尽后她会被展示件骗着一直"试试看 → 报缺料"）。


· 遍历审计了**全部**搭方块链路：蓝图建造那条只吃背包、不扫手，本来就没有这个口子；**索引石"补下方支撑"那一格**与**战斗放置（黑曜石底座 / 床 / 重生锚）的取材**还有同一个洞口，一并补上——后者尤其要补，因为它的底座同样是**逐格回收真物品**，形状与搭路一模一样。


· 展示观感零变化：副手照旧举起她这一下真正用掉的那件。





· **验证**





· 两树 `javac` 0 错误；两个 jar 重新打包通过；已部署/已上传的两个包用 `javap` 回读确认新方法（五参重载 + `offhandBorrowed`）都在包里。


· 探针账本判据（诊断构建里带着探针）：`PROBE[pay]` 不应再出现"声称扣到、真实库存没变"且"副手正被借走"；`PROBE[ledger]` 的 `NET(回收-取料)` 应 ≤ 0，而不是持续为正。


· **一个必须知道的连带效果**：修完后**给她 1 个方块就只能铺 1 格**——那一格到期回收后再拿回来铺下一格（总账守恒，不再凭空生长）。想"给 1 个也痛快铺完"属于另一个设计，不在本次。





【实测六百二十八】熔炉产物"凭空消失"：其实写进了背包等级之外的隐藏格子（全模组取材/存放口径统一）





· **反馈**





「女仆在使用我们的手指时，会把烧好的物品从熔炉里拿出来，但物品会凭空消失，而不是被女仆放到背包里。」测试包：龙之冒险整合包。





· **根因**





她用**小背包（可见可用 12 格）**，而我们当时把东西塞进的是**原始 36 格** ItemStackHandler。物品没丢：读 region + NBT 取证确认那批铜锭躺在她背包**第 12 格**——"背包等级之外"的隐藏格子，玩家界面看不见，所以看起来像消失。TLM 的可用视图是按背包等级截取的（`getAvailableBackpackInv()`），我们却写在了原始视图上。





· **修法**





全模组（两树各 132 处）把"取材 / 存放 / 回收"的目标从原始 `getMaidInv()` 换成 `getAvailableBackpackInv()`（按背包等级截取；压缩盒存在时自动延伸到盒子的格）。只有压缩盒自身的判定与主入口保持原样。临时搭方块的回收落点也改成她真正装得下的格子。





· **效果与已知代价**





· 小背包女仆不会再"吃掉"东西；回收的方块也不会再落进看不见的格子。


· **代价（须知）**：以前被她放进隐藏格子的东西（例如那批铜锭）现在依然够不到——换中/大背包，或手动把那一格的东西取出来即可。





【实测六百二十九】不得把方块搭在别人头上：压人判定扩到其他女仆（自己那格照搭）





· **需求原文**：「除了不能搭到主人的脑袋上，也不应该搭到其他女仆的脑袋上，但是对于自己不需要有这个判定。」


· 原有闸门只判主人碰撞箱。现在同一格若被**其他女仆**的碰撞箱探到也不搭；**她自己那一格不判**（否则搭高会把自己锁死）。覆盖四个自主搭块模块 + 插火把 + AI 工具 smart_place + 蓝图/碑石建造（蓝图类遇阻是**延后**而非跳过）。配置 `misc.noPlaceOnOwner` 关掉即回旧行为。





【实测六百三十】主人踩临时方块也刷新寿命（与女仆同款）





· **需求原文**：「将主人站在方块上采用和女仆同款的刷新机制。」


· 旧版主人只有"脚下正踩着那一格"这一条：站偏半格 / 站在一格宽桥的边缘 / 刚走开，正在踩的那块就不再刷新 → 站久了自己脚下的桥烂掉（反馈："似乎只是重置了一次时间，站太久还是会掉下去"）。现在主人与女仆共用同一套几何：**站上去、或进入刷新带（水平 ≤4 格、垂直 ≤6 格）都刷新**；绑定女仆离线时倒计时照旧暂停。





【实测六百三十一】索引石「补下方支撑」那一格补上压人闸门





· 遍历所有搭块落点发现：四家自主搭块（搭路 / 挖矿 / 伐木 / 自保）与蓝图建造、碑石建造（supPos/below）都是**逐格**过压人闸门的，只有碑石建造里"目标格悬空 → 在下方补一块支撑"这一条漏了。补上同一口径：支撑格被主人或其他女仆占着就不补、自然也不放。


【附件重传 · 2026-09-22】v1.2.3 的下载附件已重传覆盖（同名文件，不新发版本号）





· **为什么**：下面三条（实测六百二十四 / 六百二十五 / 六百二十六）当时都写了"本条不重新发布"——意思是当时没发版。现按用户要求把这三条直接**覆盖进 v1.2.3 的附件**，所以那三条里"附件仍是原样"的说法作废，以本条为准。





· **重传后的附件** = v1.2.3 首发内容（实测五百九十五 ~ 六百二十三）+ 六百二十四（巨型文件拆分、删死代码，玩家可见行为零变化）+ 六百二十五（只带拔刀剑/弹幕的女仆不再被自主战斗切进法术模式）+ 六百二十六（手册那句过头话的更正）。**本模组不会自动更新，装的是哪个 jar 就是哪个版本**。





· **玩家侧唯一可见变化**：女仆只带拔刀剑（或妖怪归乡的弹幕/激光/符卡）时，不会再被自主战斗切进「近战法术 / 远程法术」；要旧口径就把配置 `combat.spellGearIgnore` 留空。文件名与放置位置不变（mods 里覆盖同名 jar），存档无需任何处理。





【实测六百二十六】更正手册里一句过头话：「女仆喂食繁殖不再有任何击杀行为」（繁殖模式里本体自己的「超过上限就杀」仍在；本模组一行代码未改，不重新发布）





· **反馈**：用户看着女仆界面「繁殖动物」任务的提示问"繁殖动物界面还是带上了老版本的宰杀文本描述"——提示第二行是「主手持有攻击性武器（用于杀死超过上限的动物）」。





· **查证：那条提示不是旧版残留，它现在依然成立，也不是本模组的文本**。①该提示属于车万女仆本体（`task.touhou_little_maid.feed_animal.condition.assault_weapon`），1.20.1 与 1.21.1 两版一字不差，本模组从未定义过这个键；②本体 `hasAssaultWeapon(maid)` = 主手物品带攻击伤害属性，正是提示里那半句；③本体 `findFirstValidAttackTarget(maid)` 统计附近同种动物，**数量顶到「繁殖动物的最大数量」−2 时才返回一只目标**，④且这个方法在线——javap -v 的 BootstrapMethods 实证：`createBrainTasks` 里 `StartAttacking` 的目标查找器就是它，配套 `MaidMeleeAttack` 与 `StopAttackingIfTargetInvalid`（手里有武器且目标不远）执行击杀。⇒ 本体繁殖模式确实会杀超上限的动物。





· **真正过时的是我们自己手册那句**：工作章原写「早期版本的『畜牧数量控制（杀幼保成）』已在 v1.5.291 整体移除——女仆喂食繁殖不再有任何击杀行为」。前半句对（我们确实把自己的 MaidFeedAnimalCapMixin 连同配置项与面板行整体删了），后半句是过头话——我们删的是**我们自己的加码**，露出的是本体本来就有的「超上限就杀」。已两树同步改正：本模组不再碰繁殖模式，但本体自己的「超过上限就杀」还在（提示里那条是真事），不想让她动手就别往她主手放武器、或把上限调高。





· **本模组一行代码未改**（`mixins.promaid.json` 无畜牧 mixin、配置无 `animalCap*`，v1.5.291 已删干净，本次复核确认），也没有去改本体的文本——那是本体的准确说明。两树 `javac` 0 错误、两个 jar 重新打包校验通过。**不重新发布**。





【实测六百二十五】法术任务的「她会用法术吗」闸门排掉拔刀剑/弹幕（修「只给了把拔刀剑的女仆被自主战斗切去法术模式」；本条不重新发布）





· **反馈原文**（粉丝私信）："包里没有法术书却仍然显示法术类的切换选项，可以确定包里主手副手是完全没有任何魔法类道具的。而那个自主战斗切到法术是真的。"——拆成两件事查，第一件不是我们的，第二件是我们的。





· **第一件（不改）**：那两个法术任务是《车万女仆：万法皆通》注册的，它的 `isEnable(EntityMaid)` **恒返回 true**（javap 反编译：方法体只有 iconst_1; ireturn），它自己那个 `hasSpellBook(maid)` 同样是空壳恒真；TLM 女仆界面列任务走 `TaskManager.getNotHiddenTaskList`（调用方 `AbstractMaidContainerGui`），**只按 `isHidden` 过滤**。所以**装了附属就一直显示这两项**，与背包无关，也与本模组无关（本模组从不拦手动切换任务）。





· **第二件（已修）**：自主参战对法术任务的闸门，判"她会不会用法术"时问的是附属各前置 provider 的 `isSpellBook`——其中两个认的"法术装备"**本身就是武器**：`slashblade` 认**拔刀剑**（`ItemSlashBlade`）、`youkaishomecoming` 认**弹幕/激光/符卡**（`DanmakuItem`/`LaserItem`，TLM 原版就有 danmaku_attack）。旧口径 = "背包里有把拔刀剑 = 她带着法术书" → 法术任务进候选池 +「模组任务优先让位」把原版任务整体挤掉 → 她就被切去法术模式（实测三百七十九 同款反馈的第二次）。





· **改法**：新增配置 `combat.spellGearIgnore`（默认 `slashblade, youkaishomecoming`，逗号/空白分隔的 provider id = 前置模组 modId），列进去的 provider 认的物品不再算"她会用法术"；面板「战斗与自保 → 主动参战 → **法术装备忽略表**」一行；**留空 = 旧口径**。排查：`latest.log` 搜 `combat pools`，池里有法术任务时会附一行 `spellGear=`，直接点名是哪件东西放行的（形如 `slashblade:slashblade:slashblade`，或 `addon-data`）。手册「战斗与自保」章新增第十一 / 十二节讲这两件事。





· **只影响这一处**：法术任务能否被**自主切换**选中。手动切换、空袭施法、位移法术、友军风免等既有链路一字未动；忽略 `slashblade` 后，拿拔刀剑的女仆照旧走她自己的拔刀剑战斗模式，只是不再被拉去法术模式。





· **验证**：两树全量重编 **0 错误**、两个 jar 重新打包校验通过（类齐全 / 语言文件正常 / mixins 齐全）。**本条不重新发布**——GitHub 附件仍是 v1.2.3 原样；未跑游戏内实测（按约定测试留给用户）。





【实测六百二十四】内部大扫除：拆分超大源码文件 + 清理死代码（**玩家可见行为零变化**，本条不重新发布）





· **为什么做**：到 v1.2.3 为止，几个源文件已经涨到「读一次要滚几千行」（蓝图库 6677 行、配置面板 4647 行、自保行为 4392 行），改一行动辄要翻几千行。本条**只搬文件**：不改逻辑、不改配置项名、不改任何玩家可见文案与数值。





· **拆了什么（两树同步）**：蓝图库 6677 → **570** 行（拆出 14 个类：方块分类表 / 旧版 id 兼容层 / 结构文件解码 / 外部文件扫描 / 世界提取 / 目录描述 / 步骤几何 / 方块形状材质 / 材料统计 / 机器识别 / 宽松替代 / 中文名表 / 机器收尾 / 放置处理）；轰炸 2733 → **1058**（拆出配置读取 / 物品识别 / 爆炸来源 / 放置选点 / 投掷弹道 / 战斗节拍 6 个类）；蓝图网络包 2618 → **854**（拆成建造包与实体包两个容器）；手册正文 2090 → **194**（按主题拆成入门/生产/战斗/飞行/系统 5 个章节文件）；排班网络包 1265 → **约 350**（拆成排班数据包与女仆操作包）。主动参战 2000 → **783**（拆出「威胁判定」与「自动参战任务池」两个类）。





· **外部调用点一行都没改**：公开方法在原类留**同签名转发**、公开常量表留**同名字段别名**，所以 `BlueprintLib.canBreak(...)`、`BlueprintLib.FORBIDDEN` 这类写法照旧可用；搬走的网络包类，全树 7 个文件里的 `BlueprintBookNetworking.X` 写法（含 `import` 全限定与方法引用）已自动改写。手册正文、中文提示、配置键名**逐字未改**。





· **删了什么**：蓝图库里 5 个早已废弃的内置预设方法（小屋/凉亭/喷泉/瞭望塔/水井——内置预设早已全部移除，这五个全仓库零调用）；`PersonaSyncManager`（写完从没接线的类，零引用，所以「人设同步」实际上一直没有代码在跑）。





· **验证**：两树全量重编 **0 错误**，两个 jar 重新打包校验通过（类齐全 / 语言文件正常 / mixins 齐全）。**本条不重新发布**——GitHub 上的下载附件仍是 v1.2.3 原样，游戏内行为与 v1.2.3 完全一致。





· **没拆的**：配置面板 / 蓝图界面 / 自保行为 / 各类工作行为等文件，静态成员只占 6%~47%，主体是绑在 `this` 上的实例方法，机械搬运会断链，要拆得重新设计状态归属；`MaidSmartConfig` 里 1479 行是 static 初始化块，`public static final` 配置项按语言规则只能在本类初始化，搬走即编译不过。





【实测六百二十三】版本号升为 v1.2.3（正式发布 · 加急）：先修「收放魂符丢血量上限与装备」，再带上 v1.2.2 之后攒下的一批新功能





· **为什么加急**：B 站用户 lawte 反馈「女仆由好感度升阶带来的血量上限加成、以及身上穿的装备，会因为收放魂符被卡掉」，GitHub issue #18（P1aneZ）也报了同一条（魂符放出后从她背包/装备栏取物会丢失）。这是会**吃掉玩家存档里实打实物品**的严重 bug。两条的根因与修复其实早已在仓库里做完、只是没进过任何一次 release 附件——**血量上限掉回 20**（实测五百九十五：我们自己的「入世界自动补包」new 出全新客户端实体却没补属性表，原版只在"开始追踪"那一枪发属性表）、**装备被吞**（实测五百九十六：收放魂符的「放出」本身就是一次入世界，于是每收放一次客户端那只女仆就被销毁重建，你正开着的容器还指着已被删掉的那只）。v1.2.2 的 release 附件比这两条修复早约 12 小时上传，所以从 release 下到的人仍会踩到。同类还有 issue #17（着火连吃金苹果到背包空，实测六百一十九已修）。





· **这一版都更新了什么（v1.2.2 → v1.2.3：实测五百九十五 ~ 六百二十三）**——**修复**：收放魂符丢血量上限/装备、着火连吃金苹果（issue #17）、法术冷却被多乘 20 倍（issue #16）、图纸三条（issue #12/#14/#15）、回收方块整体还她（萤石不再变粉）、"拿着羽扇却报缺飞行道具"假提示、坐下还在搭路、无主女仆一概不碰、**面板字符串输入行一直没画标签**（六百二十二）；**新功能**：**战斗模式分类表**（逐任务点名近战/远程/不参与 + 可关掉的「模组任务优先让位」+ `/maid_smart combat modes`）、**压缩盒**（5 格 × 每格 114514，放进她背包 = 背包延伸；禁入清单 + 箱子式界面 + 常驻附魔光效）、**空袭轰炸**（末地水晶/重生锚/床/投掷 TNT，三段并行，默认不破坏方块、不伤主人友军、火改粉色且不烧玩家与女仆）、**飞行跟随**（主人飞走了她背鞘翅追过来，默认关）、**散步速度终于调得动**（默认 0.7→0.4、下限 0.3→0.05，配 `/maid_smart stroll check|go|speed`）、**索敌两处调整**（隔着方块的怪不再算威胁 + 战斗时工作范围临时扩圈）、副手动作表现推广到全链路、空袭 34 项数值可调与俯冲段冲刺加速；**删掉**：排班表「全员在家」整条移除（注册号留空不补位）。





· **手册正文同步（两树）**：「这是什么」章的版本表述改成**两版本对齐**（1.20.1 Forge / 1.21.1 NeoForge），并补上 **v1.2.2 起 / v1.2.3 起两段新增功能速览**（此前只有 v1.1.0 与 v1.2.0 两段）；「战斗与自保」章新增一条 **【收放魂符后血量上限掉回 20 / 拖她的装备被吞？】**（症状 / 根因 / 改法 / 诊断入口「装备离开」）；「专业术语」章补 7 条（空袭轰炸、粉色火焰、飞行跟随、压缩盒、战斗模式分类表、散步速度倍率等）。**README 首页**：新增「v1.2.3 加急修复」一节（含 issue 链接）、空袭轰炸与压缩盒两节、散步速度对照表与三个新命令，章节数 32 → 33、面板小类数 28 → 31。





· **战斗时临时扩圈默认值 32 → 15**（用户指定，本批唯一的行为变化）：追怪时的工作范围圈原本默认 32 格（默认工作范围才 12 格，等于放她跑到两个半工位以外接战），改成 **15**——够追出工位、又不至于被拽进几十格外的乱战。新档生效；老档 toml 里已写着 32 的不会被改，想跟着调小可在「战斗与自保 → 主动参战」改。


· **版本号**：mods.toml ×3（含游戏内模组列表描述里的一句本版摘要）、两树构建脚本、部署/测试/校验脚本共 32 个文件 97 处 jar 名、README / PUBLISH_GUIDE / GIT_SYNC 与两份 release 说明一并同步；本批**只有版本号与文档字符串改动，无行为变化**。





· **验证**：两树 `javac` 0 错误、两树打包成功（`promaid-1.2.3.jar` / `promaid-1.2.3-neoforge-1.21.1.jar`，jar 内 `mods.toml` 版本为 1.2.3）、`verify_jar_classes` 全过、语言 JSON 校验通过、两树本文件逐字节一致；部署三个目标字节一致。按用户要求**测试交回**——发布后请重点复看「收放魂符后血量上限与装备」与「面板主动战斗两行」。





【实测六百二十二】面板的「字符串输入行」一直没画标签与说明——新加的「模式分类表」因此整页只剩一个黑框





· 用户反馈（附截图）："战斗模式分类表根本看不到在哪，似乎根本就没做出来。" 截图里那一页只有一个没有标题的黑色输入框。





· **根因是两件事叠加**：①面板行定义有七种，**渲染侧的分支链唯独漏了 TextRow**（字符串输入行）——`init` 照常建输入框、行高照常按注释折行留出，渲染侧却连标签带说明一个字都不画，于是只剩一个空黑框；这个洞是旧版就有的（TextRow 是 v1.5.127 加的），既有那几条字符串行（API 地址/密钥/模型、导入路径、提供高度/速度的法术表、危险方块表、禁入清单）平时夹在别的行中间、看着又像"没做完"，一直没人点名。②我 六百二十一 给这条行的注释写了约 250 字（折 5 行、行高 84px），用户那台窗口每页只装得下 1~2 行 → 这条行**独占一页**，而它又恰好是唯一不画标签的类型 → 整页只有一个黑框。





· **改法**：渲染链补上 TextRow 分支（标签 + 注释，与 NumRow 同款布局，两树同步）——顺带把上面那几条老字符串行的标签一起修好；两行新配置（模式分类表 / 模组任务优先让位）的面板注释压到 2 行以内，长说明只留在配置注释、手册与 `/maid_smart combat modes` 的输出里。





· **验证**：两树 `javac` 0 错误、两树打包成功（747 / 749 项）；未跑用例（纯客户端渲染问题，自动化看不到）。复看：打开「战斗与自保 → 主动参战」，应能看到「模组任务优先让位」与「模式分类表」（标签 + 蓝字说明 + 输入框）两行。





【实测六百二十一】战斗模式分类表（哪些算近战/远程、哪些参与自主切换，逐任务点名）+ 可关掉「模组任务优先让位」+ 删掉排班表「全员在家」+ 清掉金苹果冷却那处过时注释





· 用户原话："然后在主动参战面板里面加一个配置项目，可以配置哪些模式属于近战或者远程,然后确认这些模式哪些参与自主切换，目前是默认都能参与，模组优先。有人认为这个逻辑太笼统了。然后删除排班表的全员在家选项。" 另有一条顺带指出的不一致：`SelfPreservationBehavior` 里按药水种类记的 CD（`potionCds`）注释把 `golden_apple` 写成会走这套 CD 的键，实际没有任何地方读它。





· **"太笼统"是实情**：入战选任务与战中换战术共用一份候选池，规则只有三条且**一条都不由玩家决定**——①谁能进池 = 任务自己认的武器在她背包里 + 几道硬门（模组任务要有非原版物品背书、枪械要有弹药、法术任务要有法术装备、第三方玩法模式黑名单）；②算近战还是远程 = 靠 UID 关键词与命名空间**猜**（`ranged/gun/danmaku/spell/crossbow/trident/bow` 与 `maidspell` 命名空间算远程，`ef_tlm/slashblade/sbr_core/truepower` 算近战，**其余未知模组任务一律当近战兜底**）；③池里有模组专属任务时原版通用五件套**整体让位**（"模组优先"）。装了冷门模组的人只能看着远程任务被当近战冲脸、看着某个模式被整体挤掉，而这两件事在面板里一个字都改不了。





· **新增「战斗模式分类表」**（配置 `combat.taskModes`，默认空）：一行一条 `任务UID=近战/远程/不参与`（认中文写法与逗号分隔）。写 `近战`/`远程` = 分类以表为准、并且这个任务**不再被"模组优先让位"挤掉**（你点名的优先）；写 `不参与` = **入战选任务与战中换战术两条路都永不选它**（想让它只在你手动指派时上场就用这档）；**留空 = 与旧版一字不差**。表里没写的任务照旧走内置推断与那几道参与门——写"参与"也不保证被选中，她还必须手里有那个任务认的武器/弹药/法术装备。





· **「模组任务优先让位」做成开关**（配置 `combat.vanillaYieldToMod`，默认开 = 旧行为）：关掉 = 原版与模组同池纯按权重随机（原版/模组两条权重照旧生效）。觉得"模组优先"太笼统就关它，再在表里逐个点名。





· **查询入口 `/maid_smart combat modes`**（只读）：把当前服务器上**所有攻击类任务**列成一张表——内置算什么 ｜ 现在算什么 ｜ 表里写了什么 ｜ 参不参与，抄一行改一改就是表项（UID 在游戏里没有别的地方能看到）；输出同时进 `logs/promaid.log`（分类「战斗分类表」）。





· **面板两行**（战斗与自保 → 主动参战）：「模组任务优先让位」（开关）与「模式分类表」（文本行，**非法行整串拒收**并保留旧值——缺 `=`、缺命名空间、取值不认的都算非法）；语言文件补两条。





· **删掉排班表「全员在家」**（用户裁定）：列表页那个按钮、它的计数逻辑、以及 C2S 的 `BatchHomePacket` 与其注册（index 13）整条移除——一键把全队留在家不跟随，误伤面太大；注册号**留空不补位**（注册号是双方约定的固定槽位）；翻页按钮随之回到下方中间。**单只女仆的在家开关照旧**（详情页，实测三百四十二）。





· **顺手改掉手册里一句已经被实测一百零七作废的话**："仅原版任务参与互换（实测八十六）"——一百零七 起模组任务双向参与，"谁不参与"现在由分类表说了算。另把 `potionCds` 的类注释改对：`golden_apple` 那条键**从来没人读过**（着火连吃的根因），实测六百一十九 已把它改成两条吃苹果路径共用的**持久化**冷却（CD = 该苹果自己的效果时长）；不并回那张表的原因是本表是**实例字段**，脑重建/切任务会连冷却一起丢，而"她正烧着"往往就是切任务的时刻。`honey`/`milk` 是真的在走这张表，照旧不动。





· **本批验证到哪一步**：按用户要求测试交回——**未跑自动化用例、未开专用服务器实测**。已做的只有静态几项：两树 `javac` **0 错误**；两树打包成功并逐类核对（`promaid-1.2.2.jar` 9783849 字节 747 项、`promaid-1.2.2-neoforge-1.21.1.jar` 9805353 字节 749 项；语言 JSON 校验通过）；`BatchHomePacket` 两树已无任何引用；新命令按"与 `check`/`box` 同级挂在 `combat` 下"核对过括号平衡（是 `combat modes` 而不是 `maidsmart modes`）；分类表在选任务循环里被逐任务查询，故加了"配置内容变才重建"的缓存。待复跑：`/maid_smart combat modes` 输出、面板两行当场改并落盘、排班表按钮确实没了，以及 六百二十 遗留的两条断言。





【实测六百二十】空闲散步速度终于调得动了（默认 0.7→0.4、下限放到 0.05、给了现场量速入口）+ 压缩盒禁入清单（附魔书/附魔武器/压缩盒，鼠标也选不中）





· 用户原话："1.女仆空闲散步移动速度调不了，0.1倍速都跟快步跑一样 2.加一个黑名单，在压缩盒界面内无法放入附魔书/附魔武器和压缩盒，压缩盒在这个界面内无法被鼠标选中，并再次提示玩家不能把压缩盒放进压缩袋里。"





· **散步速度"调不了"的两半原因**：那条倍率**乘在女仆的基础移动速度属性上**（`MaidMoveControl` 走的就是原版 `setSpeed(倍率 × MOVEMENT_SPEED)`），而 TLM 没动过这个属性——它是原版 `LivingEntity` 的默认值 **0.7**（玩家 0.1、僵尸 0.23、村民 0.5；javap 实证全 TLM 只有 5 个类引用它，除 `MaidMoveControl` 就是扫帚/妖精，没有一处给女仆加修正）。**实测**（专用服务器量她走一段路的平均格/秒；玩家走路 4.32、跑步 5.61）：0.1~0.2 → 0.1（几乎不走）、0.3 → 1.9、0.4 → 3.3、0.5 → 4.9、0.6 → 6、**0.7 → 8**、1.0 → 14。所以：①**老默认 0.7 实测约 8 格/秒 = 比玩家跑步还快**，用户看到的"快步跑"就是这个数（只改默认值对老档毫无作用——老 toml 里写着 0.7，框架不会再取默认值，所以这一批加了**一次性迁移**：还停在旧默认 0.7 的自动改成 0.4，之后玩家想写回 0.7 随便）；②**老下限 0.3 就是"能调到的最慢值"**，想调慢的人被卡死，现在放到 0.05。另外实测清了一件事：**实际格/秒与倍率不成线性**（慢到一定程度她一步一顿，寻路每格重新判定），0.2 及以下几乎都是 0.1——拿"属性 × 倍率"算出来的数只会误导人，所以自检与配置里给的都是**量出来的**对照表。





· **新增现场调试入口**（"调不了"另一half 是试起来太麻烦）：`/maid_smart stroll check [女仆]` 打她**自己**的移速属性（万一装了加移速的装备/模组一眼看出）、实测参考表与口诀、当前散步门禁（逐条念出被哪条挡着）、当前移动目标的速度修正，以及上一次测量的读数；`/maid_smart stroll go [女仆]` 用当前倍率让她走一次 24 格直线；`/maid_smart stroll speed <值>` 当场改倍率并写进配置文件（越界**在命令里就夹住**并回报"实际生效为 X"，不会出现"界面写着 0.005、重启变 0.05"两套数）。读数给两个：全程均值 + 起步 1 秒后的**巡航窗口**速度（起步加速段不算进去），到点即冻结。





· **压缩盒禁入清单**（"加一个黑名单"）：新增一个判据给**四条路**用（界面点击 / 服务端搬运 / 女仆背包插入 / 溢出回退），禁入三类——**压缩盒本身**（老口径，文案不变）、**带附魔的物品**（用户点名的附魔书/附魔武器，含附魔工具与盔甲；附魔书按**物品类型**判——1.20.1 它的附魔在 `StoredEnchantments`、1.21.1 在 `STORED_ENCHANTMENTS`，两版的 `isEnchanted()` 都看不见书）、**配置禁入清单**（新配置 `refuseEnchanted` 默认开，`refuseList` 默认空，可自己点名加，如 `minecraft:gunpowder`）。





· **"压缩盒在这个界面里无法被鼠标选中"**：界面里点背包中的压缩盒**既不拿起、也不和手上的东西交换**，只回一句「压缩盒不能装进压缩盒」（以前能拿起来挂鼠标上，之后每次点击都被服务端挡掉，看着像界面坏了）；服务端同样挡。**拒绝要给话**：服务端每次点击都记一句理由随内容同步发给界面画成红字（协议 2→3），"配置清单"这种只有服务端知道的判据也能把理由说给玩家听；悬停说明直接列出"放不进"的原因，压缩盒还多一行"这个界面里鼠标选不中它"。女仆那一侧口径一致：她捡到附魔书不会再"顺手塞进盒子"。





· **实测与自检**：`/maid_smart box check [女仆]` 新增「禁入清单」段（判据三种拒绝 + 石头放行对照、数据层 `mergeInto`/`merge` 退回、界面那条路 Shift+左键被拒、配置清单当场加一条再清空、鼠标选不中压缩盒两条路、女仆那侧 `isItemValid`），并把 六百一十八 那几条"拿附魔书当不可堆叠探针"的回归改成**临时关掉开关再跑**（口径变了不是坏了），跑完还原。用例 `test_box620.py` / `test_stroll620.py` 双服跑：散步速度两支全绿（0.15/0.4/0.7 三档实测速度单调上升、约 0.45 / 3.2 / 9.6 格/秒，`speed 0.005` 夹到 0.05 并落盘），压缩盒在 neo 上全绿（含"老档 strollSpeed = 0.7 起来后被迁到 0.4"这条端到端迁移）；Forge 侧那支只剩"老档迁移"一条断言没读到 0.4（已在迁移里加了日记，待复跑定位）。回归 19 例 17 条绿，另两条是测试现场状态（Forge 侧迁移断言、六百一十九 扩圈自检把测试女仆判成"刚挨过打"）。





【实测六百一十九】索敌：隔着方块的怪不再算威胁 + 战斗时临时扩圈 + 着火的金苹果不再吃到背包空





· 用户原话："1.用了几天感觉索敌还是有一点点问题 在地面下面有空洞里面有僵尸 酒狐隔着方块就感知到了 但是因为没有可以下去的入口所以就会开始原地打转 或许改成有方块遮挡的怪不能被感知到会好一些？还有近战战斗时由于超出工作范围而被传送回来然后就这么来回循环 感觉可以改成战斗状态临时扩圈把范围改大些回到常态在用正常设置的工作范围 2.https://github.com/fadersketch/Promaid-mod/issues/17 反馈的问题"（#17：女仆着火时会把背包里的金苹果瞬间全部吃完）





· **隔着方块的怪不再算威胁**：本模组的威胁驱动（每 0.5 秒扫 16 格内"`getTarget()` 正是主人或她"的怪 → 写进 brain 的 `ATTACK_TARGET`）此前**只按距离与锁定关系**；而原版所有"这个实体我可见吗"的判据（`Sensor.m_26803_`）是"**它就是我的攻击目标 → 跳过视线判定**"。于是我们自己写进去的目标在**全部下游链路里都变成看得见**：TLM 的走位追人、TLM 近战那条 `nearestVisibleLivingEntities.contains(target)`（隔着墙挥刀）全被绕过——追不到的怪（地下空洞、墙那头）就成了"原地打转"。现在威胁候选加一条**视线门**（与原版 `hasLineOfSight` 同口径：只挡实体方块、不看流体）：被挡住的怪**根本不会被写进攻击目标**，那条绕过链从源头断开。





· **战斗时临时扩圈**：TLM 的 `SchedulePos.tick` 每 40 tick 检查一次——「离圈心超过 **(int)工作半径 + 4** 格就直接**传送回工位**」，没超就"走回去"。排班/在家模式（不跟随）下追怪的近战女仆因此被反复拽回原地（追出去→传送回来→再追出去，仗永远打不完）。现在把**读数**改掉：`EntityMaid.getRestrictRadius` 的出口在**接战期间**返回 `max(常态半径, 战斗时临时扩圈)`（新配置项，默认 32 格，0 = 关闭）——圈内判定、寻路节点评估、上面那条传送阈值、各任务"离家多远该回去"**全都读这一个值**，一处改完全线一致；**战斗结束（目标清掉 / 挨打后 5 秒）自动落回**你设置的正常工作范围，不需要任何收尾操作。取 `max` 不缩圈、不动存储、客户端照旧画常态圈（参数在「战斗与自保 → 主动参战」小节）。





· **着火不再把金苹果吃到背包空**（issue #17）：着火链路每 tick 调一次吃金苹果，只问了"喂食冷却"那张表——而那张表**只在物品没被消耗时才写**（遗物类），普通金苹果是普通食物、吃完就少一个 → 永远不写 → 每 tick 都答"没冷却" → 一整组几十个几秒清空。现在两条吃金苹果的路（岩浆/着火兜底、低血回血阶梯）**共用一条冷却，CD = 它自己给的效果时长**（普通 = 吸收 2 分钟，附魔 = 抗性/抗火 5 分钟），一次一颗、效果耗尽才会再吃；冷却存**实体持久数据**（脑重建/切任务/重载都还在，实例字段会丢）。





· **实测（专用服务器 + 游戏内自检 `/maid_smart combat check [女仆]`，只读、摆出来的场景用完复原）**：金苹果那条——**修之前**点火 1 秒后 16 → 0（对照组 16 个不动）；**修之后** 16 → **15**（只吃 1 个），自检读数「冷却剩余 2056 tick」；索敌那条——墙在时"看不见 + 不算威胁 + 扫描不选它"，**拆墙同一只僵尸立刻翻转**，且"没锁定我方"与"远在 40 格外"两条对照都不算威胁；扩圈那条——常态圈读数 8.0、离圈心 10 格在圈外，**脑里真写一条攻击目标后半径变 32.0、10 格进圈、传送阈值从 12 格抬到 36 格**，清掉目标立刻落回 8.0 / 出圈。回归 15 条里 13 条一次过、两条（压缩盒 neo / 飞行 612）是抖动、单独重跑均绿。





【实测六百一十八】压缩盒：她终于吃盒子里的饭 + 附魔物品不再消失 + 界面改成箱子式鼠标取放 + 常驻附魔光效





· 用户原话："1.我发现一件事，往压缩盒里面加食物，女仆是不会去吃的。只是往里面加弹药以及TNT这些会认。2.对于附魔类的物品似乎有一些bug，存入会导致物品直接消失。3.这个存入目前跟玩家的认知不太一样…我想的是可以像往箱子存东西一样，玩家可以通过用鼠标的方式将物品拖进去。直接照搬往箱子存东西的代码就行。4.给压缩盒打上附魔的特效。这个…排班表中已经做过了，直接照搬即可。"





· **她终于吃盒子里的饭**：女仆"自己吃饭"走的是 TLM 的另一条入口（`getAvailableBackpackInv`，读的是背包**字段**、按背包等级截到 6/12/24/36 格），六百一十六那批只注入了 `getMaidInv()`——所以盒子在那条路上根本不存在，她只认走自己那条路的弹药/TNT。现在把盒子**接在那两条路的最后**（照抄 TLM 自己的构造：类型还是它的 `MaidInvWrapper`，第三方模组的"女仆要东西"事件照发；背包里没有盒子时一行都不多做）。挤奶/剪羊毛/喂动物/采蜜/插火把/取箭这些也一并看得见盒子了。





· **附魔物品不再消失**：不可堆叠的东西（附魔书、附魔工具/甲、药水）一格能存好几个，而原版上限是 1——以前她的"视野"一律按 64 封顶，于是原版拿到一个"2 个的附魔书"这种**非法堆**，按上限只收下 1 个、剩下的**当返回值退回**，而 TLM 那几十处调用点不看返回值 = 那 1 个凭空没了。现在改成 `min(64, 物品自己的堆叠上限)`：**交给她/原版的永远是合法堆**。





· **界面改成箱子式鼠标取放**（用户要的"照搬往箱子里存东西的代码"）：鼠标上挂着一叠——左键点自己的背包格拿起、再点盒子格放下（塞不下的继续挂在手上）；右键拿 1/放 1（在自己背包格上按原版拿一半）；Shift+左键快速移动（背包格 → 盒子整叠、盒子格 → 背包最多 64）；手上有东西点自己的格子可合并/放下/交换；点空白处、或关界面（ESC）时手上的东西**还回背包**。那一叠的真身在服务端，关界面、换手、掉线、甚至每 tick 的兜底都会把它放回背包——**任何路径都不会丢**。顺带修掉"界面开着滚一下滚轮就把手上的盒子换走了"（以前会看着像界面坏了）。





· **常驻附魔光效**：排班表／手册早就有的那层流光（重写 `isFoil` 恒 true，逐字同源），压缩盒也加上了；物品本身不加附魔（附魔会影响铁砧/交易/经验修补，这里只要外观）。





· **顺带修掉的两个数**：界面上盒子每一格的数字、光标上那一叠的数量以前**全都多 1**（网络回读时用错了方法：写的是 grow 不是 setCount；1.21 那棵树一直是对的，这一批两树口径对齐）；另外"盒子里 5 个石头"曾经在她眼里变成"64 个"（视野上限写成了"设成上限"，而不是"不超过 min(真实数量, 上限)"）——被自检当场打红。





· 验证：`test_box618.py` 在 1201 与 neoforge1211 两台专用服各跑一遍；游戏内自检 `/maid_smart box check <女仆>` 扩到 27 条全 PASS（附魔合法堆、鼠标取放①~⑥、六百一十七那批的回归也都在）；行为面证据：给女仆一个装着 114514 个石头／16 份熟牛肉／8 个 TNT／32 根光谱箭的压缩盒、把她打到 8 点血，她自己把盒子里的熟牛肉吃掉、血量一路回到满（一份熟牛肉治好约 4.16 点，与她"自己吃"的公式逐行同源）。


【实测六百一十七】压缩盒：贴图改樱花粉 + 界面里背包格照原版显示数量与耐久条 + 修「盒子能把自己装进去被卡掉」





· 用户原话："1.将这个物品的贴图外观改成粉色调（参考樱花木板色调）2.点进这个界面的时候，最好跟玩家的物品栏一样，可以显示这个物品的实时状态：也就是有多少个，它的耐久是有多少，玩家看到的就跟玩家生存时的物品栏一样。3.存在一个bug，就是收纳箱可以把自己装进去，导致被卡掉。"





· **压缩盒贴图换成樱花木板那一组粉**：先量了 1.20.1 原版 `cherry_planks`（樱花木板）这张贴图的真实调色板（最亮 `#E7CAC5`、木纹 `#CD8580`，色相 4~9°、饱和 0.44~0.53），再**保住原图每个像素的明暗关系**、只把颜色映射过去——是"同一只箱子换了木料"，不是重画一张。**锁扣仍是金色**（用户没要求改；而且全粉的箱子太空，锁扣是图上唯一的对比点）。





· **界面里每个背包格现在和生存物品栏一模一样**：格子里**画数量、画耐久条**。数量与耐久条不是我们照猫画虎画的，是**直接调原版那两个绘制方法**（`renderItemDecorations`）——就是生存物品栏用的同一段代码，所以是逐像素一致（1 个的数量不写）。盒子里那 5 格的数量仍然是格子下面那行大字（原版右下角那种小字放不下六位数），但**耐久条照画**。**悬停提示**也跟着补齐：背包格现在也显示名字、数量、耐久；按住 Shift 会告诉你这一下会发生什么（「Shift+左键存入压缩盒」；指到盒子那一格则是红色的「压缩盒不能装进压缩盒」）。





· **修掉「压缩盒能把自己装进去、导致被卡掉」**：以前**把盒子 Shift 存进它自己**（界面里"打开的盒子"就是背包第 0 格那个盒子）是**能存进去**的，存完以后盒子里装着盒子——打开只看见一层、里面的东西再也取不出来。现在**三层一起拦**：数据层（所有写入都经过的那一个方法）开头就把它原样退回；服务端在**取出之前**就拒（不会出现"东西拿出去了又被拒"的一瞬间）；女仆那一侧（她能往盒子里放东西的那条路）同样拒；客户端界面**连包都不发**。





· **顺带查出并修掉两个真 bug（不是这一批要做的，是自检逼出来的）**：


  ① **盒子里所有东西的数量都多 1**——"设数量"那一句用错了方法（写成"加数量"了）。写 3 个石头读出来是 4 个；六百一十六那批的烟花写 114514 读出来是 114515。**上一批的用例为什么没发现**：它那条写的是"不少于 114514"（松断言），多的那 1 个正好被放行——本批把那条**收紧成相等**。


  ② 自检自己踩的坑：假玩家"把东西拿在手上"用的那个方法在 1.20.1 里是**空实现**（写了不动），所以整条点击路径第一次是全红的；换成真正的方法之后才真的在测。





· **怎么验证（新增 `/maid_smart box check <女仆>`，游戏内自检）**：把这一批修的那条路——"界面点击"——做成了一条能在游戏里跑的检查。**为什么要这么做**：专用服务器上没有客户端、点不了界面，六百一十六那批就是在这里栽过（改的是点击那条路，跑的却是"她看不看得见盒子"）。这条自检用假玩家在服务端**把界面点击真正调用的那个方法原样走一遍**，逐条打印 `[PASS]/[FAIL]`，其中包括**用户报的那一下**（Shift+点自己那一格 → 被拒、盒子还在手上、里面东西不变），并且配了两条**对照**（同一个动作存普通物品**必须成功**——不然"全被拒"也能骗过测试）和一条**对账**（两个盒子合计 15 个，没东西凭空消失）。1201 与 NeoForge 1.21.1 两台专用服实测**各 12/12 全过**。





【实测六百一十六】压缩盒（5 格 × 每格 114514，放进女仆背包 = 她背包的延伸）+ 面板里飞行跟随那一档的描述压短





· 用户原话："1.功能测试全部通过！现在在进行一个小调整。截图这一页对于飞行跟随的描述，字数太多了，直接撑满屏幕。需要精简化。2.加入一个新道具，压缩盒。建模如图。整体机制和潜影箱差不多。但不同的是，将这个箱子放进女仆的背包里面，女仆可以从这个里面拿东西，这个箱子里面的内容会被视为女仆背包的延伸。而且这个箱子里面物品堆叠上限大大增加，也就是说不再是只能堆64个，而是可以堆114514个。合成：要在中间放一个粉色染料。第2，4，6，8格放钻石，剩下格子放置粉色樱花木板在工作台内合成。虽然堆叠上限很高，但它的格子数量只有5个。"





· **面板那一页不再撑满屏幕**：飞行跟随的说明从 868 字压到 344 字（其余四条一起收短），保留"默认关、四个起飞条件、收手时消掉推进矢量、升版要重开一次开关"，把动作细节与来龙去脉挪进手册——那一行末尾现在写「完整判定、调试入口与细则见手册」（配置文件的注释与手册正文里的完整版**一字未删**）。





· **新道具「压缩盒」**：工作台里**中间放粉色染料、上下左右（第 2/4/6/8 格）放钻石、四个角放樱花木板**合成。**5 格**，默认**每格能堆 114514 个**（可在「系统与杂项 → 压缩盒 → 每格上限」里改，64~1000000）。**拿在手上右键**就打开它的界面（不用像潜影箱那样先放下）。





· **放进女仆背包 = 她背包的延伸**：盒子在她背包里时，她找材料、拿食物、取建材都会**先看盒子里有没有**（每个盒子各贡献 5 格）。**存进去不封顶**（她捡到的东西会并进那一堆，一直堆到每格上限），**拿出来一次最多 64**——这不是小气：1.20.1 的存档把物品数量写成 1 个字节，114514 整堆塞进她的背包格会被截断成 82（等于凭空蒸发），所以她的「视野」必须封顶 64。想知道她到底看得见什么：**`/maid_smart box <女仆>`** 会把每一格「看得见多少 / 实际多少」打出来（例如「看得见 ×64，实际 ×114514」）。开关在「系统与杂项 → 压缩盒 → 女仆背包延伸」（默认开）。





· **界面怎么点**（不是原版箱子界面，没有"光标上那一叠"，所以是点而不是拖）：**左键点盒子格 = 取 64 个**；**右键 = 取 1 个**；**Shift+左键 = 整格取**（背包塞不下就停，剩下的留在盒子里）；**Shift+左键点你自己的背包格 = 把那一叠存进盒子**（Shift+右键只存 1 个）。数量印在格子下面（六位数也放得下），**超过 64 的用金色**标出来；背包满了多出来的部分会掉在你脚边。





· **两条边界（写清楚，免得来回问）**：① 它**不接原版漏斗**——漏斗那套按"一格最多 64"算，碰到一格 11 万个的堆会把目标格改写成 64，等于销毁物品，宁可少一个接口也不丢东西；② 它**现在不能放置成方块**（手上右键就是开界面）——方块形态卡在一个编译限制上（方块实体类型的工厂接口在原版 jar 里是包内可见，没有 Forge 的 AT 环境过不去），留到以后做；"放下成箱子、拆掉连内容一起掉出来"这类潜影箱的方块侧行为暂时没有。





· **怎么验证**：拿到压缩盒 → ×右键打开 → 往里面塞点石头（Shift+点背包格），看数量印在格子下面；然后把盒子放进女仆背包（背包界面拖给她，或让她自己捡），`/maid_smart box <女仆>` 看一眼她"看得见多少"；再看她平时取材料、搭路、飞行跟随（烟花/鞘翅都可以放在盒子里）是否照用。把「女仆背包延伸」关掉，她那一侧就**完全看不见**盒子里面的东西（`/maid_smart box` 会显示"原版的 36 格"）。


【实测六百一十五】飞行跟随：起手 25 格 / 收手 5 格（两个球）+ 单独成一个板块；空袭俯冲的烟花力度 ×1.4





· 用户原话："1.现在女仆稍微走出去一点就开始飞（默认5格导致的），需要对判定进行一个收紧……但实际上开始跟结束两个半点的球大小应该不一样，默认值就是我说的那两个。依旧可以在手册内调试。2.然后把飞行跟随这个板块单独拎出来，不要放在搭路板块的里面，而是改成跟搭路平行的一个板块。3.近战空袭不是刚加了一个俯冲时使用烟花加速的功能吗？但是我发现加强力度太小，加速效果不明显。还耗了一颗烟花，没啥用。这边建议这个烟花加速力度效果乘以1.4倍"





· **起手与收手是两个不同的球**：起飞要等你离她超过 **25 格**（3~128 可调），收手只看你**进到 5 格**内（1~64 可调）。旧版只有一个判定球——收手半径是「起手距离减 1」推出来的，迟滞只有 1 格：她滑出去刚过线就起飞、飞回来刚进线就收手、走两步又滑出去再起飞，来回抖（用户："这样子带来的麻烦很多"）。两个数都在 **「移动与行为 → 飞行跟随」** 小节里调；**写反了也不会出事**（收手 ≥ 起手时自动压到「起手 − 1」，否则就是「起飞即收手」）。





· **飞行跟随搬到了自己的板块**：以前它挂在「搭路」里面（配置写在 `[bridge]` 小节、面板也在搭路那一页），现在与搭路**平级**——配置是新小节 `[flightFollow]`，面板是「移动与行为 → 飞行跟随」。**升级注意**：老配置文件里 `[bridge]` 下那四行 `flightFollow*` **不再生效**（值不会被自动搬过去），想要旧设置请手动搬一次、或者干脆重新打开开关一次；另外起手距离的默认值这批从 5 改成了 25（老配置里写着的 5 会照旧用 5）。





· **俯冲那一枚烟花 ×1.4**（用户原话："加强力度太小，加速效果不明显。还耗了一颗烟花，没啥用"）：原版挂载烟花的推力是**把速度往「1.7 倍视线」这个不动点上拉**——俯冲里那点力度只够勉强维持速度。现在**只有俯冲段**点的那一枚会额外补一份推力，把不动点抬到 **2.38 倍视线**（默认 ×1.4；可在「战斗与自保 → 空袭数值 → ⑥.5 俯冲段冲刺加速 → 烟花力度倍数」里调，**1.0 = 完全照原版**）。**不额外消耗烟花**（仍然一发一枚）、起飞/爬升/盘旋那几处的烟花一字未动；日志搜「空袭·俯冲」会写「俯冲加速（烟花×1.4，距敌 N 格）」。





· **怎么验证**：飞行跟随的两个球——日志搜「飞行跟随跳过」能看到「距离不够（N 格 ≤ 起手距离 25.0）」，起飞那行写「主人飞远了（N 格）」（N 刚过 25），收手那两行写「进到主人 N 格内」与「结束（主人 N 格…）」（N 刚进 5）；把「收手距离」改成 12 再试一次，她就会在 12 格左右收手——**收手球确实跟着配置走**。俯冲烟花——把倍数改成 3.0 飞一圈，肉眼就能看出俯冲比 1.0 时猛得多（收尾记得调回 1.4）。





【实测六百一十四】鞘翅赶路：只合并粉丝那份实现里的「精华」（坐标档 + 法术 id 诊断）





· 用户原话："关于那个粉丝issue鞘翅赶路的问题，我们就照你说的办，只合并一部分内容。取其精华，去其糟粕。"





· **她可以飞向指定坐标了**：`/maid_smart elytra_goto <x> <y> <z> [女仆]`（不写女仆就取离你最近的一只）——她会穿鞘翅飞向那个坐标，走的就是「飞行跟随」**同一条链路**（判定 / 起飞 / 补推 / 收手一模一样），只是把「目标」从主人换成一个坐标。到点 / 超时（60 秒）/ 出现威胁 / 燃料与鞘翅掉了 / 她换了维度 → **当场收手并忘掉这个坐标**（一次性；之后交回普通跟随，主人还远的话由原版瞬移接手）。命令会先把几条硬条件判一遍并**直接告诉你原因**（开关关着 / 目标太近 / 没有可用鞘翅 / 没有能飞的道具），其余情况看日志「飞行跟随跳过」。





· **为什么只取这一件**：粉丝 Roderick32 那份「鞘翅赶路」的主干（非战斗常态下主人跑远 → 穿鞘翅飞过去、空袭打完没敌人时跟随、赶路不消耗鞘翅耐久开关、法术冷却 ×20 修正）与**我们这边即将要更新的「飞行跟随」完全重叠**——而重叠的那几条我们不但已经有了，还更全（所有模式通用、烟花 / 羽扇 / 位移法术三种燃料、两个省料开关、与搭路 / 自保 / 传送的让位全套）。他那边唯一我们没有的能力就是「**目标可以是一个坐标**」，所以只合并这一件：**同一条链路多一个目标来源，不是第二套飞行逻辑**（坐标档同样吃那两个省料开关与法术口径）。





· **顺带收下的一条诊断**：两张法术表（提供高度 / 提供速度）里**认不出来**的 id，表现是「这张表里排第一的法术永远静默失效」；现在挑法术前都会校验一遍，认不出的在 `logs/promaid.log` 里报一条「法术兼容 … 配置里的法术 id 认不出来，已被跳过 …」（同一个 id 只报一次；最常见的原因是少写了复数 `irons_spellbooks:`）。





· **实机验收**：打开「飞行跟随」（`bridge.flightFollow`，默认关）→ 女仆背包放鞘翅 + 烟花（或羽扇 / 位移法术任一件）→ 站在她 5 格外执行 `/maid_smart elytra_goto <x> <y> <z>`（多女仆时末尾加一只点名）→ 日志搜「飞行跟随」，应看到「飞向指定坐标（N 格），背上鞘翅追过去（…）」，到点后「结束（目标点 N 格，…）」。





【实测六百一十三】飞行跟随的启动并入位移法术：能飞的道具两选一 → 三选一





· 用户原话："位移法术也可以加入到飞行跟随的启动中"。





· **能飞的道具由「烟花 / 羽扇」两选一扩成三选一**（加**能上天的位移法术**，也就是升腾 / 烈焰冲锋这类「提供高度」的法术）：只带一本法术书、一枚烟花都不带的女仆，现在**也会起飞**了。改动只有一条口径——门禁改调 `MaidFlightKit.hasFlightPropellant`，与空袭三件套的第三条**同一份定义**（这份"三选一"原先在空袭激活与缺件气泡里各写一遍，本批收进一个方法，三处共用）。





· **推进顺序 = 扇子优先 → 烟花 → 位移法术**，与空袭**起飞**那一套同序（"有烟花先走烟花链路、没有才用位移法术"）。所以**带烟花/羽扇的存档手感与烧料节奏一字不变**，法术是**第三条腿**：扇子不在、烟花也真拿不出来时接手（消耗档把最后一枚烧完之后的下一脚，就是它）。法术那一口**抬头瞄着主人放**（先清掉法术模组那份施法目标，否则它施法前会把朝向拧平、升腾就变成"向前扑"），按书里铭刻的等级放——与空袭"平地起飞/补高"完全同一套打法。





· **不新增配置项**：法术这一支归空袭的**「位移法术·起飞/补高」**管（默认开）——关掉它，空袭与飞行跟随**一起**退回只认烟花/羽扇。法术不消耗物资（按自己的冷却放），所以「消耗烟花」那个开关管不到它，两种档位下都一样。





· **三种道具都没有时**：跳过日志（搜「飞行跟随跳过」）写的是「背包里没有「可以飞行的道具」（烟花火箭 / 孔雀羽扇 / 能上天的位移法术，三选一）」；**起飞行**多一个字段「位移法术=开/关」，**法术补推**单独一行「放位移法术追主人（irons_spellbooks:ascension Lv3）」。进收手半径那一刻的"解除推进矢量"天然也把法术给的那一份解掉（法术是瞬发改速度，没有烟花那种"活着期间每 tick 都推"的残留）。





· 验证（1.20.1 专用服务器，同一轮两只女仆）：**只带鞘翅的女仆**——6 条跳过日志、**0 次起飞**，文案正是上面那句新文案；**鞘翅 + 64 烟花的女仆**——照常起飞两趟、照常烧烟花（起飞行写着「燃料=烟花火箭，烟花=消耗，位移法术=开，鞘翅耐久=照原版扣」，收手行「解除火箭推进矢量（水平速度 0.74 → 0）」）；四条标准回归（1.20.1 / 1.21.1 启动 × 女仆装载）全 PASS。





· 【这批验不到的那一层】"她真的放出了升腾 / 烈焰冲锋"必须装《车万女仆：万法皆通》+ 铁魔法两个模组才触发，测试服里装不了，所以自动用例只证到"门禁真的改了 + 烟花那条老链路没坏 + 新日志字段真的打出来了"。**实机怎么验**：背包放鞘翅 + 一本写着【升腾】的书（一枚烟花都不放）→ 打开「飞行跟随」→ 自己飞远 → 日志搜「飞行跟随」，应看到「燃料=位移法术（irons_spellbooks:ascension）」与「放位移法术追主人（… LvN）」；再把「位移法术·起飞/补高」关掉试一次，她应退回"不起飞"。





【实测六百一十二】飞行跟随：默认 5 格、进半径解除推进矢量、所有模式通用、威胁当场断链





· 用户反馈（四条）："1.飞行跟随配置默认距离从16改为5，同时加一个判定，在距离内检测到主人之后解除烟花带来的矢量（之前空袭状态下是不解除，在此模式下改为解除）2.2个空袭状态（未接敌），也可以触发飞行跟随，不做额外抑制。简单来说，就是目前飞行跟随触发判定推广到所有模式下。3.跟搭路有同款的判定，检测到威胁的时候会解除这个链路（又变成自然滑行）。4.我需要确认一点，在此类状态下，女仆在什么时候会使用烟花火箭这类推进呢？"





· **触发距离默认 16 → 5 格**（范围 6~128 → 3~128）：16 那档配上"收手半径 = min(15, 距离-1)"只剩 1 格迟滞，现实里几乎飞不起来。**升级注意**：老存档配置文件里若已经写着 flightFollowDist = 16，Forge/NeoForge **不会**替你改小——想用新默认就删掉那一行，或在面板/配置里手动改成 5。





· **进到收手半径 = 解除推进矢量**（收手半径 = 触发距离减 1，默认 **4 格**；触发距离调到 16 以上才回到 15 格上限）：你进这个范围，本趟就中断，而且**当场把烟花给的速度解掉**——收掉还挂在背上的那枚助推火箭 + 速度归零，之后零速滑翔自然下沉。为什么"收掉那枚火箭"是必需的一步：烟花不是"一次性给一份速度"，而是它活着的那十几 tick 里**每 tick** 都把骑手速度往"1.7 倍视线"拉（1.20.1 `FireworkRocketEntity.tick` 字节码实证：`v = v*0.5 + look*0.85`），只清零不收它，下一 tick 就被补回来。空袭那边**故意不解除**（她正需要这份动量贴脸、扑击）。





· **触发判定推广到所有模式**：旧版那句"她是空袭任务就不起飞（它自己有飞行作战链路）"已删除——两个空袭任务**未接敌**时照飞（未接敌 = 脑内没有存活攻击目标、也没在起跳/爬升/猛击/助推那几段出手里）。空闲 / 战斗 / 挖矿 / 烹饪 / 搭路一视同仁；**只有"真在打"与"真有活干"才让位**（后者是与搭路同一套的"任务实质占用"）。链飞到一半才接上敌的，也会**当场把控制权交回**给她自己的空袭链路。





· **威胁一出现就断链**（第 3 条）：与搭路**逐条同源**——同一个威胁半径配置（默认 8 格）、同一个"敌对生物"判据（史莱姆/岩浆怪这类只实现 Enemy 的也算）、起飞时挡 + 途中也中止（搭路同样是这两处）。空中就**保持动量自然滑翔**：这一支**不**解除矢量——威胁是"撤"，进距离才是"收"。





· **她什么时候烧烟花**（第 4 条的正面回答）：不是"每隔几秒烧一枚"，而是**掉了才补**——三个条件同时成立才补一口：① 本趟还没进到收手半径内；② 距上次补推满 1.5 秒；③ **她比主人低 ≥0.5 格**或**水平速度掉到 0.35 格/tick 以下**。最后一条就是"起飞那一下"：刚跳起来时水平速度是 0，所以**起飞后第一口推进几乎是立刻发生的**。背包里有孔雀羽扇时这一口走扇子（不烧烟花、扣扇子自己的耐久）；两样都没有就补不了，本趟会因燃料门禁收手。





· 验证（1.20.1 专用服务器；红 / 绿，同一套用例五轮）：**红**（旧 jar）——A 轮收手 **13.5 格**（旧 15 那一档）、**没有**解除矢量行、配置被旧 jar 写回 **16.0**；A2 轮同样没有解除行，而独立取证显示她那一刻仍在以 **1.601 格/tick** 飞（烟花推力的不动点那一档，人还在空中）；B 轮空袭任务被整类跳过。**绿**（本批 jar，同一套用例五轮，**全部通过**）：**A 轮**（把配置里 `flightFollowDist` 那一行删掉，验新默认）——替代主人只在约 **14.3 格**（10 平 + 8 高）外她就起飞（旧 jar 要等到 tp 到 64 格才飞），收手距离 **3.8 / 3.4 格**（= 触发 5 → 收手 4 那一档），配置被新默认写回 **5.0**；进半径时打了两行「解除火箭推进矢量（收掉还挂着的烟花，水平速度 1.46 → 0）」；**独立取证**：解除之后她还在空中（y=72.3，站立基线 69.0），水平速度只剩 **0.309 格/tick**（旧版是烟花推力的 1.7 那一档）；tp 到 64 格之后**又飞了一趟**；烟花 64 → 62（真扣）、鞘翅 1 件。**A2 轮**（触发距离 20 → 收手取上限 15）——收手距离 **14.7 格**、独立取证水平速度 **0.280**。**B 轮**（空袭任务、未接敌、无武器、周围无怪）——照样起飞并在 3.8 格收手（旧版整类跳过）。**C 轮**（替代主人 64 格外，她起飞后把 NoAI 无敌僵尸刷到她身边 3 格）——**第一趟在 54.0 格处当场断链**（远大于收手半径，且该趟没有解除矢量行），清掉僵尸后她又能飞（解除行只出现在收手那一趟 3.2 格）。**D 轮**（开关关着）——一行日志都没有。





· 【这批没做的】空袭那套"能上天的位移法术"仍未接进飞行跟随的燃料口径（原因同前：它绑在空袭状态机的朝向函数上，照搬会把"朝主人去"拧成"背对主人"）；视觉三样没动（六百一十一 已统一）；"解除矢量"那一瞬的观感是纯客户端，自动用例验不了（验到的是"速度确实归零 + 那枚火箭确实被收掉"）。





· 【这批自己踩到并修掉的两处（都是用例抓出来的）】① "在 tick 里解除矢量"**一次都跑不到**——Brain 先问 canContinue、为假就直接 stop，而"进半径"正是 canContinue 末尾那条（绿轮现场：日志里没有那一行）；改成由 stop() 执行。② forge 树把"收掉那枚火箭"写成了 `m_6075_`——javap 实证那是 **baseTick()**，火箭根本没被收掉、继续每 tick 推她（现场：日志说"1.35 → 0"、可她一秒后仍以 1.4~1.6 格/tick 飞出 140 格）；改成 `remove(RemovalReason.DISCARDED)`。镜像复核脚本里那条"想当然的映射"（`m_6075_` → `discard`）也改掉了——它正好把两树的差异抹平，这比 bug 本身更值得记。





【实测六百一十一】飞行跟随：15 格内就收手、自然滑翔、飞出去再飞一趟，动作也跟上空袭





· 用户反馈："飞行跟随的启动只认烟花，而且动作没有换成空袭飞行的动作……在飞行跟随期间自身周围 15 格内找到主人那么此链路就中断，不需要紧挨着主人。具体的表现就跟空袭女仆把怪打死一样。自然滑翔。反正飞出去了会再次启动链路。"





· 收手半径 4 格 → **15 格**：你进到她 15 格内这一趟就中断（**不需要贴到你身边**），她若还在空中就**什么都不做**——原版滑翔把她自然带下去、落地那一 tick 自动还回胸甲，与"空袭把怪打死之后"完全同一套（旧版要贴到 4 格、还得抬头泄速 60 tick 刹车，整段删掉）。你飞远了（又超过触发距离）她**再飞一趟**，一趟一趟地跟。起飞与结束两行日志现在**都带距离**（各自 5 秒限频，边界来回穿越不刷屏）。





· **动作与空袭同款**：展翅的游泳姿态 / 背上的鞘翅翅膀 / 跟着俯角的前倾——这三样原来都只认"飞行任务"判据，而跟着飞的她不是飞行任务，于是一样都跟不上（这就是"启动只认烟花"的现场：除了手上那发烟花，她身上没有半点"在飞"的样子）。现在判据统一成"飞行任务 **或** 正在滑翔"（取同步过的滑翔位，多人下客户端也认）。放宽游泳姿态是安全的：扫过 TLM 1.5.3 的 2344 个类，读 `isVisuallySwimming` 的只有 3 个（动画选择表 / 游泳动画本体 / 女仆自己的覆写），全是客户端动画，没有一个玩法判定读它。





· **燃料口径并入孔雀羽扇**：起飞与补推从"只认烟花"扩成"烟花火箭 **或** 孔雀羽扇"（与空袭口径一致；有羽扇先用羽扇），起飞行日志会写明这一趟按什么飞。缺件时的跳过日志也分得更清（「背包里既没有烟花火箭、也没有孔雀羽扇（能飞的道具）」）。





· 验证（1.20.1 专用服务器；红 / 绿；两树编译零错、两 jar 重建、三处部署 match=True、四条标准回归 PASS）：**红**——旧 jar 的结束行没有距离，红轮现场她**落在替代主人身边 3.3 格**才收手（等于"贴到 4 格才收手"），新用例第一条就判失败；**绿**——结束行带上距离且每一条都 ≥5 格、把替代主人 tp 到 64 格后又飞了一趟、烟花照扣、鞘翅没丢；另有省料档 / 开关关 / **只给鞘翅不给燃料（必须不起飞）** 三轮。视觉三样是纯客户端渲染，自动用例验不到"看起来像不像"，只验了判据口径与空袭同一处。





· 【这批没做的】空袭那套"能上天的位移法术"没接进飞行跟随的燃料口径（它与空袭的状态机绑在一起，照搬会把"朝主人去"的朝向拧成"背对主人"）；威力 / 地形 / 动力学一概没动。





【实测六百一十】她扔的模组 TNT 那一炸：主人/友军也被护住了（并更正六百〇七 那句交代）





· 六百〇七 收走模组 TNT 的「破不破方块」时留了一句交代："那一炸的伤害源是那枚 TNT 实体、不是女仆，所以现有的主人/友军免伤覆盖不到它。" 这批把它验了：那句话**只说对了一半**——伤害源里的**直接实体**确实是那枚 TNT，但**造成者**是她（原版拿引信 TNT 记的点火者当造成者，等价交换把点火者原样交给了它自己那一枚），而主人/友军免伤判的正是造成者。所以对**记了点火者的模组**，主人与友军本来就被护住了：同一队里的女仆在她连炸十几发新星期间 26 次采样恒为 1024 血、一格没掉，而**没入队的对照女仆被炸到 67 血**。





· 但六百〇七 指出的**边界是真的**：模组未必把点火者写进伤害源（那时造成者是空的，谁也认不出这一炸）。修法就是把"她扔的 TNT"这条归因接进主人/友军免伤：`MaidTntBlastGuard` 公开一条 `maidOfTnt`（引信 TNT 记的点火者 → 我们亲手登记过的那一枚，与六百〇七 为地形留的那张登记表同一份判据），主人/友军免伤改成三级归因"造成者 → 直接实体 → **直接实体是她放的 TNT**"。**「伤到主人/友军」开着时这一层让位**（那是玩家要的原版口径）；威力 / 带不带火 / 地形权限一概没动。





· 验证（1.20.1 + 等价交换，红 / 绿 + 阳性对照；两树编译零错、两 jar 重建、三处部署 match=True、四条标准回归 PASS）：**红**——把点火时的 igniter 临时传成 null（模拟不记点火者的模组），友军女仆 1024 → 110，与对照女仆（1024 → 48）一样惨；**绿**——修好后同一场景友军 26 次采样恒 1024、对照 984 → 32，日志里 `间系=空 伤害源{造成者=空}` 却照样护住友军（兜底真的在工作）；**真身**——去掉探针、走等价交换自己的点火者，同样友军不掉血、对照被炸。模组 TNT 那一炸的日志行现在会写**归因现场**（直系 / 间系 / 伤害源里的造成者与直接实体 / 类型 / 我方登记）。





· 【顺带更正两处旧记录】① 六百〇七 说的"伤害源是那枚 TNT"指的是**直接实体**，造成者是她；② `Health:2000f` + `max_health 2000` 这套 NBT 落到世界里的血量是 1024（僵尸与女仆都一样），所以旧记录里"木桩 2000 → 1024"其实是落地血量、不是被炸掉的（木桩还带抗性 5 = 免伤）；本用例的阳性对照改用没入队的对照女仆。





· 【这批没做的】不改威力 / 带不带火 / 消耗 / 飞行动力学；也不管玩家自己的 TNT 与别人的爆炸（只认她扔的那一枚）。





【实测六百〇九】"她说缺飞行道具"的假提示：本模组自己的副手动作把副手那件借走了





· 反馈原话："女仆受伤，明明拿着孔雀羽扇，但她却还是说缺少飞行道具。执行链路可以执行，但这个系统消息不停。"





· 现场定因（实机日志逐毫秒对上）：孔雀羽扇挂在女仆**副手**上，而本模组自己的"手上亮一下"（放置 / 充能 / 投掷那一刻把"她正在用的那件"放进副手举十几 tick，用完原样还回去）在空袭轰炸段每十几秒借一次副手——那十几 tick 里羽扇**不在任何槽位里**，于是就绪判定报「没有可以飞行的道具」并给主人发一条系统消息；借还之后判定又齐、把 15 秒播报冷却清零，下一轮再报一条，**每 10 秒一条、永远不停**。证据是日志里"副手 羽扇→重生锚"那一瞬间的下一条就是系统消息，而同一段里她挥扇起飞一直是好用的（反馈说的"执行链路可以执行"就是这个）。受伤与这条提示无关——真正的节拍是轰炸链路。





· 修法之一（就绪口径）：判定"她身上有没有这件东西"时，**把我们自己借走的副手那件也算上**（本模组自己的动作表现借走的，不是玩家拿走的）。"判定有没有"与"动手去用"因此不再互相打脸：借走的那十几 tick 里不再误报，而她真要挥扇 / 烧烟花时仍要求它老老实实待在槽位里。





· 修法之二（播报不再一抖就响）：缺件与齐备都必须**连续成立 2 秒**才算数——缺没站稳不播报，齐没站稳也不清冷却。所以"借出去又还回来"这种十几 tick 的抖动既报不出来、也解锁不了下一条；真把羽扇/烟花拿走时照报，只是晚 2 秒（她本来就在按普通战斗打，这条提示是补充说明）。





· 顺带补了一行诊断日志：缺「可以飞行的道具」时，运行日志里会写清主手 / 副手各是什么、背包里有没有烟花和羽扇、会不会位移法术（与"缺鞘翅"那行同一节流，最多 15 秒一行）。旧版这种情况日志里一片安静、气泡却照发，玩家报过来时没法复核——这批就是先补这一行才把现场读出来的。





· 验证（1.20.1 专用服务器，红 / 绿 + 阳性对照；两树编译零错、两 jar 重建、三处部署 match=True、四条标准回归 PASS）：场景是远程空袭女仆 + 背包 TNT/打火石/箭、**烟花只放副手 64 枚**（测试服没装暮色森林，用烟花顶替羽扇，机制完全相同）、面前一尊 2000 血木桩。**修之前**（先加诊断日志的中间件）：8 次投掷 TNT = 8 个"副手被借走"的窗口 → 6 条假提示，与粉丝现场同节拍；**修之后**：同样 8 次投掷 → **0 条**，且收尾时副手烟花 64 → 50（她真飞了、真烧了燃料）；**阳性对照**（副手也不给烟花）：5 条缺件提示 —— 证明这条播报通路确实会响，不是"没量到"。





· 【这批没做的】不改消耗 / 耐久 / 飞行动力学，只动"读"的路径与播报节流；也没把"借走的那件"算进**取用**（挥扇、烧烟花仍然要求它在槽位里）。





【实测六百〇八】飞行跟随：主人自己飞走了，她背上鞘翅追过来（默认关，手册内开启）





· 需求原话（粉丝留言）："女仆跟随能不能给她整个使用鞘翅一起飞呢，自己飞了后女仆只能搭着方块干着急了，两人一起飞想想还挺有意思的"。作者回复定为可选玩法：这种观赏类型的东西可以做，**默认关闭**，并在手册/面板里提供两个省料开关。做法与口径完全照作者给的："女仆在判定使用搭路时，发现主人离自己太远且自己跟主人之间没有方块阻拦，自己包里面还鞘翅和烟花的时候，target=主人，执行飞行（跟空袭模式的起飞是一样的，但是 target 等于主人）。"





· 触发点就是「搭路」那一档：新行为注册在 core 246，刚好压过搭路（245）——条件不满足时本行为不启动、搭路一字未动；条件满足时她直接起飞，搭路另加了一道"她正在飞行跟随就不搭"的让位。起飞要同时满足：开关打开、她不在空袭任务/守家/坐姿/骑乘/睡觉/自保、任务没有实质占用（与搭路同一套判据）、主人（或调试目标）活着且同维度、**3D 距离超过「飞行跟随距离」**（默认 16 格，6~128 可调）、**背包里同时有可用鞘翅和烟花火箭**、**你俩之间没有方块挡住视线**（raycast）、周围威胁半径内没有怪。





· 飞起来跟空袭的起飞一样：先跳一下离地、置滑翔位、抬头换高度，再放一发**挂载烟花**（只写飞行时长、不带爆炸星，到期不会炸到她自己）；推力沿她的视线方向生效，而她此刻正看着你——所以"看向主人"本身就是油门加方向盘，不用额外造速度。每 tick 把视线钉在你身上，比你低或速度掉了就补一发（最短 1.5 秒一次）；进 4 格收手，落地的就地结束，还在空中的先抬头泄速几秒，剩下的高度靠滑翔自然落下去（滑翔期间原版每 tick 清坠落距离，摔不伤）。





· 两个省料开关（都在「移动与行为 → 搭路」小节）：**消耗烟花**（默认开）关掉后每次补推不再从背包扣那一枚——但**照旧要求背包里有烟花**（它是她能飞的凭证）；**消耗鞘翅耐久**（默认开 = 照原版每 20 tick 扣 1 点）关掉后这一趟不啃耐久。后者是靠一笔 mixin 拦在原版 `ElytraItem.elytraFlightTick` 入口（那就是扣耐久的地方）：注意这一手**只能"跳过扣耐久"、不能"取消滑翔"**——那个方法的返回值是"这一 tick 滑翔还算不算数"，返回假她当场就掉出滑翔、20 血自由落体，所以拦法是照旧返回真。**边界**：只认原版鞘翅及其调用 super 的子类，模组那种自带滑翔钩子的护甲拦不到。





· 她不会抢别人的活：飞行期间**自动传送与同维度拉回一律让位**（否则她飞到 48 格会被直接拽走，"一起飞"当场结束）；搭路一见她在飞就让位；撞墙/摔落免疫也照飞行任务那两档一起管（女仆 20 血，撞一次墙就没了）。**装备两处硬约束**：起飞前把胸甲槽换成鞘翅（她自己本来就穿着可用鞘翅时一字不动）、换下的胸甲当场回背包；而**归还只在她站到地上时发生**——原版每 tick 都要看胸甲槽里那件鞘翅能不能飞，空中摘走等于清滑翔位（自由落体），所以收手时若她还在空中，鞘翅继续戴着让原版把她安全滑下去，落地那一 tick 再还。有界：一趟最多 60 秒，超时 / 烟花用完 / 威胁出现 / 自保 立刻收手。





· 怎么验证她真的会飞？（服务端）`/maid_smart flyfollow <实体> [女仆]` 给最近的一只女仆（或点名的那一只）指定一个"替代主人"，`/maid_smart flyfollow clear` 摘掉——它走的仍是这条链**同一套**判定与飞行链路，只把目标从"在线主人"换成你指定的实体（专用服务器上没有玩家，主人实体恒为 null，这条链结构上没法端到端触发，与 `/maid_smart feedtest` 同一类困境）。运行日志搜「飞行跟随」，能看到**起飞 / 补烟花 / 结束 / 已落地把胸甲还回去**四行；**不起飞**时搜「飞行跟随跳过」，她会说清是哪一条判据没过（睡觉/守家/空袭任务/自保/任务占用/没有目标/距离不够/缺鞘翅/缺烟花/视线被挡/附近有怪）——每只女仆 10 秒最多一条，且**只在开关开着时才记**，默认档一行都没有。替代目标请用**非敌对活体**（比如村民）：她自己的"附近有怪就不飞"会把敌对目标当威胁，拿僵尸当替身只会验出"起飞就收手"的假象。





· 验证（1.20.1 专用服务器实跑，三轮对照；两树编译零错、两 jar 重建、三处部署 match=True、四条标准回归 PASS、两树 608 段镜像复核一致）：**全默认档**——起飞 22 格、补推 1 次、4 秒追上并落地，背包烟花 **64 → 62**（真扣）、鞘翅耐久 **0 → 3**（照原版扣）、胸甲原样还回背包；**两个省料档**——起飞日志写「烟花=不消耗，鞘翅耐久=不消耗」，同样追得上，但烟花 **64 → 64**、鞘翅 **Damage 0**；**开关关着**——一行日志都没有、她原地不动。【要你进游戏验收】：把「飞行跟随」打开，自己背上鞘翅飞远一点，看她是不是也背上鞘翅追上来、贴到你身边才收翅落地；再回面板把「消耗烟花 / 消耗鞘翅耐久」两个开关关掉跑一轮，确认烟花不再少、鞘翅耐久不再掉。【实测踩过的三个坑，写在这里省得你排查】① 命令默认取"离你最近的女仆"，测试世界里若有别的女仆可能挂错人（现在可以 `/maid_smart flyfollow <目标> <女仆>` 点名）；② 沿途/附近有敌对生物（威胁半径内）她不会起飞——日志「飞行跟随跳过」会写明「附近有敌对生物」；③ 替代目标别用敌对生物（僵尸之类）：她自己的"附近有怪就不飞"会把目标当威胁，刚起飞就收手，请用村民这类非敌对活体。





· 【这批没做的】不做"跨维度飞过去"（她一见目标与自己不同维度就收手，跨维还是走原有跟随/传送链路）；也不做"她自己主动起飞兜风"——这条链只在"该搭路却够不着你"的那一刻被动触发。








【实测六百〇七】模组 TNT 那一炸也受「不破坏方块」管辖





· 需求原话："mod的TNT也受我们的不破坏方块管辖。" 收掉的是六百〇五 留下的边界：那一批让女仆扔出**模组自己的 TNT 实体**（等价交换的爆破新星），当时定的口径是「威力 / 破不破方块 / 带不带火全归它自己」——于是那一炸照样在地上留坑，本模组的「轰炸破坏方块」开关管不到它。现在把「破不破方块」收回来：开关关着（默认）时，她扔的模组 TNT 也一个方块都不拆；开着才照它自己那一炸炸坑。**威力与带不带火仍归它自己，伤害与击退一概没动**。





· 机制（javap 实证，两版一致）：Explosion 没有威力/交互模式的公开读取口（都是 private），所以「照原威力重炸一遍」走不通；但 Forge/Neo 都在 **Explosion.explode() 里**发布 ExplosionEvent.Detonate，而方块真正被拆发生在之后的 finalizeExplosion，且它读的就是 getToBlow() 那张表——而 Detonate.getAffectedBlocks() **直接返回活的 getToBlow() 表**（不是副本）。所以在事件里把这张表清空 = 这一炸不拆任何方块，伤害完全不受影响。对覆盖了 finalizeExplosion 的模组（等价交换）同样成立：它也是先 explode() 再 finalizeExplosion()，拷贝发生在事件之后；而我们自己的四类炸弹走 ExplosionInteraction.NONE，本来就不拆方块，这层对它是空转。





· 怎么认出「这是她扔的那一枚」（新增 com.maidsmart.combat.MaidTntBlastGuard，只认 TNT、绝不动别人的爆炸）：① 主人链——直系来源是引信 TNT、点火者（间系来源）是她（原版/加载器自己就是这么归因的，等价交换也把 owner 收下了）；② 兜底——primeTnt 在我们放出「模组自己的 TNT 实体」时把它登记进一张带 400 tick 过期的小表（惰性清理防死 id 堆积），个别模组不记 owner 时靠这条兜住。日志搜「空袭轰炸」可见「模组 TNT 那一炸按「不破坏方块」处理（清掉 N 个方块；威力与伤害照它自己的）」。





· 验证（1.20.1 + 等价交换实跑，开/关两轮对照）：场景是先立一尊不动不落的 Marker 盔甲架当锚点、以它为准铺 5x5 石头台并在台上放 2000 血木桩，女仆反复扔新星砸木桩。**关**（默认）：8 行守卫日志（每次清掉 9009~22630 个方块）、石头台采样格 **5/5 全在**、木桩 **2000 → 1024 血**（只收地形权限，伤害照旧）；**开**：守卫日志 0 行、石头台**全被拆掉**（证明是这道开关在管）。两树编译零错、两 jar 重建、三处部署 match=True、四条标准回归 PASS。【要你进游戏验收】：拿等价交换的爆破新星喂她，看新星炸开时**地面是不是不留坑**（默认档），再进面板把「破坏方块」打开跑一轮，确认那时才恢复拆坑。





· 【这批没做的】只挡地形、不挡伤害：那一炸的伤害源是**那枚 TNT 实体**（不是女仆），所以现有「主人/友军免伤」覆盖不到它——需要的话另开一批。若某个模组的爆炸不走 getToBlow() 而是自己另开一套循环拆方块，这层拦不住。





【实测六百〇六】俯冲段冲刺加速：朝着敌人俯冲那一段补一口推进，方向不变（烟花 / 法术 / 羽扇三路）





· 需求原话："近战空袭向下朝着敌人俯冲期间补一个链路：用烟花/法术加速（孔雀羽扇好像不行，行的话也加上），方向不变，这样可以大幅提高周期 dps。"





· 先说清"俯冲"是哪一段：一轮是「放烟花/挥扇爬升 → 朝目标压低机头一路滑翔扎下去 → 进 3.5 格收翅猛击」。中间那截下滑就是俯冲——它才是周期里最长、最影响周期 DPS 的一截。





· 为什么旧版这里没有加速、而烟花其实可行：javap 实证，原版挂载烟花的推力分支与暮色孔雀羽扇的滑翔分支，开头都是 `if (isFallFlying())`——都只在滑翔时给推力。俯冲这一段正是滑翔中，所以**点一枚烟花真的吃得到推力**（沿她的视线 = 朝着敌人，方向天然不变），旧版只是没在这一段调用它。而扇子那一式自带 +1.25 的竖直升力、还会把速度往视线×2 收敛，会把「往下扎」顶成「往前平飞」——这就是「孔雀羽扇好像不行」的由来：不是扇子坏了，是它的推力方向不对。





· 改法（面板新一段「⑥.5 俯冲段冲刺加速」，三路燃料按优先级）：① 位移法术（不消耗物资、最省，有就先用，旧版那处冲刺收进这里统一排序）；② 烟花（默认开）——俯冲途中真的点一枚，消耗 1 枚，推力由原版给、方向不变，副手照旧亮一下、有点火音效；③ 羽扇（默认关）——挥一次只借它的动作与消耗（挥臂 / 音效 / 扇风盒把贴脸怪扇开 / 原版扣耐久），不用它那一式推力，速度由本模组按「一口速度」（默认 0.55 格/tick）给。闸口：距敌 5~40 格才补、两次间隔默认 30 tick（1.5 秒）、激流突进期间让位。日志搜「空袭·俯冲」看「俯冲加速（烟花，距敌 12.3 格）」。





· 验证（1.20.1 实跑，开/关两轮对照）：开时本轮女仆 9 行「俯冲加速（烟花，距敌 39.6 格）」（全部落在闸口内）、背包烟花 63 → 31、照常起飞；把服务端配置的 `diveBoost` 改成 false 重启后 0 行、她照常飞。两树编译零错、两个 jar 重建、三处部署 match=True、四条标准回归 PASS。【要你进游戏验收】：看俯冲那一下是不是明显更快扎下去、烟花是不是每轮少一枚、以及**方向有没有被带偏**（这一条最关键——它应当始终朝着敌人扎，不会因为加速而平飞或偏航）；把「用羽扇」打开再看一轮，确认它只多了挥扇动作、方向同样不乱。





【实测六百〇五】认出来的 TNT 就放它自己那一枚（等价交换的爆破新星这类名字里没有 tnt 的模组 TNT 也认）+ 点火料按「类」认





· TNT 判据再加一条（需求原话："我们应该判断一下这个方块是否存在其对应 tnt 形式，如果有则确认，并释放其对应的 TNT（而不是原版tnt）。"）：六百〇四 只按注册名判，而模组 TNT 十有八九根本不含 tnt 这三个字母（等价交换的爆破新星注册名是 projecte:nova_catalyst）。现在两条取并集：① 注册名里带 tnt；② 这一件是方块物品、而**它的方块继承 TntBlock**——名字可以随便写，方块的血统骗不了人。





· 认出来的是哪一件，就放它自己那一枚：旧版不论手上拿的是哪一件，扔出去的都是自己 new 出来的**原版**引信 TNT（模组 TNT 的威力 / 破不破方块 / 带不带火全被抹掉）。现在投放前先问那一件自己：方块不是 TntBlock 本身（= 模组自己写的 TNT 方块）就走它自己的点火钩子 onCaughtFire（原版拿打火石点 TNT 走的正是这一步）——等价交换在这一步 new 出来的是它自己的 projecte:nova_catalyst_primed（威力 16、照它自己的 NovaExplosion 炸），**那一炸完全归它自己，本模组不接管**。它继承 PrimedTnt，所以追踪照旧生效、引信仍按配置（默认 40 tick）。**原版那一件一字没动**：还是自己造引信 TNT + 本模组「默认不破坏方块」的那一炸。日志那一行现在会点名放出来的是哪一枚（搜「投掷 TNT」）：「投掷 TNT ×1（projecte:nova_catalyst，引信 40 tick，它自己那一枚 projecte:nova_catalyst_primed）」。





· 点火料按「类」认（需求原话："类打火石/火焰弹识别"）：**类打火石**（FlintAndSteelItem 的子类）/ **类火焰弹**（FireChargeItem 的子类）都算，注册名兜底——模组自加的点火料不用再各自打补丁。打火石那一类仍然优先（手 → 背包）。





· 道具走耐久、消耗品走消耗（需求原话："点燃 tnt 的消耗品走消耗，道具走自己的耐久机制"）：走哪一条不看名字，看这一件**有没有耐久**——有耐久的是道具（打火石 64 点）→ 扣 1 点耐久、放回原槽（耐久耗尽才真的少一件）；没耐久的是消耗品（烈焰弹）→ 整件消耗。模组自加的点火料各按自己的性质落进对应那一档。





· 验证（1.20.1 装了等价交换那台，test_bomb605.py）：新星女仆 6 条「投掷 TNT」行、每条都点名物品与它自己那一枚；独立抽样「execute if entity @e[type=projecte:nova_catalyst_primed]」命中 3 次、data get 回读到「Primed Nova Catalyst … : 40s」（我们自己配的引信）——放出来的确实是等价交换自己的新星实体；另一台拿原版 TNT + 火焰弹的（4 条行、点名 minecraft:tnt、不带后缀）证明原版那条路没被改坏；负向对照（圆石 + 打火石）0 条行；打火石一件没少、耐久正好掉 6 点。两树编译零错、两个 jar 重建、三处部署 match=True、604 双服务器回归 + 四条标准回归 PASS。【要你进游戏验收】：1.21.1 那台测试服的 mods 里没有任何 TntBlock 子类（全部 jar 扫过 0 命中），所以模组 TNT 那条路只在 1.20.1 实测过——你在 1.21.1 上遇到模组 TNT 时照「投掷 TNT」那一行验收（它会写明放出来的是哪一枚）；另外**模组 TNT 自己那一炸不受本模组「不破坏方块」管**（那一炸归它自己），介意的话先说一声。





【实测六百〇四】TNT 边界放宽：注册名里带 tnt 的都算 TNT





· 判据放宽（需求原话："为 tnt 放宽界线，模组内含有 tnt 词条的都可被视作 tnt。"）：旧版把 minecraft:tnt 写死，别的模组自己加的 TNT（tntmod:tnt / xxx:tnt_block / yyy:super_tnt …）她一概看不见——玩家把那种 TNT 塞进背包，投掷链路照样按"缺料"跳过。现在改成**注册名（namespace:path）里出现 tnt 就算**，大小写不敏感：原版那一件天然还在里面，各模组的 TNT 一视同仁。看的是注册名、不看显示名（中文客户端里它根本不叫 tnt，按显示名判会随语言变）；边界只看名字、不看"这一件能不能放"，所以像 minecraft:tnt_minecart 这种名字里带 tnt 的也算数——她扔出去的本来就是一枚原版引信 TNT。真要不认这一类，说一声就加例外。





· 投掷日志点名物品：那一行原来是「投掷 TNT ×1（引信 40 tick）」，现在是「投掷 TNT ×1（minecraft:tnt_minecart，引信 40 tick）」——模组 TNT 认没认，翻日志（搜「投掷 TNT」）一眼就知道。





· 验证（专用服务器场景实测，两树各一轮 PASS）：探针物品用 minecraft:tnt_minecart（注册名里带 tnt、但绝不是原版那一件——两台测试服的 mods 全扫过，没有任何"注册名带 tnt 的模组物品"），旁边再放一台负向对照女仆（背包只有圆石 + 打火石）。探针那一台掷出「投掷 TNT ×1（minecraft:tnt_minecart，引信 40 tick）」、那一格回读为空（真消耗）、打火石掉 1 点耐久；对照那一台 0 条投掷行、圆石原封不动。两树编译零错、两个 jar 重建、三处部署 match=True、四条回归 PASS。【要你进游戏验收】：拿真正带 tnt 的模组物品（比如某个模组的大 TNT）喂她，看她照不照扔、扔完是不是少一个。





【实测六百〇三】远程空袭也放炸弹（重生锚 / 末地水晶 / 床）+「走后门」让悬空目标也放得下 + TNT 点火料多了烈焰弹





· 远程空袭加入放置链路：旧版按"她一直飞在天上，本来就放不了"把远程空袭排除在外，这道闸已整个删掉。现在盘旋期间每次开火之后先试放炸弹（重生锚 / 末地水晶 / 床），材料齐就交给相位、相位收尾自己投 TNT；没起手才就地投 TNT——与近战猛击那条路完全对称。放置是她自己的服务端动作，与她飞多高、离目标多远都无关。





· 「走后门」悬空也放得下（新面板项「空中悬空投弹」，默认开）：落点从三级扩到四级。难题是目标**自己悬空**（蝙蝠 / 恶魂 / 被击飞到半空 / 站在水里的怪）——它脚边那四格全是空气，前两级找不到支撑面。第四级直接把落点取在「目标头顶那一格 → 目标自己那一格」：不要求支撑面、也不要求那格没站着目标（原版以"那一格站着实体"为由拒绝时我们强制放下），于是底座可以悬在空中、贴在目标身上，0.5 秒后原地开花；那格若站着除目标以外的别人则跳过。日志里搜「走后门」能看到它有没有被用到。





· 投掷 TNT 的点火料多了烈焰弹 / 火焰弹：打火石优先（每发就地掉 1 点耐久），没有打火石时消耗 1 个烈焰弹（整颗消耗），两样都没有才跳过；副手动作跟着换——只有烈焰弹时举的就是那枚烈焰弹。





【实测六百〇二】粉火"不灭 / 又变橙"的真正根因 + 轰炸改判为附加链路





· 粉火被打回原版火（真凶）：原版火在任何邻居方块变化时都会按"原版火"重算一次状态（FireBlock.updateShape → 私有助手 → BaseFireBlock.getState 里写死 Blocks.FIRE），于是粉火旁边一动（爆炸、方块变化、水流、别的火…）就变回橙色火。橙色那些是"爆炸时又粉又橙"的来源；而原版火在石头/地面上是永不熄灭的——这就是"粉色的火又不会熄灭了"。现已补两处转发把状态翻回粉色，并保留它自己的 AGE（不会被反复更新续命）。


· 粉火一定会灭：粉火的自灭依赖方块 tick 链，而它所在的区块可能在它老化之前就停了 tick（远处战斗 / 玩家走开 / 区块卸载），冻住后还会被存进存档。新增 PinkFireSweep 两层兜底：① 每格粉火登记到期时间，服务端 tick 到点直接抹掉（不看方块 tick）；② 区块加载时把存档里带进来的粉火收进同一张表、下一 tick 抹掉——老版本残留的那 70 格会在下次进入那片区域时自动消失。


· 轰炸=附加链路（不占用行动与飞行）：旧版轰炸相位在飞的那十几 tick 里飞行行为整段让位，而起手正好是"收翅猛击打完、马上要再起飞"那一刻，等于把起飞压后 0.5~1 秒。现在删掉那道闸：放方块 / 挂水晶 / 充能都不需要她停手，该起飞起飞、该扑击扑击、该开火开火。


· 验证：专用服务器实测（放粉火 → 制造邻居更新后仍是粉 → 10 秒后变空气；存档 NBT 粉火 0 格）；两树编译零错、jar 重建、三处部署、四项回归 PASS。





## 实测六百〇一【粉火范围收回：只影响女仆那一炸点着的火】





- ① **收回越界的那一步**（反馈："你这样等于直接开挂了呀。你应该只影响女仆造成爆炸所产生的火焰，并且将它替换。而不是将所有的火焰全都开了。"）：实测六百 曾经做到"开关开着 → 世界里每一格新火都换成粉色"外加"每炸一次把爆炸范围里既有的原版火也扫成粉色"与一套 15 秒守护区；因为粉火不蔓延、约 8 秒自灭，那等于顺手把**全世界的火蔓延**关掉（含你自己点的火），已全部收回


- ② **现在的边界**：换的只有**她自己那一炸点着的火**（"边点边换"，一炸点着多少格就换多少格——实测 119 格全中，与距离无关）；打火石 / 闪电 / 岩浆 / 别的模组 / 早先留下的原版火**一概不碰**（该烧还是烧、该蔓延还是蔓延）；"不掉血也不着火"只对**那片粉色火**生效；别处的火按原版规则走，本模组不介入


- ③ 三个开关不变：`bombing.pinkFire`（爆炸火焰改粉色）/ `bombing.fireRender`（粉色火焰渲染）/ `bombing.fireProtect`（火焰伤害保护）


- ④ 验证：两树编译零错、两 jar 重建、三处部署 match=True、回归 4/4 PASS








## 实测六百【无主女仆全面降级；粉火"新火全变粉 + 不会被烧"；重生锚不再被末地水晶吞；空袭放锚照搬黑曜石那套】





- ① **无主女仆：promaid 一概不碰**（反馈："某些整合包对于无主的女仆会有一些额外的特殊效果，我们这个 mod 不能够阻碍他们……对于无主女仆 promai 所做的一切改动全都不生效"）：判据是**主人 UUID**（`getOwnerUUID() == null` = 野生/未认领）——**主人离线不算无主**，有主的离线女仆照旧干活。做法是卡在行为基类 `Behavior.tryStart`（final）上：`com.maidsmart.*` 的全部 AI 行为对无主女仆一律不许起手（以后新加的行为自动生效）；危险避让 / 恐慌门控 / 横扫加成 / 玩家伤害口径 / 农田踩踏 / 自动参战 / 危险减益那些改原版行为的入口也逐个让开。日志搜「无主女仆」（实测：1.20.1 服 41 条、1.21.1 服 15 条）


- ② **粉火：模式开着时"新点着的每一格火"都直接生成为粉色**（反馈："还有可能会出现橙火焰……玩家还是会被粉火烧到。这边建议改为……所有被生成的黄色火焰都会被替换成粉色火焰"）：实测日志证明旧版窗口那条路是好的（那一炸点着的 119 格火全部直接变粉），烧到人的是**窗口之外**的火。现在卡在原版唯一的放火入口上全局替换 + 每炸一次把爆炸范围内**既有的**原版火一并换掉 + **15 秒守护区**（区里你和女仆身上带火直接熄灭、踩进火里那一下取消 = "不会被烧"）。代价：开关开着时整个世界的新火都不再蔓延（含你自己点的），关掉即恢复原版橙色火


- ③ **重生锚不再被末地水晶吞掉**（反馈："此时女仆只会扔 TNT 和放末影水晶，压根不会去放重生锚。我认为它的优先级应和放末影水晶是一样的。两者可以同时启动"）：旧版三段是"取第一个材料齐的"，于是带着水晶就永远轮不到重生锚/床 → 现在 `PHASE` 改成"每段各一个相位"、最短间隔也每段各一条，**材料齐的几段同一次攻击里一起起手**（最多三段同时飞，落点互不抢：新增 `claimedByOtherPhase`），所有段放完才投 TNT


- ④ **近战空袭放重生锚"照搬黑曜石那套"**：三段放置本来就是同一套代码（`placeOnSupport`：原版放置 → 落点兜底强制放下 → 空中强制），"放不出重生锚"的真因就是 ③ 的"被水晶吞掉"；③ 落地后重生锚/床与黑曜石走同一条路


- ⑤ **验证**：两树编译零错、两 jar 重建、三处部署 match=True、回归 4/4 PASS；**要你进游戏验收**：空袭那一记是否同时放水晶+重生锚、打火石/岩浆的火是否也变粉且烧不到你、整合包给无主女仆的效果是否不再被挡








## 实测五百九十九【近战空袭不放重生锚；副手展示只亮 1 tick 的真凶；粉色火焰保护实测有效】





- ① **近战空袭不放重生锚**（反馈："近战空袭的时候不会放置重生锚"）：相位旧版一见"目标没了"就整段回滚，而收翅猛击**经常就是致命一击**——起手那一 tick 之后目标就死了，下一拍炸弹还没放下就被撤掉。现在 `Phase` 记住目标最后所在的那一格，**目标死了也把这一轮走完**（炸弹落在原地、0.5 秒后照炸）。上服实测：修复前 `目标死 → isBombing=false 附近重生锚=0`；修复后 `目标死 → isBombing=true 附近重生锚=1 → 副手=萤石（充能）→ 附近重生锚=0（炸掉）`。代价是每打死一个怪多花一份材料，手册里写了（把「轰炸最短间隔」调大或关掉「战斗模式轰炸」）。


- ② **副手展示只亮 1 tick 的真凶**（反馈："副手的物品没有换，哪怕只显示一下也行啊"）：`BombPose.show` 新建记录时**忘了写展示时长**（字段默认 0）→ 下一 tick 就还原，那件东西只在副手上存在 **1 tick（16 毫秒）**，屏幕上一帧都看不到——这就是"所有链路的副手都没换"。顺带修掉同源的第二处：`BombPose.tick` 每次被调用都扣 1，而 core 行为同一 tick 可能被评估多次（请求 40 tick、不到 20 tick 就还原）→ 改成按 gameTime 节流。实测：修复前 +2 tick 就变回盾牌；修复后 +1/+2/+8/+20/+40 tick 一直是重生锚。


- ③ **粉色火焰的伤害保护实测有效**：上服直接对女仆调 `entityInside`（原版会为 AABB 内每一格方块调它）——粉色火 + 保护开 → 血量 20.0、**没被点燃**；保护关（对照）→ 血量 19.0、点燃 160 tick。所以"被粉色火烧到"其实是**同一场爆炸里残留的原版橙色火**，那一半已在 实测五百九十八 根除（点火那一刻就换成粉色火）。**面板位置**：模组详细配置 → 战斗与自保 → 空袭数值 → 「副手动作表现」下面三条（`爆炸火焰改粉色` / `粉色火焰渲染` / `火焰伤害保护`），也可以直接改 `config/promaid-common.toml` 的 `[combat.bombing]` 段。


- ④ **验证**：两树编译零错、两个 jar 重建、四处回归全 PASS；上服实测（临时探针，验完即删）覆盖上面每一条，含"目标死后相位继续"的完整链路（起手 → 目标死 → 放锚 → 换萤石 → 起爆）。**要你进游戏验收**：那一记打死怪之后她有没有把重生锚放下、掏出萤石充能、副手三下都看得见；搭路/建造/酿造的副手是不是真的举起来了；站在粉色火里掉不掉血。








## 实测五百九十八【回收方块"整体还她"；粉色火焰三个真凶；issue #12 过滤追查；issue #14 判据统一】





- ① **回收改成"她放下去的那一件，原样还她"**（反馈："回收方块的时候是整体回到女仆的背包，但是发现在搭建萤石的时候会掉落萤石粉"）：旧版回收走 `Block.getDrops`（**挖掘口径**），可这些方块是她**从自己背包里拿出那件物品**去垫的（搭路 / 搭高 / 战斗垫脚 / 深井挖矿）。新公用方法 `PlacedBlockTracker.reclaimDrops`：方块有对应物品就还 1 个该物品本身，只有没有物品形式的方块（水 / 岩浆 / 火 / 红石线 / 耕地）才回退掉落表。**上服逐块实测**：萤石 `萤石粉×2 → 萤石×1`、草方块 `泥土 → 草方块`、石头 `圆石 → 石头`、树叶 `什么都不掉 → 树叶×1`、黏土 `黏土球×4 → 黏土×1`、钻石矿 `钻石 → 钻石矿`。


- ② **粉色火焰三个真凶**：**(a) 没中文名** → 两树 lang 补 `block.maid_smart.pink_fire`（粉色火焰 / Pink Fire）；**(b) 永远不熄灭** → `tick` 里那句"排下一拍"误用了 `m_6933_`（那是 `Level.setBlock` 的四参重载，**不是 `scheduleTick`**；真正排 tick 是 `m_186460_`），于是火只在原版 `onPlace` 那一拍动过一次（AGE 0→1）就再也不 tick——现在照原版口径改 AGE（flag 4）+ `scheduleTick`，上服实测 AGE 0 → 8 秒后 4（下一拍自灭）；**(c) 又粉又橙** → 旧版"爆炸后按盒子扫一遍再换"永远罩不住：原版点火位置来自 `Explosion` 的**射线**集，空气阻力 0、每步只花 0.09 能量，功率 5 的一炸能跑到**约 20 格开外**。改成**边点边换**：爆炸前后开/关"点火窗口"，新 mixin `BaseFireBlockPinkMixin` 在窗口内把 `BaseFireBlock.getState` 的返回值直接翻译成粉色火（上服实测：窗口关=`minecraft:fire`、窗口开=`maid_smart:pink_fire`），旧 `scanFire/pinkifyFire` 整段删除。窗口外（打火石 / 闪电 / 火焰弹）一概不动。


- ③ **issue #12 追查**：反馈者的推测成立——墙上方块的 `asItem()` **不是 air**，而是**前身物品**（`redstone_wall_torch → 红石火把`、`oak_wall_sign → 橡木告示牌`、`wall_torch → 火把`，上服实测），所以解析期那句 `asItem() == AIR → 跳过` **对它们从来没生效过**。现在解析期过滤改用与材料链同一个 `itemForBlock()`：真正没有物品形式的方块（火 / 传送门 / 活塞头）才跳过，**跳过的格数写进日志**（旧版静默）。用他的附件实测：`chuachuan-12w-cobblegen.litematic` 解析出 879 步，那两个墙上件都在计划里、归一成红石火把 / 橡木告示牌。


- ④ **issue #14 判据统一**：新增 `isMachineForMaterials(id)` = **文件名关键词 ∪ 图纸内容**，`缺料同类宽松 = machine` 改用它（不会再"水留下了、材料却不放宽"）；机器专属搭建顺序 / 活放置**仍按文件名判**（与 #15 那轮说好的边界一致）。内容判据补一类**结构件**（岩浆块 / 冰 / 蓝冰 / 灵魂沙 / 气泡柱 / 铁砧 / 石匠台 / 堆肥桶…）——纯物理结构型机器几乎没有红石件：判据为 `红石件 ≥3` 或 `红石件 ≥1 且 结构件 ≥1` 或 `结构件 ≥2`。**判成非机器因而没放宽时日志写明原因**（含"红石/物流件 N 种 / 结构件 M 种"），剥离日志也带上判据数字。


- ⑤ **issue #16 新回复（鞘翅赶路）**：口径 = **传送保底不动**（远距拉回 / 牵引绳 / 跨维度跟随照旧传送，省耐久、不误事），鞘翅赶路只作观赏；反馈者的分支要提 PR 时挂在**开关（默认关）**后面，不做默认行为。本批不动代码。


- ⑥ **验证**：两树编译零错、两个 jar 重建、三处部署 hash 一致、四个回归全 PASS；上服实测（临时探针，验完即删）覆盖上面每一条。**要你进游戏验收**：回收回来的萤石是不是整块、那一炸的火是不是一片全粉且不烧你、方块名是不是「粉色火焰」、非机器图纸不放宽时日志能不能读到原因。








## 实测五百九十七【副手持物推广到全链路；爆炸火焰改粉色 + 玩家女仆免伤；坐下不搭路；issue #16 法术冷却】





- ① **副手动作表现推广到所有"从背包掏出来就用"的链路**（反馈："放置重生锚的时候副手应该要拿对应的方块，使用荧石激活的时候也是……任何一个链路行动的时候副手都应该拿对应的物品，现版本只有动作"）：搭路（4 条腿）、插火把、建造（主循环 + 延后补建）、种植、酿造（9 处下料）、喂主人（药水/金苹果/牛奶/蜂蜜/饮品）、轰炸——每一处副手都举她**这一下真正在用的那一件**（总开关仍是 `combat.bombing.pose`）。**已在主手看得见的链路不动**（挖矿的镐、砍树的斧、耕地/骨粉）。顺带修掉两个坑：撞上"烟花姿势正在用副手"时旧版**直接丢弃**（那一记动作整个消失）→ 改成记待办、等它还原完再举；改完副手**主动补一包**装备同步包（展示只有 10 tick，不能靠"应该会同步"）。


- ② **爆炸火焰改粉色**（新方块 `maid_smart:pink_fire`）：javap 实证原版点火只看 `fire=true`、与我们"不破坏方块"无关（四类炸弹里只有重生锚与床带火）；火焰贴图蓝通道几乎为 0，**任何 tint 都乘不出粉色**，所以新增一个继承 `FireBlock` 的自定义方块 + 把原版 `fire_0/fire_1` 做**色相映射**到粉色的贴图（形状与 32 帧动画照旧）+ 照抄原版的 blockstate 与 10 份模型。三条行为收紧：**不蔓延**（原版传火逻辑整段替换）、**几秒后自灭**（约 3~5 秒）、**玩家与女仆免疫**（点燃与掉血都跳过；其它生物照烧）。只换"这一炸新点着的"火（爆炸前记一份快照），主人自己的火堆不受影响。三个开关：`bombing.pinkFire`（默认开）/ `bombing.fireRender`（默认开：关掉后火还在、只是不画）/ `bombing.fireProtect`（默认开：玩家与女仆免伤）。


- ③ **坐下不搭路**（反馈："女仆坐下的时候还是会触发搭路"）：`BridgeUpBehavior` 的 canUse / canStillUse 里一条坐姿判定都没有——坐在椅子上（passenger）或被叫坐下（sitting pose）时照旧启动。现在两处都拦，中止原因单独记 `sitting`。


- ④ **issue #16：法术自身冷却被多乘 20**：ISS 的 `AbstractSpell#getSpellCooldown()` 返回的**本来就是 tick**，旧版又乘了 20 → 烈焰冲锋（200 tick）被写成 4000 tick = 200 秒，"提供速度"第一次放过之后两百秒再也不放。现在原样返回。


- ⑤ **验证**：两树编译零错、两个 jar 重建、三处部署 hash 一致、四个回归全 PASS（两版本各跑 专用服务器启动 + 召唤女仆）；粉色火方块的上服实测（临时探针）确认 `fromVanillaFire` → `setBlock` 真的把那一格换成了粉色火、底面支撑判定为可存活。**要你进游戏验收**：副手拿的东西、粉色火与免伤、坐下不搭路、法术反复放。





## 实测五百九十六【第 2 个问题（收放魂符后装备被吞）没修好的真凶：我们自己的"入世界自动补包"每次魂符放出都把客户端那只女仆删了重建】





- ① **先把服务端拖拽逻辑洗清**（实测）：临时探针用假玩家在带【万法皆通】的测试服上真跑点击（头甲→物品栏、胸甲→物品栏、物品栏→头甲、原地四组），**东西都老老实实进了她背包**——槽位映射、`mayPlace`、背包黑名单都没问题（探针验完已删，不进包）。顺带实证：无背包女仆的容器是 49 槽，`addMainDefaultInv` 在 i==5 那轮加了两遍同一个物品栏槽（第 47、48 号都绑 `maidInv[5]`），两树一致、不是吞装备的原因。


- ② **真凶：我们自己的"入世界自动补包"**（实测五百六十五/五百六十六 加的那一枪）——**收放魂符的"放出"就是一次入世界**（TLM `spawnFromStore`：新建 → `load(快照)` → 加入世界），它却无条件发"删实体 + 重新生成"，等于**每收放一次就把客户端那只女仆销毁重建一次**（现场日志 21:09~21:11 十几轮「离场 → 重同步」）。两个后果：**a)** TLM 容器槽位绑的是"建容器那一刻那只实体"的背包/装备句柄，重建后容器还指着已销毁的那只 = 客户端对着尸体读写她的物品栏；**b)** TLM 在"开始追踪"时另发的**饰品全量同步 / YSM 模型数据**不在我们那四个包里，被一起抹掉（法术模组判"锚核"也读它）。另外法术模组的 `ClientMaidRemovalGuard` 会拦下删包，拦下后我们那发"生成"就成了幽灵副本。


- ③ **修法：补包按实体网络 id 分流**（`LAST_ENTITY_ID`，入世界/离世界各记一次）：**id 变了 = 新实体（魂符放出/重新召唤）→ 不补包**，原版追踪本来就会发全套；**id 没变 = 同一只离场又回来 → 照旧补包**（这才是当初要治的"客户端把她弄丢了"）；**本次进程第一次见到 → 不补**。补包那一支同时**补发 TLM 饰品全量同步**。五百九十五 的"界面开着就推迟"保留。


- ④ **新增「装备离开」诊断日志**（`ProMaidExtension.onMaidEquipmentLeave`）：女仆装备槽从有东西变空时记一行——**槽位 + 丢的物品 + 她背包实时快照 + 8 帧调用栈**（同只女仆 1.5 秒最多一条）。栈里是 `AbstractContainerMenu.clicked` 就是玩家拖拽；是别的类就是"谁把装备拿走了"。**再复现一次把 `promaid.log` 发我（搜「装备离开」）就能直接定位。**


- ⑤ 验证：两树编译 0 错；重打两个 jar 并部署 3 个目标 `match=True`；回归 `test_server.py` / `test_maid_load.py` 两版本 **4/4 PASS**；探针与临时世界已清理。





## 实测五百九十五【收放魂符后血量上限掉回 20 的根因（我们自己那一枪）、装备被吞的那个窗口、重生锚用萤石不是萤石粉、撤掉上一批气泡】





- ① **重生锚充能用【萤石】（方块）而不是萤石粉**（回答你的问题，反编译实证）：原版 `RespawnAnchorBlock.isRespawnFuel` 只有一句 `stack.is(Items.GLOWSTONE)`——`Items.GLOWSTONE` 就是 `minecraft:glowstone` **方块**的物品形式（字节码：`Items.f_42054_` 由 `Blocks.f_50141_` 生成，后者正是 `register("glowstone", …)`）；萤石粉是另一个字段（`glowstone_dust` = `Items.f_42525_`），**充不了能**。我们内部常量一直是 `minecraft:glowstone`，没错。


- ② **bug 2【收放魂符后血量上限掉回 20】的根因：我们自己那一枪漏了一包**（你的判断是对的，与别的模组无关）：原版只在"开始追踪"那一刻下发属性表（`ServerEntity.sendPairingData`），之后仅属性变脏才补发；而实测五百六十五/五百六十六 加的"入世界自动补包"（删实体 + 重新生成）旧版只补了"同步数据 + 装备"，**没补属性表** → 客户端那只新实体属性全是默认值（TLM 女仆 MAX_HEALTH 默认 20），于是收放魂符后客户端算出来的上限就是 20；服务端属性一变脏（好感度升阶 `setBaseValue`）原版又把 80 补回来——**这就是你看到的 80/20 反复横跳**。服务端那一侧本来就是无损的（原版 `LivingEntity` 读档先读 `Attributes` 再读 `Health`，属性连 `Base` 一起存）。修法：`resyncTo` 按原版口径补全 —— 同步数据 → **属性表** → 乘客 → 装备（两个 jar 的字节码已核对带上 `ClientboundUpdateAttributesPacket`）。


- ③ **bug 3【收放魂符后拖装备被吞】的根因与修法：同一个"删+生成"落在她界面开着的窗口里**：玩家正开着她的物品栏/装备栏时重建客户端实体，TLM 容器槽位抓的还是那只**已被删掉**的实体、背包/饰品句柄一起作废 → 客户端"哪一格有什么"与服务端错位，来回拖就会吞装备。自动补包是入世界后 20 tick 触发，而"魂符放出"正好是一次入世界，玩家顺手开界面整理装备就撞上。修法：见到 `maid.guiOpening`（TLM 公开字段）就**推迟 1 秒再看、最多 10 次**，她一直开着界面就不补（关掉界面后的下次入世界还会登记；日志搜「重同步推迟」）。


- ④ **"还东西"的路径全部改成损失免疫**（同一类隐患，新增 `com.maidsmart.tool.MaidGiveBack`）：`insertItemStacked` 堆叠插入、拿到塞不进去的那一份，有剩就掉在她脚下——最坏落地、绝不消失。`MaidBombing.giveBack` 与 `MaidFlightKit.giveBack`（空袭换鞘翅/换武器换下来的胸甲与武器）改走它：旧写法"找第一个空格 → insertItem → **不看返回值直接 return**"，那一格被女仆背包禁放规则或模组背包限制拒收时整栈被退回来、旧版拿到就丢。`BombPose` / `FlightFireworkPose` 的归还也不再覆盖玩家在展示窗口里刚放进副手的东西（原物进背包 / 落地）。


- ⑤ **撤掉上一批（实测五百九十四）加的气泡**：重生锚那一段没开成的**原因只写日志**了（搜「轰炸链路没开成」），60 秒限频不变；手册 ⑦ 段同步改口径。


- ⑥ **验证**：两树逐处同步（36 处改动各命中 1 次、neo 无 SRG 残留增加）、两树编译零错、两个 jar 重打（9,556,954 / 9,579,013 字节，新类 `MaidGiveBack` 已入包）、三实例部署 `match=True`、1.20.1 与 1.21.1 服务器回归 + 女仆实体加载均 PASS。请你实测：升到 80 血 → 收放魂符 → 上限应仍是 80；反复拖动装备不应再被吞。




















## 实测五百九十四【"放不下来"的真相、投掷 TNT 举打火石、动作表现开关】





- ① **"重生锚放不下来"的真相**：翻本机 `latest.log`（1.21.1 实例），那条诊断写的是「重生锚：缺萤石」——她**带着重生锚**，只是背包里没有那 1 颗萤石（原版 0 级充能右键不炸），于是这一段被跳过、退到了床（"床都能放置"就是这个原因）。放置逻辑本身没有问题（与黑曜石同一条路径，javap 已证），卡的是材料。


- ② **把原因说到脸上**（新）：重生锚这一段被跳过时（不管后面是床顶上还是整条链都没开成）她会**冒气泡**：缺萤石 → 「主人，给我 1 颗萤石吧——重生锚要充能 1 级才会炸～」；维度闸拦下 → 「这个维度里重生锚不会炸，我换个办法（维度闸）～」；床被维度闸拦下（主世界用床）同样有气泡。60 秒一条、同一句话不重复，日志同步落「轰炸链路没开成：…」。


- ③ **投掷 TNT 时副手举打火石**（反馈："在扔 TNT 的时候，副手武器应该切换成打火石"）：旧版举的是刚扔出去的那枚 TNT，现在改成**点火那只手里的打火石**；耐久已在投掷时"就地扣 1 点"里处理过，这里只借模型做表现（数量与耐久不动，0.5 秒后原样奉还）。


- ④ **新开关「副手动作表现」**（新配置 `pose`，默认开）：三处副手临时换物（放置举方块 / 充能举萤石 / 投掷举打火石，起爆再举一次）可以整体关掉，关掉只剩挥臂 / 音效 / 爆炸；半路关掉也会原样还回去（还原逻辑独立于开关）。

















## 实测五百九十三【回收落地成掉落物、爆炸特效对齐原版、落点兜底强制放置】





- ① **背包满 → 掉在地上**（反馈："应该变成掉落物"）：底座（黑曜石 / 基岩）仍然先塞回她背包，**塞不进的那一份掉在她脚下**——与挖矿 / 搭路的方块回收完全同一口径（面板与手册的口径说明已同步改掉）。


- ② **爆炸特效对齐原版**（反馈："爆炸的特效太小了，比正常生成的末地水晶和 TNT 要小很多"）：根因是"不破坏方块"这个模式——原版只在**会破坏方块的爆炸**里放那团大粒子，`ExplosionInteraction.NONE` 恒走小粒子分支（javap 实证：`Explosion.m_46075_` 的两个粒子分支 + `ParticleTypes` 注册名对照）。现在不破坏方块时会**补发一枚同样的大粒子**（只补视觉，伤害 / 击退 / 地形一概不动）；打开「轰炸破坏方块」时原版本来就有大粒子、不重复补。


- ③ **重生锚"放不下来"的兜底**：javap 全量方法表证明 `RespawnAnchorBlock` 的放置规则与黑曜石完全一样（没有覆写 canSurvive / getStateForPlacement），问题在原版放置判定本身（最典型：落点那一格正站着实体 → `isUnobstructed` 直接拒绝）。现在原版拒绝之后会**补一手强制放下**（仅当那一格确实可替换、且不是床）并写日志；"放不下"的日志会写明三段找法都没成，「轰炸跳过」会写明缺萤石或哪个维度被闸（带维度键）。














## 实测五百九十二【萤石回归（重生锚完整动作链）+ 轰炸推广到所有攻击模式】





- ① **萤石要回来**（反馈："不行啊，重生锚还是需要一个萤石进行充能的。所以正确的链路应该是攻击→副手换成重生锚，放置重生锚（摆臂）→副手换成萤石，拿一颗萤石充能（摆臂动画）→继续切换回飞行。0.5 秒后再挥一次手臂。正好对上重生锚自爆"）：上一版把"不需要这个机制"当成引信、把萤石去掉了——现在整段按原话重排：**攻击打完 → 副手换成重生锚 → 放下（挥臂）→ 副手换成萤石 → 消耗 1 颗萤石充能（挥臂）→ 交还链路**，**0.5 秒后她再挥一次手臂**、正好压上重生锚自己那一炸。1 颗就够的理由：0 级充能不炸，而充能等级只决定"炸不炸"、不决定"炸多狠"（那一炸写死 5.0F，反编译实证）。面板「重生锚需要萤石」（默认开）也回来了。


- ② **轰炸推广到所有攻击模式**（反馈："将末地水晶/重生锚/床的机制推广到所有的攻击模式下（除了远程空袭，因为一直飞在天上，本来就放不了）"）：相位机改由**服务端 tick 每 tick 统一驱动**（不再只由空袭行为推），所有战斗任务都在"刚打完一记"那一刻试起手——地面近战 / 弓弩 / 三叉戟 / 弹幕 / 枪械 / 近战空袭 / 第三方战斗任务都有，**远程空袭按 UID 排除**；材料齐则整段交给相位，相位收尾照旧接着投 TNT。


- ③ **轰炸最短间隔**（新配置 `bombInterval`，默认 200 tick = 10 秒）：轰炸仍挂在攻击链路里（打完一记才放），这条只是两次之间的下限——推广到所有攻击模式之后，不加下限一次挥砍放一枚会在几秒内烧光黑曜石 / 水晶；缺料那一下不占用间隔。


- ④ **硬清相位时回滚**：`forget` 现在会把已放下的方块撤掉并**原物还她**（旧版只 remove，那块黑曜石白留在世界里——空袭每次收尾都走 forget，漏点很常见）。


- ⑤ **面板 / 语言 / 手册**：「近战空袭轰炸」更名为「战斗模式轰炸」（含说明与四份语言文件），面板新增「轰炸最短间隔」、「重生锚需要萤石」回到「放置与材料」小节；手册 ⑦ 段同步改写并修掉上一版残留的一句重复文本。














## 实测五百九十一【重生锚去萤石、放置间隔、维度闸、追踪限时限转角、打火石耐久、动作表现、回收归属】





- ① **重生锚不再需要萤石**（反馈："重生锚还是放不了，放置逻辑改为跟放黑曜石一样"、"重生锚和床不需要有这个机制，因为它自己会炸"）：旧版这条链路要求「重生锚 + 萤石」，没萤石就**静默**跳过——这就是"放不了"的根因（那一炸固定 5.0F、与充能等级无关，引信本来就是我们自己点的）。现在它与黑曜石走**同一条放置口径**，放完照原版 `charge` 补 1 级（音效 + 等级变化 + 挥臂），不消耗任何引信材料；面板那条「重生锚需要萤石」已撤掉。另外补上**缺料日志**：带着材料却没开链路时写「轰炸跳过（没有可用链路）」并列出缺哪件 / 哪条被维度闸拦下（每 10 秒最多一条）。


- ② **看得见的放置间隔**（反馈："黑耀石和末影水晶几乎是同时放置的，根本看不出间隔"）：放底座与挂水晶 / 充能之间插入 **0.5 秒**间隔（新配置 `placeGap`，默认 10 tick，可调 0~40）。


- ③ **维度闸**（反馈："其他模组会添加其他的维度，在其他的维度不会爆炸，必须要检测当前维度有可以爆炸的代码才开放这条链路"）：判据就是原版自己判断要不要炸的那两句（重生锚 `respawnAnchorWorks`、床 `bedWorks`，false 才炸）；模组 / 数据包新增的维度写成"这个维度能用"就整段不开放；新开关 `dimensionGuard`（默认开）。


- ④ **TNT 追踪限时 + 限转角**（反馈："还是太鬼畜了，运动很不自然，追踪时间控制在 0.5 秒左右，0.5 秒之后不再追踪，时间可调"）：只在**离手后 0.5 秒**（新配置 `tntTrackTicks`，默认 10 tick，可调 0~200）内修正方向，且每 tick 最多转 **5 度**——一段平滑小弧线、只小幅修准，到点按当时方向直飞；仍然只改方向、速度不变。


- ⑤ **打火石就地扣耐久**（反馈："扔 TNT 的时候会直接把打火石吞掉，而不是消耗打火石的耐久"）：旧版把整件取出、只对取出的那份扣耐久；现在从**同一格**取出 → 扣 1 点 → 放回**同一格**，耐久见底才真的少一件。


- ⑥ **动作表现**（反馈："放置方块/给重生锚充能/扔 tnt 的时候要有一个动作，使用动作的时候替换一下副手上的物品；放置完重生锚/末地水晶后 0.5s 也会有一个挥臂的动作"）：新类 `BombPose`——那一刻副手短暂举起**她正在用的那件**（复制品、0.5 秒后原物奉还），**起爆那一刻再挥一记**（她还在 8 格内才做）；与烟花姿势互不打扰（免得两套快照互相覆盖弄丢盾牌 / 食物）。


- ⑦ **回收归属**（反馈："黑曜石的回收是回收到女仆的背包里" + 口径确认"回收一律不掉落，这条也套用到重生锚与床"）：底座**变成物品收进她背包**（背包满就就地消失、**不掉落地面**，所以不会白送黑曜石）；重生锚 / 床**自己那一炸就消耗掉了**（原版口径），起爆即撤、不回背包；相位失败回滚时**原物还她**。


- ⑧ **面板 / 手册 / 语言**：新增「放置间隔」「TNT 追踪时长」「维度闸（原版会炸才开）」三行，撤掉「重生锚需要萤石」；手册 ⑦ 段同步改写并新增"她带着材料却没放？搜「轰炸跳过」"一条。














## 实测五百九十【TNT 挂进攻击链路末段 + 小型追踪、傀儡模式（傀儡装配）黑名单】





- ① **TNT 改成"打完一记才扔"**（反馈："最好是跟末影水晶一样放在攻击链条的某个部分……适当的正常攻击链路走完之后加到最后。而不是一个固定的 Cd，CD 仅作为最小释放，间隔 10 秒左右"）：攻击链路收尾才登记一发待投放——近战空袭是「猛击命中 → 该段的炸弹 → TNT」，远程空袭是「开火之后」，其余战斗任务按「攻击冷却记忆刚写入」判定；投掷间隔默认 **40 → 200 tick（2 秒 → 10 秒）**，而且**只当最短间隔**（没到点就挂着、等下一记），不再是驱动本身；老配置自动迁移（120 → 40 → 200，自己调过的不动）；


- ② **TNT 小型追踪**（反馈："TNT 再加一个小型追踪功能，TNT 会朝着目标的方向飞行（只更改方向，速度不变）直到爆炸"）：引信期间每 tick 把速度**方向**掰向目标、**速度大小不变**，并按 −0.04/tick 重力做垂向补偿（只拐弯、不加速），直到爆炸；目标没了/换维度就按当前朝向直飞。新开关 **「TNT 追踪飞行」**（默认开）；


- ③ **傀儡模式自动列入黑名单**（反馈："如果检测到有傀儡模式，那么会自动将他列入黑名单。自主切换和主动作战不会切换到这个模式"）：对象 =《傀儡装配》Modular Golems（`modulargolems`）给女仆注册的**「傀儡师」**任务（javap 实证 `MaidSummonerTask implements IRangedAttackTask`，UID `modulargolems:summon_golems`，条件 = 主手持万能手杖）——**自主参战选任务 / 战中换战术**的任务池与 **LLM 自主切换**的可用任务列表里直接过滤掉（永不切进去）；**TLM 自带的 `switch_work_task` 工具也上了一道闸**（mixin 在工具调用入口按 UID 拒绝——`AutonomousTaskManager` 的自主决策驱动的就是它，那条路完全不经过本模组代码；`require = 0`，注入点对不上时只失效不崩服）；没装那个模组时它只是一句恒假判断；


- ④ **手动切进去之后，本模组战术全体让位**（反馈："原本这个 mod 安排的所有战术全都暂时无效，仅保留那个模组原汁原味的傀儡装配玩法"）：单兵走位/跳劈/举盾与威胁驱动、投弹与轰炸、自动换装、自主参战与还原、排班换段**全部停手**，切回来即自动恢复（存档数据未改动）；**保命动作不在让位之列**（自保/落地水/防火/宠物免疫）；新开关 **「傀儡模式黑名单」**（默认开，面板「战斗与自保 → 空袭数值 → 第三方玩法模式」）；


- ⑤ **面板 / 手册 / 语言**：面板新增两行（TNT 追踪飞行、傀儡模式黑名单）并改写"投掷间隔"说明；手册⑩段补上链路与追踪、新增 ⑪【第三方玩法模式：傀儡师】；四份语言文件同步；














## 实测五百八十九【TNT 准度/CD、空中强制放置、水晶外观与黑曜石保留、萤石判定、粉色标记】





- ① **TNT 准度**：旧版忽略了 0.98/tick 的水平阻尼（水平位移是等比级数、射程有限），目标一动就偏；现在改成**闭式弹道解**（每 tick 先 −0.04 重力、再位移、最后整体 ×0.98 → 先解飞行时间 n、再解竖直初速 vy0）并给目标加**提前量**，落点误差只剩地形阻挡；


- ② **TNT 冷却**：默认 120 → **40 tick（6 秒 → 2 秒）**；老配置里还是 120 的会在启动时自动迁移一次（自己调过的不动）；


- ③ **空中也能放**：落点三级找法——① 目标脚边四邻 → ② **她正下方**往下扫 16 格（「落点位于自己下方可放置方块的区域」）→ ③ 都没有支撑面就**直接悬空放下**（新开关「空中强制放置」，默认开；原版放置本身允许悬空，只是玩家手点不到空气）；关掉第 ③ 级则回到"找不到带支撑的落点就整段跳过"；


- ④ **重生锚与萤石**：**放置不需要萤石**，**要它炸**才需要 ≥1 级充能（原版规则：0 级不炸）；威力是写死的 5.0F、与等级无关，1 级足够。默认仍要 1 颗萤石点火（照原版 charge：音效 + 挥臂）；新增开关**「重生锚需要萤石」**（默认开），关掉就不消耗萤石、她直接补上那 1 级；


- ⑤ **水晶外观 + 黑曜石保留**：水晶改成 `setShowBottom(false)` = **原版物品放置的样子**（不是末地柱子上那种带底座的）；底座黑曜石**起爆后不再立刻消失**，新增**「炸弹底座回收延迟」**（秒，默认 10，0 = 起爆即回收）到期由她回收，**回收不掉落**、不会白送材料；


- ⑥ **粉色标记**：填充 0.16/0.20 → **0.34/0.38**，方块标记也加**亮粉棱线**，并在上方浮一行 **「友军炸弹」**（跟着 TNT 飞的那份会一起动）——之前太淡看不出是友方的。














## 实测五百八十八【TNT 投掷推广到所有战斗标签模式 + 防误伤 + 自爆风免】





- ① **TNT 投掷不再只属于远程空袭**：改成**所有有战斗标签的模式**都会扔（近战 / 弓弩 / 三叉戟 / 弹幕 / 枪械 / 近战空袭 / 远程空袭 / 第三方战斗任务）——判定用既有 `MaidWorkTags.isCombatTask`（`IAttackTask` 接口 + UID 兜底），服务端每 4 tick 扫一次，条件 = 战斗任务 + 有 TNT 与打火石 + 冷却到点 + 半径内有合法目标；投掷手法照《女仆生存》那套思路自己实现（身位 75% 高度朝目标中心抛、水平单位向量 × 初速 + 竖直补偿）；


- ② **参数对齐女仆生存**：索敌半径 **12 格**、冷却沿用本模组 6 秒、引信 2 秒、初速 0.9；**残血（≤ 七成血）连投最多 3 发**、横向散开（女仆生存的"低血量爆发"）；空中地面都能扔；


- ③ **防误伤（两道）**：主人 / 同主女仆 / 友军**绝不会成为投掷目标**（先过友军判定、再过任务自己的 `canAttack`）；炸弹的伤害与击飞对主人/友军一律不生效；


- ④ **自爆风免（与重锤不同）**：重锤风爆会把包括她在内的所有人掀飞；**这条链路做的炸弹，它的风对放炸弹的女仆本人也不生效**（爆炸期间挂窗口，风免的两条路径都查它）——她不会被自己的炸弹推飞，伤害本来就已经免疫；烟花推进 / 风弹自起跳 / 空袭弹开这些"她自己的机动"一字未改；


- ⑤ **新增 3 项配置**（「战斗与自保 → 空袭数值 → ⑦ 空袭轰炸」）：**投掷索敌半径**（默认 12）、**残血连投阈值**（默认 0.7）、**连投最多**（默认 3，填 1 = 关掉连投）；该小节改名为「所有战斗模式：TNT 投掷」；


- ⑥ **排查**：运行日志搜「空袭轰炸」——投掷会写「谁投掷 TNT ×N（引信 … tick）」。














## 实测五百八十七【空袭轰炸：末地水晶 / 重生锚 / 床 + 远程投掷 TNT】





- ① **近战空袭多了一段轰炸（默认开）**：猛击命中之后、再次起飞之前，按包里材料放一枚炸弹——**① 黑曜石/基岩 + 末地水晶（威力 6，优先）② 重生锚 + 萤石（威力 5，下界不生效）③ 床（威力 5，主世界不生效）**；三段按顺序取第一个材料齐的，**放不下就整段跳过**（悬空目标脚边没地面、位置被占等），直接接着起飞、不卡链路；落点选"离她最近的目标脚边那一格"（要有实心支撑、不把她自己埋了）；


- ② **重生锚只要 1 颗萤石**：反编译实证那一炸的威力是**写死的 5.0F**，与萤石充能等级**无关**（充能等级只决定"炸不炸"：0 级右键不炸），所以不需要"充到 4 级"那套功能——放下后用萤石充 1 级（照原版 `charge`，有音效 + 挥臂）；同一批数字：末地水晶 6.0F、床 5.0F、TNT 4.0F；床/重生锚的生效维度也是原版口径（床在主世界、重生锚在下界都不炸）；


- ③ **远程空袭多了一条附加链路（默认开）**：天上盘旋期间额外扔 TNT（默认 6 秒一发、引信 2 秒），需要**同时有 TNT + 打火石**（每发 1 个 TNT、打火石掉 1 耐久）；**缺料静默跳过**，不影响弓弩/枪械开火；


- ④ **爆炸口径默认全保护（四个开关都在「战斗与自保 → 空袭数值 → ⑦ 空袭轰炸」）**：**不伤主人/友军**（炸弹归因给女仆 → 伤害走友伤守卫、击退走重锤同款风免；她自己也不会被自己的炸弹炸伤，只被震开当起飞推力）、**不破坏方块**（只炸伤害与击退，地形一点不动）、**淡粉色标记**（默认开，纯客户端：她刚放的方块/水晶/扔出的 TNT 套一层很淡的粉框，TNT 那层框跟着飞）；打开「伤到主人/友军」= 完全原版（主人照掉血照被炸飞，她也吃自己那一发），打开「破坏方块」= 原版爆炸照原样炸坑；


- ⑤ **她放的方块一定回收**：无论开关如何，黑曜石/重生锚/床都在起爆时由她撤掉（不掉落）——不会在世界里留永久黑曜石、也就没有"白送材料"；


- ⑥ **排查**：日志搜「空袭轰炸」——会写"谁放下了什么、放在哪个坐标、多少 tick 后起爆、威力多少"；放不下时也会写明原因；想看全过程可把「起爆延迟」调大（默认 10 tick = 0.5 秒）。














## 实测五百八十六【缺料同类宽松：建筑严格 / 机器放宽（issue #14）】





- ① **新档位 `build.looseMaterials`（off / machine / always，默认 machine）**：机器蓝图里允许 **任意告示牌↔任意告示牌、任意树叶↔任意树叶、任意羊毛/地毯↔同类型、任意染色玻璃/玻璃板↔同类型** 互替（它们在机器里只是挡水/标记/遮光/观察窗的功能件），建筑图纸保持严格（配色/树种不会被动，樱花树不会被换成橡树）；off = 旧行为，always = 全都放宽；悬挂告示牌单独一桶（物品与放法都不同）；


- ② **分桶不写死名单**：按物品注册名后缀现场扫一次注册表并缓存（模组方块自动纳入），墙上版本先经五百八十三的归一变成前身物品（`oak_wall_sign → oak_sign`），所以立式/墙上天然同族；


- ③ **口径统一**：新增 `equivalentGroup()` 作为等价族唯一入口，材料统计 / 取料 / 主人背包转移 / 替代品验收 / 创造豁免 6 处全走它——面板与气泡里的数字跟实际会用到的材料一致；建造行为每 tick、建造入口、容器取料各登记一次"当前图纸"，machine 档据此生效；


- ④ 面板「模组详细配置 → 生产与工作 → 建造 → 缺料同类宽松」，手册建造章补【缺一个颜色就整台卡住？】；








## 实测五百八十五【流体保留判据可配置 + 剥离不再静默（issue #15）】





- ① **流体的保留/剥离不再只认文件名关键词**：`build.keepFluids` = auto（默认）/ always / never——auto 档除文件名里的机器关键词外，**还看图内容**（调色板里出现 ≥3 种红石机器件即判机器图纸；"一个拉杆+一盏灯"的装饰房不命中），`川川12w刷石机` 这类名字里不含关键词的机器不再被静默剥掉水/岩浆；


- ② **剥离不再静默**：解析时写运行日志（剥掉多少格 + 两种改成保留的办法：改文件名 / 把配置设成 always），「建造此图纸」的提示里也附一句——不再"机器一动不动、毫无线索"；


- ③ **内容判据只用于流体**，机器专属搭建顺序与活放置口径不变（仍按文件名家族）——避免"看起来像机器就改建造方式"的连带风险；


- ④ 面板新增「图纸流体保留」档位行，手册建造章补【图纸里的水/岩浆怎么办】；








## 实测五百八十四【指标石锁定死锁：进世界同步 + 无条件解锁出口（issue #13）】





- ① **根因**：客户端锁定状态是 static，只在收到 S2C 时更新，而服务端推状态只有 3 个调用点、**没有任何"进入世界时同步"**；单人档退出世界不重载客户端类 → "服务端早清了、客户端还以为锁着"，退出世界重进也没用；


- ② **补上进世界同步**：登录与切换维度各补一次 `syncTo`（会话为空自然推空状态 → 客户端解锁）；


- ③ **客户端断线/重连清镜像**：`ClientPlayerNetworkEvent.LoggingOut / LoggingIn` → 本地 static 归零；


- ④ **无条件解锁出口**：锁定期间 **潜行 + 右键 = 强制解锁**（原来只有"重新右击那一格"一条路，而那格可能已被水/地形挡住或超出 512 格长射线）；服务端仍按原规则校验（女仆搭建中会婉拒）；


- ⑤ 锁定提示与用法提示补上"瞄不到那格怎么办"，手册指标石章新增一节；








## 实测五百八十三【墙上告示牌 / 墙上火把永远报缺料（issue #12）】





- ① **方块→物品归一**：`wall_*`（墙上火把/墙上告示牌/墙上横幅/墙上悬挂告示牌）是**独立方块但没有同名物品**（物品是它的前身 `oak_sign` / `redstone_torch` / `white_banner` / `oak_hanging_sign`）——旧版按方块名搜物品永远搜不到 → 永久报缺料；现在统一做一次"去掉 `wall_` 再查注册表"的归一（模组方块同样适用）；


- ② **`itemForBlock` 对无物品方块返回 null**：物品注册表对查不到的 id 会给 air（不是 null），旧版据此认为"有对应物品"，于是 `MaidBuildBehavior` 里那条「没有对应物品 → 永久跳过并报告」**是死代码**——任何无物品方块都会以"材料不够"的名义永久卡死（实测 8 分钟不动、切创造才过关）；现在快速路径与延后轮询两处都会**跳过 + 播报 + 写 `build skip` 日志**；


- ③ **缺料气泡念物品名**：`redstone_wall_torch` 的中文规则会拼成"红石墙火把"（游戏里搜不到），现在报"红石火把"；


- ④ 入口提示：世界存档提取（.zip 蓝图）不过滤无物品方块，是最容易带进 `wall_*` 的链路；`.litematic/.nbt` 解析期会过滤掉 `asItem()==AIR` 的方块（若日志里确有这两个方块，欢迎把图纸发来核对）；








## 实测五百八十二【版本号升为 v1.2.2 / release 说明改写成「两版差异」】





- ① **版本号 1.2.1 → 1.2.2**：`mods.toml` ×3、构建 / 部署 / 测试脚本的 jar 名、README、发布指南、GIT_SYNC 一并同步；部署脚本的"旧包清理"过滤器改成通用 `promaid-*.jar`（除本次写入的那个以外全删）——换版本号不必再改脚本，也避免同 modId 双 jar 启动失败；


- ② **两份 release 说明重写成「v1.2.1 → v1.2.2 差异」**（Forge / NeoForge 各一份）：一句话总结 + 六节主题（法术兼容 / 空袭手感 / 喂食喂水 / 面板手册 / 兼容稳定 / 4 个新 AI 工具）+ 本版行为变化 + **致谢 @rodericksthescriptkid**（法术兼容由他写成，本人只做实战战术改良与 1.20.1 移植）+ 已知限制；


- ③ 本批**没有代码改动**——内容与实测五百八十一 完全一致，只是换版本号并把发布说明改写成两版差异；








## 实测五百八十一【空袭数值全部可调 / 新增「空袭数值」配置页】





- ① **34 项空袭数值全部可调**：`MaidFlightCombatBehavior` 里写死的飞行常量（起飞时长/仰角、收翅距离/命中判定/俯仰限幅、盘旋半径/期望高度/掉高容差/补推窗口、开火间隔/射程、弹开半径/速度/时长、冲刺距离）全部搬进配置的新分类 `airRaid`——**默认值与原常量一字未改**，行为不变，只是把原来只能改代码的旋钮交出来；调用点改成逐次读配置，改完立即生效；


- ② **面板新增小类页「战斗与自保 → 空袭数值」**：六个小节（起飞与爬升 / 收翅俯冲 / 盘旋与补高 / 远程开火 / 近身弹开 / 位移法术），每项都带说明（默认值、调大调小的后果），两处连带影响（起飞仰角、盘旋高度）在注释里点明；


- ③ **手册**：空袭章新增【空袭数值面板：所有数值都可调】一节，章末「⚙ 配置入口」现在直接跳到这一页；


- ④ **两版差异只有一项**：1.20.1 多「地面近战触及距离」（退路用）、1.21.1 多「猛击下落加成门槛」（重锤下落加成的写回下限）；








## 实测五百八十【喂水走物品自己的使用路径 / 勾选面板按剩余次数细分】





- ① **喂水改成对真栈结算**（旧版"喂副本 + 扣来源"）：多次使用的容器（水壶这类，用完还剩 3/4 次）不再被当一次性扣掉；交还的容器/残余塞回背包，**塞不下就掉在脚下**（旧版静默丢弃）；物品抛异常时来源一点不动；


- ② **勾选面板按「剩余次数」细分**：喂食勾选页与喂水白名单页里，耐久物品（能喂/喝好几档的）每一档剩余次数各占一行——「剩 4/4 次」和「剩 3/4 次」是两个独立条目，各自勾选互不影响，悬停写明「剩余 N/M 次」；名单条目支持手写 `注册名#剩余次数`，不写 # = 该物品任意次数（旧配置照旧生效）；








## 实测五百七十九【盘旋高度 +10 格 / 喂食改走物品自己的 finishUsingItem】





- ① **远程空袭期望盘旋高度 3.5 → 10 格**（按作者口径；3.5 格太薄，滑翔一转弯就低于敌人）——配合上一批修好的补推阈值（低 0.5 格就补）＝"低于期望高度就补"真正成立；射程不受影响（3D 24 格，盘旋 10 格时约 14 格）；


- ② **喂食改走物品自己的 finishUsingItem**（普通食物与旧版 eat() 完全等价，但重写过它的物品才按自己的语义生效）：奇异饰品的「永恒牛排」这类**吃完不消失**的遗物不再被手搓的 `shrink(1)`/`extractItem(1)` 扣掉，它自己的效果照常生效、并按自己的冷却节流（女仆身上读不到物品冷却，本地记 10 秒）；姐妹链 / 主人链 / 自救吃食物 / feedtest 共 7 处站点统一；TLM 的 HEAL_MEAL 回血只在**真吃掉一份**时补调。








## 实测五百七十八【位移法术照抄烟花 / 掉高补推修复 / 空袭续航 / 新增 4 个 AI 工具】





- ① **位移法术的朝向一律照抄烟花（A 方案）**：起飞/补高改走烟花起飞那套（地面目标＝背离敌人＋抬头，目标高出 10 格以上才朝目标爬；抬头角近战 62°/远战 45°），盘旋里用掉高窗口那 45° 抬头——不再自己发明角度（旧版"朝目标抬头 62°"等于往敌人脸上窜，"盘旋圈上冲一口"等于当面切进去）；


- ② **远程空袭"掉高补推"从来没生效过 → 已修**：期望盘旋高度 3.5 格 与 补推阈值 3.0 **相消**，实际触发点是"目标高度 +0.5 格"＝几乎贴地，所以对地面目标永远走不到（反馈"每次都是掉到地上再补"）。阈值改 0.5＝低于高度带半格就补；


- ③ **续航战术**：掉高窗口里按 **位移法术 → 羽扇 → 烟花** 的顺序补推，法术成功时**自己开那个抬头窗口**（"只带法术不带烟花"也能一直飞）；舰升腾/烈焰冲锋的两条口径（水平被放大 3 倍、"抬头 62°"实际约 45°、一次性冲量非持续推力、高速时可能削速）已写进手册与 README；


- ④ **新增 4 个 AI 工具**：`smart_switch_task`（换任务/模式，中文别名，认不出回可用列表）、`smart_air_raid`（起飞/停止空袭，含缺件回报）、`smart_work_area`（定工作区驻守/解除/查看）、`smart_readiness`（只读自检：三件套/弹药/驻守/排班/锚点/血量）。四个开关都在「AI 工具」区，默认开。








## 实测五百七十七【官方译名统一 + 手册空袭法术段致谢】





- ① **官方译名为《车万女仆：万法皆通》**：手册第七/八/十章、配置项注释、配置面板提示、中文语言文件、类注释与 README 里原先混用的旧名《车万女仆：魔法》全部统一（只改显示名，模组 id 与英文名 Touhou Little Maid: Spell 一字未动、行为零变化；历史 changelog 条目保持原样）；


- ② **手册「空袭状态下的法术怎么用」正文首行加致谢**：这套空袭法术兼容主要由 **Roderick32**（GitHub `rodericksthescriptkid`、B 站 `Roderick32`）写成——我对魔法方面并不是很了解，法术兼容这一块交给他接手；我在这里的作用只有在实战方面做了一定程度上的战术改良（法术该在空袭的哪一段放、怎么配合起飞与俯冲），以及把这份兼容移植到 1.20.1。








## 实测五百七十六【羽扇高度回调 / 冲刺法术方向 / 刚放出误报 / 喂水水质】





- ① **羽扇竖直冲量 0.85 → 1.05**（按反馈取"玩家体感的 1.25 倍"；水平分量不动）；


- ② **冲刺法术的旋转动画是法术自己的**（烈焰冲锋是冲刺类法术，法术模组会给施法者播动作；本模组的激流三叉戟链路只认主手三叉戟+激流，日志搜「激流突进」可证）；**方向已修**——盘旋相位她的视线沿切线，放冲刺前先摆正到"朝目标 + 略抬 12°"，不再横着飞出去；


- ③ **刚放出不再误报缺件**：入世界后 0.5 秒静默 + 法术书负缓存缩短到 5 tick + 入世界清缓存（法术数据要一拍才初始化）；


- ④ **喂水水质分级**：口渴模组分四档（肮脏/有点脏/可接受的/纯净），新增「喂水最低水质（0-3）」默认 2 = 可接受的（只对装水容器生效，果汁牛奶不受影响）；喂水白名单子页里每杯水会标出水质。








## 实测五百七十五【1.20.1 法术层修复 / 空袭传送说明纠错 / 排班表归位 / 面板溢出 / 羽扇】





- ① **【1.20.1 关键】法术兼容层被签名差异判死**：插件包 1.20.1 的 `stopAllCasting(女仆)` 与 `setCurrentCastingSpell(SpellData)` 与 1.21.1 不同，旧探针写死 1.21.1 签名 → 整层（顺带施法 + 位移法术）全关。现在两种签名都认、可选能力缺失不再拖垮整层——**带着烈焰冲锋的书（背包/饰品栏）就能平地起飞了**；


- ② **手册空袭传送说明纠错**：飞行期间让位的是"自动传送"（跟随/跨维/搭太高/自保归位），而且让位有界（滞空超 15 秒强制拉回）；**你手动的「一键集合」与主人死亡传送照常生效**（原手册写成"禁止被传送"是错的）；


- ③ **「排班表」配置项从「移动与行为」挪到「生产与工作」**（手册一直这么写，是面板挂错了）；


- ④ **喂食 / 喂水子页窄窗口文字溢出修复**：悬停信息按最宽行收进屏幕、标题与统计行缩短并居中钳制；


- ⑤ **羽扇**：竖直冲量夹到 0.85 格/tick（旧值低速挥扇时达 2.2，比玩家高太多），并补上挥扇动作。








## 实测五百七十四【空袭缺件文案 + 法术进起飞判定 + 手册法术实战指南】





- 缺件气泡从「缺烟花火箭/羽扇（或能上天的位移法术）」改为「**没有可以飞行的道具**」，并在缺这一项时尾注「（烟花火箭 / 孔雀羽扇 / 位移类法术任一）」；


- **可起飞判定**新增总口径：烟花/羽扇可用 **或** 她会"提供高度"的位移法术——与模式激活、缺件提示三处口径终于对齐（内部仍是有烟花先走烟花链路、没有才用位移法术，旧手感不变）；


- 手册「空袭」章新增 **⑨空袭状态下的法术怎么用（配装 + 节奏 + 排查）**：两类法术各司其职、三段施法节奏、她不会做的事、举例（只带升腾也能起飞）、日志怎么对；


- 同章三件套小节改为「③ 可以飞行的道具（三选一）」、常见问题与 README 同步措辞。








## 实测五百七十三【五个问题一起修：羽扇 / 重锤风爆 / 喂水面板 / 河童罗盘 / 魂符模型】





- ① **羽扇不认（放背包也报缺羽扇）**：注册名写成了 `twilightforest:peacock_fan`，真实是 `twilightforest:peacock_feather_fan`（少一个 feather，永远匹配不上）——已修正（两树），背包里放一把即算燃料；


- ② **主人被女仆重锤波及**：原版风爆附魔的爆炸没有来源实体（`wind_burst.json` 无 attribute_to_user），爆炸归因拿不到女仆 → 击退发给了主人的客户端（只震不伤、服务端都没变）。风免归因补第三条兜底（"这一下在谁的 tick 里创建就算谁放的"）；


- ③ **喂水面板照搬喂食**：入口行改为「喂水白名单 · 打开 →（可喂 N / 候选 M）」，子页与食物勾选页同款——搜索 + 图标网格（绿✔可喂 / 红✖不喂，点击切换）+ 翻页 + 手动填注册名 + 底名单「不喂了」一键移出；候选按口渴模组自己的口径扫描；


- ④ **中键工位标记 × 河童的罗盘**：两者写的是**同一份排班锚点**，谁后写谁生效（女仆不会挑）；现手持罗盘时中键自动让位，另加「移动与行为 → 中键工位标记」开关（默认开）；


- ⑤ **1.20.1 魂符放出后模型变灵梦**：客户端实体重同步误用了 packDirty（只发"变化过的值"，通常一个都不发）→ 复活出来的客户端实体保持默认模型；换回 getNonDefaultValues（与 1.21.1 树一致）。








## 实测五百七十二【位移类法术当推进器：平地起飞 + 空中补高 + 飞行加速】（PR #11，@rodericksthescriptkid 社区贡献）





- 需求（法术模组作者转达的玩家反馈）：位移类法术（如「烈焰冲锋」）用于①飞行加速、②平地起飞；


- 按"冲量方向"分两类：「提供高度」（默认升腾+烈焰冲锋）用于平地起飞与空中补高（施法前抬头 62°，并把法术模组的施法目标清空，免得它施法前把朝向掰平）；「提供速度」（默认烈焰冲锋）用于飞行加速（对着目标冲一口续速）；


- 推进剂口径放宽：没有烟花/羽扇时，带得起飞法术 = 三件套照常激活（否则"平地起飞"那条路永远走不到）；


- 指定法术瞬发施法（序列照抄法术模组自己的 /maidspell iron_cast），按她书里铭刻的等级放（等级影响冲量），并写回法术模组自己的冷却表；


- 冷却口径：「提供高度」不受法术自身冷却约束（激流三叉戟先例：位移手段让女仆比玩家宽松——不这样"没烟花也能持续飞"就不成立）；「提供速度」默认尊重法术自身冷却（取 max(间隔, 法术冷却)）；


- 配置六件：flightDashClimb / flightDashBoost / flightDashClimbSpells / flightDashBoostSpells / flightDashInterval（默认 40 tick）/ flightDashBoostRespectCooldown；两树同步（1.20.1 由作者按 SRG 移植）。








## 实测五百七十一【女仆喂水：软联动「口渴」Thirst Was Taken】





- 反馈原文："能让女仆喂水吗（软联动口渴mod）……把判定位点改为口渴值即可……触发效果和玩家自己喝水是一样的（也就是说会返还玻璃瓶这种）……同时给一个像喂食物一样的白名单。如果没有装这个模组，那么此配置项目不会出现。"；


- 贴身辅助新增分支：主人口渴值低于「投喂触发口渴度」（默认 15）时，从女仆背包/手持按口渴恢复量选优喂水；结构照搬喂食链；


- "跟玩家自己喝一样"：代喂走物品自己的 finishUsingItem——药水瓶口渴（TWT MixinPotionItem）、TWT 自家物品、可食用饮品、容器返还（玻璃瓶/陶碗/空桶）全走原版喝的路径，喝完的容器塞回女仆背包；其他 mod 的装水容器由 ThirstCompat 按 TWT 完成钩子同口径兜底调 PlayerThirst.drink（不双重计数）；


- 喂水白名单（aidDrinkWhitelist，默认=水瓶+陶碗水；potion 只认纯净水，其它药水永不喂；留空=不喂水）——其他 mod 的装水工具把注册名加进来即可；


- 两项喂水配置按模组在场条件注册：没装「口渴」时面板与 toml 里都不出现；


- 喂食的容器返还核查：蜂蜜玻璃瓶（v1.5.288 起）与牛奶空桶早已实现，喂水沿用同一口径。








## 实测五百七十【近战空袭起手挥臂 + 起飞前地面补跳】





- 反馈原文："近战空袭也没有攻击动作"、"触发起飞有点困难，触发烟花起飞之前先向上跳跃一下，然后再走原来的流程"；


- 近战空袭：挥臂提前到收翅俯冲起手那一刻（旧版唯一一挥在高速俯冲的最末尾，看不见）；命中结算那一挥保留；三叉戟自转不冲突；


- 起飞：跳起窗口内离地前的每一 tick 都补一跳（旧版重试只摆头不补跳，第一跳被走位吃掉就整轮点不着烟花）；离地后原流程不变。








## 实测五百六十九【攻击动画补全：重锤猛击命中挥臂 + 远程空袭弓/弩/弩烟花开火挥臂】





- 反馈原文："重锤+风弹下落攻击的时候没有攻击的动作，只有效果"、"远程空袭看不太清楚，不确定有没有"；


- 地面重锤猛击（1.21.1）确实没动画：全程直接 hurt 结算，粒子音效全播、唯独没挥臂——补在结算前（出手即挥）；


- 远程空袭缺的是弓/弩箭/弩烟花三分支：出手瞬间补挥臂；三叉戟（自转）、御币（本就挥）、TACZ 枪械（模组自己的开火动画）本来就有；


- 空袭近战猛击与地面普通近战的挥臂本来就有，未改动；1.20.1 树同步补远程三分支。








## 实测五百六十八【TACZ 弹药箱兼容：弹药箱也算「有子弹」】





- 反馈原文："远程空战模式不识别 TACZ 的弹药箱，会提示没有弹药按照普通模式来，但普通模式不会开枪（因为觉得没有弹药）"；


- 根因只有一道门：本模组的枪械弹药判定（空袭弹药门禁 + 自主战斗切枪械共用）只数散装 tacz:ammo，弹药箱（tacz:ammo_box）被当「没子弹」→ 空袭不激活退普通模式、gun_attack 也不进池；


- 消耗侧零改动：TACZ 自己的换弹本来就认弹药箱（isAmmoBoxOfGun 认箱、扣 AmmoCount、打空清 AmmoId，扫女仆护甲+双手+背包+饰品四合一背包）——判定门一开，装填即通；


- 判据与 TACZ canReload 同口径：箱内型号必须对得上枪（9mm 箱喂不了步枪）、创造弹药箱无限、打空的箱不算——比散装「任意 tacz:ammo 都算」更严，不会「判定有弹却装填不上」；


- 顺手补齐：挑枪评分（自动装备「有弹药优先」加成）同样认弹药箱；反射软兼容，没装 TACZ 时与旧版一致。








## 实测五百六十七【空袭·法术层：用飞行武器的同时顺手放法术（需装《车万女仆：魔法》）】（PR #8，@rodericksthescriptkid 社区贡献）





- 叠加层而非新任务：三件套口径不变（鞘翅+武器+烟花），法术书放**背包或饰品栏**即可（法术模组自己扫，不看主手、不占武器位）；没装《车万女仆：魔法》时零效果；


- 只在「本来就该面向目标」的两个相位发起：远战盘旋开火前、近战已爬到目标上方正要俯冲时——吟唱期间法术模组每 tick 把她朝向拧向目标，爬升段（要背离抬头吃烟花推力）与收翅俯冲那一击刻意不施法；


- 三个开关（「主动参战」小类）：空袭顺带施法（默认开）/ 施法间隔（默认 20 tick）/ 施法距离（默认 24 格，与法术模组 maxSpellRange 一致）；隔墙不施法；


- 放哪个法术由法术模组决定（随机挑不在冷却、不在黑名单的；女仆没法力条，只受冷却限制）；


- 确认方法：看效果/特效，或 promaid.log 搜「空袭·法术」（每只 30 秒最多一条）。





**已知限制**：烈焰冲锋（ISS 的 burning_dash）这类自身冲刺法术的「指定法术」受控使用——比如让她主动用冲刺给飞行加速——本版未做；当前只会随机向目标施放不在冷却的指向型法术，作者留待下一轮。





## 实测五百六十六【客户端实体丢失（服务端照打、客户端没有她）→ 重同步兜底】（PR #10，@rodericksthescriptkid 社区贡献）





- 新增 /maid_smart resync（OP）：诊断（每只女仆的维度/坐标 + Sable sub-level 追踪位置反射探查）+ 救急（对同维度主人强制重发"删+生成+数据+装备"四包，纯补包不改服务端状态）；


- 自动兜底：女仆重新入世界 / 离场时各登记一次延迟 20 tick 的自动补包（同维度且主人在线才真补）——把"法术模组对没带锚核女仆离场只删不补 + Sable 改追踪位置"的组合失配两头都盖住；


- 离场诊断日志：女仆离开世界时记录移除原因与维度，"她为什么消失"从此有第一现场。





## 实测五百六十五【空袭召回永久禁传 → 空中让位改为有界】（PR #10，@rodericksthescriptkid 社区贡献）





- 现场实例：空袭女仆在异维度打完 BOSS 后被困，45 分钟里每 60 秒一条「飞行作战进行中…不传」，客户端连实体都没有；


- 根因："重锤跃起/飞行作战进行中不传送"的让位没有上限，而空袭女仆整个作战期间都在空中——一旦她落在远处/别的维度，这道闸就是永久禁传；


- 修法：让位改为有界——跨维度需确认主人存活且确实异维度、同维度需距离/高度确实超线，才启动 15 秒计时；超时就清干净空袭状态（收翅 + 复位本轮）后强拉回来；近处的正常让位完全不计时。








## 实测五百六十四【Sable 同装时：全图扫描被拒查 → 一大片功能静默失效 + 日志刷爆】（PR #9，@rodericksthescriptkid 社区贡献）





**现象**：装了 Sable（Create Aeronautics 的物理引擎）后 latest.log 被


「Aborting entity get for abnormally large AABB」刷爆（实测 ~1 小时 19 万条 / 500MB+）。





**根因**（Sable 2.0.5 字节码实证）：SubLevelInclusiveLevelEntityGetter#get(AABB, Consumer)


开头即 if (box.getSize() > 100000.0) { logError(box); return; }——本模组用了多年的


"有限值全世界 AABB"（±131072/±4096，getSize≈3.7e5）一个实体都不会交付。





**影响面**（在 Sable 环境里等于彻底失效，不只是刷日志）：农场锄地/骨粉催熟、钓鱼座椅走位、


指标石找女仆（含客户端预览连线）、自主战斗还原扫描、仇恨清除、中立威胁驱动、宠物免疫、


home 巡逻/走动、险境脱离、危险减益、排班扫描、搭路状态清理、蓝图背包检查。





**改法**：全图扫描统一改 level.getAllEntities()（客户端那处改 entitiesForRendering()）——


不传 AABB，同时继续绕开早期两个老坑（EntityMaid.class 桶 bug、±∞ 经 blockToSection 溢出）；


另补上原 PR 漏掉的蓝图背包检查一处（并顺带修掉它的"原点高度盒扫不到远处女仆"老毛病）。





**验证**：本地无头服 + Sable 2.0.5 A/B——旧 jar 45 秒 406 条报错、农场女仆的 till diag


诊断 0 行；修复后同场景 0 条、4 行（扫描确实重新看到了女仆）。








## 实测五百六十三【空袭 × 暮色森林孔雀羽扇：扇子当飞行燃料（起飞/爬升/盘旋补推全链路），推进与扣耐久照搬扇子原版机制】





需求原文："空袭可以兼容一下暮色森林的孔雀羽扇，也和烟花火箭一样可以起飞（起飞力度及后续使用烟花的路段


都照搬孔雀羽扇自己），每使用一次都使用孔雀羽扇自己的扣耐久机制"。





- **三件套的"燃料件"扩为【烟花 或 羽扇】**：给女仆背包放一把暮色森林孔雀羽扇（不用拿在手上），空袭照常


  激活；**有扇先用扇**，没扇才烧烟花；缺件提示同步改为「缺烟花火箭/羽扇」；


- **推进全部照搬扇子自己**（反编译 1.20.1 / 1.21.1 两版 PeacockFanItem 实证）：滑翔中一挥 =


  速度 + 视线×0.1 + (视线×2 − 速度)×0.5 + 竖直 1.25（原版滑翔助推公式）；起飞、猛击后的再起飞、


  爬升补推、远程盘旋掉高补推，全部走这条链路；


- **每挥一次按原版口径扣耐久**：hurtAndBreak(扇飞数 + 1)——身前 3 格半径 2 盒内的可推生物/掉落物/


  弹射物会被 视线×2 扇飞（扇飞数计入耐久；扇到主人/同主女仆会被「友军风免」拦下）；耐久附魔照常生效；


- **使用节奏照搬原版 getUseDuration=20**：每秒最多挥一次（烟花那条 30 tick 冷却对羽扇换成 20 tick）；


- **纯软兼容**：扇子按注册名 twilightforest:peacock_fan 识别，音效/粒子用 TF 自己的 item.fan.whoosh


  与云朵粒子；不装暮色森林时零开销、行为与旧版完全一致。没有搬 TF 的方块扇风（吹蜡烛/吹散花）——


  那是玩家手持路径的彩蛋，女仆空中挥扇不涉及。








## 实测五百六十二【工作区域回归：不跟随女仆不再"越走越远" + 潜行+中键标记工位 + 修不跟随钓鱼】





反馈原文："在【不跟随】状态下女仆并不能精准的找到自己工作区域，总是会胡乱跑动脱离我想要让她工作的工作范围……


能否添加一个标记功能，用鼠标中键标记某个地方"；以及"女仆在【不跟随】状态下不会钓鱼"。





**根因（反编译对账 TLM 1.5.3）**：TLM 原版 home 模式有一套完整的"工作区域"——限制圈（工位/休闲锚点 ± 半径）


加三重保险：任务选点逐格过滤 isWithinRestriction、出圈走位目标被清、每 2 秒物理拉回。我们此前的历次


"呆立修复 / 干活不被打断"把它拆掉了：干活时整段关掉每 2 秒的拉回与圈刷新；挖矿/伐木/宰杀用直连导航绕过


走位圈；「排班活动半径」默认放大到 32 格（原版只有 12）——于是她顺着目标越走越远，收工后又因出圈被


传送回工位，观感就是"乱跑 + 闪回"。钓鱼同根：home 模式下原版只认"限制圈内"的椅子/坐垫，我们的自动


坐垫按"离女仆最近的水"找点（常在圈外）→ 原版拒收 + 走向目标被清 → 60 秒弃疗循环，所以"跟随才会钓鱼"。





**工作区域回归（两树同步）**：


- 挖矿/伐木/宰杀的任务选点（全量扫描、缓存轮、连锁队列、宰杀索敌、闲逛点）全部加"工作圈钳制"——


  home 模式下只认限制圈内的目标（与 TLM 原生任务同口径）；跟随/战斗模式不受影响；


- home 模式下挖矿/伐木的"锚点滑动迁移"停用（它正是"追出去半个地图"的源头）；女仆被推/打飞出圈时，


  重埋锚点落在圈心而不是脚下；


- 干活期间限制圈照常每 2 秒刷新（圈心跟随当前活动），只免掉出圈时的硬拽/传送——收工后由原版拉回兜底；


- 「排班活动半径」默认 32 → **12**（回归 TLM 原版量级；旧档一次性自动迁移，手动设过 32 的不动）——


  32 变成明确的大范围选项，随时调回；


- 巡逻/工作驱动的锚点兜底链去掉"当前位置"这层（旧兜底会让巡逻中心跟着人漂移）；


- **修：跟随模式下空闲散步整段失效**——"是否受限"的判定对 TLM 女仆实体不成立（半径恒为 8、圈心是零点），


  改为按 home 模式判定。





**新增：工位标记（潜行 + 鼠标中键）**：对着一个方块**潜行 + 中键**，即可把身边 32 格内所有自家"在家模式"


（不跟随/排班中）女仆的工作区域标到那里——工位/休闲锚点立刻生效，任务选点、散步、巡逻都会收进这个圈。


想让她们守农场：站在农场开排班，或潜行+中键农场中心。不潜行的中键照旧是原版取方块，不受影响。





**修：不跟随状态不会钓鱼**：自动坐垫在 home 模式下只在"工作区域内"找水坐；圈外不再生成走不到的远水坐垫


（旧行为是生成 → 走向被清 → 60 秒后"路走不通"弃疗，无限循环），改为提示主人"潜行+中键水边，把工作区域


标过去"；跟随模式照旧大范围找水。








## 实测五百六十一【修：点「保存并返回」卡死 / 下次进游戏卡在 mod 加载界面（配置自触发写盘风暴）】





反馈原文："promaid 配置书在最新版也开始不稳定，一旦点保存有大概率导致游戏崩溃，然后再次进入时


mc 卡在 mod 加载页面，触发了两次卡死，退回到 1.10 版本恢复正常。"





**根因（不是配置书本身，是配置的写盘时机）**：1.2.0 起 NeoForge 侧在收到配置事件后多做了一次


显式 `save()`（当时是为了让默认值迁移落盘）。而 NeoForge 用文件监听器盯着 `config/*.toml`：





```


我们事件里 save()  →  文件被写  →  监听器触发  →  又发一次 ModConfigEvent.Reloading


   ↑                                                                      │


   └──────────────────────  处理器再 save()  ←───────────────────────────┘


```





**实测复现**（NeoForge 专用服务器，外部把 `config/promaid-common.toml` 改一次）：


20 秒内该文件被重写 **38 次**（约 2 次/秒，停不下来），并留下**截断的**


`promaid-common.new.tmp.toml`（16 KB 半截文件，写盘被下一次写盘打断）。





两个症状由此完全对得上：





- **点「保存并返回」= 一次写盘 → 风暴起来 → 客户端卡死/崩**（"一保存就崩、大概率高"）；


- **下次进游戏时 NeoForge 发现旧键要修正**（日志里的


  `Configuration file ... is not correct. Correcting`——玩家 toml 里残留着 1.2.0 删掉的


  `build.realGhostBlocks / ghostBlockCap / placementRange`，这类修正本身也要写盘）


  → 同样踩进风暴 → **卡在 mod 加载界面**，且每次重开都会重演。





**修法**：配置事件处理里**一个字节都不写**（只记录 ModConfig 引用），并加了一道


`inConfigEvent` 闸——事件回调期间任何落盘请求一律拒绝，防止以后有人再加回来。


默认值迁移改由**服务端启动时**跑一次（本来就有这条兜底链路），且**只在真的改过值时才落盘一次**；


那一次在配置回调之外，监听器触发的 Reloading 进来只会记个引用，风暴断掉。





**验证**（同一套复现脚本、同一台机器）：





| | 修前 | 修后 |


| --- | --- | --- |


| 外部改一次配置后 20 秒内文件被重写次数 | **38** | **0** |


| 残留截断的 .new.tmp.toml | 有（16 KB） | 无 |





两树同步修改（Forge 侧原本没写盘，一并收紧到同一口径）；两个专用服务器回归照常 PASS。





## 实测五百六十【友军风免：女仆的法术 / 风弹不再震开主人与同主女仆】





反馈原文："玩家和其他女仆免疫女仆释放的风暴/风弹效果，不会被震开。当前版本免疫伤害，但是会被震风。


导致从高空攻击的时候会直接把主人也打到空中。"





**伤害早就免了，漏的是击退**：主人免伤有本模组的「主人/友军免伤总闸」，《万法皆通》自己也有


盟友判定——两条都在攻击 / 受伤 / 结算三层把"女仆→友军"的伤害取消掉了。但击退**不是伤害事件**：





- 原版 `Explosion.explode()` 在实体循环里先 `hurt()`（被上面两层拦掉），随后**无条件**执行


  `entity.setDeltaMovement(旧速度 + 击退向量)`——伤害结算返回 false 也照推不误。铁魔法的火球


  （`MagicFireball`）正是**用女仆本人当爆炸来源**构造的原版爆炸，所以"女仆在高空打、火球在主人


  脚边炸"就会把主人顶上天，与反馈完全吻合。


- 铁魔法「呼啸之风」（`GustCollider`）更彻底：一次伤害都不发，直接给对方


  `setDeltaMovement(旧速度 + 推力)`，所以三层免伤防护对它完全无效。





**做法**：不去逐个模组打补丁（写死第三方类名 = 无底洞），而是抓住所有外力位移的**唯一出口**


`Entity.setDeltaMovement(Vec3)`（各重载最终都汇到这里），再解决**归因**——"这一下是谁推的"：


新增"当前正在 tick 的实体"记录（`Entity.tick` 的 HEAD），而法术 / 风弹 / 火球推人时调用栈必定


围着效果实体自己的 tick（铁魔法在自己的 tick 里射线命中后推、火球在自己的 tick 里爆炸、瞬发范围


法术在女仆本人的 tick 里结算、风弹 collider 由法书在施法瞬间手动 tick 也在这条链上），


于是推人者 = 那个实体本身是女仆、或它的主人是女仆（投射物 / 召唤物 / 载具）。


爆炸另有一条独立归因（`Explosion` 的直系 / 间系来源），第三方模组的爆炸也照这条走。





**判据三条**：① 推人者是女仆方、且不是她自己（她自己的烟花推进 / 风弹自起跳一字不改）；


② 受害者是玩家或女仆、且与她友军（优先用《万法皆通》自己的盟友口径，保证与它对伤害的判定一致；


没装则用本模组口径）；③ 只拦**明显的外力位移**（速度变化 ≥ 0.2 格/tick，走路 / 重力 / 互相挤开


都远小于它）。





**对主人手感的额外一层**：爆炸对玩家有两条施加通道——服务端改速度，以及把击退向量塞进爆炸包让


**客户端自己推自己的玩家**。所以除了拦服务端那一下，还会在发包前把友军从"受击玩家击退表"里剔掉，


否则本地仍会被"推一下、再被服务器拉回"。两条通道都在服务端处理，客户端不掺和。





配置：`combat.friendlyWindImmune`（战斗与自保 → 单兵战术 → 友军风免，默认开）。





## 实测五百五十九【万法皆通（法术附属）：法术模式的自动换武器 / 自主参战收紧】





《车万女仆：万法皆通》（`touhou_little_maid_spell`，前置 铁魔法 与 新生魔艺）给女仆加了


「近战法术 / 远程法术」两个任务。查兼容时发现一件关键事实：





**这两个任务的 `isWeapon(maid, stack)` 恒返回 true**（javap 反汇编实证：方法体只有


`iconst_1; ireturn`）。而本模组所有"武器"判定都走"任务自带判据"，于是对这两个任务等于


**万物皆武器**：





- **自动换武器**：主手空/快坏时会把背包里**最高 DPS 的冷兵器**（剑/斧）塞进法术模式的主手；


- **自主参战**：法术任务只凭"背包里有任意一件非原版物品"就进候选池（实测三百七十九的兜底）——


  等于"带个模组食物也算会用魔法"。





### 收紧规则（本次改动）





1. 自动换武器遇到法术任务时：**她得真的带着法术装备**（法术书/法器在主手、副手、背包或饰品栏，


   或者附属数据里已经有她的法术书）才开放切换；没带 → **一次都不换**，绝不往法术模式塞冷兵器；


2. 换也**只换法术装备**（附属各 provider 认的法术书/法器）——mod 武器优先，按 附魔 > 耐久 择优；


   主手已经是法术装备则不换（尊重玩家搭配）；


3. 自主参战的法术任务改用同一道闸门（不再只看"有没有非原版物品"）：不会用法的女仆不会被切到


   法术任务上；


4. 其它模组战斗任务（拔刀剑/史诗战斗等）**口径完全不变**，照旧走任务自带的 isWeapon。





### 实现口径





新增软兼容层 `MaidSpellCompat`（与 `GunCompat` 同一套路）：**全反射 + 一次性缓存**，


附属没装 / 版本不符时所有判定恒 false、行为与旧版完全一致——本模组依旧只把 TLM 当硬前置，


不为任何第三方做编译期硬链接。"会用魔法"的判据用附属自己的公开 API：


`SpellBookManager.getLoadedMods/getProvider` + `ISpellBookProvider.isSpellBook` 认法术装备，


`MaidIronsSpellData/MaidArsNouveauSpellData.get(uuid).getSpellBooks()` 认附属数据里的法术书。





### 另外两处的兼容结论（本次未改动）





- **自主参战**：法术系任务已被识别为"远程"（按 namespace 判定），本次只补了上面那道闸门；


- **近战/远程空袭**：空袭的"武器位"仍是本模组自己的近战/远程武器判据，**不会**把法术装备算作武器——


  所以"只会法术"的女仆目前**不会**进入空袭模式（三件套永远差武器位）。要让空袭途中也能放法术，


  需要单独做一层"空袭法术"（把施法挂进飞行行为的节奏里），不属于本次收紧范围。





1.20.1 与 1.21.1 两树同步。





## 实测五百五十八【排班表和游戏时间差 6 小时：深夜在加班、白天在睡觉】





反馈：「排班表和游戏时间不一致，深夜在加班，白天在睡觉」。





### 根因：整张表的时钟比游戏挂钟早 6 小时





原版 Minecraft 的 `dayTime = 0` 是**日出 06:00**，不是午夜（`/time set midnight` = 18000 即证；


TLM 自己显示游戏时间用的也是 `dayTime/1000 + 6` 小时，反编译实证）。而排班表的换算


`分钟 = dayTime % 24000 × 3 ÷ 50` 直接把 dayTime 0 当成了 00:00 —— 于是**整张表比挂钟早 6 小时**：


你按挂钟填的「12:00~16:00 农场」实际跑在傍晚 18:00~22:00，「06:00~14:00 睡觉」实际睡到晚上——


观感就是"深夜在加班、白天在睡觉"。





### 修法





- **时钟补 6 小时偏移**（6000 tick）：现在 00:00 = 游戏午夜、06:00 = 日出、12:00 = 正午、


  18:00 = 日落——与 TLM 显示的游戏时间、F3 的时钟完全一致；


- **班次窗口跟着重锚**（旧窗口也是按"日出=0 点"排的）：早班 = **06:00~18:00**、


  晚班 = **18:00~06:00**（跨午夜，6 个槽的下半段落在 0:00~6:00）、全天 = 00:00~24:00；


  这样早班/晚班与 TLM 自家的 DAY（夜里睡觉）/ NIGHT（白天睡觉）作息正好重合；


- **服务端与客户端共用一份实现**：手册里的"当前时段高亮"、槽位标签、调度器决策全部走


  `ScheduleData`（旧版客户端自己抄了一份 dayTime 换算——两份实现不一致正是这类问题的温床）；


- 已存的日程**不做换算**：同一张表在新口径下就是"挂在挂钟上的那个时段"，正是修复的目的。





1.20.1 与 1.21.1 两树同步；时钟锚点、三个班次的槽窗口、跨午夜命中与"休息时段不排段"


都用公式镜像逐一验过。





## 实测五百五十七【建造默认速度改回 ×1；建完进度 HUD 不消失；进度到不了 100%】





三条都是实测反馈：「建造的默认速度应该改为 ×1，现在是极速」「建造完成之后，左上角的 GUI 不会


消失」（截图停在 80,445 / 80,446 = 99%）。





### 一、默认速度回到 ×1（不再一上来就是极速）





- `build.speedTier` 默认 **x1.5 → x1**；`build.turbo`（极速模式，放置冷却归零、吃满服务器上限）


  默认 **开 → 关**。旧默认下新装的档一开建就是上千块/秒，既看不出建造过程也白烧性能。


**旧档一次性迁移**：用一条**内部标记**（`build.speedMigrated`，跑一次就置位）来判定"只做一次"，


  而不是"值 == 旧默认"——实测玩家 toml 里的真实组合是 `speedTier = "x1" + turbo = true`


  （档位早就被切到 x1、只剩极速还开着），只凭值分不出"旧默认留下的 true"和"玩家自己打开的 true"。


  第一次进游戏后极速会被关掉（档位若是 x1.5 则一并归到 ×1），**之后你想开极速随时开、不会再被改回去**。





### 二、建造完成后左上角进度 HUD 不消失（根因：服务端不再推送，客户端就一直留着最后一帧）





- 进度 HUD 是**客户端拿服务端每秒推送的快照画的**；而服务端在"没有进行中计划"时旧版是**直接


  return、一个包都不发**——计划一完成/取消，客户端永远收不到"清空"，最后那一帧（实测卡在 99%）


  就挂在左上角直到退出游戏。


- 修法两道：① **计划清空时补发一次空快照**（客户端据此立刻清屏）；② 客户端加 **5 秒看门狗**


  ——超过 5 秒没收到任何快照（丢包 / 服务端广播异常 / 切世界重连）就本地清屏，HUD 不允许永久挂着。





### 三、进度卡在 99%（最后那一格）





- 蓝图盖在**已有地形**上时，"开工那一刻就已经是目标方块"的格子旧版**只前进游标、不计数**，


  于是她把该放的都放完了，进度仍差最后几格（截图里的 99% 就是这一格）。


- 现在这类格子**单独计数**（`prebuiltCount`）并计入 HUD 与手册进度条；为防一个格子算两遍，


  真放上去时把它从 prebuilt 里摘掉（两套计数互斥）。**完成判定（缺口扫描那套）口径不变**


  ——仍然只按"她真放的"算，所以"缺口补建"行为一个字节没动。





1.20.1 与 1.21.1 两树同步。





## 实测五百五十六【建造：预览/落点回退旧版（只留"区块内箱子取料"）+ 建造瞬移限频减半】





反馈：「建造功能回退旧版本，关于新版本的改造只保留一个可以从箱子里拿的功能，我发现在用新的


方式以后反而渲染不出方块了。而且 bug 也很多。」另外「将代码里建造女仆传送到要搭建的方块


旁边的间隔减半，移动区块方面的全部都按照旧版本」。





### 一、回退：投影与落点全部回到旧版





- 投影回到**彩色填充盒**（旧的填充盒 + 棱线：近处实心、远处线框那一套），服务端点云也回到


  「只发坐标」格式（去掉调色板段）——真方块渲染这条路整段撤销；


- 金色预览框回到**跟着玩家走**：点「建造此图纸」开预览 → 退出手册走位选点 → 再打开手册点


  第二下弹确认框（旧的两次点击流程），落点 = 你确认那一刻站的位置；「就地建」的快捷手感因此


  自动回归；


- 删掉「落点微调」界面（`BuildPlacementScreen`）、G 键位、方向键操控、`SelectBlueprintPacket`


  里的落点字段与服务端那套「离玩家 ≤ 32 格 / 区块已加载」校验，以及配套的三个配置项


  （投影真方块 / 投影每帧上限 / 落点微调范围）。





### 二、保留：从建造区块内的箱子取料





缺料 → 女仆去**建造区块内（外扩 4 格）**的箱子 / 木桶 / 潜影箱取那种材料：走过去 → 开箱


（盖子会动）→ 取一部分（背包塞不下的还回原槽）→ 关箱 → 回工地继续盖；末影箱不翻、区块外的


箱子不去。开建前算材料也把箱子里的算进去。配置项与手册里那一段都保留。





### 三、建造瞬移限频减半





「放置前把女仆瞬移到目标格旁」的限频 **4 tick → 2 tick**（`MaidBuildBehavior.TELEPORT_MIN_INTERVAL`）：


摆放连着出时瞬移跟得上每一次落块；瞬移前的判据（安全站立格、已在该格 2 格内不瞬移）与


「找不到安全站姿就隔空放置」的兜底都不变。





### 四、空袭：鞘翅取用优先级 + 飞坏自动补装（承五百五十五）





- 背包里**同时**有普通鞘翅与"鞘翅胸甲"这类装备时，**优先穿带护甲的那件**（既滑翔又当护甲）；


  没有护甲型才退回普通鞘翅；胸甲槽已经穿着其中任意一件时**不换**；


- 鞘翅飞坏/耗尽（原版每 20 tick 掉一点耐久，耗尽即消失）后**自动从背包再拿一件穿上**——


  「补齐三件套」那条链路在空袭任务下每 tick 都在跑，胸甲槽一空就补位；


  背包没有备用鞘翅时她**不会**凭空续航（原版滑翔闸门要求胸甲槽里有可滑翔物品），会滑翔落地；


- 每次换装写一行运行日志「空袭装备 · 胸甲槽换装：<新>（原 <旧>）」（40 tick 节流），


  想确认"到底换没换"看日志即可。





## 实测五百五十五【空袭：模组里"内置鞘翅的装备"（鞘翅胸甲这类）现在也认了】





反馈：「在我的整合包里女仆不会把内置了鞘翅的装备视为有鞘翅（如鞘翅胸甲）」。





**原因**：三件套的"鞘翅位"旧判据写死成 `stack.getItem() instanceof ElytraItem`——只认原版鞘翅


物品。而模组里这类装备基本都不是 `ElytraItem` 的子类，而是**护甲物品 + 重写滑翔钩子**，


于是被当成"没有鞘翅" → 三件套永远不齐 → 空袭模式不激活，还会一直播报"缺鞘翅"；


更糟的是"补齐三件套"那一步会去找背包里的原版鞘翅、把玩家给她穿的这件胸甲**换下来塞回背包**


（等于把护甲扒了）。





**改法**：判据换成 `ItemStack.canElytraFly(实体)`（Forge/NeoForge 扩展方法）。选它是因为


**口径完全同源**——原版滑翔的唯一闸门 `LivingEntity.updateFallFlying`（aiStep 内、travel 之前）


在 Forge/NeoForge 里读的就是这个方法（反编译实证：字节码里依次调用


`ItemStack.canElytraFly` 与 `ItemStack.elytraFlightTick`，**没有任何原版物品判定**；


原版 `ElytraItem` 自己实现的 `canElytraFly` 恰好就是 `ElytraItem.isFlyEnabled`，与旧判据等价）。


所以"她能不能滑翔"与"我们认不认这件装备"从此是同一个判据：


凡是**对玩家能滑翔**的物品，女仆一律认得，不会出现"我们认了但原版每 tick 把滑翔位清掉"。





- 受影响的三处判定全部走新口径：`hasElytra`（三件套检查 / 缺件播报）、`equip`（穿脱，


  **胸甲槽已有这类装备时原地不动**）、以及 `isModeActive`；


- **渲染层保持严格口径**（只认原版鞘翅）——鞘翅胸甲自带模型，再叠一层我们的翅膀只会穿模；


  原版自己的 `ElytraLayer` 同样只认 `Items.ELYTRA`；


- **新增诊断**：缺鞘翅时往运行日志写一行「空袭装备 · 缺鞘翅（鞘翅判定: 胸甲=<物品id>


  (原版鞘翅=false，canElytraFly=true/false) 主手=… 副手=…）」（与气泡同一节流，最多 15 秒一行）。


  看这一行就能分清是"我们没认"还是"那个物品压根没实现滑翔钩子"——后者对玩家同样滑不起来，


  该找的是那个模组（`canElytraFly` 与 `elytraFlightTick` 两个钩子都得有，Forge 给


  `elytraFlightTick` 的默认实现是 `false`，只重写一个滑不起来）；


- **取用优先级**（同一条的补充）：背包里同时有普通鞘翅和"鞘翅胸甲"这类装备时优先穿带护甲的


  那件；胸甲槽已穿着任意一件时不换。**鞘翅飞坏/耗尽后会自动从背包再拿一件穿上**（空袭任务下


  「补齐三件套」每 tick 都在跑），包里没有备用鞘翅才会落地；每次换装写一行运行日志


  「空袭装备 · 胸甲槽换装：<新>（原 <旧>）」（40 tick 节流）；


- 手册「空袭与飞行作战 → 三件套」的鞘翅那一条补了这句兼容说明。





1.20.1 与 1.21.1 两树同步。





## 实测五百五十四【落点微调改版：点「建造此图纸」直接弹界面，方向键搬区块】


> **本条已整体回退（实测五百五十六）**：落点微调界面、方向键操控、G 键位全部删除，落点回到


> 「点建造此图纸时以玩家为中心、跟着人走」的旧做法。下面这段仅作记录。








玩家反馈「微调界面按 G 键触发感觉不是很好」，要的是：点建造该建筑就跳出界面、区块就地产生，


然后直接按 ←↑→↓ 移动建筑——这四个键此时不接管移动，而是接管区块的移动。按这个改完了。





- **入口改了**：手册里点「建造此图纸」不再要点两次、也不再需要 G 键——一下就把「落点微调」


  界面顶出来，金色框同时定在你点那一下的脚下格（青色幽灵 / 真方块投影同帧就位）；


- **键盘驱动**：`←→` 走 X、`↑↓` 走 Z、`PgUp/PgDn` 调高度 Y，按住 `Shift` 一次 8 格；


  `Q`/`E` 转向（界面外照旧用 Z 键）、`R` 回到脚下、`回车` 确认建造、`Esc` 取消；


  按住可以连发（原版屏幕本来就收 GLFW 的重复按键事件）；


- **这四个键不会挪你**：原版 `KeyboardHandler` 在屏幕打开时**直接跳过**按键绑定更新


  （字节码实证：`screen != null` 时整段 `KeyMapping.set/click` 被跳过），所以 ←↑→↓ 只挪区块；


  顺带也挡住了误点方块 / 误攻击——这也是用界面而不是纯 HUD 的原因；


- **看得见才算能用**：面板只占**屏幕顶部一条**（98 px：图纸名 / 落点 / 占地 / 朝向 + 键位提示


  + 两排备用按钮），并**覆盖掉原版的全屏暗底**（`renderBackground` 置空）——挪的时候能一直看清


  世界里的金色框与投影跟着动。按钮仍在（鼠标党可用），键位都写在按钮上，不用背；


- **挪远了也知道在哪**：顶栏实时显示落点**离你多少格**，超过 32 格直接标红提醒（服务端会拒绝这种落点）。


- **确认仍是两步**：「确认建造」先弹一次原版确认框（落点 / 占地 / 朝向 / 离你多少格 + 会摧毁


  区块内障碍物），确认才发包；反悔点「再挪挪」直接回微调界面，落点与朝向不丢；


- **删掉 G 键位**：原来那条 `key.promaid.build_placement` 从原版按键设置里消失


  （`options.txt` 里可能残留一行失效配置，无害；`key.promaid.build_rotate` = Z 不变）。





服务端协议与校验（落点离玩家 ≤ `build.placementRange` 默认 32 格、区块已加载、重叠检查）


原样不动；实测五百五十三的真方块投影预览与「区块内容器取料」不受影响。





## 实测五百五十三【建造三件套：预览改真方块 / 落点固定 + 微调界面 / 从区块内的箱子取材料】


> **本条的第一、二部分已在实测五百五十六整体回退**（真方块投影渲染不出来、落点微调那一套 bug 多）：


> 投影回到彩色填充盒、金色框回到跟着玩家走、微调界面与 G 键位删除。**第三部分「区块内箱子取料」保留**。


> 下面第一、二节保留原样，作为当时的记录。








参照 [车万重工] TLM-Builder 的三处做法（只借思路，代码自己写）。三件事都做完并已部署。





### 一、投影预览改成"真方块"（不再是彩色填充盒）





以前幽灵投影是**轴对齐填充盒**（服务端只发坐标，客户端用 DebugRenderer 画盒子），


只能看出体量看不出材质。现在：





- 服务端点云格式改成 `<点>|<调色板>` 两段：点里带**调色板下标**（base36，1~2 字符），


  调色板按方块种类去重（`i~方块id~方块状态`）——同带宽只多 1~2 字符/点；


- 客户端按调色板还原 `BlockState`（走原版 `BlockStateParser`，**楼梯朝向/半砖/含水都还原**），


  用**原版烘焙模型**半透明绘制（alpha×0.6、RGB 略压暗，同参照的 GhostVertexConsumer 做法），


  已经建好的格子不再重复画；


- 渲染层挪到半透明层之后（AFTER_TRANSLUCENT_BLOCKS），每帧上限 `build.ghostBlockCap`（默认 1500）。


- 旧格式点云仍兼容（无调色板 → 回退填充盒）；开关 `build.realGhostBlocks`（默认开）。





### 二、落点固定 + G 键微调界面（不再"框跟着人走"）


> **本条的微调界面入口已在实测五百五十四改版**：不再按 G、也不再点两次——点「建造此图纸」


> 直接弹出界面，用 ←↑→↓（Shift=8 格）/ PgUp·PgDn / Q·E 搬动区块，回车确认、Esc 取消。


> 下面这段描述的 G 键入口与「X-1 / Y+1 按钮」操作方式已作废；落点固定与服务端校验不变。








以前金色框**每帧取玩家位置**，落点 = 你确认那一刻站的地方，想挪半格只能自己走位。现在：





- 打开预览时**定住落点**（= 点那一下的脚下格），之后不再跟着你动；


- 按 **G**（原版按键设置可改）打开**「落点微调」**界面：`X-1/X+1/Y-1/Y+1/Z-1/Z+1`


  （按住 Shift 步进 8 格）、`左转/右转`（每次 90°，Z 键照旧可用）、`回到脚下`、


  `确认建造`、`取消`——界面不暂停游戏，挪的时候能直接看世界里的金框与真方块投影；


- 点两次"建造此图纸"的**就地建快捷方式保留**（只是落点改为那一下的脚下格，不再漂移）；


- 协议带上落点：服务端校验「离玩家 ≤ `build.placementRange`（默认 32 格）」「区块已加载」


  后才建（重叠检查沿用原有逻辑）。





### 三、从**建造区块内**的箱子 / 潜影箱取材料（女仆会走过去并开箱）





以前材料只认"女仆背包 + 主人背包"，箱子里的东西不算也不去拿。现在：





- 新增取料行为（注册在建筑任务，优先级高于建造）：**计划里缺某种料**、且**建造区块


  （外扩 `build.chestSearchMargin`，默认 4 格）里的容器装着这种料**时，女仆会：


  解除"建造中静止" → 走到容器旁 → **开箱动画**（原版 `blockEvent`，箱子/桶/潜影箱的盖子都会动）


  → 取 `build.chestFetchPerTake`（默认 8）个（背包塞不下的**还回原槽**）→ 关箱 → 回工地继续盖；


- 容器识别用 `BlockEntity instanceof Container`：箱子、陷阱箱、桶、潜影箱、各类模组容器


  全覆盖；**末影箱天然不含 Container**，不会被翻；


- 找不到容器时行为不介入——缺料提示与延后重试仍是原逻辑（`build.fetchFromChests` 默认开，


  两趟取料之间有 `build.chestFetchCooldown`（默认 60 tick）冷却，防"箱子锁着/取不到"来回跑）；


- **开建预检也算上箱子**：材料全放在区块内的箱子里也能正常开建，不再误报"材料不足"。





### 验证





两树编译零错；jar 重建（1.20.1 = 9354824，1.21.1 = 9384700，新类已入包核对）；


Forge 1.20.1 与 NeoForge 1.21.1 专用服务器回归均 PASS；已部署两个客户端实例与 pack1201 服务器。


行为面（预览观感、微调手感、女仆走去开箱取料）待实测确认。





## 实测五百五十二【回血资源判定太窄：包里有食物（金胡萝卜/模组料理）却判成"没有"】





### 先纠错：五百五十一改错了地方





五百五十一 把搭高的门槛从"必须有回血资源"改成了"有方块就搭"——**这个改法是错的，已回退**。


没得回血爬上去确实没有意义，门槛就该是回血资源。也就是说：门槛是对的，错的是**回血资源的判定**。





### 决定性证据（五百五十一 新加的「自保诊断」日志）





```


血 16%  距威胁 1.7 格（贴身线 4.0 格） | 回血资源=无  可搭方块=有


血 65%  距威胁 3.7 格                 | 回血资源=无  可搭方块=有


血 65%  距威胁 4.8 格                 | 回血资源=无  可搭方块=有


```





威胁一直在贴身范围内（搭高分支进得去）、背包里有方块，但"回血资源=无"——而实际包里有食物。


原因：旧判定只认 **药水 / 金苹果 / 一小撮硬编码原版食物**（熟肉、面包、苹果…），


**金胡萝卜、各类模组料理一律不认**（而玩家喂给女仆的正是金胡萝卜）。





### 改法





- 回血资源判定改成与投喂系统**同一个口径**：`EmotionalActionExecutor.isFeedableFood`


  = TLM 自己的 `DefaultMaidHealSelfMeal.isHealMeal` 口径（**有食物属性且不在投喂黑名单里**）——


  任何"能吃的东西"都算回血资源；金苹果保留单独判定（它在投喂黑名单里刻意让给"低血即时增益"路径，


  但作为回血资源必须算）；


- **门槛、上限、塔顶处理全部回退**到五百五十一 之前（无回血资源不搭高；垫到 pillarMax；


  塔顶围困只清计数不主动下塔）；


- 「自保诊断」日志升级：写出**认到的那个物品名**（`回血资源=minecraft:golden_carrot`），


  以后判定再出问题一眼就能看出来。





### 验证





两树编译零错；重建 jar 并部署到两个客户端实例与 pack1201 服务器；双服务器回归均 PASS。





## 实测五百五十一【搭高的门槛压在"有没有带药"上——包里一堆方块也一块不搭】





> **本条的门槛改法已在实测五百五十二推翻并回退**：门槛（必须有回血资源）本身是对的，


> 真正的问题是"回血资源的判定太窄"（金胡萝卜/模组料理都认不出来），已改为通用食物判定。


> 下面这段保留原样，作为当时的排查记录。





### 现象





五百五十那道取料闸口修好、部署进游戏后，实测**仍然一块方块都不搭**，也不播报


"背包里没有搭方块的材料"。日志把范围缩小到一句话：整场自保只有一句


「情况不妙，我小心周旋！」，运行日志里**一行「自保搭高」都没有**——


说明搭高分支根本没进去，问题不在取料闸口，而在它前面的门槛。





### 根因：实测三百六十五那条"没回血资源就不搭"的口径





原口径是：包里没有药水/金苹果/治疗食物 → **搭高不再作为可选项**，被近身只做小幅走位。


理由（当时的反馈）是"塔的唯一意义是安全回血，没得回血垫塔纯属干耗"。





问题在于它把两件事绑在了一起：


- **能不能搭** 取决于包里有没有方块；


- **上去以后干什么** 才取决于有没有回血资源。





于是一个背着几组方块、只带武器（或只带金胡萝卜这类不在"治疗食物"名单里的食物）


的女仆，被怪按在地上打时，一块都不搭——正是"宁愿被打到死"。





顺带查出一条死链：塔顶无回血资源时，那条"围困满 10 秒"的处理在实测三百九十把


围困传送停用之后**只剩 `heallessSiegeTicks = 0` 这个空壳**——什么也不做。所以


"上去也是干耗"确实成立，只不过当初的解决办法是"干脆别上去"，这次改成"上去、


待不住就下来"。





### 改法





1. **搭高门槛改成材料**：`hasBuildBlock(maid)`（背包里有可搭方块）取代


   `hasHealResource(maid)` 作为搭高的前置——branch 2（连续被近身后的爆发搭高）


   与 branch 4（被近身垫到安全高度）都改。


2. **上限分流**：有回血资源 → 照旧垫到 `pillarMax`（塔顶守势安心回血）；


   没有回血资源 → 只垫到 `safePillarHeight`（默认 5 格，够不着就行），


   不再一路垫到 30 格站桩。


3. **塔顶不再干耗**：塔顶 + 威胁仍在 + 包里没有回血资源，围困满 10 秒 → 置位


   `towerDescendRequest`，branch 4 消费它清掉塔位状态下塔，回地面继续打；


   再被近身会重新垫上去（"打不过就上去躲、躲不成就下来打"）。


4. **决策快照日志**（本轮新增，为了不再靠猜）：每场自保第一次锁定威胁时写一行


   「自保诊断」——血 20% 距威胁 3.2 格（贴身线 4.0 格）| 回血资源=无 可搭方块=有。


   运行日志搜「自保诊断」。以后再出现"她怎么不搭方块"，一行就能看出卡在哪道门槛。





### 验证





两树编译零错；重建 jar 并部署到两个客户端实例与 pack1201 服务器；


Forge 1.20.1 与 NeoForge 1.21.1 专用服务器回归均 PASS。


行为面（她真的开始搭方块）待实测确认——若仍不搭，把「自保诊断」那一行发回即可定位。





## 实测五百五十【悬空禁搭把"她自己一跳"也判成了悬空——被围殴时不再搭方块自保】





### 现象





被怪围住时她不再往脚下垫方块自保，也不吭声，原地被磨死。连"背包里没有搭方块的材料"


那个气泡都没有——因为那个气泡在"悬空"时是**特意静默**的，于是整条搭高链路堵死时


一个字都留不下（实测四百四十三当时的设计本意是防误报，副作用就是这种静默失败）。





### 根因：四百九十八那次收紧的又一处误伤





四百九十八把"悬空禁搭"的口径从"坠落 ≥ 落地水触发高度"收紧成了"只要 `fallDistance > 0`"。


当时想拦的是滑翔与真正的高空悬空，但这一条把**她自己在战斗里的小跳**也算进去了：


战术跳劈、走位跨台阶、被击退，都会让她离地一格再落回来，落地前那几 tick


`fallDistance` 是正数 → 判定为悬空 → `takeBuildBlock` 返回 `null` → 搭高一块都放不下。


搭高自己的"放块 → 被顶起 → 落回"也是同一类 tick，等于她一边被围殴、


一边每次想搭方块都被自己的动作否决。





同一处收紧此前已经撞出一例（实测五百零九：岩浆里垫方块自救被禁搭误伤），


这次是它的第二类误伤——根都在"把下落当悬空"这个口径上。





### 改法





"悬空"回到它的字面意思：**脚下没有地**。判据从"是否正在下落"换成


"脚下 3 格内有没有可站立面"（用 `isFaceSturdy(level, pos, UP)`，台阶/楼梯/栅栏这类


"顶面能站人、但不是整块实心"的方块也算落点，免得站在台阶边跳一下就被误判）。


四百九十八要拦的一件没丢：





| 情形 | 判据 | 结果 |


| --- | --- | --- |


| 滑翔 / 飞行作战空中 | `MaidFlightKit.isFlightAirborne` | 禁（原样保留，这是四百九十八的主漏口） |


| 1.21.1 重锤跃起中 | `MaidMaceSmashBehavior.isAirborne` | 禁（原样保留） |


| 从高处掉下来 | `fallDistance ≥ 落地水触发高度` | 禁（恢复四百四十三的原始阈值） |


| 3~5 格浅坠、站在崖边往下跳 | 脚下 3 格内无地 | 禁（新判据天然覆盖） |


| 战斗里的小跳、被击退、搭高自身顶起 | 脚下 1 格就是地面 | **放行** |





三处照旧放行：水里、岩浆里（实测五百零九）、骑乘中。四个搭方块模块


（自保搭高 · 搭路 · 挖矿垫脚 · 伐木垫脚）共用这一道闸口，一起恢复正常。





### 附带的观测修复





自保搭高每次被拒都会往运行日志写一行原因（限频 5 秒/女仆，搜「自保搭高」）：


`悬空禁搭（脚下无地 / 滑翔飞行中）` / `目标格被主人碰撞箱占着` /


`放置格/头顶 2 格不是空气（防窒息拦下）` / `实际头顶被堵` / `背包里没有能搭的方块`。


以后再遇到"她怎么不搭方块"，一眼就能看出卡在哪一道闸口，不必再从行为链倒推。





### 验证





两树编译零错；重建 jar 并部署到两个客户端实例与测试服务器；


两个专用服务器回归均 PASS（Forge 1.20.1 / NeoForge 1.21.1，`Done` 后再盯 30 秒无延迟崩溃）。


行为面（她真的开始搭方块了）以待实测确认——本次另加了「自保搭高」被拒原因日志，


若仍有拦阻，运行日志里会直接写出是哪一道闸口。





## 实测五百四十九【1.21.1 上四个配方全都加载不了（数据包目录名与字段名漏改）；指标石在手册里找不到】





### 一、配方（只在 1.21.1 上坏）





**现象**：1.21.1 生存里合成不了指标石。顺带查出来蓝图书、药剂手册、排班表也都合不出来。





**根因**：1.21（数据包格式 48）把数据包目录名统一改成了单数——`recipes`→`recipe`、


`advancements`→`advancement`、`loot_tables`→`loot_table`、`tags/items`→`tags/item`。


移植 1.21.1 树时只改了后三者，`recipes/` 与 `tags/items/` 漏了，于是**整个目录在 1.21.1 上


根本不会被读取**：四个配方一个都不存在。另外三个老配方还留着 1.20.1 的字段名：





| 文件 | 旧写法（1.20.1） | 1.21 的正确写法 |


| --- | --- | --- |


| blueprint_book / brew_manual / schedule_book | `result.item` | `result.id` |


| schedule_book | `result.nbt` | `result.components` |





（`index_stone.json` 是后加的，字段当时就按新格式写了——但目录名一错，它同样加载不了，


所以玩家最先撞上的正是"指标石合不出来"。）





**改法**：neo 树 `data/maid_smart/recipes/` → `recipe/`、`tags/items/` → `tags/item/`；


三个老配方按 1.21 规范改字段（排班表那枚作标记用的附魔改成


`components` → `minecraft:enchantments` → `levels` → `minecraft:mending = 0`）。


**1.20.1 树不动**——复数目录 + `result.item` + `result.nbt` 才是 1.20.1 的正确写法。





**核验口径**：拿同版本的真实数据包当基准——1.21.1 侧 TLM 自己 49 个配方、灾变 210 个、


tacz 183 个、森罗物语 548 个，清一色 `recipe/` + `tags/item/` + `result.id`，而我们改前是


里面唯一的例外（`recipe`=0、`recipes`=4）；1.20.1 侧区块加载器 / 拔刀剑 / tacz / ProjectE


清一色 `recipes/` + `result.item`，与我们 1.20.1 的写法一致。





### 二、手册里找不到指标石





**现象**：手册目录和「功能总览」里都没有指标石，顺着手册读根本发现不了这个物品。





**根因**：用法文案写在「建造系统教程」章节的最末尾（那一章两页多），既没有独立标题、


也不在「功能总览」的跳转清单里——文案是有的，但等于藏起来了。





**改法**：


- 提为**独立章节**「指标石（即兴临时搭建）」，排在「建造系统教程」之后（全手册 31 → 32 章）：


  合成（9 块平滑石头 3×3 摆满）、三步用法（绿框 → 右键锁成红框 → 右击女仆绑定）、


  材料从哪来（先从她背包、不够从你背包，选数量最多且有碰撞的方块）、几条规矩


  （先锁再绑 / 同时只有一个锁定 + 一个绑定 / 换目标必须先解除 / 中断会报数收尾）、总开关位置；


- 「功能总览 · 一键跳转配置」的「生产与工作」一行加上**指标石**跳转链接，点一下直达它的配置行；


- 建造章节末尾只留一行指向该专章，不再重复正文。








## 实测五百四十八【装了「是，史蒂夫模型」(YSM) 时黑屏进不去游戏：鞘翅图层的泛型参数写死导致 ClassCastException】





### 现象





同时装本模组与「是，史蒂夫模型」(Yes Steve Model, YSM) 时，游戏能启动、能加载资源，


然后**卡在黑屏**进不去（日志里是 `Minecraft.abortResourcePackRecovery`）。





### 根因（一个 checkcast）





TLM 自带 YSM 联动：装了 YSM 时它把女仆交给 YSM 的渲染器


（`EntityMaidRenderer.initYsmModelRenderer`，字段 `ysmMaidRenderer` 的类型是


`IGeoEntityRenderer<Mob>`），并把已有 geo 图层逐个 `copy` 到那个渲染器上。





TLM 的基类是 `GeoLayerRenderer<T, R extends IGeoEntityRenderer<T>>`，`copy(R)` 的形参


就是 `R`。我们那两个图层（`LayerMaidElytraGecko` / `LayerMaidSpinAttackGecko`）把 `R`


写死成 `GeckoEntityMaidRenderer<Mob>`，泛型擦除后 javac 会生成桥接方法，里面


`checkcast GeckoEntityMaidRenderer`。而 YSM 的渲染器（`com.elfmcys.yesstevemodel.*`）


只实现了 `IGeoEntityRenderer`、继承的是 YSM 自己的类，**不是 `GeckoEntityMaidRenderer`


的子类**。于是：





```


ClassCastException: com.elfmcys.yesstevemodel.ooOoooo0o000Oo0OoOooo0oo


  cannot be cast to GeckoEntityMaidRenderer


  at com.maidsmart.client.LayerMaidElytraGecko.copy


  at TLM EntityMaidRenderer.initYsmModelRenderer


→ EntityRenderers.createEntityRenderers 整体中断（实体渲染器整批没建起来）


→ Minecraft.abortResourcePackRecovery → 黑屏


```





### 改法





1. 两个图层的 `R` 从 `GeckoEntityMaidRenderer<Mob>` **放宽为公共上界


   `IGeoEntityRenderer<Mob>`**——`copy` 的形参类型随之与擦除后的签名一致，桥接方法里的


   checkcast 直接消失；构造参数放宽为 `IGeoEntityRenderer<?>`，两个调用点（mixin）不用改。


2. `render()` 开头加 `getRenderer() instanceof GeckoEntityMaidRenderer` 守卫：走 YSM 时


   **不画**我们的鞘翅与旋转特效。本层的挂点逻辑依赖 Gecko 骨骼表


   （`AnimatedGeoModel.bones()` 里的 ElytraLocator/Elytra/UpperBody/Root），YSM 模型没有


   那套骨骼，硬画只会把翅膀堆在原点——那比不画更糟。女仆的背部物品/鞘翅在 YSM 那条链上


   由 TLM 自己的图层负责。


3. **两树同步**：1.20.1 的 TLM 1.5.3 同样带 `initYsmModelRenderer` + `ysmMaidRenderer`，


   装了 YSM 一样会崩，所以这不是 1.21.1 独有的问题。





### 验证





- 两树编译零错；重新打包后 javap 确认两个图层的类声明与 `copy` 形参都已是


  `IGeoEntityRenderer`，桥接 checkcast 消失；


- 没装 YSM 时 `getRenderer()` 必然是 Gecko 渲染器，守卫不生效，原有行为不变。





### 顺带说明（另一个"黑屏"传闻无关的坑）





把 jar 换进 `mods` 目录**必须在游戏完全退出后做**。运行中替换文件会让 ModLauncher


按旧偏移去读新文件，表现为"某个类明明在包里却 ClassNotFoundException"，同样会死在


加载阶段（实测五百四十六 那次的崩溃日志就是这种）。








## 实测五百四十七【空袭牵引绳：空袭期间离主人超出半径（默认 100 格）就立刻传送回主人身边】





### 一、"自己回不来"是怎么来的（三道路闸全对她关着）





她会飞高是机制决定的：两种空袭都是「放烟花给初速 → 爬升 → 收翅俯冲 / 空中盘旋」，一轮打完


人已经挂在几十上百格高空，而主人早就不在脚下。此前有三条"远距拉回"链路，它们**全都主动


放行空袭**：





| 链路 | 平时怎么触发 | 遇到空袭时 |


| --- | --- | --- |


| 同维度远距拉回 | 同维度、距离超过 48 格 | **不拉**（实测四百八十七 加的：怕打断扑击） |


| 跨维度跟随 | 主人换了维度 | **不传**（滑翔中不传，猛击落地后自然恢复） |


| TLM 原版 teleportToOwner | 离主人过远自动瞬移 | **被拦**（MaidTeleportPreserveMixin 在滑翔 / 飞行作战空中时直接返回 false） |





于是"飞太高、把目标打死后自己回不来"这条路径上谁都不拉她——她只能一路滑翔到落地，落点


可能在几百格外的山头上。这不是哪一处写错了，而是三条豁免叠起来的必然结果。





### 二、这一版加了什么





一句话：**空袭期间，以她为圆心、半径 N 格（默认 100）的球内找不到主人 → 立刻把她传送回


主人身边**，走的是排班表那次人工传送同一条链路（即实测五百四十六 的「强制 + 无视地块 +


可以空中传送」）。





- 新配置 **「空袭牵引绳（格）」**，默认 **100**，**填 0 = 关闭**。位置：配置面板 →


  「生存与复活 → 落地缓冲」小类（和「免疫鞘翅撞击 / 空袭免疫摔落 / 远程空袭近身弹开」放一起）。


- 距离按 **3D 距离**算（水平 + 竖直一起算）——所以"飞太高"本身就会触发：她爬到主人头顶


  100 格以上就出了球，那正是"飞太高回不来"的字面含义。


- 落点：先在主人身边 16 格内找可站立格；找不到就直接落在**主人当前所在格**（与排班表人工


  传送完全同一口径：主人飞在高空 / 悬在虚空边也照传）。


- 触发时主人会收到一条系统消息，并落一条 `[空袭牵引绳]` 运行日志（距主人多少格、半径多大）。





### 三、只在"她确实在空中"时生效





面板文案里那句"只在空中生效"不是偷懒，是刻意划的边界：





- **地面上的距离问题不归它管**——落回地面后交给"同维度拉回"那套更保守的规则（48 格，且


  守家 / 坐姿 / 骑乘 / 干活都有豁免）。否则"主人出门 100 格、空袭女仆守家站着不动"会被


  无条件拽走；


- 空中 = 空袭正在进行，这正是本条要管的窗口；


- 正因为已经有"空中"这道门，本入口**不查 home（在家）模式**：停放的语义已经被"她飞起来了"


  这件事本身打破；而我们的排班表**会自动给女仆锚定 home**，如果照抄 home 那道门，结果


  恰恰是"用了排班的那批女仆永远召不回"——正是报上来的场景；


- **主人跨维度时不抢**：那一路由跨维度跟随在本轮攻击结束后处理，在这里立刻抢会重演


  实测四百八十七「猛击被传送打断」的老问题。





### 四、实现（给日后排查用）





- 新增 `com.maidsmart.combat.MaidFlightRecall`：每 tick 的判定全在这里（是不是空袭任务 /


  她是否在空中 / 主人是否已加载且存活 / 是否同维度 / 3D 距离是否出球）。


- 新增 `MaidChunkLoadManager.recallFromFlight(女仆, 主人)`：复用现成的


  `teleportCore(女仆, 主人, force = true)`，与「一键集合 / 传送到我身边」同一条真传送链路


  （原版 teleportTo + 摔落距离归零 + 停导航 + 清速度 + 末影人音效）。


- 调用点挂在 `MaidToolAutoEquipBehavior.checkExtraStartConditions` 的飞行任务分支——


  core 行为，任何 activity、每 tick 一次。挂那里有个额外好处：本模组给所有有主活动女仆挂了


  2 级强制加载票（= 实体正常 ticking），所以哪怕她离主人几百格、区块早已出了模拟距离，


  她的 brain 照样在跑，这条牵引绳才真的"立即"。


- 反过来说：空袭行为本身要"有攻击目标"才 tick，而本条要管的恰恰是**目标已经死了之后**


  她还挂在几百格高空的那一段，所以这条判定不能写在空袭行为里。


- 失败保护：传送失败（主人实体不在已加载世界等）后 60 tick 内不重试；系统消息 10 秒最多


  一条（成功召回后她人已经在身边，不会连发）。


- 手册"飞行作战"章节与配置面板文案同步更新；那句"飞行期间禁止被传送（包括跟随拉回、一键


  集合）"现在补了一句"**唯一的例外就是空袭牵引绳**"——飞出 100 格早已超出任何扑击的有效


  范围，那一步是把她捞回来、不是打断她。











## 实测五百四十六【自动复活：去掉内置 CD、重生点不可用就强制落在主人身边；排班表传送同样强制 + 无视地块】





### 一、"卡住无法复活"是怎么来的





三样东西叠在一起：





1. 复活落点原本是「主人重生点（床/重生锚）→ **无则主世界出生点**」。出生点往往是另一个坐标


   （甚至另一个维度）的荒郊——女仆落在那儿可能立刻又死；


2. 她"复活后很快又死"会踩到 实测四百一十六 加的**死亡循环断路器**：把下次复活**推迟**


   60 秒，连续则 120 / 240 / 480 / 600 秒封顶；


3. 那个断路器的提示语写的是「已把下次复活推迟 60 秒」——而实际退避是**翻倍**的，


   玩家看到的就是"说要等 60 秒，然后一直不来"。





### 二、改法





| 位置 | 旧 | 新 |


| --- | --- | --- |


| 复活落点（重生点不可用） | 退回主世界出生点 | **强制落在主人当前所在位置**（不看地块，主人在高空/岩浆边/水底也照落） |


| 死亡循环断路器 | 硬编码退避 60→600 秒 + 那条"推迟 N 秒"的提示 | **整段删除**（不再有任何内置 CD） |


| 复活提示语 | 无论落在哪都说"正在主人重生点等你" | 按实际落点分流：落在重生点 → 原话；重生点不可用 → "重生点不可用（床被拆/重生锚没电/维度不允许），**已直接在你所在的位置复活**" |





理由：主人站的地方就是玩家自己站的地方，是这颗星球上最不会让她"一放出来又死一次"的落点；


这也是"复活到她该在的人身边"这个功能的本意。断路器删掉之后，"复活点不安全"不再被掩盖成


"等一会就好"，而是从根上不再发生。





**注意**：`复活延迟（秒）` / `复活时机（次日黎明）` 这两个**配置项保留不动**——去掉的是那个


硬编码的退避 CD，不是玩家自己设的复活延迟。（复活延迟正是"墓碑存在多久"的时长。）





### 三、排班表传送：强制 + 无视地块





`MaidChunkLoadManager.teleportCore` 原本在「主人身边 16 格内找不到可站立格」时直接**拒绝**，


于是主人飞在天上、悬在虚空/海面上时，点「传送到我身边」只会收到一句"落地后再点"。


现在给传送加了 `force` 口径：





- **人工传送走强制**（「传送到我身边」/「一键集合」以及未加载区块的自动补召回）：


  找不到可站立格就直接落在**主人当前所在格**——主人飞着她就落到空中，这正是


  "无视地块、可以空中传送"。（空袭女仆悬停/滑翔时也能被叫回来。）


- **自动路径保持非强制**（受困救援 / 同维度远距拉回）：这两条是系统自己发起的，


  仍然"宁可她走路归队，也不冒半空坠落的风险"，不受本次改动影响。


- 「**把自己传到女仆身边**」的跨维度分支原本也会因为"她脚下没找到可站立的位置"而**取消传送**


  （同维度分支早就有"退到她那一格"的兜底，跨维度漏了）——现补上同一个兜底。





顺带把几条不再成立的提示语改掉（"你身边 16 格内无可站立点（高空/虚空）——落地后再点"、


"N 名因身边无可站立点未动" → 改为状态异常的通用失败说明）。





### 验证





两树编译零错，jar 已重建并部署三个目标。本轮**没有跑行为测试**，留待进游戏实测。








## 实测五百四十五【投喂效果与"女仆自己吃"对齐：补上 TLM 那一记吃食物回血】





### 根因：TLM 的"吃食物回血"不在 eat() 里，而在**餐食系统**里





女仆自己吃食物时，回血发生在 TLM 的 `MaidHealSelfTask` → `DefaultMaidHealSelfMeal.onMaidEat`


（反编译两版同构）：





```java


// 1.20.1：total = 营养 + 营养 × 饱和度系数 × 2


// 1.21.1：total = 营养 + saturation()（那边的 saturation() 已含 ×2，两版等价）


float total = ...;


if (random.nextInt(5) < total) {          // MAX_PROBABILITY = 5


    maid.heal(Math.max(total / 5.0f, 1.0f));


}


```





**一根胡萝卜**：`nutrition=3`、`saturationModifier=0.6` → `total = 3 + 3×0.6×2 = 6.6`


→ `6.6/5 > 1` 即 **100% 触发** → 回 `6.6/5 = 1.32` 点血。





而本模组的投喂走的是**原版 `LivingEntity.eat`**（`feedSisterFood` / 蜂蜜分支都一样）——


那条路只做"食物自带效果 + 音效 + 饱食度"，**完全不经过 TLM 的餐食系统**。


也就是说：TLM 那一记回血在投喂时**整段缺失**，于是"喂胡萝卜只回了原版那点效果 /


喂蜂蜜只解了毒"，观感就是"喂食比她自己吃弱"。





### 修法：喂食后补调一次 TLM 的 HEAL_MEAL（不自己实现公式）





新增 `com.maidsmart.combat.MaidMealBridge`：走 TLM 自己的接口


（`MaidMealManager.getMaidMeals(HEAL_MEAL)` → `canMaidEat` → `onMaidEat`），于是





- 回血**公式、概率、上限、粒子、饰品（bauble）联动**全部与她自己吃**逐行同源**


  ——TLM 以后改公式我们也自动跟着变，不会再出现"两套回血口径"；


- **黑名单也自动一致**：`MaidHealMealsBlockList` / `HEAL_MEAL_REGEX` 里被禁的食物，


  喂食同样不给回血（与"她自己不吃这个"完全同一个判据）。





接入点三处：姐妹喂普通食物（`feedSisterFood`）、姐妹喂蜂蜜（蜂蜜也是食物、nutrition=6）、


即所有"喂给**女仆**"的入口。**主人链不动**——玩家走原版饥饿系统，


TLM 那记回血是女仆专属的餐食系统，套到玩家身上没有语义。





### 三个必须踩对的细节





1. **必须传快照**。反编译 `LivingEntity.eat`：末尾 `stack.shrink(1)`（`m_41774_`）。


   如果直接把 `toGive` 传给 Bridge，eat 之后它已经是**空栈** → Bridge 判空直接返回 →


   修复静默失效。所以先 `copy()` 出"喂进去的那一份"再调 eat。


2. **金苹果刻意不补**。TLM 自己吃金苹果也只走 `eat()`（`ApplyGoldenAppleEvent`，


   玩家喂她时触发），同样没有那记回血——所以投喂金苹果若补上 HEAL_MEAL 反而会


   **强于**她自己吃。现在的口径是"与她自己吃完全一致"，金苹果维持原样。


3. **失败要留痕**。若 TLM 未来改了 API（拿不到任何 HEAL_MEAL 实现），会退化成


   "只有原版 eat"的旧行为——这正是最难发现的那类静默缺失，所以加了**一次性**告警日志。





### 为什么还加了一条调试命令





"女仆喂女仆"发生在两个实体之间、不产生任何原版事件，而且互助链要求


"被喂的那只有主人、且主人在线"——**专用服务器上没有玩家，这条链结构上无法端到端触发**。


所以本次同时留了一个控制台可用的验证入口：





```


/maid_smart feedtest <itemId>        # OP；对最近一只未满血的女仆结算一次投喂并打印血量变化


```





它走的是与 `feedSisterFood` **完全相同的调用序列**（eat 快照 → eat → Bridge）；


**不消耗任何物品**（用新造的物品栈），所以它不是玩法入口，纯验证。





### 验证：新增行为级回归 `test_feed_heal.py`（两版 PASS）





```


verdict: PASS


note: Bridge 命中 HEAL_MEAL=True


note: 血量 12.00 → 13.32（+1.32）


note: 回血幅度 = 1.32（胡萝卜 total=6.6 → heal(1.32) 的 TLM 口径）


[promaid/投喂回血] FeedTestMaid 按 TLM 餐食口径结算 Carrot（DefaultMaidHealSelfMeal）：血量 12.00 → 13.32


```





`+1.32` 正是"她自己吃一根胡萝卜"的回血量——**数字对上了**，这才是"效果等同"的实证。


加上原有六项（`test_maid_load` / `test_spin_air` / `test_server` × 两版）共八项全绿。





**脚手架又踩到四个坑（都已修，留档）**：


①先造"满血女仆"再喂 → `heal()` 被原版上限吃掉 → `20.00 → 20.00` 假失败，必须先造成伤害；


②测试世界是持久化的，残留的**满血**女仆会被选成目标 → 命令改为"取存活中血量最低的那只"；


③按血量升序又选中了 **0 血的残留尸体** → 追加"只取存活"过滤；


④Brigadier 的字符串参数：`word()` 不接受冒号（`minecraft:carrot` 断在冒号）、


   `string()` 要求带引号 —— 只有 `greedyString()` 能吃下裸的物品 id。








## 实测五百四十四【激流：把空袭"收翅俯冲"那一记也换成旋转冲击（空中放行）】





### 改了什么：只开**一个**空中窗口





起手入口从两个变成三个，前两个的护栏**一条都没动**：





| 入口 | 位置 | 起手条件 |


| --- | --- | --- |


| TLM 原生近战 `MaidMeleeRiptideMixin` | 攻击模式 | 仍要求**站在地上** |


| 空袭地面近战 `groundMelee` | 空袭落地后 | 仍要求**站在地上** |


| 空袭**收翅俯冲** `smashHit` | 空袭扑击 | **允许空中**（本次新增） |





"收翅俯冲"本来就是她主动朝敌人砸下去的那一瞬，所以放行它不会碰 实测五百四十一 的护栏


（那道护栏防的是"在起跳/爬升途中横插一次突进"）。`airborne` 取的是 `!onGround()`，


所以她落地之后这一记自动退回地面规则。





### 空中与地面的三条差异（都有理由）





1. **不提前刹住**。地面那版一旦离目标 1.5 格内就把速度归零（免得"顶着怪推"）；空中照用会


   停在半空自由落体，而且 1.5 格根本够不到怪的碰撞箱 → 转一整套也不结算。空中改成只判


   "越过目标所在的那条线"，一路扫过它的碰撞箱——原版 `checkAutoSpinAttack` 的判定是


   **上一帧到本帧的包围盒并集**（扫掠），扫过去就必定结算一次，这正是玩家那记"撞上去"。


2. **重力照常**。反编译实证原版对旋转突进**没有任何**改重力/改速度的处理


   （`LivingEntity` 里 `f_20938_`/`autoSpinAttackTicks` 只出现在 `aiStep` 的递减与


   `checkAutoSpinAttack` 两处），所以空中旋转 = 一次朝敌人的冲量 + 正常抛物线。刻意不造悬停。


3. **坠落伤害由本链路兜掉**。既然重力照常、旋转又有 16 tick，她是边转边掉的；


   突进期间每 tick 把坠落距离清零（与收翅猛击同款口径），落地不自我摔伤。





### 空袭让位（这次是"收窄过"的让位）





突进那 16 tick 里，空袭**只让掉状态机**（`isDashing` 为真时提前 return，顺带收翅、压住原生近战）。


**必须让**：空袭同一 tick 会放烟花（推力沿视线）+ 开滑翔（`travel` 的滑翔分支把水平速度往视线


方向拽并限速），三套速度互相覆盖的结果是"刚起手就被冲掉，只闪一下"。


**这次不会重演 五百四十一 的"不会起飞"**：①起手时机 = 她的攻击时机（有攻击冷却 + 收招硬直，


不是按距离自主触发）；②让位有硬上限（一次旋转的时长 + 陈旧自愈）；③让位放在三件套维护与


"缺件提示"**之后**——装备照穿、缺件照报，被让掉的只有状态机本身。


配套把 `isEngaged`（"空中禁传送"的豁免）并入突进状态，免得主人一远、自动传送把她当场拽走，


白转一场。





### 顺带修的两件事





1. **1.21.1 树的常量漂移**：那份 `MaidTridentSpinBehavior` 的 `DASH_RANGE/SPIN_TICKS/RECOVER`


   还停在 实测五百三十八 那版（10/20/10），而 五百四十一 的定稿表格写的是 4/16/20——


   即 1.21.1 上仍是"从十格开外一路冲过去"的诡异口径。两树现已一致。


2. **突进状态的漏清**：`MaidTridentSpinBehavior.forget()` 定义了两版却从未接线。它现在会让


   空袭让位 + 豁免传送，残留的代价比之前大得多，已接进既有的两处统一清理点


   （登录清理 / 退图清理）——与 实测四百八十三 修滑翔位残留是同一类问题。





### 验证：新增一条**行为级**回归 `test_spin_air.py`





原有两条检查**结构上**覆盖不到本次改动（`test_maid_load.py` 只证明"女仆能被召唤出来、


mixin 没炸"；`test_server.py` 只等 `Done (`），而本次动的恰恰是 五百四十一 出过事的链路。


所以新增：`summon` 一个空袭女仆（NBT 指定任务 + 主手**激流**三叉戟 + 背包鞘翅/烟花）


+ 一只打不死的木桩，观察日志里是否**既起飞、又能空中旋转**。





```


verdict: PASS


note: 起飞=True 空中旋转=True


note: 武器上读到 riptide=True


[promaid/激流突进] SpinTestMaid 旋转冲击（空中起手，激流 3 级，力度 3.00，距离 2.9，目标 SpinTestTarget）


[promaid/飞行作战] SpinTestMaid 放烟花起飞


[promaid/激流突进] SpinTestMaid 旋转冲击（空中起手，激流 3 级，力度 1.8，目标 SpinTestTarget）


```





两版都是"起飞 ↔ 空中旋转"交替（不是卡在某一态），另加 `test_maid_load.py` /


`test_server.py` 两版 PASS，共六项全绿。





**脚手架上的三个坑（都已修，留档以免重踩）**：


①两版服务端日志编码不同（1.20.1 UTF-8 / neo GBK），且 neo 那份是 GBK 夹非法字节，


   必须 `errors='replace'` 解码——否则中文判据全部假失败，现象是"没起飞"而日志里明明有；


②两版附魔 NBT 完全不同：1.20.1 是 `tag.Enchantments`，1.21.1 是


   `components.minecraft:enchantments.levels`（`ItemEnchantments.CODEC` 优先认带 `levels` 层的形式）。


   写错时**不报错、只是没有附魔**，看起来就像"功能没生效"——已加 `data get` 回读自检；


③测试世界是持久化的，`@e[...,limit=1]` 会选中**上一轮**的女仆，选择器一律按名字限定。





### 一个待确认的点





空中旋转期间她**会掉**（这是原版语义：不抵消重力）。按现在的参数，一次 16 tick 的旋转从


起手高度掉约 6~13 格，随后空袭自己切回滑翔接住。若认为"掉得太快、看不全那几圈"，


两个可调量：`MaidTridentSpinBehavior.SPIN_TICKS`（缩短旋转）或 `MIN_UP/MAX_UP`（起手更朝上）。





## 实测五百四十三【那圈"激流特效"：扒出来是官方的一个独立图层，已照抄给女仆】





### 扒出来的结果：方向对了，但它既不是粒子、也不是玩家建模/动作





它是原版挂在 `PlayerRenderer` 上的一个**独立渲染图层** `SpinAttackEffectLayer`。两版源码同构：





```java


if (!entity.isAutoSpinAttack()) return;                       // 唯一门槛：LivingEntity 的方法


VertexConsumer vc = buffers.getBuffer(RenderType.entityCutoutNoCull(


        new ResourceLocation("textures/entity/trident_riptide.png")));


for (int i = 0; i < 3; ++i) {


    poseStack.pushPose();


    poseStack.mulPose(Axis.YP.rotationDegrees(ageInTicks * -(45 + i * 5))); // 45/50/55 度每 tick 三个反向自转


    float s = 0.75f * i;                                    // i=0 时 s=0（退化不可见），实际两层


    poseStack.scale(s, s, s);


    poseStack.translate(0.0f, -0.2f + 0.6f * i, 0.0f);      // 上下错开 0.6 格


    box.render(poseStack, vc, light, OverlayTexture.NO_OVERLAY);


    poseStack.popPose();


}


// box = CubeListBuilder.addBox(-8,-16,-8, 16,32,16)  ← 一个 16×32×16 的大方块


```





**所以那圈东西 ＝ 一个贴着"白色斜纹、透明底"贴图（`trident_riptide.png`）的大方块，


以三种速度反向自转。** 它不在玩家模型里、也不是玩家的动作，门槛只是


`LivingEntity.isAutoSpinAttack()` —— 这反而是好消息：**可以直接照抄给女仆**。


（贴图在原版资源包里就有，两版都确认存在，我们不需要打包任何素材。）





### 复刻做了什么





新增 `MaidSpinAttackEffect`（几何/贴图/三层方块的角度·缩放·位移**逐行照抄**原版），


再按两套模型体系各挂一个图层：





| 模型体系 | 图层 | 挂载点 | 坐标处理 |


| --- | --- | --- | --- |


| Bedrock（TLM 内置模型） | `LayerMaidSpinAttack` | `EntityMaidRenderer` 构造末尾（与鞘翅图层同一入口） | 与原版**同一个空间**，原样照抄 |


| Gecko（酒狐家族等） | `LayerMaidSpinAttackGecko` | `GeckoEntityMaidRenderer` 构造末尾 | 先 `scale(-1,-1,1)` 翻进原版模型空间（反编译实证：Gecko 图层跑在"格、Y 朝上、原点在脚下"的空间） |





判据用**原版那个已同步的旋转标志位**（`isAutoSpinAttack`）：它随实体数据同步到客户端，


而本模组那份"是否正在突进"的状态在服务端、客户端读不到；突进结束时我们会显式清标志位，


所以不会出现"没转却一直冒特效"。





### 与实测五百四十二 的关系





542 把旋转的**枢轴**抬高了 0.6 格（平躺后不再有一半埋在地面以下）；这圈特效是画在


"已经施加过旋转的坐标系"里的，所以两者叠加后观感才对：身体离地一段高度、斜纹方块绕着她转。





### 验证





两树编译零错；包内确认三个新类与 mixin 注册都在；`test_maid_load.py` / `test_server.py`


两版 PASS。**注意**：这是纯客户端渲染，服务端那几条检查覆盖不到，最终观感要在游戏里看。





## 实测五百四十二【激流旋转：把枢轴抬高，别像嵌进地里】





### 为什么偏低（几何上算得出来）





原版施加激流姿态是**绕实体原点（脚底）**转的——`LivingEntityRenderer.setupRotations` 里只有


两次 `mulPose`（`XP(-90 - 俯仰)` + `YP(-75 × tick)`），**没有任何平移**。女仆的模型是从脚下往上


建模的（身体占 y ∈ [0, ~1.8]），平躺之后这段"高度"变成了身体的"前后厚度"（约 ±0.3），于是整个


身体成了一块**以脚底高度为中心的薄片**：下半沉进地面、上半露在外面。平地勉强能看，**斜坡 / 台阶


上一转就像嵌进方块里**。玩家没有这个观感，因为玩家的激流只在水里 / 雨里、身体本来就不贴着地面。





### 修法（纯客户端渲染，机制零改动）





在旋转**之前**（矩阵还是"Y 朝上、原点在脚下"的时候）先把模型整体抬起 **0.6 格** —— 抬的是


**枢轴**，平躺的身体就以"离地 0.6 格"为中心了。新增 `MaidSpinLiftMixin`，注入**原版**


`LivingEntityRenderer.setupRotations` 的 HEAD：Gecko 与 Bedrock 两套模型最终都走这一层，


一处覆盖两边；判据与数值在 `com.maidsmart.client.SpinLift`（只对**正在旋转的女仆**生效，


其余实体 / 状态原样返回）。0.6 是唯一调节量：嫌"像在飘"调到 0.3 左右，嫌"还是压地"就调大。





### 顺带回答："空袭状态下可以触发激流吗？"





当前口径：**只有站在地上时才会**（攻击模式与空袭任务都一样）。空袭的空中链路（起跳滑翔 →


烟花助推 → 收翅俯冲）刻意完全不碰 —— 那正是上一版"不会起飞"的来源；空袭里她**落地之后的


近身战**才会用旋转冲击。想让她在空中（例如收翅俯冲那一下）也能转，说一声，我按那个窗口单独放行。





### 验证





两树编译零错；包内确认新 mixin 与注册都在；`test_maid_load.py` / `test_server.py` 两版 PASS。


**注意**：这条是**纯客户端渲染**，服务端那两条检查跑不到它（`LivingEntityRenderer` 是客户端类），


实际观感要在游戏里看。





## 实测五百四十一【激流改成"替代普通挥砍"；修"不会起飞"与"行动诡异"；去掉旋风特效】





### 一、为什么"不会起飞"（实测五百三十八 引入的回归，两条都是我这边的）





日志实证：13:44–13:45 那一串"旋转冲击（距离 1.5～9.9）"说明当时的版本**在空中也会发起突进**


（只排除了滑翔），加上触发距离 10 格、一轮只要 30 tick —— 她**大部分时间都在突进**；与此同时


`MaidFlightCombatBehavior.tick` 开头有一条"突进期间整段让位"的早退，于是"起跳滑翔 → 放烟花助推"


被反复打断，表现就是**不会起飞**。更糟的是：那个"正在突进"的状态**只要泄漏一次**（任务切换 /


大脑重建 / 被收回时行为没跑到收招），她就永久被判定成"正在突进"，**换什么武器都一直卡着** ——


这正是"换成别的武器也一样"。





### 二、修法（都是"把不该由突进插手的地方还回去"）





1. **突进只在站在地上时发起**（不在地面直接拒绝）→ 空中的空袭链路完全不受影响；


2. **删掉空袭里那条"让位"早退**，空袭状态机永远自己跑；


3. **突进状态自愈**：起手后超过一次旋转的时长还没结束（＝起手了但行为没能跑起来），


   下一次判据里直接丢弃它 —— 再也留不下"永久卡住"的状态。





### 三、触发判定改成"就是她的普通攻击"（回应"判定很奇怪"）





旧版是"目标进 N 格就自主起手"，所以会出现"什么时候触发说不清、还被跳劈等别的行为抢先"。


现在**起手只有两个入口，都是"本来要挥砍的那一记"被替换掉**：





| 入口 | 说明 |


| --- | --- |


| `MaidMeleeRiptideMixin` → TLM 原生近战那一记 `doHurtTarget` | 起手成功 = 这一记算打出去了（TLM 照常吃攻击冷却）；起不了手就**原样调回普通挥砍** |


| 空袭的地面近战 `groundMelee` | 同上，站在地上时才替换 |





共同前提：开关开 ＋ 攻击模式 / 空袭任务 ＋ 主手**激流**三叉戟 ＋ **站在地上** ＋ 目标不超


4 格 ＋ 不在突进中 ＋ 收招硬直已过。**起手时机 = 她的攻击时机**，其余时间这个行为什么都不做


（不再自己找目标）。同时把上一版放大的参数收回来：





| 参数 | 538 那版 | 现在 |


| --- | --- | --- |


| 触发距离 | 10 格 | **4 格** |


| 单次旋转 | 20 tick | **16 tick** |


| 收招硬直 | 10 tick | **20 tick** |





### 四、去掉旋风特效（实测五百三十九 回退）





参考图放大看过：主体是"**横躺自转的本体 + 被甩起来画圆的手持三叉戟 + 白色斜向拖尾**"，


主角其实是被甩起来的武器与它的拖尾 —— 靠粒子拼很难忠实还原。既然直接还原做不到，


就把 539 加的那圈粒子（`sweep_attack` / `electric_spark` / `small_gust`）**整段删掉**


（方法、常量、两处调用，两树都删干净）。现在旋转只剩"与玩家同款的本体自转 + 平躺"


（实测五百三十七 的渲染修复），不再有自造粒子。





### 验证





两树编译零错；`test_maid_load.py` 两版 PASS（女仆真的召唤出来、无 mixin 报错）；


`test_server.py` 两版回归 PASS。桌面那份 v1.2.0 更新文案草稿同步到最新。





## 实测五百四十【修：持激流三叉戟切攻击模式 → 女仆 AI 直接死、收回魂符再放下会消失】





### 根因（是我的锅，日志铁证）





实测五百三十八 新增的 `MaidMeleeRiptideMixin` 注入 TLM 近战任务时，**回调方法写成了非静态**，


而它的注入目标 `MaidMeleeAttack.lambda$create$0` 是 **static** 方法。Mixin 的规则是


"静态目标方法只接受静态回调"，于是在 `MaidMeleeAttack` **首次被加载**（＝女仆生成、


TLM 构建大脑的那一刻）直接 FATAL：





```


[Server thread/FATAL] [mixin/]: Mixin apply for mod promaid failed mixins.promaid.json:MaidMeleeRiptideMixin


    -> com.github.tartaricacid.touhoulittlemaid.entity.ai.brain.task.MaidMeleeAttack


org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException:


    non-static callback method ...MaidMeleeAttack::promaid$riptideReplacesMelee


    targets a static method which is not supported


```





`MaidMeleeAttack` 变形失败 → 女仆的大脑构建不出来 → 表现就是"AI 直接死、一动不动"；


从魂符再放下来会在同一处再炸一次，于是**女仆当场消失**。





注意：这里 `require = 0` 救不了 —— 它只容忍"没匹配到"，不容忍"回调签名非法"。





### 修法





两树的回调都改成 `private static`（静态回调仍然照常把 receiver 作为第一个参数收下）。


`javap` 已确认两个包里的方法都带 `static` 修饰。





### 为什么上一轮的服务端回归没抓到（重要教训）





`test_server.py` 只等 `Done (`，而 `MaidMeleeAttack` 这个脑任务类**只有在女仆实体生成、


TLM 构建大脑时才会被加载** —— 所以那次回归"全绿"却根本没碰到这条注入路径。





新增 `test_maid_load.py` 补上这个盲区：起服 → 等 Done → 控制台 `summon touhou_little_maid:maid`


→ 盯日志 → 判据 = **真的召唤成功** ＋ **没有任何 mixin 应用失败 / `MaidMeleeAttack` 报错**。


两版现在都 PASS（都有 `Summoned new ...` 且无 FATAL）。以后凡是要动 TLM 大脑行为类的注入，


都得先过这条检查。





### 顺带清掉的隐患





两个测试服的 mods 目录里还躺着旧包（`promaid-1.1.1` / `promaid-test` / `promaid-1.1.0`，


同 modId 双包会让"测试到底加载了哪一份"失去意义），已移到


`patched/backup_old/stale_server_jars/`（是移走不是删除）。





### 关于已经消失的那台女仆





那次消失是实体被移除导致的：如果魂符里还留着她的数据，用修好的包再放下来应当正常；


如果魂符已经空了，那台女仆的存档数据我这边找不回来 —— 这是我的 bug 造成的，很抱歉。





## 实测五百三十九【激流旋转补上"周身旋风"特效】





### 之前为什么没有





实测五百三十七 让**本体**的旋转回来了（TLM 把原版那对旋转反号抵消掉，已修），实测五百三十八 让这 20 tick


放得完、看得清。但玩家的观感其实由两部分组成：**① 横躺高速自转的本体**、**② 被甩起来绕体画圆的手持物


与粒子拖尾**。女仆这边 ① 有了；② 里的手持物由 TLM 的模型图层负责、也会随模型一起转，**但粒子拖尾我们


一颗都没生成** —— 所以看上去还是"只有音效"。





### 补了什么





旋转期间（起手 + 每 tick）在服务端就地把"绕着她转的旋风"画出来，附近玩家都看得到：





| 粒子 | 用途 |


| --- | --- |


| `sweep_attack`（白色弧刃） | 两条对置、每 5 tick 转一圈 —— 旋风的主体观感 |


| `electric_spark`（青白火花） | 反向、半径更小的第二环，呼应原版激流的青蓝色 |


| `small_gust`（小风旋，1.21.1）/ `cloud`（白雾，1.20.1） | 贴着她本体的那一圈"风"（1.20.1 没有 gust 系粒子，那是 1.20.5+ 才加的） |


| `crit` + `gust_emitter_small` | 起手与每 6 tick 一圈爆点，让"发动"这一下看得见 |





粒子只是观感，全部包在 `try/catch` 里：任何异常都不影响攻击结算。两树的粒子字段名都经过字节码核对


（1.20.1 是从 `ParticleTypes` 静态初始化的"注册名字符串 → 字段"配对里读出来的，1.21.1 按官方名并核对了


声明类型），不是猜的。





### 验证方式





两树 `javac` 编译零错；进游戏看到"旋转 + 绕体旋风"同时出现即算通过。





## 实测五百三十八【激流三叉戟：独立攻击链路（旋转冲击），并让旋转真的看得见】





### 先查清"为什么上一个版本还是看不清"





实测五百三十七 修的是**渲染被抵消**（TLM 的 `GeoReplacedEntityRenderer.setupRotations` 把原版那对


反号重复旋转消成了单位矩阵），那条修复本身是对的、也确实生效了 —— 客户端 `debug.log` 有实证：





```


[12:46:21] [Render thread/DEBUG] [mixin/]: Mixing MaidGeckoSpinDedupMixin from mixins.promaid.json


    into com.github.tartaricacid.touhoulittlemaid.geckolib3.geo.GeoReplacedEntityRenderer


```





但实测记录（`promaid.log`，12:47–12:49）每次都写着"激流 3 级，力度 3.00"，而**触发距离只有 5 格**：


3 格/tick → **约 2 tick 就撞上目标**；而"撞到实体"按原版 `checkAutoSpinAttack` 会**立刻收招**


（旋转计数清零 + 速度乘 −0.2）。20 tick 的旋转实际只放了 2 tick ≈ 0.1 秒，所以"动作也不明显"。





顺带取证：**原版玩家的旋转没有任何粒子特效**（`LivingEntity` 与 `ItemInHandRenderer` 里


`isAutoSpinAttack` 附近都没有 `addParticle`；后者那条只是第一人称手里三叉戟的翻转）。玩家看到的


"特效"就是**身体高速自转 + 平躺**本身 —— 所以这次要解决的是"让它放得完、看得清"。





### 改了什么（两树各一份）





**① 触发距离 5 → 10 格**（`DASH_RANGE`）。攻击既然就是"冲过去撞"，就得留出冲刺距离。





**② 突进期间每 tick 顶住标志位与速度**（`MaidTridentSpinBehavior.tick`）。原版"撞人即收招"是给


"玩家自己瞄着飞"设计的；女仆每次都朝怪撞，照搬就永远看不到旋转。现在：





- 每 tick 把剩余 tick 与标志位 4 重新写回 → **20 tick 的旋转画面完整放完**；


- 每 tick 按剩余比例重新给推力，**越过目标横线后停住**（不是一路滑出去几十格）→ 冲上去撞，


  然后贴着怪把这一圈转完；


- 撞到横向方块仍照原版提前收招。





**③ 同一目标每次突进只打一下**。标志位被顶住后，原版"撞击即收招"那条天然的一次性保护就没了，


所以 `onSpinTouch` 按本次突进记 hit 集合去重（最终效果与原版一致：一冲一伤）。





**④ 攻击环节被替换**（即"替换原本的攻击环节"）。持激流三叉戟且在攻击模式 / 空袭下时，


普通挥砍不再出手：





| 原本的攻击步骤 | 现在 |


| --- | --- |


| TLM 原生近战 `MaidMeleeAttack`（`swing` + `doHurtTarget`） | 新增 `MaidMeleeRiptideMixin`：把它的"近战可达"判据（1.20.1 `m_217066_` / 1.21.1 `isWithinMeleeAttackRange`，两版都是 **public**）判成 false → 整段不发生（不挥砍、不结算、不吃冷却） |


| 空袭的俯冲猛击 `smashHit` | 换成 `tryStartDash`：起手成功就按"这次出手了"收尾俯冲 |


| 空袭的地面近战 `groundMelee` | 同上（吃同样的攻击冷却） |





`MaidMeleeAttack` 这个任务**只负责"打成"**，走路 / 面向 / 索敌 / 冷却记忆都在别的任务里 —— 需求要的


"其他的如索敌等不变"因此天然成立。种田、伐木等其它任务里拿激流三叉戟**照旧正常挥砍**


（`replacesMelee` 带了任务口径，不会把女仆变成不会还手的沙包）。





**⑤ 空袭的移动权交接**。`MaidFlightCombatBehavior.tick` 在突进期间直接返回（这一秒归突进：不抢速度、


不点烟花、不改滑翔位）；另外**滑翔中不发起突进**（鞘翅物理会与突进互相抢速度，空袭那边本来就是


收翅俯冲时才出手）。





### 现在的表现





拿激流三叉戟 → 切成攻击模式或近战空袭 → 怪进 10 格：她朝怪冲过去，**平躺 + 高速自转 20 tick（1 秒）**，


撞到结算一次（攻击力 + 附魔，含击退 / 火焰附加 / 荆棘），撞完停在怪身边把这一圈转完，10 tick 硬直后


再来一次。走路、索敌、排班、其它任务一律不变。总开关仍是 `combat.riptideDash`


（面板「激流三叉戟旋转冲击」，默认开）。





### 验证方式





- 两树 `javac` 编译零错；


- 新增注入点经 `javap` 实证：TLM `MaidMeleeAttack.lambda$create$0` 内的


  `EntityMaid.m_217066_(LivingEntity)Z`（1.21.1 为 `isWithinMeleeAttackRange(LivingEntity)Z`），


  两版同名同签名、目标方法 public 可直接回指；


- "渲染被抵消"的完整推导见 实测五百三十七 那一段（CFR 反编译 + `javap` 字节码，非推测）。





## 实测五百三十七【激流旋转终于转起来了；弩用普通烟花默认改关】





### 一、Gecko 模型女仆的激流：只有音效和伤害，没有旋转与俯卧（bug 修复）





**症状**：只有音效和伤害，没有玩家那样的旋转特效与动作。





**根因：TLM 自己那对旋转把原版的那对正好抵消了。**





原版 `LivingEntityRenderer.setupRotations` 早就实现了激流姿态，而且**不是 Player 专属**


（1.20.1 字节码 197-199 行、1.21.1 202-204 行）：





```java


} else if (entity.isAutoSpinAttack()) {


    poseStack.mulPose(Axis.XP.rotationDegrees(-90.0f - entity.getXRot()));


    poseStack.mulPose(Axis.YP.rotationDegrees((entity.tickCount + partialTicks) * -75.0f));


}


```





TLM 1.5.3 的 `GeoReplacedEntityRenderer.setupRotations` 在 `super`（＝上面这段）之后


**又叠了同轴反号的一对**（javap 实证：`invokespecial LivingEntityRenderer.setupRotations`


→ `invokevirtual LivingEntity.isAutoSpinAttack` → 两次 `mulPose`，常量 `75.0f`）：





```java


if (deathTime <= 0 && entity.isAutoSpinAttack()) {


    poseStack.mulPose(Axis.YP.rotationDegrees((entity.tickCount + partialTicks) * 75.0f));


    poseStack.mulPose(Axis.XP.rotationDegrees(90.0f + entity.getXRot()));


}


```





`mulPose` 是**右乘**，于是合成矩阵为





```


M = M0 · XP(a) · YP(b) · YP(-b) · XP(-a) = M0      a = -90 - xRot，b = -75·(tick + partial)


```





中间那对同轴 YP 相邻、直接消成单位矩阵，剩下的一对同轴 XP 也消成单位矩阵 →


**净旋转恒等于什么都不做**。所以走这个渲染器的模型（Gecko 系：酒狐家族等）突进时永远不转、


也不俯卧。存档实体 NBT 核实：这边的女仆正是 Gecko 模型（`geckolib:winefox` 系列、


`geckolib:kluonoa`）；TLM 内置基岩模型（`touhou_little_maid:*`，走 `EntityMaidRenderer`）


不经这里，本来就有原版那一份，不受影响。





**修法（两树）**：新增客户端 mixin `MaidGeckoSpinDedupMixin`，把 TLM 那次


`isAutoSpinAttack()`（1.20.1 `m_21209_`）读取重定向为 `false` —— 只让它那对重复旋转整段


跳过、把**原版那一份留下**，于是效果与玩家**完全同款**（不自己写角度、不重复叠加）。该 `if`


块里除这两次 `mulPose` 没有别的语句，且标志位只在突进中为真，所以不突进的女仆一个字都不


受影响。取 `require = 0`：万一 TLM 以后自己删掉这对（＝它修好了），我们自动不再干预；写成


`require = 1` 会把"TLM 修好了"变成启动崩溃，反而有害。





覆盖范围如实说明：只覆盖走 `GeoReplacedEntityRenderer` 的 **Gecko 模型**；YSM 模型由 YSM


自己的 `IGeoEntityRenderer` 渲染、不经这个类（TLM 只给它 `is_riptide` molang 变量），那条路


的旋转取决于 YSM 本体，本 mixin 管不到也不该乱管。总开关仍是 `combat.riptideDash`（默认开）。





### 二、烟花火箭"一点伤害都没有"：不是 bug，是原版机制；开关默认改关





**结论：不是你哪里出错，也不是版本差异 —— 原版任何版本里，没放烟火之星的普通烟花火箭


伤害恒为 0。** 两版字节码完全一致：爆炸结算先看 `Fireworks.Explosions`，**为空则整段早退**。





```java


// 1.20.1 FireworkRocketEntity.m_37087_（就是爆炸伤害的结算）


float dmg = 0.0f;


ListTag ex = "Fireworks" -> "Explosions";


if (ex != null && !ex.isEmpty()) dmg = 5.0f + (float)(ex.size() * 2);


if (dmg > 0.0f) { /* 才结算伤害 */ }





// 1.21.1 同名逻辑 dealExplosionDamage()


float dmg = 0.0f;


List<FireworkExplosion> ex = this.getExplosions();


if (!ex.isEmpty()) dmg = 5.0f + (float)(ex.size() * 2);


if (dmg > 0.0f) { ... }


```





顺带：`m_37086_`／`hasExplosion()` 用的是同一个判据，所以普通烟花**连撞到方块的爆炸都不


触发**，只冒烟（客户端 `m_7822_`／`handleEntityEvent` 那条分支）。





- 想要伤害就**放烟火之星**（星越多伤害越高：`5 + 2×星数`）；`/give minecraft:firework_rocket`


  给的是无爆炸的那种，永远 0 伤害。


- **女仆这条链路本身没问题**：`shootCrossbowFirework` 把取到的弹药栈**原样**塞进


  `new FireworkRocketEntity(level, ammo, shooter, …, true)`（内部 `stack.copy()`，NBT／组件


  完整保留），带星的烟花在她手里照样打出 `5 + 2×星数`。





**配置默认值改关**：`combat.crossbowPlainFirework`（面板「弩用普通烟花当弹药」）


`true → false`。理由就是上面这条：普通烟花当弹药＝白烧一枚飞行燃料（0 伤害）。默认改成


"只有带烟火之星的**攻击性烟花**才当弩弹药，普通烟花全留给飞行推进"；想退回原版玩家口径


（任意烟花都能当弹药）随时可打开。配置面板说明与游戏内指南同步改写（指南里补上


"普通烟花打出去本来就是 0 伤害"的依据）。





> **已存在的配置文件不会被新默认值覆盖**（Forge 只补缺失的键、不覆盖已有值）：1.21.1 实例


> 的 `config/promaid-common.toml` 里这一项原本是 `true`，已一并改成 `false`；1.20.1 实例与


> 两个测试服务器的同名文件也核对过（1.20.1 客户端那份当时还没有这个键，下次启动写入新默认）。





### 验证方式





- 两树 `javac` 编译零错（1.20.1 239 源 / 1.21.1 241 源）；


- 注入点描述符经 `javap -p -c` 核对（1.20.1 `m_7523_` 内 `LivingEntity.m_21209_()Z`、


  1.21.1 `setupRotations` 内 `LivingEntity.isAutoSpinAttack()Z`），并确认编译用的 TLM jar


  与运行期 jar 逐字节相同（1.20.1 `maidmods\original_tlm.jar`、1.21.1 `libs_neo` 副本）；


- 本文所有"原版／TLM 行为"结论均来自 CFR 反编译 + `javap` 字节码，非推测。





## 实测五百三十六【不得把方块搭在主人身上（尤其是头部）】





### 判据





目标格与主人碰撞箱**真正交叠**才算被占（`AABB(BlockPos).intersects(owner 碰撞箱)`，


用严格不等式）。两个后果：





- 你**站在方块上**时，脚下那格与碰撞箱只是相切 → 不算被占 → 该在你身下垫的


  照垫，正常搭路/垫高不受影响；


- 碰撞箱真正探进去的格（身体格、**头部格**）才算 → 正是要防的"塞进身体/盖住头"。





女仆自己所在格不受影响（垫高就是往自己脚下垫）。





### 开关





`misc.noPlaceOnOwner`（默认开）——面板：**系统与杂项 → 交互与杂项 → 不得搭在主人身上**。





### 覆盖范围





自保搭高、搭路、挖矿垫脚、伐木垫脚、插火把、AI 工具「把这里填上」、蓝图建造、


碑石建造，全部 52 处落点。闸口都在**取料之前**（被拦不扣方块）。





### 蓝图/碑石是"延后"而非"跳过"





它们要"把图纸填满"，跳过会永久留洞——所以是延后：**你让开后她下一轮自动回来补上**。


不会因为你站了一下就在建筑里留个洞。





### 副作用





无。默认 = 修复方向；关掉开关恢复旧行为。





## 实测五百三十五【弩用普通烟花当弹药：加开关（默认开 = 原版行为）】





### 开关





`combat.crossbowPlainFirework`（配置面板：生存与复活 → 落地缓冲 → **弩用普通烟花当弹药**）





- **默认开** = 任意烟花火箭都能当弩的弹药（原版 `CrossbowItem` 的弹药谓词只看


  是不是烟花火箭，不看有没有爆炸组件；玩家拿弩装普通烟花照样能射）；


- **关** = 只有带烟火之星的【攻击性烟花】才当弹药，普通烟花一律留给飞行推进。





### 两处判据读同一个开关





`hasAmmoFirework`（判"有没有弹药"）与 `takeBestCrossbowFirework`（实际取用）


都读这一项——只改一边会出现"判定有弹药、起飞后一发打不出来"的死角。





### 优先威力大的不受开关影响





开关只管"普通烟花算不算弹药"；"先打哪一个"一律按爆炸条目数降序


（见 实测五百三十三），有带烟火之星的先打带烟火之星的。





### 本次事故说明





这条开关的第一次实现中途丢失：修复被写坏的 `PromaidConfigScreen.java` 时误用


`git checkout --` 回退了带未提交改动的文件，连带 10 条界面行与"投喂食物勾选"


功能一并丢失。已从事故前构建产物反编译比对后全部补回（1.20.1 448/448 方法、


54/54 字段；1.21.1 454/454、54/54），配置字段与语言条目当时未受影响。


本次补的正是那次丢失的配置面板行。





### 副作用





无。默认值 = 加入这条开关之前的行为。





## 实测五百三十四【激流三叉戟：把玩家的"旋转突进"套到女仆身上】





### 关键结论：标志位是通用的，动画不用自己写





反编译两版后有三个决定性发现：





1. **旋转突进的整套机制都在 `LivingEntity` 上，不是 Player 专属**：


   `autoSpinAttackTicks`（1.20.1 为 `f_20938_`）与"标志位 4"都在 LivingEntity，


   `LivingEntity.tick` 里就会递减 tick、扫掠这次位移的包围盒、撞墙收招。


   置位之后女仆**自动获得原版整套行为**。


2. **动画 TLM 早就做好了**——它读的就是这个标志位：


   ```


   GeoReplacedEntityRenderer.setupRotations（两版都有，字节码实证）：


       if (deathTime <= 0 && isAutoSpinAttack()) {


           mulPose(Axis.YP.rotationDegrees((tickCount + partialTicks) * 75.0f));  // 高速自转


           mulPose(Axis.XP.rotationDegrees(90.0f + getXRot()));                    // 平躺突进


       }


   ```


   而且 `YSMBinding` 还把它暴露成 molang 变量 **`is_riptide`**


   （1.20.1 读 `m_21209_()`、1.21.1 读 `isAutoSpinAttack()`——同一个标志位）。


   所以"动画这一块"根本不用我写：**标志位一置，Gecko 模型女仆就自动转起来**，


   模型作者还能用 `is_riptide` 写专属动画。Bedrock 模型那条路 TLM 自己没做


   （`EntityMaidRenderer` 无此分支），与 TLM 原版一致，本模组不额外补。





### 唯一缺的一环：伤害





`checkAutoSpinAttack` 撞到实体时回调的 `doAutoAttackOnTouch`，在 **LivingEntity 上是空实现**


（1.20.1 字节码就一条 `return`），**只有 Player 覆写成 `attack()`**。所以女仆必须自己补。


新增 `MaidSpinAttackTouchMixin` 注入基类（不改 TLM 源码），回调到


`MaidTridentSpinBehavior.onSpinTouch` 结算：攻击力 + 附魔加成 → 击退 → 火焰附加 → 荆棘 →


命中音；主人/友方与非法目标一律跳过。





### 实现（两树）





- **`MaidTridentSpinBehavior`**：触发条件 = 攻击模式（`touhou_little_maid:attack`）或


  **近战空袭** + 主手是**激流**三叉戟 + 有存活敌对目标且在其 5 格内 + 冷却好；


  触发时照原版：`push(方向 × 3.0×(1+等级)/4)` → 置 tick 数 20 → 置标志位 4 →


  按等级放 `TRIDENT_RIPTIDE_1/2/3` → 耐久扣 1。行为优先级 235（低于落地水 240、高于战术 230）。


- **`LivingEntitySpinAccessor`**：新开的 mixin。**不能复用** `EntityFlagInvoker`——


  滑翔位走的是 `Entity.DATA_SHARED_FLAGS_ID`，而旋转突进读的是


  `LivingEntity.DATA_LIVING_ENTITY_FLAGS`，**是两个不同的 SynchedEntityData**（字节码实证）。


- 配置项 `combat.riptideDash`（默认开）+ 面板一行「激流三叉戟突进」+ 四份 lang。





### 与玩家版本的刻意差异（三处）





| 差异 | 理由 |


| --- | --- |


| **不要求在水里/雨中** | 原版那道闸门是为"游泳突进"设计的，女仆在陆战/空袭时根本不在水里；照搬等于永远触发不了。水外可用是本次需求的核心。 |


| **方向取"她→目标"而非视线** | 玩家自己瞄，女仆得自动瞄准；竖直分量夹在 `[-0.15, 0.35]`，避免目标在脚下时一头扎进地面。 |


| **伤害 = 攻击力 + 附魔** | 1.20.1 玩家激流走普通 `attack()`（随攻击冷却缩放）、1.21.1 是固定 8.0 + 附魔；女仆统一成一种口径（同 `hitOne`，不吃暴击倍率），两树一致。 |





## 实测五百三十三【弩认烟花火箭当弹药 + 优先用威力大的（烟火之星多的）】





### 为什么原来不认





旧版弩的弹药判据是**只认带 `Explosions`（爆炸组件）的烟花**——那是 实测四百九十九 定的，


理由是"不带爆炸的烟花打出去是 0 伤害，拿它当子弹等于白烧一份飞行燃料"。





这个理由**只说明它弱，不说明它不能用**：原版 `CrossbowItem` 的弹药谓词认的就是


**任意烟花火箭**，不看有没有爆炸组件；玩家拿弩装普通烟花照样能射。而本模组却因此


把玩家那叠普通烟花判成"不是弹药"→ 报"缺弹药"→ 一发不开。





### 改了什么（两树）





1. **弹药口径放宽**：`hasAmmoForRanged` 的弩分支改为直接复用 `hasFirework`


   （任意烟花火箭都算），不再要求带爆炸组件。原来的 `hasExplosiveFirework` 随之删除。


2. **按威力挑弹药**：新增 `MaidFlightKit.fireworkPower(stack)` = **爆炸条目数**


   （= 合成时用掉的烟火之星个数，普通烟花 = 0），取用改用


   `takeBestCrossbowFirework`——**威力降序**挑，同威力时按"副手 → 主手 → 背包"的稳定次序。


   于是：有带烟火之星的先打带烟火之星的，没有才用普通烟花兜底。


3. `isExplosiveFirework` 保留但改为 `fireworkPower(stack) > 0`（语义不变，去重复实现）。





### 为什么"烟火之星多"就等于"威力大"（不是拍脑袋）





原版伤害只有这一个数在决定——`FireworkRocketEntity.m_37087_` 字节码：





```


伤害 = 5.0f + 2 * Explosions.size()     // 每多一枚烟火之星 +2


不带爆炸 → 整段早退                       // 0 伤害


```





（同一方法里还可见：对 5 格内实体做 `(5 - 距离)/5` 的开方衰减、对发射者本人不结算。）


所以"烟火之星多"与"打出去更疼"是同一件事，排序依据直接取自原版公式。





### 副作用（如实说明）





普通烟花现在**可能被当弹药打出去**（没有更好的时候兜底）。这是原版玩家行为


（拿弩装普通烟花就能射），不再是需要规避的意外；代价是极少数情况下会烧掉一枚


本可当飞行燃料的普通烟花。弹药耗尽后仍回落到射箭。





## 实测五百三十二【激流三叉戟按"近战武器"归类（原版里它投不出去）】





### 提问与结论





问："TLM 是怎么处理三叉戟的激流附魔的？我觉得我们的空袭也需要注意一下这个，


理论上激流三叉戟应被判定为近战武器。"





答：**TLM 完全没处理**——两个版本的 jar 全文搜 `m_44932_` /


`getTridentSpinAttackStrength` / `startAutoSpinAttack` 全部 **0 命中**


（只剩 geckolib 一个客户端动画绑定读 `isAutoSpinAttack`）。TLM 的


`TaskTridentAttack` 只做了"剥忠诚"一件事，**投掷时不看激流**。





而原版 `TridentItem` 两版字节码都证明：**带激流的三叉戟根本投不出去**——


`use`/`releaseUsing` 里第一道闸门就是"激流 > 0 且不在水中/雨中 → 直接 return"，


而**造 `ThrownTrident` 那一支只在激流 = 0 时走**（1.20.1 是 `iload 7; ifne` 跳过），


激流 > 0 走的是 `startAutoSpinAttack` + `push` 的旋转突进分支。





所以"TLM 能投"这件事本身就是个不一致：玩家拿着它投不出去，女仆却能投。


这条判断是对的，本轮按该口径改。





### 改了什么（两树）





1. 新增 `MaidFlightKit.isRiptide(stack)`——1.20.1 走 `EnchantmentHelper.m_44932_`


   （方法体就读 `Enchantments.f_44957_` = RIPTIDE，字节码实证）；


   1.21.1 走 `stack.getEnchantments().keySet()` + `Holder.is(Enchantments.RIPTIDE)`


   （那个重载不需要 `RegistryAccess`，单参判据也能用）。


2. `isRangedWeapon` 里把激流三叉戟**排除** → 远程空袭的三件套不齐


   （会照常提示"缺远程武器"，不会静默不动）；近战空袭里它照旧算近战武器


   （`isMeleeWeapon` 不看激流，三叉戟带 8.0 攻击力属性，两版一致），


   由俯冲猛击那一记结算。


3. `MaidFlightRangedTask.throwTrident` 补上原版闸门：带激流就不投


   （正常路径不会走到——第 2 条已挡住；这条是防任务切换竞态的兜底）。





### 手册同步





远程空袭的"武器位"说明补了"带激流的三叉戟除外"；弹药那段补了一句说明


（想用投掷三叉戟请放在近战空袭/攻击任务里）。





### 没动的部分





TLM 式的"剥忠诚"保留（防副本飞回来被捡走刷三叉戟）；穿刺/引雷/火焰附加照旧由


`ThrownTrident` 自己结算；御币、弩、弓的通道一律不变。





## 实测五百三十一【飞行远战的手持武器附魔：弩（多重射击/穿透）+ 快速装填】





### 现象





"飞行没有办法触发手持武器的附魔。比如弓和弩，还有三叉戟。"





### 核查结论（逐个反编译实证，不是猜）





| 武器 | 结论 |


| --- | --- |


| **弓** | **本来就是通的**：1.20.1 侧的 `shootArrow` 已逐条照抄 `BowItem.releaseUsing` 的


Power/Punch/Flame/Infinity（SRG 名与常量逐字节一致）；1.21.1 侧箭矢带武器（`firedFromWeapon`），


命中时由 `EnchantmentHelper.modifyDamage` / `doPostAttackEffectsWithItemSource` 自行结算。 |


| **弩** | **真缺**：多重射击、穿透此前**一次都没有**触发过（旧版把弩当弓、自己造一支箭就完事）。 |


| **三叉戟** | 穿刺/引雷/火焰附加由 `ThrownTrident` 自己读物品结算（本来就通）；


**忠诚**是 TLM 两个版本都刻意剥掉的（防"副本飞回来被捡走"刷三叉戟），本模组照搬，故保持不动。 |





### 弩的修法（两树）





照原版 `CrossbowItem.createProjectile` + `performShooting` 的装配与发射：





- **多重射击**（`MULTISHOT`）：1 发 → **3 发**，偏角按原版字节码里的 **0° / -10° / +10°**；


  弹药仍只扣 **1** 份（原版第 2、3 发用的是副本，"1 支箭变 3 支"正是这个附魔的价值）；


- **穿透**（`PIERCING`）：1.20.1 侧显式 `setPierceLevel`（原版 `m_36767_`）；


  1.21.1 侧**不用手动设**——那边 `AbstractArrow` 带武器的构造器里就


  `EnchantmentHelper.getPiercingCount(...)` → `setPierceLevel`，且该方法是 **private**，


  原版就是要它自动生效；


- **命中音**改成 `CROSSBOW_HIT`（原版 `createProjectile` 的唯一额外动作）；


- 耐久仍是**每次射击扣 1**（原版 `onCrossbowShot` 口径），不是每支箭扣 1。





### 快速装填（`QUICK_CHARGE`）





原版弩的蓄力时长 = `25 - 5 × 等级`（1.20.1 `m_40939_` 字节码：`25; iconst_5; imul; isub`）。


飞行时没有"蓄力"这个动作，所以把**同一相对幅度**搬到开火间隔上：


`间隔 = 基础 20 tick × (25 - 5L) / 25`——III 级 20 → **8 tick**，与原版"25 → 10 tick"


同为 2.5 倍提速；下限 4 tick，避免叠出荒谬射速。弓没有射速类附魔，一律走基础间隔。





### 没动的部分





弓的附魔结算通道、三叉戟的忠诚剥离（TLM 同款）、御币弹幕的三个专属附魔、枪械通道，


全部照旧。





## 实测五百三十【垂直占位的高度容差 0 → 10：差 10 格以内一律走原链路】





### 改了什么





`ALTITUDE_TOLERANCE`（实测五百二十七 引入的"已占位"容差）**0.0 → 10.0**，两树同改：





```java


/** 判据：她比目标低 ALTITUDE_TOLERANCE 格以内即视为"已占位" */


private static boolean onTargetAltitude(EntityMaid maid, LivingEntity target) {


    return maid.getY() >= target.getY() - ALTITUDE_TOLERANCE;   // 10.0


}


```





- 目标高出她 **10 格以上** → 进垂直占位相位：朝目标爬升，并且把当前这枚烟花的推力吃完


  才交棒（实测五百二十八）；


- 差 **10 格以内**（含等高、含她更高）→ 原链路直接应付：近战俯冲猛击 / 远战盘旋开火。





### 连带影响（同一个判据）





`faceLaunchDirection` 用的就是这条判据，所以"10 格以内"连**起飞朝向**也回到老口径


"背离 + 向上"（那是为地面目标设计的：爬升时别站在对方近战/爆炸包线里）；只有


"高出 10 格以上"才会切成"朝目标 + 抬头"顺着他爬——正是"用原链路应付"的字面意思。





### 没动的部分





地面起飞门槛（水平 ≤ 20 格）、近战 62° / 远战 45° 的爬升仰角、烟花内置 CD、


以及实测五百二十九 的朝向修正，全部照旧。





## 实测五百二十九【1.21.1 起飞/爬升方向歪掉：两个朝向方法漏了"喂 LookControl"】





### 现象





"起飞方向有问题。不是在向着敌人的方向起飞。"





### 根因（反编译实证 · 1.21.1 客户端 jar）





朝向有两条通道，**两条都得走**，1.21.1 侧只走了一条：





1. `applyRotation(...)` 直接写 `yRot` / `xRot`（含 `yRotO` / `xRotO`，渲染插值要用）；


2. `getLookControl().setLookAt(x, y, z, 360, 360)` 把"期望角度"交给 `LookControl`。





为什么第 2 条不能省：`Mob.serverAiStep()` 里 `customServerAiStep()`（脑活动 → 本行为）在


**字节码偏移 189**，`LookControl.tick()` 在**偏移 236**——**后手执行**。而 `LookControl.tick()`


第一件事就是 `if (resetXRotOnTick()) mob.setXRot(0)`（对女仆恒真），**同一 tick 内就把我们


刚写进去的抬头角归零**；若它手上还留着"期望点"（`lookAtCooldown > 0`，主人 / 游荡 /


上一次瞄准留下的），还会拿**那一份残留**把俯仰和头朝向重新压过去。烟花推力是沿


`getLookAngle()` 给的，于是她既爬不起来、方向也被带偏。





实测四百八十一（1.20.1）当时就是靠"两手都上"解决的；问题出在 1.21.1 移植时只把那一套


搬给了 `faceAwayAndUp` / `faceTarget`，**`faceUpForward` 与 `faceOrbit` 一直漏着**——


这是两棵树唯一的口径差，也正是"不朝着敌人起飞"的根（`faceUpForward` 恰好是起飞、垂直


占位爬升、远战俯冲助推三处的朝向来源）。





### 修法





把 1.20.1 的写法原样补到 1.21.1 的这两个方法。期望点取"朝目标 1 格 + 抬高 tan(仰角)"，


`LookControl` 据此反解出的俯仰角与传入的 `pitch` 同口径；`faceOrbit` 额外按 1.20.1 取


`h = |o|` 再乘进 Y 分量：





```java


applyRotation(maid, yaw, pitch);


try {


    double tan = Math.tan(Math.toRadians(-pitch));


    maid.getLookControl().setLookAt(


            maid.getX() + dx / dh, maid.getEyeY() + tan, maid.getZ() + dz / dh,


            360.0f, 360.0f);


} catch (Throwable ignored) {


}


```





### 影响面





- 1.21.1：起飞方向、垂直占位爬升、远战"俯冲助推"朝向（同用 `faceUpForward`）、


  远战盘旋的高度保持（`faceOrbit`）四处一起纠正；


- 1.20.1：本来就是齐的（四个朝向方法都喂了 `LookControl`），**本轮未改动**。





## 实测五百二十八【空袭垂直占位：够高之后不立刻撤推力，等这枚火箭烧完再交棒】





### 上一版（实测五百二十七）漏掉了什么





"高于或等于就落回原链路"这个判据本身没错，错在**落回得太干脆**：挂载型烟花的推力方向


是**每 tick 跟着她当时的视线走**的，而"抬头朝目标爬"这个朝向只有垂直占位相位在给。


她一到目标高度就切走 → 朝向立刻被后一段"瞄准敌人"改成基本水平 → 同一枚烟花剩下的


20~30 tick 推力全被摊到水平方向。结果是：她刚爬到的那点高度又还回去，敌人只要还在升，


她就在目标高度上下来回蹭，**永远攒不出"在它头上"这个占位**。





### 现在的口径





进相位的判据从一条变成两条（或）：





```


进垂直占位相位 = 还在目标身下（onTargetAltitude 为假）


              或 本相位点着的这枚烟花推力还没烧完（CLIMB_BOOST 且 LAUNCH_LEFT > 0）


```





- `LAUNCH_LEFT` 在本相位里的含义改写为"**当前这枚烟花的推力还剩多少 tick**"


  （近战 30 / 远战 20）。烟花实际寿命是 20~31 tick、`FIREWORK_COOLDOWN = 30`，


  所以"推力刚烧完"基本就是"下一枚可以点了"，不引进新的计时器。


- **够高之后不再补烟花**：只有"还差高度"时才调 `tryLaunch`。否则冷却一好又点一枚、


  本相位被自己无限续命，她会一路烧到世界顶端。


- 够高 + 推力烧完 → 下一 tick 自然交棒给原链路（近战俯冲猛击 / 远战盘旋开火）。


- 新增 `CLIMB_BOOST`（本相位标记）：同时纳入 `isEngaged`，爬升途中也算"正在打这一轮"，


  不会被"主人太远自动传送"拽走（与实测四百八十七同款豁免）；`forget` / `pauseRound` /


  `clearAll` 三处同步清理，不会残留。





### 效果





对着比自己高的敌人：她一路爬、把每一枚烟花的推力吃满，**越过目标高度之后还会继续升一段**，


在它头上攒出高度差，然后才转俯冲（近战）/ 盘旋（远战）。敌人若一直在升，追得上就追；


追不上那是火箭抬升速率本身的极限，不再是"自己把推力扔掉"。





## 实测五百二十七【空袭垂直逻辑改为"垂直占位"：爬到目标头上再谈出手】





### 换掉了什么





上一轮（实测五百二十五）用的是"目标高过 **8 格**就朝它爬"的启发式。本轮按需求换成


**唯一的垂直判据**，8 格那个值连同 `AIR_TARGET_ABOVE` 常量一起删掉：





```java


/** 是否已"占位"——高度是否已经高于或等于目标 */


private static boolean onTargetAltitude(EntityMaid maid, LivingEntity target) {


    return maid.getY() >= target.getY() - ALTITUDE_TOLERANCE;   // 容差默认 0


}


```





新增 `ALTITUDE_TOLERANCE`（默认 **0**，严格按"高于或等于就走正常路径"）。留这个常量是为了


日后一句话就能给 1~2 格的台阶/土坡留余量——**默认值不改**，现在就是严格判据。





### 新的空中链路





`tick` 里在"猛击段"之后、阶段一之前插了一道**垂直占位**闸门：





```


目标没了 / 三件不齐  → 收尾（原样）


起跳等待（JUMP_LEFT） → 原样（3 tick）


猛击段（SMASH）       → 原样（已经收翅砸下去了，让它砸完）


── 垂直占位 ──        ← 新增


   空中 且 高度 < 目标高度 → 只做一件事：爬（tickClimbToAltitude）


   否则（高于或等于）      → 落回原链路：近战俯冲猛击 / 远战盘旋开火


阶段一（LAUNCH_LEFT） → 原样


地面支点             → 原样（水平 ≤ 20 格起跳 / 太远先走过去）


阶段二               → 原样


```





**爬升相位做四件事**（`tickClimbToAltitude`）：





1. **朝向**：朝目标水平方向 + 抬头——角度与起飞同口径（近战 62° / 远战 45°，抽成 `climbPitch()`）；


2. **烟花**：走 `canLaunch` 那套**内建 CD**（`FIREWORK_READY` 30 tick + 有烟花），


   与地面起飞、远战补烟花同一道闸门，**没有新开计时器**；


3. **保持滑翔位**：挂载型烟花只推"正在滑翔"的实体；


4. **远战额外开火**：近战爬升途中没有可打的（要贴到 3.5 格才收翅猛击），远战则不然——


   所以爬升途中照常调 `fireRanged`，不白白浪费输出窗口。





**为什么这一支要"吃掉"阶段一与等待再放**：本相位自管烟花与朝向，所以进相位就把


`LAUNCH_LEFT`（阶段一倒计时）与 `WAIT_LAUNCH`（等猛击后再放）清掉。否则她爬到目标高度


之后会先走完 30 tick 的"背离 + 向上"（那是给地面目标设计的躲闪爬升），白等半秒多才俯冲。





**起飞朝向**（`faceLaunchDirection`）也统一成同一条判据：还在目标身下 → 朝它爬；


已在目标头上（含等高）→ 老口径"背离 + 向上"。四个起飞调用点（起跳 / 阶段一 /


`jumpForLaunch` / `tryLaunch`）一个都没增删。





### 出手看方块阻隔：补上两处漏网的视线门





需求里那句"是否出手肯定还是要看有没有受到方块阻隔的"——查了一遍，**有五条出手链路，


其中三条本来就有视线门**（新锁定 `scan`、地面近战 `groundMelee`、猛击的范围补刀），


另两条没有，本轮补齐：





| 链路 | 之前 | 现在 |


|---|---|---|


| 猛击**主目标**那一记（`smashHit` → `hitOne`） | 无条件结算——隔着一层方块也能凭空打中 | 加 `hasSight`；**放在冷却检查之后、消费冷却之前**：看不见 = 这次没出手、不吃攻击冷却，`tickSmash` 会继续俯冲压着打，视线一通就打出去 |


| 远战弓弩开火（`fireRanged` 的箭矢那一路） | 不看视线（箭自己撞墙是另一回事：先扣冷却、白放一箭） | 加 `hasSight`；枪械那一路本来就有 `canSeeGunTarget`，现在两条枪口口径一致 |





这与 TLM 原生近战"出手前必过 `hasLineOfSight`"、以及本模组实测五百零五给自保链路补


视线的口径完全一致。





### 验证





- 两树编译零错（1.20.1 `EXITCODE=0`；1.21.1 `exit=0`）；


- `javap` 逐项核对（两树各自）：


  - `AIR_TARGET_ABOVE` **已无残留**；`ALTITUDE_TOLERANCE` 常量 = `0.0d`；


  - `tickClimbToAltitude` 已定义且被 `tick` **调用 1 次**，相位内 `faceUpForward` /


    `canLaunch` / `tryLaunch` / `fireRanged` 四件齐全；


  - `onTargetAltitude` 已定义、被调用 **2 次**（`tick` 的闸门 + `faceLaunchDirection`）；


  - `faceLaunchDirection` 仍被调用 **4 次**（四个起飞点，没增删）；


  - `SelfPreservationBehavior.hasSight` 调用点由 **2 → 4**（`smashHit` 主目标 + `fireRanged`


    各一处，原有 2 处未动）；


- `build_promaid.py` / `build_promaid_neo.py` 均 `verify_jar_classes: OK` + lang json OK；


- `deploy_all.py` 三端 `match=True`；


- `test_server.py 1201` 与 `test_server.py neoforge1211` 均 PASS。





### 边界（如实交代）





- **地面那一段一行没动**：还是"水平 ≤ 20 格起跳 / 太远先走过去"。垂直判据只拦**空中**那一段——


  否则她站在地上、目标在半空时会跟着往天上跳（她还没起飞，没有滑翔位，烟花推力吃不到）。


  她一离地就进爬升相位，所以观感仍是"起飞即朝目标爬"。


- **地面等高的敌人行为完全不变**（`getY()` 相等 → 判据为真 → 走原链路：起跳 → 阶段一


  "背离 + 向上" → 阶段二俯冲）；1 格台阶上的目标现在会让她起飞（差值 > 0 = 未占位）——


  这是需求的严格口径，想要余量就调 `ALTITUDE_TOLERANCE`。


- **近战爬升途中不出手**（要贴到 3.5 格才收翅猛击），只有远战会边爬边打；


- 索敌口径（50 格发现 / 128 格维持 / 新锁定要视线）**未动**——垂直占位只管"往哪飞"，


  不管"锁谁"；


- 摔落免疫仍是上一轮改成的**默认开**；落地水"接住即收"、重锤落地帧的内置 CD 也都保持；


- 未改任何配置键名、网络协议号、用户可见文案。





### 已部署待实测





1. **高目标（飞在半空/崖上）**：她应持续放烟花往上爬，高度一够就转俯冲猛击，不再原地仰头看；


2. **等高目标（平地怪）**：行为应与以前一致（起跳 → 背离爬升 → 俯冲）；


3. **1 格台阶上的怪**：按严格口径她会起飞打（差值 > 0）；若认为"为一格台阶就烧烟花"


   不值当，我可以把 `ALTITUDE_TOLERANCE` 调到 1.0~2.0；


4. **隔墙**：目标躲在方块后时不应打中（猛击主目标 / 箭矢都不会结算），


   视线一开立刻打——日志里 `[promaid/飞行撞击免疫]`、`[promaid/空袭免摔]` 不受影响。











## 实测五百二十六【空袭免疫摔落伤害：默认关 → 默认开】





### 改法





默认值一个字的位置改动，但配套的**五处文案**必须跟着改，否则面板写着"默认关"、实际默认开：





| 位置 | 改动 |


|---|---|


| `MaidSmartConfig`（两树） | `COMBAT_FLIGHT_NO_FALL_DAMAGE` 的 `define("flightNoFallDamage", false)` → `true`，config comment `（默认关）` → `（默认开）` |


| `PromaidConfigScreen`（两树） | 该行 tooltip 的 `（默认关）` → `（默认开）` |


| `MaidFlightWallGuard`（两树） | 代码注释里的"（默认关）"同步 |


| 游戏内手册「空袭与飞行作战」章（两树） | 「空袭免疫摔落伤害（§c默认关§r）」→「（§a默认开§r）」，并把"开启即彻底免摔"改写成"默认彻底免摔；关掉才按落地水/雪保护" |


| 桌面发布文案草稿 | 那句"另有默认关的…开关"→ 改成默认开并说明原因 |





### 为什么"默认开"更合理（写进注释里了）





空袭整条链路本身就是"**高空盘旋 + 收翅俯冲**"，落地缓冲（水/雪）只是**兜底**；而兜底失败的


情形都在这条链路的常态里——背包没桶、落点被占、被打断，以及 `实测五百二十五` 修掉的那类


"水还在、滑翔分支被顶掉"。她只有 20 血，这些都属于"机制没接住"，不该由玩家承担。


想按原版吃摔伤，随时可在「生存与复活 → 落地缓冲」里关掉（关掉后行为与以前完全一致）。





### 一个必须交代的坑：改默认值**不会**改动已有存档的配置文件





Forge/NeoForge 的 COMMON 配置一旦生成就会把值写进 `config/promaid-common.toml`，


之后**代码默认值不再生效**（读的是文件里的值）。所以本次顺带核对了你本机的三个配置：





```


D:\.minecraft\versions\1.20.1-Forge_47.4.21\config\promaid-common.toml      flightNoFallDamage = true


D:\.minecraft\versions\1.21.1-NeoForge_21.1.250\config\promaid-common.toml  flightNoFallDamage = true


```





**两边都已经是 true**（此前已手动打开过），所以本机不需要改配置文件、行为立刻一致；


对**新装/新档**的玩家，这次改的默认值才会体现出来。这一点写在这里免得日后被当成"改了没生效"。





### 验证





- 两树编译零错（1.20.1 `EXITCODE=0`；1.21.1 `exit=0`）；


- `javap -c` 反汇编 `MaidSmartConfig` 的 `<clinit>`，`flightNoFallDamage` 后面紧跟的


  是 **`iconst_1`**（= true）——两树都确认；对照组 `flightNoWallDamage` 同为 `iconst_1`，


  `ominous_bottle` 仍在 `List.of` 的实参里（实测五百二十五 那条没被碰掉）；


- `build_promaid.py` / `build_promaid_neo.py` 均 `verify_jar_classes: OK` + lang json OK；


- `deploy_all.py` 三端 `match=True`；


- `test_server.py 1201` 与 `test_server.py neoforge1211` 均 PASS。





### 边界





- **只改默认值与文案**：`MaidFlightWallGuard` 的拦截逻辑（`FALL` 伤害取消 + 日志


  「空袭免摔」）一行未动，`实测四百九十四` 引入的行为完全保留，只是默认值反过来。


- 免疫仍**只作用于两个空袭任务**（`MaidFlightKit.isFlightTask` 门控）——普通近战/生产任务的


  女仆照常吃摔落伤害。


- 免疫的是**摔落伤害**（`fall`），不是"免疫所有伤害"：被怪打照样掉血、照样可能被击落，


  与 `实测四百九十四` 的口径一致。


- 未改任何配置项键名、网络协议号；`config.promaid.combat.flightNoFallDamage` 的翻译键与


  中英文名都没动（面板行的标题仍是「空袭免疫摔落伤害」）。











## 实测五百二十五【排班表分类说明补齐 + 空袭"敌人更高"方案 + 落地水不再挡飞行 + 不祥之瓶默认不喂】





## 一、排班表挪了分类，两处"文字说明"漏改（两树）





`实测五百一十八` 把排班表从「移动与行为」挪到「生产与工作」，但那次**只改了 `Section`


枚举的归属**，两处写给玩家看的说明漏改：





| 位置 | 旧文（错的） | 现在 |


|---|---|---|


| 配置面板大类页一句话（`groupHint`）· 生产与工作 | `建造 / 挖矿 / 伐木 / 烧制酿造 / 农场宰杀` | 末尾补 ` / 排班表` |


| 同上 · 移动与行为 | `跟随 / 空闲流畅 / 排班表 / 搭路` | 排班表已不在这个大类 → 去掉 |


| 手册「功能总览 · 一键跳转配置」① 生产与工作 | 只有建造/挖矿/伐木/烹饪与酿造/农场与宰杀五个链接 | 补 `[[@WORK:SCHEDULE|排班表]]` 链接 |


| 同上 ⑤ 移动与行为 | 还挂着 `[[@WORK:SCHEDULE|排班表]]`（跳得到，但列错了大类） | 去掉（已在 ① 列出） |





判据就是 `Section` 枚举里的实际归属（`SCHEDULE("排班表", Group.WORK)`），不再靠人工记忆。


手册那一章是「一键跳转」的性质，现在 ①②③④⑤⑥⑦ 七行与配置面板首页**逐项对齐**。





## 二、空袭面对"比自己高的敌人"没有方案（两树）





两个原因叠在一起，都在 `MaidFlightCombatBehavior`：





**① 起飞门槛用的是 3D 距离——她根本进不了起飞分支。**





```java


double dist = maid.distanceTo(target);           // 旧：3D 距离


if (maid.onGround()) {


    if (dist <= LAUNCH_RANGE && canLaunch(...)) { jumpForLaunch(...); }   // LAUNCH_RANGE = 20


    ...


}


```





敌人比她高 50 格时，**光是垂直分量就 ≥50 > 20**：她就算站在敌人正下方也永远进不了这个


分支——不跳、不放烟花，只会原地仰头看（反馈原文："只会对着比自己高 50 格的敌人看着"）。





现在改判**水平距离**：





```java


double dxh = maid.getX() - target.getX();


double dzh = maid.getZ() - target.getZ();


double distH = Math.sqrt(dxh * dxh + dzh * dzh);


if (distH <= LAUNCH_RANGE && canLaunch(...)) { jumpForLaunch(...); }


```





`LAUNCH_RANGE` 那句话的原意（常量注释）本就是"太远先跑过去，避免越炸越远"——这是个


**水平**概念；空袭是立体作战，垂直方向不该设限。爬升量由起飞仰角（近战 62°/远战 45°）承担。





**② 起飞朝向是给地面敌人设计的——目标是空中目标时会越飞越远。**





原口径是"背离敌人 + 抬头 62°"，目的是爬升时不站在对方的近战/爆炸包线里。可目标在头顶


50 格时她本来就不在对方包线内，"背离"只会让她**越飞越远再掉头飞回来**，全程烧烟花。





新增 `faceLaunchDirection(maid, target)`（起飞阶段四处调用点统一走它）：





```java


if (target.getY() - maid.getY() > AIR_TARGET_ABOVE) {   // 8 格


    faceUpForward(maid, target, -62°);   // 空中目标：朝它爬（复用远战补烟花那套）


} else {


    faceAwayAndUp(maid, target);         // 地面目标：保持原口径，一行没动


}


```





门槛取 8 格的理由：对方在 8 格以上高度时她的近战包线本来就够不着，"躲开"没有收益；


而地面怪站在 1~7 格台阶/土坡上时仍走旧口径，避免刚起飞就贴脸。





## 三、落地水会阻拦女仆飞行（两树；1.21.1 重锤落地帧另补 CD）





### 真根因不是"缺 CD"





水是**流体**。`LivingEntity.travel` 的第一个分支就是


`isInWater() → travelInWater(...)`——只要她还在水里，**滑翔分支整个被顶掉**：





- 烟花推力被水阻尼几乎吃光（`FireworkRocketEntity` 只是往速度里加，真正结算的是 `travel`）；


- 而且在水里 `onGround()` 恒为 false → 空袭"落脚再起飞"的支点判定也进不去。





所以她站在自己刚放的那格水里"看着敌人不动"。**细雪不是流体、没有这一层**，所以过去


只有落地水挡飞行——这正是"落地雪没事、落地水有事"的来源。





`实测五百一十二` 已经给空袭分支加过 2 秒内置 CD（`FLIGHT_CLUTCH_CD = 40`，这次核对


**两树都在、值没变**）。但 CD 拦的是"不要连着再放一次"，拦不住"这一格水还在那儿"。





### 改法：这类缓冲只负责"接住这一下"





新增 `flightPlaced` 标记（`placeFluid` 增加一个带来源的重载），空袭/重锤落地帧置位；


主循环里对这类**水**加一条"接住了就收"：





```java


if (this.flightPlaced && !this.placedSnow


        && maid.getDeltaMovement().y >= 0.0) {   // 不再下坠 = 已落进水里被阻尼停住 / 已踩到地


    this.recoverFluid(level, maid);


    return;


}


```





- 只用**竖直速度 ≥ 0** 做判据，不用 `onGround()`——因为在水里 `onGround()` 恰恰是 false，


  用它就永远收不掉（这正是上面那条连锁的机关）；


- **细雪不走这一支**（它不是流体，不挡滑翔分支）；


- **通用（高塔坠落）那条路不置位**：它的保持时长仍由配置 `waterHold` 决定，


  "收水后再放一次接住极高塔"的既有语义完全没动。





### 按要求：1.21.1 重锤落地帧也吃同一个 2 秒闸门





`maceLanding` 分支原先没有任何冷却（只靠 `FORCED_CLUTCH` 一次性消费）。重锤 3 秒一发、


落地水只保持 5 tick 时，"跃起 → 落地 → 立刻再跃起"的连击会让水连着放、每次都要从水里


重新起飞。现在这一帧同样过 `gameTime >= flightClutchCd` 并写回 `+FLIGHT_CLUTCH_CD`，


与空袭分支同口径（1.20.1 树没有重锤落地帧，无此改动）。





## 四、不祥之瓶默认不能喂（两树）





1.21 的**不祥之瓶**带食物组件、能"喝"，于是被投喂的通用判定（"有食物属性且不在黑名单"


= 能喂）当成普通食物。喂下去等于顺手给女仆挂**不祥之兆**（袭击/试炼的触发条件）——


玩家喂她是为了回饱食度，不该捎带这个。已加入 `aidFoodBlacklist` 默认值：





```java


"minecraft:enchanted_golden_apple",


"minecraft:ominous_bottle"), o -> o instanceof String s && !s.isEmpty());


```





1.20.1 注册表里没有这个物品，那张表在那边是空操作——**两端默认值保持一致**（配置面板的


勾选页本来就只列注册表里真实存在的食物，多一个未知 id 不会显示、也不影响计数）。


手册「贴身辅助」章那句"默认黑名单…"同步写明不祥之瓶与原因。





## 验证





- 两树编译零错（1.20.1 `EXITCODE=0`；1.21.1 `exit=0`）；


- `javap` 逐项核对：


  - `AIR_TARGET_ABOVE` = `double 8.0d`、`LAUNCH_RANGE` = `double 20.0d`（两树）；


  - **`faceLaunchDirection` 的调用点恰好 4 处**（起跳分支 / 阶段一 / `jumpForLaunch` /


    `tryLaunch`）、**`faceAwayAndUp` 只剩 2 处**（`faceLaunchDirection` 内部 + 远程近身弹开）


    ——即起飞阶段已全部改走新朝向；


  - `tick` 里出现新的 `Math.sqrt`（水平距离）且两个 `jumpForLaunch` 都在它之后；


  - `WaterClutchBehavior`：`FLIGHT_CLUTCH_CD` = `long 40l`、


    `placeFluid(...,boolean,boolean)` 重载存在、`flightPlaced` 读写齐全、竖直速度判据在位；


  - `MaidSmartConfig`：`ominous_bottle` 两端都在；


  - **自查（又一次踩同一个坑）**：第一版校验脚本把 `LAUNCH_RANGE` 判成"0 次比较"、


    重载方法体切不出来——原因是 `static final double` 常量被 javac **内联**（字段名在方法体里


    消失，这正是 实测五百二十一 记录过的坑），以及我的方法体切片正则失手。改用


    `javap -c` 原样 grep 调用点后结论如上表，**是脚本错、不是代码错**；


- `build_promaid.py` / `build_promaid_neo.py` 均 `verify_jar_classes: OK` + lang json OK；


- `deploy_all.py` 三端 `match=True`；


- `test_server.py 1201` PASS（Done 2.614s）、`test_server.py neoforge1211` PASS（Done 0.852s）。





## 边界（如实交代）





- 飞行链路的**数值**一个没动：起飞仰角（近战 62° / 远战 45°）、阶段一时长（30 / 20 tick）、


  收翅距离 3.5、盘旋半径 10、盘旋高度 3.5、弹开 3 格 / 1.5 秒、重锤下落加成算法全部原样；


  本轮只改了"进不进得了起飞分支"（水平距离）与"往哪个方向爬"（有高空目标时朝它爬）。


- `实测五百一十二` 的 2 秒 CD **没有被删除或改值**——落地水的"挡飞行"不是它没生效，


  是它管不到"水还在"；两者是互补的（CD 防连放、接住即收防挡飞）。


- 通用落地水的"收水后再放一次"语义、细雪的全部语义（含追补雪垫）**一行未动**。


- **没有改**投喂判定的通用口径（"有食物属性且不在黑名单 = 能喂"），只调默认黑名单一项；


  玩家仍可在「投喂食物勾选」页里把不祥之瓶重新勾成"能喂"。


- 未改任何网络协议号、配置项键名、用户可见文案的既有措辞（新增的说明文字除外）。





## 已部署待实测





1. 配置面板首页进「生产与工作」，上方的说明应含"排班表"；进「移动与行为」应不含；


   手册「功能总览」① 里的"排班表"链接能跳到排班表小类；


2. **空中敌人**：找一个高处的敌人（或凋灵/幻翼），她应能起飞并**朝它爬**，而不是原地仰头看；


   地面敌人（1~7 格高差）行为与以前一致；


3. **落地水**：空袭/重锤落地后她应能立刻重新起飞——水在她落地停稳的那一刻就被收回，


   不再有"泡在自己放的水里不动"；


4. 往女仆背包塞不祥之瓶：饿到阈值以下她**不应**喂它（顶多去拿别的食物）。











## 实测五百二十四【空袭"飞太高就丢目标、飘走"根治：索敌三处修正（16 格上限 / 起飞丢锁 / 停止清瞄准）】





### 先用实测日志定位，不靠猜





日志就在本机，直接读现场（1.21.1 客户端 `logs/promaid.log`，2026-09-16 22:19~22:20，


打的是监守者）：





```


22:19:32 [空袭索敌] task=flight_combat 候选=1 → 存活=0 → 可攻击=0 → … → 选中=-


22:20:25 [空袭索敌] cands=4 → 存活=3 → 可攻击=1 → 活动范围内=1 → 有视线=1 → 选中=minecraft:warden@7.1


22:20:25 [飞行作战] 放烟花起飞          ← 锁上了，起飞


22:20:27 [空袭索敌] 候选=4 → 存活=3 → 可攻击=1 → 活动范围内=1 → 有视线=0 → 选中=-   ← 2 秒后目标没了


22:20:25~22:20:39 [重锤门] 主手=minecraft:mace 目标=无 地面=false                          ← 悬在空中、没有目标


```





三条硬结论：**候选都在（有敌人）、可攻击也在，却锁不上**；**起飞 2 秒后就丢锁**；


**长时间"在空中 + 无目标"**——没有目标 = 行为停止 = 没有任何朝向指令，于是顺惯性飘走。


这就是"飞得特别高就失去方向"。





### 三个真根因（逐个字节码实证）





**① 视线门误用了 `Sensor.isEntityAttackable`——它内含 16 格上限，我们的"50 格索敌"根本没生效。**





这是本轮最关键的一条。旧版 `FlightTargeting.scan` 里那一关写的是


`Sensor.isEntityAttackable(maid, e)`，注释还写着"与 TLM 原版逐环一致"。反编译实证：





1.20.1（CFR，`Sensor.m_26803_`）：





```java


f_26793_ = TargetingConditions.m_148353_().m_26883_(16.0);                       // forNonCombat().range(16.0)


f_26794_ = TargetingConditions.m_148353_().m_26883_(16.0).m_26893_();            // … + ignoreLineOfSight


m_26803_(a, b) { return brain 已把 b 记为 ATTACK_TARGET ? f_26794_.test(a,b) : f_26793_.test(a,b); }


```





1.21.1（`javap` 客户端 jar，`Sensor.isEntityAttackable`）：





```


static { TARGET_CONDITIONS = TargetingConditions.forCombat().range(16.0);


         ATTACK_TARGET_CONDITIONS_IGNORE_INVISIBILITY_TESTING =


                 forCombat().range(16.0).ignoreInvisibilityTesting(); }


isEntityAttackable(a, b) { return brain.isMemoryValue(ATTACK_TARGET, b)


                                    ? …IGNORE_INVISIBILITY_TESTING.test(a, b)


                                    : TARGET_CONDITIONS.test(a, b); }


```





而 `TargetingConditions.test` 里就是那句


`d = a.distanceToSqr(b); if (d > (range * visibility)²) return false;`。





**两个变体都带 `range(16.0)`。** 也就是说这一环真正卡的是"**16 格以内**"，视线只是顺带。


后果：我们扫的是 50 格球（日志 `盒尺寸=100.6/101.5/100.6`，候选确实捞进来了），却**在 16 格这一关


被全数否决**（日志 `有视线=0 → 选中=-`）。她一旦被起飞弧线推到 16 格外，**再也拿不回目标**。





`实测五百二十一` 之所以"反而更严重"，根因就在这里：旧版（521 之前）根本没有"按距离丢锁"这回事


（它直接读 brain，TLM 什么时候擦才什么时候丢），所以锁能一直挂到打完；521 引入了"距离超限就丢锁"，


而她一旦丢锁，**重新索敌又被这个 16 格上限掐死**——于是从"偶尔丢"变成"丢了就再也回不来"。





**② 维持锁定与新锁定共用 50 格上限——起飞那口气就把她推出 50 格。**





起飞动作是**主动背离敌人 + 抬头 62°** 去吃烟花推力（`faceAwayAndUp` + `LAUNCH_CLIMB_TAN_MELEE=1.88`


+ `LAUNCH_TICKS_MELEE=30`），实测日志正好是这条线的证据：**锁定时距离 7.1 格，放烟花起飞 2 秒后


目标就没了**。旧口径下每一轮起飞都在半路自我解除瞄准，行为随即停止、状态被整清，她在高空


保持滑翔却没有任何朝向指令。





**③ 行为一停止就用 `forget()` 整清，把"瞄准"和"烟花冷却"一起清掉了。**





空袭一轮里行为本来就会短暂停（三件不齐那两拍、`canStillUse` 与 `tick` 的边界），而这些地方


旧版都走 `forget()`。它连带清掉：





- `FlightTargeting` 的锁定 → 她得重新过"限频扫描 + 视线门"，而此刻她多半在高空（① 的 16 格上限


  在这里再次致命）→ **有敌人却拿不到目标**，正是日志里那片 `选中=-` 的状态；


- `FIREWORK_READY` 烟花冷却 → 停一下就能重开一次，实测日志里出现了**同一秒两次"放烟花起飞"**。





### 改法（两树同步）





| 位置 | 改动 |


|---|---|


| `FlightTargeting.scan` | 视线门改用本模组自有的 **`SelfPreservationBehavior.hasSight`**（纯 raycast：`ClipContext.Block.COLLIDER`、中心点上抬 1.2 格、**没有距离上限**）。"隔墙不出手"照旧成立，但"能不能发现"重新交回用户指定的 50 格球。与实测五百零五给自保近战补视线用的是同一个方法，口径统一 |


| `FlightTargeting` 常数 | 拆成两个：`RANGE = 50.0`（**只管发现**，新锁定）+ 新增 `HOLD_RANGE = 128.0`（**维持锁定**）。起飞弧线最坏约 60 格，128 有约两倍余量，同时仍能防住"追着跑出战场" |


| `FlightTargeting.resolve` | 维持/接管锁定这两条分支顺手把扫描冷却推到"下个 tick 即可扫"——限频只为"没敌人时别空扫"，不该在交战中拖延重新锁定 |


| 新增 `FlightTargeting.pause(uuid)` | **轻量清理：保留锁定**，只清每 tick 的缓存。给"行为中途停止"用 |


| 新增 `MaidFlightCombatBehavior.pauseRound(uuid)` | 停止时只清"本轮动作"（`LAUNCH/JUMP/SMASH/WAIT/RANGED_*/ATTACK_READY/MAX_Y`），**刻意保留 `FIREWORK_READY` 与索敌锁定** |


| `endFlightSafely` | `forget(id)` → **`pauseRound(id)`**；硬清仍走 `forget`（女仆移除、清空换任务） |





净效果：**打起来之后，瞄准不会再因为"飞远了 / 视线被挡 / 行为停一拍"而断**——她会一直朝那个方向


飞回去；空中重启也直接进阶段二扑击，不会又补一轮背离爬升。





### 顺带修掉诊断日志的误导标签





`FlightTargetProbe`（实测五百一十五 加的一次性排查工具，本轮正是靠它锁定根因）原来把


`Sensor.isEntityAttackable` 的通过数标成 **"有视线"**——可那个方法含 16 格上限，


"有视线=0"常常根本不是视线问题、而是目标在 16 格外，排查时被它带偏过。现在：





- 逐环计数**改照我方索敌链**：可攻击 → 活动范围内 → **50 格内** → **我方有视线**，


  末端的 `我方选中` 就等于 `scan` 真正会锁的那个；


- 原版那一关单独列出并**标明含 16 格上限**。





新格式：





```


[空袭索敌] maid=… task=… activity=work 盒尺寸=100.6/101.5/100.6 候选=4 → 存活=3 → 可攻击=1


  → 活动范围内=1 → 50格内=1 → 我方有视线=1 → 我方选中=minecraft:warden@7.1（原版可判(含16格上限)=1）


```





### 验证





- 两树编译零错（1.20.1 `EXITCODE=0`；1.21.1 `exit=0`）；


  中途我自己出过一次错：给 neo 的 `pauseRound` 抄了 1.20.1 才有的 `GROUND_READY` 字段，


  javac 报"找不到符号"，删掉后重编通过——如实记录；


- `javap` 逐项核对（两树各自）：`RANGE` 常量 = `50.0d`、`HOLD_RANGE` = `128.0d`、


  **`keepable` 体内是 `16384.0`（=128²）而不再是 `2500.0`（=50²）**、


  `scan` 调 `hasSight` 且**不再出现 `isEntityAttackable`/`m_26803_`**、


  `pause` 存在且**不碰 `LOCKED`**、`endFlightSafely` 调 `pauseRound` 而非 `forget`、


  `pauseRound` 内**不出现 `FIREWORK_READY`**；


- 1.21.1 侧那条"16 格上限"是用**客户端 jar 的 `javap` 字节码**确认的，不是从 1.20.1 推断；


- `build_promaid.py` / `build_promaid_neo.py` 均 `verify_jar_classes: OK` + lang json OK；


- `deploy_all.py` 三端 `match=True`；


- `test_server.py 1201` PASS（Done 2.658s）、`test_server.py neoforge1211` PASS（Done 0.863s）。





### 边界（如实交代）





- **只改了索敌与收尾口径**，没动飞行链路本身：起飞仰角/时长（近战 62°×30t）、收翅距离 3.5 格、


  盘旋半径 10 格、盘旋高度 3.5 格、弹开 3 格/1.5 秒、重锤下落加成的算法**全部原样**。


  她起飞还是会爬很高——那是"吃满烟花推力"的设计，不是 bug；本轮修的是"爬高之后丢掉瞄准"。


- `HOLD_RANGE=128` 是**有界**的：目标跑出 128 格、或死了/打不着/越出活动范围，仍然会正常收尾。


- 未改任何用户可见文案、配置项与网络协议号。





### 已部署待实测





1. 近战空袭（1.20.1 暴击 / 1.21.1 重锤）能否**完整走完一轮**：起飞 → 朝敌飞 → 收翅猛击 → 再起飞；


2. 飞得再高也不丢目标，不再"飘出战场"；落地后若目标还在，是否立刻重新起飞；


3. 隔墙的怪**仍然不会被新锁定**（这条要专门验一下，是本轮唯一"放宽"的地方的对立面）；


4. 日志里 `我方有视线` 与 `原版可判(含16格上限)` 两列的差别，能直观看出旧口径卡在哪一环。











## 实测五百二十三【版本号统一为 1.2.0（两端一致）】





### 改法





把**未发布批次**的版本号统一为 **1.2.0**，1.20.1 Forge 与 1.21.1 NeoForge **两端一致**。





落地范围（共 **657 处**）：





| 位置 | 改动 |


|---|---|


| `promaid_src/META-INF/mods.toml` | `1.1.1` → `1.2.0` |


| `promaid_src_neo/META-INF/neoforge.mods.toml` | `1.1.0` → `1.2.0`（实际被打包的那个） |


| `promaid_src_neo/META-INF/mods.toml` | 同步（该文件不参与打包，仅避免日后混淆） |


| 构建脚本 | jar 名 → `promaid-1.2.0.jar` / `promaid-1.2.0-neoforge-1.21.1.jar` |


| 部署 / 测试 / 校验脚本 | 源与目标路径改名 |


| `deploy_all.py` | 新增：部署时**自动删除目标目录里旧的 `promaid-1.1*.jar`**——jar 改名后旧包若残留，同 modId 共存会导致启动失败 |


| 游戏内手册 | 玩家可见版本标注（"v1.2.0 起新增功能速览"等） |


| 内置变更日志 | 两树 `changelog.txt` |


| README | 两平台版本标注 |





### 顺带归一的历史口径





这批未发布改动原先在源码注释里**混用了 `v1.1.3` 与 `v1.1.4`**（同一批功能前后写下的两个号），


另有一处 `v1.1.2`——现在**全部归一为 `v1.2.0`**。





**安全性依据**：远端 tag 当时只到 `v1.1.1`（`git ls-remote` 确认），所以 `v1.1.2/3/4` 从未发布，


归一不影响任何已发布记录；已发布标号（`v1.0.x` / `v1.1.0` / `v1.1.1`）**原样保留、未触碰**。





### 我自己踩的坑（如实记录）





批量替换是用脚本做的，中途把两树 `changelog.txt` 写成了**双 BOM**——读用 `utf-8-sig`


（吃掉 BOM）、写又用 `utf-8-sig`（再补一个），文件头变成 `EF BB BF EF BB BF`，


游戏里首行会显示成一个方框。已修正为单 BOM 并复核（首行仍是【版本号说明】、第 2 行仍是最新条目）。





### 验证





两树编译零错；jar 内 `version=1.2.0`（两平台）、手册含 `v1.2.0` 且无 `v1.1.2/3/4` 残留、


内置日志单 BOM 且首行正常、`pack_format` 分别 15 / 34；


`deploy_all.py` 三端 `match=True`，且两个客户端目录里的旧 `promaid-1.1.x` 包已确认清除


（只剩 1.2.0）；`test_server.py 1201` 与 `neoforge1211` 均 PASS。





### 升级提示





两个 jar 的 `modId` 相同，**安装前请删除 mods 目录里的旧 `promaid-1.1.x` 包**，


否则同时加载会启动失败。





## 实测五百二十二【手册「详细介绍」全面重写：补上空袭专章 + 补齐缺失功能 + 修失效跳转】





### 先做体检：手册到底漏了什么





没有凭印象改，先把两边的**实际内容**都盘出来对账：





- 手册侧：`GuideContent` 共 30 章、1108 行（两树内容基本同源），逐章读完；


- 模组侧：`MaidSmartConfig` 共 **326 个配置项 / 12 个配置组**，配置面板另有独立的


  **7 大类 / 28 小类**层级——这才是"模组实际有什么"的权威清单。





然后拿功能关键词去手册里检索，得到硬结论：**手册完全没提的功能有一整片**，


其中最大的一块是——





### 最大的缺口：整个「空袭 / 飞行作战」系统手册只字未提





全篇检索「飞行作战 / 空袭 / 鞘翅 / 烟花火箭」= **0 次**。





而这是模组里体量最大的一套系统：两树合计 4 个任务类 + 1 个行为类（1300+ 行）


+ 装备/弹药/姿势 3 个工具类 + 若干 mixin；配置项还**散在三个小类里**


（总开关在「主动参战」/ 1.21.1 在「单兵战术」，落地保护三项在「落地缓冲」）。


玩家切到这两个任务后，完全不知道三件套是什么、为什么不起飞、为什么她贴地不飞。





**新增整章「空袭与飞行作战（会飞的女仆）」**（约 5 页，与最长的既有章节持平），


七节写全：两个任务的区别 / 三件套（鞘翅+武器+烟花，远程空袭另需弹药）/


保姆式首飞举例 / 近战空袭完整链路 / 远程空袭完整链路 / 索敌机制 / 开关位置与常见问题。





口径全部照当前字节码与配置 comment，不凭印象：


- 激活判据 = `MaidFlightKit.isModeActive/hasElytra/hasWeapon/hasFirework`（远程另需 `hasAmmoForRanged`）；


- 链路常量实测值：收翅距离 3.5 格、范围补刀 2.5 格、盘旋半径 10 格、


  盘旋高度目标上方 3.5 格、开火距离 24 格、近身弹开 3 格 / 1.5 秒、地面重新起飞限 20 格；


- 两树差异**分开写**：1.20.1 无重锤 → 收翅下落按原版暴击 ×1.5；


  1.21.1 手持重锤 → 吃**下落加成 + 风爆**，且按「本轮最高点」结算（`forcedFallDistance` 后门）。





特别把 521 那次索敌重写的**用户可感知结论**写进手册：索敌是以自身为圆心、


半径 50 格的球，且**一旦锁定就持续瞄准**（不再飞高丢目标、飘出战场）。





### 顺手修掉一个真实的失效链接





体检时发现：`MOVE:SCHEDULE` 这个跳转目标**不存在**——排班表在实测五百一十八


已从「移动与行为」挪到「生产与工作」，但手册里两处引用漏改：


① 功能总览章的 `[[@MOVE:SCHEDULE|排班表]]`；② `CFG_LINK_RULES` 里的同名规则。


点了跳不过去。现在两处都改为 `WORK:SCHEDULE`，并给新的空袭章补了两条跳转规则。





### 其余补齐（手册原来一个字都没有的功能）





- **投喂食物勾选**（实测五百一十九/五百二十的新功能）：写清"现在认模组食物


  （带食物属性就算能喂）、勾选面板在哪、点图标切换能不能吃、默认黑名单有哪些"，


  附"让女仆喂三明治"的保姆式举例；


- **AI 记忆**补「人格种子」（人设与聊天记忆分离）与「双 agent 提取」（摘要/事实分两次调用）；


- **农场与杂项**补：蛋糕可食用、悬空禁搭方块、手持光源发实光、TNT 点火保护期、红石机器专属搭建；


- **伐木**补「树冠清理」、**挖矿**补「穿透预算」；


- 「模组详细配置面板导航」补空袭相关的分类指引（说明它为什么横跨两个小类）；


- 「这是什么？」补 v1.2.0 功能速览（空袭 / 投喂认模组食物 / 三叉戟按任务分类 / 排班表分类调整）。





### 还修了一处会误导读者的编号引用





章内多处写「（见第 3 章）」指"配置 LLM"，但按实际章节顺序它是**第 4 章**


（序言与功能总览各占一位）——读者按图索骥会找错。统一改成按章节名指路


（「准备篇：配置 LLM」），不再依赖会漂移的编号。





### 验证





两树编译零错；内容校验全绿——**章节总数 31**（原 30 + 新增空袭章，


`javap` 确认字节码里 `chapters()` 调用数也是 31）、


**全部 30 个跳转链接与 CFG_LINK_RULES 目标都是配置面板里的合法目标**（不再有 `MOVE:SCHEDULE`）、


本次新增/补齐的关键词全部出现在正文；


排版估算：新章约 5 页（与最长既有章持平）、全手册约 70 页，无异常巨章；


打包 → `deploy_all.py` 三端 `match=True`；`test_server.py 1201` 与 `neoforge1211` 均 PASS。





### 边界





只改了 `GuideContent`（手册正文）与其跳转规则。**没动**任何功能代码、


**没动**配置项本身、**没动**用户可见的语义文本（AI 记忆角色标签、对话翻译提示等保持原样）。


两树内容保持同步，差异只在预期处（版本号措辞、重锤 vs 暴击、空袭总开关所在小类、


`DataComponents.FOOD` vs `FoodProperties`）。





## 实测五百二十一【空袭索敌改为"以自身为圆心、半径 50 格的球"，每 tick 持续瞄准】





### 根因（反编译实证，不是推测）





TLM 的索敌是"传感器扫盒子 → 写可见列表 → 任务从列表里选"三段，


而**扫描盒的大小是按排班活动分流的**：





```


EntityMaid.searchDimension() {


    if (getScheduleDetail() == Activity.WORK) return getTask().searchDimension(this);


    return TaskManager.getIdleTask().searchDimension(this);   // ← 非 WORK 一律走这里


}


```





我们两个空袭任务覆写的 50 格盒子**只在活动恰为 WORK 时参与**；其余时段走 idle 任务的默认口径


（`IMaidTask.searchDimension` 默认 = `inflate(searchRadius, 4.0, searchRadius)`）——


**垂直半径只有 4 格**。空袭是立体作战，她一爬高十几格，敌人就直接掉出那个 4 格高的盒子。





目标掉出可见列表之后：`StartAttacking` 再也选不到它（它的目标来源就是那个列表）；


而空袭行为的 canStillUse 又要求"当前有目标"，目标一丢行为立刻停止、状态被 `forget()` 整清——


她在高空保持滑翔却**没有任何朝向指令**，于是顺着惯性飘出战场。这就是"飞太高 → 丢目标 → 往别处飞"。





### 改法：空袭索敌从 TLM 链路上整个摘出来





新增 `com.maidsmart.combat.FlightTargeting`（两树同源），每 tick 自己维持三件事：





1. **自己扫**以自身碰撞箱为中心、半径 **50 格的球**——AABB 膨胀 50 格做粗筛、距离平方


   （`2500.0`）精筛成球。**完全不看排班活动**，所以她爬多高都算。


2. 目标选定后**每 tick 回写** `ATTACK_TARGET` + `LOOK_TARGET`。TLM 那边什么时候擦、


   按什么判据擦都不再影响空袭：下一 tick 就补回来。





   **还有第二条丢目标的通路，一并被这一条堵住**：TLM 注册在空袭任务 brain 里的


   `StopAttackingIfTargetInvalid` 除了"目标死了/换了维度/不可攻击"之外，还有一条


   **200 tick「寻路到不了就放弃」**的擦除判据——反编译实证它读的是


   `CANT_REACH_WALK_TARGET_SINCE`（1.20.1 `f_26326_`，官方 mappings 名 =


   `cant_reach_walk_target_since`），而那个记忆由 `MoveToTargetSink` 在"路径算不出来"


   时写入。她在高空盘旋时寻路本来就经常算不到地面目标，于是这条判据会把目标判成


   "追不到"而擦掉。现在因为每 tick 都回写，擦掉与否对空袭都不再有影响。


3. 已锁定的目标只要"还活着 + 同维度 + 能攻击（`canAttack`）+ 在活动范围内


   （`isWithinRestriction`）+ 仍在 50 格内"就**继续用它，不再要求视线**。


   这一条正是"持续瞄准"的实质——她飞高时与地面敌人之间的视线经常被地形/树叶/怪物身体挡住，


   旧口径下这会让她把目标判成"不可见"而放弃；现在视线只作为**新锁定**的门槛。





**与 TLM "隔墙不出手"没有任何冲突**：新锁定仍然要过 `Sensor.isEntityAttackable`


（内含 `TargetingConditions` → `Sensing.hasLineOfSight`，与 TLM 原版逐环一致），


墙后的怪不会被新锁定；改变的只是"已经打起来的目标不会因为她飞高、视线被挡一下就被丢掉"。





### 实现细节（两处必须交代）





- **同 tick 去重**：空袭行为同一 tick 会调三次索敌（`checkExtraStartConditions` /


  `canStillUse` / `tick`）。加了 `RESOLVED_TICK` 表按 gameTime 去重，保证三次给出**同一个答案**——


  否则会出现"启动判定看到一个目标、运行中维持的是另一个"的分裂，同时省掉两遍重复扫描与 brain 写入。


- **扫描限频**：行为未运行时 Brain 每 tick 仍会评估 `checkExtraStartConditions`。


  若不加限制，没有敌人的空袭女仆会每 tick 扫一次 50 格 AABB 盒。加了 `SCAN_INTERVAL = 10`


  （0.5 秒），只在"没有锁定"时生效；一旦锁定成功，维持分支短路，扫描根本不参与。





状态表用 `WeakReference` 存目标、并由 `MaidFlightCombatBehavior.forget/clearAll` 在


行为收尾、女仆移除、服务器停止时清空（与既有的状态表同一套回收路径）。





### 验证





两树编译零错；字节码核对（javap）：`FlightTargeting` 成员/字段齐全、


`RANGE` 常量值 = `50.0`、`RANGE_SQR` = `2500.0`、


**视线门只在 `scan`（新锁定）里、不在 `keepable`（维持锁定）里**、


`keepable` 含活动范围与 50 格距离判据、空袭行为已调 `FlightTargeting.resolve/forget/clearAll`


且**不再直读** `ATTACK_TARGET`/`f_26372_`（调用数 0）；


打包 → `deploy_all.py` 三端 `match=True`；`test_server.py 1201` 与 `neoforge1211` 均 PASS。





### 一点自查





我这轮校验脚本自己写错了三次判据（double 的字节序、1.20.1 视线门方法名多打了个下划线、


编译期常量被内联后不该再找字段名），一度报出 4 条假 FAIL。三处都核对过字节码原样后才改判据，


最终 `ALL PASS`——**是脚本错，不是代码错**，记在这里免得下次再踩。





v1.2.0 投喂食物改为「勾选面板」（像矿表那样点图标切换能不能吃）（两树同步）：①【反馈】"那如何进行配置呢？我预想的是就跟设置挖矿种类的面板这种一样，先将所有含标签的食物列出来。然后通过勾选的方式确认能不能吃"②【改法】原来那个逗号分隔输入框换成与矿物名单/替代品名单完全同款的勾选子页。入口在「战斗与自保 → 贴身辅助 → 投喂食物勾选」（按钮显示"可喂 X / 不能吃 Y"）。网格里列出全部带食物属性的物品——包括所有模组食物（三明治等），数据源是新建的 ensureFoodCache()：遍历全部注册物品取 getFoodProperties() != null（1.21.1 是 DataComponents.FOOD）的，不套黑名单（黑名单就是本页勾选的结果）。点图标切换：绿框+✔ = 能喂；红框+✖ = 不能吃；悬停右侧显示中文名/英文 id + "当前：能吃（点击改成不能吃）"。搜索框（中英文皆可）、翻页、下方"不能吃"名单（每行带「允许吃」按钮可一键恢复）、输入框（直接输入注册名列为不能吃，回车生效）——全部与矿表子页同交互；底部提示实时显示"共 X 种食物，当前可喂 Y 种"。③【语义】打勾 = 女仆可以喂（默认全勾），取消勾选 = 列为「不能吃」= 写进 aidFoodBlacklist，与反馈的直觉一致——"打勾的就能吃"。配置项本身仍是同一个 aidFoodBlacklist，只是编辑界面从文本框换成了勾选面板，之前设的值照旧生效。④【一个必须交代的坑·我自己踩了】移植到 1.21.1 时直接复制了 1.20.1 的列表类，neo 侧编译报了一串「需要分号」。根因是 ObjectSelectionList 的构造签名两树不同：1.20.1 是 6 参（top, bottom, itemHeight），1.21.1 是 5 参（top 与 height 合并）+ 没有 setRenderBackground/setRenderTopAndBottom、滚动条覆盖方法是 renderItem 不是 renderScrollbar；另外注册表取值 1.21.1 是 BuiltInRegistries.ITEM.get(...)（无 getValue）。全部照 neo 既有的 AltList 写法改齐后编译通过——这是我引入的错误，已修。⑤【验证】两树编译零错；字节码核对 foodTable 字段、ensureFoodCache、isFoodChecked、toggleFoodChecked、countFeedableFoods、AID_FOOD_BLACKLIST 引用、食物属性读取、$FoodList 内部类——两树全部 YES 且一致；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 投喂判定：21 项原版白名单 → 通用判定 + 可配置黑名单（两树同步）：①【反馈】"女仆的喂食功能可以喂其他mod的食物吗？检测背包中是否有能够喂食的食物的时候有没有跳过模组食物？这个提醒是只判定原版食物吗，往女仆背包里塞一堆三明治疯狂跳没食物"②【根因】EmotionalActionExecutor 里有个 Set<ItemStack> FOODS，内容是手工列出的 21 个原版物品（熟牛排/猪排/羊肉/鲑鱼/鸡肉/兔肉/鳕鱼、兔肉煲/甜菜汤/蘑菇煲/南瓜派、金胡萝卜/烤马铃薯/面包/胡萝卜/苹果/西瓜片/甜浆果/发光浆果/曲奇/海带干 + 蜂蜜瓶），所有投喂判定都是"遍历该集合按物品身份比对"——任何模组食物（三明治等）都不在内 → 背包塞满也判"没有食物" → 疯狂播报"我背包里没有吃的了"。而 TLM 自己"女仆饿了吃"那条链（DefaultMaidHealSelfMeal.isHealMeal，javap 实证）判据是"有 FoodProperties && 不在黑名单"，从不看是不是原版——一个模组里两套食物哲学，正是本问题根源。③【改法·用户选定】新增 EmotionalActionExecutor.isFeedableFood(ItemStack)，与 TLM 完全同口径：有食物属性（1.20.1 Item.getFoodProperties() / 1.21.1 DataComponents.FOOD）&& 不在 AID_FOOD_BLACKLIST 里。FOODS 白名单整体删除（不是留壳——两树字节码核对确认该字段引用已彻底消失）；任何注册了食物属性的物品（模组三明治/蛋糕/其他模组食物）现在都能喂。新增配置项 aidFoodBlacklist（面板「战斗与自保 → 贴身辅助 → 投喂食物黑名单」，TextRow 逗号分隔编辑，与「危险方块表」同款交互）；留空 = 不排除任何食物。默认黑名单：腐肉/蜘蛛眼/毒马铃薯/河豚/紫颂果/可疑炖菜 + 全部生食（beef/porkchop/chicken/mutton/rabbit/cod/salmon/tropical_fish）+ 金苹果/附魔金苹果。④【一个必须说明的取舍·金苹果】旧白名单注释明确写着"金苹果/附魔金苹果【不在内】——走即时增益道具路径（MaidAidOwnerBehavior.useGoldenApple）"。改成通用判定后金苹果天然满足"有食物属性"，若不排除，低血时会被当普通食物喂掉（浪费——金苹果的价值在吸收+再生，不是那 4 点饱食度）。故把这两种也放进默认黑名单保住原行为——这是新口径引入的回归风险，已堵。⑤【改动 6 处调用点（两树）】MaidAidOwnerBehavior 里六处"遍历 FOODS"逐处换成 isFeedableFood(...)：主人链背包/手持选优、feedHealingFood 背包/手持选优、姐妹互助链背包/手持选优、countFoods 诊断、isAidItem 预检。判定逻辑一字未改，只换了"什么算食物"。⑥【验证】两树编译零错；字节码核对 FOODS 字段引用两树两文件均为 False（白名单确实删除）、isFeedableFood 调用数（本类 3 + 行为类 6，正是改动的 6 处）、判定链正确（Item.m_41473_ / DataComponents.FOOD → 取注册名 → 遍历黑名单比对）、MaidSmartConfig 与 PromaidConfigScreen 均含 AID_FOOD_BLACKLIST；四个 lang 文件（两树 × zh_cn/en_us）插入翻译键后 JSON 仍合法、BOM 保留；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 配置面板：排班表从「移动与行为」挪到「生产与工作」（两树同步）：①【反馈】"模组详细配置里面把日程表相关的内容移动到生产与工作中。"②【改法】PromaidConfigScreen.Section 里 SCHEDULE 的所属大类：SCHEDULE("排班表", Group.MOVE) → SCHEDULE("排班表", Group.WORK)。只改所属大类这一个参数——排班表小节的参数行（总开关、战斗还原宽限、可用性检测、反向切换窗口/阈值/冷却、最短持有期、切段后大脑自省、排班活动半径）一行未动，MaidSmartConfig 里的配置项更没动（改分组不影响存档/配置键）。理由：排班表的本质是"给女仆安排各时段干什么活"（种地/挖矿/伐木/烹饪…），属于生产调度而不是移动行为，放生产与工作下更符合直觉。③【顺带同步手册旧路径文案】GuideContent 里 6 处硬编码的旧面板路径一并改掉（两树）：(a)「移动与行为 → 排班表」的「排班活动半径」→「生产与工作 → 排班表」…；(b)总开关：「模组详细配置 → 移动与行为 → 排班表」→「… → 生产与工作 → 排班表」；(c)七个大类清单 ①生产与工作：…/农场与宰杀；→ 末尾加 /排班表；；(d)同一清单 ⑤移动与行为：移动与跟随/空闲与流畅/排班表/搭路；→ 去掉「排班表/」；(e)「怎么找某个开关」·干活（…/宰杀）→生产与工作；→ 末尾加 /排班；(f)同一段 ·跟随/拉回/散步/排班/搭路 → 移动与行为；→ 去掉「排班/」。全库扫描确认：除上述改动说明注释外，再没有"排班表+移动与行为"的残留路径文案。④【验证】两树编译零错；字节码逐常量核对所属大类——SCHEDULE→Group.WORK（原 MOVE）、FOLLOW/IDLE/BRIDGE 仍为 MOVE（确认没误伤）、BUILD/FARM 仍为 WORK，两树一致；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 三叉戟在攻击模式下走近战、不再原地转圈（两树同步）：①【反馈】"三叉戟现在没法作为近战武器用在攻击模式里面了。虽说在各判定里面，它被判定为远程武器。但是我希望保留它被女仆拿在手上开攻击模式的时候不是像个傻子一样在那边转圈。核心问题就在于三叉戟目前只认远程武器，那个模式，调整为攻击模式时候，根本就不认这个武器。明明这个武器也有近战的方式的。"②【根因·不是 TLM 不认三叉戟，是我们自己的走位分类按物品一刀切】javap 钉死两树事实：TridentItem 不是 ProjectileWeaponItem（1.20.1 extends Item implements Vanishable；1.21.1 extends Item implements ProjectileItem）；近战 ATTACK_DAMAGE=8.0（构造器 AttributeModifier(...,8.0d,ADDITION) 实证）；TLM 侧完全认它——TaskAttack.isWeapon="主手装备槽带攻击力属性"（三叉戟满足），MaidMeleeAttack 只在 isHoldingUsableProjectileWeapon（只认 ProjectileWeaponItem）时才不出手→近战任务是愿意挥三叉戟的。真正的问题在 MaidCombatTacticsBehavior：它把 main instanceof TridentItem 写进了"物品即远程"的判定里，于是拿三叉戟走攻击模式时(a) engageRange 被撑到任务搜索半径（十几格就接管走位）、(b) tick 主分支落进 rangedTick → 保持理想射程≥7格+横移绕圈——正是反馈里的"像个傻子一样在那边转圈"：她按远程风筝逻辑跑，而三叉戟既投不出去（投掷要走 trident_attack 任务）、又不肯走近战。另：findBestMeleeWeapon 也把三叉戟排除在"近战武器"外，导致远程任务被贴身时的近战反击手里握着 8 点攻击力的三叉戟却按"无近战武器 1 点"结算。③【改法·分类看当前任务，不看物品类型】三叉戟是双用武器，该按她当前在做什么来分：主分支改成 if (ProjectileWeaponItem || GunCompat.isGun || isRangedTaskActive) → rangedTick 风筝；else → meleeTick 走近战（取代原来的 instanceof TridentItem）。于是：attack（近战任务）持三叉戟 → meleeTick 蛇形逼近/跳劈暴击/贴脸绕圈，8 点攻击力的近战形态这才用得上；trident_attack（投掷任务）→ isRangedTaskActive 为真 → rangedTick 风筝照旧（远程机制一点没动）。判定口径与 AutoCombatSwitch.isRangedTask（同样按任务 UID 分类）对齐，两层不再打架。engageRange 同步去掉 TridentItem（否则近战任务持三叉戟仍会被撑到搜索半径、从十几格就被接管走位）；findBestMeleeWeapon 去掉 TridentItem 排除项（现为合格近战武器参与评分），仍排除的是纯投射武器（弓/弩 ProjectileWeaponItem、枪械——没有可用的近战形态）。④【保留未动】isRangedAttacker（判敌人手里拿什么）里的 TridentItem 照旧——敌人拿三叉戟确实会投掷，那里判定是对的。全类现在只剩这一处 TridentItem 引用（字节码实证）。⑤【验证】两树编译零错；字节码核对全类仅剩 1 处 TridentItem（在 isRangedAttacker）、主分支与 engageRange/findBestMeleeWeapon 均已改走 isRangedTaskActive，两树一致；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 严格门控：中立威胁驱动的"写目标/挥砍"也归"主动参战"管（两树同步）：①【反馈】"是默认会自动清敌对怪吗？把主动参战关了好像也会自己打怪，工作模式甚至都没变，似乎是空手攻击。创造模式下检测不出来，一旦切回生存模式，就会主动攻击敌对生物。"②【根因】NeutralThreatDriver（实测三百四十四为"发狂的狼女仆没反应"加的兜底驱动）做三件事，只有①触发参战在 AutoCombatSwitch 内部查了 COMBAT_AUTO_SWITCH；②往 brain 写 ATTACK_TARGET/LOOK_TARGET 与 ③直接 doHurtTarget 挥砍+导航追人，旧版【一个配置项都没读】（两树全量扫描实证：该类此前引用配置为空），且它在 ProMaidExtension 里无条件注册。于是关掉总开关后：任务不切（①被挡），但②③照跑——任务 UID 不变、人却自己走上去挥刀，正是反馈里的"工作模式没变却在打怪"。③【为何像"空手攻击"】isArmed 只查主手有没有 ATTACK_DAMAGE 属性，而 DiggerItem 构造器会给主手挂 Attributes.ATTACK_DAMAGE（"Tool modifier"，字节码实证）——镐/斧/锹/锄这些干活工具都算"有武器"，工作模式手里正拿着工具 → 判定通过直接挥砍，伤害却只有工具那点加成，观感就是"空手在打"。④【为何创造模式测不出】威胁判据是行为化的 mob.getTarget()==主人/女仆，而原版 TargetingConditions.test 会经 canBeSeenAsEnemy() 过滤目标，创造模式玩家的 Abilities(invulnerable) 为 true → 该判定 false → 怪物根本不会把创造模式的玩家当敌人，getTarget 恒 null → 本驱动静默；切回生存（invulnerable=false）→ 怪物立刻锁定主人 → getTarget 就位 → 当轮即出手。（1.21.1 侧同环是 Player.canBeSeenAsEnemy() 读 Abilities.invulnerable，两树均实证。）⑤【改法·用户选定严格口径】在 drive() 里①之后、②之前插入 COMBAT_AUTO_SWITCH 门控：关了就 return，②写目标与③挥砍一律不做。三点设计说明：(a) 放在①之后——威胁扫描与"威胁解除清理"（threat==null 时那两个 remove）照常运行，Map 不积压，只是不再主动出手；(b) 顺手收回自己写过的目标——关开关时若她脑里还留着"我们写的" ATTACK_TARGET，TLM 攻击行为仍会照着打，故用 ASSIGNED_TARGETS（只登记我们写过的）精确判断，命中才清 ATTACK_TARGET/LOOK_TARGET，不碰 TLM 自己写的目标（战斗任务女仆由 TLM 传感器写，本表无记录）；(c) 两树同步。⑥【行为后果·取舍不是缺陷】按严格口径，关掉「主动参战」后女仆被怪咬只挨打、不还手——除非另外开「单兵战术」（COMBAT_TACTICS，默认开，有自己的开关与战术行为，不受本门控影响）。这正是用户选定的语义。⑦【范围】重锤猛击（1.21.1 专属）有独立 COMBAT_MACE_SMASH 开关、且只在战斗任务下激活，不属本次漏网范围，未改动。⑧【验证】两树编译零错；字节码核对 getstatic COMBAT_AUTO_SWITCH 紧跟 tryEngagePublic(①)之后、eraseMemory/doHurtTarget(②③)均在其后（门控位置正确）；ASSIGNED_TARGETS 仅本类与 assignedTargetOf(无人调用)引用；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 空袭索敌诊断日志【数值不动·只加取证手段】（两树同步）：①【反馈】"锁敌范围仍然很奇怪。建议加到半径为 200 格。然后加个日志来判定一下，我看看为什么锁不到。"→ 随后"如果确认了不是因为索敌范围太小的问题，那就先保持原样。加个日志，然后等我测试。"②【排查结论·已用 javap 证实】不是"范围太小"，是我们的半径在大多数时段根本不参与——EntityMaid.searchDimension() 的实现是：if (getScheduleDetail() == Activity.WORK) return getTask().searchDimension(this); return TaskManager.getIdleTask().searchDimension(this); 而 getScheduleDetail() 按当前时间(getDayTime()%24000)查排班表返回活动。所以只有排班活动恰为 WORK 时，传感器拿到的才是我们覆写的 inflate(50,50,50)；其余时段（夜间 REST 等）盒子来自空闲任务默认实现（水平=限制半径，垂直硬编码 4.0）——实测五百零八/五百一十三 两次调半径在非 WORK 时段等于没改。这本身就足以解释"范围仍然很奇怪"，而不是"50 还不够大"。另：Level.getEntitiesOfClass 一链没有任何 AABB 体积上限（EntityGetter 默认实现只在 getSize()<1.0E-7 时早退），200 格扫描本身可行，不存在"盒子太大静默失效"。③【改法】按用户指示数值保持原样（FLIGHT_SEARCH_RADIUS 两树仍 50.0，没改成 200），新增只读诊断探针 FlightTargetProbe（两树同步），按 TLM 原版那条链逐环计数：候选(复扫 searchDimension 盒子的活体数) → 存活(isAlive && != maid) → 可攻击(EntityMaid.canAttack→IAttackTask.canAttack→DefaultMonsterType，动物/村民/玩家/盔甲架在此出局) → 活动范围内(isWithinRestriction) → 有视线(Sensor.isEntityAttackable，含视线判定=隔墙不出手的来源) → 选中(最近的那个)。日志同时打印 activity=(当前排班活动) 与 盒尺寸=x/y/z——这两个字段是本次关键，一眼看出"此刻盒子多大、是不是走了空闲口径"。④【挂载点】MaidToolAutoEquipBehavior.checkExtraStartConditions（core 行为、任何 activity 都跑、每 tick 一次）——挂这里是为拿到真实排班活动字段（搜索盒子按它分流）。⑤【频率】每女仆每 40 tick(2 秒)最多一条，且只在空袭任务+当前无攻击目标时打印（正是锁不到的现场）；有目标或非空袭任务一律静默，零刷屏。走 PromaidLog，受「运行日志」总开关控制，同时写 logs/promaid.log 与 latest.log；搜关键词【空袭索敌】。纯只读：不写 brain 记忆、不改状态、不参与战斗决策。⑥【边界】本次没有"修复"任何东西，只加观测手段；WORK 门控是已证实的事实，但"用户实际遇到的正是它"要等日志说话——探针的 activity= 字段就是用来验这一点的。探针是一次性排查工具，验证完可直接删（类注释已写明）。⑦【验证】两树编译零错；字节码逐项核对五环判据/盒尺寸三访问器/activity 名/实体类型名/挂载点；打包→deploy_all.py 三端 match=True；test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 副手展示改用"实际消耗的那枚火箭"【修正五百一十一的错误判断】（两树同步）：①【反馈】"应该用使用的那个火箭的那个贴图吧。"（针对实测五百一十一里我写的"展示用的固定是朴素火箭"）②【我上一轮错在哪】实测五百一十一我让副手展示一枚朴素火箭（new ItemStack(Items.FIREWORK_ROCKET)、不写 Explosions），理由写的是"避免被当弩弹药吃掉"。这次核实发现这个理由【方向反了】：(a) 烟花火箭的物品模型是静态的——models/item/firework_rocket.json 全文只有 {"parent":"item/generated","textures":{"layer0":"minecraft:item/firework_rocket"}}，【没有任何 overrides、不读 NBT/组件】（1.20.1 与 1.21.1 的 extra jar 都实证过），所以"带不带爆炸组件"在手部看上去完全一样——我那句"观感一致"是对的，但"必须换掉真实那枚"就没有依据了；(b) 而所谓"被当弹药吃掉"的风险【根本不存在】：展示窗口内若正好开弩，takeExplosiveFirework（副手优先）读到的是真实的爆炸烟花、它本来就是合格弹药；反倒是朴素火箭万一被当弹药打出去才是 0 伤害（FireworkRocketEntity.dealExplosionDamage 开头 if (!this.hasExplosion()) return;）——也就是说我为了"避险"引入了一个更糟的假想风险。用户一句"应该用使用的那个火箭的那个贴图吧"点破了这层：既然模型一样，就该用她真正消耗掉的那一枚显示——语义也对（"掏出的就是刚才那枚"）。③【改法】FlightFireworkPose.show(EntityMaid) → show(EntityMaid, ItemStack consumedFw)：display = (consumedFw != null && !consumedFw.isEmpty()) ? consumedFw.copy() : new ItemStack(Items.FIREWORK_ROCKET)（兜底）；display.setCount(1)；再 setItemInHand(OFFHAND, display)。四个调用点（两树 × 近战起飞 / 远程补推）都改成把 takeFirework 取出的 fw 传进去。其余部分（10 tick 保持、原物快照与还原、core 行为里的归还入口、clearAll）完全不动。④【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；javap -p -c 反汇编构建产物：FlightFireworkPose.show 签名已是 (EntityMaid, ItemStack) 两参，方法体里能看到"参数非空且非空栈 → consumedFw.copy()，否则 new ItemStack(Items.FIREWORK_ROCKET)"的分支（字节码 ifnull / isEmpty / 两条 copy() 路径清晰可见）；两树调用点的 show 调用均为 2 参形式；build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.833s）与 neoforge1211 PASS（Done 0.994s）。已部署待实测：空袭放烟花那一瞬副手显示的就是你包里被消耗掉的那枚烟花（外观与它一致），约 0.5 秒后自动换回原副手物品（盾牌/食物）；其余行为与五百一十一相同。


v1.2.0 空袭索敌：三轴统一 50 格（以自身为圆心）【隔墙不出手由原版保证】调整（两树同步）：①【反馈】"索敌范围还是太小，改为以自身为圆心，半径为 50 索敌，但就和 tlm 原版一样，有隔墙阻挡不出手。"②【演进】实测五百零八先把垂直半径从 TLM 默认的 4 格提到 50 格、水平仍沿用 maid.searchRadius()（默认 16）——当时"只动垂直"是保守取舍，但实测水平 16 格仍太小：空袭是立体作战，敌人在斜上方 20~40 格时水平距离早就出界。本条按需求三轴统一。③【改法】FLIGHT_SEARCH_RADIUS=50.0，两个分支都 AABB.inflate(50,50,50)（有 home/工作点限制时以限制中心为基准、与 TLM 同款）。AABB.inflate 是三轴独立膨胀，等价于"以自身碰撞箱为中心、半径 50 的立方盒"（边长 100）；水平/垂直一视同仁，且【已彻底不再引用 searchRadius】（字节码可见 m_21535_/searchRadius 在这两个方法里归零）——水平不再受 TLM 默认 16 格限制。④【隔墙不出手：由 TLM 原版机制保证，我们没动这条链路】这是本条最需要说清的一点——"放大扫描范围"与"能不能打"是两件事、分属两层：扫描范围由本方法 searchDimension 决定（MaidNearestLivingEntitySensor 用它做 AABB 扫描，结果写进 NEAREST_VISIBLE_LIVING_ENTITIES）；能否出手由 TLM 原版 IAttackTask.findFirstValidAttackTarget 决定（从该记忆里 findClosest(predicate) 选目标）。而 NearestVisibleLivingEntities 【自带视线过滤】：构造器把 Sensor.isEntityAttackable 作为可见性谓词（字节码实证），后者一路走到 TargetingConditions.test、其 ignoreLineOfSight 字段默认 false → 最终查 Sensing.hasLineOfSight。所以墙后的怪能进候选列表、但会在可见性那一关被滤掉——放大范围不会让女仆隔墙出手，这正是"和 TLM 原版一样、有隔墙阻挡不出手"的要求。（另：实测五百零五给自造的那几条近战链路补过 hasSight，那是另一层保险，针对我们绕过 TLM 索敌直调 doHurtTarget 的路径，与原版这条链路互不冲突。）⑤【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；javap -p -c 反汇编构建产物、四处逐一确认（按方法边界切出 searchDimension 全文）：每个 inflate/m_82377_ 调用的三个参数都是 ldc2_w 50.0d × 3（常量序列 [50.0×6]——两个分支各三个）；searchRadius（m_21535_/searchRadius）在方法体内出现 0 次（已不再依赖）；两分支都保留。build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.816s）与 neoforge1211 PASS（Done 0.888s）——本条是服务端行为（索敌传感器跑在服务端），回归有参考价值。已部署待实测：空袭时 50 格内（含斜上方/正上方）的敌人都能被锁定；隔墙的目标仍不出手（与原版一致）；守家/工作点受限的女仆索敌盒以限制中心为基准。


v1.2.0 空袭落地雪/水无限重放 → 女仆无法起飞【内置 2 秒冷却】根因修复（两树同步）：①【反馈】"落地雪在空战状态下有 bug，女仆落地后会一直释放导致无法起飞。解决方法也简单，给此空袭状态的落地雪/水内置一个 2 秒 cd 就好了"②【根因：空袭分支复用了 clutchedThisFall，而它在回收时被清零】WaterClutchBehavior.recoverFluid() 结尾有一行this.clutchedThisFall = false（注释写明 v1.5.25e：收水后允许再放一次——极高塔坠落时 1 秒水可能提前收、落地前最后一段若速度仍达标则再放一次接住）。这行对【通用落地水】是合理的，但空袭分支 isFlightAirborne 复用了同一个标志，于是变成无限循环：①空袭落地缓冲触发 → clutchedThisFall=true → 放出雪垫；②保持时长（雪默认 100 tick / 水按配置）到期 → recoverFluid() → 标志被清零；③下一 tick 条件再次满足 → 又放一次 → 再清零……无限循环。【为什么"落地了还在触发"】细雪不是实心方块——实体是沉进雪里的，onGround() 一直不成立，所以那行"落地即重置"在雪里永远不执行；而 recoverFluid 又会清零，两条路径叠加就让她反复重放。表现即"落地后一直释放、雪花不停铺、行为槽一直被占 → 无法起飞"。③【改法（按用户说的：内置 2 秒 CD）】新增独立字段 flightClutchCd（下一允许触发的 tick）与常量 FLIGHT_CLUTCH_CD=40L（2 秒），【只作用于空袭分支】：条件里加 gameTime >= this.flightClutchCd，触发时记 this.flightClutchCd = gameTime + FLIGHT_CLUTCH_CD。刻意与通用分支解耦：recoverFluid() 里那行"收水后再放一次"原样保留、不动它的语义（那是为通用落地水的极高塔场景服务的），CD 是独立字段、recoverFluid 不碰它，所以空袭分支的循环被彻底切断，普通坠落的落地水行为完全不受影响。④【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；改动过程中我自己出过一次错：第一次编辑误删了 boolean flightWater = ... 那一行，靠括号平衡核对（{/} 各 61/69、diff=0）与"boolean flightWater = 出现次数 = 1"发现并补回——这类结构性错误编译不一定第一时间报，所以每次改完都会核对；javap -p -c 反汇编构建产物，两树 tick 方法体内确认：getfield flightClutchCd + lcmp（比较 gameTime >= cd）、ldc2_w 40l + putfield flightClutchCd（触发时写入 2 秒冷却）——两树完全一致；build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.748s）与 neoforge1211 PASS（Done 1.059s）——本条是服务端行为（放块/回收都在服务端 tick 里），回归有参考价值。已部署待实测：空袭落地放一次雪/水后 2 秒内不再重复释放；落地后能正常起飞；普通（非空袭）坠落的落地水"收水后再补一次"语义不变。


v1.2.0 放烟花的瞬间副手"亮一下"烟花模型【纯表现，零逻辑改动】新增（两树同步）：①【需求】"我还是希望女仆放烟花的瞬间副手拿一下烟花（只加了个动作和烟花模型在副手，不改运行逻辑，用完恢复）"②【背景】实测五百一十 已把副手永久让出给盾牌/食物（烟花改为从背包按需取用、发射路径自己造弹体），功能上正确但"她在放烟花"这件事完全看不到了——本条只补动作表现：发射那一刻把烟花放进副手 0.5 秒，然后原样还回去。③【实现：新增 FlightFireworkPose（两树各一份，约 130 行）】不碰任何运行逻辑：不改弹药判定/推力/冷却/三件套就绪判定，只是 show() 里 setItemInSlot(OFFHAND, 烟花)、tick() 到点还原，烟花在手上【不参与任何计算】；原物必还：放置前先 getOffhandItem().copy() 快照整栈（含 NBT/组件/数量），到点整体写回——盾牌/食物绝不会丢；有界：每女仆至多一条倒计时记录（HOLD_TICKS=10，0.5 秒），到点自清；同步：setItemSlot/setItemInHand 走 LivingEntity 的装备槽同步（EntityMaid 覆写了 m_8061_），服务端一改客户端立刻看得到手里的烟花——【不需要新网络包】。④【固定展示一枚朴素火箭（new ItemStack(Items.FIREWORK_ROCKET)、不写 Explosions）】烟花火箭在手部的物品模型与有没有爆炸组件无关（颜色只在飞行轨迹里体现），观感一致；而这样能避开一个交互风险——展示窗口内若正好开弩，TLM 的 getProjectile（副手是烟花就交出来）与我们的 takeExplosiveFirework（副手优先）都会先读副手，展示"带爆炸的那枚"就可能被当弹药吃掉。⑤【归还点选在 core 行为，这是本条最关键的设计】第一版把归还在 forget() 里做，但 forget(UUID) 拿不到实体引用，清表会把"原副手物品"快照一起丢掉 → 若行为在 10 tick 窗口内被强杀（切任务/目标丢失/死亡），盾牌/食物会【永久】留在烟花状态，这不可接受。所以改成：归还挂在 MaidToolAutoEquipBehavior.canUse——那个 core 行为任何 activity 都跑、每 tick 一次（正是本项目自己注释里写的），且空袭分支里本来就调了 MaidFlightKit.equip；FlightFireworkPose.tick(maid) 放那里 = 无论飞行行为是否在跑，最迟 10 tick 后一定还原。forget() 因此刻意不清表（注释里写明了原因），由 clearAll() 在服务器停机时收尾。⑥【接线（两树各 4 处）】MaidFlightCombatBehavior 近战空袭起飞 tryLaunch → show(maid)；MaidFlightCombatBehavior 远程空袭掉高补推 tickRangedAir → show(maid)；MaidToolAutoEquipBehavior（core、每 tick）→ tick(maid)【归还入口】；MaidFlightCombatBehavior.clearAll() → clearAll()。⑦【验证】两树编译零错（新文件已重跑 gen_compile.py / gen_compile_neo.py 进 argfile）；javap -p -c 反汇编构建产物：FlightFireworkPose（两树）六个方法齐全（show/tick/restoreNow/forget/clearAll/restore），且含副手写入（m_21008_/setItemInHand）、副手快照读取（m_21206_/getOffhandItem）、快照用 copy()（m_41777_/copy）、以及 Items 常量（朴素火箭）；调用点逐一计数：MaidFlightCombatBehavior 里 show=2、clearAll=1，MaidToolAutoEquipBehavior 里 tick=1——两树完全一致、与接线表相符；build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.532s）与 neoforge1211 PASS（Done 0.986s）——本条虽是表现层，但槽位操作与还原都在服务端执行，回归能覆盖"新增类不崩服"。已部署待实测：空袭放烟花（起飞/掉高补推）那一瞬副手会短暂显示一枚烟花火箭（约 0.5 秒），随后自动换回你放在副手的盾牌/食物；期间盾牌/食物只是被暂时盖住、不会丢失；飞行机制、弹药消耗、三件套判定全部不变。


v1.2.0 空袭锁死副手 → 无法进食回血【副手让出，烟花改为按需取用】根因修复（两树同步）：①【反馈】"女仆没办法在空战状态下吃东西回血"；用户补充诊断（方向准确）："空袭状态下锁定了主副手物品，女仆无法在副手装盾牌和食物，我认为这是原因。应该为，不把烟花绑在主副手，检测但只有用的时后放手上"②【根因：equip() 把烟花常驻副手，且写进了"就绪"判定】MaidFlightKit.equip 旧版做三件事：胸甲穿鞘翅、主手换武器、【副手放烟花】——最后一步是长驻占用，而且就绪判定也要求副手必须是烟花（return ok && ... && isFirework(hands.getStackInSlot(1))）。后果：空袭期间副手被烟花锁死，盾牌/食物都装不进去，于是 TLM 的回血吃 MaidHealSelfTask（扫"主手/副手/背包"、换手时要把旧物腾出来）拿不到东西 → "空战无法进食回血"。用户的判断是对的。③【关键事实：烟花根本不需要拿在手上】发射走的是 MaidFlightCombatBehavior.launchFirework，它自己造弹体：new ItemStack(Items.FIREWORK_ROCKET) + 只写 Fireworks.Flight（不写 Explosions）+ new FireworkRocketEntity(level, rocketStack, maid) 直接入世界——【全程不读女仆手里的物品】，方法签名里那个 ItemStack consumedIgnored 参数就是证据（收了却不用）。所以"常驻副手"纯属多余占用。④【改法（按用户选的方案）】(1) 副手完全让出：equip() 删掉副手放烟花那一段，【不再写副手任何槽位】——盾牌/食物可正常装在副手；(2) 起飞门槛改为"烟花在任意位置"：就绪判定由 isFirework(副手槽) 改为 hasFirework(maid)，而 hasFirework 本来就是"主手/副手/背包"三处覆盖（实测五百零二已对齐），所以烟花放背包、放主手、放副手都认。⑤【同步修正 5 处已不适用的注释/文案（代码本就正确、描述已过时）】MaidFlightKit.equip 与类头"副手拿烟花"→"不再占用副手"；MaidToolAutoEquipBehavior（两树）幂等性说明从"副手已有烟花"改为"烟花在身上任意位置"；MaidFlightRangedTask（两树）"两种身份抢同一个副手槽"→说明已不抢槽，但【"只认带爆炸的烟花"这条规则必须保留】（0 伤害烟花当子弹打等于白烧燃料）；neo 用户可见配置文案（MaidSmartConfig + PromaidConfigScreen）"主手换重锤、副手拿烟花"→"主手换重锤（烟花不必拿在手上，副手留给玩家放盾牌/食物）"。⑥【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；javap -p -c 反汇编构建产物、按方法声明边界精确切出 equip 方法体（不靠字符串搜索）：1.20.1 为 117 行、1.21.1 为 129 行，两者的 setStackInSlot 都【只剩 1 次且槽位常量是 iconst_0（主手）】——副手槽（iconst_1）零写入；就绪链为 isUsableElytra(胸甲) → isWeaponForTask(主手) → hasFirework(整个女仆)。另全库复查"副手 + 烟花"的所有提及，剩下都已是正确表述（描述 TLM 原版弩口径或其它功能的副手用途）、无残留误导。build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.777s）与 neoforge1211 PASS（Done 0.835s）——本条是服务端行为（装备槽操作在服务端 tick 里跑），回归有参考价值。已部署待实测：空袭时把盾牌/食物放副手不再被顶掉；缺血≥2 点时能正常进食回血；飞行动力照常（烟花从背包按需取、发射推力不变）；三件套判定不受影响。


v1.2.0 岩浆垫方块自救被禁搭误伤【四百九十八收紧引入的回归】根因修复（两树同步）：①【反馈】"女仆在没有持有火焰保护饰品和抗火效果的情况下，在岩浆里面垫方块自救是一个很正常的行为。需要将它改回来。"②【根因：1.20.1 的放行名单从来没有岩浆这一条（SRG 名注反了）】MaidPlaceGuard.blocked 的放行名单本意是"落地 / 在水里 / 在岩浆里 / 骑乘 → 不拦"，但 1.20.1 侧 SRG 注释写的是 m_20096_=onGround m_20071_=isInWater m_20069_=isInLava——最后这个注反了。javap 复核 Entity 字节码：m_20069_ 是 return f_19798_（触水标志位）＝isInWater；m_20071_ 是 m_20069_||下雨||气泡柱＝isInWaterRainOrBubble；真 isInLava 是 m_20077_（!f_19803_ && f_19799_.getDouble(FluidTags.f_13132_=LAVA) > 0）。三个全是"水"，真 isInLava 的 m_20077_ 一次都没出现——所以岩浆从来就没被放行过。③【为什么以前没暴露、四百九十八之后才炸】岩浆不会像水那样把 fallDistance 清零（水有两条清零路径、都只认水 tag），岩浆只有 Entity.baseTick 里一句 f_19789_ *= 0.5f（每 tick 减半），所以泡岩浆下沉时 fallDistance 稳定停在一个小正数（稳态≈每 tick 下沉量）。四百四十三那版阈值是"≥ 落地水高度 6 格"，岩浆的小正数远小于 6 → 放行，垫方块自救正常；四百九十八收紧成"只要在下落就禁（>0）"后，岩浆下沉必然满足 → 禁搭 → lavaStepUp 里 takeBuildBlock 返回 null→ 岩浆垫方块自救整条失效。所以这是那次收紧顺带引入的回归，不是老问题。④【失效表现很隐蔽】她背包只有方块（没水桶/没末影珍珠/没抗火药）时会一路走到"自救手段都用尽了，我没招了！"；而且 announceNoMaterial 自己也调 blocked() 来抑制误报，连"背包里没有搭方块的材料"这个气泡都不会出——属于静默失败。另：1.21.1 侧一直是对的（用 Mojmap 的 isInLava()，不存在注反问题），所以这本来是两树行为不一致。⑤【改法】1.20.1 放行名单补上真 m_20077_()（isInLava），并把那行错误的 SRG 注释改对：if (maid.m_20096_() || maid.m_20071_() || maid.m_20069_() || maid.m_20077_() || maid.m_20159_()) return false;保留 m_20069_/m_20071_（水与雨/气泡柱放行本身是对的），只加岩浆这一条；1.21.1 代码无需改动、仅补注说明这条为何必须留——两树现在同款。⑥【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；javap -p -c 反汇编构建产物：1.20.1 MaidPlaceGuard.blocked 的放行段现在是 m_20096_ → m_20071_ → m_20069_ → m_20077_ → m_20159_ 一路 ifne 到同一个"返回 false"，与 1.21.1 的 onGround → isInWater → isInLava → isPassenger 结构完全对齐；build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 3.007s）与 neoforge1211 PASS（Done 0.848s）。⑦【这条回归有参考价值】放行名单在服务端 tick 里求值，是服务端行为判定、不是纯客户端渲染。已部署待实测：泡岩浆时女仆照常垫方块脱离液面（无抗火/无火焰保护饰品也一样）；水/雨/气泡柱里的放行不变；正常悬空/滑翔/坠落仍按四百九十八的口径禁搭（不受影响）。


v1.2.0 空袭的垂直索敌提到 50 格【修"凋灵飞高了就锁不上"】根因修复（两树同步，两个空袭任务）：①【反馈】"女仆对高度上的索敌判定太小，在空袭状态应该要增大索敌范围。在进入两个空袭状态下的时候，高度的锁敌应提高为 50 格左右。测试中发现，女仆跟凋灵打的时候，可能会因为凋灵飞得太高而无法锁敌到。"②【根因：TLM 的索敌盒把垂直方向硬编码成了 4 格】女仆索敌范围由 IMaidTask.searchDimension(maid) 决定，MaidNearestLivingEntitySensor 每 tick 用其返回值做 AABB 扫描、结果写进 NEAREST_VISIBLE_LIVING_ENTITIES，而 IAttackTask.findFirstValidAttackTarget（我们两个空袭任务注册的索敌行为）正是从这个记忆里挑目标（反编译实证：读 f_148205_）。TLM 的默认实现（IMaidTask，两树同构）是 AABB.inflate(searchRadius, 4.0, searchRadius)——【中间那个 4.0 就是垂直方向，被写死了】，水平才是 searchRadius（默认 16）。所以空袭模式下女仆的垂直索敌只有 4 格：凋灵稍微飞高一点就出了这个盒，传感器扫不到它 → 记忆里没有 → findFirstValidAttackTarget 选不出目标 → 失去锁定。空袭本来就是打空中目标的模式，4 格垂直盲区完全不合理，与实测现象完全吻合。另已核对：MaidFlightCombatTask / MaidFlightRangedTask 原文里都【没有】覆写 searchDimension，所以一直吃 TLM 默认值。③【改法】在两个空袭任务（近战空袭 MaidFlightCombatTask + 远程空袭 MaidFlightRangedTask）各覆写 searchDimension：【只把垂直半径提到 50 格】（按需求"提高到 50 格左右"），水平仍沿用 maid.searchRadius()——保持与其它任务一致的接战半径、不改索敌广度，避免"顺带把水平拉大"引入未要求的行为变化。两分支写法与 TLM 保持一致：有活动范围限制（home/工作点）时以限制中心为基准，否则以自身碰撞箱为基准——这样守家中的空袭女仆不会因为索敌盒变大而跨出活动范围。④【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0）；javap -p -c 反汇编构建产物、四处逐一确认：都声明了 searchDimension(EntityMaid)（覆写生效）；方法体里能看到 searchRadius() 取值 + AABB.m_82377_(dx,dy,dz)/inflate(...) 调用，且中间那个参数是 ldc2_w 50.0d（垂直位）、两侧是同一个水平值（dload_3）——即 inflate(horizontal, 50.0, horizontal)，正是预期形态；两个分支（m_21536_()/hasRestriction() 为真/假）都在。build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.603s）与 neoforge1211 PASS（Done 0.959s）。⑤【这条回归比前几条有意义】索敌跑在服务端（传感器 + brain 记忆），不是纯客户端渲染，所以服务端能正常加载并启动对本次改动是有参考价值的。已部署待实测：空袭时凋灵飞高（50 格内）仍能被锁定并追击；地面目标行为不变；守家/工作点受限的女仆索敌盒以限制中心为准、不会因此跑出活动范围。


v1.2.0 鞘翅光泽改回"只有真附魔才亮"【撤掉五百零四的常亮】根因修复（两树同步，四处）：①【反馈】"现在对于鞘翅附魔的渲染不分对象。不管附没附魔都渲染出了附魔的样子。"②【根因：这是五百零四自己改出来的】那次把光泽参数固定成 true（常亮）：ItemRenderer.m_115184_(buffer, RenderType.m_110431_(WINGS), false, true)。当时的理由是"本模组手册/排班表/协议源石都是常亮光泽的口径，顺手统一"——但鞘翅跟那几件物品不是一回事：手册是我们自己的物品（常亮是我们的设计选择），而鞘翅是玩家自己附魔的装备，光泽应当如实反映它有没有附魔。注释里甚至留了"想改回只有附魔才亮就把 true 换回去"的话，但默认值本身选错了，等于把"有没有附魔"这条信息抹掉了。③【改法】四处（两树 × Bedrock/Gecko）全部改回与原版 ElytraLayer 完全同款的判定：1.20.1 传 chest.m_41790_()（= ItemStack.hasFoil）；1.21.1 传 chest.hasFoil()。四处差异说明：1.20.1 Gecko 层原本就是对的（五百零四之前一直传 chest.m_41790_()）；1.21.1 那两处改前是硬编码 false（从来不亮），被五百零四一并改成 true，现在一并改为按附魔判定。④【确认判定不会被本模组污染】MaidFlightKit.equip() 只是把鞘翅从背包原样搬进 CHEST 槽（m_8061_/setItemSlot），全程不改写 NBT、不加附魔、不动 Enchantments——所以 hasFoil 读到的就是玩家自己那件鞘翅的真实附魔状态。⑤【验证】两树编译零错；过程中修掉一个自己引入的错误：1.21.1 的 LayerMaidElytra 里原本没有 chest 局部变量（那是内联调用 maid.getItemBySlot(...) 的写法），我直接写了 chest.hasFoil() → "找不到符号: 变量 chest"，已补上局部变量与 ItemStack 导入（该类原本没引这个 import）；javap -p -c 反汇编构建产物，四处逐一确认光泽位已从常量变为真实调用：1.20.1 为 invokevirtual ItemStack.m_41790_:()Z 出现在 foil 调用点前的常量序列末位（序列 iconst_1, iconst_0, iconst_0, m_41790_——前两参是 noEntity/useTranslucent，末位才是光泽位）；1.21.1 为 invokevirtual ItemStack.hasFoil:()Z 同位置（序列 iconst_1, iconst_0, hasFoil）；四处都含取胸甲槽的调用且判定存在。build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.770s）与 neoforge1211 PASS（Done 0.905s）。⑥【证据边界】本条同样是纯客户端渲染改动，服务端回归只证明"新 jar 没让服务器启动失败"，不对渲染效果构成证据——实际观感仍以客户端目视为准。⑦【待部署】与上一条相同，deploy_all.py 检测到游戏实例在运行时会拒绝写入；退出游戏后执行 python deploy_all.py 即可。已部署待实测：普通鞘翅（未附魔）不再有流光；给鞘翅附上任意附魔（如耐久/经验修补）后正常出现附魔光泽；其余外观（尺寸/上移/展翅/前倾）不受影响。


v1.2.0 飞行作战"跟随俯角"前倾【让滑翔姿态向玩家鞘翅靠拢】（Gecko 链，两树同步）：①【需求】"近战空袭飞向敌人时身体建模可能还要向前方向翻一下，大致 35 度左右，这样更接近玩家的鞘翅飞行动作，现在的游泳动作让女仆在俯冲时更类似全身体正对敌人。仅修改建模，机制不变。"②【"游泳动作"确实是我们在播】本项目自己的 MaidSwimGlideMixin 让女仆滑翔时 isVisuallySwimming() 返回 true，而 TLM 的 Gecko 动画选择表 AnimationRegister（两树反编译实证）里游泳动画的触发谓词正是它：AnimationRegister.register("swim", 0, (maid,event) -> maid.asEntity().m_6067_())（1.20.1）/ isVisuallySwimming()（1.21.1）；酒狐（winefox）模型里确实带 swim / swim_stand 两段动画（winefox.animation.json 实测有条目），于是滑翔期间播放游泳动画——身体水平俯卧、中轴直指目标，就是反馈的观感来源。③【玩家鞘翅的权威配方】PlayerRenderer.setupRotations（字节码实证）：ramp = clamp(t*t/100, 0, 1)（t=连续滑翔 tick），然后 mulPose(Axis.XP.rotationDegrees(ramp * (-90.0F - player.getXRot())))——即"基础 -90° 之外【再减掉俯仰角】"。女仆这边的基础 -90° 由模型动画承担，所以本次只补 -getXRot() 这一项：俯冲时（低头为正角）身体继续前翻，平飞/盘旋（俯角≈0）几乎无变化、无需再分阶段。这也说明"大约 35 度"的准确含义是【35° 是俯冲角本身、不是固定要加的角度】，故本条实现的是"跟随俯角"而非硬编码 35°。④【只改 Gecko 这条链（按用户选择）】Gecko（酒狐家族等）走 EntityMaidRenderer.render 的 isGeckoModel() 分支 → GeckoEntityMaidRenderer（继承 GeoReplacedEntityRenderer），本次已改；Bedrock（原版风格）走 BedrockModel.setupAnim，本轮未动。【为什么用 mixin 而不是 TLM 官方扩展点】ICustomAnimation 确实同时提供 setupRotations(Bedrock) 与 setupGeckoRotations(Gecko) 两个回调，但 TLM 自己的 SwimAnimation 【只覆写了 setupRotations、没覆写 setupGeckoRotations】——说明 Gecko 路径的姿态根本不经过扩展点分发，扩展点够不到，必须注入 GeoReplacedEntityRenderer.setupRotations 本身。注入位置选 RETURN：该方法先调 LivingEntityRenderer.setupRotations 摆好朝向，此刻矩阵仍在"Y 朝上、原点在脚下"的世界式空间（scale(-1,-1,1) 与 translate(0,-1.501,0) 尚未施加，即模型渲染之前），原版所有飞行/俯卧姿态都在这一层用 Axis.XP 施加——轴向与枢轴因此与游戏一致，叠加而非覆盖模型动画。⑤【触发口径】MaidFlightKit.isFlightTask(maid) && !onGround()——与鞘翅图层展翅口径一致；刻意不认 isFallFlying()：收翅猛击那段 tickSmash 会主动 setGliding(false)（收翅才吃得到猛击判定），只认滑翔位的话"飞向敌人"最需要前倾的那一段恰好不生效。⑥【可调】唯一调节量 FlightDiveTilt.TILT_FACTOR（默认 1.0 = 与玩家鞘翅同款）；方向若相反把 apply 里的取负去掉即可（一行）。纯客户端渲染，不动任何飞行机制、不加网络包、服务端无感。⑦【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0 仅既有警告），新文件已重跑 gen_compile.py / gen_compile_neo.py 进 argfile；javap 反汇编构建产物确认 FlightDiveTilt 含 isFlightTask + onGround + getXRot/m_146909_ + Axis.XP/f_252529_ + rotationDegrees/m_252977_ + mulPose/m_252781_，MaidFlightDiveTiltMixin 调 FlightDiveTilt.apply 且注解值经 javap -v 提取为 method="m_7523_"（1.20.1）/ method="setupRotations"（1.21.1）、at=RETURN；注入点离线核对：TLM 目标类 GeoReplacedEntityRenderer 里对应方法确实存在且描述符逐一匹配——1.20.1 是 5 参 m_7523_(T,PoseStack,float,float,float)（与原版 LivingEntityRenderer 一致），1.21.1 是 6 参 setupRotations(T,PoseStack,float,float,float,float)，两树签名不同故 mixin 各写一份；build 脚本 verify_jar_classes OK + lang json OK；test_server.py 1201 PASS（Done 2.723s）与 neoforge1211 PASS（Done 0.855s/0.815s，首次跑出现一次 TIMEOUT、重跑两次均 PASS——与上一轮同样的启动偶发超时，如实记录）。【⚠️ 证据边界，必须说清】本条 mixin 的目标 GeoReplacedEntityRenderer 是纯客户端渲染类、且 mixin 注册在 client 列表里，专用服务器根本不加载这个类也不加载这段 client mixin 配置——所以 test_server 结果对本次改动不构成任何有效性证据，只证明"新 jar 没让服务器启动失败"。本条真正依据是：编译零错 + 字节码断言 + 注入点离线核对（目标类里方法存在且描述符与 mixin 签名逐一匹配）。实际渲染效果只能由你在客户端目视确认。⑧【待部署】部署时检测到用户正在游玩（Minecraft NeoForge 1.21.1 窗口运行中），deploy_all.py 按设计拒绝写入（防覆盖运行中的 jar），故本轮尚未部署到游戏实例；退出游戏后执行 python deploy_all.py 即可。已部署待实测：近战空袭俯冲时酒狐身体随俯角继续前翻（更接近玩家鞘翅），抬头爬升自然回正；平飞盘旋几乎无变化；落地恢复站立；飞行机制（索敌/伤害/烟花推进/收翅猛击）完全不变。


v1.2.0 隔墙疯狂挥空【四条自造攻击链路都绕过了"视线"这道门】根因修复（两树同步）：①【反馈】"可以优化一下攻击逻辑吗，我发现装了这个之后墙壁后有怪物酒狐就会疯狂的攻击墙壁。"②【根因：TLM 原生近战本来就有视线门控，我们自己造的链路全绕过了】TLM MaidMeleeAttack 出手条件三条（两树反编译实证）：!isHoldingUsableProjectileWeapon(maid) && maid.isWithinMeleeAttackRange(target) && nearestVisibleLivingEntities.contains(target)——最后那条 contains 走 Sensor.isEntityAttackable → TargetingConditions.test（ignoreLineOfSight 字段默认 false）→ Sensing.hasLineOfSight。也就是说【原版女仆隔墙本来打不到怪】。而本模组为了补 TLM 的结构性盲区（中立/魔改生物索敌、近身反击、空袭命中率）自己实现的攻击链路直接调 m_7327_/doHurtTarget，全都没带上这道门，共四处：(a) NeutralThreatDriver 第③步兜底挥砍只判"距离≤3.1 + getTarget() 锁定我方"——主犯，墙后怪持续锁定主人/女仆 → 每 12 tick 对着墙挥一刀；(b) MaidCombatTacticsBehavior.meleeCounterAttack 按目标周围 2 格 AABB 横扫、只查友伤 → 隔墙能把墙后怪一起真打到（比空挥更糟）；(c) SelfPreservationBehavior.counterAttack 上游 findThreat 查了视线，但"着火兜底"走 findNearestThreatNearby（只有类型+距离、没有视线）→ 墙后怪也能让她隔墙挥刀/放箭/射弹幕；(d) MaidFlightCombatBehavior 的 smashHit 范围命中 + groundMelee 按 AABB 直接结算 → 空袭/落地近战隔墙也打得到。③【改法：统一往项目现有的那条视线工具里收，不新造轮子】项目里本来就有 SelfPreservationBehavior.hasSight(from,other)（public static、两树同名，raycast 到眼睛高度 +1.2 的 ClipContext.Block.COLLIDER），findThreat 与举盾判定都在用它，缺的只是没贯穿到"直接出手"的路径上——六处出手点统一补上（两树同改）。④【隔墙不罚站，这是本次最关键的取舍】NeutralThreatDriver 只在"挥砍"那一步拦、导航照常执行：她仍会朝怪走过去、绕到能看见的位置再打，追击欲望不受影响；因此原第③步末尾那段内联导航提成了 navigateTo(maid,threat) 供"看不见所以不出手"复用（否则会在墙这边原地罚站）。空袭 groundMelee 同理：看不见就 moveTo(target) 继续走近而不是干等。⑤【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0 仅 16 条既有警告）；过程中修掉一个自己引入的语法错误——NeutralThreatDriver 首次编辑多留了一个右大括号（drive 的收尾括号被替换带了过来），报错是"-source 21 中不支持隐式声明的类"，指向的是结果不是原因，靠括号平衡核对定位；javap -p -c 反汇编构建产物按【方法边界】逐一统计 hasSight 出现次数（避免把常量池引用误判成方法体命中）：NeutralThreatDriver.drive=1（两树）、MaidCombatTacticsBehavior.meleeCounterAttack=2（主目标+逐受害者各一次）、SelfPreservationBehavior.counterAttack=1（收口处覆盖三个分支）、MaidFlightCombatBehavior 的 groundMelee/smashHit 各 1——全部与源码意图一致；build 脚本 verify_jar_classes OK + lang json OK；deploy_all.py 三处 match=True；test_server.py 1201 PASS（Done 3.005s）与 neoforge1211 PASS（Done 0.882s）。⑥【附：本次是纯服务端行为判定，不开新包、不动渲染；hasSight 是 raycast，只在真正要出手时（贴脸/范围内）才调用，不进入每 tick 的扫描热路径】。已部署待实测：墙后站怪时女仆不再对墙挥刀而是绕过去（走位照旧），绕到有视线的那一刻立刻开打；近身横扫/自保反击/空袭范围命中同样不再穿墙；开阔地带正常战斗观感不变。


v1.2.0 鞘翅渲染附魔光泽 + 绑定 TLM「显示背部物品」根因修复（两树同步）：①【需求】"可以让鞘翅渲染出附魔光泽吗？同时绑定 tlm 的显示背部物品来决定是否渲染出鞘翅。"②【附魔光泽：原版本来就有通道，只是传的是"有没有真附魔"】原版 ElytraLayer 调 ItemRenderer.m_115184_(buffer, type, false, stack.m_41790_())（1.21.1 是 getArmorFoilBuffer(..., stack.hasFoil())）——第 4 / 第 3 个参数就是 hasFoil：字节码实证为 true 时额外复合一层 glint 渲染类型（1.20.1 armor_entity_glint / 1.21.1 RenderType.armorEntityGlint），为 false 时直接返回原 buffer。③【改前两树还不一致，且都不亮】1.20.1 两处图层传 chest.m_41790_()（只有真附魔过的鞘翅才亮，普通鞘翅全无光泽）；1.21.1 两处图层硬编码 false（永远不亮）。现在四个图层统一固定传 true = 常亮，与本模组手册/排班表/协议源石的"常亮光泽"口径一致；想改回"只有附魔鞘翅才亮"就把 true 换回 chest.m_41790_()（1.20.1）/ chest.hasFoil()（1.21.1），四个文件里都就地留了注释。④【绑定「显示背部物品」】TLM 女仆配置页那一项 = lang 键 gui.touhou_little_maid.maid_config.show_back_item（两树同名）= 显示背部物品，落点 EntityMaid.getConfigManager().isShowBackItem()；字节码实证它读同步实体数据 EntityMaid.BACK_ITEM_SHOW，在 defineSynchedData 里默认值 iconst_1 = true，所以绑定后默认行为与旧版完全一致，只有玩家主动关掉那一项才会消失（两树 API 同名同签名，javap 实证 MaidConfigManager.isShowBackItem():boolean 均 public）。鞘翅画在女仆背上本就属于"背部物品"——TLM 自己的 LayerMaidBackItem / GeckoLayerMaidBackItem 正是在 isShowBackItem() 这个条件下绘制的，跟着走才是同一口径；四个图层（两树 × Bedrock/Gecko）都在"任务为飞行作战 + 胸甲槽是可用鞘翅"之后追加了这道判定。⑤【验证】两树编译零错（1.20.1 EXITCODE=0；1.21.1 exit=0 仅 16 条既有警告）；javap -p -c 反汇编构建产物，四个 class 逐一确认：含 getConfigManager + isShowBackItem 调用、含 m_115184_/getArmorFoilBuffer 调用且其光泽参数位为 iconst_1、m_41790_/hasFoil 残留为 False（证明是把旧参数换掉而非只加不改）；build 脚本 verify_jar_classes OK + lang json OK；deploy_all.py 三处 match=True；test_server.py 1201 PASS（Done 2.815s）与 neoforge1211 PASS（Done 1.030s）。⑥【注意：附魔光泽是纯客户端渲染，服务端看不到也无需同步；「显示背部物品」是 TLM 已有的同步实体数据，多人下由 TLM 自己同步——本模组不新增任何网络包】。已部署待实测：穿鞘翅/空袭激活后翅膀带附魔流光；女仆配置页关掉「显示背部物品」→ 翅膀消失、重新打开恢复；尺寸/上移 0.7 格/展翅等既有外观不受影响。


v1.2.0 远程空袭新增"近身弹开"【3 格内有怪就给她一个远离怪物的速度矢量】新机制（两树同步）：①【需求】"远程空袭激活后需要再加一个机制：周围三格内出现怪物时女仆被弹开（或强制增加一个远离怪物的速度矢量），防止她在远程攻击时还采用向敌人飞的策略、下落时被直接打死。女仆自己被弹开更加平衡，弹开敌人太超模（也保证狭小空间内敌人仍有命中的可能）。"②【为什么需要】远程空袭的盘旋 faceOrbit 只做"绕半径10格的圈 + 按高度偏差调俯仰"，切向分量天然带"向敌"横向速度，怪物贴过来（或她被地形/烟花推力挤过去）时盘旋会让她贴着怪物转，而她只有20血；起飞那次"背离+向上"的推力只在起飞有效。③【最关键实现点：必须同时改速度+改朝向】滑翔物理在 LivingEntity.travel 的 isFallFlying 分支（反编译实证，1.20.1 在 :1946 起）：每 tick 做一次 `速度 += (视线水平单位向量 × 当前速率 - 速度水平分量) × 0.1`——持续把水平速度往"视线方向"拽。所以只压速度不改朝向：视线仍指敌人，一两 tick 就被这份转向力拉回"朝敌人"，等于没弹；只改朝向不压速度则起步太慢（10%/tick）。两者一起给才是"立刻离开+持续保持"；也正因这份转向力，朝向与推力必须引用同一个威胁，否则自相拉扯（朝A飞被B推）——实现上让 pushAwayFromThreat 把"该背离的威胁"作为返回值交给调用方摆朝向。④【参数取舍】触发半径3格（需求原话）；水平速度0.55格/tick、【覆盖式非叠加】（鞘翅水平巡航约0.4~0.8，0.55 可在一两 tick 内把向敌压反向且仍是连续弧线；叠加会让反复触发时越滚越大变成"被弹飞"）；向上+0.25（抬离近战竖直包线，竖直保留原值再抬不往地面压）；持续30 tick=1.5秒（盘旋向敌分量每 tick 都在拉，只弹一 tick 立刻被拉回）；只在 isFallFlying() 时生效（落地/骑乘不叠加速度）。⑤【只弹女仆不弹怪物】需求明确否定后者：弹开敌人等于远程角色获得持续、无需操作的群体击退，太超模；弹开自己是"用机动换安全"——离开的同时也脱离输出位，且在狭小空间（墙角/洞穴）跑不掉，敌人仍有机会命中。⑥【威胁判据对齐既有口径】只认真威胁：Enemy → 已记仇中立生物（NeutralMob.m_21660_() / neo isAngry()，走 AutoCombatSwitch.neutralAngry 同款防御封装，模组 getter 抛异常不炸 tick）→ 正在锁定她或她主人的任意 Mob（魔改生物靠这条兜住）；并用 FriendlyFireGuard.isFriendly 先排除主人/同主女仆/友军，绝不弹开自己人。⑦【开关与文档】新增配置 combat.flightRangedPush（默认开）+ 配置面板"远程空袭近身弹开"一行 + 四个 lang/*.json 补 config.promaid.combat.flightRangedPush（中英两树）；弹开是高频事件，日志按5秒/女仆限频。⑧【验证】两树编译零错（过程中修掉两处真实问题：NeutralMob 记仇 getter 我一开始误用 m_21672_()、实为 m_21660_()；滑翔判定一开始误用 m_20161_()，反编译证实它是 m_6144_()、与滑翔无关，正确名是 m_21255_()——本项目 MaidFlightKit/EntityFlagInvoker/MaidSwimGlideMixin 全用这个名）；javap -p -c 反汇编构建产物按"真正的声明行"校验：tickRangedAir 同时含 pushAwayFromThreat/faceAwayAndUp/faceOrbit/faceUpForward（弹开分支在、原盘旋分支也在，是二选一而非替换）；pushAwayFromThreat 含 COMBAT_FLIGHT_RANGED_PUSH、m_21255_/isFallFlying、findNearestThreat、applyAwayVelocity、logPushThrottled；applyAwayVelocity 含 set/getDeltaMovement；isThreat 含 Enemy/NeutralMob/记仇方法/目标锁定方法——两树全部命中；deploy_all.py 三处 match=true；test_server.py 1201 与 neoforge1211 均 PASS。已部署待实测：远程空袭盘旋时怪物进3格内女仆立刻往反方向飘1.5秒（日志 [远程空袭] xxx 近身弹开（2.4 格：zombie），5秒限频），脱离后自动恢复盘旋；关掉配置项即完全回到旧行为。


v1.2.0 烟花检测的副手覆盖核查【副手本来就查了；顺带补上"攻击性烟花"漏查的主手】根因修复（两树同步）：①【反馈】"检测烟花火箭的时候似乎没有检测副手。"②【逐条核查：副手确实查了】前提已用字节码钉死——LivingEntity.m_21206_() 反编译 = return this.m_6844_(EquipmentSlot.OFFHAND)，即 m_21206_ 就是副手；EquipmentSlot.values() 的 <clinit> 常量创建顺序 = MAINHAND,OFFHAND,FEET,LEGS,CHEST,HEAD，而 EntityHandsInvWrapper 只保留 Type.HAND 的槽并照该顺序装进 slots 列表 → getHandsInvWrapper() 的 slot 0 = 主手、slot 1 = 副手。四处烟花函数的覆盖面（改动前）：hasFirework 主手✓(m_21205_)/副手✓(m_21206_)/背包✓；takeFirework 主手✓(slot0)/副手✓(slot1)/背包✓；hasExplosiveFirework 主手✗缺/副手✓/背包✓；takeExplosiveFirework 主手✗缺/副手✓/背包✓——即"副手没被检测"在代码里不成立，反倒是四百九十九新增的那对"攻击性烟花"函数漏了主手（方向相反）。另确认消耗路径两次调用（起跳/远战补烟花各一处）走的都是 takeFirework，副手在 slot 1 是第一优先，所以"副手放烟花却不生效"也不是这条链路的问题。③【顺手修掉的真实不一致】hasExplosiveFirework/takeExplosiveFirework 只查"副手+背包"，而 hasFirework/takeFirework 是"主手+副手+背包"三处都认——同一口径两套覆盖面是隐患，具体场景：主手拿着带爆炸的烟花、弩放背包里时，resolveRangedWeapon 会返回背包那把弩（规则是"主手已是本任务合法武器才用主手"，而烟花不是武器），那一刻弹药就在主手上，旧代码只扫副手 → 判成"缺弹药" → 远程空袭不激活、气泡报"缺弹药"。修法：补齐为与 hasFirework/takeFirework 完全一致的三处覆盖面，取用顺序也照抄（副手→主手→背包），"副手优先"这条原版口径保留不变。④【验证】两树编译零错；javap -p -c 反汇编构建产物确认四个函数主手/副手/背包三处全部为 True，takeExplosiveFirework 字节码里可见 iconst_1→getStackInSlot(1)（副手）紧跟 iconst_0→getStackInSlot(0)（主手）最后 takeOneFromBackpack；out/ 的 MaidFlightKit.class 与 jar 内该类 SHA-1 逐一比对相同（防"改源码却打旧字节码"）；deploy_all.py 三处 match=true；test_server.py 1201 与 neoforge1211 均 PASS。⑤【待补充】"检测烟花没查副手"在代码里找不到对应路径（四处都查了副手，见上表）——若现象仍在，请给一句话复现步骤（什么任务、烟花放哪只手、看到什么）：是切任务时气泡报"缺烟花火箭"？是副手那枚没被消耗？是 1.21.1 重锤猛击链路（那查的是风弹不是烟花）？还是别的界面在判断烟花？


v1.2.0 御币被误判"缺弹药"【它是 ProjectileWeaponItem 的子类，被"数箭"那一路吃掉了】根因修复（两树同步）：①【反馈】"检测远程武器御币的时候会显示没有弹药。明明这个武器不需要弹药来驱动的。"②【根因·判据顺序】MaidFlightKit.hasAmmoForRanged 的注释本就把御币归在"不消耗弹药"那一类（弹幕用武器本体），最后也写了 `// 御币 / 三叉戟 / 其它：不消耗弹药 → 放行`，但那条兜底排在 ProjectileWeaponItem 分支之后，而御币恰恰是这个类的子类（ItemHakureiGohei extends ProjectileWeaponItem，两版 TLM 字节码实证）——所以它走不到兜底，落到"数箭"分支 hasArrowFor：副手不是箭→背包按 supportedProjectiles 找箭→再兜底找任意箭→都没有就返回 false→ 判定"缺弹药"→ 远程空袭不激活（missingParts 于是报出"弹药"那一项）。③【三条旁证把路径钉死】(a) 御币自己的弹药谓词 m_6437_()/getAllSupportedProjectiles() 是 Predicates.alwaysTrue()（反编译逐字）——它压根没有"专用弹药"这个概念；(b) 御币 1.20.1 没有覆写 shootProjectile/m_6394_（空实现），说明它不走原版箭矢通道，硬塞箭进去也没意义；(c) TLM 自己的 TaskDanmakuAttack 全部成员里没有任何 hasArrow 之类的弹药判据（javap 实证），只有 hasGohei（查的是"有没有御币"）。即原版/TLM 都没有"御币要箭"这个前提，是我们自己那条门禁的判定顺序把它卷进去了。④【改法】判据顺序改为（两树同步）：枪械 → 御币 → 三叉戟 → 弩 → 其它弹射武器 → 放行。御币新增判定用 TLM 自带的 ItemHakureiGohei.isGohei(stack)，与开火路径 MaidFlightRangedTask#isGohei 同一口径（门禁与开火必须一致，否则会出现"判定有弹药却没按弹幕打"或"能打弹幕却不起飞"）；三叉戟本来就不是 ProjectileWeaponItem、走不到那条分支，但显式写出来，让门禁与开火路径三条"无需弹药"通道逐条对齐、以后改动不漏；MaidFlightKit 新增 public 助手 isGohei(ItemStack)。只动判定顺序，不动伤害/弹幕/激活条件——御币该走 shootDanmaku 照样走（那条分流本来就在 shootArrow 之前，顺序是对的），这次修的是门禁那一侧。⑤【验证】两树编译零错；javap -p -c 反汇编构建产物按常量池判定调用序列校正：21:GunCompat.isGun、33:isGohei、45:instanceof TridentItem、57:instanceof CrossbowItem、82:instanceof ProjectileWeaponItem(→hasArrowFor)，两树都实测 isGohei 与 TridentItem 都排在 ProjectileWeaponItem 之前（三项顺序断言全部 True），isGohei 方法已就位；deploy_all.py 三处 match=true；test_server.py 1201 与 neoforge1211 均 PASS。已部署待实测：远程空袭持御币时不再报"缺弹药"、能正常起飞并按弹幕开火（与她自己用弹幕任务时一致）；弓/弩仍照旧要求箭（弩还认攻击性烟花）；不带御币的配置不受影响。


v1.2.0 空袭适配拔刀剑【TLM 的拔刀斩门控在她的"地面攻击任务"上，空袭任务永远进不去】根因修复（两树同步）：①【反馈】"空袭技能目前不适配拔刀剑"。②【根因一·门控】TLM 对拔刀剑的支持只有一处：EntityMaid.swing()（1.20.1 m_6674_）开头调用 SlashBladeCompat.swingSlashBlade(maid,主手)，而那个方法体第一步就是 if (isSlashBladeItem(stack) && maid.getTask().getUid().equals(TaskAttack.UID)) —— TaskAttack.UID 的静态初始化字节码是 new ResourceLocation("touhou_little_maid","attack")；我们的飞行任务 UID 是 maid_smart:flight_combat / flight_ranged，这个 equals 恒 false → 女仆在空袭里挥刀，拔刀斩一次都不会触发（不是"触发得少"，是根本不进那段代码）；同一个女仆换成地面攻击任务却能触发。③【根因二·缺倍率】MaidFlightCombatBehavior#hitOne 的伤害是 ATTACK_DAMAGE×暴击1.5+附魔，而拔刀剑自己的口径（AttackHelper.calculateTotalDamage 反编译）是 (ATTACK_DAMAGE+横扫加成+段位加成+附魔加成)×comboRatio×getSlashBladeDamageScale(攻击者)×SLASHBLADE_DAMAGE_MULTIPLIER，getSlashBladeDamageScale 读的就是拔刀剑注册的 SLASHBLADE_DAMAGE 属性（默认1.0、随练度/段位成长）——我们那条通道整段没有这个乘数。④【改法】新增 com.maidsmart.combat.SlashBladeCompat（全反射），在我们自己的挥刀点复刻 TLM 同一段调用：roll=random.nextInt(60)-30；AttackManager.doSlash(maid, roll, Vec3.ZERO, mute=false, critical=false, comboRatio=1.0, KnockBacks.smash)；再记一次 BLADESTATE.setLastActionTime(level.getGameTime())。doSlash 生成的 EntitySlashEffect 自己按拔刀剑完整公式结算伤害（EntitySlashEffect.tick→AttackManager.areaAttack→AttackHelper.attack），所以根因二那条缺口被同一调用一并补上；刻意不再往 hitOne 里乘倍率（否则变成"原版伤害+拔刀斩伤害"叠乘，比玩家与 TLM 女仆都强）。挂载点两处：smashHit（收翅俯冲，空袭主体）与 groundMelee（落地后贴身近战退路——只补俯冲会留下"落地就变哑巴"的半截体验）；全反射的原因：compile_promaid.txt / compile_neo.txt 的 classpath 里都没有 slashblade（已核对），模组也不保证装，直接 import 会编译不过；符号在 ModList.isLoaded("slashblade") 之后一次性缓存，缺模组时一次布尔判断即返回。doSlash 在 AttackManager 里有 7 个重载，按参数名盲选会静默失败（IllegalArgumentException），故按精确签名 (LivingEntity, float, Vec3, boolean, boolean, double, KnockBacks) 定位。⑤【1.21.1 侧额外实情】1.21.1 的 TLM 1.5.3 完全没有拔刀剑兼容代码（jar 内常量池扫 slashblade 零命中，1.20.1 那份 SlashBladeCompat/SlashBladeRender 在 neo 版不存在）——即 1.21.1 上"女仆持拔刀剑"本来就没有官方联动、连地面攻击任务都不触发拔刀斩；本兼容层挂在我们自己的挥刀点上、不依赖 TLM 门控，所以在 1.21.1 上是"同时补上地面与空袭"的净新增。另：1.21.1 的拔刀剑 2.0.7 已无 CapabilitySlashBlade 类（capability 包改成 BladeStateAccess/SlashBladeDataComponents），故状态入口走 BladeStateAccess.of(ItemStack) 静态方法。⑥【主人免伤】doSlash 的伤害来源是 damageSources().mobAttack(女仆)（AttackHelper.attack 非玩家分支，反编译实证 attacker.m_269291_().m_269333_(attacker)，m_269333_=mobAttack）→ 加害者就是女仆 → 已有的 FriendlyFireGuard 会拦掉主人/友方，无需新增免伤代码；另有两层天然保险：拔刀剑默认 pvp_enable=false、friendly_enable=false，其目标筛选器 TargetSelector$AttackablePredicate 只打 Enemy。⑦【验证】两树编译零错（新增类需重跑 gen_compile.py/gen_compile_neo.py 才进编译清单，首次报"找不到符号"即此因，已处理）；对真实 jar 起 JVM 逐个校验反射符号（initialize=false 不触发 mod 静态初始化）：1.20.1 11/11、1.21.1 5/5 全通过，含 doSlash 重载数=7 且精确命中 7 参那条、ItemSlashBlade 父类=SwordItem、状态入口两版各自正确；javap -p -c 反汇编构建产物确认 SlashBladeCompat 三方法就位、MaidFlightCombatBehavior 的 smashHit 与 groundMelee 各含一次 invokestatic swingSlash（两树 2 处）；1.20.1 测试服装着拔刀剑（启动日志实证 slashblade 1.9.65 + mrqxs_Slashblade_Core-1.4.1），回归 PASS 且启动无异常；deploy_all.py 三处 match=true；test_server.py 1201 与 neoforge1211 均 PASS。已部署待实测（1.20.1）：空袭持拔刀剑时俯冲那一记与落地近战都会带出拔刀斩（有伤害、有刀光）；持其它武器不受影响；没装拔刀剑的实例零开销、行为与之前一致。注：1.21.1 侧客户端实例里拔刀剑本体是 .disabled 状态（玩法层面无法在客户端实测），但 NeoForge 测试服是启用状态（mods 下两个拔刀剑 jar 均无 .disabled），本模组 SlashBladeCompat 在该服上随模组加载、启动与回归全程无异常——即"装了拔刀剑的 1.21.1 服务端"这条路径实测过；反射符号另按 2.0.7 真实 jar 逐个校验。


v1.2.0 弩无法发射攻击性烟花火箭【飞行远战任务的武器分流里压根没有"弩"这一路】根因修复（两树同步）：①【反馈】"在使用弩的时候没有办法发射攻击性烟花火箭"。②【根因】MaidFlightRangedTask.performRangedAttack 的分流是 枪械→三叉戟→ProjectileWeaponItem→御币→shootArrow，弩属于 ProjectileWeaponItem 因此必然落到 shootArrow，而 shootArrow 只认 ArrowItem：1.21.1 的 findAmmo 第一站是 maid.getProjectile(主手)，TLM 的 EntityMaid.getProjectile 在副手是烟花时就把副手那叠烟花交出来（TLM 给女仆做弩的官方口径，字节码实证），拿回来却被 instanceof ArrowItem 判掉→退化成一支普通箭，最后 arrowStack.shrink(1) 还把这枚烟花消耗掉了（表现为"烟花少一枚、飞出去一支箭"）；1.20.1 的 findArrow 第一站是副手，同样只认 ArrowItem，副手烟花判不过、背包再没箭就返回空→shootArrow 直接 return，一枪都放不出来。原版（CrossbowItem.createProjectile 有专门的烟花分支）和 TLM（TaskCrossBowAttack.hasAmmunition 把副手烟花算弹药，1.21.1 还有 MixinCrossbowItem 给女仆做弩的瞄准）本来都支持，唯独我们自己那条飞行远战通道漏了。③【必须处理的撞车】副手烟花既是"原版口径的弩弹药"又是"本模组的飞行燃料"（MaidFlightKit.equip 副手槽 + MaidFlightCombatBehavior#launchFirework 优先消耗副手），而【不带爆炸（Explosions）的烟花打出去是 0 伤害】（FireworkRocketEntity.dealExplosionDamage 开头就是 if (!this.hasExplosion()) return，字节码实证），拿它开火等于白烧一份飞行燃料+空中掉高风险——所以弩的弹药只认"攻击性"烟花。④【改法】MaidFlightKit 新增 isExplosiveFirework（1.20.1 读 NBT Fireworks.Explosions，与 FireworkRocketEntity 自己的读法一致；1.21.1 读组件 DataComponents.FIREWORKS.explosions()）、hasExplosiveFirework（只查不取）、takeExplosiveFirework（取1枚：副手优先=原版口径，背包路径只抽1枚绝不整叠拿走）；弹药门禁 hasAmmoForRanged 的弩分支同步改为"有攻击性烟花 或 有箭"（门禁与开火路径必须同口径，否则会出现"判定有弹药、起飞后一发打不出来"的死角）；MaidFlightRangedTask 新增弩分支并排在 shootArrow 之前，shootCrossbowFirework 逐点对齐原版+TLM：弹体 = new FireworkRocketEntity(level, 弹药, 女仆, x, 眼高-0.15, z, true)（原版 createProjectile 烟花分支逐字一致，末位 true = shotAtAngle → 命中即爆、不给射手推力，不是给滑翔加速的挂载烟花）、瞄准取双方眼高差 + setNoGravity(true) 直线飞、速度 clamp(距离/10,1.6,3.2)、散布 1-clamp(距离/100,0,0.9)（TLM 女仆弩口径）、耐久扣 3（原版烟花分支，射箭是 1）、音效 CROSSBOW_SHOOT（音高带±0.4随机）、顺序=先取弹药→立即建弹体入世界→再扣耐久（取弹与入世界之间只有算术、没有任何可能失败的分支，不存在"烟花扣了却打不出去"的窗口）。⑤【主人免伤】烟花是爆炸伤害、DamageSources.fireworks(烟花, 女仆) 的造成者就是女仆，会被已有的 FriendlyFireGuard 拦下，无需新增免伤代码。⑥【验证】两树编译零错；javap -p -c 反汇编构建产物确认 MaidFlightRangedTask 有 instanceof CrossbowItem + shootCrossbowFirework 且排在 shootArrow 之前、MaidFlightKit 三个新接口就位且 isExplosiveFirework 被三处引用（3/3）、烟花判定路径分别引用 Explosions/FIREWORKS；deploy_all.py 三处 match=true；test_server.py 1201 与 neoforge1211 回归均 PASS。已部署待实测：飞行远战下弩+攻击性烟花→打真烟花（命中即爆、有伤害）；弩+只有箭→照旧射箭；弩+只有普通烟花（无爆炸）且没箭→判为缺弹药不开火，不会烧掉飞行燃料。


v1.2.0 悬空/滑翔禁搭方块【收紧：滑翔被放行、普通悬空只拦深坠落】根因修复（两树同步）：①【反馈】"女仆似乎还是会在悬空和滑翔状态下搭方块"。②【根因·两处漏口，都在 MaidPlaceGuard.blocked 的口径里】四百四十三的判定是"未落地 &&（重锤跃起 || 坠落距离 ≥ 落地水触发高度）"：(a) isFallFlying() 与"站在地面/水里/骑乘"并列在【放行名单】里——空袭期间她本来就在滑翔，所以"滑翔中搭方块"被整条放行；(b) 普通悬空只拦"坠落距离 ≥ combat.waterFallDistance（默认 6 格）"，3~5 格的悬空下落照样放行；而滑翔中 LivingEntity.travel 每 tick 调 Entity.m_245125_() 把 fallDistance 钳在最多 1.0，所以就算把滑翔从放行名单拿掉、只留那条阈值，滑翔期也永远不会触发——必须单独判滑翔位。③【为什么空袭期真的会搭】搭路行为 BridgeUpBehavior 是 core 245（任何 activity 都跑），自保虽在飞行任务下被硬闸关掉，搭路却没有；空袭期间只要脑内暂无攻击目标，搭路就会启动并朝主人/前方缺口铺块——正是玩家看到的"悬空搭方块"。④【改法】MaidPlaceGuard.blocked 重写：未落地且不在水/岩浆/骑乘时，(a) 滑翔或飞行作战空中（MaidFlightKit.isFlightAirborne，含收翅猛击/被击落盘旋）→ 禁；(b) 【1.21.1】重锤跃起中（MaidMaceSmashBehavior.isAirborne）→ 禁；(c) 正在下落（fallDistance > 0）→ 禁（旧版要坠够 6 格才禁）。上升段 fallDistance == 0 不受影响，所以站在地面/浅跳垫脚、搭路把自己顶上去这些正常链路照常工作。⑤【覆盖面】四个搭方块模块（自保搭高·搭路·挖矿垫脚·伐木垫脚）的 takeBuildBlock 全部先问闸口（逐条核对：两树各 4/4 就位），被禁即返回 null → 不消耗方块、不改世界；取料失败气泡同样静默。⑥【不拦】落地水/细雪（WaterClutchBehavior 直接放水，是本模组防摔死的保命机制）、照明光块、农耕播种、插火把（要求目标格脚下有实心支撑面且贴着主人）、AI 工具 smart_place（玩家显式指令）。⑦【验证】两树编译零错；javap -c 反汇编构建产物确认 MaidPlaceGuard 里已是 isFlightAirborne + isAirborne + fallDistance>0 三段，且 isFallFlying/m_20161_ 已彻底消失；deploy_all.py 三处 match=True；test_server.py 1201 与 neoforge1211 回归均 PASS。已部署待实测：空袭（近战/远战）飞行期间不再搭方块；正常挖矿/伐木垫脚、搭路、浅跳垫高不受影响。


v1.2.0 1.21.1 女仆模型【同 domain 的模型包只活一个，其余整包模型从选择界面消失】根因修复（两树同步）：①【反馈】"1.21.1 版本女仆选择模型里面出现了问题……有很多模型被隐藏了"。②【根因·TLM 字节码 + 磁盘双实证】模型包 id 就是 assets/ 下的域名目录名（CustomPackLoader.loadMaidModelPack → pack.decorate(domain)）；MaidModels.sortPackList() 先对 DefaultPackConstant.MAID_SORT = [touhou_little_maid, touhou_little_maid_old, touhou_little_maid_seihou, geckolib, authors_and_credits, minecraft_15th] 的每个 id 用 findFirst() 只收第一个包，再用 !MAID_SORT.contains(id) 过滤后半段——同 id 的第二个包两头都不在，直接从 packList 里消失；而 GUI 是按包遍历 getModelList() 的（搜索模式的全量表也按包拼），于是整包模型一起"被隐藏"。③【为什么只有 1.21.1】磁盘实测：1.21.1 实例 tlm_custom_pack 里有【两个】domain=geckolib 的女仆包（maidspell_geckolib_models-1.0.0 的 3 个 + touhou_little_maid-1.0.0 的 27 个），客户端日志实测 maidspell 那个先加载 → 内置 geckolib 包（声明 27 个 / 注册 41 个 id：winefox 全家族 + zhiban 系列 + hailuo_new_year + kluonoa + foxmaid + rice_cake_fox）整包从界面消失；1.20.1 实例只有内置那一个 geckolib 包，实测丢包 0（对照组成立）。④【改法】新增客户端 mixin MaidModelPackSortMixin（两树同源，登记在 mixins.promaid.json 的 client 段）：@Inject(sortPackList, HEAD, cancellable) 用"先按 MAID_SORT 名单顺序收齐同 id 的全部包（保持加载顺序）、其余包按 id 排序跟在后面"替换原实现，唯一语义变化是"不再丢弃"。⑤【实测证据】SortDemo 直接加载 TLM jar 里的 DefaultPackConstant / CustomModelPack 跑两套算法：1.21.1 输入 7 包 → 原版 6 包存活（丢掉内置 geckolib 的 27 个声明模型），本模组算法 7/7、丢包 0；1.20.1 输入 6 包 → 两套算法都是 6/6。⑥【未动】椅子模型（ChairModels.sortPackList + CHAIR_SORT）是同一份逻辑、同样的坑，但用户包内没有第二份椅子域名（磁盘实测），本次不改以免扩大改动面。⑦【验证】两树编译零错（229 / 231 个源文件）；jar 重建并逐项核对（新类已入包 + client 段已登记 + javap 确认 getPackList / MAID_SORT / getId / ci.cancel 字节码）；deploy_all.py 三处 match=True；test_server.py 1201 PASS（Done 3.199s）、neoforge1211 PASS（Done 0.937s）；把本条 changelog 打进 jar（重新打包）后复测仍 PASS（Done 2.847s / 0.976s）。已部署待实测：打开女仆模型选择界面，geckolib 页应能看到 27 个内置模型（winefox 全家族等）。


v1.2.0 空袭鞘翅【一穿上就渲染，不再需要先起飞一次】：①【反馈】1.20.1 版本女仆必须起飞一次才会把鞘翅渲染出来，应该一穿上就渲染。②【渲染层本身没问题】两树 LayerMaidElytra / LayerMaidElytraGecko 的显示条件只有三件：isInvisible==false && MaidFlightKit.isFlightTask(maid) && isUsableElytra(胸甲槽)——没有任何"必须离地/必须滑翔"的额外条件，故"穿上即渲染"在渲染侧本来成立，问题在于有没有被穿上。③【根因·代码路径实证】全局搜索确认胸甲槽的唯一写入者是 MaidFlightKit.equip()（两树各一处 m_8061_(EquipmentSlot.CHEST,ely) / setItemSlot(EquipmentSlot.CHEST,ely)），而它此前只在飞行作战行为里调用，那个 tick 开头是 currentTarget(maid)==null 就 endFlightSafely+return——没敌人时永远走不到 equip。因果链：切到空袭任务→还没遇敌→tick 在 target==null 处返回→equip 从未执行→胸甲槽无鞘翅→渲染层看不到→直到第一次接敌起飞（equip 终于跑一次）才出现。MaidToolAutoEquipBehavior（core 行为，每 tick 都跑）虽会进入飞行任务分支，但源码注释明写"本分支只提示、不擅自换装（换装仍由 MaidFlightKit.equip 在行为里做）"，故没兜住。④【改法】两树 MaidToolAutoEquipBehavior 的飞行任务分支里，先调一次 MaidFlightKit.equip(maid) 再 notifyNotReady。安全性（已核对源码）：equip 幂等——三段都先判是否已就位（胸甲已可用鞘翅/主手已是本任务合法武器/副手已是烟花），就位即整段跳过零开销，故每 tick 调用不会反复换装；换下的旧装备走 giveBack 塞回背包空格、无空格才落地(m_5552_)，不丢物品；与战斗行为里那次调用互不冲突（同一份幂等逻辑），战斗行为保留原调用（它还要用返回值判三件是否齐备）。⑤验证：两树编译零错；字节码校验两树 MaidToolAutoEquipBehavior 的 checkExtraStartConditions 依次出现 isFlightTask → equip → notifyNotReady；deploy_all.py 三处 match=True；test_server.py 1201 PASS(2.734s)、neoforge1211 PASS(0.930s)。⑥待实测：把女仆切成空袭任务（附近无敌人）鞘翅应立刻出现在背上、不再需要先起飞一次；切回其它任务鞘翅应随任务切换脱下（equip 只在飞行分支调）；接敌后滑翔/收翅的展翅表现不受影响（渲染层展翅逻辑本次未改）。


v1.2.0 远程空袭【激活时检测弹药，没弹药就不起飞】：①【反馈】远程空袭激活时还需要检测一下有没有对应的弹药，否则没必要起飞。②【现状】isModeActive 原来只判鞘翅+武器+烟花三件，远程空袭会在背包一支箭/一颗子弹都没有时照常起飞、升空盘旋却一发都打不出来——白烧烟花、白暴露在敌方火力下。③【关键·不能一刀切，TLM 反编译实证】远程武器里只有一部分消耗弹药：弓 TaskBowAttack 有 hasArrow（ItemsUtil.findStackSlot(inv, bow.m_6437_())）、弩 TaskCrossBowAttack 有 hasAmmunition（箭 或 烟花火箭）、而御币 TaskDanmakuAttack 与三叉戟 TaskTridentAttack 都【没有】弹药门禁（弹幕不耗弹药、投出去的是武器自身副本）；枪械另有既有判据 GunCompat.hasGunAndAmmo（已正确豁免卓越前线能量武器的内部充能）。故本模组门禁只对真吃弹药的武器生效：弓/弩认箭、枪认子弹，御币/三叉戟/能量枪一律放行——若对它们也强加门禁会造出更糟的新 bug（持御币/三叉戟的女仆永远判定缺弹药、永远不起飞）。④【改法】新增 MaidFlightKit.hasAmmoForRanged（两树），箭的判据与开火路径 MaidFlightRangedTask#findArrow/findAmmo 的三层一一对齐（副手箭 → 武器 supportedProjectiles 认可的弹药 → 背包任意箭），保证激活检测与能否打出去同口径；isModeActive 近战不变、远程追加弹药要求；missingParts 远程缺弹药时额外报出「弹药」，否则玩家只看到三件齐了却没起飞、不知为何。⑤【实现时规避的陷阱】isModeActive 在 equip() 之前调用，那一刻主手可能还是空的（弓和箭都在背包），直接用主手判弹药会把这配置误判成缺弹药→永远不激活；故新增 resolveRangedWeapon 逐字照抄 equip() 的选武器规则（主手已是本任务合法武器就不换，否则取背包第一把合法的）再判。⑥验证：新用符号逐个 javap 核对编译期真实 jar 存在（m_6437_/getAllSupportedProjectiles/CrossbowItem/FireworkRocketItem/ArrowItem）；两树编译零错；字节码校验两树 MaidFlightKit 均含 hasAmmoForRanged/resolveRangedWeapon/hasArrowFor 且常量池含 ProjectileWeaponItem/CrossbowItem/FireworkRocketItem/GunCompat.hasGunAndAmmo 引用与缺件文案「弹药」；deploy_all.py 三处 match=True；test_server.py 1201 PASS(2.735s)、neoforge1211 PASS(0.783s)。⑦待实测：远程空袭空手或无箭时不起飞并报「缺弹药」，放箭后正常起飞开火；御币/三叉戟/枪械（含能量武器）无箭也照常激活；弩+副手烟花算有弹药；近战空袭不查弹药不受影响。


v1.2.0 空袭落地水/雪【改成重锤同款特殊落地缓冲】+ 新增空袭免摔开关：①【反馈】女仆空袭时的落地水/雪需要改正为和重锤状态下一样的特殊落地水，一直防止女仆摔死；同时加一个配置开关可直接关掉空袭状态下的摔落伤害（默认关闭）。②【先说清重锤那套】重锤跃起落地时行为置位 FORCED_CLUTCH，由 WaterClutchBehavior（core 240，在重锤 235 之后 tick）同 tick 在落点强制放一格水/细雪——不看 fallDistance 阈值、不看抑制条件（猛击加成此刻已结算完，放水只负责不摔）。③【空袭根因·两树不同】(a) 1.21.1：MaidMaceSmashBehavior.isAirborne = isLeaping || MaidFlightKit.isFlightAirborne 把飞行也并进"空中"，于是落地水把它当"重锤跃起中"直接 return（让位保住 fallDistance），但飞行侧从不置位 FORCED_CLUTCH → 空袭落地完全没有落地水/雪、纯硬摔；(b) 1.20.1：该树无重锤类、通用落地水仍在跑，但通用逻辑按 fallDistance>=阈值 触发，而滑翔中 LivingEntity.travel 每 tick 把 fallDistance 钳在 ~1.0（本模组多处已实证）、远低于默认阈值 3 → 空袭里几乎不触发。④【改法一·两树】把"重锤跃起让位"与"空袭落地缓冲"拆开判，不再共用一个 isAirborne：跃起中（isLeaping）仍完全让位（保住 fallDistance 给猛击）；重锤落地帧（consumeForcedClutch）先判（落地那 tick 两标志同时为真，顺序不能反）原语义不变；空袭（新增）的落点帧改由落地水自己判——未落地+正在下坠+脚下 1~2 格内有地面（照抄重锤 isNearGround 同款判据），不看 fallDistance 阈值（滑翔钳在 ~1.0，按阈值判永不触发，这正是 1.20.1 旧版接不住的原因），有水桶（非下界）/细雪桶即强制放一格，与重锤落地帧完全一致。⑤【改法二·新开关】combat.flightNoFallDamage（默认关，用户明确要求）：复用 MaidFlightWallGuard（原负责飞行免疫鞘翅撞击伤害），同套 message_id 手法新增 fall 分支；fall.json 的 message_id 两版都是 "fall"（1.20.1 与 1.21.1 版本目录 jar 逐个开包核对 68/79 个 damage_type json，与既有 flyIntoWall 同样跨版本稳定）；只作用于飞行任务、不影响普通坠落；触发有日志（搜「空袭免摔」）。面板在"生存与复活"分区紧跟"免疫鞘翅撞击伤害"一行；四个语言文件同步（空袭免疫摔落伤害 / Air Raid: Immune to Fall Damage）。⑥验证：两树编译零错；字节码校验（javap -c 反汇编构建产物）两树 Guard 均含 String "fall" 比较 + COMBAT_FLIGHT_NO_FALL_DAMAGE 字段 + setCanceled（与 flyIntoWall 分支并列）、两树 WaterClutchBehavior 均含空袭分支与 flight clutch trigger 日志；四个 lang JSON 合法；deploy_all.py 三处 match=True；test_server.py 1201 PASS(2.990s)；NeoForge 首跑 TIMEOUT 已定位为新增配置项触发的一次性配置迁移（日志尾部 promaid-common.toml is not correct. Correcting、目录出现 promaid-common.new.tmp.toml），核对迁移后文件确认 flightNoFallDamage=false 已写入且旧键保留，重跑即 PASS(0.996s)，残留 .new.tmp.toml 已清理。⑦待实测：两种空袭高空落地有水桶/细雪桶时应自动放一格接住不摔伤（搜 flight clutch trigger）；收翅猛击伤害加成不应因放水丢失（放水在猛击结算之后）；开关打开后空袭高空摔下完全不掉血（搜空袭免摔）；普通坠落不受该开关影响。


v1.2.0 1.21.1【pack_format 写成了 1.20.1 的值】修复（15 → 34）：①【背景】这是此前主动标出但当时未获授权修改的瑕疵。②【现状】四处 pack.mcmeta（两树各一 + 对应 staging_* 构建产物）原本全部写着 15；构建真源是 promaid_src(/_neo)/pack.mcmeta（build_promaid*.py 第 71/76 行 copy2），staging_* 每次构建会被覆盖。③【1.21.1 正确值 34，三条独立证据一致】(a) 最权威：javap -v 读 vanilla net.minecraft.SharedConstants 的 ConstantValue —— RESOURCE_PACK_FORMAT ConstantValue: int 34、DATA_PACK_FORMAT ConstantValue: int 48（1.20.1 侧字段名被混淆读不到符号名，但同一常量表资源/数据格式均为 15）；(b) TLM 官方 pack.mcmeta：1.20.1 版=15、1.21.1 版=34；(c) 同目录其它 1.21.1 mod：TACZ=48，而写 15 的 Neat(标 1.21)、IMBlocker(标 1.20.6-1.21.8) 都是跨多版本打包、用旧值兼容更宽区间，不能作 1.21.1 单版本目标值。④【修法】只改 promaid_src_neo/pack.mcmeta 的 15→34；1.20.1 那棵本来就是对的、不动。⑤【影响】pack_format 只参与资源包版本匹配校验（不匹配时给"为旧/新版本制作"警告、影响默认启用），不参与任何代码逻辑，不影响玩法/存档/本次其它修复。⑥验证：构建后逐个开包核对 8 个副本（构建产物、三处部署点、两处 staging、两处源码）全部符合预期（1.20.1 系=15、1.21.1 系=34）；首次核验发现"已部署的 1.21.1 仍是 15"（改源码并重建后尚未重新部署），部署后复验通过；deploy_all.py 三处 match=True；test_server.py 1201 与 neoforge1211 均 PASS。⑦待实测：1.21.1 客户端加载时不应再对本模组资源包报"为其它版本制作"警告；Gecko 皮肤列表应与修复前一致（本改动不涉及皮肤资源）。


v1.2.0 飞行攻击【附魔效果核查与补齐】+ 两模式改名：①【反馈】看飞行时攻击能不能触发附魔，不能就做；顺带把两个模式改名为 近战空袭 / 远程空袭。②【近战核查】两树已用 m_44833_（锋利/亡灵/节肢伤害加成）、m_44914_（火焰附加）、m_44823_（荆棘等 doPostAttack），1.21.1 更已走现代 API（modifyDamage/modifyKnockback/doPostAttackEffects）；唯一缺口是 1.20.1 击退量被写死 1.0、没读击退附魔与 ATTACK_KNOCKBACK 属性——已按原版 Mob.m_7327_ 算式补为 (属性+m_44894_)*0.5。③【远战·弓弩 1.20.1 全缺、1.21.1 本就生效】根因（反编译实证）：1.20.1 ArrowItem.m_6394_(level,stack,shooter) 只有三参、不收武器，给箭上附魔是原版 BowItem.releaseUsing 手工写的：Power→m_36781_(m_36789_()+lv*0.5+0.5)、Punch→m_36735_(lv)、Flame→m_20254_(100)，我们旧版造完箭直接射出、这三条从未被调用；1.21.1 则因 createArrow(level,stack,shooter,weapon) 多一个武器参数、存进 firedFromWeapon，命中时由 modifyDamage/modifyKnockback 读取（onHitEntity 374 行 / doKnockback 457 行，数据驱动且无 instanceof Player 门控），我们早已传入 weapon 故一直生效。修法：1.20.1 shootArrow 照抄原版 BowItem 三行（算式逐字一致），两树再补无限附魔（f_44952_/INFINITY，有则不吃箭）。④【远战·御币三个专属附魔两树都缺】TLM 阻碍/神速/末影杀手注册在 GOHEI 类别上（InitEnchantments：EnchantmentCategory.create("gohei", item->item instanceof ItemHakureiGohei)），自身无行为，作用全靠 TaskDanmakuAttack 读等级后传给弹幕 API：speed*=(speedyLevel+1)、setImpedingLevel(impedingLevel)、setHurtEnderman(endersEnderLevel>0)；旧版整段跳过（注释原写"保持与旧版一致：不吃这三个"），本次按用户要求补齐，算式与 TLM 一致（1.20.1 用 EnchantmentHelper.m_44843_，1.21.1 用 EnchantmentKeys.getEnchantmentLevel）。⑤【改名】flight_combat：飞行近战/Flight Melee（1.21.1 英文原为 Flight Combat）→ 近战空袭/Melee Air Raid；flight_ranged：飞行远战/Flight Ranged → 远程空袭/Ranged Air Raid。改了四个 lang 的名称值+desc 首句称呼、两个任务的 getMaidActionSummary()（TLM 任务列表 UI）、运行时日志标签；刻意不改任务 UID（maid_smart:flight_combat / flight_ranged，写进存档的内部标识，改了会让已存档女仆任务失效）；配置里的"飞行作战"是伞形总开关覆盖两模式，不是模式名，保持原样；历史 changelog 按旧名的记录不改写。⑥验证：新用符号全部先以 javap 对着编译期真实 jar 逐个核对存在（f_44988_/f_44989_/f_44990_/f_44952_、m_36781_/m_36789_/m_36735_、m_44894_、f_22282_、InitEnchantments.IMPEDING/SPEEDY/ENDERS_ENDER、EnchantmentKeys.*、setImpedingLevel）；两树编译零错；字节级校验 17 项全 OK（直接读 jar 内 .class 引用）+ 四个 lang 均为新名无旧名残留；deploy_all.py 三处 match=True；test_server.py 1201 与 neoforge1211 均 PASS。⑦待实测：1.20.1 附魔弓（力量/冲击/火矢/无限）箭伤更高/命中击退/点燃/不掉箭；附魔御币（阻碍/神速/末影杀手）弹幕更快/有减速/能打末影人；附魔剑（击退）命中打退怪；任务列表显示近战空袭/远程空袭。


v1.2.0 鞘翅渲染【尺寸改为与玩家等大 + 整体下移 0.3 格对齐肩线】：①【反馈】鞘翅改成跟玩家同样大小；顶端要对准 winefox 建模的肩膀，目前视觉上需再降低约 0.3 格。②【落点】两树各两个图层文件（LayerMaidElytra=Bedrock 模型女仆、LayerMaidElytraGecko=GeckoLib 模型女仆），四文件常量原本一致。③【改动】SCALE_FACTOR 0.85f→1.0f（尺寸回到与玩家等大，即 0.9375=原版 PlayerRenderer 缩放系数）；LIFT_BLOCKS 1.0f→0.7f（上移量由 1.0 格减到 0.7 格，净降 0.3 格）。④【为什么减在上移量】实测四百八十记着当时要求是「整体上移 1 格」，用户现在说「再降低 0.3 格」，方向无歧义。⑤【为什么顶端由 LIFT_BLOCKS 单独决定·数据实证】扫全部 18 个 winefox 族模型 JSON：ElytraLocator 枢轴 y=30.9644px，肩线 LeftArm 枢轴 29.1 / 手臂方块顶 30.436px，换算成格后同高——挂点本身就落在肩线上，故顶端高度只由上移量决定。⑥【为什么改尺寸不挪顶端】反编译原版 ElytraModel.m_170539_：左右翅 PartPose 枢轴为 (±5,0,0)，枢轴就在 y=0，scale(k) 只缩放翅膀不移动枢轴 → 尺寸与顶端位置解耦，两个常量可独立调。⑦【变换链坐实（本次新证）】RenderUtils.prepMatrixForLocator 全部按 /16 换算 → 锚点后单位是格、Y 朝上；IGeoRenderer.renderEarly 直接 scale(width,height,width) 且 GeckoEntityMaidRenderer 覆写 getWidthScale/getHeightScale 返回 renderEntityScale，而 renderEarly(render 第82行) 与图层调用(第246行)之间没有 push/pop 包住该 scale → 图层矩阵带实体缩放，「除以 modelScale 才是世界格」成立；winefox 的 render_entity_scale=0.65，不除缩放 0.3 格会被放大成约 0.46 格。⑧验证：两树编译零错；两 jar 构建成功；字节级校验四个 .class 常量池均为 1.0f+0.7f+0.9375f 且旧 0.85f 完全消失；deploy_all.py 三处 match=True；test_server.py 1201 与 neoforge1211 均 PASS。⑨待实测：飞行作战穿鞘翅看翅膀大小是否与玩家一致、顶端是否落在 winefox 肩线；若仍偏，只调 LIFT_BLOCKS，每 0.1 格即 ±0.1。


v1.2.0 飞行远战【御币扇形范围攻击丢失】修复（两树同步）：①【反馈】御币的攻击本是扇形范围攻击，但飞行远战期间失去该功能。②【根因·CFR 反编译实证】DanmakuShoot 有两条不同形态的发射路径：aimedShot 只造 1 发直线瞄向目标；fanShapedShot 按 setFanNum(n)+setYawTotal(弧度) 张开扇形（内部：以目标方向为轴，yaw 从 -yawTotal/2 起每发递进 yawTotal/(fanNum-1)，逐发 Vec3.yRot(yaw)）。而我们的飞行远战御币分支是无条件 aimedShot 单发，整段扇形规则从未被调用。③【TLM 原始规则——不是永远扇形】TLM TaskDanmakuAttack.performRangedAttack 按周围同种敌人数分档：≤1 且无多重射击→aimedShot 单发；≤1 且有多重射击→3 发/0.2618 rad(15°)；≤5→8 发/1.0472 rad(60°)；>5→32 发/2.0944 rad(120°)。单挑单发、群战撒开是有意设计，旧实现把整张表丢了。同种敌人判据=可攻击+同 EntityType+有视线（同 TLM enemyEntityTest）。④【修法】shootDanmaku 按上表重写并对齐 TLM 原式的伤害/速度/散布：speed=0.3*(df+1)*(急速+1)+clamp(dist/40-0.4,0,2.4)；inaccuracy=1-clamp(dist/100,0,0.8)（扇形再/5）；damage=攻击力×(df+1.0/1.2/1.5) 按档。⑤【仅两处有意偏离原版并在注释写明】速度基线 0.3f→1.6f（空中交战距离更远，沿用原速弹幕追不上，这也是旧版写死 1.6 的原因）；不吃 TLM 独有的阻碍/神速/末影杀手三个自定义附魔（与旧版一致），只保留多重射击。⑥数敌人记忆按各版 TLM 原文口径：1.20.1 用 NEAREST_VISIBLE_LIVING_ENTITIES(f_148204_) stream 计数；1.21.1 用 NEAREST_LIVING_ENTITIES(List) stream 计数 + hasLineOfSight。⑦开火节流由本模组 RANGED_SHOT_COOLDOWN 控制，distanceFactor 恒传 1.0f（满蓄力，与 TLM 蓄力完成才开火一致）。⑧验证：本次新用到的每个 SRG/Mojmap 名均以 javap 逐个核对存在（不凭记忆）；两树编译零错、deploy_all.py 三处 match=True、test_server.py 1201 与 neoforge1211 均 PASS。⑨待实测：飞行远战持御币，单只怪→单发；3~5 只同种怪→8 发扇形；超 5 只→32 发大扇形。


v1.2.0 床铺互通【核对两端落地 + 修 Neo 编译 classpath 顺序隐患】：①【核对结论】提示"config 开关/面板条目/语言键全都没有"——逐项核对后这些其实都已在后续提交里补齐（配置 2026-09-15 08:22 落地），故本次不新增功能代码，改为验证其确实可用：MaidBedInteropBehavior（女仆睡原版床）、MaidBedBlockInteropMixin（玩家睡女仆床）、mixin 登记、MISC_BED_INTEROP 开关（默认开）、面板「杂项→床铺互通」一行、lang 中英条目、手册 bedInteropGuide() 章节、getRestBehaviors() 注册（优先级 6）、changelog 记录——两树全部在。注入签名逐字对照 TLM 字节码：1.20.1 isBed(...,Entity) + m_6227_→InteractionResult；1.21.1 isBed(...,LivingEntity) + useItemOn→ItemInteractionResult，均匹配。计划里两个"需实测确认"点也已验证：ServerPlayer$RespawnPosAngle.of(Vec3,BlockPos) 确为 public static 可直接调用；NeoForge IBlockExtension.getRespawnPosition 默认恒空，故显式实现是必要的。②【修掉真实隐患】gen_compile_neo.py 的 cp=[neo,mc,neou,tlm] 把未修补 vanilla srg jar 排在 NeoForge 修补 jar 之前，与本文件注释要求相反。javap 实测 RespawnPosAngle 在 vanilla srg 里是 final class（包私有）、在 neoforge client jar 里是 public final class、在 universal jar 里不存在；而 MaidBedBlockInteropMixin 要实现 getRespawnPosition，返回类型正是它——用包私有类型作 public 方法返回类型按 JLS 就是编译错误，旧顺序能过只是靠先命中 neo(client) 的 public 版本（偶然命中）。修为 cp=[neo,neou,mc,tlm]（与运行期一致，修补版在前）并写明依据。③验证：对照实验证实顺序决定可见性（mc 在前→final class 包私有；neo 在前→public）；两树编译零错、deploy_all.py 三处 match=True、test_server.py 1201 与 neoforge1211 均 PASS。④待实测：女仆睡原版 16 色床；玩家睡女仆床并可设为重生点；关掉开关后两者恢复 TLM 原版。


v1.2.0 空战缺装备提示【只在遇敌时才触发】修复（两树同步）：①【反馈】汇报缺乏装备的语音包没有触发，且文本触发条件很奇怪——应该一进入这个模式就发现缺乏然后发系统消息，而不是仅遇到敌人才发。②【排查结论：语音链路全部正常】flight_not_ready.ogg 存在且有效（30981 字节 OggS）、两树 jar（构建版与已部署版）都已打进、manifest 条目为 {"text":"空战装备不齐","file":"flight_not_ready.ogg","mode":"contains"}、按真实 manifest 模拟 matchKey 命中且排 122 条最前、TTS_JAR_PACK_ENABLED 默认 true、speakable() 放行汉字——所以"没触发"是根本没走到那句气泡（语音挂在气泡上）。③【根因】notifyNotReady 只被 MaidFlightCombatBehavior.tick() 里两处"三件不齐"分支调用，而 tick() 前面还有 canUse：currentTarget(maid)==null 时行为根本不启动——没敌人时缺装备永远走不到提示分支，只有真遇到怪行为开始跑才顺带报一句，正是反馈的"只在遇到敌人才发"。④【修法】notifyNotReady 改 public，挪到 MaidToolAutoEquipBehavior.checkExtraStartConditions 调用（core 行为、优先级 200、任何 activity 每 tick 都评估，与有没有敌人无关）——切到空战任务立刻检查、缺件立刻报；三件齐备时清冷却（NOTIFY_READY.remove），"补齐→又缺"能立刻再报；该分支只提示不擅自换装（补齐仍由 MaidFlightKit.equip 负责）。⑤验证：两树编译零错、deploy_all.py 三处 match=True、test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 飞行近战【飞向敌人贴近地面时被自动传送、卡掉本轮攻击】修复（两树同步）：①【根因】负责"空中禁传送"的 MaidFlightKit.isFlightAirborne 只认滑翔标志位 isGliding；而收翅猛击那一段【主动】清滑翔位（tickSmash 里 setGliding(false)——原版猛击判定要求 !isFallFlying，不收翅打不出猛击），于是"飞向敌人、贴近地面"那一刻恰好不满足 isGliding → 禁传送豁免失效，主人一超阈值就被任意自动传送链路拽走，本轮扑击作废。②【另查出两处漏接线】TLM 原生 teleportToOwner 拦截：1.20.1 有飞行豁免、1.21.1 完全没有（这棵树定义了 isFlightAirborne 却从未调用）；同维度远距拉回 trySameDimPull 与跨维度跟随 followIfCrossDimension：两树都没有飞行豁免。③【修法】MaidFlightCombatBehavior 新增 isEngaged(maid/UUID)——起跳 JUMP_LEFT / 烟花爬升 LAUNCH_LEFT / 收翅猛击 SMASH / 等待再放烟花 WAIT_LAUNCH / 远程俯冲助推 RANGED_BOOST_LEFT 任一为真即算"本轮攻击进行中"；isFlightAirborne 改为"滑翔中 或 isEngaged"，覆盖收翅扑敌那段滑翔位为空的窗口；并接到全部自动传送入口（同维度拉回、跨维度跟随、TLM 原生 teleportToOwner，含补上 1.21.1 缺失的那道）。④【豁免有界】状态表由行为收尾 forget() 整清：一轮打完/目标消失/行为停止立刻恢复正常传送，不会永久禁传。⑤验证：两树编译零错、deploy_all.py 三处 match=True、test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 指标石【女仆搭建中被收回 → 建造不停 / 放出来建模卡住】根因修复（两树同步）：①【根因·CFR 反编译实证】TLM 的 ItemSmartSlab.storeMaidData / AbstractStoreMaidItem.storeMaidData 是【先给女仆拍 NBT 快照、再 post ToItem 事件】——我们的钩子执行时魂符里已经躺着一份"临时建造中"快照（任务=maid_smart:index_build + ForgeData/NeoForgeData 里建造标记=true），旧实现只清女仆活体 persistentData 改不到它。放出来时 TLM maid.load(快照)：读 MaidTask → setTask(index_build) 让隐藏临时建造任务当场复活（而会话早在收回时结算 = 建造不停）；读回建造标记 → 行为 canUse 通过 → 只能靠 selfHealStale 自愈，而自愈是在【行为 tick 内部】直接调 setTask → TLM refreshBrain 重建整个 Brain = 行为列表被换掉（未定义行为，项目在 IndexStoneBuildBehavior.finish 注释里已记录过该风险）→ 女仆僵住（建模卡住）。②【修法一·清洗快照】IndexStoneService.onMaidRecalled 新增 snapshot 参数，收符时把 TLM 即将存进物品的那份 event.getData()（同一对象引用）一起修：MaidTask 写回【原任务】（从快照自己的 persistentData 读 prev_task），建造/站桩标记整段删除；自守护——只有快照里任务确实是我们那个隐藏任务时才动一个字段，相机/胶卷等其他"存女仆物品"路径只洗快照不结束会话（玩家拍照不该把正在进行的搭建判死刑）。③【修法二·自愈延后】selfHealStale 调用改为与 finish 同款 server.execute（1.20.1 为 m_18707_）延后一 tick 执行 + healing 锁防重复排程，杜绝行为 tick 内重建 Brain。④站桩标记 maid_smart_work_still 一并从快照清除。⑤验证：两树编译零错、deploy_all.py 三处 match=True、test_server.py 1201 与 neoforge1211 均 PASS。


v1.2.0 指标石【锁定期间完全不能改选其它方块】：对四百八十四的补充/纠正——要求是锁定期间没有任何办法再去选定其他方块（右击别的方块一律无效）；四百八十四只做到“右击别处不解锁”，但仍把那次右击当成换锁定点，等于照样能改选，没达到要求。【修法（两树同步）】锁定期间只认两种结果、没有第三种：①右击【同一个锁定方块】= 解除锁定（唯一途径）；②右击别的方块/空气/超出距离 = 一律无效，保持原锁定、吞掉这次右键并提示怎么解锁。三处改动：①客户端钩子 IndexStonePreviewClient.onInteraction：已锁定时先 pickAnyBlock 取瞄准方块，只有命中格 == 锁定格才发解锁请求，其余情况【不再发任何锁定请求】（旧版会发“换锁定点”），改 hintLocked() 提示 + cancel 事件；②服务端兜底 IndexStoneService.toggleLock：把“已锁定+不同格”从“换锁定点”改为【拒绝并保持原锁定】，提示“锁定中——要先右击那个锁定方块解除锁定，才能选别的方块”（客户端已拦截，这层防伪造包/其它入口绕过）；③近处方块路径误报修复 IndexStoneItem.useOn：锁定期间右击别的近处方块会被 toggleLock 拒绝并保持原锁定，旧文案却把【被拒的那一格】播报成“已锁定 X,Y,Z”，现在按“操作前是否已锁定在别处”分流，只在【真正锁上新格】时报告坐标。新增 IndexStoneItem.lockedHint()（锁定期间专属提示，纯字符串无客户端依赖）；hintLocked 与 hintUsage 共用 hintCooldown 限频，同一 tick 不会刷两条。顺带改掉“右击别的方块=换锁定点”等过期文案。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 指标石【解除锁定必须右击那个锁定方块】：反馈——锁定之后再右击别的地方就自动解锁了，跟“锁定”词义不符；要求必须再次右击那个锁定的方块才解除。【根因】服务端 toggleLock 本来就是对的（只在“右键位置与已锁格相同”时解锁，右键别的方块是换锁定点），问题在【客户端输入钩子】IndexStonePreviewClient.onInteraction：已锁定时它【无条件】发 LockRequestPacket(unlock=true, 0,0,0)，服务端收到 unlock=true 就走 toggleLock(player,null)（pos==null 语义=直接解锁）——于是只要右击任何地方（别的方块、甚至空气）都会解锁。【修法（两树同步）】已锁定时先做长射线取瞄准的方块，再分流：①同一个锁定方块 → 发解锁请求；②别的方块 → 发锁定请求（服务端按“换锁定点”处理，不是解锁）；③空气/超出距离 → 什么都不做，锁定保持不变，只提示用法。新增 pickAnyBlock(mc)：与 farPick 的区别是【不做距离过滤】——已锁定的方块常常就在眼前（触及距离内），而 farPick 会把 ≤ 触及距离的命中过滤成 null，用它判断会漏掉近处锁定格，导致“对着锁定方块右键却解不掉”。顺带更新误导文案与注释（原“再右键→取消锁定”已明确为“右击同一个锁定方块才解除；右击别的方块=换锁定点”）。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 两版本代码交叉审查【1 处真实 bug + 4 处移植不一致 + 死代码清理】。【真实 bug（两树都有）】getTagType 冒充 getByte 读布尔值 → 三个开关“关不掉”：注释写“getBoolean 等价实现 = getByte != 0”，实际调的是 getTagType；官方 mappings + 字节码双重实证 getTagType 返回 Tag.getId()，而 ByteTag.getId() 是 iconst_1/ireturn（常量 1），故 `!= 0` 恒真——等于上一版声称修好的“关了又自己打开”原样复发；getByte（1.20.1 m_128445_）才返回真实值。三处已改：AiMemoryManager（per-maid 记忆开关）、LlmEnableManager（per-maid LLM 开关）、BuildArchive（legacy 计划 paused）。【移植不一致】①1.20.1 缺飞行作战 static 表清理接线（唯一有“卡死”后果的）：MaidFlightCombatBehavior.forget/clearAll 早已写好却零调用——滑翔/俯冲是 static 表，崩溃/强杀/卸载时行为没走到 stop() 就残留，该女仆永久“俯冲中”（再进不了飞行作战 + 空中禁传送/落地缓冲全线卡死）；已按 1.21.1 补两处接线（ProMaidExtension.onServerStarted 逐女仆 forget+清滑翔位+全清兜底、AiMemoryManager.onEntityLeaveLevel 卸载路径）。②鞘翅“上移 1 格”方向与缩放（我上一轮引入的回归）：1.20.1 Gecko 图层写成 +LIFT_BLOCKS（翻转后该空间 Y 朝下，向上应为 -Y，方向相反），且两处图层漏除 modelScale（图层矩阵已被实体缩放乘过，不除则位移随模型缩放漂移）；已与 1.21.1 对齐为 -LIFT_BLOCKS/modelScale。③1.21.1 Bedrock 鞘翅图层缺 try/finally：中途抛异常会漏 popPose、污染后续渲染矩阵（Gecko 图层两树都有），已补。④1.20.1 sweepWithin 缺 xo/yo/zo 全 0（刚生成/刚传送）守卫：线段会从世界原点拉过来 → 扫掠判定被放大成超大命中范围（等于放宽猛击命中），已按 1.21.1 补。【死代码】删 1.21.1 的 SMASH_SWEEP_SQR 与 MELEE_REACH（均已被新实现取代）。【撤回】审查发现 1.20.1 jumpForLaunch 无 1.21.1 的 hurtMarked=true，但无法在合理成本内确证该字段 SRG 名（官方 mappings 给的 f_19864_ 在真实 jar 里不存在，映射表与 jar 版本不一致），故撤回改动、不留凭记忆写的字段名，仅作已知差异记录。【覆盖说明】文件集/mixin/网络包/配置项/坐标朝向/防摔死/防误伤/防刷物品 两树逐条对齐；try/catch 差异仅来自删掉的诊断代码与上述③；36 处下标访问两树对称且都有守卫。已知合理例外：重锤(1.21.1 独有)、药水组件、数据包目录单复数。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 生存搭建【搭一个扣一个】：需求——生存搭建要搭一个扣一个，不能搭完再一起扣，否则可刷方块。【排查结论】①蓝图建造主流程本来就正确：MaidBuildBehavior 在 doPlace 之前先 consumeBlock（内部 extractItem(...,1)）；②不存在“搭完统一结算”机制——placedCount/placedSet 只用于进度显示与缺口检查（BuildHudTracker/BuildPlan.getProgress），不参与扣料，完工路径（激活红石/水、TNT 结算、清理掉落物）都不扣方块；③索引石搭路 place 的目标格由 resolveMaterial 先取（takeBuildBlock 先扣 1），下方支撑也走 takeOneFor（先 extractItem）再 setBlock，顺序同样正确——（我先前把它判成“先放后扣”是扫描器启发式误报，此处更正）。【本次修掉的真口子】①蓝图建造“自动补支撑”白送方块（3 处）：doPlace 里附着方块（火把/按钮/拉杆/甘蔗/雪层等）支撑面缺失时会 setBlock 补一块支撑却完全不扣料，每补一次凭空多一块，生存可刷；现在三处补支撑（主支撑/掉落验证后的附着类补支撑/漏网类型补下方格）统一改为先扣后放：新增 consumeExtraBlock(maid,support)，走与主放置同一套 consumeBlock + tryTakeFromOwner 补料链，扣不到就不补支撑、延后（等有料再建），绝不“放了但没扣”。②种树苗先种后扣、且不检查扣除结果：MaidPlanting 原本先 setBlock 再 extractItem 且完全没看返回值——扣除失败（槽位被并发改动）时树苗已种下，等于白得一棵苗；现改为先扣后种：新增 takeOneSapling（手部栏优先、其次背包，槽位失效退化为按物品搜），返回非空才算成功，扣不到放弃本轮不放置。【顺带】tryTakeFromOwner 改 static。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 1.20.1 飞行作战与 1.21.1 未同步【只水平飞 + 漏的优化】。反馈：1.20.1 能跑，但烟花给了初速度后全是水平飞、不向上爬，索敌很差；而且 1.21.1 的飞行优化很多没落到 1.20.1。【只水平飞的根因，字节码双重确证】LookControl 每 tick 在 resetXRotOnTick() 为真时会把俯仰角清零——1.21.1 LookControl.tick() 偏移 12 是 setXRot(0)；1.20.1 m_8128_ 偏移 12 是 m_146926_(0)，而它判定的 m_8106_ 字节码是 iconst_1/ireturn【恒真】。lookAt(m_7618_) 写的抬头角正在被清零的字段上，下一 tick 即被抹掉，烟花推力只剩水平分量 → 永远平飞。1.21.1 靠 applyRotation() 每 tick 重写角度 + getLookControl().setLookAt(...) 交期望角度来对抗，这套当时【没同步到 1.20.1】（那边四个朝向函数全只有 m_7618_）。【修法】①1.20.1 新增 applyRotation(maid,yaw,pitch)：m_146922_(yaw) + m_146926_(pitch) + f_19859_/f_19860_（yRotO/xRotO，渲染插值要用）+ m_5616_/m_5618_（yHeadRot/yBodyRot），与 1.21.1 对齐；②faceAwayAndUp / faceTarget / faceUpForward / faceOrbit 改为直接算 yaw/pitch 立即写入，并保留 LookControl 期望角度（m_24950_(x,y,z,360,360)）让每 tick 归零被重新覆盖。【顺带补齐另一处漏同步】suppressVanillaMelee（飞行中给 Brain 写 40t ATTACK_COOLING_DOWN，避免 TLM 自带近战在滑翔时插手挥砍、与“视线当操纵杆”打架）只有 1.21.1 有，1.20.1 完全缺失，3 个调用点（阶段一爬升/阶段二盘旋/猛击段）一并补上；SRG 名以官方 mappings 实证：ATTACK_COOLING_DOWN = f_26373_（同表 ATTACK_TARGET = f_26372_，与项目既有用法交叉验证）、Brain.setMemoryWithExpiry = m_21882_。【逐项核对】endFlightSafely、notifyNotReady、sweepWithin、isCriticalHit、isNearGround、tickGunFire/stopGunAim、takeFirework、LAUNCH_TICKS/CLIMB_TAN 分模式、RANGED_BOOST、throwTrident/御币弹幕两树均齐；applyRotation 与 suppressVanillaMelee 本次补上；重锤后门(MAX_Y/forcedFall)仅 1.21.1 属正常——重锤是 1.21.1 独有物品，1.20.1 无 Items.MACE，不是漏同步。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 鞘翅渲染【尺寸 0.85× + 上移 1 格 + 修「只有 winefox 能渲染」】：反馈三条——①仍然太小、②位置偏下、③除 winefox 外都渲染不出。第③条根因已查清（与①②不是一回事）：扫描全部 28 个 Gecko 女仆模型骨骼 JSON——Elytra 17/28（全是 winefox 家族）、ElytraLocator 17/28、UpperBody 26/28、Root 28/28。旧代码只找 ElytraLocator/Elytra，于是【没有这两根骨骼的 7 个模型】（foxmaid、hailuo_new_year、sta、winefox_hanfu、winefox_mini、zhiban_hanfu、zhiban_new_year）全掉进硬编码兜底，而兜底是从【原版】ElytraLayer 抄的 scale(-1,-1,1)+translate(0,-1.501,0)——那是【原版模型空间】的补偿；Gecko 图层栈单位=格、Y 朝上、原点在脚下，根本不是那个空间，于是翅膀被顶到脚下/身体里 → 看不见。也就是说，【只有 winefox 显示，是因为只有 winefox 家族恰好带那根骨骼、绕过了坏兜底】。【修法：永不掉进兜底】按优先级找骨骼，找不到专用挂点就锚到一定有的躯干/根骨骼，并补一段从真实模型统计出的平均偏移（17 样本，Elytra.pivot−bone.pivot 均值/16）：ElytraLocator/Elytra→(0,0,0)、UpperBody→(0,+0.4310,+0.2011)、Root→(0,+1.8596,+0.2022) 格。28/28 模型现在都有锚点，兜底实际到不了；万一全都没有会打一条「鞘翅挂点」日志便于定位（只报一次），不再静默失败。【尺寸与位置】尺寸 k = 0.85×0.9375/modelScale（0.9375 是原版 PlayerRenderer.scale，即原先的"与玩家等大"，再乘 0.85）；上移 1 格：Gecko 侧锚定后先 scale(-1,-1,1) 转成 ElytraModel 的 Y 朝下空间，故向上 = -Y，写作 translate(0, -1.0/modelScale, 0)，Bedrock 侧同为 -Y；两者都放在 scale(k) 之前（不受鞘翅尺寸缩放影响），除以 modelScale 是因为图层矩阵已被 renderEarly 的 scale(widthScale,heightScale,widthScale) 乘过（字节码实证），不除就变成 1/modelScale 格。编译零错（两树）、字节码校验入包、两个 server 回归 PASS、部署三目标。


v1.2.0 重锤后门【取消 40 格封顶，改回无限增伤】：反馈「不要上限，要的就是无限增伤」。旧版（实测四百七十七）我给 `MAX_FORCED_FALL = 40` 做了个务实天花板，理由只是「连放多枚烟花爬上天怕算出离谱数字」——但那条是我自己加的，不是原版行为，也不是你要的。现按原版口径去掉上限：`forcedFallDistance` 只保留下限（`SMASH_MIN_FALL + 0.01`，为绕开原版严格的 `fallDistance > 1.5`，恰好等于 1.5 时加成仍为 0，那不是封顶），不再有上界——爬多高就吃多高的加成，与原版三段式末段 `22 + (fd-8)` 的无上限特性一致。实测：落差 50 格 = +64、100 格 = +114。仅 Neo 树（重锤是 1.21.1 独有物品）。


v1.2.0 飞行远战「三叉戟单独一路（ThrownTrident）」【含一处更正】：上一轮我说「三叉戟也是 ProjectileWeaponItem，现在会落到造箭那支」——【这个说法是错的，此处更正】。反编译实证：1.20.1 是 `TridentItem extends Item implements Vanishable`、1.21.1 是 `TridentItem extends Item implements ProjectileItem`（ProjectileItem 与 ProjectileWeaponItem 是两个不同接口）——两版都【不是】ProjectileWeaponItem。所以旧版女仆拿三叉戟时：①「武器位」判否 → 飞行远战根本激活不了；②自动装备也认不出它。【为什么不能走原版那条路】`TridentItem.releaseUsing`(`m_5551_`) 第一件事就是 `if (!(entityLiving instanceof Player)) return;`（字节码 instanceof Player; ifne）——女仆不是 Player，调用它等于空转。【修复：照搬 TLM 自己的 TaskTridentAttack.performRangedAttack（字节码实证）】①`MaidFlightKit.isRangedWeapon` 增加 instanceof TridentItem（与 TLM TaskTridentAttack.isWeapon 同口径）→ 激活与自动装备都认它；②新增 isTrident / throwTrident：自建 ThrownTrident，投【副本】（主手只掉 1 点耐久）；③【剥掉忠诚附魔】（1.20.1 Enchantments.f_44955_，已用官方 mappings「LOYALTY -> E」交叉验证即忠诚；1.21.1 Enchantments.LOYALTY）——留着它 isAcceptibleReturnOwner() 对活着的女仆返回 true，副本会飞回来、还可能被玩家捡走，等于凭空多出一把三叉戟（刷取漏洞）；④setNoGravity(true) 直线飞行（配 TLM 的 ThrownTridentMixin：命中实体后才恢复重力）；⑤pickup=CREATIVE_ONLY——副本不落地可捡、到期由 tickDespawn 自行回收；⑥速度/散布照 TLM：clamp(距离/10, 1.6, 3.2) 与 1-clamp(距离/100, 0, 0.9)，音效 TRIDENT_THROW。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 飞行近战「重锤增伤按最大上升高度强制结算」（后门）：走的是"强制按最大上升高度结算增伤"这条捷径。【结论：可以，而且比改飞行 AI 更对路】反编译实证：`MaceItem.getAttackDamageBonus` 只读 `fallDistance` 一个字段，`canSmashAttack` = `fallDistance > 1.5 && !isFallFlying()`；风爆的 wind_burst.json 谓词也只是 `is_flying=false` + `fall_distance.min=1.5`。而 `fallDistance` 是普通浮点字段，且这两处结算都是本模组自己发起的（我们自己调 getAttackDamageBonus 与 doPostAttackEffects）——所以在调用前后临时改写它即可，不必真的让她爬到某个高度（滑翔物理每 tick 把 fallDistance 钳回 1.0，靠飞行动作永远攒不出高数值，这正是吃不到加成的根）。【实现】①新增 MAX_Y：每 tick 记录本轮最高 Y，每放一枚烟花（=新一轮爬升）重置为当前高度；②forcedFallDistance = clamp(本轮最高点 − 当前高度, 1.51, 40)，即"她实际砸下来的这段距离"；③hitOne 里持重锤时写入该值 → 结算 → **finally 必还原**（绝不把假值留给原版摔落判定）；④因为不再依赖真实累计，把四百七十三那版"必须等攒过 1.5 才出手"的门撤掉，恢复"够近就打"，顺带解决玩家反馈的「空中时间太长掉 DPS」。【数值】落差 8 格=+22、10 格=+24、15 格=+29、30 格=+44——**并按原版口径无上限，爬多高就吃多高**（实测四百七十九已取消 40 格封顶；floor 取 1.51 是为满足原版【严格大于】1.5）。⑤附诊断日志（latest.log 搜「重锤后门」）如实打印"落差X格 → 写入Y（真实值Z）"，便于实测核验。仅 Neo 树（重锤是 1.21.1 独有物品；1.20.1 无 Items.MACE，且其暴击只要 fallDistance>0，滑翔中天然满足）。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 飞行远战「手持武器不参与开火」修根：反馈「远战正常，但只会发射箭；换了武器（如御币）也没用，依旧发射弓箭」。【根因】`performRangedAttack` 里只判了武器是不是 `ProjectileWeaponItem`，之后就【无条件自己造箭】（旧代码：取箭 → ArrowItem.createArrow → shoot → 扣耐久扣箭）——武器本身是什么、该怎么发射，完全没被调用过。所以拿御币/三叉戟/模组弹射武器都变成射出一支普通箭。【实证】反编译 TLM 的御币：`ItemHakureiGohei` 虽 extends ProjectileWeaponItem，但它的 `shootProjectile` 是【空实现】、`getAllSupportedProjectiles` 返回 alwaysTrue（谓词恒真）——说明御币压根不走原版箭矢通道；TLM 自己是用 `DanmakuShoot`（弹幕 API）开火的（`TaskDanmakuAttack` 实证）。硬造箭必然变成「射箭」。【修复】按武器分流（与玩家手持该武器时的表现对齐）：①枪械（TACZ/卓越前线）→ 直接返回，仍由 tickGunFire 走 TLM 枪械通道（换弹+瞄准+开火）；②御币 → 走 TLM 弹幕通道 `DanmakuShoot.create().setWorld/setThrower/setTarget/setRandomColor/setVelocity(1.6)/setInaccuracy(1.0)/setDamage(女仆攻击力).aimedShot()`（与 TLM 弹幕任务同一套 API，反编译确认各字段均有默认值，只需补世界/投掷者/目标/速度伤害）；③其它弹射武器（弓/弩/模组弹射物）→ 造箭，且【弹药优先按武器自己的 supportedProjectiles 判据找】（弓弩认箭、模组武器认自己的弹药），拿不到才退回背包里的普通箭。两树同步（1.20.1 用 SRG 名：`m_6437_`=getAllSupportedProjectiles、`m_6394_`=createArrow 三参版）。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 支援语音包核查 + 语音间隔 8→5 秒：反馈「女仆支援语音似乎没有语音包」。【实证结论：支援语音包其实一直都在】逐条转写核对（whisper）——aid_sister_help「しまいっ頑張って今助けるからね」、aid_food「ご主人様お腹空いたでしょ 食べ物持ってきたよ」、aid_potion_given「ポーション、カバンに入れたよ」、aid_bag_full、aid_eat_something 等 10 条 aid_* 全部是正常日语人声，响度也已按全包统一归一（max≈-1.5dB，与其它 112 条同档）；两树 + 三个已部署实例的 jar 里都是 10 条齐全、manifest 命中正确（姐妹气泡 → aid_sister_help.ogg 实测命中）。所以「像没做」的真正原因是【被播报间隔吞掉了】：内置语音包最小间隔默认 8 秒，而支援/互助类台词与系统消息共用同一道门——一场战斗里系统消息频繁触发，支援语音轮到播放时大概率还在冷却窗口内，听感上就等于没有。【修复】jarPackMinIntervalS 默认 8 → 5 秒（配置面板「语音与 TTS → 内置语音最小间隔（秒）」可调，范围 0~60，0 = 不限制）。同时把三个已部署实例的现有配置文件一并从 8 改成 5（旧配置文件不会自动跟随新默认值，已在改前逐个备份 .bak_interval_*）。【顺带更正】配置面板内置语音包说明里的条数从旧的「115 条」改成实际「122 条」，并把支援/投喂台词与新增的空战提示计入分类说明。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 空战未激活提示 + 语音：需求"两种空战未进入激活状态时加一条气泡对话 + 系统消息"。做法：新增 MaidFlightKit.missingParts(maid)——按 isModeActive 的同一口径列出缺哪几件（鞘翅 / 近战或远程武器【按任务分流】/ 烟花火箭，缺多件一起报），行为里两处未激活分支（烟花用完或鞘翅损坏 / equip 后仍不齐）调用 notifyNotReady 发气泡。台词：「空战装备不齐，缺鞘翅、烟花火箭，先按普通战斗来」。【一个入口同时满足两个需求】走 addTextChatBubble：ChatBubbleLimitMixin 会把它①同步成主人系统消息（青色 [女仆名] 前缀）②按 misc.bubbleLimitMs 限频③汇入 SystemTTSManager 朗读——所以气泡 + 系统消息 + 语音三者共用同一句文本，不需要各写一遍。【限频】气泡自身 5 秒限频由 mixin 兜底；另加 15 秒冷却（WeakHashMap 按女仆记，防强引用泄漏），因为"三件不齐"是持续状态（站在怪堆里没烟花会每 tick 命中该分支），不拦会刷屏；clearAll 一并清 NOTIFY_READY。【语音】新增 flight_not_ready.ogg（日语「空戦の装備が足りないの、普通に戦うね」，小酒狐 v2Pro e15 音色，battle 档 speed 1.08，峰值归一 -1.5dB / libvorbis q4，3.64s 单声道）。manifest 条目用 contains 探针「空战装备不齐」——【必须排在 fallback_missing 的「缺」之前】：manifest 是自上而下顺序匹配，旧的通用「缺」条会把这句吞掉（已实测验证命中自己）。条目 122 条 / ogg 122 个两树一致，jar 内 manifest 与音频已复验可命中。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 飞行近战命中率 + 重锤下落加成/风爆 修根：①【本轮重点·重锤】原版 `MaceItem.canSmashAttack` = `fallDistance > 1.5 && !isFallFlying()`（字节码实证：`ifle` 严格大于），而 `MaceItem.getAttackDamageBonus`（下落三段加成 4 / 12+2(f-3) / 22+(f-8)）与「风爆」附魔【都以该判定为门】——风爆的 data/minecraft/enchantment/wind_burst.json 明写 requirements：`entity=direct_attacker`、`flags.is_flying=false`、`movement.fall_distance.min=1.5`，且经反编译确认 is_flying 走的就是 `LivingEntity.isFallFlying()`（滑翔位）。旧版在 t>=2 就出手，而那一刻 fallDistance 还被滑翔钳在 ~1.0（`Entity.m_245125_`：滑翔中 y 速度 > -0.5 时把 fallDistance 压回 1.0），于是重锤打出来等同白板、风爆一次都不触发——正是反馈的"触发不了风爆且伤害低"。修复：持重锤时【等 fallDistance 真正攒过 1.5 再出手】（收翅即自由落体，两三 tick 达标，远早于贴地）；贴地/落地时不再等待、照打（没加成也要打出去）；门槛用【严格大于】1.5 与 `ifle` 对齐（恰好等于 1.5 时加成仍为 0）。非重锤武器不吃这道门（暴击只要 fallDistance > 0）。顺序也已核对：命中结算里 `doPostAttackEffects`（风爆入口）在 `resetFallDistance()` 之前，收翅时滑翔位已清→ is_flying=false 成立，两个条件都满足。②【命中率】俯冲约 1~2 格/tick，旧版只比较"当帧中心距"，整段可能从目标侧上方掠过、每帧都不在半径内→ 整套俯冲白打。新增 sweepWithin：把"上一帧位置→本帧位置"连成线段，取目标到线段最短距离，擦身而过的这一帧也算够得着（与弹射物扫掠判定同思路）。两树一致（1.20.1 用 SRG 的 f_19854_/m_20182_ 等）。③【白打修复】smashHit 改为返回 boolean：攻击冷却未好时返回 false，调用方【不再收尾】，下一 tick 继续压着打——旧版无论打没打中都 endSmash，等于每次俯冲白送。④【安全】收尾分支改到命中分支之外，保证"未命中"时也一定走到落地清零（旧版冷却中命中分支 return，贴地时既没清零也没收尾，她会带着满坠落距离撞地）。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 飞行近战回退 + 摔死根治：①【回归根因】上一版为飞行远战「飞太高够不到地面敌人」把 LAUNCH_TICKS 30→20、LAUNCH_CLIMB_TAN 1.88(62°)→1.0(45°)，但这两个是【近战远战共用】的常量——近战链路是「起飞→扑击→收翅猛击→再起飞」的循环，每次重新起飞都要先把高度拉起来，仰角被压到 45° 后她一放烟花就往目标方向压头，几 tick 内贴地、滑翔位被原版 updateFallFlying 清掉 → 立刻变自由落体摔死。现按需求【回退】：近战恢复 62°/30t（LAUNCH_CLIMB_TAN_MELEE / LAUNCH_TICKS_MELEE），远战保留 45°/20t（..._RANGED），faceAwayAndUp 与 tryLaunch 按任务分流取值。②【实证】整合包实机日志（latest.log）里飞行期的死亡【全部是 类型=fall 伤害=21~23】（她 20 血必死），位置清一色在地面高度，全程没有一条 flyIntoWall——「很容易摔死」成立，与撞墙无关。③【附带根治】目标丢失 / 三件不齐（烟花用完或鞘翅损坏）/ 行为结束这三处旧版都【无条件清滑翔位】——若此刻正在高空（烟花推进的必然结果），清位即自由落体。新增 endFlightSafely：空中一律【保留滑翔自然下降】、落地再收翅（与玩家同款：没推力就滑下来），落地的清理由原版 updateFallFlying 自然完成。④【鞘翅撞击免疫可核验】MaidFlightWallGuard 判定本身正确（getMsgId=="flyIntoWall"，1.20.1/1.21.1 的 fly_into_wall.json 实证一致；已确认 Forge 的 ForgeHooks.onLivingAttack 挂在 LivingEntity.hurt 头部、NeoForge 的 LivingIncomingDamageEvent 同样在 hurt 内 post 且可取消），但此前日志里一条 flyIntoWall 都没有，「没生效」无从分辨是没触发还是没拦住——现在拦下时记一条「[promaid/飞行撞击免疫] XX 拦下鞘翅撞击伤害」，再复现即可确认。经核实注解自动注册【是生效的】（FriendlyFireGuard 同为注解注册且日志持续有输出），故【不】额外显式注册以免重复登记。


v1.2.0 指标石修 bug：建造完成后不自动清绑定与幽灵方块。根因是我上一轮引入的分支顺序错误——行为 tick 顶部先判 "s==null || s.cells.isEmpty()" 走 selfHealStale，而 selfHealStale 出于"魂符放回来"的安全考虑【绝不触碰玩家会话】，于是最后一格放完后 cells 变空时被它吃掉：绑定/锁定框/任务标记全清不掉、女仆永远卡在临时搭建任务上。修法：把【完成判定提到最前】——"她的会话还在（s.maidId==她）且 cells 已空" = 成功完成 → 走 finish（server.execute延后一 tick 调 onMaidFinished：清幽灵格 cells + 解绑 + 取消锁定框 + 释放强制加载票 + 还原原任务/作息 + 播报 + 立即 S2C 推空状态让客户端红框橙影当场消失）；另加 finishing 闸防延后期间重复排程/重复播报；onMaidFinished 改为【无论会话是否还在都解除她的临时建造任务】（防会话先消失时她永久卡住）。编译零错、两 server 回归 PASS、部署三目标。


v1.2.0 指标石细节：搭建中途失败/中断一律"视作结束和初始化"。①女仆被收回/死亡 → 清橙色幽灵格 + 解绑 + 取消锁定框 + 释放强制加载票，结束本次（消息如实报"已搭 X/Y 块"）；②搭建中途发现缺材料也视作失败——连续 3 秒拿不到任何材料即烂尾（留宽限防"刚消费完一叠、主人正在补"误判）；③新增失败收尾 failSession（幂等，多处触发只结算一次）：清幽灵格/解绑/取消锁定框/释放票/还原她原任务与作息/系统提示/立即 S2C 推空状态让红框橙影当场消失；④中断检测三入口：EntityLeaveLevelEvent（按 RemovalReason 精确区分——KILLED死亡/DISCARDED收回=结束，UNLOADED_*区块卸载与CHANGED_DIMENSION换维度=不结束，否则她走远被卸载会误判成烂尾）、LivingDeathEvent（死亡兜底）、魂符收回 MaidAndItemTransformEvent.ToItem（手动空魂符/自动回魂符唯一汇聚点，字节码实证 storeMaidData 先存 NBT 再 post，故此处只结束会话）；⑤魂符把女仆 NBT 一起存进魂符，放回来后残留标记由行为顶部 selfHealStale 兜底：只还原任务、不播报"搭好了"，且【绝不触碰主人的会话】——那可能已属于另一只女仆（failSession/selfHeal 都有会话归属校验，防"放回来一个旧女仆把正在干活的新任务清掉"）；⑥完成路径也一并取消锁定框、可立刻开下一个；⑦长时无进展（20秒）也按烂尾收尾。编译零错、两 server 回归 PASS（Neo 有一次 90s 内未到 Done 系该重模组包启动慢，放宽到 240s 后 Done(0.871s) PASS，非崩溃）、部署三目标。


v1.2.0 指标石贴图替换：物品图标换成玩家提供的「带指针的平滑石头」成品图（64x64，指针红/白、石头面带明暗过渡），替换两树 assets/maid_smart/textures/item/index_stone.png；模型 item/generated 引用同一路径无需改动，物品栏与手持渲染自动生效（item/generated 把整张图铺在物品面上，分辨率提高只增加细节、不改变尺寸）。


v1.2.0 飞行作战（近战/远战）下不触发自保（需求逐条落地）：SelfPreservationBehavior.tick 最顶端加飞行硬闸——MaidFlightKit.isFlightTask 为真时既不进自保会话、也把已在会话里的立刻安静中止（sessionActive=false + 清 PRESERVE_TAG + 清垫高/塔顶状态 + MOVING_SURVIVE 摘除），只保留与保命无关的常驻轻量逻辑（蜂蜜/牛奶解负面）后 return；两个易踩点都已避开：①豁免不能放 canUse——那会让行为在飞行任务下根本不启动（setTask→refreshBrain 后新实例永不 tick），而清残留 PRESERVE_TAG 的代码藏在 tick 里，结果从飞行切回时标记残留、排班/参战/搭方块对该女仆永久让位，故豁免统一放 tick；②清标记必须无条件（判 sessionActive || PRESERVE_TAG）——区块卸载/崩溃/魂符收放会把标记写进存档而内存态归零，只看内存态时该残留正好落在飞行任务上会被本闸提前 return 挡掉、再也清不掉；顺带把闸放在岩浆感知扫描之前（空中不需要避让，省掉每 10 tick 一次的全量方块扫描）。口径：只拦"自保接管"、不放宽真实伤害——飞行中照样掉血/可能被击落，只是不再由自保把她从空中拽下来（传送/垫高/放水/逃跑会打断滑翔与烟花推进），与 AutoCombatSwitch 里飞行任务"自主战斗不介入"一致。编译零错（两树）、两个 server 回归 PASS、部署三目标。


v1.2.0 指标石：临时蓝图制作器（需求逐条落地）——物品 maid_smart:index_stone（9 平滑石头 3×3 合成，创造栏建材页+工具页）；手持时视线指向的方块渲染为绿色（跟随指针）、右键锁定为红色不再移动、再右键取消，客户端 512 格长射线可锁极远、不可锁空气；右击自己的女仆绑定（再右击解绑/点其它女仆换绑），必须先锁方块再绑女仆（先绑会提示）；两件事齐备后女仆所在格到锁定格之间的空气方块渲染为橙色幽灵方块，女仆立即进入一次性临时建造、由近及远逐格填充，材料先从她背包取不够从主人背包拉（选材复用搭路规则：数量最多者优先/必须有碰撞/排除下落方块与TNT与危险方块，创造主人视为无限）；表现与建造任务一致（站桩+瞬移工地旁+挥臂+放置音效）；一个玩家同时只允许一个锁定+绑定，完成前不能开下一个；完成或取消均还原原任务/原作息并播报。实现：IndexStonePlan（体素连线几何，服务端开建与客户端幽灵共用，保证逐格一致）/IndexStoneService（会话内存态+女仆 persistentData 双写、隐藏任务 index_build 强制指派与还原、会话级 FORCED 票据给起点到锁定点之间区块强制加载并在完成/取消/下线/停服全路径释放）/IndexStoneItem/IndexStoneInteractHandler/IndexStoneNetworking（C2S 锁定+S2C 状态）/IndexStonePreviewClient（渲染+长射线锁定）；config BUILD_INDEX_STONE（默认开，面板建造区）+四份 lang。审查拦下并修复四个坑：①已锁定时取消 use 键会把右键女仆一并吞掉（绑定永远收不到包）——改为视线命中 EntityHitResult 时不拦截；②在行为 tick 里 setTask 会refreshBrain 重建 Brain=未定义行为——收尾改 server.execute 延后到本 tick 之后；③未加载区块的格被永久丢弃留洞——改为退避等加载就绪；④强制加载票在解绑/取消/下线/停服路径泄漏——补全路径释放。编译零错、lang json OK、新类与资源入包、两个 server 回归 PASS、部署三目标。


[05:40:00] v1.2.0 全盘修复五条：①任务栏 mod 任务全变英文——根因是 promaid_src_neo/assets/maid_smart/lang/{zh_cn,en_us}.json 末尾多了尾逗号，整份语言文件被 ClientLanguage 跳过（日志实证 Skipped language file），而 TLM 任务名走 task.<ns>.<path> 翻译键，键丢失就显示键名；删逗号并 json.load 复验四份 lang 合法（1.20.1 本来合法，只有 1.21.1 中招）；②飞行远战飞太高——LAUNCH_CLIMB_TAN 1.88(62°)→1.0(45°)、LAUNCH_TICKS 30→20、补烟花抬头角 55°→45°、抬头窗口 20→10tick、期望盘旋高度 6→3.5 格，远战图标弓→鞘翅；③免疫鞘翅撞击伤害（后门，默认开）——新增 MaidFlightWallGuard + config COMBAT_FLIGHT_NO_WALL_DAMAGE（生存与复活→落地缓冲），用 getMsgId()=="flyIntoWall" 判定（跨版本稳定），NeoForge 用 LivingIncomingDamageEvent、1.20.1 用 LivingAttackEvent；④鞘翅渲染——用的就是玩家 ElytraModel，前几版错在硬编 1.501 且未抵消模型 render_entity_scale(0.65)：鞘翅只有玩家 69% 大、挂点被顶到腰背埋在长发里（LongHair z 0.34~0.64）、只在 isGliding 展翅会露折叠态；改为锚定模型自带 Elytra 骨骼(pivot y30.9644 z3 → 世界 y≈1.258 z≈+0.122，在头发之前，16/27 模型有) + scale(-1,-1,1) + scale(0.9375/0.65)，Bedrock 分支同步补缩放抵消，展翅条件放宽为飞行任务且离地；⑤两模式落地水/雪恢复照常——撤掉 WaterClutch 对飞行任务的屏蔽。编译零错、四份 lang 合法、两个 server 回归 PASS、jar 重建部署三目标。


[05:30:00] v1.2.0 飞行远战·兼容枪械（TACZ / 卓越前线）（需求：把枪械接进飞行模式，接在"武器位判据 + 开火路径"两处）：①判据——MaidFlightKit.isRangedWeapon 放开为 ProjectileWeaponItem || GunCompat.isGun，因 isWeaponForTask/hasWeapon/isModeActive/任务 isWeapon 全复用该判据，故一处放开全线生效（飞行远战认枪；飞行近战仍只认近战武器），equip 远战分支也会从背包掏枪到主手；②开火——新增 fireRanged/tickGunFire，持枪走 TLM 枪械通道（照搬 GunShootTargetTask.tick：每 tick 换弹→冷却好且 canSee 时 performGunAttack→冷却按 MAID_GUN_ATTACK_SPEED 缩放），射程用 GunCompat.gunMaxRange，弓弩仍走箭矢通道 24 格，performRangedAttack 对枪械直接返回；③坑：performGunAttack 会自己算朝向把身体拧向目标，而滑翔方向=视线方向，故开火必须放在朝向之前、开完立刻盖回盘旋朝向，否则会被拉向目标；④退出模式在 stop() 调 GunCommonUtil.onStop 收枪（复位 ADS/手雷冷却，用一次性 GunShootTargetTask 实例，已确认无静态初始化块）。编译零错、两个 server 回归 PASS、jar 重建部署三目标。


[05:20:00] v1.2.0 飞行远战·盘旋高度保持（反馈：盘旋时高度会慢慢降低，像玩家滑翔那样；每 5 秒补一次烟花既费烟花又顶不住）：①盘旋俯仰改为按高度偏差的反馈控制——期望高度=目标上方 6 格，偏差按 5°/格 换算并留 1 格向上偏置（平衡点≈原 -6°），低了抬头把速度换成升力、高了低头把高度换成速度，限幅 +45°/-35°；②烟花由定时改为按需——只有掉出高度带（低于期望 3 格）才补，5 秒间隔仅为下限，补时仍抬头 55° 放；③其余不变（开火走 TLM performRangedAttack、24 格内每 2 秒一发；烟花耗尽或鞘翅损坏→模式自然未激活→退回地面）。说明：能绕圈（切向+半径修正）但飞行模型是鞘翅滑翔而非幻翼悬停，半径随速度变化，加高度保持后表现为稳定高度带绕圈。参数：RANGED_HOLD_HEIGHT/GAIN/BIAS、RANGED_BOOST_DROP、ORBIT_RADIUS、RANGED_BOOST_INTERVAL。编译零错、两个 server 回归 PASS、jar 重建部署三目标。


[05:10:00] v1.2.0 飞行作战·模式内两条强制限制（需求：该模式下女仆不会自主切换武器、自主战斗也不会被触发）：①不自动换武器——MaidToolAutoEquipBehavior.m_6114_ 对飞行任务直接返回 false，ensureForTask（主手择优换装）与 ensureShieldForCombat（副手换盾）都不再执行；模式内装备只由 MaidFlightKit.equip 补齐三件套（鞘翅/武器/烟花），主手已有合格武器时绝不替换；②自主战斗不介入——AutoCombatSwitch.tryEngageMaid 对飞行任务直接返回 2（已是战斗任务跳过），叠加已有三处 UID 排除（入战不进池/战中不换战术/还原视为玩家接管），参战评估→选任务→换战术→还原全链路都不碰。注：限制按任务生效——即使因没烟花/鞘翅损坏而模式未激活，只要任务还是飞行作战就不换武器、不被切走。另附兼容性结论：飞行近战与 TLM 攻击模式同判据（主手槽带 ATTACK_DAMAGE 修饰符，镐/弓/弩/御币排除）→ 多数模组近战武器天然兼容；飞行远战与 TLM 弓弩任务同判据（ProjectileWeaponItem）→ 原版弓弩/御币/多数模组弓弩兼容；TACZ 等枪械不属于以上两类，暂不参与（要接 GunCompat 另开一轮）。编译零错、两个 server 回归 PASS、jar 重建部署三目标。


[04:00:00] v1.2.0 回退「落地贴身就地近战」+ 强制受击绑定攻击频率（反馈：担心女仆只顾地面近战不再起飞；既然已有后门强制触发攻击事件就不需要这个改动；并强调强制受击的间隔要取决于女仆的攻击频率）：①回退——删掉 GROUND_FIGHT_RANGE 与「落地时目标仍在 5 格内 → 就地近战收尾」的分支，恢复原逻辑（落地只要在 LAUNCH_RANGE(20) 格内且能放烟花就立刻起跳重新进入飞行链路，够不到才退回地面近战），飞行近战循环恢复为「一直在飞」；②smashHit（含主目标与范围补命中）新增攻击冷却闸 ATTACK_READY——冷却没好就不出手，出手后按女仆自己的攻击间隔 20/ATTACK_SPEED（与 TLM MaidMeleeAttack 同口径）记冷却，所以强制受击是「每次能攻击时最多一次」而不是无条件每 tick 触发；③保留命中率另两项：攻击判定距离 4.0 格 / 收翅后 2 tick 即可出手、被无敌帧吃掉时清零 f_19802_ 再打一次。飞行远战不受影响。编译零错、两个 server 回归 PASS、jar 重建部署三目标。


[03:20:00] v1.2.0 飞行作战拆分为「飞行近战 / 飞行远战」（需求：手持远程武器时近战链路不适用——起飞不变，其后改为持续在天上盘旋、每 5 秒补一次烟花，攻击改为用手持远程武器在锁敌范围内开火，击败敌人后同样降落）：①原 maid_smart:flight_combat 显示名改为「飞行近战」（UID 保持不变，老存档不受影响）；②新增 maid_smart:flight_ranged「飞行远战」（图标=弓，不响应自主切换），三件套=鞘翅+任意远程武器（ProjectileWeaponItem：弓/弩/御币/模组弹射武器）+烟花火箭；起飞方式与近战完全相同（起跳滑翔+放烟花背离+向上）；起飞后不再扑击，改为绕目标约 10 格持续盘旋（切向视线+半径自动拉回），每 5 秒（100 tick）补一枚烟花——补的那一下先开 20 tick 的「抬头 55° 窗口」，让推力全部用在爬升、抵消滑翔下沉；开火走 TLM 自己的通道 EntityMaid.m_6504_(performRangedAttack)：本任务实现 IRangedAttackTask，在 performRangedAttack 里自己找箭（副手优先、其次女仆背包——原版 getProjectile 找不到女仆背包）、造箭、朝目标射出、耗 1 箭+耗耐久+射击音效；刻意不注册 TLM 的 MaidShootTargetTask（它每 tick 拽视线，与盘旋转向打架）；击败敌人后行为结束→清滑翔落地（与近战一致），落地后还有目标就重新起飞恢复盘旋。③MaidFlightKit.isFlightTask 现在覆盖两个 UID，所以鞘翅渲染/滑翔禁传送/飞行作战期间不收落地水雪/自主切换三处排除自动对两个模式同时生效；武器位判据按任务分流（近战认近战武器、远战认远程武器）。编译零错（218 源文件）、两个 server 回归 PASS、jar 重建部署三目标。


[02:00:00] v1.2.0 飞行作战·命中率与展翅修复（同 1.21.1 同步）：①命中率——攻击判定距离 2.5→4.0 格、收翅后 t≥2 tick 即可出手；以女仆为中心 2.5 格内的其他合法敌对目标强制补结算一次攻击（只认 IAttackTask.canAttack，女仆一律跳过 = 女仆免疫重锤友伤）；被无敌帧吃掉时清零 f_19802_(invulnerableTime) 再打一次。②落地时目标仍在 5 格内 → 就地近战收尾，不再立刻起飞（修"落地以后打不到怪"）。③飞行作战期间不收落地水/雪。④滑翔中强制完全展翅（原版会按下落速度部分收拢）：新增 ElytraModelAccessor（SRG 字段 f_102533_/f_102532_）+ ElytraSpread（ModelPart f_104203_/f_104204_/f_104205_ = xRot/yRot/zRot）。编译零错（217 源文件）、两个 server 回归 PASS、jar 重建部署三目标。


[00:30:00] v1.2.0 新增"飞行作战"任务（1.20.1 版，武器无关 + 暴击）：三件套 = 鞘翅 + 任意近战武器 + 烟花火箭，齐备才激活（缺任一件则与普通攻击模式一致）。链路：遇到敌人先【起跳滑翔】（跳一下离地——挂载烟花只对正在滑翔的实体给推力，而站在地上置位会被 LivingEntity.updateFallFlying 清掉）→放烟花给"背离敌人 + 向上 62°"的初速→1.5 秒后转入飞行、朝敌人飞（滑翔的唯一操纵杆就是视线方向）→进入 3.5 格【取消滑翔】收翅自由落体→触底前一次近战命中（1.20.1 无重锤，结算原版暴击 ×1.5：伤害 = 攻击力 × 1.5 + 附魔加成；依据是滑翔中 fallDistance 被 Entity.m_245125_() 钳在 ≤1.0，收翅后再掉 2~3 tick 就满足暴击的 fallDistance>0）→命中后【切回滑翔】→再放烟花，如此反复直至怪物死亡或模式退出；烟花用完或鞘翅损坏（滑翔每 20 tick 扣 1 点耐久）则模式自然失效、退回普通攻击模式。实现：新增 MaidFlightKit / MaidFlightCombatTask / MaidFlightCombatBehavior（SRG 化）、双路径鞘翅图层 LayerMaidElytra（Bedrock，照抄原版 ElytraLayer 配方）+ LayerMaidElytraGecko（GeckoLib，补 scale(-1,-1,1) + translate(0,-1.501,0) 的空间换算）、mixin EntityFlagInvoker（暴露 setSharedFlag/getSharedFlag 第 7 位滑翔标志）/ MaidSwimGlideMixin（滑翔时套游泳展翅动画）/ LivingEntityRendererAddLayerInvoker + MaidElytraLayerMixin + MaidElytraLayerGeckoMixin（客户端注册图层，不用 ILittleMaid.addAdditionMaidLayer 以免专用服务器 RuntimeDistCleaner 崩溃）；接入 mixins.promaid.json、addMaidTask、配置项 combat.flightMode、配置面板、lang；AutoCombatSwitch 三处按 UID 排除（入池/换战术/还原），MaidTeleportPreserveMixin 增加"滑翔途中禁传送"。踩过的 1.20.1 坑：RenderLayer 抽象方法在 SRG 里是 m_6494_(…,Mob,…)；Behavior 的钩子是 m_6114_/m_6737_/m_6725_/m_6732_；烟花是 NBT 结构（只写 Fireworks.Flight、不写 Explosions → 挂载烟花到期不炸自己）；EntityAnchorArgument 在 net.minecraft.commands.arguments 包；ItemStack.isEmpty()/EMPTY = m_41619_()/f_41583_。编译零错（215 源文件）、两个 server 回归 PASS、jar 重建部署（neo 客户端/1.20.1 客户端/pack1201）。


[11:00:00] v1.2.0 落地雪补齐独立配置 + 落地水/落地雪默认触发高度改为 4 格（需求：落地雪没有像落地水那样的下探填写面板，配置只能调落地水；同时两者默认释放高度改为 4 格）：①问题——落地雪此前只借用落地水的三个数值（触发高度 / 保持时长 / 下探格数），配置面板里只有"落地水"的那三项，落地雪的调不了。②改动——落地雪新增三项独立配置：触发高度（snowFallDistance，默认 4）、保持时长（snowHold，默认 5 tick）、下探格数（snowLandingScan，默认 2），面板在「战斗与自保 → 落地水/落地雪」区补齐四行（开关 + 三项数值），水/雪两条路径各读各的值，互不影响（两者都有桶时仍优先用水）。③默认值——落地水触发高度 6 → 4 格、落地雪同样 4 格（旧档里存着旧默认 6 的会自动迁移到 4，手动改过的值不动）。④顺带修复一个沉默很久的 bug（实测才发现）——旧版把配置事件 ModConfigEvent 注册在了 Forge 主事件总线上，而它其实是【MOD 总线】事件（implements IModBusEvent，javap 实证），因此该回调从未被调用过：此前累积的所有"旧默认自动迁移"（穿透预算 22→6、看门狗 30/45→8、搭路 8→5 等）其实一直静默失效。现在改为注册到 MOD 总线，并在服务端启动时兜底再跑一次；Neo 侧另发现迁移结果不回写磁盘（运行时值已变但文件仍旧），改为在配置事件里拿到官方 ModConfig 显式 save。⑤验证——实测两树都会把故意写回的旧默认 6.0 迁成 4.0、22 迁成 6，且磁盘文件与运行时一致；两树编译零错、jar 重建部署 6 处、server 回归 PASS。


[10:10:00] v1.2.0 开局赠礼改为原版进度系统：①现象与实证——实机日志显示同一存档 10:55 登录发一次、10:59 又登录又发一次（每次登录都发），背包里确实多出书。旧版是在玩家的 persistentData 里手写一个 maid_smart_blueprints_given 标记来判断"已发过"，这个手搓标记拦不住跨会话重发。②正解（参照 TLM《记忆中的幻想乡》）——那本手册就是用【原版进度系统】发的一次性赠礼：自定义触发器 + 进度 json + 奖励掉落表，「只发一次」由原版进度数据保证（服务端强制持久化、随玩家存档走）。本模组照做：新增 data/maid_smart/advancements/first_join_gift.json（用原版 minecraft:impossible 触发器——它永远不会自己触发，只由本模组显式授予，正好当"已领取"记录）+ 奖励掉落表 gift/first_join.json（两本书）。发奖励只在"从未完成 → 完成"那一刻触发（PlayerAdvancements.award 字节码实证：仅 !wasDone && isDone 才调 AdvancementRewards.grant），重复授予同一个已完成的进度是空操作。③老存档迁移——已用旧标记拿过书的人：静默把进度置为已完成但【不触发奖励】，不会多给一份；没拿过的照常首次发放。④兜底——万一数据包缺失或进度系统异常，退回旧的标记+直接塞背包，至少保证功能不消失。⑤验证——两树编译零错；差分测试证明进度真的加载（旧 jar 2081 条 → 新 jar 2082 条，正好 +1，且无 maid_smart 解析错误）；jar 重建部署 6 处；server 回归 PASS。1.20.1 与 1.21.1 的数据包目录名不同（advancements/loot_tables vs advancement/loot_table），两树已分别适配。


[09:40:00] v1.2.0 复活后反复摔死：实机日志确认 + 死亡循环断路器（整合包实测反馈）：「女仆摔死后自动复活，复活瞬间又摔死，反复循环」。①日志实证（整合包 latest.log）——受击类型明确是 fall（摔落）而不是战斗伤害：`[maidhurt] 类型=fall 伤害=91.4 位置=(28.2,294.0,101.9)`，另有 88.7/87.9/86.1/113.1 等共 9 次，落点高度 y=282~294（空岛平台）与 y=112~115（地面），与「从 y≈386 天上掉下来」完全吻合；女仆只有 20 血，摔伤 86~113 必死。②根因同实测四百五十——安全落点函数把 getHeight（384，维度总高度）与 getMinBuildHeight（-64）写反，两个扫描循环全空 → 兜底落点算到 y≈386 天空，一放出来就自由落体。该修复已在此前完成（上下界归位 + 改走原版 findStandUpPosition）。③新增【死亡循环断路器】——光修落点是治因，但只要任何因素让复活点不安全（地形被改/床被拆/跨维传送），「复活即死」仍会复发，且延迟设得越小转得越快（该整合包把复活延迟调成了 1 秒，所以变成 4 秒一轮的高频循环）。现在：同一只女仆在【上次复活后 30 秒内】又死亡 → 判定复活点不安全，把下次复活退避推迟（1 分钟起、翻倍、上限 10 分钟）并给主人一条明确告警（提示检查重生点或先关自动复活）；正常间隔的死亡不受影响，关掉自动复活时断路器状态一并清空。④顺带说明——TLM 原版右键墓碑只做「归还物品 + 删除墓碑」（字节码实证：全程无 EntityMaid/load/addFreshEntity），并不复活女仆；「自动复活」是本模组新增能力，因此不存在可照抄的原版复活实现，本模组已按原版口径复用玩家复活的 findStandUpPosition 求落点。两树编译零错、断路器退避数学单测通过、jar 重建部署 6 处、server 回归 PASS。


[09:20:00] v1.2.0 排班表血量百分比公式修正：女仆名字后面那个百分比此前取反了——写的是最大/当前（满血恰好 100% 所以一直没被发现），掉血后数字反而往上涨（15/20 显示 133%、10/20 显示 200%、1/20 显示 2000%，完全没法看）。现改为当前/最大，与该功能其余 8 处同类计算口径一致：掉血就显示 75%、50%、5%。两树编译零错、jar 重建部署、server 回归 PASS。


[08:00:00] v1.2.0 复活后原地反复摔死修复 + 清理失效配置项：①根因——「安全落点」函数把两个高度取值写反了：m_141928_ 是 getHeight（主世界 384，维度总高度），m_141937_ 是 getMinBuildHeight（-64），代码却写成「最低建筑高度 = getHeight+1 = 385」「上限 = getMinBuildHeight-3 = -67」，两个扫描循环全成空循环 → 落到兜底分支「原坐标上方 1 格」→ 实际复活点在 y≈386 的天空，女仆一放出来就自由落体摔死；摔死又触发自动收符、放出再摔，形成死循环。1.21.1 侧同名函数同样写反。②修复——上下界各自归位（minY = getMinBuildHeight+1、limitUp = getMaxBuildHeight-3）；复活/死亡传送的落点新增「原版等效站位」：床与重生锚走原版 BedBlock/RespawnAnchorBlock 的 findStandUpPosition（就是玩家复活时站的那一格），床是两格宽方块，之前自己柱状扫描会把她放到床边地面、与原版位置差半张床。③复活清洗（用户建议的兜底）——死亡快照先清 FallDistance/Motion/Fire/DeathTime/HurtTime 再 load（否则她带着「还在下坠」的状态出现，落地首 tick 再吃一次摔落伤害）；复活后清死亡/受击计时、清着火、清全部负面效果，并给抗性提升 V + 抗火各 2 秒作为保护窗口；魂符释放（TLM 释放逻辑读的就是同一份 NBT，已反编译确认）同样先清洗再 load，放出即摔死的死循环一并堵掉。④清理失效配置——「释放血量比（0.35）」从写入之日就没有任何读取方（TLM 释放走的是自己存档里的血量），现整体移除该配置项与相关 NBT 写入。两树编译零错、落点算法单测通过（修复前 y≈386 天空 → 修复后贴地）、jar 重建部署、server 回归 PASS。


[06:40:00] v1.1.1 蛋糕可食用——新增配置开关：①定位——本模组把 minecraft:cake 设为可食用物品（"女仆爱吃蛋糕"的设定），这会让"按食物判定识别投喂目标"的第三方模组（如 solmaiddream）把蛋糕也算作投喂物，并在不检查女仆是否已有主人的情况下抢走这次右键交互；而 Forge 的 Player.interactOn 在事件被取消后【不会再调用 mobInteract】，TLM 的 tameMaid（消耗驯服物+认主）因此不执行；②实测确认——未加装该第三方模组时，喂蛋糕驯服野生女仆完全正常、成就照常触发，说明这是模组间的交互冲突，不是本模组的功能缺陷；③改动——新增 misc.cakeEdible 开关（默认开）：关闭后蛋糕恢复原版行为（只能放置、女仆不再当食物），「女仆吃蛋糕／喂蛋糕加好感」相关功能整体停用，遇到上述冲突可直接关掉；④未做其它改动：不加事件拦截、不改变驯服逻辑。


[06:10:00] v1.1.1 正式发布（1.20.1 Forge 从 v1.1.0 升版）：本版把 v1.1.0 发布之后的全部改进合并发布——内置日语语音包（121 条，含情绪分档与规则气泡补漏）、冷却 HUD（复活倒计时 / 回魂符冷却）在 1.20.1 的渲染根因修复、悬空禁搭方块、床铺互通、女仆自动复活、重锤猛击（1.21.1）、收符期间冷却条保留、睡觉静默气泡与语音、配置面板重组（两级菜单 7 大类 28 小类）、手册详细介绍重写与一键跳转配置等；mods.toml 版本号 1.1.0→1.1.1，附件更名 promaid-1.1.1.jar（升级时请删除旧的 promaid-1.1.0.jar，避免两个同 modId 的 jar 同时加载）。


[05:40:00] v1.1.0 规则气泡语音补漏 6 条：①根因——WorkStatusReporter 的"卡住原因"气泡是以变量传入的（addTextChatBubble(reason)），"附近没有值得挖的矿石 / 我没有镐，没法挖矿——给我一把镐吧 / 附近没有需要打理的农田了 / 被骑乘着，动不了"四条文本从来没有进内置语音包 manifest，JarVoicePack.matchKey 落空 → 回落 TLM TTS 人机语音（不是没触发，是没登记）；②全盘扫描——site-aware 提取两棵树所有 addTextChatBubble 调用（含拼接与三元）并追到变量间接来源（WorkStatusReporter/PerceptionManager/EmotionNetworking/ScheduleBubbleBehavior/SmartDesignTool/MaidAidOwnerBehavior），逐条与 manifest 比对，除上述 4 条外还发现 MaidAidOwnerBehavior 金苹果投喂的"主人，金苹果给你！/主人，附魔金苹果给你！"也未覆盖，共 6 条；③新增 6 条日语配音（附魔金苹果条目排在普通金苹果之前，防 contains 遮蔽），manifest 115→121 条，ASR 客观校验通过（如「近くに掘る価値のある鉱石が無いの」整句转写正确）；④校验——121 条逐条用自身 text 反查，零遮蔽；语音随 jar 分发，重建两 jar + 部署 6 处。


[05:00:00] v1.1.0 冷却 HUD 在 1.20.1 不渲染的【真正根因】：窗口高度取错了 SRG 字段（顺带修建造 HUD 同款）。①证据链——从玩家上一轮会话（已轮转的 debug-1.log.gz）里看到：服务端每秒下发（revive/soul 都有）、客户端有「first snapshot received」和「first render event」，却【没有 first draw】——回调进来了、数据也在，是进到函数里被某个守卫提前 return 了。②根因（对照 srg1201_to_moj1211.json）：Window 的 SRG 名是 m_85445_=getGuiScaledWidth / m_85446_=getGuiScaledHeight / **m_85447_=getX（窗口位置！）**；433/444 两版都误把 m_85447_ 当高度 → h 拿到的是窗口 X 坐标，窗口贴左（最大化/全屏 x=0）时 h-12=-12，而 y=4 永远大于它 → 每帧提前 return，HUD 恒不显示；只有窗口恰好在 x≥16 的位置时才偶然画得出来（这就是"我自己拉个窗口测试就正常、你那边一直没有"的原因）。③修复——冷却 HUD 改用 m_85446_；**建造 HUD 同款错误一并修**（w 应为 m_85445_、h 应为 m_85446_，旧版 w/h 双双取错，折行宽度与"超出屏幕不画"判定一起失真）；另外加一道保险：h≤32 视为取值异常时不拦（宁可画出来也不要因为取值错误整块消失）。④诊断——新增 cooldown hud: SKIP[原因] 一行（按原因各记一次，最多 5 行），以后再出问题能直接看到是 screen / f3 / stale / noRoom / fontNull 哪一个拦的。⑤实测验证——把存档复制成一次性世界、用玩家本人 UUID 启动 1.20.1 客户端、并把窗口贴到屏幕左上角（复现 x=0 的失败条件），杀掉一只女仆触发待复活后：latest.log 依次出现 first snapshot received (3 entries) → **first draw at y=44**，HUD 实际画出来了；测试世界与临时脚本已清理，原存档未改动。1.20.1 编译零错、jar 重建部署、server 回归 PASS。1.21.1 侧本来就用 getGuiScaledHeight（Mojmap），不受影响。


[04:40:00] v1.1.0 语音包情绪化重制 + 落地水语音/颜色两个 bug：①情绪分档——manifest 只存中文 key、旧日语台词没落盘，本次按中文语义【重写 115 条日语台词】，并按情境分五档情绪：战斗 20（郑重参考+语速1.08）/关心 11（放慢0.95）/俏皮 60（含 49 条排班闲聊+钓鱼+天气+拥抱摸头，换用同一位女仆的另一条俏皮参考音频+1.05）/干活 17（1.03）/请求 7（俏皮参考）。两条参考音频都是小酒狐本人，不会变声，只是把"郑重 vs 俏皮"的语气底色换开。②采样参数是 ASR 对照试出来的：temperature 1.15 / top_k 20 / top_p 0.95 这组会把短句合成糊掉（「敵が来たよ、気をつけて！」被转写成「てちがちだよ 塩つけて」），而语速 1.08 + 默认采样（temp 1.0 / k15 / p1.0）清清楚楚——所以情绪靠【参考音频 + 语速】做，采样一律用验证过的默认值；五档各抽一条 ASR 复核（enemy_near/aid_food/hug_02/build_done/no_food_left）全部清晰。③落地水"没语音、还是人机语音"的根因——SystemTTSManager 旧版把「内置包最小间隔（默认 8 秒）」的判断放在命中判定【之前】：命中但被间隔挡下时会继续往下走，最终落到 ④ TLM TTS 合成。现在命中即由内置包负责，被间隔挡下就【静默】（绝不换人机语音），并让落地水/落地雪/敌人靠近三句豁免该间隔。④[警示]红字——落地水/落地雪是女仆自己的动作播报，不再按"敌袭警示"染红（聊天框前缀与世界气泡颜色都改回常规青色）。两树编译零错、jar 重建部署、server 回归 PASS。


[04:05:00] v1.1.0 冷却 HUD 在 1.20.1 恒不渲染根因修复：①证据——1.20.1 客户端 latest.log 有「cooldown hud: first snapshot received」却【没有】「first draw」：数据（服务端每秒下发、含 soul:名字:秒）正常，是客户端渲染回调从未进入。②根因（1.20.1 侧独有，1.21.1 的 NeoForge RenderGuiLayerEvent 同款写法是好的）——旧版用 @Mod.EventBusSubscriber 注解自动注册 + RenderGuiOverlayEvent.Post 只认 HOTBAR 层；1.20.1 上该注解自动注册对客户端 FORGE 总线这类没生效（同类里只有 MOD 总线/懒注册的先例），于是事件永远到不了本类。③修复——改用 RenderGuiEvent.Post（反编译 ForgeGui 实证：render 末尾每帧必然 post 一次，不再依赖"逐层 overlay 事件 + 比对 minecraft:hotbar"），并改为【显式注册】：客户端 Mod 构造期由 PromaidClientSetup.registerHudHooks() 调 CooldownHudRenderer.ensureRegistered()，onSnapshot 里再兜一次（registered 闸防重复）。④诊断加强：新增「cooldown hud: first render event」一行，和 first snapshot / first draw 三点连成一条链，一眼区分"没注册/没事件/没绘制"。⑤本次用客户端烟测验证：把存档复制成一次性世界 _hudtest 后用测试账号启动 1.20.1 客户端，latest.log 依次出现 first snapshot received (2 entries) → first render event → first draw at y=4，HUD 实际画出来了；测试世界与测试账号条目已清理，原存档未改动。1.20.1 编译零错、jar 重建部署、server 回归 PASS。


[03:35:00] v1.1.0 悬空禁搭方块：①统一闸口——四个搭方块模块（自保搭高/搭路/挖矿垫脚/伐木垫脚）都在各自 takeBuildBlock 取料前问 MaidPlaceGuard.blocked：悬空即返回 null → 各模块自然放弃本次放置（不消耗方块、不动世界）；取料失败的四条"没有搭方块的材料"气泡同样静默（否则悬空时会误报）。②判定口径——开关 misc.noPlaceInAir（默认开）；站在地面/水里/岩浆里、骑乘、鞘翅滑翔一律放行；其余情况坠落距离 ≥「落地水触发高度」(combat.waterFallDistance，默认 6) 才算悬空禁搭（这个高度落地水本来就会接管——搭方块既救不了她，又会把落点的水/结构改掉挡住落地水）。③不影响正常垫高——本模组的垫脚都是站在地面/浅跳时往前方脚下垫（fallDistance≈0），照常工作。两树编译零错、jar 重建部署、server 回归 PASS。


[02:35:00] v1.1.0 收符期间冷却条消失修复：①根因——HUD 的"女仆在符里"分支只认【自动标记】的魂符（AUTO_SAVED_TAG + 冷却戳）；玩家手动用空魂符收女仆时魂符是 TLM 自己 new 的、不带任何标记 → 扫背包查不到 → 冷却条消失（冷却其实记在女仆数据里继续计时）。②修复——TLM 的 storeMaidData 会发 MaidAndItemTransformEvent.ToItem（反编译实证：1.21.1 在 AbstractStoreMaidItem、1.20.1 在 ItemSmartSlab），新增 onSoulSpellToItem 钩子：女仆仍在冷却时把冷却戳+名字盖到魂符物品上，自动/手动两条收符路径全覆盖；charmCooldownUntil 放宽为只认冷却戳（AUTO_SAVED_TAG 仍只用于"释放时是否重算冷却"）；自动路径改为先写女仆数据再 storeMaidData，确保钩子拿得到本次冷却。③顺带修魂符落位 bug：主手恒写 items[0]（选中槽≠0 时顶掉 0 号槽物品）、副手误写 armor[0]（顶掉靴子）→ 改为 items[selected] 与 offhand[0]。④冷却HUD 日志补条目详情（kind:名字:剩余/总），便于下次一眼区分"没数据"还是"名字丢了"。两树编译零错、jar 重建部署、server 回归 PASS。


[02:05:00] v1.1.0 敌袭/感知系统消息日语语音 6 条：根因——PerceptionManager 的 7 条感知气泡（敌袭出现/靠近、敌人清空、主人受伤、主人低血、主人注视、天气变化）从未进内置包 manifest，故回落到 TLM TTS 合成（即"人机语音"）；且文案带动态距离/血量数字，exact 模式本就无法命中。修复：新增 6 条 contains 前缀映射 + 日语语音（enemy_near/enemy_clear/owner_hurt/owner_low_hp/owner_looking/weather_change.ogg，GPT-SoVITS 小酒狐合成、峰值归一化 -1.5dB、libvorbis q4）；"有敌人靠近"一条同时命中"…了！离我 X 格"与"…离我 X 格了"，"天气变成"一条覆盖下雨/雷雨/放晴；气泡照旧显示精确数字，语音说固定台词（不念数字）。条数 109→115（60+49+6），面板/手册同步。两树编译零错、jar 重建部署、server 回归 PASS。


[01:40:00] v1.1.0 回魂符冷却放出后消失根因修复：javap 实证 1.21.1 NeoForge 实体持久化键是 NeoForgeData（不是 Forge 1.20.1 的 ForgeData），neo 树把释放时的冷却写进 ForgeData 子标签 → getPersistentData() 读不到 → 冷却失效+HUD 不显示；修复：neo 的 FORGE_DATA_TAG 改 "NeoForgeData"（含 MaidInfo 子 NBT 路径），两树再加「直接写实体 persistentData」双保险（防 TLM 事件后才 apply data）；冷却仍从释放时刻重新起算。两树编译零错、jar 重建部署、server 回归 PASS。


[01:05:00] v1.1.0 右键墓碑不再当场复活：新增 MaidAutoResurrect.cancelPendingOnTombstoneClick（服务端+主手+本人待复活登记时移除该登记），mixin 由 MaidTombstoneReviveMixin（立即复活）重写为 MaidTombstoneClickMixin（不取消事件→右键走 TLM 原版归还物品，同时取消本次自动复活）；删除废弃 reviveFromTombstone/pendingOwnerOfTombstone；mixins.promaid.json 同步改名；手册「女仆自动复活」章对应改写。两树 gen_compile 重建源清单 + 编译零错、jar 重建部署、server 回归 PASS。


[23:45:00] v1.1.0 拥抱/摸头亲昵语音 6 条：EmotionNetworking 的 say() 里拥抱 3 条（抱抱！好温暖～/主人的怀抱最安心了～/嘿嘿，被抱着都不想动啦～）+ 摸头 3 条（嘿嘿，好舒服～/摸摸头就又有精神了！/最喜欢主人摸头了～）用 GPT-SoVITS 小酒狐合成日语并峰值归一化 -1.5dB，产物 hug_01~03/pat_01~03.ogg 打进 jar（两树），manifest 追加 6 条 exact → 共 109 条（54+49+6），已脚本校验 6/6 命中；括号旁白叙述不进语音（按要求排除）；无需改代码（say→addTextChatBubble→ChatBubbleLimitMixin→SystemTTSManager 本就会命中内置包）；面板/手册条数 103→109。两树编译零错、jar 重建部署、server 回归 PASS。


[23:00:00] v1.1.0 冷却 HUD 照搬 HeartPact 显示机制 + 收符窗口缺口：①证据——1.20.1 实例 promaid.log 显示 22:30~22:31 服务端每秒确有「[冷却HUD] 下发 1 条」但屏幕无 HUD → 数据正常、渲染通路坏；②改为 @EventBusSubscriber 注解常驻注册（去掉懒注册），绘制事件只认 HOTBAR 层（1.20.1 RenderGuiOverlayEvent.Post+VanillaGuiOverlay.HOTBAR；1.21.1 RenderGuiLayerEvent.Post+VanillaGuiLayers.HOTBAR），带阴影/升序/M:SS；③补收符窗口缺口——MaidSoulSpellGuard 收符时把女仆名写进魂符（AUTO_SAVED_NAME_TAG）并新增 charmCooldownUntil/charmMaidName，CooldownHudTracker 扫描玩家背包冷却中的魂符，女仆在符里也显示「回魂符 · 名字 M:SS」；④客户端加两条诊断日志（first snapshot / first draw）与服务端「冷却HUD」配对。两树编译零错、jar 重建部署、server 回归 PASS。


[22:25:00] v1.1.0 搭路默认开启 + 配置迁到「移动与行为」大类：①bridge.enabled 默认 false→true（两树），注释/面板说明「默认关」→「默认开」；已有实例 toml 里写死的 enabled=false 也一并改为 true（6 个本地实例），否则旧值会盖过新默认；②Section.BRIDGE 大类 SURVIVAL→MOVE，groupHint 两处同步；③手册同步：可点链接改 [[@MOVE:BRIDGE|搭路]]、大类小类清单与「怎么找开关」迁移、被动技能章两处大类别名改写、搭路「默认关」→「默认开」；④BridgeUpBehavior 类注释同步。两树编译零错、jar 重建部署、server 回归 PASS。


[22:10:00] v1.1.0 冷却 HUD 恒不渲染根因修复：①根因——四个 HUD 渲染器（两树×冷却/建造）的 F3 门禁都误写成 options.useNativeTransport（该值默认 true，玩家 options.txt 亦为 true）→ 条件恒真 → onGui 一进来就 return，HUD 永远不画；正确字段 1.20.1 为 Options.renderDebug（f_92063_，Gui 字节码实证）/1.21.1 为 getDebugOverlay().showDebugScreen()（javap 实证）；②连带修 1.21.1 建造 HUD 的 w/h 颠倒与折行误用 drawCenteredString（x=4 为中心画到屏幕外）；③显示对齐 HeartPact 分娩倒计时——带阴影、按剩余秒升序、时间 M:SS；1.20.1 改用 RenderGuiEvent.Post 整帧只画一次；④CooldownHudTracker 加限频「冷却HUD」日志便于区分没数据/没渲染。两树编译零错、jar 重建部署、server 回归 PASS。


[21:55:00] v1.1.0 睡觉中静默系统气泡与语音：改动用 LivingEntity.isSleeping()（1.20.1 SRG m_5803_，字节码实证=getSleepingPos().isPresent()，与 startSleeping 同源）判定——①ChatBubbleLimitMixin.addTextChatBubble HEAD 睡着直接 -1，拦住所有系统气泡 + 主人聊天框同步 + 汇入的 TTS/内置语音包朗读；②SystemTTSManager.speak 入口兜底 return，拦住排班气泡等直接调用者；③ScheduleBubbleBehavior 显式判睡（原为"简化不判"注释）；④MaidCakeEatHandler 直发 addChatBubble 路径睡着不弹蓝气泡/消息，回血与好感照常。两树编译零错、server 回归 PASS、已部署。


[21:40:00] v1.1.0 排班气泡日语语音包：①ScheduleBubbleBehavior 的 49 条排班贴身气泡全部翻成日语，用 GPT-SoVITS 小酒狐合成（同四百二十流程），每条峰值归一化到 -1.5 dB；②sched_01~49.ogg 打进 jar（两树），manifest 追加 49 条 exact 映射（追加在 54 条系统消息之后，脚本校验无截胡）；③冒泡后调用 SystemTTSManager.speak 接入语音链（命中内置包即播并压制原生）；内置包总条数 54→103，面板与手册同步；④两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[21:20:00] v1.1.0 内置语音包响度修复：①根因——ffmpeg 实测内置 ogg 平均 -32 dB / 峰值 -12 dB（GPT-SoVITS 直出未做响度处理），是素材太轻而非播放链路问题；②修复——两树各 54 条 ogg 峰值归一化到 -1.5 dB（volume+alimiter，libvorbis q4），平均响度提升约 11 dB（-32→-21 dB）；音量上限 5.0→20.0（jarPackVolume，默认仍 1.0）；③配置面板与手册（含条数 53→54）同步；④两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端（含整合包实例）与 pack1201


[21:00:00] v1.1.0 女仆复活机制修复：①根因——到期登记用 getGameTime、检查用 getTickCount，老存档 now>=due 永不成立（不触发）；且开启时清空墓碑容器导致右键墓碑拿不到东西（看起来像点击复活没了）；②修复——统一 getGameTime 口径（HUD 同步）；不再清空墓碑，新增 mixin MaidTombstoneReviveMixin 注入 EntityTombstone 右键：开启时主人右键墓碑=立即复活并在主人重生点落地，关闭时完全不介入=纯 TLM 原版（右键归还物品），并在关闭时丢弃待复活表防重复复活；③新增配置 autoResurrectTiming（0=延迟 N 秒 / 1=次日黎明，照驯养革新宠物床），面板「生存与复活→死亡与复活」加「复活时机」循环按钮，中英翻译与手册同步；④两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[20:50:00] v1.1.0 主人踩踏刷新扩展到挖矿/伐木/自保垫脚方块：PlacedBlockTracker 的 refreshOnOwnerStand 由仅搭路扩展到四套实例全部开启——挖矿/伐木（4.0/6.0/true）、自保战斗方块（0.5/12.0/true）与搭路一致：绑定女仆的主人在同维度踩在其方块上即补满回收寿命，玩家跟在女仆后面挖矿/伐木/爬塔不会踩到即将到期消失的垫脚方块而坠落；手册说明同步泛化；两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[20:40:00] v1.1.0 手册详细介绍重写 + 一键跳转配置：①手册正文新增链接语法 [[@大类:小类:行标签|文字]]——GuideScreen 换行把链接当原子 token（按标签宽度计量、不拆行），渲染成青色下划线并记录命中区，点击经 PromaidConfigScreen.openAt 直达配置对应页（大类/小类/具体参数行三级定位，跳转后目标行金色高亮，配置面板以手册为父界面、保存并返回回到手册）；②新增首章「功能总览·一键跳转配置」（7 大类 28 小类的可点击清单）；每章正文末尾由 configLinkFor(标题) 自动附加「配置入口」链接；手册使用指南补充用法说明；③两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[20:30:00] v1.1.0 配置面板重组：①两级菜单——新增 Group（7 大类），重写 Section 为 28 小类（各自带所属大类），首页列大类、大类页列小类、小类页才是参数；原 passiveRows/combatRows/miscRows 三大页约 230 条参数按配置键拆成 19 个细化小类方法（生产与工作/AI 与对话/战斗与自保/生存与复活/移动与行为/语音与显示/系统与杂项）；两级菜单共用自适应 menuGrid，小类页返回分类、大类页返回大类；②开关集中化——移除手册内「自动复活设置」「日语语音包设置」两个设置页入口（相关代码标 @Deprecated 保留不可达），所有开关只在配置面板；手册导航章重写并同步全部交叉引用路径；③完整性脚本校验行数/配置键与重组前一致（Bool112/Num175/Btn6/Cycle3/Text5/Info1，无丢失重复）；④两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[20:20:00] v1.1.0 搭路方块 CD·主人踩踏也重置：PlacedBlockTracker 新增 refreshOnOwnerStand 构造开关（默认 false，挖矿/伐木/自保方块不变），expirePlaced 在原「女仆踩着/女仆在附近」刷新之外，新增 masterOnBlock——搭路实例开启后，绑定女仆的主人也踩在该方块上同样补满寿命；BridgeUpBehavior 两树搭路追踪器改传 true。两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201


[20:10:00] v1.1.0 冷却可视化 HUD：①服务端——MaidAutoResurrect 的 Pending 增记女仆显示名并提供 hudReviveEntries（复活倒计时与到期判定同源），MaidSoulSpellGuard 新增 hudCooldownSeconds（读 persistentData 的回魂符冷却），新增 CooldownHudTracker 每秒按主人打包两类倒计时；②网络/客户端——新增 S2C CooldownHudPacket 与客户端 CooldownHudRenderer（屏幕左上角「冷却」列表：复活倒计时 + 回魂符冷却，排在建造进度下方不重叠，开界面/F3 不画，2.5 秒无新快照自动清空），BuildHudRenderer 两树新增 bottomY() 供避让；③配置——杂项新增「冷却可视化 HUD」（misc.cooldownHud，默认开）+ 中英翻译 + 配置面板行，手册「女仆自动复活」章补充说明；④两树编译零错、test_server 1201/neoforge1211 均 PASS，已部署客户端与 pack1201（rename-swap）


[04:40:00] v1.1.0 内置日语语音包：①语音素材——用本机 GPT-SoVITS（小酒狐权重 e15/e8_s184 + 该角色日语参考音）合成 53 条系统消息日语台词（覆盖建造/投喂治疗/自保/落地水雪/钓鱼/不死图腾/排班/搭路/酿造/挖矿伐木/攻击等 49 个系统消息点 + 4 条兜底），ffmpeg 转 vorbis ogg 打进 jar（assets/promaid/voice/ + manifest.json）；②播放链路——新增 JarVoicePack（jar 内资源包，classloader 读取，最高优先级：先于磁盘语音包/TTS 缓存/TTS 合成），SystemTTSManager 命中后经新 S2C 包 PlayJarVoicePacket 只下发【女仆实体 id + 文件名】，客户端从自己 jar 取字节播放（省流量、两端一致）；内置包有独立门禁（开关 + 自带最小间隔），不要求配置 TTS 站点——开箱即用；③自管播放——新增 PromaidVoiceSoundInstance 继承 TLM MaidAISoundInstance（复用其 OGG/Opus/MP3 解码），构造后覆盖音量字段 = TTS 倍率 × 内置包倍率（要求的"可调音量"）；④压制原生语音包——新增 ClientVoicePlayback：播放前按音频字节估算时长开启压制窗口，窗口内 PlaySoundEvent 里把TLM 原生 MaidSoundInstance（1.20.1/1.21.1）与 MaidSoundInstanceAtPos（1.21.1）setSound(null)，窗口到期自动解除（要求的"播放时卡掉原生、播完解除、优先级更高"）；试听音不受压制；⑤手册——目录页新增「日语语音包设置」整页（开关 + 音量 + 最小间隔，与自动复活设置并排），配置面板「语音 → 内置日语语音包」四行（开关/音量/最小间隔/播放时暂压原生），四份 lang 补条目；⑥两树同步（1.20.1 SRG / 1.21.1 Mojmap），编译零错。


[04:35:00] v1.1.0 排班表快捷调整引入整页任务选择：①第 1 页「快捷设置」的任务行，点中间任务名 → 打开整页任务选择（与第 2 页排班槽共用同一页实现）——整页列出全部任务、分页点选，不再"点一下只换下一个"（任务多时要循环点十几下）；②两种来源靠 pickQuick 区分去向：快捷页进来=选中即立即生效（走既有 QuickApply 包，服务端校验排班锁定）并回快捷页；排班页进来=填入时段槽 + 未保存脏标记（原行为不变）；③左边「空闲」按钮快捷页=切到 TLM 空闲任务（touhou_little_maid:idle），排班页=清空槽；左上返回按钮按来源显示「返回快捷设置 / 返回排班」；④两侧 ◀▶ 箭头保留（旧习惯不变）；⑤标题与提示随来源切换（"点击任务立即切换她的当前任务" / "点击任务填入第 N 时段"）；两树同步，编译零错。


[04:30:00] v1.1.0 床铺互通·女仆床↔玩家床：①方向一【女仆睡原版床】——新增 MaidBedInteropBehavior 挂 REST 活动（经 TLM 扩展点 getRestBehaviors 注册，优先级 6 排在 TLM 自带 MaidBedTask=5 之后：女仆床优先），用原版 PoiTypes.HOME 找最近的原版床（16 色床 HEAD 都注册在 HOME），校验 is(BlockTags.BEDS)+PART==HEAD+未占用+在活动范围内，走到 2 格内调 startSleeping+setPos（占床/回血/好感度全走 TLM 既有逻辑）；TLM 原版 MaidBedTask 只查 InitPoi.MAID_BED 且硬判 state.is(女仆床)，所以原版床原本对女仆完全不可用；②方向二【玩家睡女仆床】——新增 MaidBedBlockInteropMixin（@Mixin BlockMaidBed）：isBed 对玩家与 null（重生点解析）返回 true（女仆仍走 TLM 原分支）、use/useItemOn 非染料时服务端调 startSleepInBed（染料仍优先走 TLM 染色；潜行右键不躺下）、neo 版并显式实现 getRespawnPosition（NeoForge 默认恒空，不实现则死亡复活会报"重生方块缺失"）；女仆床 OCCUPIED/PART 与 BedBlock 是同一 Property 实例、FACING 来自 HorizontalDirectionalBlock，占床/床朝向用平台默认实现即可；③两方向共用开关「模组详细配置→杂项→床铺互通」（默认开，关掉恢复 TLM 原版）；④两树同步（1.20.1 SRG / 1.21.1 Mojmap）；neo 编译 classpath 把 NeoForge 修补 jar 排到 vanilla 前（RespawnPosAngle 在未修补 vanilla 里是包私有，顺位反了编译不过——实测）；手册新增「床铺互通」章节；两树编译零错。


[04:25:00] v1.1.0 手册内自动复活设置页：①《详细介绍》章节目录页右下角新增「自动复活设置」按钮——一页直接改【总开关 + 复活延迟（秒）+ 复活血量比】三项，「保存并返回目录」即时生效，不用再跳「模组详细配置」面板；②输入交互照搬配置面板既有管线（点击显式聚焦 + 自跟踪 activeBox + charTyped/keyPressed 直接转发，1.20.1 走 m_5534_/m_7933_），避开焦点链差异导致"点得进打不了字"；数字文本延迟提交（输入时只校验红/白字，离开本页或关界面时统一写入 + SPEC.save()），不在输入路径写配置；③越界钳制到声明范围（延迟 1~86400 秒、血量比 0.05~1.0），非法/空文本跳过保留原值；④《详细介绍》新增「女仆自动复活（v1.1.0）」章节：运作链路、话语提示、血量比、保姆式测试步骤与"在哪里调开关和 CD"；顺手把手册一处过期"详见第 20 章"改为按章节标题引用（插入新章节不再编号漂移）；⑤设置页布局 = 控件与渲染共用一套坐标（行高随窗口自适应、说明按可用高度取舍），小窗口不重叠；⑥两树同步（1.20.1 SRG / 1.21.1 Mojmap），两树编译零错 + 服务器回归 PASS。


[04:20:00] v1.1.0 女仆自动复活（新功能）：①死亡瞬间抓取女仆完整存档 NBT（死亡事件早于背包搬走），墓碑创建时配对墓碑 UUID，写入持久化待复活表（SavedData 存主世界，跨维度共享、重启不丢）；②延迟到期（默认 60 秒）自动让墓碑消失（discard，同步清理 TLM 墓碑记录）并在主人重生点复活女仆（床/重生锚；无重生点则主世界出生点，安全落点）+ 气泡"主人……我回来了。"；③复活与 TLM 魂符释放同构（new+load+复用原 UUID+设落点血量+addFreshEntity），背包/饰品/任务/模型原样恢复；④复活时女仆用话语提示玩家（气泡"主人，我回来啦！让你担心了～" + 主人系统消息"你的女仆 XX 已复活"）；⑤抓取快照后清空墓碑容器（墓碑转纯标记，物品只存快照、复活归还）——防 60 秒窗口内拾取墓碑导致物品复制；⑥复活血量默认满血可调、deathTime/摔落/速度清零；主人不在线墓碑保留等她上线、无主女仆不复活；⑦开关/延迟/血量比三项在「战斗自保 → 女仆自动复活」小节，关掉恢复原版死亡流程。


[04:10:00] v1.1.0 彻底移除第三方专属软联动(问题：保留按攻击力属性识别的通用武器联动不动，只把对特定第三方模组的专属联动删干净)：①精妙储存/超越维度——删除 com.maidsmart.storage 整包(BoundStorageInteractHandler/StorageBindingStore)与 storage_bind_card/storage_unbind_card 两个绑定卡物品，含模型/贴图/配方/语言，并移除全部调用点(建造材料统计与取物回退、酿药取物回退、创造栏、主类注册)；②爱憎分明——删除饥饿门控 mixin、4 个联动配置项与配置面板「爱憎分明模组调试」整页(含版本反射/接口探测)、自保极端饥饿反射保命、相关语言键；③心契誓约(maidmarriage/Heartfelt)——删除拥抱/子女交互与调试面板 mixin、关系标签反射(MaidConfigMemoryMixin 的 RelationshipExemption)、纪念日情绪脉冲(AffectManager.onAnniversaryDay/onAnniversaryApproaching)、建造心契交互锁、Heartfelt 纪念日联动配置；④配置项清理——MEMORY_RELATIONSHIP_ADAPTER / MEMORY_RELATION_SCAN / MEMORY_TRUST_DELTA / MEMORY_HEARTFELT_ANNIVERSARY 及界面行、callresponse 语言键全部移除；⑤手册/README/CHANGELOG/mods.toml 描述同步为纯独立版。保留：按攻击力属性的通用武器判定/自动装备(tacz/卓越前线/拔刀剑/万法皆通等模组武器天然参与，无需专属代码)。两树同步，1.20.1 与 1.21.1 服务器回归均 PASS。


[03:00:00] v1.1.0 手册补写：战斗与自保——宠物免伤用法（命名牌改「玩家宠物/主人的宠物」、主人驯服的动物自动识别、模组宠物标记；女仆不锁定不攻击、溅射不误伤、仇恨自动清除）、带保命道具（绀珠之药/不死图腾）低血不逃跑、背包有模组武器必定切专属战斗模式（拔刀剑→拔刀剑模式）；被动技能——搭路新启动规则（只要水平距离+朝主人前方脚下悬空就铺方块踩上去，平地跟踪距离无上限；平桥启动距离默认 6 格 3~64 可调；挖矿/伐木垫块同款踩上）；农场与杂项——回家与传送（跨维度跟随/同维度 48 格拉回/Y 轴垂直 16 格拉回，主人旁安全落点才传，排班与守家不响应）；排班表——活动半径默认 32 格（杂项可调 8~512）、空闲散步（默认 10 秒一次/16 格半径/0.7 倍速，参数在杂项）、排班锁定下原版界面改任务当场弹回；烧制——什么能烧就烧什么（矿石/沙子/圆石/原木/模组食材，cookSmeltAny 默认开）、多女仆各占一炉（占用表+不抢炉+日志限频）；武器与工具——剪刀任务自动装备剪刀（forge:shears 标签模组剪刀同认）；对话——击杀邀功气泡默认关（想听在「对话提示」打开）；建造——走出红色区块橙色幽灵方块仍可见（越远越淡，不再整片消失）。同步项目根 CHANGELOG.md 实测二百零三。


[02:45:00] v1.1.0 合集·战斗与搭路（一百七十三~一百八十二，对照 v1.0.4 的新功能/修复摘编；完整逐条见项目根 CHANGELOG.md）：①自主战斗还原根治——还原扫描用无限 AABB 查询永远命中空（section 溢出坍塌，日志 active=0 但标记在），改有限 AABB（±131072/±4096）后战斗还原/排班扫描真正生效；②模组武器专属模式确定性选择——拔刀剑等被原版 attack 认作武器时同池加权随机 1/3 落原版，现同池有模组任务则原版让位；③熔炉多女仆分配+通用烧制——FURNACE_USERS 占用表、抢炉竞争校验、喂料全熔炉配方非装备类回退（cookSmeltAny）；④搭路新触发口径——水平距离+朝主人脚前方缺口即铺桥踩上（不看主人高低、缺口分支无距离上限），平桥冷却永锁修复（只在垂直腿递减=只搭一块），新增 walkOn 持续推送踩块（旧版一次脉冲半格就停在沟对面）。


[02:47:00] v1.1.0 合集·排班/散步/传送/防护（一百八十三~一百九十五）：①排班活动范围与散步——排班/home 模式活动半径增幅（scheduleActivityRange 默认 32，原 TLM 8~16）、空闲散步替代 TLM 原生（原生 0.3 倍速 5 格概率两小时一次；现在间隔 200tick/半径 16/速度 0.7）；②排班三连修——段软失败日志 5 分钟限频、散步全速鬼畜降速、TLM 面板换任务客户端脱钩修复（守卫拒绝即同步真实任务回客户端）；③跨维度跟随 home/排班拦截（同维度拉回早就有，跨维补上）；④Y 轴拉回——3D 48 格门槛按水平标定，竖直搭高 30 格不触发；新增 maidSameDimVertical 默认 16 格+主人旁安全落点判定；⑤宠物免疫——名字含「玩家宠物/主人的宠物」/MaidNoAttack 前缀/主人可驯服宠物：仇恨不进（canAttack 拦截+自清）、AOE 伤害全免（LivingHurtEvent 总闸）；⑥挖矿/伐木垫块走不上方块死循环修复（BlockWalkOn 持续推送踩块）；⑦击杀邀功气泡默认关（proactiveKill=false）；⑧建造投影 96 格剔除移除（走出区块幽灵方块可见）、Z 键旋转朝向提示每次点击都弹出；⑨排班表/手册贴图更换。


[05:50:00] v1.1.0 实测一百二十三：搭路启动距离门槛放宽。核查结论：已空中的女仆会（airMaxDist 开大到 ≥50 即逐块搭过去）；落地女仆旧版不会——canUse 距离上限只在女仆【自己已空中】时才用 airMaxDist，落地恒用 maxDist（7 格）：主人悬空/飞高时她走不到（导航无路）、传送可能找不到落点（无落脚方块），搭方块是唯一通路却被 7 格门槛挡在门外原地干站。修复：距离上限判定增加两个放宽条件——主人【空中】（onGround=false，创造飞行/下落）或主人【高于女仆】（dy≥1）时，落地女仆同样用 airMaxDist。安全论证：落地平地上平桥只在"前方脚下悬空"时才垫块（tryAirBridgeStep 要求 fill 为空气），实心地面走导航零副作用，不会在地上搭一串方块；跨空/爬高才真正搭方块。注意边界：airMaxDist 设 50 且主人恰在 50.0 格时因 `>=` 判定被拒，开大一点（60/64）更稳。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:43:00] v1.1.0 实测一百二十二：搭路速度对齐玩家 + 方块滞留时间联动。原版无放置冷却，玩家持续搭块约 4~6 块/秒（人手点击上限）。取值：搭路节奏 BRIDGE_STEP_COOLDOWN 默认 8 → 5 tick/块（≈4 块/秒，玩家区间中值——与玩家基本持平略偏快）；方块滞留 BRIDGE_PLACED_LIFETIME 默认 10 → 2 秒（配置下限 3 → 1），纯数学联动：2 秒 × 4 块/秒 = 稳态最多 ≈8 块同时存在（实测一百二十一的"≤8 块"目标，站立格自动延后不影响移动中的稳态），不再堆成片。存量配置迁移（onConfigLoad）：stepCooldown 旧默认 8 与玩家手调的激进值 2 → 5；placedLifetime 旧默认 10 → 2。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:39:00] v1.1.0 实测一百二十一：搭路启动门槛落地水平远距放宽。根因：BridgeUpBehavior.canUse 的启动门槛是【纯高度差】——dy >= minDy（默认 2）才允许行为启动；两人各自向上搭时主人与女仆高度长期接近（dy 只有 0~1，主人未达门槛高差），水平距离却越拉越大 → 搭路行为永远不启动，而 tick 里明明为 dy<minDy 场景准备了平桥分支（tryAirBridgeStep——主人高度附近铺平桥横向逼近，实测一百零六注释也写明"水平距离远（>3格）时也允许启动"），但那个水平放宽当时只写在注释里、代码从未落地，平桥分支被启动门槛挡在门外形同虚设。修复：canUse 落地水平远距放宽——水平距离 >3 格且主人【不低于】女仆（dy >= 0）即允许启动，铺平桥朝主人横向逼近（站在自己垫的方块上算空中，24 格内有效；地面时同门槛只导航不搭块，无副作用）；高度门槛（dy >= minDy）只在近距离垂直搭高场景严格生效。垂直垫块/斜上台阶分支本就要求 dy >= 1，不会在平路误触发。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:34:00] v1.1.0 实测一百二十：自主战斗与模组武器联动三修（玩家确认上轮审计的三个边界逐一修复）。①【枪械弹药闸】AutoCombatSwitch.buildPools 新增闸门：gun_attack 任务进池必须弹药可用——TLM TaskGunAttack.isWeapon 只查 isGun（javap 实证），旧版有枪没子弹也会被切到 gun_attack → TLM 换弹失败干等；hasGunAndAmmo（背包有枪+任意弹药；卓越前线能量武器免弹药）不满足 → 枪械任务不进任何池，参战选任务/距离切换都不会把她切到打不出伤害的模式。②【远程分类任务走远程走位】MaidCombatTacticsBehavior 新增 isRangedTaskActive（当前任务为 isRangedTask 判远程的攻击任务）并接入三处：主走位分支 m_6725_（法术书/法杖等非投射远程武器不再落进 meleeTick 跳劈贴脸，改走 rangedTick 拉开距离风筝）、接战范围 engageRange（远程任务按任务搜索半径而非近战 8 格）、举盾判定 shouldUseShield（远程任务自己放风筝不举盾）——与距离切换的远程分类彻底对齐，消除"切换层认她远程、走位层当她近战"的矛盾。③【过时注释更正】retuneCombatTactics 的"模组隔离（模组任务永不被换出）"注释是实测八十六的旧语义，实测一百零七已改为任意 IAttackTask 双向参与——注释更新为实际行为，避免后续维护误解。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:27:00] v1.1.0 实测一百一十九：伐木发呆根因修复。日志实证：latest.log 显示女仆沿树干一截截砍到第 4 节后冻结在树基（wood stuck-reset idle=208t），看门狗确实触发但重置后几何不变照样冻死。根因一【伸手判定取方块中心】：砍伐距离判定是女仆脚底到【目标方块中心】的距离（m_20275_(pos+0.5)），站进树洞/树基时下一根木头的中心恰好压在 4.5 格伸手边界上——女仆实体只要水平偏移 0.1 格（走进树洞/寻路抖动）distSq 就超过 reachSq → 永远进搭路分支 → 搭方块被防窒息检查/没材料挡住 → 原地发呆（看门狗重置、收魂符重放都救不了，因为几何没变）。修复：伸手判定改为【目标方块 AABB 最近点】距离（玩家手长的正确语义——方块任一点够得着就能挖）——正上方木头的底面离女仆只有 dy=4 格，稳定够得着，逐节往上啃；够不着的（dy≥5）才走搭方块爬树。根因二【firstWoodAbove 从 dy=2 起跳】：树基的木头被砍掉后女仆站进树洞，下一根木头在她头平齐的 dy=1 处，旧版从 dy=2 起跳会漏掉它。挖矿行为同款中心点边界问题一并修复（站坑里挖正上方矿石同样会冻结）。修复后看门狗/魂符重放不再是救火手段——几何冻结从源头消失。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:16:00] v1.1.0 实测一百一十八：死亡传送重生点方块校验。上一轮（实测一百一十）已把死亡传送目标从"死亡地点"改为"玩家重生点访问器"（m_8961_/m_8963_），但还有个边界没盖：respawnPosition 字段在【床被拆/被占/重生锚空充能】时仍存旧坐标（原版只在复活流程才把它清空，死亡瞬间读到的是旧值）——直接传送会把女仆送到"床原来的位置"，而玩家实际复活在世界出生点，两处分离（此前仅靠复活保险纠偏，且旧床位置离实际复活点 64 格内时保险还会跳过）。修复：doDeathTeleport 加【原版同款校验】（镜像 Player.findRespawnPositionAndUseSpawnBlock，m_36130_ javap 实证）：重生点方块必须是【床】（维度允许睡觉 BedBlock.m_49488_=canSetSpawn + 床边能站起 BedBlock.m_260958_=findStandUpPosition 非空）或【充能重生锚】（RespawnAnchorBlock.f_55833_=CHARGES>0 + RespawnAnchorBlock.m_55850_ 维度允许，下界 true）；两者都不是（/spawnpoint 空地、床没了、锚空电）→ 直接主世界出生点兜底——第一次传送就瞄准正确位置，不依赖保险纠偏，与玩家实际复活点严格一致。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:11:00] v1.1.0 实测一百一十七：危险方块避让改用【原版寻路 malus 机制】。反馈"女仆仍会走岩浆块这种威胁方块——留一格站中心、周边围岩浆块、拿蛋糕吸引，女仆还是往上走；能不能套用原版生物的一些寻路逻辑"——核查证实这正是原版层面的根因：javap 字节码实证原版寻路对危险格子的通行裁决是 Mob.getPathfindingMalus（m_21439_，没设覆盖时回落 BlockPathTypes 枚举默认值 m_77124_）——LAVA=-1（不可通行，原版生物自动躲岩浆）、DAMAGE_OTHER=-1（仙人掌/浆果丛自动躲）、而 **DAMAGE_FIRE=+16（正数=可通行只是代价高）→ 原版生物踩岩浆块/火/营火**。TLM 女仆寻路全链路（MaidPathNavigation → MaidWrappedPathFinder → MaidNodeEvaluator，全部 javap 实证 extends 原版 WalkNodeEvaluator 体系，节点类型评估 m_77567_ → 缓存 m_264026_ → m_7209_）对岩浆本体格和岩浆上方的空气格都返回 DAMAGE_FIRE，16 的 malus 不拦路 → 女仆跟着蛋糕踩上去。修复：新增 MaidDangerMalusHandler——用原版决策点本身，把女仆的 DAMAGE_FIRE/DAMAGE_OTHER/LAVA/POWDER_SNOW 按配置危险表覆盖为 -1（m_21441_=setPathfindingMalus，与 LAVA 完全同款不可通行语义）：原版 WalkNodeEvaluator 生成邻居节点时 malus<0 节点直接跳过（m_164725_）、m_7209_ 对 malus<0 类型直接短路，对所有寻路路径（跟随/巡逻/干活/战斗）统一生效，不依赖任何注入点。应用时机：女仆入场（EntityJoinLevelEvent）+ 每 5 秒全服刷新（覆盖配置热改，Map.put 开销可忽略）；危险表→类型的映射按原版 raw 口径（岩浆块/火/灵魂火/营火/灵魂营火→DAMAGE_FIRE，岩浆→LAVA，仙人掌/浆果丛/凋零玫瑰/石笋→DAMAGE_OTHER，细雪→POWDER_SNOW），表外/开关关时恢复枚举默认值；DANGER_* 邻接型不管理（允许在危险旁正常走位绕行）。与 MaidDangerPathMixin 双保险：mixin 在节点层按配置方块 ID 拦截（覆盖模组危险方块、头顶灼烧型），本处理器按原版类型覆盖 malus（独立于注入点）——岩浆/火/营火两路都拦。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:51:00] v1.1.0 实测一百一十六：远程风筝距离管理重做。反馈"女仆拿弓箭时没有与怪物拉开明显的距离"——根因：rangedTick 旧版只在敌人贴进 ideal×0.55（弓≈4.95 格）才后退、退到 ideal-dist+2（≈6 格）就开始绕圈（半径 0.45×射程≈6.75）——怪物几乎全程贴在 5~7 格内，弓手看不出在放风筝。修复：①后退阈值提前到 ideal×0.75（弓≈6.75 格——怪物刚进近战威胁区就拉开）；②后退目标 = ideal+2（弓≈11 格——干净拉出一整段射程差）；③后退速度 1.15 → 1.3（肉眼可见甩开追兵）；④绕圈半径 = ideal（弓≈9 格）——在理想射程圆周上横移放风筝，保持清晰距离；⑤太远阈值 1.4 → 1.5（逼近节奏不变）。远程距离管理（m_20270_ 已 javap 实证返回线性距离，阈值比较无误）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:47:00] v1.1.0 实测一百一十五：距离切换武器设计落实。反馈"不会根据距离远近切换武器，之前的设计未落实"——根因：按距离切近/远程的 retuneCombatTactics 只在 hasThreatNearby（**8 格威胁圈**）为真时每秒评估，而距离切换本身覆盖 6~16 格——**8~16 格范围内的远处敌人**（不主动攻击/动态威胁圈过期）进不了切换分支，女仆不是切弓而是直接走还原退出战斗（设计在 8~16 格出现空洞）。修复：还原扫描新增远处敌人兜底分支——威胁圈外但最近敌人在近战够不着、远程够得着的 6~16 格时，先尝试按距离切远程：切成功（近战→远程）或已是远程任务 → 刷新威胁计时维持战斗继续射（此时 CORE 自动装备行为会把弓/弩/枪装到主手，任务切换即武器切换）；背包没有远程武器切不动 → 落回正常还原（10 秒安全期后退出）。切换时写 promaid.log（"距离切换：x -> y（远处威胁 N 格）"）便于验证。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:43:00] v1.1.0 实测一百一十四：自主战斗还原失效根因修复。玩家复测"威胁解除后仍还原不到之前的状态"——日志实证：promaid.log 15 次参战（每次"idle -> 战斗任务"）零还原。根因：isOnAssignedCombatTask 用 maid.getTask()（TLM 字节码实证读同步数据 DATA_TASK，且 findTask(uid).orElse(getIdleTask())——uid 解析失败/同步抖动直接回落 idle）与 ASSIGNED 比对，战中 retune 换战术/同步抖动/第三方模组换任务后 getTask() 与 ASSIGNED 不一致 → 误判"玩家接管"→ 静默清标记 → 战斗任务永不还原。修复：①isOnAssignedCombatTask 放宽——当前任务只要仍是【攻击类任务】（IAttackTask，与 buildPools 同口径）一律视为本系统战斗，还原扫描照常推进；只有任务真被换成非攻击任务才按接管处理；②还原分支 stillOnCombat 同步放宽（换过战术的战斗任务也还原到战斗前任务）；③tryEngageMaid 的静默"接管退出"路径补运行日志（下次复测可直接从 promaid.log 定位是哪种情况）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:34:00] v1.1.0 实测一百一十三：Home 模式呆立根治（守卫巡逻）。玩家澄清"即使 TLM 原版 GUI 手动 home 也呆立，原因未知"——javap 反编译 TLM 实证 home 女仆移动驱动只有三个：①SchedulePos.tick 每 2 秒回家走位（仅出圈才给目标，站 home 中心直接早退）；②IDLE/WORK 活动 MaidRunOne 随机散步；③follow 任务启动占位（home 下空转）。断点叠加：排班路径不设锚点（一百一十二已修）、isNonCombatWork 漏排跟随 → SchedulePosTickMixin 把 tick 整段 cancel（跟随任务命中"干活不打断"闸门，home 下 TLM 跟随本就空转，唯一移动驱动被掐）、站中心时 tick 不给走位 → 原地呆站。修复三层：①MaidWorkTags.isNonCombatWork 补排 follow（与文档契约"idle/跟随/战斗类不命中"一致）；②新增 HomePatrolHandler：对非干活 home 女仆每 4 秒在 home 锚点（restrictCenter→schedulePos 最近点→当前位置）附近随机选点给 WALK_TARGET（Brain.m_21879_，MoveToTargetSink 每 tick 消费与活动无关），女仆在家范围内持续巡逻走动；干活（挖矿/农活/建造任务驱动）、战斗/自保/战术/站桩/坐姿/骑乘一律不干扰。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:26:00] v1.1.0 实测一百一十二：Home 模式呆立根因修复。反馈"Home 模式下女仆还是傻站着一动不动"——根因：排班开启只调 maid.setHomeModeEnable(true)，反编译 TLM 证实该 setter 仅置 DATA_HOME_MODE 标志（MaidConfigManager 字节码 = entityData.set 单行），**从不设置女仆 home 坐标**；TLM 的 home 行为全靠 SchedulePos.tick（每 2 秒）→ restrictTo 用 workPos（null）调 setRestriction → hasRestriction=false → isWithinRestriction 恒 true → tick 早退：无回家走位、无越界拉回，整个 home 机制空转，无任务女仆原地呆站。修复：ScheduleManager.applyNow 开启 home 模式时同步用 TLM 自家 SchedulePos API 锚定 home 坐标（setHomeModeEnable(maid, 当前位置) + setConfigured(true)，与 TLM GUI 点 home 同款路径；玩家在 TLM GUI 配过 home 的保留原锚点不动）。锚定后每 2 秒的 home tick 恢复工作：范围内给回家走位目标（BehaviorUtils 0.7 速度）、越界拉回传送，女仆在 home 附近活动/干活，不再呆立。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:18:00] v1.1.0 实测一百一十一：建筑转向机制根因修复。玩家质疑"建筑区块转向机制真的生效了吗"——核查 rotateSteps（BlueprintLib）发现**双重 bug**：①外部结构蓝图步骤是全格式 {Name,Properties} 状态，重建步骤时漏加 '|' 分隔符，状态串紧跟方块名且含逗号，parseStep 按逗号切分长度不对 → 所有带状态步骤（楼梯/门/告示牌等）整块被静默丢弃；②内置/JSON 蓝图是 state-only SNBT（{facing:"south"} 无 Name），m_247651_（readBlockState）对无 Name 标签直接返回空气（javap 字节码实证），旋转后写回 {Name:"minecraft:air"} → 状态全毁。修复：重建步骤时统一补 '|' 分隔符，并把两种状态格式统一重建为 {Name,Properties} 全格式（有 Name/Properties 直接用，state-only 则补方块名+把顶层属性收进 Properties 子标签）再旋转——与 parseStructure/parseStepState/resolveBlockState 口径完全一致。修复后旋转 = 坐标矩阵 + 占地 W/D 互换 + 方块状态真实转向，橙影投影与实建逐块重合。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:12:00] v1.1.0 实测一百一十：死亡传送根因修复。反馈"死亡传送还是没有长进，女仆回到死亡地点而非玩家设定的重生点"——根因：doDeathTeleport 用 player.m_219759_() 解析传送目标，javap 反编译证实该 SRG 方法实为 getLastDeathLocation()（字节码 = getfield f_238176_ 死亡位置字段），注释"m_219759_ = getRespawnPosition"是错的——女仆一直**被传去主人上一次死亡的地点**！1.20.1 重生点字段在 ServerPlayer：m_8961_()=getRespawnPosition(BlockPos)、m_8963_()=getRespawnDimension(ResourceKey<Level>)（均 javap 字节码验证）。修复：①主路径 doDeathTeleport 改用 m_8961_/m_8963_ 解析目标（无重生点→主世界出生点兜底）；②保险路径 onPlayerRespawn 同样改用重生点访问器，不再依赖重生事件触发时玩家当前坐标（时机不可靠），传送目标维度改为解析出的重生维度。随 promaid-1.1.0.jar 部署，重启游戏生效。


[14:00:00] v1.1.0 实测一百零九：Litematica 风格幽灵方块渲染（重做）。此前实测一百零一尝试 renderSingleBlock 时方块完全不可见，原因已定位：TransparentVertexConsumer 的 builder 方法返回 delegate 而非 this——链式调用 vertex().color().uv() 直接在底层 consumer 上执行，alpha 缩减从未生效且渲染链中断。本次修复：①所有 builder 方法（m_5483_/m_6122_/m_7421_/m_7122_/m_7120_/m_5601_）返回 this，保证链式调用始终走包装器；②renderType 用 RenderType.m_110466_()（字节码实证 = translucent，引用 f_110375_ + TransparencyStateShard），translucent 管线开启 alpha 混合；③服务端 BlueprintProjectionSampler.sampleCloud 输出含方块注册名+状态 SNBT 的新格式，客户端解析重建 BlockState 后渲染真实方块模型。随 promaid-1.1.0.jar 部署，重启游戏生效。


[13:30:00] v1.1.0 实测一百零八：死亡传送根因修复。反馈"死亡传送还是不生效，女仆回到死亡地点"——根因：onPlayerRespawn 保险传送只搜索同一维度（level.m_45976_），跨维度女仆永远不会被拉。修复：改用 MAID_TRACK 遍历所有维度的女仆（与 doDeathTeleport 同口径），已加载的直接传送，未加载的强制加载后传送。同时保留原有的 doDeathTeleport 路径（真死校验后立即传送）作为主路径，onPlayerRespawn 作为保险兜底。随 promaid-1.1.0.jar 部署，重启游戏生效。


[13:00:00] v1.1.0 实测一百零七：近远战切换修复。反馈"女仆不会自己的近远战切换，模组武器也被屏蔽"——①retuneCombatTactics 旧版硬性要求 cur.getUid().getNamespace().equals("touhou_little_maid")，模组任务（拔刀剑/弹幕/御币等）永远被排除在切换之外；修复：改为 instanceof IAttackTask 即可参与切换（与 pickCombatTask 同口径）。②isRangedTask 旧版只认五个原版远程 UID，模组远程任务全部误判为近战；修复：新增 UID 关键词匹配（ranged/gun/danmaku/spell/crossbow/trident/bow）+ 命名空间匹配（maidspell/spellbook），模组远程任务也能正确分类。随 promaid-1.1.0.jar 部署，重启游戏生效。


[12:30:00] v1.1.0 实测一百零六：搭路欲望增强。反馈"搭路向主人方向搭方块的欲望太低了"——①放宽 canUse 启动门槛：旧版要求主人至少高 minDy（默认2格）才启动搭路，改为至少高1格即可（水平距离远时1格差也搭）；②tryAirBridgeStep/tryDiagStep 三方向尝试：正前方 → 左前45° → 右前45°，主方向被挡时自动尝试偏移方向继续搭路（向主人方向倾斜），不再一次被挡就放弃。仅影响 BridgeUpBehavior（普通搭路），自保/伐木/挖矿的搭路逻辑不变。随 promaid-1.1.0.jar 部署，重启游戏生效。


[12:00:00] v1.1.0 实测一百零五：回退幽灵方块渲染。反馈"建造模式下连幽灵方块都看不到了"——实测一百零一的 renderSingleBlock + TransparentVertexConsumer 方案中，VertexConsumer 接口的实际调用路径与 m_6122_ 不匹配（renderSingleBlock 内部可能走 m_7404_ 或其他路径），导致顶点数据全部丢失→方块完全不可见。紧急回退到 DebugRenderer 色块方案（0.4 格半透明方块），保证幽灵投影可见。Litematica 风格渲染需更深入研究 VertexConsumer 调用链后重做。随 promaid-1.1.0.jar 部署，重启游戏生效。


[11:30:00] v1.1.0 实测一百零四：自主战斗还原根因修复。反馈"威胁解除后不会切回原来的模式"——AutoCombatSwitch 的参战判定和还原扫描均用 getBoolean(PREV_TASK_TAG)，但 PREV_TASK_TAG 存的是字符串（前任务 UID），getBoolean 对字符串标签永远返回 false → 恢复逻辑永远不触发、排班调度器也误判为"不在战斗"。修复：新增独立布尔标记 COMBAT_ACTIVE_TAG（maid_smart_combat_active），参战时写 true、clearMarkers 时写 false、isAutoCombatActive 读此标记——与 PREV_TASK_TAG（字符串）职责分离，不再混淆类型。随 promaid-1.1.0.jar 部署，重启游戏生效。


[11:00:00] v1.1.0 实测一百零三：模组武器自动装备。反馈"拔刀剑模式不会自己装到主手上"——MaidToolAutoEquip.ensureForTask 只处理 touhou_little_maid 命名空间任务（attack/ranged_attack 等），模组任务（slashblade/ef_tlm/truepower/sbr_core 等）全部 return false → 自动装备从未生效。修复：else 分支检测任务是否实现 IAttackTask，若是则用任务自带的 isWeapon 方法匹配武器 + weaponScore 评分，实现与原版攻击模式相同的自动装配逻辑。随 promaid-1.1.0.jar 部署，重启游戏生效。


[10:30:00] v1.1.0 实测一百零二b：搭路跳跃动画恢复自然。反馈"搭路状态下往上跳看着不自然，能不能维持老版本的样子"——BridgeUpBehavior.stepOnto 每次放完方块都给0.42垂直速度（标准跳速），水平搭路时女仆明显跳一下；修复：水平搭路（d>0）去掉垂直跳速只给水平速度，视觉上是"走过去"而非"跳过去"；垂直垫块（d≈0）保持原地起跳不变。随 promaid-1.1.0.jar 部署，重启游戏生效。


[10:15:00] v1.1.0 实测一百零二：五项致命 bug 修复（实测反馈）。①【排班完全失效根因】ScheduleBookScreen.saveSchedule 发送 SchedSavePacket 时 loadedOn 默认 false → ON_TAG 写 false → 调度器每秒扫描跳过该女仆 → 排班/Home 模式/跨维跟随全部连锁失效；修复：有非空任务槽时自动将 loadedOn 置 true。②【死亡传送被 Home 模式阻断】MasterDeathTeleportHandler.shouldStayPut 将 isHomeModeEnable() 纳入豁免 → 排班女仆（被强制 Home 模式）死亡传送全部失效；修复：死亡传送豁免仅保留坐姿和骑乘，Home 模式不再阻断。③【自主战斗还原卡死】prevTask 为 null（原任务被卸载/删除）时 maid 永久留在战斗任务；修复：兜底还原到排班当前段或 TLM idle 任务。④【危险方块回避静默失效】MaidDangerPathMixin 的 catch(Exception) 吞掉所有异常 → 若 DangerBlocks.cellDangerous 抛异常则回避系统静默失效；修复：改为 warn 日志。⑤【伐木看门狗】WOOD_STUCK_RESET_SECONDS 默认 8→5 秒，更快速重置发呆状态。随 promaid-1.1.0.jar 部署，重启游戏生效。


[08:30:00] v1.1.0 实测一百零一：Litematica 风格幽灵方块渲染。反馈"当前幽灵方块颜色一样、大小不是标准方块，根本看不清建筑形态"——旧版 drawGhost 用 DebugRenderer 画 0.4 格均匀色块，玩家无法辨识方块种类与朝向。改造为真实方块模型半透明渲染：①服务端 BlueprintProjectionSampler.sampleCloud 输出格式从 "x,y,z;…" 升级为 "x,y,z,id|state;…"（附加方块注册名 + 状态 SNBT）；②客户端 BlueprintAreaPreview 投影缓存从 int[] 改为 Object[]{x,y,z,BlockState}——新增 parseCloudWithState 解析新格式，resolveBlockState 从 SNBT 重建 BlockState（支持默认态/属性补全/Name 包装兜底）；③drawGhost 改用 BlockRenderDispatcher.renderSingleBlock 渲染实际方块模型（全亮 0xF000F0、无覆盖 OverlayTexture）；④TransparentBufferSource + TransparentVertexConsumer 双层包装器：拦截 VertexConsumer.m_6122_（RGBA 颜色写入）缩减 alpha 分量，实现半透明幽灵效果；⑤BlueprintBookNetworking 无需改动——cloud 作为不透明字符串透传。随 promaid-1.1.0.jar 部署，重启游戏生效。


[19:49:08] v1.1.0 实测一百：转向键默认 P→Z。字节码实证原版「社交互动」默认键同为 P（Options 构造 bipush 80）——金色预览按 P 转向会同时弹出社交界面，每次都要多按一次 Esc。新默认 Z：原版零占用，且避开主流模组默认键（JEI 查看配方 R / OptiFine 缩放 C / 背包类 B / 语音类 V / Xaero 地图 M）；左手小拇指从 Shift 邻位自然够到，走动选位中无需腾手。手册教程第 3 步与建造确认提示语同步改为 Z；已存 options.txt 的玩家不受影响（改键随时经详情页「转向键位」按钮或原版按键设置调整）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[15:11:22] v1.1.0 实测九十九：实测前终审，一修一证。①【修】同点位换朝向重建改为原位替换——此前续建判定忽略朝向（按 P 选的旋转对旧计划完全不起作用）；初版修复走“取消旧区块+新建”会生成新 planId，导致已绑定女仆的持久化绑定全部失效被迫手动重绑。现新增 BuildPlan.replacePlanSteps：保留 planId 与工头/绑定关系，仅替换步骤与朝向并重置进度（已建方块由 filterBuilt 已建感知自然跳过，不白费材料也不重搭；包围盒与存档条目同步更新）。②【证】按键冲突实证：原版「社交互动」默认键同为 P（字节码 bipush 80 确认）——金色预览按 P 转向的同时会弹出社交界面；默认键位遵要求保持 P，可在原版按键设置里把社交互动改键、或经详情页「转向键位」按钮把转向键改到其他键位。随 promaid-1.1.0.jar 部署，重启游戏生效。


[14:52:45] v1.1.0 实测九十九：朝向系统实测前终审，三处修复。①【换朝向重建】建造执行器的“续建识别”此前只比对蓝图+原点、不比对朝向——同一位置先建 0° 取消后再按 P 转向重建会走续建分支沿用旧步骤，玩家选的旋转完全不起作用；现在朝向不一致视为重建：自动取消旧计划后按新朝号新建（已建的方块由已建感知自然跳过，进度不白费）。②【按键计数排空】转向键的 consumeClick 计数器改为无条件排空——旧版非预览态直接跳过轮询，平时误按的 P 会攒在计数里，下次开启金色预览瞬间一次性爆转多次；现在始终清空、仅预览态生效。③【常驻注册】BlueprintAreaPreview 原为首次预览才挂事件总线（懒注册），开机即由 BuildKeysClient 的 ClientSetup 挂载，保证计数器从进世界起就有人排空；渲染入口自带空态短路，常驻零开销。随 promaid-1.1.0.jar 部署，重启游戏生效。


[14:39:33] v1.1.0 实测九十八：手册补充说明（要求）。①建造教程「第 3 步」改写为“确认范围与朝向”——补入按 P 键整体旋转建筑（默认键位、「转向键位」按钮直达原版按键设置、占地宽深互换与楼梯/门跟随转向）、新增「建造期间的工地投影」段落（红色区块框+橙色幽灵关手册走近可见/96 格剔除/取消或完工自动消失/蓝图投影开关只管幽灵）；②杂项功能章补两条：「女仆安全与区块保载」（全区块持续加载语义+寻路避让+险境脱离+搭方块排除+受困救援，入口指向配置小节）与「运行日志」（logs/promaid.log 用途、4MB 轮换、镜像 latest.log、反馈问题附文件对账）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[05:35:33] v1.1.0 实测九十七：建造朝向系统——金色预览按 P 整体旋转建筑（每次顺时针 90°），手册内可跳原版按键设置改键。(1)【整体旋转】按判定口径“切换的应该是整个区块，而不仅仅是其中的方块”：新增 rotateSteps 通用步骤旋转器——坐标矩阵变换 + 占地 W/D 自动互换（金色框/红色区块框随之换向）+ 楼梯/门/原木轴向等方块状态跟随转向；内置蓝图此前完全不可旋转，现与外部文件统一走“先解析后旋转”。(2)【按键】注册原版 KeyMapping「建造预览·旋转朝向」（默认 P，分类 Promaid 建造）：金色预览激活期间按一次转 90°，青色幽灵实时刷新、悬浮文字显示当前角度与换算后的占地尺寸；建筑详情页新增「转向键位」按钮——直接打开原版按键绑定界面（与调整原版按键操作完全一致），改完点完成返回手册。(3)【贯通】朝向随 SelectBlueprintPacket 下发 → 建造执行器以同变换落地 → PlanState.quarters 存档持久化（BuildArchive 第 10 字段，旧档默认 0 兼容）→ 重启恢复仍按该朝向重建 → 每秒区块同步追加 r[14] 朝向字段（15 字段，三包同步扩位）→ 红色态橙影按 id#quarters 取旋转版点云，与实建逐块重合。(4) SRG 实证：KeyMapping.m_90859_=consumeClick（字节码级确认计数递减语义）、Minecraft.f_91066_=options。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:52:33] v1.1.0 实测九十六：金色预览回归——「建造此图纸」未确认阶段的金色框（随玩家移动）+ 青色幽灵投影。(1)【回归】实测九十五误将建造确认流程里的两步选位一并简化——玩家澄清：未确认时金色区域随玩家移动的功能很重要；现恢复两步流程：首次点击「建造此图纸」开启金色框+青色幽灵方块并提示退手册选位，再次点击弹确认框。(2)【增强】每次点击都会把金色预览刷新到当前脚下（取消弹窗重新选位后再点也一样），且金色状态下同步显示青色幽灵方块投影——未确认阶段即可看建筑形态与朝向；确认创建后金色框关闭、红色区块框+橙色幽灵投影由每秒同步接管，两种状态都有投影。(3) 独立「区块显示」按钮保持删除（其功能已被流程内金色预览覆盖）；“看过预览”标记沿用 v1.5.204 口径（重开手册不重置、确认创建后才复位）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:44:41] v1.1.0 实测九十五：删除「区块显示」金色预览，建造投影并入「建造此图纸」全程显示。(1)【根因】此前携带建造区块行的网络包只在打开手册/创建计划/控制操作时一次性下发——真正开始建造后玩家关掉手册走到工地，客户端没有任何区块数据，橙色幽灵方块从不渲染（“建造此建筑里面没有显示”的根因）；金色预览因为是纯客户端逻辑所以一直好用，形成“只有区块显示里能看到投影”的落差。(2)【删除】建筑详情页「区块显示」按钮与金色玩家中心预览（含青色幽灵方块、两步确认流程、“看过预览”标记）整体下线；建造此图纸改为一步确认弹窗（以玩家脚下为中心，保留摧毁障碍物警告）。(3)【新增】RegionSyncPacket：服务端每秒向全体玩家广播建造区块行（14 字段，复用 collectBuildRegions），驱动红色区块框 + 橙色幽灵方块在建造期间持续显示——玩家走近工地即见建筑最终形态与朝向，随移动自由观察（96 格距离剔除），计划取消/完成后自动清除；投影点云仍按蓝图 id 请求缓存。(4)【顺带】底部按钮改自适应均分布局（区块内 4 键/区块外 2 键），不再有出屏风险。随 promaid-1.1.0.jar 部署，重启游戏生效。


[04:32:46] v1.1.0 实测九十四：新增运行日志系统 + 新版内容复查收尾。(1)【运行日志】新增 游戏目录/logs/promaid.log（UTF-8 追加写，满 4MB 自动轮换为 promaid.log.old，同时镜像一行到 latest.log）：排班段应用（时段/模式/任务）、战斗参战/还原/僵局逃逸阀触发/战中任务被接管/原任务丢失、险境脱离挪格与应急灭火、跨维跟随传送、自保残留标记自愈等状态迁移事件全部带真实时间戳落盘——以后反馈“XX 没生效”可直接按时间线对账；巡检类每秒扫描的空转路径不落盘防日志爆炸；配置面板杂项页新增「运行日志」小节（总开关 + 文件位置说明），关闭后完全静默。(2)【新版复查】伐木等五个自定义任务确认经 addMaidTask 注册进 TLM 任务管理器（排班 UI 六槽可选）；mixin 注册表增量核对一致（仅新增 MaidDangerPathMixin）；旧 MaidDimensionFollow 类零代码引用（仅存历史注释）；静态容器清理核查通过（钓鱼坐垫三表 sweep 中 removeIf、关系记忆方法缓存有界、僵局日志节流表按女仆数有界）。随 promaid-1.1.0.jar 部署，重启游戏生效。


[03:30:55] v1.1.0 实测九十三：排班联动与打断处理审计，双修。①战斗还原直通排班的路径绕过了排班系统总开关——全局关闭排班系统后，女仆打完一架仍会被战斗还原代码强制进入在家模式并按旧日程换任务（跟随女仆打完架就不再跟人）；现在 applyNow 入口统一设总开关闸，三个调用方（每秒调度扫描/保存日程立即应用/战斗还原直通）口径一致。②自保标记残留自愈：女仆在自保会话进行中遭遇区块卸载/服务器崩溃/魂符收放时，自保标记随存档保留而会话内存态归零，重载后健康安全的她不会再进会话、标记永无人清→排班调度、主动战斗参战、搭方块、险境脱离对这只女仆永久让位；现在会话外检测到“带着标记但血量健康且环境安全”即自动清除。其余联动链路逐条核验通过：手动切任务/批量应用的去抖保护、战中换战术同步指派标记、还原宽限时间基一致、跨维跟随与一键集合对排班自动 home 女仆的豁免、快捷设置对排班中手改任务/模式的硬拦截均正确。


[16:40:0] v1.1.0 实测九十二（玩家质疑：“这个功能似乎并没有相关的开关和配置表吧”——属实）：①自绘配置面板补齐 5 个缺失条目（杂项页新增“女仆安全与区块保载”小节）：区块持续保载/受困救援/寻路危险避让/险境脱离四个开关 + 危险方块表文本行（逗号分隔注册名，缺命名空间拒绝提交）——此前这些只有 spec 键，游戏内面板看不到也改不了，只能手改 toml；②接线跨维度跟随：MISC_DIMENSION_FOLLOW 开关+面板条目早已存在，followIfCrossDimension 函数也早已写好，但两者从未连接（开关是摆设、函数是死代码）——现在每 5 秒扫描全服女仆，开关开启时异维度跟随者自动传送到主人身边；home/坐姿/骑乘/主人非存活豁免在函数内自判。


[16:00:0] v1.1.0 实测九十一（要求：搭方块时这些危险的方块也不应该在名单里面，无论如何都不会搭）：MaidBuildBlockFilter（挖矿/伐木/自保/搭路四模块统一选材入口）新增危险表排除——dangerBlocks 配置表中的方块无条件从垫脚选材中消失（与寻路避让/险境脱离共享同一张表），且不受 dangerAvoid 开关影响；反馈往表里加模组危险方块即自动从选材名单同步排除。原硬编码黑名单（仙人掌/岩浆块/浆果丛/营火等）保留双保险。


[15:10:0] v1.1.0 实测九十（追问：女仆已经处于这些危险方块上时会怎么办）：新增险境脱离——每 0.5 秒巡检，站立柱（脚格/头顶/脚下）命中危险表的女仆立即挪到最近安全格（环形半径 4 由近及远）+应急灭火（m_7311_(-1) 清剩余着火时间），不再等血量跌破自保线白挨伤害。语义边界：home 模式也救（只挪几格脱离险境，非传送到主人身边）；坐姿/骑乘跳过（椅子/载具系统管理位移）；自保中让位（有专属珍珠/放水/垫高链路）；四面皆险记冷却交给自保兜底。配置 dangerEscape 可关。同时把危险表逻辑抽取为 DangerBlocks 工具类供寻路 mixin 与本处理器共用。


[14:30:0] v1.1.0 实测八十九（需求：优化女仆寻路机制，让她可以自己避开一些危险的方块）：新增寻路层危险方块避让——注入原版 WalkNodeEvaluator 的逐节点路径类型评估（仅女仆生效，其余生物零影响），站立格/脚下方块命中危险表、或头顶一格为灼烧型方块时，把该节点强制改判为不可通行：女仆规划路径会主动绕开岩浆湖岸边、火焰地面、仙人掌丛等，宁可停下等过远传送兜底也不往里走。三处细节防误伤：①起点格不拦（已身处险境时保留逃出路径）；②危险表可配置（杂项 dangerBlocks，默认 9 种：岩浆/火/灵魂火/岩浆块/仙人掌/甜浆果丛/凋零玫瑰/细雪/石笋），总开关 dangerAvoid 可关；③配置列表按实例同一性缓存，改表即时生效不重复解析。


[12:40:0] v1.1.0 实测八十八b（玩家纠正：home/坐姿/骑乘只豁免传送，不豁免区块加载——他们的区块也要加载）：撤销八十八刚加的三态保载豁免。现在所有有主女仆一律持续加载所在区块：停放的女仆只是不走动，但周边农场/熔炉照常运转，也随时可被集合/召回/救援找到；三态豁免语义回归其本源——只作用于传送路径。


[12:00:0] v1.1.0 实测八十八：区块保载范围从“仅异维度”扩大到全部有主活动女仆。旧规则只给跨维度女仆挂加载票——同维度跟随的女仆一旦落后主人超过模拟距离，所在区块卸载、AI 冻结，TLM 的“离主人过远自动传送”永远无法触发，只能等主人走回去。现在除三态豁免（home/坐姿/骑乘=玩家明确停放）外全部持续加载：跟随、挖矿、伐木、建造中的女仆所在区块永久保持 实体 ticking 级别（与玩家同级），随时可以被传送/召回/救援。资源代价=每只活动女仆一张单区块票。无主野女仆不保载。


[11:00:0] v1.1.0 实测八十七d（/goal 女仆传送逻辑全量审查）：清点全部 9 类传送路径逐条核对守卫一致性。发现并修复 1 处：一键集合的“待召回队列”不豁免 home/坐姿/骑乘女仆——未加载区块里的女仆只能靠最后出现位置快照召回，旧快照没记录她的状态，导致 home 女仆的区块被白白强载、载出来才发现要豁免然后静默收队（玩家视角：点了集合看到待召回却石沉大海）。修复：①登记时快照三态豁免；②建队前先过滤并计入“保持原位”；③强载后才发现豁免的静默分支补上聊天播报。另确认：跨维度自动跟随函数为死代码（从未接线）——现役跨维手段=一键集合/死亡传送/受困救援三项，是否接线自动跟随留待玩家决策。其余路径守卫齐全：死亡传送与复活保险三态豁免、自保双闸、TLM teleportToOwner 双 mixin 拦截、救援绕过 home 属设计（基岩顶不是家）。


[10:10:0] v1.1.0 实测八十七b（/goal 逻辑链路审查——覆盖实测八十六隔离门与八十七中立口径增量）：全链路复审结论=无功能性 bug，一处健壮性隐患已修。①还原扫描每秒对参战女仆执行的中立生物记仇判定（isAngry）此前直接调用实体方法且无异常防护——NeutralMob 实现类是玩家可安装的第三方内容，模组实现若在 getter 里抛异常会顺着 ServerTickEvent 炸穿服务端 tick；现在统一走防御封装 neutralAngry（动态圈来源判定同步），异常按“不记仇”处理。②逐项复核确认无虞：八十六隔离门为纯提前返回零状态写入；四触点标记读写对称；动态圈查找跨维度安全（同维检索失败即回落固定圆）；僵局阀与圈互斥成立；被动生物任何链路不参与。


[09:30:0] v1.1.0 实测八十七（玩家提问："主动战斗在面对中立生物的情形怎么处理"）：确立【激怒即通行证】口径。中立生物分两层：实现 Enemy 的（末影人/僵尸猪灵/猪灵/蜘蛛）本来全链路算数；只实现 NeutralMob 的（蜜蜂/北极熊/狼/山羊/羊驼/海豚）此前三不管——蛰了女仆不触发参战、不进威胁圈、不挡还原，主人挨蛰拉女仆进来又 10 秒后撤走，状态机断裂。现在按字节码实证的 isAngry/isAngryAt 接口接入四触点：①女仆被记仇她的中立动物打了→参战；②该动物登记进动态威胁圈（圈内口径同步放宽）；③还原扫描把记仇状态（isAngry）的中立动物算作现役威胁——它们还在圈里转就保持战斗态，气消/打死/走远后正常还原；④主人开火目标是记仇主人的中立动物也算开战（帮打合理），平静态的照样不触发（驯养/管教不受影响）。被动生物（牛羊鸡）任何链路都不算，八十四b 口径不变。手册主动切换战斗条目同步。


[21:00:0] v1.1.0 实测八十六（要求：战中近远程互换对模组武器隔离——"原版有可能切到模组武器里，但是模组武器切不到原版里"；并审查该切换对主动战斗触发链的影响）：①战中动态换战术仅限【当前任务是原版五件套】的女仆参与——按命名空间判定（touhou_little_maid），模组任务（万法皆通/史诗战斗等）被切到后保持硬打、永不被换出；方向约定=原版可切入模组池（火力不浪费）、模组不可切回原版。理由：模组武器普遍更强且常带专属机制（姿态/施法循环），isRangedTask 的远近二分法对它们是瞎猜。②触发链审查结论：换战术只发生在还原扫描内部（参战评估从不调用它），且每次切换同步 ASSIGNED_TAG、不碰 PREV_TASK/LAST_THREAT——参战/续杯/还原/僵局阀/动态圈五条链路均不受切换次数影响；隔离门为纯提前返回、零状态写入，对全链路无副作用。③边界留档：模组远程任务被近身时不再自动切近战（信任模组武器自身近身机制如反击击退）；初始参战选池仍含模组候选（加权随机不变），隔离只约束战中互换。手册主动切换战斗条目同步。


[20:00:0] v1.1.0 实测八十五（玩家设计："动态威胁范围——出现那种情况时威胁圈自然放大，直至包含检索到伤害来源的那个生物"）：还原判定的固定 8 格圆与参战触发口径不对称——骷髅站在 10 多格外放风筝时固定圆扫不到它，还原计时照走，"还原 10 秒→又中一箭→再参战"反复横跳。实现动态威胁圈：①女仆挨打时登记【伤害来源生物】（uuid + 时刻）；②还原扫描时该生物若仍存活、距离 ≤32 格（硬上限）、且在扩展窗口（默认 10 秒）内有过接触 → 威胁圈自动放大把它包含进来，被远程压着打期间保持战斗态还击；怪死/走远/超窗后圈回落到固定半径。时长可调（combat.autoSwitchThreatExpandSeconds，0=关闭）。配套修挨打判定口径：骷髅放箭时伤害的直接实体是箭矢、造成者才是骷髅，旧版只查直接实体 → 远程怪放风筝女仆永不参战；现在造成者/直接实体任一是 Enemy 即算参战与登记。手册主动切换战斗条目同步。


[19:10:0] v1.1.0 实测八十四b：上轮只核查了还原扫描端（确实只认 Enemy），漏了参战触发端——主人"主动开火"触发不看目标是谁，打牛羊也算开战，且每次命中都经参战评估无条件续杯安全计时。两处口径修正：①主人主动开火的目标必须是敌对生物（Enemy）才触发参战——宰牛杀鸡/剪羊毛等被动生物交互不再让全员拔刀，无威胁战斗从源头消失；②已参战女仆的"续杯安全计时"只在真实存在敌对威胁（8 格内有 Enemy）时进行——任何其他路径的重复评估不再把还原时钟往后推。被动生物挨打侧不变：女仆被怪打才参战的口径本来就只认 Enemy。手册主动切换战斗条目同步（打被动生物不算开战）。注：主人打玩家（PVP）现在同样不算开战——此前任意受害者都会触发，属同一口径的边界收敛。


[18:30:0] v1.1.0 实测八十四（玩家两条）：①搭方块新效果——"女仆自己搭下一个方块以后，立刻移动到那个方块上"。旧版空中铺桥/斜台阶只挂导航目标（半空寻路永远失败=目标形同虚设，人留在原地等下一轮），垂直垫块则等方块卡身后被防窒息逻辑顶起（有半嵌瞬间）。现在三条放置路径（空中平桥/斜台阶/垂直垫高）放完块都给一拍朝目标格的物理跳跃速度（水平朝目标、垂直一拍标准跳速），落地即站上刚垫的方块；地面能寻路时导航照常接管。②自主战斗"打完了切不回来"根治——还原判定要求 8 格内无敌对生物才算威胁消失，怪卡在墙后/玻璃后/传送门里/飞行绕圈等杀不掉也够不着的死局会让安全计时永远续杯。新增僵局逃逸：女仆与敌对生物超过 60 秒没有任何伤害往来（挨打/打到都算接触，远程放风筝全程无伤也不会误判）→ 不再干等，按正常安全计时切回原任务；latest.log 搜 auto-combat stale 可查是哪种怪卡住的；时长可调（combat.autoSwitchStaleSeconds，0=关闭）。另确认玩家疑问：被动生物判定没有问题——参战触发与还原判定的威胁口径都只认 Enemy 接口（敌对生物），牛羊鸡等被动动物从来不算威胁、也不会触发参战。


[17:20:0] v1.1.0 实测八十三c：回退实测八十三在 TLM 跟随任务上加的 home 门。字节码取证：TLM 对 home 女仆本就是「跟随任务每 tick 正常启动 → start() 内部判 home 后全分支空转」，这个"启动即空转"占据着它的优先级组、是大脑正常运转的一环；从外部拦掉启动后同组行为接管异常，home 女仆站桩。home 的传送防护不受影响——自保两处、死亡传送三态（home/坐姿/骑乘）、一键集合与跨维度跟随的豁免都仍在各自传送点生效；跟随任务只保留"主人已死亡不启动"一道闸（原版只查 isRemoved 不查存活，尸体期会放行瞬移到死亡地点）。


[16:40:0] v1.1.0 实测八十三b：根因在传输层——投影点云整条打包成【一个字符串】下发，而网络层单字符串上限 32767 字符；大建筑点云（≤3000 点 × 每点 12~16 字符 ≈ 最高 4.8 万字符）在服务端编码阶段就超限抛异常、包静默发不出去，客户端的防重发标记又让后续点击不再补发 → 投影永远不出现。修复：①点云分块传输（每块 ≤16000 字符、客户端拼接还原，尺寸上限彻底解除）；②服务端采样与发送全程 try/catch 并写日志，异常不再静默；③客户端请求/接收各加一条诊断日志——latest.log 搜「projection」即可确认链路是否打通（request → size/chars sent → received N points）；④渲染距离剔除改用已验证的符号组合，消除一处潜在运行时风险。


[16:10:0] v1.1.0 实测八十三（实测三条反馈）：①到下界提示"一名女仆因为无站立点未动"——集合/跟随的找落脚点逻辑只从主人脚下一路向下扫 16 格，下界桥面、熔岩海高架、悬崖地形正下方悬空几十格就直接判失败；现在单柱向下 16 格 + 向上 12 格、水平由近及远扫半径 3 圈（沿桥面横走一格就能落脚），一键集合与跨维度跟同一并受益。②死亡后女仆出现在死亡地点而不是重生点——元凶是自保行为的两处"传回主人身边"传送（血恢复退出自保 / 自保中确认安全）都没有死亡主人防护：主人尸体停在死亡点等待重生，自保结束的女仆被直接拽到尸体旁；顺带堵死 TLM 内置跟随的同类路径（离主人过远自动瞬移——主人已死亡或 home 模式时跟随行为直接不启动）。③home 看家女仆仍被死亡传送拽走——死亡传送处理器此前声明"不受任何女仆状态判定影响"，漏掉了实测七十八确立的看家语义；现在「home/坐姿/骑乘」三态豁免与一键集合同口径，死亡传送和复活保险拉取都遵守，战斗类女仆照常无条件赶往重生点。手册被动技能章与自动在家模式条目同步。


[15:20:0] v1.1.0 实测八十二：建造范围预览升级为「区块框 + 蓝图投影」。①「区块显示」与建造流程第 1 步的金色框预览时，框内叠加青色半透明幽灵方块轮廓随玩家移动——选位阶段就能看到建筑的形状、长边朝向和门洞位置；②建造中/暂停中的红色区块标记按计划原点叠加橙色幽灵方块，与女仆实际搭建位置逐块重合（同一居中坐标变换），建到一半也能看到最终形态；③投影数据由服务端抽壳降采样生成（只保留六邻有开口的外壳块，实心大建筑内部全部丢弃；超 3000 点等距抽稀），经新增的请求/下发网络包传给客户端并按蓝图 id 缓存，55 万块级蓝图也只解析一次；④配置面板建造页新增「projection」开关（默认开），关闭后回到纯区块框。手册建造教程第 3 步与小知识页同步。


[14:40:0] v1.1.0 实测八十一（玩家提醒："结合 1.20.1 特性确认基岩层位置；之前把 -60 误判过——超平坦世界地表恰好在 y=-60"）：受困救援的虚空判定从硬编码 y<-60 改为按维度真实边界 getMinBuildHeight 判定（1.20.1：主世界 -64~319、下界 0~255 且 y=127 整层基岩天花板、末地 0~255）。修复站在超平坦地表（y=-60）的女仆被误判成"掉出世界"而反复触发救援传送的问题；下界基岩顶阈值 ≥126 与落点上限 124 经核对维持不变（顶层方块占 127、站面 128，中间留一格缓冲）。


[14:00:0] v1.1.0 实测八十（纠正实测七十八的误诊，真正根因修复）：反馈"主人下界死亡后女仆被传到基岩层上面"的元凶不是跟随链路，而是主人死亡强制传送的落点函数 findSafeLanding——下界 y=127 是整层基岩天花板，旧版从重生点向上扫第一处两格净空，重生点柱子上方被实体方块封到天花板时（封闭基地/埋地重生锚），第一处露天空间就是基岩层上方，随后的向下贴地又被基岩挡住 → 女仆稳落基岩顶。修复：下界落点搜索上限压到 y≤124、向上找不到改为向下继续扫（地下空腔/基地内部）、都无果才退回原坐标上方一格。跨区块（追踪表+区块强载）与跨维度（teleportTo）能力不变。


[13:10:0] v1.1.0 实测七十九：死亡传送策略成型为三层。①死亡瞬间：主人非存活一律不追（实测七十八），任何女仆都不会被拉去死亡点这类坏落点；②复活之后：跨维度跟随与"最后出现位置强载召回"两条既有链路照常工作，跟随女仆自动归队到复活的主人身边；③兜底救援（新增）：女仆被困下界基岩顶层（高度≥126）或掉出世界底部（y<-60）时，每 5 秒扫描自动把她安全传回存活主人身边（跨维度通用；在家模式的女仆也救——基岩顶不是家；已在主人 8 格内不触发防循环救援）。开关在配置面板杂项「maidRescue」，默认开。手册小知识补「受困救援」条目。


[12:30:0] v1.1.0 实测七十八（bug 报告：主人在下界死亡且未关死亡传送，主世界看家女仆被传到下界基岩层上面）：根因一——主人死亡期间实体仍留在原位置（血量 0 但未移除），跨维度跟随照常触发，女仆被传到死亡点附近，死亡点在高位时向下找站立格就直接落在下界基岩顶层上：修复为主人非存活状态一律不追。根因二——实测七十曾放开"home 模式女仆也响应一键集合"，现按玩家裁定的语义回滚：home=看家钉死，不响应任何传送（跨维度跟随本就豁免；一键集合在场/待召回两条路径均恢复豁免并在气泡注明；所有传送的咽喉点统一拒绝 home 女仆与已死亡主人）。想召回看家的女仆，先关闭她的排班/解除在家模式。手册同步。


[11:50:0] v1.1.0 实测七十七（新功能二次自查）：复核实测七十六锁定链路的最终形态——包处理顺序语义、批量跳过计数、按钮/提示渲染均无接缝问题；唯一调整：快捷页排班提示行措辞压缩到 30 字内，最小窗口宽度（320）下不再出屏。另留档两处边界：配置热重载事件里迁移矿表后，运行中的矿表缓存要等下次打开面板或重启才刷新（主流程不受影响）；跨维度集合召回 home 模式女仆后，她的"家"仍在原维度（守家行为自然退化为跟随主人附近活动，无害）。


[11:20:0] v1.1.0 实测七十六：实测七十只拦了"任务"，工作模式（早/晚/全天）仍可被一键更改、下个时段边界又被日程翻回去。本次锁全：①单女仆快捷页的「工作模式」按钮对排班中的女仆直接锁定（红色提示），服务端同步拦截并说明；②列表页「全员模式」和「应用任务」对排班中的女仆【整体】跳过（模式与任务都不动），汇总消息注明"先关闭她们的排班才能一键更改"；③先处理排班开关再判定，"关排班+立即改状态"的合并操作语义正确；④手册「排班锁定」条目同步。


[10:40:0] v1.1.0 实测七十五：①看门狗判定时长默认 伐木 30 秒/挖矿 45 秒统一降为 8 秒（范围下限放宽到 4），旧档存的旧默认自动迁移；②配套修时序坑——看门狗窗口小于目标超时（15 秒）后，若重置清掉"当前目标的等待时钟"，够不着的目标永远等不满超时去被弃置，反而无限循环；现在重置把当前目标与等待时钟一并保留（对超时弃置机制透明，失效检查逐 tick 照常复核）。配合七十四的"弃置即进展"，舞蹈期间每 15 秒必有进展标记，快速重置与慢速弃置两条链互不打断。


[10:00:0] v1.1.0 实测七十四（新功能逻辑自查）：修复实测六十九看门狗与"目标超时弃置"机制的相互干扰——够不着的目标每个要站桩等 15 秒超时才被弃置，两个以上就超过看门狗 30 秒窗口；而旧版重置会把 30 秒短排表一并清空，刚学到的"这些够不着"归零，从同一个目标重新开始等 → 形成"每 30 秒重置一次"的新死循环。修复：①看门狗重置保留短排表（600 tick 自过期的学习成果）；②超时弃置/视线弃置/走路卡死判不可达弃置统一视为真实进展喂狗——弃置是消耗工作清单的前进动作，真死循环（选中→拒绝同一块）不产生弃置仍会被抓住。另经审查确认无碍并留档的权衡：穿透预算迁移会把"故意保持 22"的配置一次性改为 6；空矿表的"故意全禁"会被一次性播种默认全家桶（均为一次性，之后不再干预）。


[08:20:0] v1.1.0 实测七十二：根因是穿透预算的挡路计数跳过了石头/泥土这些"可开路方块"——地下到处是石头=零阻挡，预算形同虚设，隔地板/隔墙的矿全部入选，而脚下的矿距离分最低，于是女仆在洞里对着地板连凿竖井。修复：①挡路计数重新计入实心可开路方块，预算真正约束"最多隔着几层实心方块选矿"；②默认 22→6，更远的矿先走近再评估（想要旧版全穿行为调回 20+）；③旧档配置里存的旧默认 22 在加载时自动迁移成 6，手动改过的值不动。


[07:30:0] v1.1.0 实测七十一：猜对了。四套搭方块表（挖矿/伐木/搭路/自保）各自独立登记，而各任务的防误挖检查只查自己那套表——搭路垫的原木桥不在伐木的保护名单里，而搭路选材恰是背包最多的可放置方块（刚砍下的原木），正好命中木材目标，女仆把脚下桥当树砍掉摔落。修复：放置登记器新增跨系统统一查询，四个系统的防误挖判定全部改走统一口径——伐木/挖矿的扫描与连锁不再把任何系统垫的方块当目标，挡路判定也一律放行自己人垫的方块。


[06:40:0] v1.1.0 实测七十（反馈两条）：①日程表与主人主动切任务的冲突改硬规则——排班开着的女仆在第 1 页的任务按钮直接锁定（红色提示），服务端同步拦截单发与批量应用（聊天栏说明原因），想手动切任务必须先把她的排班关掉；同时排班开启期间她自动进入在家模式（守家按日程干活、不跟着主人乱跑，旧档已开排班的女仆自动迁移，建造行为临时占用后会被重新扶正），关闭排班即解除。②一键集合跨维度修复——根因是未加载区块里的女仆根本不在实体列表里（票务只救还加载着的）：现在每 5 秒登记全部有主女仆的最后出现位置，一键集合时对不在场者按最后位置强载区块并进待召回队列，实体一出现自动传回身边并聊天栏回报（15 秒兜底放弃）；在家模式的女仆也响应一键集合（主人明确指令优先于守家豁免），坐着/骑乘仍保持原位。手册排班章同步更新。


[05:30:0] v1.1.0 实测六十九：伐木/挖矿新增「发呆看门狗」——连续 N 秒（wood 默认 30/mine 默认 45，配置面板可调）既没挖掉任何方块、位置也没挪动时，自动整体重置该女仆的全部行为状态（锚点/扫描缓存/弃置排除表/目标/连锁队列/走路记忆），等效收回魂符再放下去，不再需要玩家手动救；走路赶路与垫方块都算进展不会误触发，重置时气泡说一声（限频）。顺带修复两处会喂出「发呆」的漏洞：关闭透视时被挡目标从「丢弃但不记录」改为进 30 秒短排（不再无限「选中→看不见→丢弃」循环）；伐木抬头兜底不再回选已被硬挡路弃置的木材。开关与时长都在配置面板挖矿/伐木小节。


promaid V1.1.0 实测四十九：种树苗间隔减半（100→50 tick，5 秒→2.5 秒）——砍完树补种树苗的限频冷却减半，连续砍多棵树时补种更跟手。


[05:00:0] v1.1.0 实测六十八（反馈两症状同根修复）：武器匹配的异常兜底从方法级改为逐物品——修复拿斧子的女仆被切到三叉戟模式无法攻击；威胁判定 Monster 改 Enemy 接口与参战同口径（史莱姆等也算威胁/可测距）——修掉打不死怪导致永远不还原的连锁。


[04:30:0] v1.1.0 实测六十七：主手和背包都没有任何攻击任务认可的武器（剑/弓/枪械/模组武器等）的女仆，不再触发自主战斗——维持原任务继续干活（含女仆被打触发与主人被打触发两条路径）；新增「空手不参战」开关（默认开，配置面板主动切换战斗小节，关闭恢复空手近战兜底的旧行为）。


[04:00:0] v1.1.0 实测六十六：搭路台阶始终朝主人——水平没对齐时朝主人方向斜着垫台阶（地形实心也会在地面上再垫一级踩上来），不再原地直上搭出够不着主人的柱子。挖矿/伐木/自保的搭方块维持原逻辑。


[03:30:0] v1.1.0 实测六十五（实际运用审查修复 4 处）：分帧扫描进行中不再被 5 tick 节流（实际吞吐曾只有设计值 1/5，默认预算找一框从 0.85 秒变 20 秒）；主人摔落/岩浆/火烧等环境伤害不再误触发全队参战（要求伤害有来源实体）；女仆被打参战的判定 Monster 改 Enemy 接口（史莱姆/岩浆怪打她也参战了）；一键集合跳过在家模式女仆（跨维度集合后回不了家）。


[03:00:0] v1.1.0 实测六十四（重大修复）：排班表界面重写时遗漏了保存方法，导致五十一起每次编译都静默失败、打包的一直是旧界面字节码——五十一至六十三的全部界面/战斗/伐木挖矿改动实际未进过 jar。已补回方法并修复 7 个积累编译错误（改名 getValue SRG、5 处消息调用括号、MaidFireGuard 编译清单），构建脚本新增「源比 class 新拒打包」硬校验。本次为五十一以来全部新功能的首次真正部署。


[02:30:0] v1.1.0 实测六十三（代码自查修复 4 处）：列表搜索框击键重建后光标回 0 导致输入倒序乱码（光标移到末尾）；批量应用跳过自保中/主动战斗中的女仆（不再打断保命与战斗还原链）；一键集合跳过坐着的女仆（建造强制坐下不再被拽走）；伐木/挖矿分帧扫描进行中不再触发锚点滑动/播报（低预算下扫描永远完不成的死循环）。


[02:10:0] v1.1.0 实测六十二：女仆着火不传主人——燃烧女仆对主人的伤害直接取消；贴身接触传火每半秒检查一次自动给主人灭火（主人自己站火里/岩浆里不干预）；开关在配置面板「贴身辅助」小节。


[01:50:0] v1.1.0 实测六十一（借鉴 TLM-Sincerely 防抖与预算制探测）：战中换战术新增最短持有/反向切换窗口/反向冷却三重防抖；主动战斗还原后新增排班宽限期（防威胁闪烁拉扯，手动保存日程立即生效）；伐木/挖矿全量扫描改为分帧游标续扫（每 tick 预算可调，消除单 tick 扫描尖峰）。全部参数进手册配置面板。


[01:20:0] v1.1.0 实测六十（借鉴 Maid_Roster 军队管理，不学绑定点名册）：排班表列表页新增——搜索框按名字过滤、每行血量+维度状态（列表改为跨维度扫描）、全员模式一键应用（早/晚/全天）、批量任务选择+应用、一键集合（全部在场女仆含跨维度传送到身边，复用真传送链路）、详情快捷页改名（等同命名牌）。


[00:50:0] v1.1.0 实测五十九：排班表详情页拆回两页（页签切换）——第 1 页快捷设置（工作模式/任务点击立即生效，恢复实测五十一移除的遥控能力），第 2 页排班（班次+6 任务槽+保存）；排班开关两页共用右上角；排班页纵向压缩（班次行下移+槽行距 22）适配 240 高最小窗口。


[00:30:0] v1.1.0 实测五十八：女仆被怪物攻击也触发自主战斗（本人立即参战+周围同主人姐妹响应，只认怪物来源防女仆混战）；新增近战/远程偏好权重配置（两者皆可用且近身时按权重选池，默认 3:1 偏近战；设 0 永不选/切向该类，战中换战术同步受限）。


[23:59:59] v1.1.0 实测五十七：主动战斗战中动态换战术——远程被敌人近身（≤5 格）且背包有近战类武器（含模组）立即切近战；近战敌人远到大跳都跳不过去（>6 格）但仍在索敌范围（≤16 格）切远程放风筝；5~6 格滞回带防横跳；切换同步指派标记，还原链路零影响（威胁消失照常回原任务）。


[23:59:0] v1.1.0 实测五十六：排班表 UI 像素级自查修复 5 处缺陷——班次按钮与排班开关重叠（默认宽 427 下压 38px）、保存按钮与第 6 时段按钮角部重叠、底部提示被返回按钮遮挡前 17 字、页码两端被翻页按钮压 10px、时段标签与按钮 0px 贴边；列表页副标题文案同步新 UI。


[23:23:0] v1.1.0 实测五十三～五十五：排班表任务名中文化（翻译组件误用字面量的老 bug）；排班表图标改为原版旗帜图案物品贴图、附魔光效改手册同款（isFoil，0 级经验修补方案证实无效已移除）；伐木修复——站树洞里对头顶木材视而不见（空闲抬头兜底+站立点空中格兜底删除+头顶目标等待重试），垫块节奏 4→2 减顿挫。


[22:22:0] v1.1.0 实测五十二：修复被动插火把严重浪费——亮度判定旧版只读方块光（白天地表恒 0，跟着走路一路狂插），改为原版合并口径 max(方块光, 天光-天空变暗度)：白天/雨天不插，夜晚地表与地下照常。


[20:20:0] v1.1.0 实测五十一：排班表 UI 重做——选班次（早/晚/全天）+ 6 个任务按钮排一天，不再手填时间；工作时间均分 6 份（相邻同任务自动合并），休息时间不排段由作息睡觉；手册排班章节同步更新。


[16:16:0] v1.1.0 实测五十：排班表物品图标改为游戏内旗帜（banner）图案样式（悬挂红旗，旗帜图案风）；附魔流光特效沿用实测十一的 0 级经验修补方案，确认仍生效。


promaid V1.1.0 实测四十八：伐木移动速度减半（反馈至少快一倍像狂奔）——基础速度 wood.moveSpeed 默认 0.6→0.3；远处（>8 格）赶路加成 1.5×→1.25×（基础变慢后远处不必再猛加速，衔接更自然）；锚点迁移赶路原本硬编码 0.6（正是过快的一处来源）改为随 wood.moveSpeed 配置走。已有存档的配置文件若残留 0.6，需在配置面板伐木页手动调低或改配置文件。


promaid V1.1.0 实测四十七：伐木站树洞发呆修复 + 站方块刷新寿命落实到挖矿/伐木——(1)【站树洞发呆】女仆砍掉树干下 3 节后走进树洞，头顶就是树干本体：垫方块（pillarUpStep）的防窒息检查拒绝在树洞里往上垫（头顶被树干堵死）→ 旧版不垫也不走也不挖，站桩发呆。现垫块失败时改为把目标切成头顶正上方第一块木材（同柱最多上探 8 格），下一 tick 从正常挖掘入口直接挖掉（伸手范围内），逐节往上啃完整棵树，全程不需要垫方块；(2)【站方块刷新寿命】站上垫脚方块刷新满寿命的机制原先只在搭路/战斗落实，挖矿/伐木没落实：旧判定只认『正在挖矿』标记，而该标记在空闲扫描期会短暂移除（女仆仍站在方块上）→ 方块在她脚下照常到期消失。现 PlacedBlockTracker 到期扫描加第一道判定：绑定女仆本人站在方块上（脚下或脚下下一格）恒刷新寿命；第二道谓词（同任务姐妹借踩）保持不变。魂符收回暂停计时、跨维度强制回包均不受影响。


promaid V1.1.0 实测四十六：女仆放方块音效修复（反馈放方块变成咆哮声）——旧实现用 levelEvent 3001+方块id 附加数据，该事件的附加参数在客户端按注册表 id 解析，mod 环境下 id 错位被当成无关音效播放（咆哮）。现改为服务端 playSound 直接广播方块自身 SoundType 的放置音效，音量/音调按原版 BlockItem 公式（(vol+1)/2、pitch×0.8），与玩家亲手放方块完全一致；挖矿垫脚/斜坡/搭桥、伐木同款、搭路 9 处放置点全部切换。


promaid V1.1.0 实测四十五：配置手册第2页起排版错乱根治——旧版行坐标是【从第0行开始全表累加】的绝对 y，渲染/实例化侧却只取当前页的 start..end 行去查这张表：第1页恰好正确，第2页第一行拿到的还是它在第1页时的 y（起点整体偏低、行高分布随页错位）→ 文本与输入框/注释互相重叠。现改为按【页】布局：init 侧先算每页行数，再把【当前页】各行从 CONTENT_TOP 重新起算的 y 存入 pageRowY，渲染与控件实例化同用一份字段，任何页/任何分辨率下行位置一致；渲染侧不再重算（旧重算逻辑同款错误）。


promaid V1.1.0 实测四十四：女仆区块强制加载（约等于玩家）+ 跨维度跟随真传送——(1)【区块强制加载】与主人不同维度的女仆，其所在区块自动挂强制加载票（2 秴=实体正常 ticking，玩家同等待遇）：主人走远/下维度后女仆区块不再卸载，跨维度跟随与死亡传送永远能找到她（旧版：主人远离后区块卸载→扫描找不到女仆→传送静默失效『女仆全部消失』）；同维度时不占票（区块由玩家自然加载）；女仆传送走/收回魂符/死亡自动撤票，服务器关闭全量撤票；杂项页新增开关（默认开）；(2)【跨维度跟随改真传送】旧版手动 setRemoved+addFreshEntity 搬家不走 Forge 维度事件链、实体不重新注册到新维度存储（假传送）；现改用原版传送（完整跨维度流程+客户端维度同步），并补传送后清残留速度。


promaid V1.1.0 实测四十二：垫脚方块回收机制重做——新统一追踪器 PlacedBlockTracker（挖矿/伐木/搭路/战斗四方块共用）：(1)方块登记时绑定搭建女仆（UUID），到期不再按位置找附近女仆，而是跨维度定位该女仆本人，把方块以物品形式强制送回她的背包（含下界之星等），背包满才落地掉落物；(2)女仆被收回魂符/不在世界期间，她名下所有方块计时暂停（找回后继续倒计时）；(3)方块被任意女仆踩着时刷新时长（不立即回收），离脚后继续计时；行为内与 ServerTickEvent 双通道到期兜底（旧版行为停止后靠 8 格找女仆、找不到就原地掉落是缺陷根源）；实测四十三：挖矿间歇性停歇修复——接近够不着的矿期间若女仆位置连续 40 tick（2 秒）未变（导航被地形卡住、有路径走不动，即玩家观察的『挖一会儿停一下，收回重放立刻恢复』的停歇状态），自动执行软复位：清移动目标记忆+停导航强制重寻路（与收回重放清空导航状态等效）；同一目标 3 次重寻路仍原地不动即判不可达，弃置该矿并 30 秒短时排除，不再原地磨蹭等待 15 秒目标超时。


promaid V1.1.0：六大新功能——伐木 / 枪械战术 / 主动切换战斗 / 排班表 / 搭路——(1)【伐木任务】全新任务 maid_smart:woodcut（架构克隆挖矿）：女仆自动找树、持斧渐进砍伐、连锁砍完相连树干（默认开）；树叶不挡视线不计阻挡（树冠里的树干照常可见）；木材表默认含全部原版原木/菌柄/竹/去皮变体，配置面板独立「伐木」板块，木材名单创造网格只列木质类产品（原版木质 tag 并集，模组木材自动纳入）；障碍物名单与挖矿共享一处配置；带原版 #minecraft:logs 标签的模组原木自动识别为可砍木材（默认开，价值 300——「自动识别模组原木（标签）」开关在伐木板块，关闭则只认名单；与挖矿要手动加模组矿不同，伐木对模组原木零配置）；手册详细介绍新增「伐木系统教程」章节；(2)【枪械战术（TACZ/卓越前线）】TLM 内置枪械任务接入 promaid 战术体系：持枪女仆自动装枪（弹药充足的枪优先），走位复用弓弩三叉戟同款风筝逻辑（理想射程 = TLM 枪械中距离配置 × 60%，远近自动调整、绕圈横移），接战范围/举盾豁免/远程攻击者识别全部覆盖枪械；切到枪械任务模式即自动换枪；(3)【主动切换战斗模式（默认开）】主人被敌对生物攻击时，附近（默认 16 格）的女仆无论在干什么（挖矿/伐木/烹饪/建造/跟随…）立即切战斗模式保护主人——装了 TACZ/卓越前线且背包有枪+弹药优先切枪械模式，否则从近战/弓/弩/三叉戟/弹幕按背包武器随机选；威胁消失 20 秒自动还原原任务（可配置；判定威胁消失用独立小威胁半径默认 8 格，战斗中玩家手动安排的任务不会被自动还原翻回去，有排班表的女仆还原时直接交给排班当前段）；优先级：自保 > 排班表 > 主动战斗 > 玩家手动；(4)【排班表】新物品（纸+墨囊合成，旗帜风图标+附魔流光）右键打开：女仆列表 → 快捷设置（工作模式早班/晚班/全天 + 任务切换 + 排班开关，点击即生效）/ 日程设置（00:00~24:00 游戏内时间分段编排一天干什么，每段 = H:MM~H:MM + 工作模式 + 该时段任务；保存自动去缝隙补满 24:00；跨段时间点自动切换，一天 20 分钟）；排班数据随女仆存档（魂符收放不丢）；杂项页有「排班表系统」总开关（关闭=排班调度停摆、日程数据保留，重开即恢复）；(5)【搭路（默认关）】主人在女仆上方 ≥2 格且距离 <7 格（传送判定距离）、周围无威胁、背包有方块时，女仆走到主人下方垫方块逐格搭高靠近（借鉴僵尸搭方块追人；真实消耗背包方块，搭的方块 10 秒自动回收）；搭路期间禁止瞬移防止白搭；被动技能页「搭路」开关默认关闭；(6)【落地雪（默认开）】落地水的细雪桶版：高空坠落时自动在【落点平面】铺 3×3 细雪垫接住她并收回（桶不消耗，与落地水共用触发高度/保持时长/下探格数，两者都有桶时优先用水）——细雪不流动、落点必须正好是雪，与水的放法有本质区别：水可提前放稍高处靠流动铺开（碰到的任何水都重置摔落），雪只铺在落点平面且坠落途中逐 tick 跟着落点补垫（防击退/横移漂移错过），绝不在高处拦她减速（出雪后剩下的路是自由落体照样摔）；下界也能用（水会瞬间蒸发、细雪不会）；细雪接触 7 秒（140 tick）才开始冻伤、保持时长上限 100 tick（5 秒）在安全线内；注意：落地雪天生比落地水难、有小概率失败（细雪不流动、落点被漂移出雪垫或落点格非空气铺不进雪等）——机制固有上限，追求稳定请优先给水桶；开关在被动技能页；(7)【修复】配置面板排版修复——①标签偏移：宽屏时行标签固定 x=20、控件居中，改用 panelLeft+10 对齐；②第2页起控件全堆到屏幕顶部 y=0：rowY 数组是全局累加，第2页 start 行的 rowY[start] 未赋值默认 0，布局侧和渲染侧都从 CONTENT_TOP 重新累加当前页行位置（实测二十四）；(7)【修复】挖矿/伐木中自己搭的方块不再被自己当障碍物/目标挖掉（10 秒标记内跳过——根治"拆了再搭"死循环；伐木搭路材料几乎必是原木，此前必触发该循环）；建造强制坐下被玩家"解除坐下"后坐着走动——建造坐下状态现全程启用移动源头锁（MoveToTargetSink 取消 + 实体层水平速度清零，保留垂直下落不溺水；直连导航/直接速度矢量等一切通道都无法再让坐着的建造女仆挪动，此前只在建造行为激活期间有锁，缺料/找区块等间隙漏锁）；发布前终审再修多处——主动切换战斗（①标记判定改 getBoolean：旧 contains 判定遇 putBoolean(false) 不删键永远为真——女仆打完一仗后永不再响应主动参战、排班调度器也会因她"看似在战斗中"而永久让位；②还原前先校验任务未被玩家换过，不再覆盖玩家手动安排的任务；③还原扫描改按持久化标记遍历——存档重读/换维度不再卡死在战斗任务，单只找不到战斗任务不再 return 跳过同半径其他女仆）；搭路（①canUse 探测改无副作用判定——旧版每轮启动尝试都凭空消耗背包一枚方块；②头顶被挡/材料耗尽 20 秒垫不出方块自动放弃并播报，不再原地僵站；③中途因威胁中止时脚下已垫方块延后到走开才回收，不再瞬间消失让女仆从半空掉进威胁堆）；home 模式（在家模式）的女仆不搭路——守家不出门，与跨维度跟随同款豁免；搭路新增「垫脚方块回收进背包」开关（默认开，全局开关——搭路/挖矿/伐木一切女仆搭的垫脚方块都适用；v1.1.0 实测十从搭路专属升级并默认开启）——开启后到期/被摧毁的垫脚方块不掉落地面，直接塞回附近女仆（8 格内最近者）背包，背包满/附近没人才落地；实测十二修复——排班表界面三连修（①崩溃：删掉唯一一行分段/数据未到时 schedTab 对空 rows 取 get(0) 越界直接崩游戏——空表现在只显示「+ 添加分段」按钮不再建行，冒号/波浪线渲染同口径；②日程 tab 切换：每次进入都无条件重新拉最新数据（旧版只在特定状态补发请求，切回快捷再切日程会显示旧数据或空白）；③时间格式固定 □□：□□～□□：□□ 六框形状——小时十位空也显示框（9:00 显示成 _9:00 而不是挤掉一个框），形状不随数值变形，每个框仍只填一个数字）；实测十一修复——排班表物品常驻附魔光效（附魔书同款紫色流光——ScheduleBookItem override isFoil 恒 true，与 UI 红色调+紫红斜体标题的排班主题呼应）+ 物品图标改为红色调书本贴图（原先是借用原版 writable_book 的棕白贴图，与排班界面的红色主题不搭）；实测九新增——伐木冲破树叶（伐木状态下女仆身边 3×3×3 立方体内的树叶每秒直接摧毁并产生掉落物【autoCollect 开着直接进背包】——走路被树冠卡住不再绕路/站桩，直接穿过去；开关跟随树冠清理 wood.leavesClear，不想清叶一起关；立方体内的原木不碰——那是正常挖掘目标，走挖掘流程有工具加成）；实测八扩展——女仆互助链对齐主人支援链（把套给主人的方案复刻给姐妹女仆：①着火丢抗火药水；②负面效果喂牛奶全解【空桶返还；女仆版不做有增益不喂牛奶的保守检查——战场实用主义】或蜂蜜【解中毒+回饱食+玻璃瓶返还】；③低血走完整治疗链——喷溅治疗→金苹果/附魔金苹果【吸收+再生，附魔加抗性抗火】→再生药水→按饱和度择优喂食【手持也认】；主人优先级永远更高——同一 tick 主人链先跑完，主人有需求时互助轮空）；实测七修复——搭方块选材全局统一安全过滤（新建公共过滤器：火把等【无碰撞体积】方块绝不能垫脚——旧版四个模块【自保搭高/挖矿/伐木/搭路】的选材只拦下落方块，火把成了背包里数量最多的方块就会被选中，垫了个寂寞还白消耗；统一过滤 = 非下落方块 + 非 TNT + 伤害黑名单【仙人掌/岩浆块/甜浆果/营火/细雪等】+ 碰撞形状非空 + 非可替换方块【#minecraft:replaceable——火把/草/雪片第二道闸】+ 完整碰撞形状，战斗/挖矿/伐木/搭路一切垫脚场景同口径）；实测六新增——女仆互助战斗支援（①不死图腾共享范围扩大：女仆自己手里/饰品栏/背包的图腾全用完仍是致命伤时，16 格内同一主人的其他女公会共享图腾救她——与救主人同机制（背包优先再饰品栏、真实消耗、原版特效），双向气泡播报；②女仆给女仆丢药水：同主人 16 格内的姐妹低血（低于治疗阈值）或着火时，有富余药水的女仆会给她丢喷溅治疗/再生/抗火药水（同款追踪弹+择优+同种 CD 记账；主人不在身边时互助照常运作——两女仆外出战斗场景；主人优先级永远最高）；③投掷药水命中率优化（追踪弹速度 1.6→1.05 格/tick——旧速度单 tick 位移超过目标碰撞箱半径会整个穿过去、方向修正永远慢一拍；近距离直接命中阈值 1.2→2.5 格——喷溅半径内贴脸必中））；实测五新增——伐木树苗补种（树砍完后在树干原址向下找泥土/草地面，从背包拿任意树苗【#minecraft:saplings 标签，模组树苗自动兼容】种下消耗一个，限频 5 秒防连种；运作逻辑与插火把同款被动式——手中有树苗才会种，没有就跳过）；插火把兼容更多火把（灵魂火把/红石火把等——普通火把绝对优先，背包有普通火把绝不用灵魂火把【亮度 14>10】，只有普通火把用完/没有时才退而求其次；放置按火把自己的方块不再是写死的普通火把）；实测四修复——搭路区分地面/空中两套距离阈值（地面维持 7 格传送判定距离——平地上太远该走路/传送不铺桥；空中【脚下悬空或站在垫的方块上】放宽到独立配置 airMaxDist 默认 24 格——空中没有走路选项，远远看见主人就直接空中铺桥走过去，canUse/canContinue 双口径同步）；伐木新增「树冠清理」开关（默认开，伐木板块）——树干砍完后顺手把上方树冠的树叶也清掉（半径 3 格 BFS 相连树叶，掉落物/树苗/苹果直接进背包），不再挂着挡视线慢慢掉东西；实测三修复——伐木中距离站桩（斜坡/搭桥距离门槛 4.5→8 格——树干下半部常在 4.5~8 格，旧版这段距离不垫也不走干等；找不到站立点时直接朝树干走不再原地站桩；站立点搜索圈扩到 2 格——树冠内 1 格圈常被树叶占满）；搭路空中水平铺桥（参照僵尸追人：主人高度接近但水平还远时，朝主人方向前方一格脚下直接垫方块走过去——旧版只垂直叠柱/傻站，导航在半空永远失败）；搭路威胁半径默认 12→8（刷怪频繁的整合包里 12 格内几乎永远有怪，搭路会永不触发；主动切换战斗的还原判定同样新增独立的 8 格威胁半径配置，响应半径 16 不变）；排班表界面（两个设置 tab 旧版建在同一坐标完全重叠——看得见的是「日程设置」、点中的却是先注册的「快捷设置」；已改左右并排，整页窄屏自适应——排班开关不再与 tab 同行、分段行宽度/行数按窗口尺寸收敛，六档分辨率像素审计 0 重叠 0 出屏）；搭路方块耗尽改立即中止播报（不等 20 秒；选材保持「背包数量最多的可放置方块」不变，canUse 无料不触发原本就有）；终审二实测反馈修复——伐木空手傻站（找树前/够得着树后主手没斧都不会主动从背包装备，改 findWood 前 ensureAnyAxe + 挖掘前补换斧，完全没斧时限频播报）；搭路向前搭（水平未对齐时朝主人方向前方一格脚下垫台阶斜上逼近，不再只垂直叠柱等导航绕路）；排班表界面重做（红色调+附魔书风紫红斜体标题+顶部红色饰条；列表只显示名字不再拼任务串；任务名翻译缺失时回退显示 path 段而非 task.touhou_little_maid.xxx 整串；时间改 6 个单数字输入框「时时:分分～时时:分分」默认空、只收数字；末段到 24:00 后「+ 添加分段」按钮隐藏）；钓鱼任务自动换钓鱼竿（主手非钓竿即从背包装备，优先级与武器一致——TLM 钓鱼任务条件就是主手持钓竿）；建造强制坐下改走 setOrderedToSit 指令位（与玩家 shift+右键坐下完全同状态，根治坐姿位与指令位不一致导致的坐着小幅挪动）；卓越前线能量武器（二次灾变/超级星星炮）不吃常规弹药（内部充能）——枪械可用判定与装枪评分对它们跳过弹药检查（旧版「枪+弹药」硬要求导致拿二次灾变判定无子弹不切枪械模式）；(8)【手册】玩家可操作原则补齐——杂项页新增「排班表系统」总开关；战斗与自保/被动技能/单兵战术/武器切换章节补主动切换战斗、搭路、枪械战术的使用说明与开关位置；新增「排班表（全女仆日程调度）」教程章节（合成/两设置页/让位规则/举例）；配置面板导航补 v1.1.0 各开关入口；伐木/搭路/主动战斗章节同步补模组原木自动识别、还原威胁半径与玩家手动接管语义。；实测十五修复——搭路追主人加任务空闲门禁：挖矿/伐木已锁定目标（MINING/WOODING 标记）、建造任务且未暂停、烹饪/酿造站桩中都不再启动搭路；搭路途中任务重新占用（排班切班/矿刷新被重新锁定）立即中止让位、脚下方块 10 秒自回收——手上的活没干完不撂挑子，挖完矿的空闲间隙（框内无矿、标记已摘）照常追主人；实测十六修复——发布前全面审查（P1×5 + P2×8）：①主动切换战斗标记判定 contains→getBoolean（旧版 putBoolean(false) 不删键致 contains 永真——女仆打一仗后排班永久失效+每秒无效 NBT 写）②空手砍树双 return 死锁（没斧的女仆锁树后永久发呆——没斧照砍承诺落地：换不上斧播报后继续走挖掘公式）③树冠清理 BFS 起点失效（旧版从树干正上方一格起扫、非叶即终止——常规自底向上砍树几乎触发不了；改为沿树干向上爬最多 8 格找第一片真树叶起步）④金苹果/喂食链无 CD 连喂（旧版只有增益链记 3 秒间隔——主人低血+治疗药水在种类 CD 内时每 tick fallback 金苹果，几十个几秒蒸发；互助链每 tick 全量扫描+双重调用无节流——统一 CD 记账+互助链复用同一间隔让位）⑤排班 taskUid 直接 parse 脏数据崩服（改 m_135830_ isValid 预检+try/catch 双保险——1.20.1 无 tryParse）⑥落地水/雪 doStop 回收兜底（保持期内魂符收回/死亡→水/雪永久残留——加 doStop 兑底+回收改女仆当前维度感知）⑦树苗/火把消耗改 extractItem（旧版直缩 getStackInSlot 返回栈——handler 返副本时扣不掉=无限刷方块）⑧purge 存活联合补 SAPLING_PLANT_SINCE（旧版漏加→活女仆补种限频被每 30 秒误删→多耗树苗）⑨图腾 mixin 致命伤路径加整体 try/catch（旧版异常抛穿 hurt/die 链最坏崩服）⑩图腾特效改 levelEvent 2007（旧版 levelEvent 35 要求双手持图腾——从背包/饰品/同伴消耗时客户端什么都不渲染，四个分支全中招）⑪红石火把移出自动插火把清单（亮度仅 7 且会发红石信号——黑暗中自动放可能意外激活附近红石机关）⑫排班表 S2C 包加方向校验（恶意客户端发 S2C 包 ID 到服务端→加载客户端类→专用服崩）+Screen 空任务列表 IOOBE 守卫+副手食物哨兵值 -1 冲突修复；实测十六续二修复——P3清理项：①蜂蜜喂姐妹返还玻璃瓶（旧版吃完蜂蜜不返还瓶子——主人链有返还逻辑姐妹链漏了，物品凭空消失）②树冠清理掉落尊重 WOOD_AUTO_COLLECT 开关（旧版无条件进背包，与 burstNearbyLeaves 口径不一致——开关关着时该落地由拾取任务处理）③树苗补种可种地面改用 #minecraft:dirt 标签（旧版只认 dirt/grass_block——雨林/红树/繁茂洞穴等长在灰化土/苔藓/泥巴/缠根土上的树不补种；标签涵盖全部泥土变体+模组泥土）；实测十六续修复——P2/P3 深度加固：①药水 CD 改按女仆 UUID 分桶（旧版按效果种类全局共享——A 女仆给主人投治疗 B 女仆的治疗链跟着进 CD，投给姐妹的治疗也占主人的 CD；改为 (maidUUID, potionKey) 二元 key 每女仆独立）②跨维度排班请求回空数据包（旧版女仆不在当前维度时服务端静默 return→客户端 waiting 永不清除、日程 tab 永远卡请求中…；改为回空数据包客户端正常显示空表）③伐木危险方块判定改 static 缓存（旧版每次调用对 6 个 ID 各做一次 ResourceLocation.parse+注册表查询——fullScanWoods 每 5 秒数百候选×6=数千次注册表查询，改类加载时解析一次）④APPLIED_TAG 类型混写统一（AutoCombatSwitch 用 putBoolean 清、ScheduleManager 写 String——getString 对类型不符安全返回空串恰好等效但类型不一致是隐患，统一 putString 空串）；实测十七新增——战斗搭方块改为 60 秒自清 + 女仆可拆（自保行为的搭高/翻墙/搭桥/封头盖帽/岩浆垫高方块原本【永久留下】——玩家当初选择不清理；现改为与挖矿/伐木/搭路同款的标签追踪机制：①60 秒后自然消失——战斗节奏多变女仆可能还要在塔上待一阵，比挖矿的 10 秒长；女仆还站在上面的方块延后回收（搭高塔上打持久战不会脚下被抽走）②女仆可以直接破坏挡路的战斗方块——逃跑方向前方 1~2 格（脚部+头部高度）有自己搭的方块直接拆掉再导航，实际战斗多变，防止女仆自己搭了个死路还逃不出去；只拆自己登记过的战斗方块（自然地形/玩家建筑绝不碰）③bridge.reclaimToMaid 开关（垫脚方块回收进背包）从仅控制挖矿/伐木/搭路扩展为也控制战斗方块——开启时到期/被拆的战斗方块直接塞回附近女仆背包，关闭则正常掉落落地④玩家替换过的方块不误破坏（blockId 比对同口径）；服务器停止时残留战斗方块立即回收清场）；实测十八修复——搭方块站上面时刷新计时：旧版挖矿/伐木/搭路/战斗方块对正被女仆踩着的到期块只做【无限延后】（tick 不变、走开即清）——挖矿女仆在高柱上连续作业几分钟，脚下块早已过期但被一直延后，她一挖完走开整根柱子【瞬间全部到期】（含下落途中可能踩到的下段）→ 高空坠落。改为女仆踩着时【重置该块寿命】（从最后一次被踩起算满寿），走开后每块还有完整的 10 秒（挖矿/伐木/搭路）/ 60 秒（战斗方块）缓冲逐块到期，不会整塔瞬间塌——四张追踪表（MINE/WOOD/BRIDGE/COMBAT_PLACED）统一口径）；实测十九新增——战斗搭方块清理时间可配置（combat.placedLifetime，默认 60 秒，范围 3~600——与其他搭方块清理项同款玩家可调；搭路配置页新增「战斗搭方块清理（秒）」输入行；挖矿/伐木/搭路的清理时间原本就可在各自配置页调整）；实测二十重构——主动切换战斗触发与选模式双双升级：①触发条件扩展——主人被【任意来源】攻击（敌对生物/玩家 PVP/弹射物都算，仅自伤排除）或主人【攻击了别的生物】（主动开火也算开战；打自己女仆的误伤不触发）都让附近女仆参战②选模式重构——删除「枪械优先」开关，改为扫描 TaskManager 全部 IAttackTask 攻击类任务（自动含万法皆通/史诗战斗/真正的力量等模组任务与 TLM 枪械），按各任务自己的 isWeapon 匹配背包武器过滤候选，候选池加权随机：模组任务权重 2.0、原版五件套（近战/弓/弩/三叉戟/弹幕）权重 1.0（模组武器普遍更强降半权但不绝对排除）；任务对所有物品都不认（不限武器型模组任务）照样入池；全都匹配不上空手近战兜底③配置面板「枪械优先」行删除（旧配置文件残留键无害），手册/面板说明同步更新；实测二十一半——武器权重可配置（选战斗任务的加权随机权重拆成两条配置项：模组武器权重 combat.autoSwitchModWeight 默认 2.0、原版武器权重 combat.autoSwitchVanillaWeight 默认 1.0——例：背包法书+铁剑时法术:近战≈2:1=67%:33%；想五五开把两条都设 1、想模组绝对优先把原版设 0.1；面板「主动切换战斗」小节新增两行输入框，范围 0.1~10）；实测二十一半附——战斗还原延迟默认 20 秒改 10 秒（autoSwitchRestoreDelay 默认 400→200 tick——威胁解除后更快回岗；该配置项原本就有 UI：面板「主动切换战斗 → 还原延迟（tick）」输入框，范围 60~3600，手册说明同步）；实测二十二新增——配置面板像素级重叠防御：旧版行高固定 44px，注释折成 3 行以上时（长说明+窄窗口+GUI 缩放大）尾部与下一行标签/输入框像素重叠。新架构三层防护：①wrapComment 像素切割——注释按面板实际像素宽逐字符累积测量（font.width）超宽即折行，成为高度计算的统一口径②rowHeight 动态行高——每行高度=控件 22px+间隔 3px+注释行数×10px+底留白 9px（SectionRow 紧凑 18px），注释长行自动加高③布局与渲染同口径——perPage 按动态行高从 CONTENT_TOP 逐行累加计算（超出内容区换页），行位置两侧共用同一份累加结果，彻底消除两套计算脱节的根源；任何分辨率/GUI 缩放下注释与下一行永不重叠，长注释页自动少排几行；实测四十一调整——伐木两项默认开启：①透视感知（隔墙找木材）默认值 false→true——树木天然被树冠/地形遮挡，关着容错率太低；②连锁砍伐代码默认本就是 true，但现存档配置文件里是 false（历史遗留），已同步改回 true——与挖矿连锁默认开对齐。两项仍可在配置面板伐木页随时关闭；实测四十新增——远程被贴身近战反击：远程走位（rangedTick）新增贴身判定——敌人贴进 2 格内触发一次近战反击（冷却 2 秒防连拍）：摆臂动画 + 横扫粒子（levelEvent 2004，玩家横扫同款）+ 对贴身目标及 2 格内其他敌人造成近战伤害 + 原版击退（0.5 力度，玩家普攻级）；伤害 = 背包 DPH（攻击力属性修饰和）最高的非远程武器（枪械/弓/弩/三叉戟按判定口径不算近战），无近战武器 1 点空手伤害；反击不换主手（远程武器保持在手，反击是瞬发动作）；实测三十九修复——主动战斗威胁消除后无法还原原任务：根因是还原流程【先清标记再还原】——还原链路任何一环失败（战斗前任务找不到/排班重应用异常/任务 UID 非法），标记已被清掉：下次触发时她被当成「玩家手动安排的战斗任务」跳过、还原扫描也因无标记跳过 → 永久卡死在战斗任务；且还原全程零日志无从观测。修复：①改为【先解析战斗前任务、全部就绪才清标记执行还原】——解析失败保留标记下轮重试，不再一失足成千古恨；②还原全程加日志（latest.log 搜 auto-combat restore：成功记 原任务←战斗任务+威胁消失时长、失败记原因 FAIL/任务被换）；实测三十八新增——主动战斗选武器加距离判定：候选攻击任务先按武器类型分近战/远程两池（原版近战 attack 与 ef_tlm 史诗战斗/slashblade 拔刀剑/sbr_core/truepower 系=近战；原版弓/弩/三叉戟/弹幕/枪械与万法皆通法术系=远程；未知模组任务按近战兜底），只有一类有武器时该类内仍按原版/模组权重随机；两类都有时看女仆与最近敌对生物的距离——≤5 格用近战池、>5 格用远程池（池内仍加权随机，不退化成固定顺序），找不到敌人按远程兜底（安全距离输出）；选择结果记日志（auto-combat pick: both-pools dist=x -> melee/ranged）；实测三十七新增——女仆搭方块播放动作：挖矿/伐木/搭路三大行为里全部 9 处放方块点（垫高 pillarUpStep / 斜坡台阶 slopeStep / 搭桥 bridgeToOre·Wood / 搭路的垂直·斜坡·空中铺桥）统一补上摆臂动画（swing 主手，与挖方块/插火把同款）+ 放置音效粒子（blockEvent 3001，玩家放方块同款反馈）——旧版方块凭空出现没有任何动作，观感突兀；实测三十六修复——主动切换战斗「主人被打仍不触发」：LivingHurtEvent 不是唯一受伤信号——枪械模组（卓越前线等）伤害走自定义管线（自管伤害计算/减伤），事件可能【不发】或被其他模组（EF/SBW 自带减伤）【取消】。兜底：触发事件从单 LivingHurtEvent 扩为三事件全监听——LivingAttackEvent（受伤链最上游，任何模组 cancel 之前必经）+ LivingHurtEvent + LivingDamageEvent（结算层），三事件任一先到即触发、后到的被 20 tick 节流去重（同一次攻击只切一次）；主人开火侧同样补 LivingDamageEvent 兜底；触发时记日志（auto-combat trigger: owner 名字 hp——下次再不生效可直接看有没有这行）。；实测三十五调整——插火把检索再提速：①检索冷却 15 tick（0.75 秒）→ 10 tick（0.5 秒）；②亮度扫描范围主人脚下 3×3 → 5×5——旧版只有一格宽的跟随带，走路时边缘暗格轮不到检索就走出去了；5×5 覆盖正常走路节奏，每轮仍只插最暗的一格（节奏由冷却控制，不连插一片）；实测三十四修复——喂姐妹蜂蜜瓶不解中毒：姐妹链走 LivingEntity.eat 进食——只加饱食/食物效果，原版「喝蜂蜜解中毒」的 removeEffect(poison) 发生在 HoneyBottleItem.finishUsingItem（玩家喝完才触发），eat() 不经过它；主人链之所以有效是因为 feedFoodDirect 里手搓了 removeEffect。姐妹链补同款解中毒 + 喝蜂蜜音效；实测三十三重做——排班表 UI 整体重写：旧界面多轮补丁后布局参数多处复制粘贴口径不一（tab 与排班开关重叠、6 个单字符数字框焦点链脆弱易丢字、渲染与按钮两套坐标各算各的）。重写原则：①所有控件坐标集中在唯一布局函数、渲染层静态文字复用同一组常量——消灭「改一处漏一处」；②固定栏式分区：顶部标题/tab/内容区/底部按钮区互不重叠，行数按窗口高度自动收敛；③时间段改单输入框（形如 9:00~17:00 直接打字，保存时校验）替代 6 数字框——多行输入焦点稳定不丢字；④任务按钮文字截断防溢出，超可视行数给「还有 N 段未显示」提示；⑤工作模式改普通循环按钮（旧 CycleButton 在部分场景不刷新）。功能与网络协议不变，已保存的排班数据不受影响；实测三十二修复——女仆支援系统四项：①姐妹支援气泡固定文案「药水来了」改为「姐妹挺住，我来帮你」，并新增绿色系统字幕记录具体支援内容（[maid_smart] 某某 支援了 某某：扔了治疗药水/喂了金苹果/喂了牛奶（负面全解）/喂了蜂蜜瓶（解中毒+回饱食）/喂了某食物——与喂主人字幕同款显示）；②喂主人蜂蜜瓶修复：手持分支旧版先把蜂蜜消耗成空瓶再喂（空瓶无食物属性喂不进→蜂蜜凭空消失且无效果），改为复制一份喂、成功才消耗；③普通饮用型药水传递补绿色系统字幕（传递功能一直在但只有气泡提示，观感像没给——现在「女仆给了你一瓶治疗药水（在背包里，快喝）」）；④音效补齐：喂金苹果（主/姐妹链）、喂姐妹牛奶旧版静默无声，现在都有对应吃/喝音效；实测三十一调整——插火把间隔减半：插火把冷却 30 tick（1.5 秒）→ 15 tick（0.75 秒），照明跟上走路节奏；实测三十调整——伐木树叶清理口径改小：删除砍完树干后整冠 BFS 清除树叶的 clearTreeTop，树叶清理只保留【以女仆为中心 3×3×3】的随行破坏——女仆走动经过树冠逐层清叶，每块树叶破坏时各自播破坏音效+粒子特效（levelEvent 2001，同玩家挖方块）+ 摆臂动画，不再是静默瞬清；树苗补种补齐反馈与诊断：种下时播种下音效/粒子，跳过原因（背包没树苗/找不到可种地面）和种下成功都记日志（latest.log 搜 wood sapling）；实测二十九修复——搭路/方块回收间歇性失效根治：搭方块追踪表（内存 PLACED）只在正常退出时清场（ServerStopping），崩溃/任务管理器强杀进程后残留进新会话——①回收失效：残留的放置 tick 是旧会话 gameTime，新会话时间更小，「放置时间+寿命」判定永不过期，垫脚方块从此不回收；②搭路失效：残留位置命中脚下时 isAirborne 误判为空中，行为状态混乱；③BRIDGING 标记持久化在存档里，崩溃时没走到清理代码 → 重进后女仆永久背着「搭路中」（禁瞬移）。现兜底：每次服务端启动（ServerStartedEvent）清空全部搭方块追踪表 + 全量清除女仆 BRIDGING 标记（此刻任何女仆都不可能在搭路，清理零误伤）；另加诊断日志（latest.log 搜 bridge-up：启动记 owner-dy，中止记原因 reached/threat/no-block/head-blocked/task-occupied/owner-too-far 等，间歇性失效可精确定位）；实测二十八修复——主动切换战斗「主人主动开火」触发从未生效：旧版判定把受害者当成攻击者（getEntity=被打的人 instanceof ServerPlayer、又要求来源也是同一玩家——主人打怪时受害者是怪物，条件永远不成立，只有玩家互打才触发）；现改为【来源】是玩家即触发（受害者不限、打自己女仆除外）。被动挨打分支（主人被打→女仆参战）逻辑本来就正确。另加限频诊断日志（latest.log 搜 auto-combat：记录每次切换 女仆名 原任务→战斗任务，触发但零切换也记一笔）；实测二十七修复——挖矿「软方块不耗耐久」默认值改回开启（代码默认本就是 true，但注释与面板描述写着"默认关"系历史遗留；现存档配置文件里已是 false 的会被本次部署同步改回 true——与伐木行为一致，软方块开路不磨损工具）；实测二十六新增——排班表物品进创造物品栏「工具与实用品」标签页（与蓝图卷轴同页，可直接拿取；拿出的即带附魔流光）；实测二十五修复——全部界面翻页按钮改 20 像素宽纯箭头 ◀/▶（旧版 80~100 像素宽的"上一页/下一页"按钮在五个界面里都存在遮挡正文/图标的问题：排班表盖女仆行、手册盖正文末行、配置面板盖注释末行、矿表/替代品表盖网格底部图标、蓝图手册盖建筑名/材料行/女仆行/区块行/记忆行/调试行、文件选择器盖底行文件名；现在按钮只保留箭头符号、去掉了"上一页/下一页"文字，页码文字统一画在左右两枚箭头的正中间同一行——像素级计算：箭头各占 20px 分布在中心线两侧 ±20~±40px，页码居中不与箭头矩形相交，任何分辨率/GUI 缩放下不再遮挡文本）；实测二十三修复——岩浆格视作硬阻挡；实测十四新增——攻击任务接口化统一判定（主动切换战斗的让位检测升级：不再只按固定 UID 名单认战斗任务，改为直接问当前任务【是否实现 TLM 的 IAttackTask 接口】——万法皆通/史诗战斗等第三方模组给女仆注册的攻击任务一并识别：①主人被攻击时她们本来就在攻击任务上，绝不会被强切回原版近战/弓模式（尊重玩家手动安排的第三方攻击任务）；②副手盾牌自动装备、干活不被打断闸门（吃饭/偷吃/恐慌/切班拉回）全系列同口径覆盖第三方攻击任务；UID 名单保留为兜底）随 promaid-1.1.0.jar 部署，重启游戏生效。


promaid V1.0.4：挖矿体验大改 + 透视感知开关 + 矿表 UI 重构——(1)【透视感知（默认关）】女仆不再隔墙透视挖矿：关闭时像玩家一样只能发现视线无阻的矿物——除水/岩浆外任何方块（泥土/石头/玻璃/半砖等）都挡视线，被挡的矿不可见、不报点，也不会隔墙挖穿（已锁定目标同样持续检查视线）；开启后恢复隔墙找矿并挖穿开路（等同旧版逻辑）；(2)【矿表赋值交互重构】点方块图标锁定（黄框固定，鼠标移开不消失）→ 输入框出现 → 输优先级数值点「添加」/回车即赋值/加入（留空直接点添加/回车 = 默认价值 300）；悬停图标显示中文名/英文 id/绿色当前优先级；已加入矿物右上角 × 取消添加；列表条目显示中文名 + 价值输入框 + 标准删除按钮；(3)【UI 修复】矿表/替代品列表滚动条不再是一根突兀的亮色竖线（低调样式）；悬停提示移到网格右侧空白不再与图标重叠；障碍物输入校验方块真实存在；矿表网格每页 24→48 个（翻页数减半）；(4)【死 UI 清理】删除 4 个无效果配置项（主动会话记忆话题/防窒息传送冷却/恢复缓存保留/沉默阈值）；播报间隔、女仆管理上限两个原本硬编码的配置恢复生效；(5)【建造加固】建造中女仆每 tick 锁定移动——玩家解除坐下后也不会"坐着走动"；(6)【手册】挖矿章节更新为新交互说明，修正首页板块等过时描述；(7)【内置蓝图】移除授权不明的内置预制建筑（大型 snbt），内置蓝图仅保留 81 套授权玩家蓝图包，旧版自动复制的预制残留一并清理。随 promaid-1.0.4.jar 部署，重启游戏生效。


promaid 1.5.385：材料经济链审计修复——自动转交材料不再吞物品——(1)【物品丢失修复】transferFromPlayer（玩家背包→女仆背包的蓝图材料转交）：女仆背包放不下时，旧版把未插入的 left 部分计入 back 公式，但 whole.split(back) 最多只能分出 whole 剩余数量——放不下的物品凭空消失（注释声称"放不下的留在玩家原处"实际没有）。改为：剩余放回玩家原槽，放不下的还回玩家背包（PlayerMainInvWrapper），背包也满则掉落身侧；(2)【复核通过】consumeBlock 精确/等价族/自定义替代三级消耗逻辑与创造性模式豁免正确，water=无限/岩浆=消耗桶+空桶归还闭环；extractExact/returnEmptyBucket/deliverToMaid 计数正确；等价族计数（countMaterial/combinedHaveAll）一致。随 Promaid-1.0.12.jar 部署（备份 .bak_v1384），重启游戏生效。


promaid 1.5.384：深审补漏——导入文件名清洗——(1)【纵深防御】importBuildFile/importWorldFile 的目标文件名来自客户端可控路径，旧版直接 new File(blueprints目录, 原名)——含路径分隔符/.. 的恶意文件名可逃逸 blueprints 目录（服务器端已有 OP 门槛，此处再加一层：拒绝含 / \ .. 或纯 . 的文件名）；(2)【复核通过】SRG NBT 方法名全库扫描（m_128441_=containsKey 事故模式无复发，6 处全部正确）；语音包导入目录遍历与 zip 分支均有 startsWith 归一化防穿越+扩展名过滤；BlueprintLib 缓存失效全覆盖（文件变化按条目清除/扫描清残留/重启全清）；parseStep/parseJson/validate 边界完整；双 agent 提取（v1.1.0）完成结算正确（remaining 防双重递减、水位线双完成后推进、EXTRACTING 超时自愈）。随 Promaid-1.0.12.jar 部署（备份 .bak_v1383），重启游戏生效。


promaid 1.5.383：全面审计修复（安全/稳定性/泄漏/性能）——(1)【安全S1】语音包/世界地图/建筑三个导入包在联机服务器上改为仅 OP 可用：旧版任意玩家可发任意绝对路径让服务端读文件（单机同机无感，服务器是文件读取漏洞）；蓝图删除包同样加 OP 门槛（防任意玩家删他人蓝图）；记忆状态查询包补主人/OP 校验；(2)【稳定性S2】记忆日记生成的 LLM 响应解析异常（偶发坏 JSON）不再静默丢边界——挂入 pending 自动重试；(3)【M1孤儿票据】无绑定女仆超 10 分钟的建造计划自动取消（已建方块保留可续建）——防女仆全消失后 FORCED 区块票据与区块冻结永久驻留拖垮服务器；每 5 分钟自检一次；(4)【M3数据安全】女仆实体卸载/换维度时立即落盘记忆（旧版 WeakHashMap 键被 GC 时最多丢 20 秒窗口内的写入）；(5)【M4泄漏】实体卸载时清理挖矿行为 10 张状态表与感知危险缓存（长会话泄漏）；(6)【性能】归档器状态内存缓存（消除每 20 秒/女仆的磁盘读）；日记覆盖判定（登出部分天被完整天覆盖不再重复生成）；判空轻量化（不再拼整块事件字符串）；文件对话框线程改 daemon。随 Promaid-1.0.12.jar 部署（备份 .bak_v1382），重启游戏生效。


promaid 1.5.382：手册与配置界面跟进记忆系统——(1)【配置面板】AI 记忆段新增「多级记忆索引」配置组：多级记忆索引 / 睡一觉自动处理 / 登出会话收尾 / 月索引保留事件数 / 单次索引事件上限 / 短期→长期阈值（游戏日）六项，带说明悬浮提示；(2)【手册·女仆记忆页】新增「记忆日记」段（日/3日/周/月日记式摘要，显示篇数与最新 6 篇的级别/天数跨度/内容预览；暂无时提示"睡一觉后自动整理生成"）；记忆条目新增【长期】标记（long_term 沉淀记忆，与【永久】并列显示）；(3)【手册·AI 记忆系统章】新增"记忆日记"与"睡一觉自动整理"两段说明（含熬夜不整理、服务器登出收尾的语义）；AI 工具清单新增 query_memory_index；(4)【TLM 女仆配置面板】记忆区新增"记忆日记：N 篇（最新X记 第A~B天）"状态行，记忆条目前缀【长期】标记。随 Promaid-1.0.12.jar 部署（备份 .bak_v1381），重启游戏生效。


promaid 1.5.381：会话收尾按服务器/单机区分——(1)【单机集成服】玩家退出即关服、剧情时间停滞，登出收尾无意义（记忆原地不动，下次游戏内睡眠收尾会以"完整一天"的日级索引自然覆盖），登出处理器直接跳过，也不再写 pending——避免在关服竞态里抢跑；(2)【服务器】剧情时间持续推进，玩家登出后世界照常运转——保留登出收尾（当日日记先持久化 pending 再生成，下次进游戏由周期 tick 自愈补完成）。判定方式：MinecraftServer instanceof DedicatedServer。随 Promaid-1.0.12.jar 部署（备份 .bak_v1380），重启游戏生效。


promaid 1.5.380：会话收尾归档——补齐 Sphantosis 睡眠语义的【真人玩家】维度——(1)【背景】原项目是面向真人玩家的 AI 聊天记忆系统，wrap-up 收尾同时覆盖"角色入睡"与"真人玩家结束一天下线睡觉"（WorldManager 有状态机 active/sleeping/offline、角色作息表）；v1.5.379 只映射了游戏内睡眠维度，漏掉了真人维度。(2)【登出收尾】玩家登出（PlayerLoggedOutEvent，周围128格女仆）= 真人睡觉/结束一天：当日（部分天）「日」级日记边界【先持久化进 pending 再尝试生成】——单人模式关服后异步 LLM 响应可能丢失，pending 已落盘，下次进游戏由周期 tick 自动补生成（"女仆在你睡觉时整理好了昨天的记忆"），并复用睡眠收尾的边界检查与短期→长期提升；(3)【pending 语义加固】出队条件从"异步请求已发出"改为"has() 确认已归档（或跨度无事件）"——在途丢失自愈，不再静默丢边界；(4)【配置】新增 memory.indexOnLogout（默认开）。随 Promaid-1.0.12.jar 部署（备份 .bak_v1379），重启游戏生效。


promaid 1.5.379：记忆移植架构修订——熬夜排除 + 移除伪图聚簇 + 索引上下文长度管理——(1)【熬夜排除】归档触发从 PlayerWakeUpEvent 改为 SleepFinishedTimeEvent（全员真实睡过夜、时间跳到清晨才触发的服务端事件），并移除周期调度的跨日生成兜底（周期 tick 退化为纯 pending 重试）——白天躺床即起/入睡失败不误触发，熬夜过夜不再归档，「睡一觉自动处理」名副其实；多日未睡的日级空档不回填，由 3日/周/月 粗粒度层级自然覆盖；(2)【移除伪图聚簇】v1.5.378 的短期→长期转移用 ngram-Jaccard≥0.42 并查集伪聚类近似 Sphantosis 的图连通分量——但 promaid 段落是扁平线性列表（无 LLM 建立的语义边、单一存储），连通性检查无对应物，且 O(n²) 每 20 秒常驻是负优化。改为线性架构匹配的 O(n) 单遍规则：short_context 段落年龄≥shortTermDays 游戏日 且 重要度≥decaySalience → 打 long_term 标记（豁免衰减遗忘），仅在睡眠收尾时执行；(3)【上下文长度管理】新增配置 memory.indexMaxEvents（默认40）：所有级别索引的事件块超限按重要度裁剪后再按时间排序（月级另受 indexMonthTopN≈20 更紧约束），忙日不会撑爆摘要 prompt；prompt 同步注明事件可能已截取。随 Promaid-1.0.12.jar 部署（备份 .bak_v1378），重启游戏生效。


promaid 1.5.378：移植 Sphantosis 记忆系统——快速记忆索引 + 睡一觉自动归档（experimental/memory-port 分支）——(1)【跳表时间索引】自研跳表（游戏tick→段落hash，O(log n) 范围查询）取代全量线性扫描，索引生成/按时间检索记忆提速；(2)【多级记忆索引】日/3日/周/月四级日记式摘要（LLM 第一人称日记，跨度越大压缩越高，月级仅保留重要度 top20），跨游戏日/周(7日)/月(30日)边界自动生成，永久归档 memory_index.jsonl，失败不静默挂 pending 队列下个 tick 重试；(3)【睡一觉自动处理】玩家睡醒(PlayerWakeUpEvent)→周围128格内女仆强制归档：生成当日「日」级日记 + 短期→长期簇转移（对齐 Sphantosis start_role_sleep→wrap-up→tick(force_day_index=True) 链路）；周期调度兜底跨日（不睡过夜也归档）；(4)【短期→长期转移】short_context 层段落按 ngram-Jaccard≥0.42 聚簇，簇内全部段落年龄超过 shortTermDays(默认3)游戏日才整簇打 long_term 标记（豁免衰减遗忘，不拆散关联事件簇）；(5)【检索接入】query_memory 召回新增第5路（索引日记命中整段召回）；新工具 query_memory_index（先列可用跨度再查日记+关键事件原文，前缀和式缩小范围）；对话投影新增「记忆日记」段（最近2条日级日记）。配置新增 memory.indexEnable/indexOnSleep/indexMonthTopN/shortTermDays。随 Promaid-1.0.12.jar 部署（备份 .bak_v1377），重启游戏生效。


promaid 1.5.377：全面审计修复——死亡传送图腾校验时序 + 女仆追踪表清理——(1)【图腾校验时序】MasterDeathTeleportHandler 的死亡登记后校验(v1.5.329 加的图腾防误判)注释说延迟 1 tick,实际在【同一服务端 tick 的 END】就执行——死亡发生在 tick T 的实体阶段,同 tick 末立刻查血量,modded 图腾(charmofundying 等)若在 T+1 才复活仍会误传(女仆被传去重生点)。改为记录登记 tick,严格满 ≥1 tick 才校验执行(与 heartfelt 死亡调侃真死核验同款时序);(2)【追踪表清理】女仆追踪表(死亡传送对未加载女仆定位用)对【永久移除】(死亡/解雇/魂符收纳)的女仆条目永不清理——主人每次死亡都要白白强制加载一次死亡女仆遗照坐标的区块。现在实体永久移除时删条目(区块卸载仍保留坐标待用),强制加载后找不到实体也删。随 Promaid-1.0.12.jar 部署,重启游戏生效。


promaid 1.5.376：建造目录搜索框套用原版物品搜索栏样式——搜索框默认是"带边框+深色字"，在深色面板上几乎看不清，输入看起来"没反应/延迟"；改为原版 CreativeModeInventoryScreen 物品搜索栏同款：无边框（setBordered(false)）+ 白字（setTextColor(白)），提示语保留。字节码实证方法名 m_94182_=setBordered、m_94202_=setTextColor。随 Promaid-1.0.12.jar 部署（备份 .bak_v1376），重启游戏生效。


promaid 1.5.375：手册打开卡死修复——启动预热缓存全覆盖——(1)【根因】旧版 warmupNeedsCache 只在服务端启动预热了材料需求（countNeeds），但手册打开时 buildCatalogEntries 对每个外部蓝图调 describe（摘要）、openFor/目录请求对每个调 blueprintSize（占地尺寸）——这两项【没预热、没缓存】，8000+ 蓝图 × 各自遍历全部步骤（一次约 6400 万次坐标解析）= 首次打开手册主线程卡死几十秒；(2)【修复】①新增 SIZE_CACHE（id → 占地尺寸）+ blueprintSizeCached 缓存方法；②warmupNeedsCache 扩展为预热 describe + blueprintSizeCached（外部 + 内置全量）——把"手册打开时的遍历"提前到服务端启动（读世界阶段，玩家看不到）；③openFor / 目录请求改用 blueprintSizeCached；(3)【缓存失效】文件变化/删除/重启时同步清 SIZE_CACHE（与 DESCRIBE_CACHE/NEEDS_CACHE 一致）。效果：服务端启动多花十几~几十秒做一次性预热（读世界阶段），之后手册打开纯读缓存、秒开。随 Promaid-1.0.12.jar 部署（备份 .bak_v1375），重启游戏生效。


promaid 1.5.374：建造目录改革——搜索框 + 网格多列分页——(1)【搜索框】目录页顶部加搜索框，按建筑名/蓝图 id 实时过滤（大小写不敏感），搜索时不重建按钮、不打断中文输入法（条目改为渲染 + 坐标点击，搜索框只建一次）；(2)【每页数量】目录从"一列通栏按钮"改为【网格多列】（宽窗口 3 列、窄窗口 2 列，行高 16），每页显示数量约为原来的 3~6 倍；(3)【页码】目录底部显示"第 X/Y 页"；(4) 删除按钮（外部蓝图 ）保留在网格格内右侧。随 Promaid-1.0.12.jar 部署（备份 .bak_v1374），重启游戏生效。


promaid 1.5.373：玩家导入的地图放建造目录第 1 页 + 清理超限导入文件——(1)【目录顺序】buildCatalogEntries 调换顺序：外部/玩家导入的蓝图（maid_smart_ext:*）先入目录（按名排序），内置预设后入——玩家自己导入的地图在第 1 页就能看到，不再被 25 套内置小屋/农场挤到最后一页；(2)【清理】删除 33 个超方块上限的导入文件（3 个超绝对上限 419 万：星系城堡/春天城堡/法国马赛市体育场 + 30 个超默认 100 万上限，如罗马斗兽场/足球场1/迪拜金字塔等）——原文件仍在桌面「投影文件完整版(8511文件)」，需要可随时重导。随 Promaid-1.0.12.jar 部署（备份 .bak_v1373），重启游戏生效。


promaid 1.5.372：养鸡场重排——发射器不再压在漏斗下面——旧版把承接漏斗叠在发射器正上方往下灌，发射器被压在最底、且顶部平台偏高；重排为视频同款：投蛋发射器嵌在鸡舍南墙，进料漏斗在发射器【正后方、同层】横向指进发射器后背（"放一个指向它的漏斗"），上方再一个漏斗承接、9 漏斗平台架在 y=2 石砖基座上（平台更矮一层）。鸡蛋流向：平台→中心漏斗→上层漏斗→进料漏斗→发射器→射进鸡舍。随 Promaid-1.0.12.jar 部署（备份 .bak_v1372），重启游戏生效。


promaid 1.5.371：修复方块/物品朝向全部丢失——机器都用不了的根因——(1)【根因】doPlace 走 BlueprintLib.parseStepState 把步骤里的状态串（如 {facing:"south",lit:"false"}）转成 BlockState：旧版用 NbtUtils.snbtToTag 解析后只补了 Name，却没把 facing/half/shape 等属性包进 Properties 子标签——NbtUtils.readBlockState(m_247651_) 只读 Properties（javap 实证），顶层属性全被无视 → 所有带状态的方块一律回落默认态（朝向全朝北/朝下）：漏斗指向错误→熔炉组不投料不输出、发射器朝错方向→作物/树/鸡场全废、活塞/比较器/拉杆朝错→机器不动作；(2)【修复】parseStepState 在无 Name 时把 Name 之外的全部属性收集进 Properties 子标签再交给 readBlockState——所有内置（门/床/楼梯/火把/熔炉/农场机器）与 JSON/LLM 生成的带朝向方块现在正确落位朝向；(3) 附带：门此前"落地朝外"只修了位置，朝向同样受此 bug 影响，一并修复。随 Promaid-1.0.12.jar 部署（备份 .bak_v1371），重启游戏生效。


promaid 1.5.370：6 座农场正式接入"机器专属搭建顺序" + 完工使用说明 + 导入小蛋挞01（已授权）——(1)【搭建顺序】machineFamily() 为 6 座内置农场（farm_cactus/farm_superfurnace/farm_lavafountain/farm_crop/farm_tree/farm_chicken）+ 自动熔炉组（machine_furnace_array）登记机器家族 → 自动走 sortMachinePlan 六层相位（结构→惰性机构→活动件→传感件→动力源），动力源（红石粉/火把/拉杆）最后落地，避免半网通电误触（活塞乱推/发射器乱喷），v1.5.369 已建的农场从此建得更稳；(2)【完工说明】machineFinishTip 新增 7 条中文使用说明（仙人掌塔/超级熔炉/岩浆泉/作物/伐木场/养鸡场/自动熔炉组），建造完成的系统消息自动附带对应用法；(3)【手册】「红石机器」段落改为「机械类蓝图搭建顺序 + 各机械用法」七条（v1.5.300 旧文"只剩自动熔炉组"已过时同步替换）；(4)【导入】外部蓝图 小蛋挞01.litematic（82×96×34、约 2.2 万方块，体积 26.7 万，均在限内）已导入并重命名 小蛋挞01（已授权）.litematic。随 Promaid-1.0.12.jar 部署（备份 .bak_v1370），重启游戏生效。


promaid 1.5.369：新增 6 座生存农场蓝图——实现：语音转写 + 视觉逐帧还原视频方块布局后，全新设计 6 座农场并入内置目录（总数 19 → 25）：①仙人掌塔农场（farm_cactus）——11×11 石砖围墙两层塔，沙环+仙人掌+断顶环(离地 3 格碰碎第 3 格)+四角水流冲进正中 3×3 收集井(漏斗→箱,梯子下井)，无红石全自动；②超级熔炉组（farm_superfurnace）——6 熔炉一字排开，纯漏斗定向(输入箱→熔炉顶/燃料箱→熔炉背面/熔炉底→输出箱)，无红石 100% 可靠（原视频用漏斗矿车——实体女仆无法放置，改纯漏斗）；③无限岩浆泉（farm_lavafountain）——5 口炼药锅+滴水石锥悬锅上+石砖平台盛岩浆+台阶封顶防刷怪，随时间自动满锅；④高效作物农场（farm_crop）——中央耕地+水坑+前/右/后 3 发射器贴耕地+拉杆→红石粉网→三发射器骨粉齐射（半自动，比原视频 0-tick 观察者时钟在女仆建造环境下可靠；边按住种子右键边拨拉杆即可持续催熟）；⑤自动伐木场（farm_tree）——骨粉发射器朝树苗+拉杆开关+7 粘性活塞塔(朝南)+红石楼梯(zigzag 同时激活)+比较器脉冲+原木收集漏斗（仿视频布局，红石细节以视频为准）；⑥全自动养鸡场（farm_chicken）——鸡在顶部 3×3 九漏斗平台下蛋→漏斗链进发射器→拉杆向 3×2 鸡舍投蛋→小鸡孵出长大→玩家透过半砖缝隙击杀取肉+经验，鸡舍地板 5 漏斗收进中央箱。材料均 1.20.1 生存可得（滴水石锥/炼药锅 1.17+、无铜傀儡等超版本物品）。随 Promaid-1.0.12.jar 部署（备份 .bak_v1369），重启游戏生效。


promaid 1.5.368：内置建筑大改版——删雷同 + 修门 + 新增 12 种不同结构——(1)【门摆放根因修复】door() 辅助的调用方传参 bug：smallHouse/villa 里 `door(doorX, y0, 0, ...)` 把门中心当墙原点传，door() 内部再 `x + doorX` → 门被放到 x=2×doorX = 屋外一格（渲染实证：前视图门洞在 x=10、屋子只有 0..9）——全部内置小屋/别墅的门一直"不在正面墙上"；修复：改传墙原点 0（`door(0, y0, 0, ...)`）；(2)【删雷同】旧 15 小屋全是 smallHouse 同款盒（10×12×3 仅材质不同）、旧 10 别墅全是 villa 同款盒（14×16 三层仅材质不同）——删 12 小屋 + 7 别墅，保留材质风明显的 3 小屋（原木/雪原/沙漠）+ 3 别墅（石质山景/现代玻璃/陶瓦地中海）；(3)【新增 12 种不同结构建筑】A字架小屋（三角截面斜墙）、瞭望塔（空心塔+垛口+梯子）、灯塔（圆塔红白条纹+海晶灯灯室）、庭院屋（外墙环抱露天中庭）、谷仓（高双坡大空间+双开大门）、高脚小屋（柱上架空+梯子）、风车（圆塔锥顶+十字木翼+磨坊附间）、半地堡屋（厚墙+下沉坡道+天窗）、树屋（粗树干+平台小屋）、坡地阶梯屋（三级阶梯平台）、双子屋（两屋+连廊+前院）、城堡门楼（双塔夹门楼+垛口）——全部方块 500~5000（独立编译验证：各 531~2139 块），生存易得材料、家具齐备、无红石无液体；(4) 内置总数 26 → 19（指南/注释同步），手册目录即见新建筑。随 Promaid-1.0.12.jar 部署（备份 .bak_v1368），重启游戏生效。


promaid 1.5.367：heartfelt_connection 软联动——promaid 手册/配置面板新增「heartfelt 联动」页——实现：PromaidConfigScreen 新增 Section.HEARTFELT（左列按钮，ModList.isLoaded("heartfelt_connection") 门控——未安装不显示，与爱憎分明页同款）；页面分三组实时读写该模组配置（反射 HeartfeltConfig 字段，改完即生效、重启不丢）：①告白——玩家告白方向修正/女仆告白前摇开关、尝试间隔、所需好感、基础概率、失败心情惩罚、前摇最短等待/超时/走向速度；②女儿/成长——成长事件、父女互动；③伤心窗口——窗口时长、心情惩罚。随 Promaid-1.0.12.jar 重新部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.366：女儿对话选项专属文本池——实现：①新客户端 mixin HugDialogueDaughterFrameMixin 拦截 HugDialogueRuntimeBridge.currentFrame()（对话帧唯一出口,反编译实证其内部调 HugDialogueStageFlavorComposer.apply;帧含 nodeId/speaker/text）——当前交互女仆是女儿(ClientOnlyState 从成人 HugClientState/儿童 ChildInteractionClientState 双查 UUID)时,按 阶段×选项分类 替换【女仆台词】(speaker=女仆名,旁白/玩家提示/选项标题不动,不破坏界面):成年女儿(hug_menu 4 选项)聊天/讨好/讲笑话/摇曲柄/摸头;幼儿/少女(儿童场景)摸头/举高高/让妈妈抱抱/陪她说话;原版普通女仆文本在女儿身上永不触发。②PromptTexts.daughterOptionText 新增 3 阶段专属文本池（幼儿/少女/成年 各选项 4 条,按天轮换,扩充文本量）。随 heartfelt_connection-1.5.94.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.365：告白失败(缓一缓)对记忆/情绪零影响——根因(剧本实证)：maidmarriage 告白剧本的接受选项("我也喜欢你"=confession_accept)带服务端事件 story_confession_accept(完成告白),而拒绝选项("……先让我缓缓"=confession_reject)【没有任何服务端动作】——heartfelt 的永久记忆(CONFESSION_FAILED/CONFESSION_FAILED_AT)只有调整器手动标记才会写,真实拒绝路径完全没接。修复：①新 C2S ConfessionRejectPacket——客户端 PlayerConfessionEntryMixin 拦截 choose("confession_reject") 时发送；②服务端 MaidConfessionManager.handleConfessionRejected：写永久记忆 CONFESSION_FAILED+时间戳(她之后不再主动告白——isEligible 门；AI 记忆 MEMORY_CONFESSION_FAILED 与后果文本 AftermathPrompt 生效)+ 心情惩罚(CONFESSION_FAIL_MOOD)+ 系统消息(confessionRejected)。随 heartfelt_connection-1.5.93.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.365：村民机水闸活板门无法放置→机器不工作——(1)【根因】村民繁殖与交易一体机蓝图（BY：黑山大叔）把【顶部活板门直接设计在水上方】（探针实证：y=5 的 6 个活板门 half=top、上方 y=6 就是水；原版 TrapDoorBlock.canSurvive 要求上方实心，水不合法 → 正常手段放不上，蓝图作者用工具强放）；建造支撑逻辑要么【无限延后】等"水支撑建好"（水永远不会是合法支撑），要么 3 次后 force【把水换成石头】再放——水闸的水被替换/活板门缺失 → 机器水运/水闸机制毁掉 → "无法工作"；(2)【修复】新增 BlueprintLib.isFluidStepAt（蓝图指定位置是否为水/岩浆步骤，仅在支撑格已是流体且计划含该格时调用，O(N) 可接受）；doPlace 支撑检查：支撑格是流体且蓝图该格就是流体步骤 → 【直接放置附着块，不替换流体、不延后】（javap 实证 TrapDoorBlock.neighborChanged 只切换开合状态不自毁——强放后即使上方是水也稳定存在；水保持原位，机器水闸完整）；bad 校验对流体支撑块跳过 canSurvive 判定（粘住即成功，没粘住延后不替换流体）；(3) 注：矿车自动放置为【轰炸机家族专属】（v1.5.320 设计，完工提示已说明）——天机屠龙炮（never4get）手动触发、村民机（villager）无需矿车，均不自动放矿车，属预期行为。随 Promaid-1.0.12.jar 部署（备份 .bak_v1365），重启游戏生效。


promaid 1.5.364：女仆主动告白"准备文本→走向主人"衔接不稳定——根因：工作任务（建造/烹饪/酿造）每 tick 清 WALK_TARGET，且 promaid 站桩锁（isStill = WORK_STILL_TAG + 工作任务）会取消 MoveToTargetSink——正在干活的女仆（如建造中的妻子）告白前摇的走向目标被稳定打断，有时走、有时不走。修复：ConfessionApproachManager.startApproach 启动前摇时【切到待机任务】（TLM TaskManager.getIdleTask，记录原任务）——WALK_TARGET 独占、站桩锁自动失效（isStill 需工作任务）；会话结束（到达跳转告白/取消：死亡/离线/超时/威胁）时【恢复原任务】。随 heartfelt_connection-1.5.92.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.363：女仆主动告白跳过前文直接到选项——根因：jumpToMaidMarriageConfession 的断点续播用 HugStoryResumeState.remember(... "confession_accept_choice") 直达【选项节点】，整段告白前文（confession_intro 女仆的告白台词"那个……主人……我有话要对你说！"）被跳过——老代码设计如此。修复：续播节点改为 confession_intro——女仆主动告白从告白前文开始，完整流程（前文 → 开口选项 → 分支 → 接受）正常播放。附带修正：1.5.362 加的 jumpToNode 拦截过宽（会误劫女仆主动告白的 confession_* 续播，把合法流程换成玩家告白屏）——已移除，玩家告白拦截保留 choose + 女仆名兜底。随 heartfelt_connection-1.5.91.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.362：玩家告白拦截加固——根因：PlayerConfessionEntryMixin （玩家告白方向修正）在运行时可能落空——HugClientState.tick() 在女仆实体瞬时不在客户端世界时（hasEntityWithUuid false）会【清空交互状态】，getLocalInteractionMaidUuid() 返回 null → 拦截失败 → maidmarriage 原版 confession_intro 剧本照播（女仆向玩家告白，方向反了——"为什么是这样的文本"），且它的对话渲染偏左（"显示偏左，超出屏幕应移至中央"）。加固：①choose 拦截时 UUID 找不到 → 按女仆名兜底（bridge.maidNameSupplier，64 格内同名女仆）；②新增第二道防线——jumpToNode 跳入 confession_* 节点时同样拦截打开 heartfelt 告白屏（choose 拦截失败的剧本推进也被拦下，女仆告白文本永不触发）。随 heartfelt_connection-1.5.90.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.361：AI 调用心契工具 hug 仍然失败——定位（debug 日志实证）：LLM 已成功调用 smart_intimate({"action":"hug"})，但拥抱没发生。反编译 MaidHugManager 找到根因：工具调用的 handleHugPoseToggle(player, maidId) 转发到 3 参版，而 3 参版【第一行 PLAYER_TO_SESSION.get(player)】——玩家不在交互会话（没开 Alt+J 面板）时【静默 return】，拥抱什么都不做。kiss/lap/pet 各有独立流程不依赖 HugManager 会话（反编译确认），唯独 hug 需要。修复：①MaidMarriageCompat.ensureInteraction(player, maid)——女仆不在会话时先用 handleInteractionToggle 开启交互（内部全套校验：主人/儿童/坐姿/骑乘/距离，坐姿由 MixinInteractionSittingAllow 预站起； 已在会话则不动——handleInteractionToggle 是开关，再调会变停止）；②SmartIntimateTool hug(start) 执行前 先 ensureInteraction。随 heartfelt_connection-1.5.89.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.360：穿婚纱自动变 TLM 花嫁酒狐模型——实现：新增 WeddingDressModelManager，每 10 tick 扫描主人 48 格内女仆的胸甲槽（判定与 maidmarriage 自己的对话变量一致——反编译实证 HugDialogueContextVariables 用 getItemBySlot(CHEST).is(maidmarriage:wedding_dress) 判定 maid_wearing_wedding_dress）；穿着婚纱 → maid.setModelId("geckolib:winefox_wedding")（TLM 内置模型包 maid_model.json 实证的花嫁酒狐 model_id，实体数据同步客户端即时生效）；脱下婚纱 → 还原原模型（切换前把原模型 ID 存入女仆 persistentData heartfelt_original_model，重启不丢）。随 heartfelt_connection-1.5.88.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.359：婴儿剧本对话全量替换——定位：ChildGiftTextMixin（拦截 DialogueScriptManager.componentForMaid 文本汇聚点，日志实证已注入运行）对幼女(INFANT/JUVENILE,isTooSmall)已替换多数剧本键，但 daughterDialogueText 对【未覆盖的键】返回 null → 放行 maidmarriage 原版文本——成人送礼键 dialogue.maidmarriage.gift.*、pet_head 成人键、"陪她说话"等其他一切未列出的对话键全部漏网，婴儿仍会播正常台词。修复：①daughterDialogueText 全量兜底——tooSmall 时所有未覆盖的剧本键一律替换为婴儿旁白（无台词动作描写，按对话类型分池按天轮换：送礼"她好奇地抱着礼物看了又看…"/其他"她睁着圆溜溜的眼睛看着你，咿咿呀呀地挥着小手…"），原版文本完全不触发；已有的专属分支（child_gift/摸头/抱抱/思慕等）保留更贴合的文本；妈妈台词(carry_child.infant_hold.*)仍不拦。②新增旁白池时曾加的 RomanceSleepManager.speakSingleLine 冗余 mixin 已删除（componentForMaid 汇聚点已全覆盖，不需要第二层）。随 heartfelt_connection-1.5.87.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.358："摇摇晃晃地站起来"系统提示改革——根因：该消息挂在 checkChildGrowth 的 CHILD_STAGE 标签判定上（stored != stage 即触发），而 maidmarriage 每 tick 按 growthTicks+persistent 重写 ChildStateData 阶段——标签与实际阶段错位（标签陈旧/阶段数据震荡）时，消息就在错误时机误发，看起来"随机触发"。改革：站起动作与"站起来"系统提示统一移到 ChildGuardManager 的【可靠运行时检测】（lastStage Map 每 tick 对比实际阶段，只在真实 INFANT→JUVENILE 升级瞬间触发一次，v1.5.357 的机制）：同帧完成 站起 + 系统消息(会自己站起来了) + 幼儿旁白气泡(摇摇晃晃站起来) ——消息必然贴合"她刚刚真正站起来"的情境；checkChildGrowth 的 JUVENILE 分支只保留标签记录与站起保险，不再发消息（其余阶段 INFANT/CHILD/ADULT 的成长消息保持原样）。随 heartfelt_connection-1.5.86.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.357：幼儿站起方案修正——撤掉每 tick 强制站立——修正：①删除 v1.5.355 的幼儿每 tick 强制站立（本末倒置——幼儿从此无法自由坐/站）；②正确语义落地：强制坐下只约束婴儿（isInfant 判定天然对幼儿解除）；升级瞬间用【运行时阶段记录】（ChildGuardManager.lastStage Map，每 tick 记录上一阶段）检测 INFANT→JUVENILE 变化时站起一次（若还骑乘妈妈先 m_8127_() 放下，再 m_21837_(false)+m_20124_(STANDING)）——不依赖可能陈旧的 CHILD_STAGE 标签（陈旧标签会让 checkChildGrowth 跳过导致"消息有了但没站起来"），也不持续强制站立（升级后幼儿自由坐/站，与"婴儿幼儿唯一区别是能站起来"的模型一致）。随 heartfelt_connection-1.5.85.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.356：手册/配置面板里"LLM 调用次数限制(API 日配额)"设置找不到——排查结论：设置一直存在（MaidSmartConfig.DIALOGUE_API_DAILY_LIMIT，默认 40 次/天、0=不限；配置面板 对话提示 区），部署 jar 也确认包含；但它排在对话提示区第 5 行，窗口高度/GUI 缩放较小时被分页藏到第 2+ 页，看起来像"没了"（与 v1.5.293 自主决策、v1.5.295 主动对话同一类可见性问题）。修复：把 API 日配额行提到对话提示区【第一行】——任何窗口高度打开 对话提示 第一屏即可见。随 Promaid-1.0.12.jar 重新部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.355：幼儿"会自己站起来"消息有了但没实际站起来——根因：站起只有 checkChildGrowth 升级瞬间的一次性 m_21837_(false)，一旦被同 tick/后续任何按坐覆盖就失效；且静态排查发现多个可能按坐路径（婴儿强坐判定、伤心坐、哀悼坐、等妈妈坐等）。修复：ChildGuardManager 每 tick 对幼儿(JUVENILE,isTooSmall && !isInfant)【强制站立】（与婴儿强制坐下对称——完全贴合"婴儿幼儿唯一区别是能站起来"的模型），尊重已知"该坐"状态：伤心窗口(isHurtFeeling 赌气坐)/哀悼(isMourning)/等妈妈(WAITING_MOTHER)/心契交互(inChildInteraction 锁站姿)/被抱着(骑乘 m_20159_)；其余情况坐着即站起(m_21837_(false)+m_20124_(STANDING))。随 heartfelt_connection-1.5.84.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.354：幼儿束缚失效——调整器设阶段与 maidmarriage 重推导打架——根因（反编译 maidmarriage 2.3.0 实证）：MaidChildEntity.tickExternalChildLifecycle 每 tick 读 growthTicks 然后 resolveGrowthStageByTicks(ticks)【重新推导阶段】并写回 ChildStateData（覆盖存储的阶段字符串）。阈值：INFANT < 24000；JUVENILE < 24000+max(48000, adultAfter-24000)/2；CHILD < adultAfter（adultAfter = max(3, childGrowthDays)×24000，childGrowthDays 默认 10、可调 3~120）。而调试器 setStage 写死 growthTicks：幼儿=48000 在 childGrowthDays=3 时 ≥ childStage(48000) 被推导成 CHILD（幼儿变少女→ isTooSmall 假 → 语音/任务锁/手持限食物/拾取 ONLY_XP 全部失效）；少女=120000 在默认 10 天时 < childStage(132000) 被推导成 JUVENILE（少女变幼儿）。修复：新增 MaidMarriageCompat.stageTicksFor(stageName)，按 maidmarriage【实时 childGrowthDays 配置】复刻阈值并取各阶段区间中点——任何配置下都稳定落在目标阶段（婴儿=0、幼儿=24000+(childStage-24000)/2、少女=childStage+(adultAfter-childStage)/2、成年=adultAfter）；调整器四个阶段按钮改用 stageTicksFor。已误分类的档位需重新用调整器点一次对应阶段即恢复。随 heartfelt_connection-1.5.83.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.353：婴儿丢地武器主人捡不起来——按方案根治——根因：①v1.5.63 的 stripHand 给丢弃物品设 setPickUpDelay(1000000) 无限拾取延迟防女仆自捡循环，但原版 ItemEntity.playerTouch 先查拾取延迟——主人也被卡住（v1.5.75 的 ItemEntityPlayerPickupMixin 想清延迟，但误注册在 mixins.heartfelt.json 的 client 段——playerTouch 是服务端逻辑，客户端注册从不生效，一直没修好）。②按方案根治：TLM 拾取类型 PickType（反编译实证 EntityMaid 拾取逻辑先查 canPickItem 再捡 ItemEntity）——heartfelt 新增 enforcePickupXpOnly，把幼儿/婴儿（isTooSmall）的拾取类型【每 1 秒强制为 ONLY_XP】（canPickItem=false → 女仆从源头不拾取任何物品，丢→捡循环从源头断开），长大（CHILD 起）后还原静音前类型（纯内存 Map 记录）；stripHand 移除 1000000 无限拾取延迟——丢下的武器/杂物恢复正常拾取延迟，主人碰触即可拾起。同时删除失效的 ItemEntityPlayerPickupMixin（client 段、服务端不生效、已无必要）。随 heartfelt_connection-1.5.82.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.352：Alt+J 面板"进入一下就瞬间退出"修复——根因（反编译 maidmarriage 2.3.0 实证）：交互会话每 tick 的 isValidInteractionPair 含坐姿检查（isMaidInSittingPose → 会话失效 → stopInteraction → 面板瞬关）。两个"交互中把女仆按回坐下"的冲突源：①promaid tickBuildSit（建造任务强坐）的交互豁免只查成人管理器 MaidHugManager、漏了儿童管理器 ChildInteractionManager——建造中的女儿（儿童路径 Alt+J）交互时豁免不生效，每 tick 被强坐 → 会话立刻终止；②heartfelt ChildGuardManager 婴儿每 tick 强坐没有交互豁免，婴儿女儿交互同样被秒杀。修复：①promaid inMaidmarriageInteraction 改为双管理器检查（MaidHugManager + ChildInteractionManager）；②heartfelt 婴儿强坐增加 inChildInteraction 豁免（反射 ChildInteractionManager.getInteractionPlayer，交互中跳过强坐，交互结束恢复）。随 Promaid-1.0.12.jar + heartfelt_connection-1.5.81.jar 部署（deploy_latest.ps1 无备份），重启游戏生效。


promaid 1.5.351：审核整改二——①MixinMaidHugSittingAllow 重命名为 MixinInteractionSittingAllow（v1.5.344 起同时覆盖成人 MaidHugManager 与儿童 ChildInteractionManager 两个交互管理器，旧名已不准确；纯内部改名，行为不变）；②按玩家政策清理遗留：删除 C:\Users\Sketch\Desktop\.minecraft（229.8MB 死目录，非 PCL 运行目录）与工作区未实现的"红石机器改革"计划文档。随 Promaid-1.0.12.jar 部署（deploy_latest.ps1 无备份流程），重启游戏生效。


promaid 1.5.350：审核整改（heartfelt 1.5.80）——①mixins.heartfelt.json 去掉 UTF-8 BOM（源+部署一致，与 opt json/promaid json 风格统一；Mixin 的 Gson 此前容错没崩，属卫生问题）；②声音频率还原不再写死 1.0——v1.5.77 静音逻辑改为记录【静音前频率】、条件解除时还原原值（玩家在女仆配置 GUI 手动设的频率不再被覆盖，纯内存 Map 无额外字段）；③changelog 版本撞号重排：此前 1.5.345 出现两条（TNT 完工不炸膛 + 抱起回应），本次按部署时序重排为 1.5.344 Alt+J 儿童路径 / 1.5.345 TNT / 1.5.346 抱起回应 / 1.5.347 思慕 / 1.5.348 手册 bug / 1.5.349 坐姿缓降；④工作区清理临时脚本与提取目录、精简 mods 备份（每家族留最新 2 个）。随 heartfelt_connection-1.5.80.jar 与 Promaid-1.0.12.jar 部署（备份 .bak_v1580 / .bak_v1350），重启游戏生效。


promaid 1.5.349：坐姿女仆"一直坐着缓慢下落"修复——根因（源码实证）：SelfPreservationBehavior.m_6725_ （TLM 常驻 extra brain，每 tick 跑）的"坐下锁移动"（v1.5.212）在女仆坐姿且无环境危险时执行 m_20256_(Vec3.ZERO)【整速度清零】——坐着的女仆一往下掉，重力加速度刚积累就被抹掉，永远加不起下落速度 → 每 tick 只下落一个重力单位，呈缓慢下落（还顺带免疫摔落伤害，坐姿被架空成缓降模式）。修复：与 MaidStationaryMixin v1.5.98c 同款教训——只清水平速度（x/z 归零）、保留垂直 y（m_20256_(0, v.y, 0)）：坐姿照常锁水平移动（防"坐下还在走"），下落/上浮/落地恢复正常物理（水中也不影响上浮，不溺水）。随 Promaid-1.0.12.jar 重新部署（备份 .bak_v1348），重启游戏生效。


promaid 1.5.348：每次进游戏都送一本 Promaid 手册的 bug——根因：onPlayerLoggedIn 发放前的"只送一次"查重写的是 contains("maid_smart_blueprints_given", 99)——NBT 类型根本没有 99，而实际写入的是 Byte（TAG_BYTE=1）；contains 要求类型完全匹配，99 永远不匹配 → 标记永远等于"没发过" → 每次登录都重发。修复：类型 99 → 1（TAG_BYTE），与 m_128344_(putByte) 一致，"只送一次"标记真正生效；全库扫描确认仅此一处 99（其余 contains 都是合法类型 7/8/9/10/12/1）。已堆叠的多余手册不会自动消失，手动丢弃即可，之后进游戏不再重发。随 Promaid-1.0.12.jar 重新部署（备份 .bak_v1347），重启游戏生效。


promaid 1.5.347：思慕明显触发效果——背景（反编译 maidmarriage 2.3.0 实证）：maidmarriage 自带思慕呈现太弱，onMaidTick 每秒只在女仆身边撒 2 颗心形粒子加低频循环对话，玩家几乎感知不到；且 isLongingForInteraction 有 isDatingOrMarried 前置，女儿（儿童）永远进不了恋爱向思慕——v1.5.57 备好的三档女儿思慕文本（BABY/CHILD/ADULT_LONGING）因此一直是死代码（"要么触发不了"）。修复：heartfelt 侧新增 LongingEffectManager 每秒扫描（随 heartfelt_connection-1.5.79.jar 部署）：对【思慕中】的关系女仆（主人 24 格内）给足明显反馈——①心形粒子爆发每秒 8 颗（maidmarriage 的 4 倍）；②思慕气泡每 30 秒一句（妻子/恋人/女儿分文案，女儿按成长阶段轮换 LONGING 池，激活死代码）；③系统消息每游戏日一次明确"她在思念你"（触发看得见）。触发判定：妻子/恋人走 maidmarriage isLongingForInteraction（恋爱/结婚 + lastInteractionDay 距今 ≥3 天），女儿走同条件 lastInteractionDay ≥3 天（父女思念文案，不落恋爱向）。边界：伤心窗口/哀悼中不触发、主人不在附近不触发。调试器"long=1d"按钮可直接测：设完靠近女仆即可看到心形粒子爆发+气泡+系统消息。备份 .bak_v1579，重启游戏生效。


promaid 1.5.346：抱起回应 + 记忆库接入验证——


【验证结论】爱憎分明（callresponse 2.0.2）的 LLM 调用【已经能读到记忆库】：反编译实证它的唯一真实 LLM 调用点 MaidResponder → TLM MaidAIChatManager.chat（工具类都是 LLM 函数调用，不主动调 LLM）。而 chat() 内部 normalChat 做 <context> 替换（GameContextRegister → StoryMaidContext → StoryMemoryManager 剧情记忆：结婚/怀孕/心情/告白/父女/丧子/告白失败/心碎），getMessages 又全部汇聚到 LLMMessage.systemChat（SmartPromptMixin @WrapMethod 注入 SmartPromptAppender：关系准则+事件历史 P1+情境 P2+后果 P3）——两条注入链路对爱憎分明的调用同样生效（验证了部署 jar 的 mixins.heartfelt.opt.json 与 MANIFEST 双配置注册，opt 配置里 SmartPromptMixin 等 9 个可选 mixin 都在）。


【新功能】抱起回应（随 heartfelt_connection-1.5.78.jar 部署）：新 PickupResponseManager 监听 Forge EntityMountEvent （TLM 潜行右键抱起 = 女仆骑玩家，服务端骑乘瞬间触发）——确认关系女仆（妻子/女儿/恋人，strictRelationKey）被抱起时：有 LLM+配额 → 沉浸式回应（提示词含关系准则+记忆库）；无 LLM/配额满 → 固定文本气泡降级（女儿按成长阶段口吻：少女"爸爸爸爸！举高高——！"、成女"都这么大了还把我抱起来"、妻子"怎么突然把我抱起来了呀"、恋人"诶诶？！等、等一下啦——！"）。边界：伤心窗口/哀悼中不回应、婴儿幼儿不会说话（chatWithQuota 兜底）、每女仆 10 秒冷却防刷屏。maidmarriage 的举起/抱女儿走代理链且自带剧本对话，不在此路径（避免双重回应）。随 heartfelt_connection-1.5.78.jar 部署（备份 .bak_v1578），重启游戏生效。


promaid 1.5.345：TNT 完工不炸膛——点火保护升级为时间窗 + 完工点火结算——(1)【根因】v1.5.330 的 BuildTntGuard 线程护栏只挡 doPlace 放置瞬间的 onPlace；天机屠龙炮（Never4Get 蓝图，探针实证）320 个 TNT 六邻【无任何红石组件】（玻璃/TNT/石英/栅栏门/空气/珊瑚扇/活塞/釉陶），点火靠【观察者→活塞→推 TNT】：活建造下机器电路随建随跑，TNT 落位后观察者/活塞被放置事件触发 → 推 TNT → 移动重放的 onPlace 检测到邻接带电 → 点燃 → 全链引爆 = "刚建好直接炸膛"（活塞推动发生在 doPlace 之外，线程标记覆盖不到）；(2)【修复】BuildTntGuard 升级为【时间窗压制】（suppressing = 线程标记 || 游戏刻 < 窗口截止）：每次放置（placeTntSafe）与完工激活前都延长窗口（宽度 = 新配置 build.tntIgnitionGrace 秒，默认 120）；MixinTntBuildGuard 双入口压制（onPlace m_6807_ + neighborChanged m_6861_——放置/活塞推动/邻居更新全拦）；(3)【完工点火结算 settleTntIgnition】完工激活（recalcRedstone/activateWater）后遍历蓝图 TNT，只点燃【当前邻接带电】（hasNeighborSignal）的 TNT——轰炸机（矿车压轨带电）当场启动复制循环；天机屠龙炮 TNT 静止无电 → 保持惰性，触发（观察者→活塞）时才点火——不再"刚建好炸膛"，宽限期满后机器按正常红石逻辑点火；配置页·建造设置新增「TNT 点火保护期（秒）」（0 = 关闭保护回到 1.5.330 行为）。随 Promaid-1.0.12.jar 重新部署（备份 .bak_v1345），重启游戏生效。


promaid 1.5.344：Alt+J 对坐着的女仆无法进入对话面板——儿童路径漏修——根因（反编译 maidmarriage 2.3.0 实证）：成人/妻子路径 MaidHugManager.handleInteractionToggle 有 need_standing 坐姿拒绝（v1.5.333 已修）；但女儿走的是独立的儿童路径 ChildInteractionPayload → ChildInteractionManager.handleInteractionToggle——这个管理器里有一模一样的坐姿检查（isMaidInSittingPose → child_interaction.need_standing），之前三次修复只覆盖了成人管理器，儿童管理器从未修过，所以坐着的女儿一按 Alt+J 仍被 child_interaction.need_standing 拒绝。修复：MixinMaidHugSittingAllow 扩展为双 target（@Mixin(targets={MaidHugManager, ChildInteractionManager})）——两个 handleInteractionToggle HEAD 处都先把坐姿、未骑乘、主人的女仆站起来（m_21837_(false)+m_20124_(STANDING)），原版 need_standing 检查自然通过；坐着的妻子、坐着的女儿都能正常进入对话面板。随 Promaid-1.0.12.jar 重新部署（备份 .bak_v1344），重启游戏生效。


promaid 1.5.343：语音包改直接静音——声音频率=0（承接 heartfelt 1.5.77）——根因：TLM 语音包（声音包自主讲话）走的是独立路径 MaidSoundInstance → MaidSoundFreqEvent.onPlaySoundEvent，按 maid.getConfigManager().getSoundFreq()（每女仆声音频率 0..1）概率播放；之前用 mixin 拦 getAmbientSound（v1.5.64）拦不住这条独立路径，语音包仍在播。修复：按方案【直接静音】——ChildGuardManager 每 1 秒对 幼儿/婴儿（isTooSmall）或 伤心窗口（isHurtFeeling，妻子/女儿/恋人）的女仆强制 setSoundFreq(0) → MaidSoundFreqEvent 概率判定永不命中，语音包彻底不播；条件解除（长大/伤心结束）恢复默认 1.0（运行时 Set 记录被静音者，纯内存不落 NBT、无额外字段）；原有 TTS/心契语音 mixin 保留为冗余覆盖。随 heartfelt_connection-1.5.77.jar 部署（备份 .bak_v1577），重启游戏生效。


promaid 1.5.342：调试器设阶段不再吞成长事件（承接 heartfelt 1.5.76）——根因：AdjusterManager.setStage（调试器 4 档阶段按钮）直接把 heartfelt 的 CHILD_STAGE 标签同步成新阶段，checkChildGrowth 看到 stored==stage 直接跳过——用调试器从 婴儿→幼儿 测试时成长事件永不触发，"会自己站起来了"不出现（旧注释本意"触发成长事件"，实现却吞掉了）。修复：setStage 不再预同步 CHILD_STAGE 标签（设女儿 setChild 的同步保留——新建女儿本就不该触发成长事件）；下一 tick checkChildGrowth 检测到阶段变化，按新阶段触发事件文本+站起动作——婴儿→幼儿="会自己站起来了"+ 真正站起、幼儿→少女="又长大了一点"、少女→成年="长大成人了"。随 heartfelt_connection-1.5.76.jar 部署（备份 .bak_v1576），重启游戏生效。


promaid 1.5.341：婴儿丢地物品主人捡不起来（承接 heartfelt 1.5.75）——根因：ChildGuardManager.stripHand 为防女仆自捡（丢→捡→再丢循环，v1.5.63）给物品设了 setPickUpDelay(1000000) 无限拾取延迟，但原版 ItemEntity.playerTouch（m_6123_）【先查拾取延迟】——javap 实证开头 `if (f_31986_ > 0) return`，玩家（含主人）同样被挡，旧注释"玩家碰触清延迟可捡"在 1.20.1 并不成立。修复：新 mixin ItemEntityPlayerPickupMixin 在 playerTouch HEAD 处，若拾取延迟 ≥ 100 万（防自捡标记值）则清零——主人/任意玩家碰触即可正常拾取；女仆的捡拾路径不经过 playerTouch（走 TLM 拾取 AI、尊重延迟），防自捡循环保持不破。随 heartfelt_connection-1.5.75.jar 部署（备份 .bak_v1575），重启游戏生效。


promaid 1.5.340：启动崩溃修复——mods.toml 被误加 UTF-8 BOM——根因：本次迭代用 PowerShell `Set-Content -Encoding UTF8` 改版本号时给 META-INF/mods.toml 写入了 UTF-8 BOM（EF BB BF），Forge 的 tomlj 解析器拒绝 BOM → "corrupt or misconfigured toml" 启动崩溃（tomllib 实证：1.5.68 无 BOM 解析正常、1.5.69 起全带 BOM 全部 Invalid statement at line 1）；promaid 1.0.12 的 toml 同样中招（1.0.11 起）。修复：字节级剥离两个源 mods.toml 的 BOM（内容不动），重建 heartfelt 1.5.74 + promaid 1.0.12 并重新部署（备份 .bak_bomfix）；tomllib 验证两个新 jar 的 toml 均无 BOM、解析 OK。附带说明：描述字段是历史遗留 mojibake（1.0.4 时代起就这样，GBK 误转，字节已部分丢失无法还原）——但它始终是合法 TOML（仅 mod 列表显示乱码，不影响加载），非本次崩溃原因；后续改版本号禁用 PowerShell Set-Content（会加 BOM），改用 Python/编辑工具。


promaid 1.5.339：幼儿手持限食物主副手交叉 bug 修复（承接 heartfelt 1.5.74）——根因（源码实证）：ChildGuardManager.enforceHandItems 旧版 `stripHand(MAIN_HAND, maid.m_21206_())` 检查的是【副手】物品却清【主手】、OFF_HAND 同理反着来——主手的非食物物品永远不会被移除（它检查副手），副手却被错误清空；"非武器物品还留在幼儿手上"的确切原因。修复：参数一一对应（主手查主手 m_21205_、副手查副手 m_21206_），幼儿（INFANT/JUVENILE）主手/副手【任何非食物物品】——武器/工具/杂物/方块一律——移除并原地丢一个（先清手持再丢副本、归属玩家、无限拾取延迟防女仆自捡循环，v1.5.63 机制保留）；食物判定 = getFoodProperties() != null（"认标签food"，1.20.1 唯一标准方式）。promaid 侧 v1.5.332 的武器禁持（MaidWeaponGuard）保留为冗余安全网，与 heartfelt 非食物规则叠加无冲突。随 heartfelt_connection-1.5.74.jar 部署（备份 .bak_v1574），重启游戏生效。


promaid 1.5.338：伤心状态不播语音包（承接 heartfelt 1.5.73）——伤心窗口（被主人打伤赌气，HURT_UNTIL 未到期）内的关系女仆（妻子/女儿/恋人）不再播放任何语音包：三处语音拦截 mixin 全部追加 isHurtFeeling 判定——① MaidAmbientVoiceMixin（TLM 自主 ambient 声音包语音 getAmbientSound + 捡拾声）：伤心窗口返回 null（声音频率=0）；② HeartPactVoicePlaybackMixin（maidmarriage 心契语音包 play 统一出口）：伤心窗口 cancel；③ MaidSpiritTtsGuardMixin（TLM TTS 入口）：伤心窗口 cancel。FamilyInteractionManager.isHurtFeeling 改 public 供 mixin 复用；伤心窗口内她坐着赌气不开口说话（文本气泡/拒绝互动机制维持原样），窗口到期自动恢复。随 heartfelt_connection-1.5.73.jar 部署（备份 .bak_v1573），重启游戏生效。


promaid 1.5.337：婴儿强制坐下改每 tick（承接 heartfelt 1.5.72）——ChildGuardManager.onServerTick 的婴儿强制坐下从 5 tick（0.25 秒）节流改为【每 tick】独立循环：被背叛女仆攻击时战斗/自保 AI 会把婴儿短暂拉起站起反击，5 tick 窗口内肉眼可见；每 tick 按回坐下后站起窗口缩到 1 tick 内不可感知，婴儿永远坐姿（无法攻击）；任务锁空闲/手持限食物与坐姿无关，维持 5 tick 节流（消息另有 40 tick 冷却）。随 heartfelt_connection-1.5.72.jar 部署（备份 .bak_v1572），重启游戏生效。


promaid 1.5.336：成长"站起来"时刻移到婴儿→幼儿（承接 heartfelt 1.5.71）——heartfelt v1.5.64 起幼儿（JUVENILE）可行走（与婴儿唯一区别=能站），但"站起来"的成长时刻还挂在幼儿→少女（JUVENILE→CHILD），不一致；本次将站起动作与文本整体前移：(1)【站起动作】FamilyInteractionManager.checkChildGrowth 的 setOrderedToSit(false) 从 stage==CHILD 改为 stage==JUVENILE——婴儿→幼儿升级瞬间真正站起来一次（婴儿期一直被强制坐下，升级时她实际还坐着；幼儿→少女时幼儿已可行走，无需再站）；(2)【成长文本】PromptTexts 系统消息/LLM 提示词/降级气泡/幼儿旁白全部对调：JUVENILE="会自己站起来了"（摇摇晃晃站起来又惊又喜"爸爸！你看我！"/幼儿旁白无台词站起来要抱抱），CHILD="又长大了一点"（好奇打量自己）；(3)【promaid 同步】RelationshipMemoryAdapter.stageLabel：JUVENILE「会自己站起来的小宝贝」、CHILD「活泼的小女孩」（撤销 v1.5.50 的挪移，与 heartfelt 一致）。随 heartfelt_connection-1.5.71.jar + Promaid-1.0.12.jar 部署（备份 .bak_v1571/.bak_v1012），重启游戏生效。


promaid 1.5.335：婴儿/幼儿统一约束（承接 heartfelt 1.5.70）——heartfelt 侧：(1)【儿童工作漏网修复】maidmarriage 的儿童工作（学习/探险，MaidWorkManager）对 isTooSmall（INFANT/JUVENILE）强制阻止——旧 v1.5.82 的"坐姿→NONE 放行"对幼儿是反效果：婴儿被强制坐下却被放行可工作、幼儿不坐下直接可工作（"任何工作都做不了"漏网）；MaidWorkSittingMixin 在 resolveWorkBlockReason 加 isTooSmall 分支强制返回 SITTING 阻断；(2)【拒绝文案】heartfelt lang 覆盖 message.maidmarriage.child.work.sitting 为"还太小了，还不能安排工作"——只有幼儿会看到这条（其余女儿的坐姿被转 NONE 放行，不触发该消息）；(3)【语音包核对】三处拦截（MaidSpiritTtsGuard/HeartPactVoicePlayback/MaidAmbientVoice）均用 isTooSmall 已覆盖两者，无缺口；(4) 强制坐下仅 isInfant（婴儿），幼儿（JUVENILE）可站立——这是两者唯一区别，其余（任务锁空闲/手持限食物/禁语音/不产生主动话语）一律 isTooSmall 两者一致。随 heartfelt_connection-1.5.70.jar 部署（备份 .bak_v1570），重启游戏生效。


promaid 1.5.334：受伤文本统一去指向（承接 heartfelt 1.5.69）——heartfelt 侧 8 处"玩家伤害惩罚"文本（AftermathPrompt 伤心窗口提示词 + PromptTexts 的 harmPenaltyMessage/harmPenaltyBubble/harmPenaltyBubbleChild/harmPenaltyLLMPrompt/harmPenaltyLLMPromptChild/babyCryMessage/babyCryLLMPrompt）全部去掉"被你打/主人打了你/爸爸打了你/主人伤害了你"等来源指向，统一为"被弄疼了/受了伤"——玩家放岩浆、怪物、环境等非直接攻击触发该路径时不再误导成主人打的；其余反应描写（怔怔看着、背对、呜哇大哭）保留。promaid 侧无代码改动（文本全在 heartfelt），随 heartfelt_connection-1.5.69.jar 部署（备份 .bak_v1569），重启游戏生效。


promaid 1.5.333：心契誓约 Alt+J 对坐着的女仆无法进入交互面板——根因（javap 字节码实证 maidmarriage 2.3.0）：MaidHugManager.handleInteractionToggle（Alt+J 的服务器端处理器）校验链含 `if (maid.isMaidInSittingPose()) { 发 need_standing 消息; return; }`——坐着的女仆被直接拒绝，面板不开（child 女仆另有 need_adult 拒绝，但儿童走独立儿童交互面板路径，不受影响）；修复：(1)【放行坐姿】新 mixin MixinMaidHugSittingAllow（@Pseudo 软注入 MaidHugManager，require=0 版本变动静默降级）在 handleInteractionToggle HEAD 处把【目标坐姿女仆】先站起来（m_21837_(false) 清坐姿+指令位 + m_20124_(STANDING)——仅限坐姿中、主人、未骑乘，骑乘由原版 riding_blocked 继续拒绝）→ 原版 need_standing 检查自然通过 → 面板正常打开；后续 lockMaid 本就会把女仆设为 STANDING 并锁定姿势/位置，与预站起一致无副作用；(2)【建造不打架】MaidBuildBehavior.tickBuildSit 在女仆处于心契誓约交互中时（反射 MaidHugManager.getInteractionPlayer(maid) 非空，软联动）跳过强制坐下——否则交互锁定站立 vs 建造强制坐下每 tick 互搏（坐下↔站起振荡）；交互结束后自然恢复建造坐姿。随 Promaid-1.0.11.jar 部署（备份 .bak_v1333）。


promaid 1.5.332：幼儿女儿武器禁持——范围严格限定【女儿 且 婴儿/幼年阶段】（RelationshipMemoryAdapter.isTooSmall：isChild 为真且 heartfelt_child_stage 为 INFANT/JUVENILE，缺失时兜底读 child_state_data.growthStage——正是"幼儿女儿不产生主动话语"那套既有判定）；实现：MaidWeaponGuard 每 1 秒轮询全部已加载女仆（ProMaidExtension.onServerTick 挂载），主手/副手（getHandsInvWrapper：Forge EntityHandsInvWrapper，slot 0=主手、1=副手）出现武器（剑/斧/弓/弩/三叉戟）→ 从手上删除并【原地丢一个完全一样的】（ItemStack.copy 副本 → ItemEntity 生成），覆盖玩家喂给/自动装备/任务换装等一切入手机制；成年女儿/普通女仆不受影响，镐等挖掘工具不在武器判定内不受影响；移除日志 latest.log 搜 "weapon guard"。随 Promaid-1.0.10.jar 部署（备份 .bak_v1332）。


promaid 1.5.331：撤销 v1.0.7「图腾/濒死背叛保护」——实测确认女仆"全部消失"实为主人死亡传送（MasterDeathTeleportHandler）误传，非爱憎分明背叛：v1.5.326/327 基于"坠落触发图腾→女仆消失=背叛"的误判加入的 BetrayalGraceManager（5 分钟保护期 + 反射拉回背叛女仆 + 清 dangerTimer）、MixinEmotionBetrayalGrace（压制 triggerBetrayal 与危险计时）、MaidBaubleTotemMixin 主人分支两处无条件 activate、config 开关 loveLoatheBetrayalGrace 全部移除。不死图腾判定恢复 v1.5.325 原样：女仆自己饰品栏/背包图腾复活、主人致命伤时女仆共享图腾救主（TOTEM_SHARE_ENABLE），无背叛干预。死亡传送保留 v1.5.329 延迟 1 tick校验存活（图腾救活不传，真死才传）。


promaid 1.5.330：TNT 建造期防自燃 + 半透明渲染重发修复——(1)【半透明修复失效根因】resendPlacedChunks 节流初值 LAST_CHUNK_RESEND = Long.MIN_VALUE：首次调用 now - Long.MIN_VALUE 整数溢出为负 → 节流判定恒 <40 → return，区块重发【从不执行】= v1.5.322 声称的修复从未生效（字节码实证客户端链路上链路完整：ClientboundLevelChunkWithLightPacket → LevelChunk.replaceWithPacketData 换数据 → handleLevelChunkLighting → setSectionDirty 无条件触发重建，空光源掩码不影响）；修复：初值改 0（游戏刻恒 ≥0 首次即生效）+ 待重发集合改【静态跨轮累积】（旧版每轮新建局部集，节流 2 秒窗口内非触发轮的放置会漏发），重发成功后一次清空；(2)【TNT 自燃根因——Never4Get"游标走到底卡死/反复跟活塞过不去"的真相】活建造（v1.5.316）下动力源先带电，TNT 最后放置时旧代码用 flag 2 静默以为能防点燃——字节码实证 LevelChunk.m_6978_（setBlockState）服务端【无条件】调用 BlockState.m_60753_（onPlace），flag 2 挡不住；TntBlock.m_6807_（onPlace）检测相邻有信号（m_276867_）→ onCaughtFire 点燃 + m_7471_ 移除 → "放置后目标仍空气" → 旧计划 320 个 TNT 全延后 → 卡死（日志实证 "build stall: 游标=10391/10391 延后=320 [tnt@3,34,26 现=minecraft:air …]"）；修复：MaidBuildBehavior.doPlace 放置 TNT 时挂 BuildTntGuard 线程护栏（try/finally 清除）+ MixinTntBuildGuard 在 TntBlock.onPlace 头部 cancel → TNT 以稳定方块落地，机器运行时红石翻转/触发走 neighborChanged（m_6861_）正常点燃——"建好就能跑/手动标靶触发"不受影响；BlueprintLib.normalizeMachineState 对 TNT 归一 unstable=false（蓝图导出的点燃态不落地）；(3) 均随 Promaid-1.0.8.jar 部署（备份 .bak_v1330），重启游戏生效。


promaid 1.5.329：主人死亡传送延迟校验——图腾救活不传——根因：非背叛、非 1.5.327 排查的结论（当时判"图腾保命不触发 LivingDeathEvent"），玩家环境（charmofundying 不死图腾槽位等）在触发图腾时【先触发 LivingDeathEvent 再图腾复活】——MasterDeathTeleportHandler 立即执行，把女仆传到【重生点】（主人实际没死、离坠落现场很远）→ 女仆"凭空消失"。修复：死亡传送改为【延迟 1 tick 校验】——onPlayerDeath 只登记，下一 ServerTick 检查玩家存活（isDeadOrDying / 血量≤0）——图腾救活（活着）→ 跳过传送（女仆留在身边）；真死 → 才执行死亡传送。原传送逻辑（安全落点/追踪表未加载女仆/最近者播报）整体保留。


promaid 1.5.328：图腾/濒死背叛保护无条件激活——承接 1.5.327：旧保护激活（MaidBaubleTotemMixin）只认【主手/副手有图腾】与【女仆共享图腾】两个入口，漏了【背包/槽位图腾】触发场景（charmofundying 不死图腾槽位等扩展：图腾从背包/槽位触发时保护没激活 → 爱憎分明背叛判定继续走满 → 女仆全部消失）。修复：致命伤瞬间（m_21262_ 被调用 = 玩家死亡检查）无条件 activate 保护——无论图腾在哪个槽位（手/背包/槽位）甚至没有图腾（真死亡）都先进入 5 分钟保护期（拉回正背叛女仆 + 清 dangerTimer + 压制背叛判定），防"主人濒死瞬间女仆背叛跑掉"；手里有图腾仍放行原版路径（不抢消耗）。


promaid 1.5.327：图腾/濒死背叛保护——(1)【根因排查】不是 promaid 的死亡传送（字节码实证：图腾保命时 LivingEntity.m_6469_ 走 ifne 跳过分支，die() 不执行 → LivingDeathEvent 不触发，MasterDeathTeleportHandler 不运行，pro 死亡传送/目击死亡链均排除）；消失的是【爱憎分明（callresponse 2.0.2）的背叛机制】：EmotionBetrayalManager 每 tick 检查 信任<10 且 恐惧>90，持续 200 tick（10 秒）→ triggerBetrayal 解除契约（setOwner(null)）、停止跟随、跑开攻击，NBT IsBetraying 跨存档持久化（实测：汉服酒狐背叛后重进存档仍是背叛态，右键才恢复）——女仆的恐惧/信任早已被"主人打女仆"等事件积累到临界（日志实证 03:05"好痛…主人为什么要这样对我"→03:06 背叛→次日 13:05 仍在背叛态），玩家坠落触发图腾的瞬间恰好是危险计时（dangerTimer）走满 200 tick 的时刻 → 两只女仆同时触发背叛 = "突然全部消失"；(2)【修复】图腾/濒死保护：主人触发不死图腾（自己手里的原版路径 或 女仆共享图腾保命路径，MaidBaubleTotemMixin 双入口挂钩）→ 进入 5 分钟保护期——MixinEmotionBetrayalGrace（@Pseudo 软注入，target 爱憎分明 2.0.2，require=0 版本变动静默降级）保护期内压制 triggerBetrayal 与 dangerTimer 累加；BetrayalGraceManager 激活瞬间反射调用 EmotionBetrayalManager.resetBetrayal 拉回正背叛的女仆（播报"主人……我不该离开你的……我回来了！"+系统消息）并清空其 dangerTimer——给玩家时间喂食/互动抬信任降恐惧，濒死不再成为背叛导火索；(3)【配置】爱憎分明模组调试页新增「图腾/濒死背叛保护」（misc.loveLoatheBetrayalGrace，默认开，受爱憎分明联动总开关约束）；未装爱憎分明/版本变动时全部静默跳过，不影响任何功能。


promaid 1.5.326：LLM 防乱伦铁律（承接 heartfelt 1.5.55——要求大语言模型防止乱伦很有必要）——AiMemoryContext 女儿关系标签在称呼铁律后追加防乱伦铁律：我是他的女儿，对他只有亲情——严禁任何恋爱、亲密、暧昧的言行与情感，即使已经长大也永远是父女关系，遇到恋爱或亲密话题要困惑地拒绝；与 heartfelt 四段女儿准则的 RELATIONSHIP GUARD 及亲密工具行为层拦截构成三层防线。


promaid 1.5.325：幼女成长记忆描述修正（承接 heartfelt 1.5.50——"站起来"是幼女→少女(CHILD)的标志，JUVENILE 幼女站不起来）——stageLabel：JUVENILE「会自己站起来了的小宝贝」→「又长大了一点点的小宝贝」，CHILD「活泼的小女孩」→「会自己站起来的小女孩」；与 heartfelt 成长系统消息/提示词同步（幼女成长只报"又长大了一点"，CHILD 才报"会自己站起来了"）。


promaid 1.5.324：蓝图删除根因修复——根因：模组同时扫描 config/maid_smart/blueprints 与 <cwd>/schematics 两个目录，但蓝图书删除只删 EXTERNAL_PATHS 登记的那一份——同一蓝图文件若在两个目录都有副本（多实例/多存档残留），删一份、另一份下次扫描又注册回来 = "删了又出现/重登又出现"（RRR/SSS 删不掉的历史根因，v1.5.252ad 注释即有记录）；修复：deleteBlueprint 改为【全扫描目录清除同名文件】——按文件名（去扩展名、大小写不敏感，RRR/rrr 都删）在 config 与 schematics 全部扫描目录里清掉所有副本，再清内存缓存；删除后即彻底，不再复发。


promaid 1.5.323：内部版本号同步 1.0.2 → 1.0.4。


promaid 1.5.322：半透明渲染刷新修复——根因：建造过程中快速批量放置时，客户端区块重建队列积压，玻璃/粘液块等【半透明渲染层】迟迟不重建（实心层先重建、半透明层滞后），从区块外往里看就"看不见"（走近/建完后恢复正常）；修复：建造每轮放置后【节流重发本轮放置过的区块】给同维度玩家（每 2 秒一次；`ClientboundLevelChunkWithLightPacket` 空光源掩码=只刷方块渲染不动光照）→ 强制客户端整块重建（含半透明层），玻璃/粘液块即时可见；只重发本轮实际放置过的区块、2 秒节流，性能影响可忽略。


promaid 1.5.321：对外版本号 1.0.0 → 1.0.2——mods.toml version 与 jar 文件名同步改为 1.0.2（Promaid-1.0.2.jar），构建链（build_promaid.py/verify_jar_classes.py）与发布脚本（Modrinth/GitHub）同步更新；影响面核查：源码无硬编码版本号（游戏内不显示版本），modId 不变（promaid），持久化数据（config/maid_smart、灵魂记忆、存档 TaskData）按 modId/UUID 索引不受版本/文件名影响，TLM 依赖声明 [1.5.0,) 不变；唯一注意点=部署时旧 Promaid-1.0.0.jar 必须删除（同 modId 双 jar 会冲突崩溃），本次部署已同步删除。


promaid 1.5.320：矿车纳入工具需求——(1)【矿车=启动工具·消耗】轰炸机类完工自动放矿车不再凭空白给：每条探测铁轨消耗 1 辆矿车（女仆背包优先、主人背包兜底；主人创造模式无限），背包没有矿车 → 不自动生成；(2)【完工动态提示】按实际放置数提示——「已自动放置矿车 X 辆，即将启动轰炸循环——请撤离机器附近！」/「缺少矿车（需 N 辆），未自动启动——把矿车放进背包后，手动放到探测铁轨上即可启动轰炸循环」（machineFinishTip 轰炸机文案改动态）；(3)【材料提示全链】蓝图摘要/创建气泡/缺料消息追加"矿车×N（完工自动放置，需备齐）"（fluidBucketNeeds 统计探测铁轨数）；(4)【材料表工具行】矿车行加灰色角标「（工具·消耗）」（水桶「（工具）」、岩浆桶「（返还）」）；中文名补"矿车"；(5) LLM smart_build 汇报走 describe 自动携带。


promaid 1.5.319：材料表液体行角标（承接 v1.5.318 材料提示补全）——手册材料详情页的水桶/岩浆桶行加灰色小字角标：水桶「（工具）」、岩浆桶「（返还）」，与普通消耗材料视觉区分（工具不消耗、岩浆放置后返还空桶，这两行不是普通消耗品）；LLM smart_build 材料汇报无需改动——其汇报走 describe，v1.5.318 起已自动携带"另需水桶×1（作工具，不消耗）、岩浆桶×N（放置后返还空桶）"。


promaid 1.5.318：生存材料提示补全液体需求——水/岩浆被 countNeeds 排除（FORBIDDEN），旧版材料提示/材料表完全看不到桶的需求 → 玩家建造前不知道要备水桶/岩浆桶，建到一半才卡住看气泡。本次把液体需求补进全部材料提示面：(1)【蓝图摘要 describe】材料 top5 后追加"另需水桶×1（作工具，不消耗）、岩浆桶×N（放置后返还空桶）"；(2)【创建气泡 needBubbleText】"还需 X×N"后追加液体需求；(3)【缺料消息】"材料不足，缺少：X"与"背包里没有任何建造材料"后追加液体需求；(4)【手册材料表 buildCatalogEntriesWithMaterials】新增"水桶（工具）"与"岩浆桶×N"行（已有=玩家+绑定女仆桶数，需要=1/N；创造模式显示齐）；(5) 中文名补"岩浆桶"；空气/矿车维持不显示（空气非材料、矿车完工自动生成）。


promaid 1.5.317：水/岩浆/空气/物品判定改革——(1)【根因修复：水/岩浆从未被放置】旧版导入蓝图时 FORBIDDEN 黑名单把 water/lava 全部剥离、且方块无对应物品（物品是桶）→ 打包机/潜影盒仓库的冰道水道、甘蔗机水槽、南瓜机灌溉沟、分类机水冲、仓库岩浆焚烧口【建成全是干槽/空槽】——这才是机器"建好不动"的更深层根因（v1.5.315 activateWater 建立在错误前提上：计划里根本没有水步骤，重放=空转）；(2)【机器蓝图保留液体】parseStructure 新增 keepFluids：红石机器家族（文件名匹配）导入时保留 water/lava 步骤，普通建筑维持剥离（防洪水/岩浆事故）；(3)【材料链按桶结算】BLOCK_ITEM_OVERRIDES 加 water→water_bucket、lava→lava_bucket：水=【无限材料】——女仆或主人背包有 1 个水桶作工具即可放任意多水源（不消耗，与原版水源可再生一致，缺桶提示"需要水桶"）；岩浆=【消耗 1 岩浆桶/格、放置后返还空桶】（returnEmptyBucket：主人背包优先→女仆背包→掉落身边，防被完工清理；岩浆不可再生，需玩家备 N 桶周转）；(4)【液体只放源块】normalizeMachineState 对水/岩浆归一 level=0——蓝图导出的流动态不直接放置，流动由水源模拟产生（防串流/重复）；液体始终 flag 2 静默放置，完工 activateWater 批激活 → 统一流动/气泡柱成形（逐个活放会因相邻水源先流过来而跳过后续源块，气泡柱/水道不完整——机器与建筑水池均走此路径，旧"机器跳过 activateWater"一并修正）；(5)【空气判定保持】外部蓝图空气隐式剥离（不建不清，天然留空）；显式空气步骤（世界提取/JSON）保留"清除"语义（只清黑名单外可拆方块），材料统计排除空气不变；(6)【矿车等物品】实体不入蓝图（方块列表天然无实体），轰炸机完工自动放矿车已由 v1.5.316 提供；无物品方块（活塞头/气泡柱/附生茎/绊线/火）继续剥离并在完工提示按家族说明需手动补充项。


promaid 1.5.316：红石机器改革——(1)【去禁锢】机器不再"静默放置（flag 2 机械冻结）+ 完工唤醒/不唤醒"，取消旧禁锢链（红石线保留冻结 power、活塞保留冻结伸缩态 → 建好不运行的根因）；机器改走【活建造】（flag 3，红石/水流随放随算）；(2)【专属搭建顺序 sortMachinePlan】按红石拓扑分层：结构 → 惰性机构（漏斗/箱子/铁轨/冰/灵魂沙/水等）→ 活动件（活塞/粘液块/蜜块/红石灯）→ 传感/信号（观察者/比较器/中继器/标靶）→ 动力源（红石线先、红石块/火把/拉杆/按钮/压板最后）→ TNT（最后且始终静默放置）——动力源最后落位，机器在最后一格落下时自然进入运行态（"建好就能跑"）；相位内 y→x→z 自下而上（支撑/槽壁/灵魂沙/冰先于其上的附着物/水，气泡柱/冰道成形）；(3)【状态归一化 normalizeMachineState】机器模式丢弃蓝图导出时的冻结瞬态（伸出活塞 extended→缩回、通电铁轨 powered→断电、线 power→0、火把/灯 lit→自然态），方块以自然初始态落地、由活放置收敛到设计态；(4)【轰炸机自动启动 spawnStartMinecarts】单TNT自返回/1.111倍速等带探测铁轨的轰炸机，完工自动在每个探测铁轨上生成矿车（矿车压轨才带电 → TNT 复制循环自动启动），完工提示同步改为"已自动放置矿车,即将启动轰炸循环——请撤离机器附近!"；2TNT/29方块/推土机（纯观察者/活塞自启动）与 Never4Get（手动标靶触发）自动跳过；(5)【配置开关】新建「红石机器专属搭建」（build.machineSmart，默认开，面板·建造设置）——关 = 完整回退旧行为（常规顺序+静默+完工唤醒）；(6) 作用范围仅红石机器家族（外部 21 台 + 内置 machine_ 前缀 + LLM 机器），普通建筑保持静默放置防活塞推墙互搏；carveEntrance 红石豁免沿用（机器不开门洞）。


promaid 1.5.314：调试面补全——Promaid 手册·女仆记忆→「链路调试」页新增 3 行状态：【人格】人格名字·核心记忆条数·TLM人设=补充/完整（人设统一模式）、【纪念日】基准=告白/初遇·第N天·已达成M天·临近L天（heartfelt 联动游标直读）、【双agent】开关·提取中=是/否·关心点N条；状态区小窗口超行时右上角 ◀/▶ 翻页（不丢行）。


promaid 1.5.313：人设统一 + 动态化——(1)【人设统一（注入消歧）】检测 TLM 原版人设（per-maid「自定义设定」customSetting + per-model settings/*.yml，反射兜底）：TLM 已有人设时，人格种子块【降级为补充】——只注入 TLM 没有的[人格参数]（害羞/依赖/边界敏感等 8 维）+[核心记忆]（宪法级），块首声明"冲突以 TLM 设定为准"，不再重复身份/性格（TLM 模板 Data Overridden 规则让 <context> 优先于 system，双人设并存行为不可预期——根因在此）；TLM 无人设时保持完整人格渲染；新配置「人设统一」（memory.personaUnify）默认开，可一键关回旧行为；记忆面板显示「TLM 人设：有（补充模式）/无（完整模式）」；(2)【人设动态化（不一成不变）】重大关系事件沉淀进角色包核心记忆（core_memories.jsonl，宪法级随投影每轮注入）——结婚/告白/父女/纪念日里程碑达成时自动追加（内容去重 + 上限 20 条防膨胀）：人格的"自我认知"随时间成长（初遇 → 恋人 → 妻子 → 纪念日 N 天），配合已有的 ai_affect 情绪注入 + 关系状态注入，人设 = 稳定内核（persona.properties 种子保证连续性）+ 成长外壳（核心记忆/关系现状/情绪驱动演变）。


promaid 1.5.312：记忆升级四件套（借鉴 maidSoulCore 思路深度落地，全量向后兼容）——(1)【情绪快照入记忆】每条新写入的记忆自动附带当时 PAD 情绪（愉悦/唤醒/支配/亲密/冲突/思念/受伤债/修复债，存 metadata 字段；旧记忆不受影响）；情绪注入上下文（ai_affect）新增「表达建议」——受伤/修复债高→先接住余波再和好、亲密+思念高→可更粘人、心情低→回复短而软，LLM 不再只看数值；(2)【人格种子（角色包）】每女仆记忆目录自动生成 persona.properties（人格种子）/ traits.properties（人格参数）/ core_memories.jsonl（宪法级核心记忆，salience≥8），作为【只读投影】优先注入对话上下文——人设与聊天记忆彻底分离、聊天永不改写人格（maidSoulCore 哲学：「prompt 只是状态投影，不是人设本体」）；改人设直接编辑文件（UTF-8），女仆配置·记忆面板显示人格名字；(3)【每日关心点】每日回顾自动附加「下次该怎么对主人」的行动建议（从情绪残留/边界/偏好/风格推导，纯规则零 LLM），主动对话（TOPIC_PUSH 读 daily 段落）会自动复用当话题——记忆↔主动对话联动；(4)【双 agent 提取】记忆提取拆成摘要（SUMMARY/IMPORTANCE/TAGS）与事实事件（FACT/EVENT）两次独立 LLM 调用——更聚焦、互不阻塞，任一失败不阻塞对方，水位线等两者都完成才推进（防重试风暴）；memory.dualAgent=false 回退单次合并提取省 token；(5)【路由统一】主动对话（memoryTopic/relationMemory）与回复反馈（error_mark/强化）的记忆读写统一改走灵魂路由（SoulBindingService.storeFor）——灵魂女仆不再读错世界目录，人格/情绪/关心点路由一致；新配置四项：情绪快照入记忆 / 人格种子注入 / 每日关心点 / 双 agent 提取（配置面板·AI 记忆→「人格与情绪」小节，均默认开、可一键关停）


promaid 1.5.311：红石建造根因修复 + 红石/状态能力扩展（承接 v1.5.300"红石机器基本都不能用"的根因）——(1)【红石唤醒 v2（recalcRedstone）】旧版完成时对红石组件"同状态重放 flag 3"在 1.20.1 是失效操作：Level.setBlock 对状态相等的重放不调用 onPlace（红石线/火把/活塞的放置重算逻辑永不执行），updateNeighborsAt 又只广播水平邻居——红石线保留图纸导出时的冻结 power、活塞保留冻结伸缩态 → 机器建好不运行/状态错乱。v2 每格补齐：组件自身 neighborChanged 直调（红石线重算 power、活塞 checkIfExtend 重定伸缩含 BUD/准连接、灯/铁轨/火把重查供电）+ 垂直邻居手动补广播 + 白名单补全（活塞/铁轨/绊线/陷阱箱/门/音符盒/标靶）+ 3 轮收敛 + 替代品位置兼容；(2)【红石族进 LLM 白名单】redstone_wire/红石火把/红石块/红石灯/拉杆/按钮/中继器/比较器/观察者/活塞/粘性活塞/发射器/投掷器/漏斗/铁门/压力板/铁轨（充能/探测/普通）/标靶/音符盒/陷阱箱——LLM 现场生成的红石机器可以真实运行（v1.5.300 删机器是唤醒失效所致，根因已修复）；(3)【JSON 蓝图支持 state/nbt】LLM JSON 每块可带可选 state（SNBT，如 {facing:"north"}）与 nbt（方块实体）——门朝向/楼梯方向/漏斗朝向/箱子内容不再一律默认态；BlueprintLib.validate 与 smart_design 宽松校验同步兼容（state 含逗号，旧版 split(",") 误报格式错误）；(4)【开门洞红石感知】carveEntrance 对含红石组件的蓝图整体豁免（不再只认 machine_ 前缀）——外部红石蓝图墙内布线不会被完工开门洞切断。


promaid 1.5.310：爱憎分明（Love Loathe）软联动调试页——(1)【配置面板】装了爱憎分明（modId=callresponse）时首页左列多一个「爱憎分明模组调试」大按钮（未装不显示，软联动零影响），页面布局自动压缩不压保存按钮；页内【联动状态（调试）】只读探测：模组版本 / HungerData 反射接口 / EmotionData 反射接口（新旧包名双试，v1.5.284 机制）/ 饥饿门控注入状态 + 「重新探测」按钮；(2)【联动开关】爱憎分明联动总开关（默认开，关掉后不再反射读取其数据）、禁用爱憎分明饥饿（原杂项页迁入本页）、极端饥饿保命联动（饥饿值 ≤9 时吃金苹果）、情绪数据联动（信任/恐惧入记忆与 AI 上下文）；(3)【行为接入】SelfPreservationBehavior.isExtremeHungry 与 RelationshipMemoryAdapter.emotionValues 均受对应开关门控；(4) 指南「模组详细配置面板导航」章补充说明（11→12 板块）。


promaid 1.5.309：指南去虚假宣传——战斗与自保章逃生手段③ 由「卡墙窒息自动垫台阶翻越，溺水游上去」改为「窒息/卡墙时会尝试上浮，必要时传送到主人身边」（与实际防窒息措施一致）


promaid 1.5.308：区块名单页进度条重叠修复——根因：v1.5.302 给区块名单页补进度条时，行数公式只给翻页按钮预留了 32px，绑定女仆多 + 窗口矮时名单撑到进度条区域（h-94）→ 重叠。修复：行数公式【预留进度条空间 48px】（4 行状态文本 + 进度条 + 边距）——名单每页行数相应减少、自动分页，进度条永远在名单下方不重叠；按钮区与渲染区的行数口径同步（防进度条与翻页按钮错位）


promaid 1.5.307：缺吃的播报实现 + 喂食提示补全——排查结论：(a)【缺吃的播报从未实现】代码里根本没有该播报，手册确实是虚假宣传——日志实证女仆无食物时每 tick 刷屏"背包与双手都无投喂食物"（08:03:44 起每 50ms 一条）；现在实现：主人饿且女仆背包/双手无食物时，女仆气泡 + 系统消息「我背包里没有吃的了，给我备点食物吧～」（60 秒限频不刷屏，日志同步限频）；(b)【喂了什么系统提示】食物喂食的系统消息一直在（feedFoodDirect「女仆喂你吃了 X」），之所以"不生效"是因为女仆根本没有食物、喂食从未成功（日志实证）；金苹果路径确实只有气泡没有系统消息——补上「女仆给你吃了附魔金苹果（效果已加成）」，现在所有喂食路径（食物/蜂蜜/牛奶/金苹果）都有系统提示


promaid 1.5.306：指南微调——(1)【工头补充】女仆管理章补：没设立工头时，区块开始建造自动从绑定女仆里随机选一只当工头（想换人随时在名单页手动指定）；(2)【价值分保留】排查结论：价值分功能未失效——评分公式 `距离² + 深度惩罚 − 价值×权重`（权重默认 2.0）仍在用，内置价值表（钻石 500/绿宝石 450/铁金 250/煤 100…）+ 矿表自定义值（id=价值）双重生效；玩家配置里 oreValues 为空表 → 用的全是内置默认值，所以"感觉不到"；如需高价值矿更优先可把「价值权重」调大（最大 5.0）——不删除；(3)【落地水去重】战斗与自保章的逃生手段去掉落地水/岩浆逃生条目（与被动技能章重复），改为指引「被动技能」章统一介绍


promaid 1.5.305：手册调整——(1)【「女仆配置」按钮删除】手册女仆详情页的直达 TLM 配置按钮整体移除（OpenMaidGuiPacket 网络包一并删除；玩家判断该路径有 bug 且不想修——打开 TLM 女仆配置请直接右键女仆），指南中所有相关描述同步清理（跳转链路、女仆管理章、LLM 章直达路径）；(2)【指南：配置 LLM 章简化】AI 记忆的 API 只需一句"默认跟随原版（TLM）配置"——删除原"记忆 API 单独配置/留空=跟随 TLM"详细说明（AI 记忆章同改）；(3)【指南：建造教程补记】末尾新增「关于自动甘蔗机的废弃」：版本问题导致漏斗无法接收隔着一块泥巴的甘蔗掉落物，机器无法正常收集（搭建本身可正常收割，仅收集失效）；有一定历史意义（蓝图打印——终于能打印出可工作的红石机器），后续版本会尝试加入真正有用的红石机器；小知识补充自定义替代方块的作用（连材料等价物都找不到时，用玩家自定义方块替换，替代品名单在建造板块）


promaid 1.5.304：详细介绍全面重写——25 章保姆式教程：每章先讲"是什么"再手把手教"怎么用"，关键步骤配【举例】；新增「准备篇：配置 LLM（保姆式）」「模组详细配置面板导航」「被动技能（独立板块）」三章；重写同时修正全部过时内容：投喂直接喂（不再塞快捷栏）、手持食物可喂、蜂蜜是投喂食物、自主决策默认开且必须 LLM、投喂距离 16 格、极速模式放置间隔、畜牧数量控制已删除说明、阈值 4-20 真实生效、区块坐标显示、名单页跳转、进度条只在区块详细页、输出语言选项选择、红石机器只剩自动熔炉组、门落地修复——更新日志章节保持原样（读 changelog.txt）


promaid 1.5.303：对话输出语言改为选项选择——「对话提示 → 输出语言」原先是手填文本框（要填 zh_cn/en_us/ja_jp 语言代码，填"中文"这种自然语言无效、填错无提示），改为【循环选择按钮】：跟随游戏语言（默认）/ 中文 / 英文 / 日文 / 韩文 / 俄语——点一下换一个，选项直接写中文名，内部自动映射语言代码（跟随=留空，中文=zh_cn 等），不再有填错风险；旧配置里手填的未知值显示为"跟随"（点一次选项即修正）


promaid 1.5.302：进度条位置修正——【女仆管理页移除进度条】旧版底部进度/速度文本叠在女仆名单最后几行上（截图实证：第 6 行女仆按钮上叠着"速度：极速"；v1.5.190 只修过页数口径、没根治重叠）；【区块详细页（区块「X」· 绑定女仆名单）补上进度条】——进度条现在只在对应区块的详细界面显示（有翻页时上移避开底行、无翻页贴底）；建造总目录/蓝图材料详情页的进度显示不受影响


promaid 1.5.301：投喂阈值按钮真实生效——根因：配置定义 `aidFoodThreshold` 的 defineInRange 范围是【4-18】（注释写 0-20，范围上限没跟上）——面板填 20 → Forge 配置系统把越界值【静默钳制回 18】（输入框显示 20、实际生效 18）→ 饱食度 18~19 时永远不喂，「修改按键看起来无效」。修复：范围上限 18 → 20（填 20 = 只要不满就喂，真实生效）；治疗触发血量范围同步对齐（0.1-0.8 → 0.1-1.0，注释 0-1 同样存在钳制问题——测试调 1=掉血就治会真实生效）；两处提示语更新；`aid feed` 诊断日志加实际阈值（latest.log 搜 "aid feed" 可确认当前生效值）。修改即时生效无需重启（面板「保存并返回」写入内存+文件，行为每 tick 读配置）


promaid 1.5.300：红石机器精简 + 蓝图门修复——(1)【机器删除】自动甘蔗机/自动南瓜西瓜农场/昼夜自动灯 3 台整体移除（目录/生成器/离线验证同步删），只保留【自动熔炉组】（纯漏斗定向无红石，100% 可靠）——观察者+活塞脉冲时序在建造环境不可靠（信号线柱、脉冲驱动在女仆逐个放置/方块更新时序下经常不通），不再保留；(2)【门修复】两处根因：a) 门【离地 1 格】——旧版门放 y0+1、地基垫在门下，从外面看门悬空在地基上，不像正常的门；改为门【落地 y0】（地面高度），并清掉门内一格地基，玩家可从地面直接走进来（进门一步踏上室内地板）；b) 门【朝向错误】——正面墙 z=0 的门旧版 facing=south（朝屋里开），改为 facing=north（朝屋外开，玩家迎面推门）；15 小屋 + 10 别墅全部生效


promaid 1.5.299：投喂不触发根治——两个根因：(1)【硬编码阈值 15】giveFoodToOwner 内部写死 `foodLevel >= 15 就 return`（对话动作时代的旧闸门）——配置面板的「投喂触发饱食度」只在调用处生效，方法内部还有一道 15 的旧闸门：阈值调 20 时饱食度 15~19 被这道闸门拦住（阈值小于 15 时碰巧正常，所以看起来"时灵时不灵/奇怪的间隔"）。修复：改读配置阈值（阈值 20 = 只要不满就喂）；(2)【手持食物不识别】喂食扫描只查 getMaidInv()（TLM 实证：手部是独立 handsInvWrapper，不算背包）——肉排拿在女仆手上时永远找不到。修复：三条喂食路径（饱食投喂/治疗食物/牛奶蜂蜜）全部纳入主手+副手扫描（手持同饱食度优先用手上的，不再翻背包）；牛奶喂食公共动作抽出共用。触发间隔也正常化：配置阈值生效后，饥饿即喂、成功一次 3 秒冷却（原有投掷间隔），不再有"莫名不触发"


promaid 1.5.298：区块名单页跳转补齐——本页右上角新增「女仆管理」直达按钮（一键跳女仆管理页；女仆管理页点女仆行进详情页绑定/解绑，详情页有「女仆配置」直达 TLM 配置界面）；空名单提示同步改为指引右上角按钮。跳转链路全通：区块名单页 → 女仆管理 → 女仆详情 → TLM 配置


promaid 1.5.297：区块悬浮标签两修——(1)【外部可见】TLM renderFloatingText 末参（shadow）实测（javap 字节码）：false = NORMAL 深度测试（被方块/线框遮挡）、true = SEE_THROUGH 透显（TLM 女仆名字牌同款渲染）——旧版传 false，区块外看标签被遮挡，实际只有站在区块内部才看得到；两行全部改 true，任何角度、隔方块都可见；(2)【重叠修复】名字行「苔藓神庙·三（建造中）」与坐标行「创建于 x,y,z」锚点只差 0.35 格，而 0.15 字号行高约 1.3 格 → 两行叠在一起（截图实证 OCR 读成一行「苔藓神庙-214，-60 建造中」）；坐标行下移，锚点差 1.4 格、净距约 0.45 格不再重叠


promaid 1.5.296：【连接已丢失】崩溃根治 + 坐标显示/跳转真正落地——根因：v1.5.290 把 OpenBlueprintBookPacket/ProgressUpdatePacket 的 regions 区块数组 encode 改成 14 字段（追加创建坐标 r[11..13]），但【两处 decode 漏改仍读 11 字段】。后果：(a) 客户端区块永远只有 11 字段 → 创建坐标字段在传输中丢失 → 区块"创建于 x,y,z"坐标显示从未生效（玩家反复反馈"坐标显示没做出来"的最终根因）；(b) 每个区块多余 3 个坐标字符串错位到后续字段：1 个区块时 initialView 读到坐标数字（不崩但数据错乱），【≥2 个区块时 initialView 读到蓝图 id（如"maid_smart_ext:苔藓神庙·三"）→ NumberFormatException → 客户端连接损坏 → "此服务器发送了一个无效的数据包" → 连接已丢失】（日志实证：06:21:42 创建第二个区块后，06:21:55 打开手册即断连）。修复：两处 decode 改 14 字段——坐标显示、区块列表、跳转界面（区块名单行点击 → 女仆详情页；女仆详情页「女仆配置」→ TLM 配置界面）全部随此生效


promaid 1.5.295：UI 翻页陷阱全面排查——按"重要开关被翻页藏到第 2+ 页"标准审计全部界面，修 3 处：(1)【战斗自保页重排】旧版 41 行：逃生/搭高/逃跑的数值参数（22 行）夹在开关区中间，把「贴身辅助」（自动投喂/治疗主人、火把、盾牌、图腾）挤到第 3 页、「单兵战术」（近战贴脸后退等）挤到第 4 页——GUI 缩放 2 时这些关键开关根本看不见（与"自主决策按键没了"完全同类）。修复：自保行为开关 → 贴身辅助 → 单兵战术 → 页尾「自保参数」（数值集中，调参才需翻页）；所有开关现在前 2 页内可见；(2)【对话提示页】主动对话提到工作播报之前——自主决策+主动对话两个主开关都在第 1 页（旧版主动对话开关在第 2 页）；工作播报（次要）顺延；(3)【语音页】三个同名「语音包操作」按钮改名为「导入语音包/重新加载语音包/查看语音包状态」。审计结论：手册（BlueprintBookScreen）全部列表为同类条目+自适应翻页+显式翻页按钮、女仆详情页按钮位置小窗口安全、指南屏有完整章节翻页、其余板块（AI 工具 9 行/被动技能 7 行/感知开关等）第 1 页内可见——无同类问题


promaid 1.5.294：被动技能独立成大栏——配置面板【首页新增「被动技能」大栏按钮】（右列「战斗自保」正下方，与建造/挖矿等同样式的独立板块，非战斗页内小节）：落地水（含触发高度/保持/下探格数）、岩浆逃生放水、主人死亡传送共 7 行从「战斗自保」页整体搬入被动技能页；「战斗自保」页只保留主动行为（逃生与自保策略/贴身辅助/单兵战术）——布局上右列 6 按钮与左下「保存并返回」无水平重叠，矮窗口也不压按钮


promaid 1.5.293：详细设置「自主决策」按键找回——排查结论：分区一直存在（部署 jar 常量池/配置 toml 全实证），它在「对话提示」页第 14 行；该页共 30 行按窗口高翻页（每页行数 = (窗口高-128)/44），窗口高 ≤744 或 GUI 缩放大时「自主决策」被翻到第 2+ 页——第一屏只看到工作播报/主动对话，看起来就像"没了"。修复：把「自主决策」区块（开关+决策冷却+日上限+API 配额，共 5 行）整体提到对话提示页【第一屏】——任何窗口高度打开对话提示第一页第一行就是它；「工作播报」顺延为第二节。另修构建链：build_promaid.py 新增无源码陈旧 class 自动清除（v1.5.291 删除 MaidFeedAnimalCapMixin 后其 .class 残留 out 被一起打进 jar，本次起不会再发生）


promaid 1.5.292：喂食动作——自动投喂/治疗食物/蜂蜜瓶直接喂时，女仆会挥动手臂（原版摆臂动画，与投药水、给金苹果同款；服务端调用 swing 自动广播，主人可看到）；牛奶直接喂同补——所有喂食路径动作统一（含：饱食投喂、治疗食物、蜂蜜解中毒、牛奶清负面）


promaid 1.5.291：删除"杀幼保成"——畜牧数量控制功能（MaidFeedAnimalCapMixin：附近同种动物超上限时击杀多余幼年动物）整体移除：mixin 文件删除 + mixins.promaid.json 取消注册 + 配置项（animalCapControl/animalCapLimit）与面板两行删除——女仆喂食繁殖不再有任何击杀行为，动物数量交给玩家自己管理


promaid 1.5.290：六项修复（重启后实测反馈）——(1)【区块坐标不显示根治】根因：服务端 regions 已追加创建坐标（v1.5.279，14 字段），但 OpenBlueprintBookPacket/ProgressUpdatePacket 的 encode 写死 `i < 11`——坐标字段从未发出去，客户端 `r.length > 11` 恒 false 显示 "?"。修复：两处 encode 改 14 字段；【世界内区块标记显示坐标】——截图实证坐标要显示在游戏世界里"「苔藓神庙·三」（建造中）"红色悬浮文字【下方】：BlueprintAreaPreview 新增创建坐标第二行（灰色"创建于 x, y, z"，渲染在名字下方；服务端 r[11..13] 到达客户端后随 2 秒轮询刷新）；手册女仆管理页区块列表同样显示"创建于 x, y, z"；(2)【跳入水悬空修复】旧版(288)近水给水平+垂直速度"冲进去"，岸沿高一格/悬空边缘时反复小跳悬空进不了水。修复：距水面 2.5 格内直接【传送入水】（水源格内部，清摔落距离）；(3)【喂食音效+系统提示】女仆喂主人吃东西播放吃/喝音效（蜂蜜=喝、其他=吃）+ 系统消息「[maid_smart] 女仆喂你吃了 X」；牛奶同理（"女仆给你喝了牛奶，负面状态全解！"）；(4)【建造名单跳转女仆管理】手册建造名单页（VIEW_BUILD 列表）新增「女仆管理」按钮（原来只有返回/导入）+【区块详情页名单跳转】右击进入区块的详情页里，女仆名单每行原来是无动作的空按钮——现在点击跳转女仆详情页（查看/绑定/解绑区块）+【女仆配置直达】女仆详情页新增「女仆配置」按钮——点击直接打开 TLM 原版女仆配置界面（右键女仆同款：背包/外观/行为设置；新 OpenMaidGuiPacket 网络包 → 服务端按 UUID 找到女仆 → openMaidGui；512 格内找不到会提示）；(5)【蜂蜜/食物不喂根治】负面解除分支（牛奶/蜂蜜）提前到治疗分支之前（中毒+低血时先解毒再治疗）+ 饱食投喂分支【独立于其他动作】——旧版负面解除喂牛奶成功（清效果但不加饱食）→ aided 短路 → 饱食分支跳过；现在饿就喂，即使本轮已做其他动作；giveFoodToOwner 加诊断日志（latest.log 搜 "aid feed"——主人饱食度/FOODS 大小/喂了什么/背包无食物原因）；(6)【被动技能独立分区确认】配置面板「被动技能」（落地水/岩浆逃生放水/主人死亡传送）早已独立 SectionRow——重启后生效


promaid 1.5.289：投喂牛奶先查正面状态——负面解除分支的牛奶（清全部效果）加前提：主人身上【有增益效果（力量/再生/抗火等）时不喂牛奶】（牛奶会把增益一起清掉，与女仆自己喝牛奶同款前提）；此时只喂蜂蜜（蜂蜜只解中毒+饱食，不清增益）——主人中毒且有增益 → 喂蜂蜜保增益；无增益 → 牛奶全解；无中毒的其他负面且有增益 → 两者都不喂（等效果自然消失）


promaid 1.5.288：投喂机制修复 + 着火入水修复（玩家五项反馈）——(1)【蜂蜜瓶恢复投喂】蜂蜜瓶重回投喂食物清单（饱和 14.4 中等优先级；女仆自己背包的蜂蜜仍走自保解毒链路，两条链路独立不冲突）；(2)【投喂改直接喂食】自动投喂主人/治疗食物/负面解除（牛奶/蜂蜜）全部改为【直接喂】——饱食度直接加到主人（原版 FoodData.eat 语义），不再塞主人背包/手上；牛奶直接喂 = 清全部效果+空桶返还，蜂蜜直接喂 = 解中毒+饱食+玻璃瓶返还；(3)【着火入水修复】根因：女仆是陆地生物，寻路器默认避水——导航到水面格只会走到水边就停。修复：距水面 2.5 格内直接向水方向施加速度冲进去（着地时带小跳跨过岸沿），越过寻路限制；(4) 说明：区块坐标标签（v1.5.279）、替代品面板跳转女仆管理按键（v1.5.275）、配置面板被动技能独立分区均已实装——此前看不到是因为游戏自 v1.5.279 前一直未重启，本版本重启后全部生效


promaid 1.5.287：综合大版本——畜牧控制修复 + 干活不被打断补漏 + 自主决策修复 + 主人背包 AI 工具 + 建造优化 + 4 台红石机器(离线验证可运行)：


(1)【畜牧数量控制】击杀半径与统计半径统一(24 格——旧版统计 24 格只杀 4 格内,超限动物永远杀不到 = 控制失效根因)+ 命名动物不杀(宠物保护)+ 删除死代码;


(2)【干活不被打断】两处补漏：isNonCombatWork 排除 idle(旧版待机女仆也不吃工作餐/不偷吃/不恐慌——恢复空闲行为);新增 SchedulePosTickMixin 拦截每 2 秒的 home 模式周期拉回(旧版只堵了切班瞬间,周期传送拽人漏网);


(3)【自主决策】idle 判定改按任务 UID 比较(旧版对象同一性比较——第三方任务对象不同 → 自主决策永远静默不触发);


(4)【新 AI 工具 smart_owner_inventory】查看主人背包内容(只读查询,可选物品过滤,输出 id×数量+槽位区,限行防 token 爆表)——LLM 问"我背包里有什么"可直接答;配置/面板可关;


(5)【建造优化】tick 热路径 toPlan 惰性缓存(55 万步蓝图不再每 tick 全表拷贝)、scanGaps 用计划级 blockCache、缺口扫描限频键从维度改 planId(多区块互不挤占)、statusText 缺料走 countNeedsCached、延后轮询缺料条目 40 tick 退避(不再每 tick 扫空背包)、FAIL_LOG/PLAN_MAIN 惰性防膨胀;


(6)【红石机器 4 件套,全部离线红石验证通过后发布】修前置 bug:redstone_wire 无对应物品(红石粉是 minecraft:redstone)导致生存/创造都建不出线——BLOCK_ITEM_OVERRIDES 统一材料结算(含南瓜茎→种子、耕地→泥土);机器：自动甘蔗机(观察者+活塞+漏斗 3 单元)、自动南瓜/西瓜农场(茎长果+观察者检测+活塞 2 单元)、自动熔炉组(纯漏斗定向 2×2 无红石 100% 可靠)、昼夜自动灯(倒置阳光传感器+红石灯,需露天);carveEntrance 对 machine_ 前缀豁免(不开门洞破坏机器);红石件中文名补全;离线验证覆盖状态键白名单(1.20.1 合法键)/支撑可达/电源→执行器连通/信号路径


promaid 1.5.286：背包内不死图腾触发修复——根因：MaidBaubleTotemMixin 女仆自己分支只认"手 + 饰品栏"（主人共享分支反而查背包）——自保机制平时会把图腾提前放进饰品栏，但饰品栏满/未转移时背包里的图腾被白白浪费 → 秒杀直接死。修复：女仆分支补【背包查找】（findTotemSlotInInv + 真实消耗 + levelEvent 35 特效 + 气泡"不死图腾救了我一命！"）；致死伤害（含秒杀）原版都会先走 checkTotemDeathProtection（m_21262_），补上背包即全覆盖——优先级：手（原版）> 饰品栏 > 背包


promaid 1.5.285：蓝图书附魔光泽修复——根因：v1.5.251c 加过"始终显示附魔光效"但重写了错误的 SRG 方法（m_41463_，Item 里默认返回 false 的另一个方法），真正的 isFoil 是 m_5812_（javap 实证：原版附魔书 EnchantedBookItem 重写 m_5812_ 返回 true 才有光泽；m_8120_ 才是 isFireResistant）——所以光效从未生效（历史上"光效没出现"反馈的根因）。修复：改重写 m_5812_ 返回 true + 删除 10 秒节流调试日志——现在开局/合成/创造栏拿到的蓝图书天生带紫色附魔闪光


promaid 1.5.284：全链路试运行检查 + 安全加固（防再出"编译通过、运行时才炸"事故）——(1)【离线试运行 harness】（offline_check/RunProMaidCheck.java，纯 JVM 不启动游戏）：25 个内置蓝图全部生成 + 步骤可解析 + 材料统计/摘要 + 并发生成不串步；BlueprintLib 纯方法 parseStep/blueprintSize/countNeeds/cnName/trimTerrainLayers/describe 全走通；BuildPlan 头部解析 + 空输入容错；BuildArchive 序列化往返一致；13 份 .snbt 预制蓝图 21.7 万步全部可读——43 项 PASS 0 FAIL（validate 依赖 config 离线 SKIP，游戏内生效）；(2)【构建链补漏】verify 新增 src→out 全量比对：120 个 .java 源文件必须都有编译产物（防源文件漏编译——内部类 $ 由 javac 连带生成，上次 $BlockCheck 缺失就是此类盲区）；(3)【防崩溃判空】EmotionalActionExecutor.addFood 物品查询判空（防类加载即崩）、materialFamily/blockFunction/isAltPlaced 的 getKey 判空、supportBlockFor 支撑方块查询判空兜底；(4)【bug 修复】爱憎分明反射旧包名改双兼容（2.0.2 迁移 com.github.tartaricacid → com.github.JumDa5he——旧包名反射永远 ClassNotFoundException → 极端饥饿判定/情绪投影静默失效，现在新旧都认）；创造栏 modid 修复（旧写 maid_smart ≠ modId promaid → 蓝图书进创造栏从未生效，现在「建筑方块/工具」能直接拿）；zh_cn.json 重复键去重（cook/brew）；(5)【线程安全】BuiltinHouses 生成缓冲 S 改 ThreadLocal + 缓存改 ConcurrentHashMap（并发首次生成不串步）；(6)【资源清理】删除 soul_core 孤儿模型/贴图（无物品注册、未接线）


promaid 1.5.283：崩溃修复——女仆环境逃生找水时 `ClassNotFoundException: SelfPreservationBehavior$BlockCheck`(日志实证 02:03:46 女仆"汉服酒狐"手持冰火传说龙钢火斧进自保 → 着火逃生 findWater → 崩)。根因：验证脚本 verify_jar_classes.py 旧版只比对 6 个指定类，`SelfPreservationBehavior` 及其内部类从未被验证 → 某个历史构建的 jar 缺 `$BlockCheck.class` 却一路"ALL OK"部署(Java 对内部类懒加载,启动不报错,女仆第一次走"找水"路径才炸)。修复：verify_jar_classes.py 重写为【双向全量】(遍历 out 全部 221 个 .class 必须存在于 jar 且哈希一致 + jar 无多余条目 + mixin 齐全),并内联进 build_promaid.py(构建后自动自检,不通过即 FATAL);本次重建的 jar 双向验证全部通过后重新部署


promaid 1.5.282：剑单敌跳劈 70% → 100%——单敌时攻击冷却满必跳劈（空中暴击 1.5 倍）；跳劈冷却 2 秒空档由 TLM 攻击行为自动打普攻/横扫（战术只接管移动+跳劈，攻击一直由 TLM 原版 MaidMeleeAttack 执行——"普攻/横扫从哪儿来"的答案），所以跳劈 100% 不挤掉横扫；多敌仍完全不跳（群怪尽横扫），4.5~6.5 格大跳仍受 10 秒 CD 约束


promaid 1.5.281：蜂蜜瓶解毒修复 + 女仆自喝牛奶——根因：v1.5.202 轻量化后自保是常驻检查器，但蜂蜜瓶检查在【自保会话主流程内】，满血中毒（血未低到触发自保、无环境危险）时会话不激活 → 提前 return → 蜂蜜瓶永远不会喝。修复：蜂蜜解毒 + 牛奶清负面提升为【会话外也生效】的常驻轻量逻辑（每 tick 只查效果状态，有负面效果才扫背包）——被洞穴蜘蛛/毒马铃薯/药水箭中毒满血也会立刻喝蜂蜜瓶（精准解毒+回血+留玻璃瓶，只清中毒不误伤增益）；蜂蜜瓶之后，若自身【无任何增益效果】且【有负面效果】才喝牛奶（牛奶清全部效果——有增益时喝会把增益也清掉，所以前提是"只有负面 buff"；喝 1 桶清全部效果并返还空桶，原版一致；饥饿/虚弱/凋零/失明等都能解；蜂蜜与牛奶各自独立冷却）


promaid 1.5.280：近战贴脸后退——近战走位新增【贴脸后退】：敌人贴进 2 格内（= 僵尸/骷髅的近战攻击范围）主动后退拉开，退到 3 格即停——女仆手长（攻击距离 3.1）退开后照样挥砍（"女仆的手很长，完全打得到"），同时脱离敌人近战范围不再白挨刀；与打一刀退一步（攻击瞬间短撤 14 tick）互补、与跳劈衔接（贴脸不跳劈，后退拉开后再蛇形接近/跳劈——斧的"跳劈→普攻→后退"节奏对剑同样成立）；非自保天然满足（自保移动保命动作时战术行为已让位）；面板「战斗自保 → 单兵战术」新增【近战贴脸后退】开关（默认开，可关）


promaid 1.5.279：区块打标签 + 替代品多维度划分——(1) 女仆管理页区块列表【打标签】：每区块竖排两行（名字/维度/状态 + 「创建于 x, y, z」——玩家创建区块时的原点坐标；上限 6 区块防撑爆，女仆按钮自动下移避让）；(2) 替代方块多维度划分：新增两个自动判定维度——【材质族】（木/石/砖/矿/璃/陶/毛/他）与【功能】（红石/照明/存储/炉/装饰/结构），替代品面板条目显示「族·功能」标记（如「木·结构」橡木木板）；匹配升级：高度/碰撞严格匹配之上，表内【同族优先】（第一轮只扫同材质族，第二轮才扫其余）


promaid 1.5.278：手册进度显示三修——(1) 速度/预计【无条件显示】：旧版缺料停滞时速度统计 ema→0 → 进度条旁只剩"56%"（日志实证 speed=0.0 eta=-1）；现在停滞时如实显示"0.0块/秒 · 预计--"（-- = 缺料/统计窗口内无法估计），有速度时正常显示实测值；(2) 材料表"已建"按【蓝图隔离】：旧版把所有计划的已建数混在一起（石质山景别墅 + 现代红石智能住宅共用石头/木板 → 其他计划已建把本蓝图剩余需求扣成 0）→ 材料表只剩 3 种未污染的（截图：圆石墙/蓝色床/火把）；现在只扣本蓝图计划的已建，17 种材料完整显示；(3) "共 X 块"用去重后计数（旧版原始 steps.size() 含同坐标重复步骤 → "共2138块" vs 进度"2024块"不一致，截图实证 114 块差额）


promaid 1.5.277：斧特殊战术 + 图腾特效——(1) 主手为斧时（AxeItem）战斗节奏改为"每一击都尝试跳劈 → 普攻 → 后退"：跳劈概率 70% → 100%（无视单/群怪分流，旧版群怪全禁是剑的横扫节奏，斧无横扫之刃、跳劈是唯一高伤手段；4.5 格内必跳、更远需大跳受 10 秒 CD 约束先走近再跳；跳劈落地后 TLM 普攻、攻击完成自动后撤 14 tick 拉开，节奏闭环）；(2) 女仆饰品栏不死图腾触发补上原版同款特效（levelEvent 35 绿色粒子 + 音效——旧版只有复活和气泡、无特效，主人分支一直有播 35，女仆分支漏了）


promaid 1.5.276：替代品"搭一个又掉一个"根治——根源：替代品放置后被"已建判定"（主循环/延后轮询/缺口扫描三处）不认可 → 拆掉重放 → 拆→掉→捡回背包→再放→再拆 循环（掉落物回收只治标，捡回背包的删不掉）。修复：放置成功时记录该步骤的替代品（altUsed 表），三处已建判定认可"目标格=该替代品"即视为完成 → 不再拆、不再掉、不再翻倍；放置后同 id 掉落物回收降级为兜底（仅清理拆障残留/悬空废料）


promaid 1.5.275：批量优化——(1) 右击手册卡顿：25 个内置蓝图生成结果静态缓存（旧版每次打开重新生成 600~2100 步 ×25）；(2) 缺料播报 30 秒冷却（旧版 blockId 轮流缺就每 5~11 秒刷屏，日志实证）+ 有替代品时不报缺料、改报"缺 X，我用 Y 替代一下"（30 秒冷却）；(3) 放置后回收目标格附近同 id 掉落物（替代品放置 + 同类掉落并存 = 材料"翻倍"观感；276 起为兜底）；(4) 钓鱼一步一停再根治：mixin 记录原版 FindSit 椅子目标 + 每 3 tick 高频补寻路（TLM 站立等行为清 WALK_TARGET 后 3 tick 内补回，绕过 12 tick 间隙）；(5) 替代品分类扩展：两格再分【竖两格（门/高植物/甘蔗/竹子）】与【横两格（床）】，新增【无碰撞方块】单独区（花/火把/地毯等），面板 5 个分类标签 + 执行层严格匹配；(6) 替代品面板加【女仆管理】跳转按钮（新 OpenBookRequestPacket 网络包 → 服务端重新下发手册并直接打开女仆管理页）


promaid 1.5.274：钓鱼找椅子完全套用原版——删除自研 findNearbySeat（16/32 格实体扫描）+ PENDING_SEAT/SEAT_GIVEUP 走向机制：原版 FindSit 已用可见实体内存（NEAREST_VISIBLE_LIVING_ENTITIES）找椅子并设置 walk target（找到 → sitEntity 非空 → 完全走原版流畅流程，promaid 零干预）；原版没找到（不可见/被占）→ promaid 只做 7 秒宽限（原版传感器刷新窗口）→ 生成坐垫。彻底消除"原版没找到但 promaid 扫到 → 接管走向 → 每 12 tick 重设 walk target → 一步一停"的分歧根源


promaid 1.5.273：钓鱼一步一停根治（日志实证：25 格走了 18 秒——findNearbySeat 每 0.6 秒被调用，tryAutoChair 每 12 tick 无条件重设 walk target + 重写 PENDING_SEAT → 寻路每 0.6 秒重算一次）——已在走向目标（PENDING 远水坐垫 / PENDING_SEAT 现成椅子）时 tryAutoChair 直接跳过，走向由 sweep 推进（272 起仅在无 walk target 时补）——不再打断寻路；顺带修复 PENDING_SEAT 超时计时被反复重置（60 秒超时永不触发）的问题；scanGaps 全表扫描限频 60 秒（55 万步 O(N) 每 20 秒一次 tick 尖峰）+ 缺口统计日志（"建造永不完成"直接看日志定位缺什么）


promaid 1.5.272：钓鱼移动"一走一卡"修复（实测："移动方式又变成一走一卡，恢复成跟空闲状态一样"）——根因：FindSit 每 12 tick 设置 walk target，promaid sweep 每秒无条件重设 → 两者交替重置 → 寻路频繁重算 → 一走一卡。修复：补寻路只在【女仆当前没有 walk target】时进行（MemoryModuleType.f_26370_ 为空）——FindSit 已设目标时由 MoveToTargetSink 持续走（流畅），目标被清/被打断才补（PENDING 远水坐垫 + PENDING_SEAT 现成椅子两处同改）


promaid 1.5.271：收官内置蓝图——新增 15 个生存简易小屋（各 636 块，橡木/圆石/石头等易得材料、种类少，标配门/火把/熔炉/工作台/床/箱子/玻璃窗，带烟囱与三角屋顶：橡木原木/圆石/石砖/砂岩/雪原/桦木/云杉/丛林/红砖/山岩/渔屋/矿工/猎人/花园/农舍）+ 10 个进阶别墅（各 2124~2138 块，3 层 + 三角屋顶 + 塔楼 + 花园围墙 + 后院露台 + 阳台，木质/石质/砖质/现代玻璃/陶瓦等风格）——全部程序化生成（BuiltinHouses），支撑自洽可正常建造


promaid 1.5.270：手册蓝图大清理——(1) 外部蓝图（config/maid_smart/blueprints）：43 个 <2000 块的已从文件层面删除（含甘蔗农场 727/花园藤架 259/锁链吊桥 319/小神社×3/森林系列/苔藓小建筑等，备份在 analysis/blueprints_deleted_backup，需要可恢复）；(2) jar 内置预制 19 个 <2000 的删除（installBuiltinBlueprints 不再复制——否则下次启动又回来）；(3) 内置 5 个示例（小木屋/凉亭/喷泉/瞭望塔/水井 22~100 块）从代码注册移除（BUILT_IN_NAMES/getBuiltIn/buildCatalog）；(4) 手册只剩 2 千 ~ 673 万块级的大蓝图；(5) 顺带清掉 RR/SS/aaa/qqq/www 测试蓝图


promaid 1.5.269：钓鱼找椅时间延长（实测："钓鱼正常了，但找椅时间太短，有时还是会自己做坐垫"）——生成坐垫前加 7 秒（140 tick）找椅宽限：当轮 32 格扫描没找到椅子时不立即生成坐垫，每 0.6 秒 FindSit 触发都重新扫描，7 秒内找到椅子就走过去坐，7 秒后仍没有才生成坐垫（覆盖"椅子刚放置/实体列表未收录/女仆移动中"的时机差）


promaid 1.5.268：智能科技住宅"大量方块放不了"根除——日志实证：藤蔓（vine）大量 place-fail/skip（"目标现=air"）——藤蔓/发光地衣是贴墙方块，supportDirection 漏网（返回 null）→ 墙未建时直接放 → canSurvive 失败掉落 → 3 次失败永久跳过 → **墙建好后也不补** → 完成时报"悬空放不上"。修复：supportDirection 识别藤蔓/发光地衣（多方向布尔属性取贴面方向）+ 可可豆/仙人掌（贴丛林木/需沙地）→ 墙缺失时延后等墙建好再补建（悬空无墙时补石头/沙）；钓鱼椅子被占用提示（实测：自己坐在椅子上时女仆"找不到"——乘=1 被排除，现在气泡告知原因）


promaid 1.5.267：钓鱼"玩家放的椅子识别不到"修复（实测："只认自动召唤的垫子；玩家放的坐垫位置稍刁钻就识别不到，其他种类都不坐"）——(1) 找椅子范围 16 → 32 格（椅子在水边 17+ 格外时旧版扫不到 → 直接生成坐垫）；(2) 支持 TLM "座位"（EntitySit，LLM"坐下"工具实体）；(3) 椅子走向超时 30 → 60 秒，超时后记"这只椅子走不到"30 秒内转生成坐垫钓鱼（防死磕卡死），之后重新尝试；(4) 坐垫删除原因日志（sweep：女仆不在/任务非钓鱼/脱离超时）定位"生成坐垫被删循环"；(5) removeOldChairs 不删正被骑着的坐垫（防误删致女仆掉下来）


promaid 1.5.266：工头"没设置时随机设一个"真正落地——根因：v1.5.182 的补选只在建造行为 start 时跑一次，创建区块时女仆还没绑定、远程绑定 scanAreaMaids 扫不到 → chooseForeman 返回空 → foremanUuid 恒空 → isForeman 全员放行 = "所有人都能发"（完成汇报/跳过/缺料全刷屏）。修复：isForeman 遇到无工头/工头失效时【当场随机挑一只绑定该区块的活跃建造女仆顶上并持久化】再判断（服务端单线程顺序执行无竞态）；随机失败（真没人）才放行兜底防静默；工头被手动暂停仍放行（面板操作语义）


promaid 1.5.265：三修复——(1) 钓鱼"旁边有椅子/坐垫不坐"：找椅子范围 8→16 格（对齐 TLM 原版传感器半径）+ 新增 PENDING_SEAT 持续走向机制（现成椅子 2~16 格时每秒补寻路直到骑上，30 秒走不到才放弃提示，不再直接生成自己的坐垫）；(2) olymp-final 95 万方块图延后 10324 永不收敛：蓝图支撑格已加载但 10 次重试仍未建成（悬空依赖链）→ 超时跳过收敛，建造能正常完成（缺口重下达补建）；(3) 工头名存实亡：skip 气泡/缺料播报统一只由工头发（有有效工头时非工头静默——完成汇报早已过滤，这次补全跳过错报/缺料提示）


promaid 1.5.263：悬浮红石放不上修复（实测日志：甘蔗农场红石线 y=4 层 3 次失败永久跳过）——supportBlockFor 补支撑用蓝图主要建材（甘蔗农场=甘蔗）→ 红石线补在甘蔗上 canSurvive 失败 → 红石科技件（红石线/中继器/铁轨/压板/活板门/拉杆/按钮/火把等）固定补石头支撑；另修空气清除步骤的 canSurvive 误判（air 清除红石线报"目标仍空气"）


promaid 1.5.262：替代品面板按类别过滤显示（玩家建议："应该直接不显示"）——半格表只显示台阶类、一格表只显示整方块、两格表只显示两格高，类别不匹配的方块根本不出现（无需点击再拒绝；矿表面板保持全物品）+ 确认创造面板动态兼容模组物品（遍历 ForgeRegistries.ITEMS 全注册表，1 分钟缓存）


promaid 1.5.261：替代品高度类别严格匹配（需求："1 格只能用 1 格替换，半格/两格同理"）——面板添加/点击时校验类别（半格表只收台阶、一格表只收整方块、两格表只收门/植物/床/甘蔗/竹子，不匹配拒绝并提示）+ 执行层双保险（旧配置/误添加的错配条目跳过，防半格位置被一格方块顶坏建筑）


promaid 1.5.260：m_60815_ 全套误用修复（反编译实证：0 参 m_60815_ 是 isSolid 有碰撞、不是 isLiquid！日志统计对比验证）——建造支撑判定 5 处（已建实心支撑格被误判"空"→ 疯狂补沙子循环 + 红石线支撑被换掉放不上，实测甘蔗农场）+ 钓鱼 findBank 岸边判定 + 自保倒水判定 + PlayerWaterLog 水判定——统一改 getFluidState().is(FluidTags.WATER) 或语义修正


promaid 1.5.258：钓鱼大扫描卡死修复（256 实测卡死——日志实证 "Can't keep up! Running 7404ms"：单次 160 万次逐格检查 + 30 秒一次 → 服务端主线程冻结 7 秒）——大扫描改多阶段递减（A1 同层 dy=0 半径 64 ≈ 1.6 万次 5ms / A2 ±3 ≈ 11 万次 35ms / B 全垂直 -16..+32 半径 40 ≈ 29 万次 100ms 限频 30 秒）


promaid 1.5.257：钓鱼问题诊断增强（玩家提议"把玩家行为也写日志"）——玩家放置/收走水的位置日志（latest.log 搜 "player water"）+ 大扫描失败日志带判定链路统计（液体 N 格/上方空气 N/合法岸边 N——卡在哪一步一目了然）


promaid 1.5.256：钓鱼坐垫大范围找水再扩大（实测：超平坦世界水池在女仆 55 格外、高 46 层——255 的 40 格/±40 层够不到）——半径 64、垂直 -32..+64（模拟验证可找到）；找不到水/走不到水边时气泡提示（旧版只有日志——玩家体感"毫无反应"）


promaid 1.5.255：钓鱼坐垫"又放不出"修复（日志实证：女仆附近 12 格内无水 → 一直不放坐垫）——12 格内找不到水域时大范围扫描（半径 40、垂直 -24..+40，30 秒限频）最近岸边，生成标记坐垫并让女仆自动走向水边坐下（sweep 每秒推进寻路，2.5 格内上骑，60 秒超时删垫重试，任务解除自动清理）


promaid 1.5.254：缺料自动替代（先同族后自定义，按高度分类的半格/一格/两格三张表，开关在详细设置-建造栏，自定义面板同挖矿矿物/障碍物同款创造面板）+ 修复状态串从未生效（NbtUtils 无 Name 返回空气——台阶上下半/楼梯朝向/门开合补 Name 后真正恢复）+ 台阶/楼梯等价族扩充


promaid 1.5.253：世界提取"中间镂空"根因修复——1.12 老格式完整注册表映射（旧版仅 1.8 子集：天安门 1.12 地图丢 125/126 木台阶 5470 块、96 活板门 511、136 丛林楼梯 312、139 石墙 156、235-250 釉陶瓦/混凝土 1900+；159 染色陶瓦 14930 块被映射成 1.20.1 不存在的 stained_hardened_clay → 解析为空气 → 整面红墙当"清除步骤"不建 → 城楼中间完全镂空）+ 读取元数据（羊毛/陶瓦 16 色、台阶上下半、楼梯朝向、门/活板门开合全恢复）+ 状态串改规范 SNBT（doPlace 保证可解析，旧 "{prop=val}" 能否解析不确定）


promaid 1.5.252af：支撑格在蓝图内且未建 → 延后等蓝图支撑（甘蔗泥巴缺料不再跳过）+ 支撑格区块未加载不再 setBlock 静默失败（补支撑后仍空的根因）+ 已建但不合法支撑走补支撑


promaid 1.5.252ae：控制按钮只在区块内显示（旧版区块外也显示 → 点击误报"区块不存在"，实测）+ 区块外操作提示更准确


promaid 1.5.252ad：悬空误判根治（放置后目标已是目标方块即算成功——旧版 canSurvive 对已放置的玻璃/树叶/活塞等误判失败致 130 个跳过）+ 名单空提示残留修复 + 删除/打开手册诊断日志


promaid 1.5.252ac：预计完成时间修正——剩余 = 总 − 已放 − 永久跳过，除以速度（已放的不算；跳过的不会再放也不计入剩余）


promaid 1.5.252ab：红石机器建好不运行修复（红石激活提前到缺口检查之前——scanGaps 发现缺口就 return 导致 recalcRedstone 永不执行）


promaid 1.5.252aa：极速模式放置冷却归零（13 女仆从 25 块/秒提到服务器极限）+ 补支撑默认方块改蓝图主要建材（不再无中生有出石头）


promaid 1.5.252z：打开手册立即显示速度/预计时间（OpenBlueprintBookPacket 带 etaSec/speedBps，不再等 2 秒轮询）+ 进度条标签上移不再压条 + 材料表已建完条目不显示 0/0


promaid 1.5.252y：进度条百分比/速度/预计标签上移不再压进度条（截图实证重叠）+ 材料表剩余需求≤0（已建完）条目不再显示 0/0


promaid 1.5.252x：拟人化排序加骨架优先（结构类内部先搭水平外轮廓：墙圈/四角柱/屋顶边缘先立，再逐层填充内部——排序键 prio→骨架→y→x→z）


promaid 1.5.252w：材料表排除空气等不可获取方块（0/0 消失）+ 手册大目录不再显示建造进度/进度条 + 悬空跳过实锤日志（build skip/build place-fail）


promaid 1.5.252v：坐垫实心判定改碰撞箱（花/草/火把不再误当地面）+ 坐垫下方位块与手册速度/ETA 双向诊断日志


promaid 1.5.252u：建造进度字段分行显示（状态文本


 分行：进度/缺料/参与女仆各行独立右对齐，进度条随行数自适应下移）


promaid 1.5.252t：进度条上方字段出屏修复（右对齐改左锚点绘制——旧版以 x-lw 为圆心右缘偏左悬空；换行截断补省略号；蓝图名截断）


promaid 1.5.252s：坐垫贴地修复（岸格改找最上面实心块，不再悬浮/放进水里）+ 手册进度条旁显示 块/秒速度与预计完成时间（防出屏钳制）+ HUD 广播限频验证日志


promaid 1.5.252r：修复进世界第一 tick 即崩（普通代码直接引用 mixin 类被 JVM 判 invalid；钓鱼坐垫逻辑全部迁出到普通类 FishingChairService，mixin 仅留注入壳）


promaid 1.5.252q：钓鱼坐垫重做（有坐垫直接坐/无则在最近岸边生成带标记坐垫并直接坐上/任务解除或脱离坐垫自动删除）+ 建造 HUD 速度/剩余时间修复（多行折行显示、速度显示小数、总块数去头部行）+ 清理 5 处调试日志刷屏 + 建造瞬移拦截恒 false 比较修复（自动坐垫被摧毁无掉落）


promaid 1.5.252p：创造模式材料显示 -21 亿修复（MAX_VALUE 溢出短路）+ 拟人化排序扩充装饰分类（灯笼/书架/陶罐/蜡烛/地衣等）


promaid 1.5.252o：修复爱憎分明饥饿开关失效（2.0.2 作者包名 tartaricacid→JumDa5he,mixin 从未加载）


promaid 1.5.252n：参与女仆数改为绑定数口径（手动绑定/全员加入计数一致,不再按区块附近统计）


promaid 1.5.252m：悬空补支撑不再把树叶换成石头（树冠装饰恢复跳过，保护树冠外观）


promaid 1.5.252l：世界存档提取不再把石头/砂岩等建材当地形过滤（天安门 32 块根因修复）


promaid 1.5.252k：删除红石智能住宅·挖空版蓝图（挖空设计致附着物悬空、补支撑卡死下部设施,仅保留原版）


promaid 1.5.252j：建造 HUD——左上角实时显示建造速度与预计完成时间（防错位防出屏）


promaid 1.5.252i：钓鱼坐垫岸边判定修复（水面高 1 格才找岸边）+ 世界存档导入兼容带顶层目录的 zip + 悬空方块强制补支撑（不再大量跳过）


promaid 1.5.252g15：治疗链只看血量（环境优先由分支顺序保证）


promaid 1.5.252g14：治疗链跳过条件改为"正处于危险情境中"（不再看效果残留）


promaid 1.5.252g13：情境药水改用【效果判定】做正门（按规则重做）


promaid 1.5.252g12：投掷间隔按女仆记 + 主人有效果不投 + 情境中跳过治疗


promaid 1.5.252g11：投喂优先级重排——情境保命（着火/溺水）优先于低血治疗


promaid 1.5.252g10：蜂蜜瓶归为药物（只在中毒喝，不当食物）


promaid 1.5.252g9：CD key 改为【效果种类】（变体不再各算一种）+ 投喂/自喝日志


promaid 1.5.252g8：消灭药水浪费（饮用优先 / 喷溅优先 / 跳跃归情境）


promaid 1.5.252g7：药水 CD 改为【按药水种类】记（异种互不阻塞）


promaid 1.5.252g6：塞给主人的物品优先放快捷栏空位


promaid 1.5.252g5：抗火药水也归为情境药水（不通用乱喝）


promaid 1.5.252g4：投喂药水形态修正 + 情境药水不乱喝


promaid 1.5.252g2：女仆投喂主人的药水补全（水肺/增益）


promaid 1.5.252g：情境药水系统（溺水喝水肺 / 中毒喝蜂蜜 / 增益 CD=效果时长）


promaid 1.5.252f：工作卡住播报降频（20 秒才说一次）


promaid 1.5.252e：去掉冶炼，回归纯烹饪


promaid 1.5.252d：回退复杂化，回归简洁


promaid 1.5.252c：烹饪/酿造按女仆绑定调控 + 炉子/酿造台独占（v1.5.252d


promaid 1.5.252b：手册新增"烹饪和冶炼"/"酿造"调控两页


promaid 1.5.252：烹饪和冶炼重写（烟熏/高炉/熔炉 + 燃料修正 + 坐下绑定）+ 酿造台坐下 + 灵魂核心移除补全


promaid 1.5.251e：灵魂核心功能移除 + 手册附魔光效验证


promaid 1.5.251d：灵魂核心键位冲突修复 + 附魔光效 + 手册改蓝色


promaid 1.5.251c：同存档双分身防护（一魂一仆）


promaid 1.5.251b：记忆开关加固（磁盘优先 + 未加载女仆可关）


promaid 1.5.251：物品纹理换色 + 记忆世界/时间标注 + 灵魂共享存储（双向同步/克隆继承）


promaid 1.5.250：三处 SRG 错用根因修复（放水 / 记忆开关 / 记忆目录）


promaid 1.5.249：记忆开关"关了又开"根因修复 + 放水双向诊断


promaid 1.5.248：农场"走一步停一步"修复（批量种植/连锁收割性能优化）


promaid 1.5.247：修正"包里什么都没有"误导文本


promaid 1.5.246：放水失败定位诊断（有水桶却放不出水的排查版）


promaid 1.5.245：自保会话进出防抖 + 高血量着火不喊救命（日志审查修正）


promaid 1.5.244：WorldProbe 启动预热加载（"让女仆帮忙填坑"报错修复）


promaid 1.5.243：着火链路补末影珍珠逃生（"跳入水坑和用末影珍珠都没有了"）


promaid 1.5.242：记忆开关"关了一会儿又变回开"修复（显示 + 持久化双管）


promaid 1.5.241：逃跑避让岩浆 + 搭高防跌落修复（getValue 全链替换为注册名比较）


promaid 1.5.240：修复"有水桶却说没招了"——hasWaterBucket 改 getKey + 放水链


promaid 1.5.239：修复启动崩溃（真正根因）——追踪药水 mixin 目标类改声明者


promaid 1.5.238：修复启动崩溃——追踪药水 mixin 方法名漏写 SRG 名


promaid 1.5.237：末影珍珠逃生改原版投掷特效（紫色轨迹 + 落地传送爆炸）


promaid 1.5.236：岩浆误报"卡住"修复 + 农场批量种植（与连锁收割同设置格式）


promaid 1.5.235：自保播报精简（不聒噪）——只留危险/求救，删光动作汇报


promaid 1.5.234：阶梯判定思想推广——环境逃生补全阶梯 + 回血资源阶梯化


promaid 1.5.233：判定阶梯（岩浆 300 > 着火 280 > 自保 250）+ 链路全失败让位恢复


promaid 1.5.232：岩浆/着火链路按玩家规范重写 + 避让降级为仅移动 + 全失败"我没招了"播报


promaid 1.5.231d：语言检测改自动重刷（直到中文才放行）+ 背包满明确"我给不了东西"


promaid 1.5.231：着火不逃窜（朝主人走）/ 追踪药水弹 / 死亡传送全维度 / 女仆烧死兜底 / 记忆防注入污染


promaid 1.5.230：放水判定补强（岩浆放水 fallback + 着火多位置）


promaid 1.5.229：岩浆判定全面优化（感知半径/逃生顺序/抗火豁免/地狱特判）


promaid 1.5.228：投喂/火把/盾牌从出生起就没运行过的根因修复（canStillUse）+ 死亡传送落点贴地 + 日文根治


promaid 1.5.227：主人死亡传送安全落点（女仆"消失"）+ 系统消息刷屏根治 + 记忆开关显示修 + 投喂/火把运行时诊断


promaid 1.5.226：AI 记忆开关真正生效（事件源守卫 + 客户端状态同步）


promaid 1.5.225：修复编译链（5 个编译错误）+ 锄头黑名单 + 抗火豁免补全 + Brain 诊断


promaid 1.5.224：手册"导入世界地图"按钮——玩家自助提取，无需手动放文件


promaid 1.5.223：世界存档 ZIP 提取（"Tiananmen Palace.zip" 实测打通）


promaid 1.5.222：构建链上限放开到 50 万 + ZIP 打包蓝图解析


promaid 1.5.221：区块材料表单（0/88 页）公式修正——已建 + 女仆背包 + 玩家背包


promaid 1.5.220：手册"导入建筑"按钮（文件选择器 + 版本警告确认 + 服务端解析校验注册）


promaid 1.5.219：链路调试页按钮重叠修复（刷新 vs 重要度 / 页码 vs 标记删除恢复）


promaid 1.5.218：建造悬空方块根治（门/活板门/中继器支撑判错）+ 材料缺口公式修正（已搭建+主人+女仆）


promaid 1.5.217：图腾触发系统消息 + 盾牌诊断日志 + 语音包按钮反馈 + 玩家伤害模式 4 + 音量注释


promaid 1.5.216：岩浆脱困五项修正（walk-out 时限/抗火豁免/逃跑防抖/范围云/卡墙误报）


promaid 1.5.215：岩浆水桶时序修复 + TTS 音量 mixin FATAL 修复 + 投喂/火把诊断日志


promaid 1.5.214：修复进入世界卡死（Server thread 自锁）——农场扫描/自保扫描加区块加载检查


promaid 1.5.213：坐下受伤立刻站起来（锁移动判定保留，仅加受伤起身）


promaid 1.5.212：删除施工区避让行为 + 坐下锁移动根治（每 tick 判定）


promaid 1.5.211：着火分支尾部"贴脸反击"兜底（逃出岩浆带火的 15 秒窗口收口）


promaid 1.5.210：着火分支"抗火/找水"位置互换


promaid 1.5.209：岩浆逃生分支重构——walk-out 加时限 + 主分支 2 逃生链路重排 + 岩浆内/烧伤职责分离


promaid 1.5.207：玩家对女仆伤害策略（面板可调——TLM 原版压制 / 完全免疫 / 无限制 / 有上限）


promaid 1.5.206：投喂/治疗策略全面化——状态感知优先级链，不再只投喂单一药水/食物


promaid 1.5.205：对话语言根治（TLM 聊天 GUI 也强制中文）+ 治疗药水判定修复 + 图腾扣减修正


promaid 1.5.204：不死图腾真实消耗 + 岩浆感知/逃生大修（播报/无水自救/避让）+ 放水 3 秒回收 + 建造两步流程解锁


promaid 1.5.203：搭高安全高度配置化（与落地水触发高度配对）


promaid 1.5.202：轻量级自保（落地水格式）——边打边保命，不再与战斗冲突


promaid 1.5.201：喂食按饱和度恢复量排序 + 金苹果改即时增益道具（不再当常规食物）


promaid 1.5.200：自保退出稳定期（防 70% 上下浮动频繁进出）


promaid 1.5.199：自保逻辑链修复（搭高积极性/传送保命/围殴喝药）+ 水桶垫水 + 岩浆逃生 + 环境提示 + 爱憎分明饥饿开关


promaid 1.5.198：TTS 音量倍率 + 系统消息朗读（训练一次缓存复用）+ 系统语音包导入 + 记忆 API 绑定 + 对话语言强制


promaid 1.5.197：自绘蓝图大小无限制 + 只入册不自动动工（防止拆周围）


promaid 1.5.196：LLM 查询-行动闭环 + 感知查询工具 + 工作清单 + 画蓝图防超时（移植 PatchouliAI）


promaid 1.5.195：威胁判定扩大——所有携带仇恨的生物（含中立反咬/主人被攻击）+ 文案"敌人"


promaid 1.5.194：搭方块中途窒息封头（替换"搭完后糊脸"）+ 所有系统对话青色


promaid 1.5.193：女仆说话的时宜——敌袭状态冻结情绪价值/主动对话 + 系统提示特殊颜色


promaid 1.5.192：Promaid 手册——女仆工作链路调试面板 + 改名


promaid 1.5.191：女仆大脑升级——7 阶段主动对话 + 配额改革 + 记忆维护周期 + 回复反馈学习


promaid 1.5.190：AI 双手工具 + 记忆系统修复 + 面板修复（全链路排查）


promaid 1.5.189：全能管家扩展——农业畜牧运维 + 玩家贴身辅助 + 环境交互


promaid 1.5.187b/188：死循环修复 + 区块范围精确化 + 建造确认流程 + 战斗调整


promaid 1.5.187：删掉站桩围壁，改为"拿方块糊怪物的脸"


promaid 1.5.186：自保搭高统一——不再按敌人近战/远程划分


promaid 1.5.185：大跳劈距离修复——触发距离与实际可达距离一致


promaid 1.5.184：跳劈重构——普通跳劈 = 玩家同款垂直跳 + 玩家式暴击结算


promaid 1.5.183：修复绑定失败 + 女仆管理显示问题


promaid 1.5.182：女仆管理 UI 重构（详情页绑定 + 区块名单/工头）


promaid 1.5.182：普通跳劈 = 玩家跳跃高度 + 修复"莫名其妙跳一下"（跳劈动作同步）


promaid 1.5.181：大跳劈降低高度（不触发落地水）+ 大跳 10 秒 CD


promaid 1.5.180：多区块共存 + 无女仆创建 + 女仆-区块显式绑定（架构级重构）


promaid 1.5.179：缺料数字改为实时计算（需求 − 已建 − 绑定女仆/主人背包）


promaid 1.5.178：区块内控制限制 + 女仆管理升级（区块列表/命名/绑定解绑）


promaid 1.5.177：暂停 = 解除绑定——暂停后女仆可以停下来去干别的事


promaid 1.5.176：修复游戏加载世界时崩溃（MixinTransformerError 致命错误）


promaid 2.0：建造系统彻底重构（单计划状态机 + 统一已建感知 + 区块内右击进详情）


promaid 1.5.175：跳劈斩改抛物线大跳（玩家指定"那种抛物线曲线"）


promaid 1.5.174：跳劈触发重构（距离触发 + 人数分流）+ 取消"平A"概念


promaid 1.5.173：建造不启动根因修复（TLM 时间表 activity≠WORK → 任务行为不被评估）


promaid 1.5.172：连锁采集改为同时破坏（FTB Ultimine 式）+ 挖矿换镐改目标驱动


promaid 1.5.171：控制按钮不再依赖玩家位置（修复"继续建造按钮消失"）+ canUse 诊断 + 清理残留存档


promaid 1.5.170：建造死锁修复（被拒时看不到计划位置→无法取消）+ 恢复诊断日志


promaid 1.5.169：手中正在使用的工具/武器/盾牌不触发切换，除非即将用坏自动换下一个


promaid 1.5.168：附魔装备黑名单保护（即将用坏）+ 评分翻转（附魔>耐久）+ 镐切换挖掘等级对标


promaid 1.5.167：武器切换优先级重写（DPS>耐久>附魔）+ 三叉戟自保确认覆盖 + 记忆调试面板


promaid 1.5.166：弓/弩自保失效修复（StartAttacking 断源）+ 御币武器切换修复 + 自保诊断日志


promaid 1.5.165：血机制解除自保后第一件事 = 定位主人并传送过去


promaid 1.5.164：建造区块红色固定框 + 自保退出传送 + 跳劈连贯性 + 挖矿诊断日志


promaid 1.5.163：连锁采集 / 连锁收获的范围可自定义


promaid 1.5.162：建造区块标记机制 + 控制按钮按区块显示 + 去掉强制建造/强制续建


promaid 1.5.161：进阶采集——挖矿连锁采集/自动收集 + 农场连锁收获/自动收集（默认关闭）


promaid 1.5.160：详情页控制按钮随"有无进行中计划"动态显示


promaid 1.5.159：手册建造面板 UI 重做 + 区块显示（建造范围预览）


promaid 1.5.158：自保提示矛盾消除——威胁播报/退出播报区分 + 传送免触发窗口


promaid 1.5.157：搭高不再扔珍珠 / 传送重试封顶 5 秒（旧配置 60 秒不跟随修复）/ 自保解除播报


promaid 1.5.156：建造"瞬间完成"根因修复——建材等价不再算"已建"


promaid 1.5.155：跳劈全程禁横扫 / 挥砍延迟 / 跳劈斩弧线加高 / 跳劈不后撤


promaid 1.5.154b：跳劈斩改为"跳向"敌人（抛物线弧线，非贴地冲刺）


promaid 1.5.154：跳劈斩（30% 概率冲向敌人 + 大击退），其余普通垂直跳


promaid 1.5.153：单敌必跳劈 + 自保触发/取消机制重定义


promaid 1.5.152：跳劈/横扫按敌人数分流 + 自保全程实时传送判定


promaid 1.5.151：传送安全半径默认 4→5 格（防远程怪；魂符可兜底绝对安全）


promaid 1.5.150：自保传送只判定主人身边安全（撤销"自己身边无怪"拦截）


promaid 1.5.149：跳劈"只跳不打"根因修复——空中挥砍由行为亲自执行 + 明显暴击提示


promaid 1.5.148：半成品原点识别改为"世界方块种类 ∩ 蓝图"交集锚点（方案）


promaid 1.5.147：御币弹幕还击（遵循御币原版攻击方式）/ 取消后续建识别兜底


promaid 1.5.146：跳劈高度回调（0.8 ≈4 格 → 0.5 ≈1.7 格，和玩家差不多）


promaid 1.5.145：自保近身反击支持远程武器（弓/弩贴脸直接放箭）


promaid 1.5.144：生存模式续建修复（缺料步骤不再被截断丢弃）/ 缺料播报中文名


promaid 1.5.143：障碍播报带矿物名 / 自保近身回击 / 跳劈加高+暴击特效


promaid 1.5.142：进阶装备控制（副手盾牌 + 饰品栏图腾）/ 跨维度跟随 / 蛋糕可吃（整块回复7切片量）/ 建造强制坐下


promaid 1.5.141：平A横扫修复 / 后撤加强 / 蛇形走位 / 攻击词条过滤


promaid 1.5.140：挖矿拾取/中文播报 / 删除建造传送 / 搭建速度档位硬限 / 删除伐木 / 搭方块边缘保护 / 换武器词条匹配


promaid 1.5.139：挖矿耐久根治——软方块判定从"硬度"改为"镐正确工具"


promaid 1.5.138：挖矿耐久对齐伐木 / 自动换武器只换空手 / 伐木补强（砍树叶+垫高+中文名）/ 建造传送根治


promaid 1.5.137：修复两个 bug（虚空误判疯狂传送 / 手册按钮重叠）


promaid 1.5.136：战斗指挥工具（smart_attack）——AI 对话直接下令开打


promaid 1.5.135：自保盲区修复（自然伤害/无来源伤害/非 Monster 生物）+ 骷髅对策


promaid 1.5.134：单兵作战战术（PVP 式战斗）——删除战斗协同与整理任务


promaid 1.5.133：新增伐木任务（maid_smart:woodcut）——TLM 缺失的高需求任务


promaid 1.5.132：战斗任务增强——协同作战（共享目标 + 护主集火）


promaid 1.5.131：AI 聊天/记忆大脑链路全面核查 + 记忆可诊断性改进


promaid 1.5.130：产出型任务专项增强（农场连收连种 + 钓鱼主动找水域）


promaid 1.5.129：TLM 原生任务通用呆滞修复 + 干活不被打断（两个总开关可回滚）


promaid 1.5.128：输入框 StackOverflowError 根治（真正的 setTextColor 是 m_94202_）+ 崩溃日志确认旧 jar 缺内部类


promaid 1.5.127：挖矿"愣 2~3 秒"根治（直接导航 + 扫描重建提速）+ 整理任务大升级


promaid 1.5.126：主人防窒息保护 + 材料提示中文名 + 输入框根治（照搬原版搜索框）


promaid 1.5.125：创造模式"缺 21 亿材料"整数溢出根治（不建造链路之一）


promaid 1.5.124：建造根治（暂停改内存态）+ 行为不再 3 秒重启 + 挖矿节流减半 + 输入延迟提交


promaid 1.5.123：三 bug 根治（挖矿"一步一停"根因 / 配置面板输入 / 下达后不建造根因）


promaid 1.5.110：配置面板每项注释 + 全部文本"偏左超出屏幕"修复


promaid 1.5.109：上一版 4 个问题真正修复（换镐/搭高漂移/不停传送/安全区传送）


promaid 1.5.108：建造"没规划就提前传送"修复（根因：存档静默恢复旧计划）


promaid 1.5.107：挖矿按需换镐（不再无条件切最高级）+ 耐久核实


promaid 1.5.105：记忆系统 ×（心契誓约 + 爱憎分明）联动全修（优雅降级）


1.5.122：挖矿"完全不动"修复 + 面板输入修复 + 建造诊断日志


1.5.121：建造恢复 home 模式（回归修复）+ 挖矿移动容差 + 面板输入聚焦


1.5.120：感知播报"分时宜"——危险时停止不合时宜的非紧急感知


1.5.119：敌对感知显示降频（感知检测照常，仅显示频率降低）


1.5.118：挖矿移速对齐 TLM 伐木（0.6）+ 部署 116/117


1.5.117：建造侧传送从根源停用（玩家决策）+ 撤销强制 home 模式


1.5.116：挖矿"一走一停"修复 + 建造被埋判定消抖（诊断预备）


1.5.115：确认建造 → 施工区【一次性清场传送】（按玩家预期修订）


1.5.114：建造逻辑深度优化（A 正确性 + B 性能 + C 手感 + D 清理 全组实施）


1.5.113：挖矿机制深度优化（A 性能 + B 正确性 + C 手感 + D 清理 全组实施）


1.5.112：自保传送节奏 + 死亡传送重定位 + 换工具评分分离 + 手册注释全面重写


1.5.111：标题居中模板 + 挖矿漂移根治 + 建造传送修复（3 大项）


1.5.104：审查建议实施——确认弹窗 + 建造完成通知 + 优化


1.5.103：全模组审查修复（1 高 + 4 中）+ 优化


1.5.102h：修复 heartfelt SmartPromptMixin（与 102g 同源的第二个崩溃点）


1.5.102g：修复 PromaidPromptMixin 注入非法导致女仆对话崩服


1.5.102f：模组镐评分兜底（模组武器/附魔兼容确认）


1.5.102e：修复耐久扣错对象 + 方块秒破（旧版残留问题）


1.5.102d：矿表面板/搜索/障碍物预勾选 + 换镐升级 + 拖拽削弱 + 无矿挡路上报


1.5.102c：进度文字右对齐 + 报点中文名 + 石质方块开路


1.5.102b：修复建造中进度条/进度文本贴出屏幕底边


1.5.102：全模组数值面板化——所有剩余硬编码数值全部纳入配置面板


1.5.101f 末影珍珠逃生 CD 缩短 + 数值全部进配置面板


1.5.101e 珍稀掉落物捡不到提示（气泡 + 主人聊天栏双提示）


1.5.101d 珍稀标记矿物（掉落物回收改为"标记格"机制 + 手册第三个创造物品栏名单）


1.5.101c 挖矿掉落物隔墙回收 + 拾取优先级按价值（原版低价值 0.5）


1.5.101b 挖矿黑白名单升级（目标矿物 + 障碍物 双名单，创造面板 toggle + 输入添加恢复）


1.5.101 修复 AI 记忆开关崩溃（TaskData ByteTag 强转 CompoundTag）


1.5.100d 修复女仆莫名传送（施工区防接近传送无冷却反复拽）


1.5.100c 开路白名单批量扩容（原木等自然方块）+ 矿物扫描加速


1.5.100b 挖矿目标拖拽 + 矿表创造面板 + 记忆页全面增强 + 面板细节修正


1.5.100 配置面板重构（手册式跳转+翻页）+ 手册目录按钮调小


1.5.99c 挖矿挡路判定根因修复（SRG 映射错误）+ 自动装备全面激活


1.5.99b 感知播报全局限频 + Blocker 崩溃排查


1.5.99 配置面板 UI 重构（可滚动 + 加大行距 + 标签对齐）


1.5.98c 修复站桩女仆溺水 + 主人注视感知刷屏


1.5.98b 启动崩溃修复（换回原版 TLM 后 mixin apply 失败）


1.5.98 关系记忆适配层（模组特色联动：记忆系统软感知心契誓约×爱憎分明）


1.5.97 Soul 灵魂系统（跨存档/换女仆迁移记忆）


1.5.96 全子系统精准开关体系（防未来 agent 冲突）+ Soul 系统评估


1.5.95 女仆大脑全面加强（记忆 6 项增强 + maidsoulcore 4 个子系统借鉴）


1.5.94 AI 画蓝图（子 Agent 设计器）+ 蓝图手册删除


1.5.93 站桩锁死位移（治"坐着/站桩女仆诡异地动"）


1.5.92 建造期传送抖动根治（建仆强制 home 模式，从 TLM 源头断跟随）


1.5.91 自保传送加固（双判定 + 珍珠落点安全）


1.5.90 挖矿穿墙 + 任务工具自动装备（修复"主手有镐仍不挖挡路方块"）


1.5.25 落地水（被动生存技能）——女仆自动落地水


1.5.25 续建修复（生存分批建造闭环）——超大型建筑（上万块）背包放不下，


1.5.9（BALANCED 补丁）：修复"第 6 格误判"——爱憎分明原版逻辑把女仆背包第 6 格


1.5.25（当前）：搭方块/挖矿搭高/酿造全自动/整理增强（测试反馈）——


1.5.24：五项测试反馈修复 + 海景房蓝图 + 材料展示重构 + 6 种风格建筑——


1.5.23：自保"可见性"修复（测试反馈反思）——


1.5.22：挖矿回归修复（测试反馈）——


1.5.21：自保优化（保命道具/威胁感知/智能逃生）——


1.5.20：自保状态机重构（新逻辑）——


1.5.19：挖矿渐进化（对齐 maid_useful_task 伐木逻辑）——


1.5.18：蓝图卷轴移除 + 触发方式重述 + 手册材料缺口——


1.5.17：四问题修复（测试反馈）——


1.5.16：三入口建造闭环（非 LLM 全流程 + LLM 状态查询）——


1.5.15：预制精美建筑（5 套，首次启动自动安装）——


1.5.14：复杂精美建筑能力（迁移 numen BlueprintFormats）——


1.5.13：建造体验优化 + 蓝图获取生存化——


1.5.12：numen 式结构文件蓝图迁移——


1.5.11：不依赖 LLM 的建造触发方式 + 建造种类/数量扩展——


1.5.10：建筑链路重构（借鉴 numen 蓝图设计）——


1.5.9


1.5.8：小女仆与父亲的剧情记忆——child_state/child_lineage 的


1.5.7：自保不再"抽空打怪"（每 tick 清攻击目标，Brain 各优先级组


1.5.6：工作状态播报——女仆选了 maid_smart 任务却不干活时，每 10 秒


1.5.5：记忆写入崩溃修复——TLM 的 TaskData 用 unboundedMap codec 解码


1.5.4：借鉴史诗攻城 + numen AI——


1.5.3：任务切换修复（createBrainTasks 必须返回可变列表——TLM 的


1.5.2：任务修复（行为有目标才启动，不再空转发呆，无目标时正常漫游）、


1.5.1：自保升级——覆盖一切状态（不限战斗）、优先级 250 全局最高（压过跟随/任务/睡觉/恐慌）、双退出条件恢复原状态、直接寻路逃跑


1.5.0：战斗自保（低血逃跑/进食/搭方块，纯规则零 LLM）


1.4.1：建筑材料判定升级——缺料时先问玩家"建一半还是放弃"（mode=full/partial）


1.4.0：建筑功能（smart_build 工具 + 3 个内置蓝图 + LLM 生成蓝图 + 材料判定）


1.3.0：心契誓约剧情记忆（结婚/怀孕/心情/丧子/告白 实时注入 AI 对话）


1.2.0：情绪价值动作（跑过来/递食物/看向/撒爱心）+ 家庭保护（防同主女仆互殴）


1.1.0：新增烹饪/酿造任务、自主决策（空闲自判任务）、长期记忆（事件写入+context 注入）


1.0.0：P1 四工具 + P2 挖矿/整理 + P3 主动对话


