# Turtle Reproduce — 海龟就地产卵 适用于 **Minecraft Java 26.1.2 / Fabric**。海龟繁殖后会寻找当前位置附近最近水源边、能够走到的沙地,直接挖沙产卵,不再为了产卵返回出生地。 > 📌 **本副本基于上游源文件修改**:只对热路径做了一处**性能优化**,玩法行为不变(不做成成品 mod 分发,只提供工程源码)。 > 改动清单见文末「本副本的改动(相对上游源文件)」,逐行 diff 见 `OPTIMIZATION.diff`。 ## 行为规则 - 从海龟当前位置搜索 X/Z 各 ±32 格、Y ±8 格的已加载区域,不主动加载或生成新区块。 - 沙地须上方为空气,且水平 4 格半径、垂直 ±1 格内有露出水面的静止水源。普通沙子和红沙均适用;流动水及含水方块不算水源。 - 优先选择距离海龟最近的水源对应的沙地;对应同一水源时,选择距离海龟最近的可达沙地。被墙挡住、站立空间不足的位置会跳过。 - 保留原版挖沙动画、产卵数量(1~4 枚)、繁殖冷却和孵化机制。 - 找不到合适地点时,海龟保留腹中的蛋并稍后重试。可在附近补上水源和沙地。 - 不改写海龟的出生地数据;没有携带蛋时的普通活动保留原版行为。 ## 安装 需要 Java 25 或更高版本、Fabric Loader 0.19.5 或更高版本,以及适用于 26.1.2 的 Fabric API、Fabric Language Kotlin。 本包已随附编译好的成品 `build/libs/turtle-reproduce-1.0.0.jar`(由本包源码构建,20,528 字节,SHA-256 `f61e53d152dd2722a4dda31d63cd8fedd201af312d5fce03bc4a3c8c36e704b6`),可直接使用,无需自行编译。 将 `build/libs/turtle-reproduce-1.0.0.jar` 放入游戏实例或服务器的 `mods` 文件夹,不要安装 `-sources.jar`。 单人游戏安装到客户端。多人游戏安装到服务器即可,玩家客户端不需要安装本模组。 ## 构建与测试 使用 JDK 25,在项目目录运行: ```powershell .\gradlew.bat build ``` Linux / macOS: ```sh ./gradlew build ``` 如果终端默认使用 Java 21,先将当前终端的 `JAVA_HOME` 指向 JDK 25。IntelliJ IDEA 中也应把 Gradle JVM 设置为 25。 服务器游戏测试: ```powershell .\gradlew.bat runGameTest ``` `build` 会自动运行服务器游戏测试,也可用上面的命令单独运行。测试在独立世界覆盖真实繁殖、异地产卵、最近水岸、16 格外水岸、绕过封闭水岸、无沙地保留蛋、无水沙地拒绝产卵以及身体触水时选择干燥位置。测试代码不打包进发布模组。 手动体验时,可将两只海龟带离出生海滩,在新地点修建开阔沙岸和水池,用海草喂食繁殖。怀卵海龟应在新沙岸挖沙产卵。注意让沙岸足够宽,保留海龟进出水面的通道。 ## 本副本的改动(相对上游源文件) 本副本以上游源文件为基础,改动**只集中在一个文件**,目的是降低服务端开销;玩法规则、判定条件、数值与数据格式**一律未变**。 | 项 | 内容 | |---|---| | 改动文件 | `src/main/java/com/mr/ai/NestingSiteFinder.java`(唯一被改动的文件) | | 未改动 | 4 个 mixin(`TurtleLayEggGoalMixin`、`TurtleGoHomeGoalMixin`、`TurtleAccessor`、`PathNavigationAccessor`)、Kotlin 入口、`fabric.mod.json`、`turtle-reproduce.mixins.json`、`src/client`、全部 GameTest | | 逐行 diff | `OPTIMIZATION.diff`(+146 / −35 行) | ### 为什么要改 `NestingSiteFinder.findCandidates` 是热点:单次调用要扫 73 × 73 列 × 19 层 ≈ **10.1 万个方块**,而怀卵海龟只要偏离上次搜索原点超过 4 格、或把候选列表消费完,就会整片重扫。原实现每个方块都调用一次 `Level#getBlockState`(内部要重新解析区块、段索引与建筑高度),并为每块沙新建一个 `BlockPos` 对象。 ### 具体改动 1. **按区块段读方块,整段跳过空气段**:改走 `ChunkAccess#getSections()` + `LevelChunkSection#getBlockState()`;对 `hasOnlyAir()` 的段整段跳过。空段里只可能是空气,不可能提供水源或沙地,因此跳过它读不到任何会成为候选的方块。 2. **区块解析次数由 5329 次降到 ≤36 次**:循环由「列 → 层」改为「区块 → 段 → 方块」,每列只解析一次区块。原来的 `Level#hasChunk(x >> 4, z >> 4)` 换成等价的 `Level#getChunk(cx, cz, ChunkStatus.FULL, false)`——`hasChunk` 内部正是这个调用;`loadOrGenerate = false`,**依然绝不加载或生成区块**。 3. **沙地候选改用打包 long**:`ArrayList` → fastutil `LongArrayList`(`BlockPos.asLong` / `getX|getY|getZ(long)`),一次扫描不再产生成千上万个临时对象。 4. **`isValidSite` 去掉重复查询**:提取循环不变量(方块坐标、建筑高度判定),并记住上一个查询过的区块可用性——半径 4 的圆盘最多跨 4 个区块。 ### 为什么行为不变 - 搜索盒子的范围(X/Z ±32 加 4 格水源外扩、Y ±8 加 ±1)以及每一项判定条件与短路顺序**逐条保留**。 - 候选最终由**同一个比较器**排序,而它以沙地 `x/y/z` 结尾,构成**全序**(不同位置永不相等)。因此结果与扫描的访问顺序无关,排序后的候选列表逐位相同。 - `javap` 比对:公开方法签名、4 个公开常量值(`SEARCH_RADIUS = 32`、`VERTICAL_SEARCH_RADIUS = 8`、`WATER_RADIUS = 4`、`WATER_VERTICAL_RADIUS = 1`)以及 `Candidate` 记录的成员**完全一致**;唯一差异是一个 `private` 辅助方法的签名,故其余 class(含 4 个 mixin)的链接关系不受影响。 - 常量池比对:新增的类型引用只有原版 Minecraft 与 fastutil 的类,**没有引入任何新的运行期依赖**。 ### 验证情况 | 项 | 状态 | |---|---| | 编译 | ✅ JDK 25,`javac --release 25`;class 文件版本 69,与上游构建一致 | | 字节码 | ✅ `javap` 确认新逻辑(`getSections` / `hasOnlyAir` / `LongArrayList#getLong` / `getChunk(..., false)`)已进入产物 | | 链接 | ✅ 以真实 Minecraft 26.1.2 + Fabric API + fastutil 作为 classpath 做 `Class.forName` 反射检查,成员列表与上游逐条一致 | | 游戏内实测 | ✅ Minecraft 26.1.2 单机:怀卵海龟在本地水岸沙地产卵、蛋数 1~4、`home_pos` 未被改写、距出生地数千格仍能就地产卵 | | 7 个 GameTest | ⚠️ 未在本机执行(需要完整 Loom 构建并联网拉取依赖)。行为等价性由上述静态论证 + 游戏内实测支撑,建议在能联网的环境跑一次 `.\gradlew.bat build` 复核 | ## 许可 CC0-1.0。