Repository navigation
Add data-driven cube selection for 26.1 为26.1添加数据驱动的模型选取与描边 - #280
Conversation
- 支持按命名空间和方块配置精确模型选取、仅模型描边及原版模式 - 按资源包优先级逐项合并规则,并在模型重载完成时同步应用规则与几何 - 保留动态部件和程序排除接口,分离描边与精确选取入口并限制邻格补扫范围 - 补充资源配置格式、覆盖顺序及 KubeJS 整合包接入说明
🌿 Roseau API Breaking Change Report
|
- 在跳过精确选取前刷新客户端帧缓存,避免仅描边模式冻结动态部件 - 保留嵌套射线调用的缓存保护及超出精确选取距离时的原版行为
* feat(cube): 接入资源包驱动的模型选取与描边 - 将方块命名空间启用和选取排除名单迁移至统一资源配置,保留现有方块的默认模式 - 使整机与动态模型描边遵守统一模式及运行时排除,保留预览几何和方块实体部件排除配置 - 补充资源包覆盖顺序、旧配置迁移和 KubeJS 接入说明 - 配套 Anvil-Dev/AnvilLib#280 的资源规则,依赖发布前使用本地构建联调 * build(deps): 更新砧库模型选取依赖 - 将砧库及关联模块更新至 2.0.0+snapshot.686,使用已发布的资源包模型选取与描边功能 - 同步最低依赖版本与配置文档,完成铁砧工艺的数据驱动模型接入
Gugle2308
left a comment
There was a problem hiding this comment.
结论:PR #280 已在本次审查执行前被合并(squash e7db0829,与 dev/26.1 tip 一致,合并树与 head 7f4da6fd 完全相同),因此按「事后复核」处理;我逐条核对了 26.1 的官方字节码,未发现本 PR 引入的阻塞缺陷,但有 1 处跨版本线守卫缺口 和 1 处静默失效面 值得跟进。
一、合并态与范围
| 判据 | 结果 |
|---|---|
| state / merged / merged_at | closed / true / 2026-11-14T11:13:21Z |
| merge_commit_sha | e7db082920a62b0df4b899eeddb0deadaf5e8c67,与 ls-remote dev/26.1 相同 |
| 合并树 vs head | git diff pr280h pr280b 为空 ⇒ 进主干的就是 7f4da6fd(含第 2 个提交) |
| 规模 | 7 文件 +194/−11,与 API changed_files=7 / additions=194 / deletions=11 一致,webhook 带来的 diff 覆盖完整 |
| head CI | build / build ✅(total_count: 1) |
| 既有评论 | 仅 Roseau API 报告 1 条(`cube |
二、在 26.1 上逐条核对过的语义
以下结论来自 26.1.2 官方 client.jar 的字节码(该版本无 client_mappings,jar 未混淆,可直接按内部名反汇编),不是从 1.21.1 推的。
-
资源包优先级方向正确。
MultiPackResourceManager.<init>先把List.copyOf(packs)存入packs,再按列表顺序把它们 push 进各 namespace 的FallbackResourceManager;FallbackResourceManager.getResourceStack从size()-1倒序取、末尾Lists.reverse,listPacks()直接返回同一个packs。⇒ 索引升序 = 优先级低→高,CubeSelectionRules.java:43的升序排序 + 顺序merge与 README「从低到高逐项合并」一致。 -
路径参数与 id 前缀都对。 26.1 新增
MultiPackResourceManager.checkTrailingDirectoryPath:endsWith("/")为真时才抛IllegalArgumentException⇒CubeSelectionRules.java:37传"anvillib"(无尾斜杠)合法;FileToIdConverter.fileToId用path.substring(prefix.length() + 1, len - ext)反推,反证listResourceStacks返回的 id 带anvillib/前缀 ⇒id.getPath().equals("anvillib/cube_selection.json")能命中任意 namespace 的assets/<ns>/anvillib/cube_selection.json。 -
规则读的是本轮包集,不是上一轮。 26.1
ReloadableResourceManager.createReload的顺序是「close 旧resources→ newMultiPackResourceManager→putfield resources→SimpleReloadInstance.create」,且listPacks()与listResourceStacks()都委托同一个resources字段 ⇒ModifyBakingResult期间两处看到同一批包,packs.indexOf(sourcePackId())的索引与栈顺序同源(Resource.sourcePackId()=source.packId(),字节码可证)。 -
规则与几何同批发布。
CubeModelEvents.java:22-24暂存pending/pendingRules,:29一次性install(pending, pendingRules);CubeSelection.java:128-131是先写rules再调install(snapshot),所以extended/radius/pickingActive用的是同一套规则——顺序反过来就会出现「新几何 + 旧规则」的错配,这里写对了。 -
三种模式闭环,四个消费点收敛到同一判据。
target仅 MODEL(CubeSelection.java:202-207)、outlineTarget为 MODEL+OUTLINE(:210-211)、extendsBlock仅 MODEL(:193-199)、active()=pickingActive(:178);exclude()在mode()首行短路成 VANILLA(:111-114),registerTargetExclusion仍同时约束描边 ⇒ 与 README 的「程序排除优先于配置」一致。 -
「逐帧刷新」这条在 26.1 确实成立。 全 jar 扫
Minecraft.pick(F)只有两个调用方:Minecraft.tick()与Minecraft.renderFrame(boolean);且renderFrame里pick在gameRenderer.render之前(字节码偏移 166 < 234)⇒beginFrame一定在描边提取之前清掉FRAME并写入本帧partialTick。这正好补上前一提交把active()收窄后新出现的场景:仅描边配置下pickingActive == false,若beginFrame仍在早退之后,FRAME从首帧起不再失效,动态(BER)部件会被冻结。 -
整份文件原子、文件间隔离。
merge先复制 map、全量校验(未知顶层键 / namespace / id / mode)后才返回新 record(CubeSelectionRules.java:56-77),异常时上一份rules原样保留;Identifier.createUntrusted在 26.1 仍走assertValidNamespace/assertValidPath(字节码),所以:66用一次性Identifier校验 namespace 的做法在 26.1 有效。 -
公开面零增长:
CubeSelectionRules与Mode均为包私有,install(Snapshot, …)、isEnabled(Block, …)、mode(…)包私有,与 Roseau 的cube | 0一致;README 新增 12 条陈述我逐条对过代码,全部成立。
三、建议跟进
CubePicking.java:38-42(26.1):
context.nearest = block;
context.distance = start.distanceToSqr(block.getLocation());
...
BlockGetter.traverseBlocks(start, block.getLocation(), context, ...);1.21.1 同名方法 CubePicking.java:38-43 多三行:
double distanceToHit = start.distanceToSqr(block.getLocation());
// Sable 等模组可能返回子空间存储坐标,不能从玩家位置跨坐标空间逐格补扫。
if (!(distanceToHit <= start.distanceToSqr(end) + 1.0E-7)) return vanilla;差别不只是变量复用:Entity.pick 的返回值本身也是被重定向过的点,一旦第三方模组返回远处/子空间坐标,26.1 会从 start 到该坐标逐格遍历整段射线,每格再跑 (2r+1)³ 次 getBlockState,而这是每帧一次;同时 context.distance 失去「不超过射线长度」的上界。该缺口在 26.1 base 就存在(不是本 PR 引入),但本 PR 已经改到这个方法,补齐成本最低。
ModelSelections.java:29-30 的 MAX_STATES = 32768 / MAX_GEOMETRY_BYTES = 16 MiB 超限时只做 unsupported++(:50、:55、:125-127),而 CubeSelection.statistics() 全仓无调用者。README 新推的正是 namespace 级配置("namespaces": {"minecraft": "model"} 这类整包开启),撞上上限的表现是「一部分方块仍走原版行为」且无任何日志,排查成本很高。建议在 CubeModelEvents.apply 里补一行一次性的 warn(unsupportedStates > 0 时)。
四、细节建议
packs.indexOf(...)的-1分支(CubeSelectionRules.java:39):命不中时该源被排成最低优先级,属静默降级(当前sourcePackId()与packId()必然命中,纯防御)。建议Math.max(0, …)或加一条 debug 日志。load()的异常边界(CubeSelectionRules.java:35-37在逐文件 try 之外):listPacks()/listResourceStacks()若抛RuntimeException,会穿出ModifyBakingResult监听器并让整次模型重载失败——外部资源包触发的问题应退化成EMPTY继续跑。- 公开重载
install(Snapshot)(CubeSelection.java:133)不重置rules:第三方ModelSelections.bake(models)+install(snapshot)会沿用上一轮包规则。当前自洽,但语义是隐式的,建议 javadoc 注明(或提供显式规则入口)。 extendsBlock热路径开销(CubeSelection.java:193-196):mode()对每格做一次注册表反查 + 4 次 map 查询,而原先是两次查询。extended只在install时按 MODEL 过滤过(extended命中即必为 MODEL),可把廉价判断前移,只对DYNAMIC分支保留 mode 判定。active()/isEnabled()语义收窄(签名不变、javadoc 已更):active()从「有模型或动态注册」变成「存在 MODEL 模式几何」,outline下isEnabled仍为 true。下游若用它做渲染/拾取判断需要知会(本仓内消费点已核,全在CubePicking/CubeHighlightEvents)。- README 138–139 行连续两个空行(
### RPC 模块之前)。
五、验证边界
未执行 Gradle 构建、runClient 或 JUnit;结论来自:静态读码 + 三点 diff 对账(7 文件,与 API 计数逐项一致)+ 26.1.2 官方未混淆 client.jar 的字节码核对(listPacks/getResourceStack/checkTrailingDirectoryPath/fileToId/createReload/sourcePackId/Identifier.createUntrusted/Minecraft.pick 调用方与 renderFrame 内偏移)+ GitHub API 与 head CI。观感类结论(描边与动态部件是否跟手、outline 手感、F3+T 热换包)静态无法替代实机,建议按 model / outline / vanilla 三档各测一次,并覆盖「整包设成 model 触发预算」与「装带子空间坐标的重定向类模组」两种边界。
Gugle2308
left a comment
There was a problem hiding this comment.
结论:PR #280 已合并(squash e7db0829,与 dev/26.1 tip 一致,合并树与 head 7f4da6fd 完全相同),此前评论区只有 Roseau 的 API 报告;本次补做内容级复核,未发现本 PR 引入的阻塞缺陷,唯一实质跟进项是六条版本线里只有 1.21.1 带 CubePicking 的距离哨兵。
一、合并态与范围
| 判据 | 结果 |
|---|---|
| state / merged / merged_at | closed / true / 2026-11-14T11:13:21Z |
| merge_commit_sha | e7db082920a62b0df4b899eeddb0deadaf5e8c67 = dev/26.1 tip |
| 合并树 vs head | 空 diff ⇒ 进主干的就是 7f4da6fd |
| 规模 | 7 文件 +194/−11,与 API 计数一致,diff 覆盖完整 |
| head CI | build / build ✅ |
| 既有评论 | 仅 Roseau API 报告(`cube |
二、已验证的关键语义(26.1.2 官方未混淆 client.jar,直接按内部名反汇编)
- 包优先级方向正确。
MultiPackResourceManager.<init>先把List.copyOf(packs)存入packs,再按列表顺序 push 进各 namespace 的FallbackResourceManager;getResourceStack从size()-1倒序取、末尾Lists.reverse,listPacks()直接返回同一个packs⇒ 索引升序 = 低→高,CubeSelectionRules.java:43的升序排序 + 顺序merge与 README「从低到高逐项合并」一致。 - 目录参数合法。 26.1 的
checkTrailingDirectoryPath只在路径endsWith("/")时抛IllegalArgumentException⇒ 传"anvillib"(无尾斜杠)正确;六条线行为一致,可写进文档。 - 路径过滤跨 namespace 命中。
FileToIdConverter.fileToId用path.substring(prefix.length() + 1, …)反推,反证listResourceStacks返回的 id 带anvillib/前缀 ⇒:37的equals(PATH)命中assets/<任意 ns>/anvillib/cube_selection.json。 - 规则取本轮包集。
ReloadableResourceManager.createReload顺序为 close 旧 → new →putfield→ 派发,且listPacks()/listResourceStacks()都委托同一个resources字段。 - 逐帧链路成立。
Minecraft.pick(F)的调用方只有Minecraft.tick()与Minecraft.renderFrame(boolean),且renderFrame内pick(偏移 403)早于GameRenderer.render(偏移 520)⇒beginFrame在描边提取之前清帧缓存,仅描边模式不再冻结动态部件。 Identifier.createUntrusted仍校验(assertValidNamespace/assertValidPath)⇒CubeSelectionRules.java:66的 namespace 键校验有效。- 其余结论与同批 #276/#277/#278/#279/#281 相同:规则与几何成对发布、
install先写rules再重算、三种模式四消费点收敛、exclude优先、整份文件校验后替换、公开面零增长。
三、唯一实质跟进项:六线对比,距离哨兵只在 1.21.1
| 版本线 | PR | distanceToHit 上界 |
|---|---|---|
| 1.21.1 | #276 | ✅ 有 |
| 1.21.4 | #277 | ❌ 无 |
| 1.21.8 | #278 | ❌ 无 |
| 1.21.11 | #279 | ❌ 无 |
| 26.1 | #280(本 PR) | ❌ 无(CubePicking.java:38-39 直接赋值) |
| 26.2 | #281 | ❌ 无 |
1.21.1 的守卫处理「Entity.pick 返回被第三方模组重定向到远处/子空间坐标」(注释点名 Sable):先在 start.distanceToSqr(end) + 1e-7 处兜底返回原版结果。其余五条线会把该坐标直接喂给 BlockGetter.traverseBlocks(start, block.getLocation(), …) 逐格遍历整段射线(每格再跑 (2r+1)³ 次 getBlockState),且每帧一次;context.distance 同时失去「不超过射线长度」的上界。建议补到其余五条线,或在各线对应位置加注释固化其触发场景。
四、其他建议
⚠️ 预算截断静默(六线通病):MAX_STATES = 32768/MAX_GEOMETRY_BYTES = 16 MiB超限只unsupported++,而CubeSelection.statistics()全仓无调用者;namespace 级配置最容易撞上,表现为「一部分方块仍走原版行为」且无日志。建议在CubeModelEvents.apply里unsupportedStates > 0时打一条一次性 warn。- 💡 README 139–140 行连续两个空行(
### RPC 模块在 141 行)。 - 💡
packs.indexOf(...)的-1(CubeSelectionRules.java:39):命不中会被排成最低优先级(静默降级),可Math.max(0, …)或 debug 日志。 - 💡
load()的异常边界(:35、:37在逐文件 try 之外):listPacks()/listResourceStacks()抛RuntimeException会穿出监听器、让整次模型重载失败,外部资源包引发的问题应退化成EMPTY。 - 💡 公开重载
install(Snapshot)(CubeSelection.java:133)不重置rules,第三方重装快照会沿用上一轮包规则,建议 javadoc 注明。 - 💡
extendsBlock热路径(CubeSelection.java:193-196):mode()每格多一次注册表反查 + 4 次 map 查询,而extended命中即必为 MODEL,可把廉价判断前移。 - 💡
active()/isEnabled()语义收窄(签名不变、javadoc 已更),下游若做渲染/拾取判断需知会。
五、验证边界
未执行 Gradle 构建、runClient 或 JUnit;结论来自静态读码 + 三点 diff 对账(7 文件,与 API 计数逐项一致)+ 26.1.2 官方未混淆 client.jar 字节码核对(listPacks / getResourceStack / checkTrailingDirectoryPath / createReload / Identifier.createUntrusted / Minecraft.renderFrame 内 pick 与 render 的偏移)+ 与同批 #276 / #277 / #278 / #279 / #281 的逐文件比对 + head CI。观感类结论(描边跟手度、outline 手感、F3+T 热换包)静态无法替代实机,建议按 model / outline / vanilla 三档各测一次,并覆盖「整包设成 model 触发预算」与「装带子空间坐标的重定向类模组」两种边界。
feat(cube): 支持资源包配置模型选取与描边
fix(cube): 保持仅描边模式的动态几何逐帧更新