Skip to content

Add CFA occlusion culling and render bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器 - #4922

Draft
ZhuRuoLing wants to merge 2 commits into
Anvil-Dev:dev/26.1/1.6from
ZhuRuoLing:feat/occlusion_culling
Draft

ZhuRuoLing wants to merge 2 commits into
Anvil-Dev:dev/26.1/1.6from
ZhuRuoLing:feat/occlusion_culling

Conversation

@ZhuRuoLing

Copy link
Copy Markdown
Contributor

概要

为锻星砧(CFA)接入 anvillib 的遮挡剔除能力,并按原版调试渲染器机制提供渲染包围盒可视化(F3 调试项开关,默认关闭)。

同时把 anvillib 升级到 2.0.0+snapshot.531(该版本才有 dev.anvilcraft.lib.v2.rendering.optimization.occlusion 包)。

变更点

  • 依赖anvillib 2.0.0+snapshot.5192.0.0+snapshot.531
  • CFARenderer#getOcclusionBoundingBox(be, partialTicks)(新增,原 getRenderBoundingBox 未改动):以所有渲染图层共用的渲染中心为基准,按「实际绘制的图层」取外接半径。
  • VisibleRings(新增):束星环可见性判定(天体类型 + 巨构隐藏规则)统一到一处,extractRings 与包围盒计算共用,避免两处规则漂移。
  • CfaRenderBoundsDebugRenderer + DebugRendererEventListener(新增):按原版 DebugRenderer.SimpleDebugRenderer + Gizmos 绘制包围盒,通过 RegisterDebugRenderersEvent / RegisterDebugEntriesEvent 注册调试条目 anvilcraft:cfa_render_bounds(F3 调试选项中开关,默认关闭),颜色由包围盒哈希派生以辨识尺寸变化。
  • 配置:新增客户端配置 cfaOcclusionCulling(默认关闭),开启时在提交几何前建立跨帧稳定的 OcclusionKey 并提交遮挡记录。

包围盒推导依据

渲染中心:所有图层都以 (blockX + 0.5, blockY + centerY, blockZ + 0.5) 为中心缩放(submitMechanicalRing / pushRing / submitCelestialBody / submitCelestialRing / submitSupernovaFlash / emitBeamPyramid)。centerYbe.getSmoothCenterY();方块实体只存在于 BOTTOM_CENTER 主部件(MultiPartBlockEntity#newBlockEntity),故其方块中心即整机 3×3 底面中心。

半径:环类图层都经 pushRing 以同一个 ringScale 绘制,且模型原点即渲染中心,因此半径 = 图层模型「顶点到原点的最大距离」× ringScale(实测自模型文件,非单轴半宽):

环层 半径(格) 环层 半径(格)
R1 0.336 R4 0.614
R2 0.487 R5 0.796
R3 0.708 R6 1.039
  • 机械环按 VisibleRings(天体类型会隐藏外层环;戴森球/彭罗斯球等巨构会隐藏对应层);
  • 巨构占用的恒星同步层按其环层半径计入(挖掘机/提取器/戴森球/线圈/彭罗斯球/解压器实测均 ≤ 对应环层);
  • 戴森球额外占用的外层环与 extractMegastructureRings 一致:小型恒星用 R5(0.796),其余用 R6(1.039)。
  • 天体按分支取系数:普通恒星 1.386(光晕 0.866×1.6)、黑洞 1.414、中子星 1.503/0.406(有无喷流)、行星有环 1.4 / 无环 1.0、玩家头颅 0.75×16、资源包自定义天体 1.5 兜底;
  • 上界额外纳入托举光束 1.5 + beamHeight,超新星期间中心改为 getSupernovaCenterY() 且半径并入 12 × √进度 × scale

ringScale = 6 时:非增幅无天体约 8.5 格、增幅无天体约 12.5 格、小型恒星戴森球约 9.5 格、有岩石行星(外环被隐藏)约 5.8 格。

验证

  • ./gradlew.bat compileJava --console=plain 通过;
  • ./gradlew.bat runData --console=plain 通过,生成的 diff 仅包含新增/删除的配置项 lang 键;
  • 游戏内按 F3 打开 anvilcraft:cfa_render_bounds 逐状态比对:无天体、有星体无巨构、无巨构增幅、小型恒星戴森球、大恒星戴森球均已与可见几何贴合。

说明

  • 遮挡剔除为实验性功能,配置默认关闭;
  • 当前遮挡剔除记录仍使用原 getRenderBoundingBox(本次按要求未改动它),待新包围盒验收完成后可切换;
  • 单独绘制的托举光束(AFTER_WEATHER)与恒星 bloom 不经过该 collector,不会被剔除。

feat(rendering): occlusion culling and render-bounds debug renderer for the CFA

- 升级 anvillib 至 2.0.0+snapshot.531
- CFARenderer 新增 getOcclusionBoundingBox:以所有图层共用的渲染中心
  (blockX + 0.5, blockY + centerY, blockZ + 0.5) 为基准,按实际绘制的环类图层
  (可见机械环 / 巨构恒星同步层 / 戴森球外环)与天体分支(恒星 / 黑洞 / 中子星 /
  行星环 / 玩家头颅)的实际模型外接半径计算,并计入托举光束与超新星放射光束
- 抽出 VisibleRings 统一束星环可见性判定,extractRings 与包围盒计算共用
- 新增 F3 调试渲染器 anvilcraft:cfa_render_bounds,按原版 DebugRenderer +
  Gizmos 机制注册(RegisterDebugRenderersEvent / RegisterDebugEntriesEvent),
  默认关闭,颜色由包围盒哈希派生
- 新增客户端配置 cfaOcclusionCulling(默认关闭):提交遮挡记录前建立跨帧稳定的
  OcclusionKey,调试轮廓与遮挡记录互不影响
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /opt/data/workspace/AnvilCraft && rm -rf /tmp/cfa_models && mkdir -p /tmp/cfa_models && git archive rvhead4922 src/main/resources/assets/anvilcraft/models/block | tar -x -C /tmp/cfa_models && ls /t...

Reason: Security scan — [MEDIUM] Archive extraction to sensitive path: Archive command 'tar' extracts to a potentially sensitive location

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /tmp && for A in "anvillib-neoforge-26.1" ; do URL="https://server.cjsah.net:1002/maven/dev/anvilcraft/lib/$A/2.0.0+snapshot.531/$A-2.0.0+snapshot.531-sources.jar"; curl -s -o "/tmp/alib531-src.jar...

Reason: script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /opt/data/workspace/AnvilCraft && mkdir -p /tmp/cfa_models2 && git archive rvhead4922 src/main/resources/assets/anvilcraft/models/block | tar -x -C /tmp/cfa_models2 && ls /tmp/cfa_models2/src/main/...

Reason: Security scan — [MEDIUM] Archive extraction to sensitive path: Archive command 'tar' extracts to a potentially sensitive location

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /opt/data/workspace/AnvilCraft && for f in star neutron_star neutron_star_jet black_hole planet_atmosphereless planet_giant_0 planet_giant_ring_0; do echo "=== $f ==="; git show "rvhead4922:src/mai...

Reason: script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4922

操作: synchronize(分支 feat/occlusion_cullingdev/26.1/1.6
范围: 11 个文件(386+/31−)— 8 Java(2 新增)、gradle/libs.versions.tomlmoddevgradle.gradlelang/en_us.jsonlang/en_ud.json
diff 完整性: ✅ md5 a3b93ec398db811b70274f1a71309ebadiff --git 计数 11 = API changed_files 11,无 ghost 文件

本次核对手段(除读 diff 外):

  1. 用 base 分支源码(git show base2616:CFARenderer.java)逐行比对 extractRings/submitRingMaybe/extractMegastructureRings/getRenderBoundingBox 的旧行为;
  2. 把 6 个环模型 JSON + 24 个巨构变体模型 + 21 个天体模型逐 element 复算「顶点到旋转中心/立方体中心的最大距离」,与代码里硬编码的半径常量对账;
  3. 匿名拉取 anvillib 2.0.0+snapshot.531 的 maven jar,解出嵌套 jarjar,核对 occlusion 包的 API 契约(常量池级)。

🔴 关键

  1. CFARenderer.java:1654 超新星光束半径少算最多 30%
    包围盒用 SUPERNOVA_RAY_LENGTH * grow * Math.max(1.0f, scale),但实际射线长度在 CFARenderer.java:1316 算出 length = SUPERNOVA_RAY_LENGTH * grow * scale 后又乘了随机系数:CFARenderer.java:1338 float len = rayLen * (0.7f + 0.6f * rand.nextFloat())(最大 1.3×)。即真实最长射线 = 12 × 1.3 × √t × scale,包围盒仅按 12 × √t × scale
    现有旧实现反而是保守的(max(8,12) * scale * 1.5 + 2)。修复建议:SUPERNOVA_RAY_LENGTH * 1.3f,或把 1.3f 抽成与 submitSupernovaFlash 共享的常量,避免下次再漂移。
    (顺带核对:闪光四边形用的是 SUPERNOVA_MAX_RADIUS * expand * scale = 8×,小于射线,射线才是上界 ✅;elapsed/total 的进度公式与 extractSupernova 一致 ✅。)

  2. 遮挡记录仍喂旧 getRenderBoundingBox ⇒ 开启 cfaOcclusionCulling 后几乎永不剔除,但 tooltip 宣称已生效
    beginOcclusionRecordCFARenderer.java:754-763)用的是 state.getRenderBounds(),即本次未改动的 getRenderBoundingBox。按 base 源码复算其尺寸:无天体时水平半宽 max(6×3, 6×3)×1.5 = 27 格,增幅 + 大天体时可达 bodyScale3.945×6.667×3×1.5 ≈ 118 格、高度取 max(centerY+bsMax×1.5, 73)——这个盒子大到 HZB 判定基本永远"可见"。
    结果:开了配置只是白付记录开销,视觉上什么都不会变;而新增 lang 文案写的是 "geometry fully hidden behind terrain is skipped"。建议二选一:本次直接切到 getOcclusionBoundingBox,或把文案/描述改成"当前仅提交记录,剔除尚未启用(待新包围盒验收)"。

⚠️ 警告

  1. has*Ring 语义被改变:图层存在当前天体下可见(过渡淡出被跳过,且戴森球内环条件外扩)
    CFARenderer.java:308-312 现在把 VisibleRings(含 isRingVisible 因子)写进 setHasOuterRing/Middle/Inner,而旧实现是 hasMechanicalRings(存在性)+ 仅巨构隐藏规则。
    CFARenderer.java:832if (model == null || !present) return; 会在淡出分支之前直接返回,所以 visibleNow=false && wasVisible=true 的缩放淡出(旧 674-679 行对应逻辑)不再执行。
    具体可复现场景(非增幅):恒星 → 岩石行星切换时 prevBody=star ⇒ R3 wasVisible=truebodyData=岩石 ⇒ R3 visibleNow=false,旧版外环会随过渡缩放淡出,现在会瞬间消失;增幅下 小天体→大天体 的 R6、大天体→小天体 的 R4 同理。
    (我逐条核对过:静止状态渲染结果完全一致——差异只出现在 isAnimating() 的过渡窗口内,因为 present=falsevisibleNow 本来也是 false。所以是过渡观感回归,不是几何丢失。)
    同一处 CFARenderer.java:366 还把内环隐藏条件由 state.isDysonSphereR4() 扩成了 anyDyson,而注释仍写「小型戴森球、磁星线圈、彭罗斯球和物质解压器用恒星同步层替代内环」。大型戴森球(ring(5))在放大态下 R4 的 isRingVisible 本来就是 false(size>=48 分支),所以静止态无差异,但请确认这个外扩是否有意——从「注释/描述都没提可见性变更」看更像是顺手统一时带出的。
    建议:state 的 has*Ring 保留旧语义(存在性),包围盒半径另从"当前可见集"推导(VisibleRings 同时暴露 presentvisibleNow 两组);若要改成可见性语义,请更新注释并在描述里说明这是有意的观感调整。

  2. OCCLUSION_KEYS 静态身份映射的生命周期与清理时机
    CFARenderer.java:199keySet().removeIf(...) 被放在 if (AnvilCraftClient.CONFIG.cfaOcclusionCulling) 内部,同时这个 map 持有 BE 的强引用(⇒ 间接强引用 ClientLevel):

    • 关闭配置后旧条目永远不会被清理(此时也不会再新增,属于纯残留);
    • 离开世界/切换到另一个存档后不再发生 CFA 提取 ⇒ 最后一批条目 + 世界对象常驻,直到再次进入有锻星砧的世界;
    • removeIf 在每个 CFA 的每次提取都全表遍历,n 个锻星砧 = 每帧 O(n²)。
      建议:把清理移出配置判断(或加 if (!map.isEmpty()) 守卫),并在 ClientPlayerNetworkEvent.LoggingOut / 客户端世界卸载处 OCCLUSION_KEYS.clear()。身份映射 + "名字供应器只捕获 BlockPos" 的写法本身是对的(见下方 API 核对)。

💡 建议

  • 行星环半径口径不一致(非阻塞)bodyRadiusFactor 对有环行星取 1.4="可见环半径 1.0 × 最大环倍率 1.4",而环模型口径用的是顶点半径——renderRingCelestialBodyRenderer.java:205-251)的四边形在 x,z ∈ [-0.5, 1.5],加上提交处的 translate(-0.5,-0.5,-0.5) 后到中心距离最大 √2,即真实顶点半径 √2 × 1.35/1.3/1.4 ≈ 1.9~1.98 × bodyScale。可见几何(贴图环外的角落是透明的)确实被 1.4 覆盖,所以不影响观感;但"机械环按顶点、行星环按可见"两套口径并存,后续切换到剔除时会让人误判,建议加注释或在代码里显式写明取的是视觉半径。
  • 另有两个静态后备模型半径超过无环行星用的 1.0planet_error = 1.0825、planet_shattered = 1.1233(复算自 JSON),它们只影响"动态贴图不可用"的回退路径,量级 ~12%,可按需把无环行星系数提到 1.13。
  • 调试盒与记录盒不同源,建议同屏画两个CFARenderState.renderBounds 装的是旧盒,而 cfa_render_bounds 画的是新盒(且是在调试渲染器里 getOcclusionBoundingBox(cfa, partialTicks) 重算,未复用 state)。若命名不改,至少同时输出两个盒(不同颜色),否则"验收完成后切换"时无法在游戏内直接对比差异。
  • 硬编码半径是漂移风险ringRadius() 的 6 个常量 + PLAYER_HEAD_RADIUS + SPECIAL_BODY_RADIUS = 1.5(资源包自定义天体,无实测依据)都是"知识写死在 Java"。建议加一条单测解析环模型 JSON 复算最大顶点半径并断言 ≥ 常量,或直接由 tessellation bounds 在运行时推导。
  • moddevgradle.gradle:22-30 新增的 clientIrisWorkaroundForProfiling(含 --renderDebugLabels-XX:+DebugNonSafepoints)未在描述中提及,属 dev-only 但与本次渲染改动无关,建议拆出单独提交。
  • F3 条目用 DebugEntryNoop(无文本):可开关但用户看不出它叫什么;anvillib 自身已有 dev.anvilcraft.lib.v2.rendering.debug.OcclusionCullingDebugEntry,注意别让两者语义混淆。
  • levelRenderer.iterateVisibleBlockEntities 只遍历本帧可见 BE ⇒ 锻星砧被视锥剔除时不会画盒(可接受,但需知晓设计意图)。

🟢 看起来不错

  • 环半径表逐值复算完全一致0.3358 / 0.4871 / 0.7078 / 0.6144 / 0.7961 / 1.0390 → 代码里的 0.336 / 0.487 / 0.708 / 0.614 / 0.796 / 1.039,误差 <0.0002;坐标系假设也成立(环模型用负坐标,模型原点即渲染中心,旋转不改距原点距离)。
  • "同层巨构变体外接半径不超过该层环模型"经 24 个模型逐个复算成立:R4 家族(coil/coil_ring/dyson/matter_decompressor/penrose/penrose_laser/wormhole/coil_fix…)最大 0.6144 = ring_4ring_5_dyson_sphere 0.7848 < 0.7961;ring_5/6_stellar_evolution_accelerator = 0.7961/1.0390 = 同层基准;R1/R2 变体也都等于基准。
  • 天体系数复算一致:黑洞 1.4142≈1.414、中子星 0.4059≈0.406、中子星喷流 1.5026≈1.503(与 rotationSpeed >= 5 判喷流一致);普通恒星 1.386 ≥ 实际最大(光晕 1.0+0.9×0.6=1.54 × 0.866 = 1.334,偏保守 ✅);玩家头颅 0.75×16 也 ≥ 头颅立方体的实际外接半径。
  • 戴森球外层环的分支与 extractMegastructureRings 完全对齐(小型恒星 → R5、其余 → R6,CFARenderer.java:555-559 vs 435-438),未出现两处规则漂移。
  • 渲染中心推导正确:pushRing/submitMechanicalRing/submitCelestialBody/emitBeamPyramid 的基准都是 (0.5, centerY, 0.5),超新星中心 getSupernovaCenterY()supernovaLocalCenterY 同源,光束上界 BEAM_BASE_Y=1.5 + smoothBeamHeight 与提交条件一致。
  • anvillib 531 API 契约核对通过(常量池级):OcclusionKey(Supplier,AABB) + setBoundingBox(AABB) 存在且 boundingBox 非 final;equals/hashCodeSystem.identityHashCode(身份语义)⇒ "必须跨帧复用同一实例、按 BE 缓存"的做法是必需且正确的OcclusionCuller.wrapSubmitNodeStorage(SubmitNodeCollector) 是 default 方法;beginOcclusionRecord/endOcclusionRecord 的无参 overload 存在;ALROptimizations.getOcclusionCuller() 由库自身初始化(MinecraftMixinALROptimizations.create()AnvilLibRendering 负责帧生命周期)⇒ mod 侧不需要也不应自己 create()。记录区间内统一改写为 target 收集器、把 bloom/延迟光束排除在记录外,与库的用法一致。
  • 全限定名清理(AnvilCraft.ofMinecraft.getInstance)无行为影响;debug/package-info.java@NullMarkeden_uden_us 的逐字符镜像、ConfigScreenLang override 与生成值逐字一致(无 lang 漂移)。

未独立核实(如实说明):vanilla 26.1 侧 API(DebugEntryNoopMinecraft.debugEntries.isCurrentlyEnabledLevelRenderer.iterateVisibleBlockEntitiesGizmos.cuboid/GizmoStyle.strokeDebugRenderer.SimpleDebugRenderer#emitGizmos 签名)无法在本环境验证——26.1 的 version json 没有 downloads.client_mappings,本仓库所有远端分支也零命中,因此只能依赖作者 compileJava 通过的声明。若 CI 有构建产物,建议在描述里附上 build 链接。

📋 声称验证表

声称 状态 证据
anvillib 519 → 531 两者同版本号一处改动;occlusion 包确实只在 531 的 anvillib-rendering
新增 getOcclusionBoundingBox,原 getRenderBoundingBox 未改动 diff 只新增;旧方法在 context 中未见 +/-
VisibleRings 统一可见性规则、避免两处漂移 ⚠️ 规则确实集中了,但同时把 has*Ring 语义改成"可见"、内环条件外扩到 anyDyson(见 ⚠️3)
调试条目 + 调试渲染器(RegisterDebugEntries/Renderers,默认关闭) 新文件 + DebugEntryNoop + isCurrentlyEnabled 门控
配置 cfaOcclusionCulling 默认关闭 = false,lang 三处齐全
环层半径"实测自模型文件" 复算完全吻合(见 🟢)
"巨构占用层按对应环层半径计入,实测均 ≤ 对应环层" 24 个变体模型逐个复算成立
天体系数(1.386 / 1.414 / 1.503·0.406 / 1.4·1.0 / 0.75×16 / 1.5 兜底) ⚠️ 前四项复算吻合;SPECIAL_BODY_RADIUS=1.5 无实测依据(资源包模型不可预知,只能算兜底,建议注明)
上界纳入托举光束、超新星并入 12×√进度×scale ⚠️ 光束 ✅;超新星漏了射线的最长 1.3× 随机系数(见 🔴1)
"F3 逐状态比对已与可见几何贴合" ⚠️ 无天体/无巨构/戴森球状态可信;超新星期会短 30%,行星环走的是"可见半径"口径
遮挡剔除记录仍用旧包围盒(有意为之) ✅(但见 🔴2) 描述已说明;影响是功能实际不生效 + 文案不符

🧪 测试建议

被测目标 推荐场景 优先级
ringRadius() / bodyRadiusFactor() 解析 models/block/celestial_forging_anvil_ring_{1..6}.jsoncelestial_body/*.json 复算最大顶点半径,断言 ≥ 常量(防模型微调后静默漂移) 🔴
getOcclusionBoundingBox() 逐状态(无天体 / 主序星 / 黑洞 / 中子星±喷流 / 有环岩石行星 / 玩家头颅 / 各巨构 / 超新星)断言半径等于预期值,并断言超新星期 half ≥ 12 × 1.3 × √t × scale 🔴
遮挡记录(beginOcclusionRecord 断言开启配置后 renderBounds 换用 getOcclusionBoundingBox 时盒不缩小于可见几何;配置关闭时零开销(不建键、不记录) 🟡
OCCLUSION_KEYS 开启配置 → 移除方块实体 → 断言 map 不保留该 BE(含仅剩最后一个 CFA 的情形);关闭配置后再次提取应仍能清理 🟡
VisibleRings.of() 表格化单测:(bodyData × isAmplify × option 各 id)(outer, middle, inner),并对照旧实现的 hasMechanicalRings + 巨构隐藏 语义,把有意的差异(anyDyson)固化成用例 🟡
过渡淡出(观感) 手动:恒星→岩石行星(应看到 R3 缩放淡出)、巨型行星出现(R1 淡出)、放大态 大/小天体互换(R6/R4 淡出)、天体移除(淡入) 🟢

结论: COMMENT — 包围盒数值推导扎实(硬编码常量逐值复算全部吻合,巨构变体覆盖结论成立),anvillib 的 OcclusionKey 身份语义与该 PR 的缓存策略相互印证。建议合并前处理 🔴1(超新星半径漏 1.3×,与自测结论冲突)与 🔴2(记录仍用旧盒 ⇒ 开了不生效,但文案宣称生效),并确认/修正 ⚠️3 的 has*Ring 语义与 anyDyson 外扩、⚠️4 的键缓存清理时机。

PR 标题:当前标题只有中文部分。gh auth status 显示 token 已失效(The token in /opt/data/home/.config/gh/hosts.yml is invalid),本次未能自动修改标题,建议改为:
feat(rendering): CFA occlusion culling and render-bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

I have enough verified data (including reverse-engineering the ring/body model radii from the actual assets). Writing the review now.

代码审查摘要 — PR #4922

操作: opened | 范围: 10 个文件 (7 Java, 3 新增) / 635 行 diff (386 +, 41 −, 3 new, 0 删除)
目标分支: dev/26.1/1.6 (base e4bf6c7) | head: 299063d

包围盒数值我做了独立复算(models/block/**/*.json 顶点到原点/模型中心最大距离 ÷16,及 planet_giant_ring_0.png 逐像素 alpha 包围),结论见下方声称表。

🔴 关键

  • CFARenderer.java:315-317 + :832 — 束星环「淡出」动画被这次重构静默关掉(回归)
    旧代码里 hasOuterRing/hasMiddleRing/hasInnerRing 的语义是「该层骨骼存在」(hasMechanicalRings = !playerHead,与 isRingVisible 无关),它只作为 submitRingMaybe 的硬门:if (model == null || !present) return;:832)。现在改成 state.setHasOuterRing(rings.outer()),而 rings.outer() = isRingVisible(...) = 当前的可见性,于是「现在不可见」的层 present=false 会直接 early-return,visibleNow=false && wasVisible=true淡出分支(scale = 1-progress 缩小绘制)永远走不到
    受影响的天体切换(旧行为是缩小淡出、新行为是瞬间消失):
    切换 失效图层
    恒星 → 岩石行星 (RockyPlanetData) 外环 R3
    任意 → 巨行星 (GiantPlanetData) 内环 R1
    任意 → SpecialCelestialBodyData 外环 R3
    增幅 大恒星 ↔ 小恒星(48 阈值) 外环 R6 / 内环 R4
    淡入分支不受影响(present 基于当前 body 求值,恒为 true)。
    最小修法:submit 处传 `state.isHasOuterRing()

⚠️ 警告

  • CFARenderer.java:457 — 行星环半径取 1.0 偏小,实际可见半径是 1.327
    renderRing 画的是 x,z ∈ [-0.5,1.5] 的 2×2 方片,其顶面 UV 取 [0,0.5]²(32×32 像素象限)→ 16 px = 1 格,环心 = 象限中心。我逐像素量了 planet_giant_ring_0.png:不透明像素到象限中心最大距离 = 21.2 px = 1.327 格(贴图外圈是方环,对角方向真的画到了 1.327,不是透明角)。
    因此环的可见半径 = 1.327 × ringMultiplier(1.35 岩石 / 1.3 巨行星 / 1.4 其它)= 1.72 ~ 1.86,而 bodyRadiusFactor 只给 1.4 → 包围盒比可见几何小约 23%–33%StarData 1.386、SpecialCelestialBodyData 1.5 分支同样偏小)。
    建议把环单独算一项:ringType != NONE ? 1.327f * ringMultiplier(body) * smoothBodyScale 再与天体项取 max。
  • CFARenderer.java:1654 — 超新星半径漏了放射光束的随机长度系数
    实际绘制是 len = SUPERNOVA_RAY_LENGTH * grow * scale * (0.7f + 0.6f * rand):1338,上限 1.3 倍),包围盒只算 12 × √进度 × scale → t→1 时真实光束可达 15.6 格,盒半径 12 格,小约 23%。建议乘 1.3(或把 (0.7f + 0.6f * rand) 的 1.3 提为常量)。
  • CFARenderer.java:196 / 757-759 — 真正喂给遮挡剔除的仍是旧的 getRenderBoundingBox,新包围盒只用于调试绘制
    旧盒子是刻意放大的估计:maxHorizontal = max(bsMax, ringMax) * 1.5(无天体时 bs=6 → ±27 格)、maxHeight = max(..., 36/73) → 远远大于实际几何。后果:(1) 打开 cfaOcclusionCulling 基本不会剔到任何东西,无法体现收益;(2) F3 里看到的框(getOcclusionBoundingBox不是剔除实际使用的框,PR 描述的「F3 逐状态比对贴合」并没有验证到真正上线的那个盒子。建议:开剔除时 state.setRenderBounds(this.getOcclusionBoundingBox(be, partialTicks))(或另加开关),并把字段名(renderBounds)改成语义明确的名字。
  • CFARenderer.java:180 / 199OCCLUSION_KEYS 静态强引用表的清理不足以覆盖换存档/换服务器
    清理只在 if (CONFIG.cfaOcclusionCulling)extractRenderState 被调用时执行;退出世界后没人再调用它,isRemoved() 也不保证在区块卸载时置位 → 旧 ClientLevel/旧 BE 会被静态表长期持有(配置一旦关闭则永不清理)。建议改 WeakHashMap(BE 未覆写 equals,仍是身份语义)或在 LevelEvent.Unloadclear()

💡 建议

  • CFARenderer.java:445 — 褐矮星(GiantPlanetData.brownDwarf())额外画一层光晕:submitHalo(3, 1.15f, 0.25f, ...):1174)最大倍率 = 1.15 + (2/3)×0.25 = 1.317 → 半径 0.866×1.317 ≈ 1.14;而无环巨行星分支只给 1.0,光晕被切掉约 14%。建议 brownDwarf() 至少取 1.14。
  • CFARenderer.java:668 / 747endOcclusionRecord() 未放 try/finally:提交中途抛异常时本次记录不会转发给真正的 collector(该帧几何整体消失,且记录悬空)。库侧 beginOcclusionRecordIllegalStateException 守卫(anvillib OcclusionSubmitNodeCollection:34),建议对称地加 try/finally
  • CfaRenderBoundsDebugRenderer.java:50-55 — 描边色取 bounds.hashCode(),而盒子的中心/半径随平滑缩放、红石信号、超新星进度(含 partialTicks逐帧变化 → 颜色会持续闪烁,难以「辨识尺寸变化」。建议改用稳定的派生依据(环层数/是否增幅)或量化后再哈希。
  • ringRadius(2)=0.487 比实测 0.4871 略小(截断误差 0.02%),可忽略;顺带说明 ringRadius(1)0.336 是最外圈顶点,实测 0.3358 完全吻合。
  • 新增 lang 键只进了 en_us/en_udzh_cn 等 11 个语言文件共 167 个 anvilcraft.configuration.* 键都没有它),按 Weblate 流程属正常,仅作提示。

🟢 看起来不错

  • 六个环半径常量与模型文件完全对得上(我按 JSON 元素顶点到原点最大距离 ÷16 复算:0.336/0.487/0.708/0.614/0.796/1.039 vs 实测 0.3358/0.4871/0.7078/0.6144/0.7961/1.0390);pushRing 确实是「平移→缩放」无 -0.5 偏置,模型原点即渲染中心,旋转不改变半径——推导链成立。
  • 巨构同步层 ≤ 对应环层ring_4_dyson 0.595ring_5_dyson 0.785coil_fix 0.378/coil_ring 0.614penrose_fix 0.352/laser 0.614decompressor_fix 0.368/ring 0.614collider 0.539accelerator R5 0.796/R6 1.039 —— 全部成立;戴森球额外外环 R5/R6 的取法与 extractMegastructureRings:555-561 一致。
  • 巨构隐藏规则重构等价state.isDysonSphereR4/R5() 等标志本就来自 megastructureId = getActiveMegastructureOption().id():253/274-278),改用 option.id() 直接比较无行为差异;isSmallStar!acceleratorActiveoption.ring() 的用法也与 submitMegastructureRings/extractMegastructureRings 对齐。
  • 天体系数与模型实测吻合:黑洞 1.4142、中子星 0.4059、带喷流 1.5026(条件是 star.rotationSpeed() >= 5,与 extractBody:615 完全一致);行星系 0.866(大气 1.125 → 0.974),说明用了正确的模型中心 (8,8,8) 而非原点,这是最容易搞错的地方。
  • 超新星中心用 be.getSupernovaCenterY() 是正确的世界坐标(BE 里 supernovaCenterY = getBodyCenterWorldY():394;渲染侧 localCenterY = getSupernovaCenterY() - blockPos.getY()),与旧 getRenderBoundingBox 的用法一致。
  • anvillib 集成方式与库文档一致ALROptimizations.getOcclusionCuller() 懒创建、永不为 null(OcclusionCuller.createInstance 兜底 NoOpOcclusionCuller);wrapSubmitNodeStorage 每次返回新 storage(无跨帧串扰);OcclusionKey 的身份语义 + 每帧 setBoundingBox 正是库 javadoc 推荐的用法;RegisterDebugEntriesEvent 注册方式与依赖里的 ALRDebugEntries 完全同构。
  • 配置默认关闭、@Comment/ConfigScreenLang/生成 lang 三处文案一致(en_ud 我抽查了字符翻转+逆序,包含 Anvil;⸵ꞁᴉʌuⱯ; 映射,生成正确);getRenderBoundingBox 确实未改动。

📋 声称验证表

声称 状态 证据
anvillib 519 → 531 libs.versions.toml
getRenderBoundingBox 未改动 diff 仅新增 getOcclusionBoundingBox
六层环半径 0.336/0.487/0.708/0.614/0.796/1.039 我复算 0.3358/0.4871/0.7078/0.6144/0.7961/1.0390
巨构同步层 ≤ 对应环层 全部模型实测(见上)
戴森球额外 R5(小恒星)/R6(其它) extractMegastructureRings:555-561 一致
天体系数(黑洞 1.414、中子星 0.406/1.503、行星 1.0/1.4、头颅 0.75×16) 模型实测 1.4142 / 0.4059 / 1.5026 / 0.866×1.125
恒星 1.386(光晕 0.866×1.6) ⚠️ 实际光晕最大倍率 = 1.0+0.9×0.6 = 1.54 → 1.334,1.386 偏保守但注释里的 1.6 不对
行星环 1.0 × 1.4 实际可见半径 1.327 × 倍率(1.72–1.86)
超新星 12 × √进度 × scale ⚠️ 漏了每根光束 0.7–1.3 的随机系数
典型尺寸 8.5 / 12.5 / 9.5 / 5.8 格 逐状态复算一致(R3/R6/R5/被隐藏外环的岩石行星)
配置默认关闭 + lang 一致 cfaOcclusionCulling = false,三处文案一致
「F3 逐状态比对与可见几何贴合」 ⚠️ 调试器画的是 getOcclusionBoundingBox,剔除记录用的是旧 getRenderBoundingBox,验收对象不一致

结论: REQUEST_CHANGES(改动大多很小)—— 建议至少修掉 🔴 的淡出回归 + 两个包围盒数值偏差(行星环、超新星光束),并明确剔除记录究竟该用哪个盒子;其余为可选优化。遮挡剔除 API 用法、环半径推导、巨构规则重构三项我逐值复算通过,质量是好的。

建议标题gh auth status 显示 token 失效,本次未自动改):PR 标题目前只有中文,且未体现「包围盒重算/可见性重构」的实质:
Add CFA occlusion culling and render bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器

🧪 测试建议

被测目标 建议场景 优先级
VisibleRings.of(...) 参数化:body×{null,星,巨行星,岩石行星,头颅,自定义} × amplify × {戴森球小/大,线圈,彭罗斯,解压器} × 加速器开关,断言与旧规则逐格等价 🔴
getOcclusionBoundingBox 各分支半径下界断言(环/天体/光束/超新星),并新增「任意状态下 half ≥ 可见几何实测半径」的护栏断言 🔴
bodyRadiusFactor / ringRadius 与模型文件解析结果做数值回归(可脚本化,防止以后改模型忘改常量) 🟡
OCCLUSION_KEYS 模拟区块卸载/换世界后表大小不增长 🟡
submit 遮挡记录 提交中抛异常后下一帧几何仍正常转发(try/finally 保护) 🟡

由 Hermes Agent 审查(本地未提供 MC 26.1 反编译源码,debugEntries.isCurrentlyEnabledlevelRenderer.iterateVisibleBlockEntitiesGizmoStyle.stroke 三处 API 依赖作者的 compileJava 结果,其余结论均有源码/资源实测支撑)

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'rendering-gpu-pr-review' (1 replacement).

@ZhuRuoLing
ZhuRuoLing marked this pull request as draft September 18, 2026 13:21
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

curl -s "https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4922" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('action/state:', d['state'], 'draft:', d['draft'])
print('head sha:',...

Reason: Security scan — [HIGH] Pipe to interpreter: curl | python3: Command pipes output from 'curl' directly to interpreter 'python3'. Downloaded content will be executed without inspection.
Safer: tirith run https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4922 — or: vet https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4922 (https://getvet.sh); script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gu-ZT Gu-ZT changed the title feat(rendering): 锻星砧遮挡剔除与渲染包围盒调试渲染器 Add CFA occlusion culling and render bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器 Sep 18, 2026
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /opt/data/workspace/AnvilCraft && mkdir -p /tmp/m4922 && git archive 18f7ec5020 src/main/resources/assets/anvilcraft/models | tar -x -C /tmp/m4922 && find /tmp/m4922 -name "*ring*" | wc -l; echo "=...

Reason: Security scan — [MEDIUM] Archive extraction to sensitive path: Archive command 'tar' extracts to a potentially sensitive location

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /tmp && mkdir -p alibx && python3 -c "print()" 2>/dev/null; unzip -l /tmp/alib_render531.jar 2>/dev/null | head -40 || python3 - <<'EOF'
EOF
python3 /opt/data/skills/github/github-code-review/scrip...

Reason: script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

审查完成。以下是审查意见(已尽量核对源码/模型/依赖 jar 实证):


代码审查摘要 — PR #4922

操作: converted_to_draft(按规则仅报严重问题,风格建议从略)
范围: 11 个文件(7 Java,2 gradle,2 lang json)/ +386 −31
分支: feat/occlusion_cullingdev/26.1/1.6(head 18f7ec5020,2 个提交)


🔴 1. VisibleRings 把「环存在(present)」和「环当前可见(visibleNow)」合并,丢掉了消失方向的淡出动画

extractRings 现在把 has* 直接写成 isRingVisible(...) 的结果(CFARenderer.java:308-317),但 has*outerVisibleNow/wasVisible 在提交阶段是两个不同语义的参数

private void submitRingMaybe(model, boolean present, boolean visibleNow, boolean wasVisible, ...) {
    if (model == null || !present) return;         // ← present=false 直接返回
    if (!state.isAnimating()) { if (visibleNow) draw(); return; }
    if (visibleNow && wasVisible) draw();
    else if (visibleNow) fadeIn(animationProgress);        // 淡入仍正常
    else if (wasVisible) fadeOut(1 - animationProgress);   // ← 现在永远走不到
}

基线分支(dev/26.1/1.6extractRings)里 hasMechanicalRings = true(非玩家头颅),只有增幅 + 戴森球/彭罗斯等巨构情形才置 false;而 isRingVisible 的 false 情形(岩石行星隐藏 R3、巨行星隐藏 R1、非玩家头颅的特殊天体隐藏 R3、增幅下恒星尺寸切换隐藏 R4/R6)过去 present=true,靠 visibleNow=false && wasVisible=true 做淡出。合并后这些状态全部变成 present=false → 瞬间消失。

具体可复现场景(都是 mod 的招牌动画):

  • 非增幅:无天体 → 放入岩石行星/巨行星,"外环/内环" 不再淡出而是直接消失;
  • 增幅:无天体(size 判空)→ 放入大型恒星,R4 内环瞬间消失(此时外环 R6 仍在淡入,视觉割裂最明显)。

这是行为回归,不是等价重构。建议 VisibleRings 分成两套语义返回(present/visible),或只在 getOcclusionBoundingBox 里使用 visible 语义、has* 保持原基线判定。

附带影响:maxRingLayerRadius 也用同一组被合并的 flag(:410-427),淡出中的环不计入包围盒;换成新盒子做剔除后,过渡期正在缩小的环存在被提前剔除的风险。

🔴 2. 程序化行星环的外接半径算小了(切到新包围盒前必须修)

bodyRadiusFactor 里"有环行星"取 1.4CFARenderer.java:456-457,注释说"半径 1.0 × 最大倍率 1.4"),但 submitCelestialRing:1184-1200)的位姿是

T(0.5, centerY, 0.5) · S(bodyScale × ringMultiplier) · R · T(-0.5,-0.5,-0.5)

CelestialBodyRenderer.renderRing 画的是 rmin=-0.5 … rmax=1.5y=0.5方形平板(2×2,含四个角);经 T(-0.5,-0.5,-0.5) 后,环平面相对渲染中心是 (±1, 0, ±1) → 角点到中心距离是 √2 ≈ 1.4142,不是 1.0。因此真实上界应为

1.4142 × ringMultiplier × bodyScale(岩石行星 1.35 → 1.909,巨行星 1.3 → 1.838),而代码只给 1.4 × bodyScale,即少算约 0.44–0.51 × bodyScale。bodyScale 在红石信号 15 时可到 bodyScale × 18(旧 getRenderBoundingBox 的注释自己也写了"红石信号最大 3× 缩放"),极限情形偏差可达数十格;即便默认状态也有 1–2 格。这与本 PR 自己的方法论(环层用"顶点到原点的最大距离"、拒绝用单轴半宽)不一致。

(已验证不是问题的部分:SpecialCelestialBodyData 永远是 RingType.NONE,所以 SPECIAL_BODY_RADIUS = 1.5 足够——实测 planet_shattered 1.123、planet_error 1.083;所有已注册普通行星模型实测量得 0.866 ≤ 1.0。)

⚠️ 3. OCCLUSION_KEYS(静态 IdentityHashMap)的清理时机与生命周期

:180 / :199:清理只写在 extractRenderState 内且必须在配置开启时才执行。后果:

  • 玩家关闭 cfaOcclusionCulling 后,已有条目再不会被回收(强引用 BE + 其所在 level);
  • 世界里只剩这一个 CFA 且它所在的区块被卸载、或切换存档后新世界里没有 CFA 被提取时,旧 BE 会一直被持有;
  • 每个 CFA 每帧做一次全表 removeIf 扫描(CFA 数量多时是 O(n²)/帧)。

建议改成弱键(客户端渲染线程专用,WeakHashMap 即可)或用 Map<BlockPos, OcclusionKey> 并在 setRemoved/关卡卸载回调里清理。

⚠️ 4. 剔除用的盒子与调试渲染器展示的盒子不是同一个

beginOcclusionRecordstate.getRenderBounds()(= 旧 getRenderBoundingBox:754-763),而 F3 覆盖层画的是新的 getOcclusionBoundingBox。也就是说:F3 验收验的不是剔除实际使用的盒子;而开启配置后剔除实际依赖的正是新 javadoc 里自己写着"中心与尺寸都与实际渲染不符"的旧盒子(旧盒子偏大,属于安全但低效)。建议要么在本 PR 内把 key 的 bbox 换成 getOcclusionBoundingBox(不必改 getRenderBoundingBox 本身),要么在注释/配置 tooltip 里明确"开启后使用旧盒子,仅供实验"。

⚠️ 5. gradle 里新增的 profiling run config 属于本地调试配置

gradle/scripts/moddevgradle.gradle:54-63clientIrisWorkaroundForProfiling(第二个提交 chore: add profiling jvm args)加入 -XX:+UseParallelGC / +UnlockDiagnosticVMOptions / +DebugNonSafepoints,与本文件既有 clientRenderDoc* 的定位重复(同样有 --renderDebugLabels),且漏掉了其它所有 client run 都设的 neoforge.enabledGameTestNamespacesanvillib.rendering.debugMode。建议从本 PR 移除,或改名并入既有 profiling 约定并补齐属性。

💡 小记

  • 调试条目用 DebugEntryNoop(无标题),F3 调试选项列表里该行只能靠位置识别;26.1 若提供带标题的 DebugEntry 实现,建议换上。
  • beginOcclusionRecord() / endOcclusionRecord() 目前靠顺序配对(submit 内无提前 return,逻辑上没有泄漏点),但提交期间若抛异常就会漏掉 end;用 try/finally 包一层成本极低。

🟢 已实证核对通过的部分

结论
anvillib 依赖 2.0.0+snapshot.531 ✅ Cjsah Maven 上存在;anvillib-renderingdev.anvilcraft.lib.v2.rendering.optimization.occlusion 包确实含 OcclusionCuller.wrapSubmitNodeStorage(SubmitNodeCollector)→OcclusionSubmitNodeStorageOcclusionKey(Supplier,AABB)+setBoundingBoxbeginOcclusionRecord/endOcclusionRecordALROptimizations,调用签名一致
环层半径表 R1–R6 = 0.336/0.487/0.708/0.614/0.796/1.039 ✅ 逐模型顶点复算:0.3358 / 0.4871 / 0.7078 / 0.6144 / 0.7961 / 1.0390
"同层巨构变体 ≤ 该层环模型" ✅ 挖掘机 0.336、提取器 0.336/0.487、戴森球 R4 0.595/R5 0.785、线圈 0.614、彭罗斯 0.614、解压器 0.614、对撞机 0.539、虫洞稳定器 0.614、R5/R6 加速器 0.796/1.039 全部 ≤ 对应环层
天体系数 ✅ 黑洞 1.4142(=√2,模型实测)、中子星 0.406/喷流 1.503、恒星/行星立方体 0.866 ≤ 1.386/1.0;玩家头颅 0.75×16 足够(头颅模型 ≈0.43×16)
超新星/光束上界 SUPERNOVA_RAY_LENGTH=12 > SUPERNOVA_MAX_RADIUS=8(且平板对角 8√2≈11.3 < 12);托举光束上界用 BEAM_BASE_Y + smoothBeamHeight,水平半宽 ≤0.4 远小于环半径
中心与坐标系 (pos.x+0.5, pos.y+smoothCenterY, pos.z+0.5)submittranslate(0.5, centerY, 0.5)MultiPartBlockEntityBOTTOM_CENTER 主部件一致;javadoc 里 8.5 / 12.5 / 9.5 格的例子复算无误
遮挡记录覆盖面 ✅ 机械环/巨构层/天体/天体环/超新星全部改走 targetsubmitStarBloom(后处理)与延迟托举光束不入采集器,与描述一致;submitbegin/end 配对无提前 return
配置与 lang cfaOcclusionCulling 默认 false,ConfigScreenLang 覆盖 + en_us/en_ud(含 en_ud 倒序)齐全,tooltip 描述与实现相符

📋 声称验证表

声称 状态 依据
接入 anvillib 遮挡剔除(519→531) 依赖存在 + API 签名匹配
VisibleRings 统一可见性判定"避免两处规则漂移" ⚠️ 统一了规则但改变了行为(见 🔴1)
包围盒按"实际绘制的图层"取外接半径 ⚠️ 环层部分 ✅,程序化行星环部分低估(见 🔴2)
getRenderBoundingBox 未改动 旧实现逐字未动;但剔除记录仍用它(见 ⚠️4)
F3 调试项默认关闭、颜色由包围盒哈希派生 DebugEntryNoop 默认不启用;颜色随尺寸变化
compileJava / runData 通过 未复核 无本地构建环境,未验证

结论: COMMENT(draft,不建议直接转正式 PR 合并) — 阻断项为 🔴1(环淡出动画回归)与 🔴2(行星环半径低估,切到新包围盒前必须修);⚠️3/4/5 建议一并处理。实测部分(半径表、天体系数、依赖 API)质量不错,说明推导过程是认真做过的,问题集中在"把两个语义合并"和"用半宽代替对角"这两处。

标题建议: 现标题只有中文,建议改为 Add CFA occlusion culling and render bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器。(本次 gh auth status 显示 /opt/data/home/.config/gh/hosts.yml 的 token 已失效,故未自动执行 gh pr edit。)


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'rendering-gpu-pr-review' (1 replacement). · Patched references/pr-class-routing.md in skill 'github-webhook-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4922edited:标题/描述更新,代码未变)

范围: 11 个文件(8 Java,2 新增)/ 656 行 diff(397 +, 42 −)
head: 18f7ec50base: dev/26.1/1.6 @ e4bf6c7mergeable_state: clean
diff 完整性: ✅ md5 a3b93ec398db811b70274f1a71309ebadiff --git 计数 11 = API changed_files 11,28 个 hunk 覆盖全部文件,无 ghost、无 EOF 缺换行
CI:build + checkstyle18f7ec50 上均 success(描述中「compileJava 通过」可信)

本轮 edited 只改了标题/描述,代码内容与上一轮 synchronize 完全一致(diff md5 相同),因此上轮提出的阻塞项仍然成立;下面先做新版描述的逐项交叉验证(本轮重点关注),再列仍未修的问题与我新发现的两处数值缺口。


📋 描述声称验证表(本轮新增的推导表逐项复算)

声称 结论 我的独立复算
升级 anvillib 至 2.0.0+snapshot.531 libs.versions.toml:9;531 的 render 模块里确有 optimization.occlusion 包(含 OcclusionCuller/OcclusionKey/OcclusionSubmitNodeStorage
环层半径 R1–R6 = 0.336/0.487/0.708/0.614/0.796/1.039 完全吻合 逐 element 顶点(含 rotation)到原点距离 ÷16:0.3358 / 0.4871 / 0.7078 / 0.6144 / 0.7961 / 1.0390;且 r_origin == r_center,证明「模型原点即渲染中心」成立
巨构占用的同步层「实测均 ≤ 对应环层」 逐模型核对通过 挖掘机/提取器/生态站/神殿 0.3358=R1;巨行星提取器 0.4871=R2;对撞机 0.5386、线圈 0.6144、彭罗斯球 0.6144、解压器 0.6144、虫洞稳定器 0.6144 ≤R4;小型戴森球 0.5954 ≤R4;大型戴森球 0.7848 ≤R5;恒星演化加速器 0.7961/1.0390 =R5/R6
戴森球额外外层环:小型恒星 R5、其余 R6,「与 extractMegastructureRings 一致」 两处条件逐字一致(isSmallStar = size < 48R4&&small → R5 / R5&&!small → R6CFARenderer.java:553-561:317-323
中子星 1.503/0.406(有无喷流) neutron_star_jet.json 中心半径 1.5026neutron_star.json 0.4059;喷流开启条件 rotationSpeed() >= 5extractBody :615)与 bodyRadiusFactor :452 一致
黑洞 1.414、玩家头颅 0.75×16、资源包天体 1.5 兜底 black_hole.json 1.4142(精确);头颅模型在 translate(0.5,0.25,0.5) 偏移下,角点到渲染中心恰好 √(0.5²+0.25²+0.5²)=0.75,常数取得很准;自定义天体实测 1.08/1.16 ≤ 1.5
普通恒星 1.386(光晕 0.866×1.6) ⚠️ 数值安全、推导有偏差 submitHalo(..., 10, 1.0f, 0.6f, ...)progress=i/iterations 最大为 0.9 ⇒ 实际最大缩放 1.54(不是 1.6),实际半径 0.866×1.54=1.334。1.386 偏保守 ✅,但描述里的「×1.6」不是代码值,建议改成 0.866×(1.0+0.9×0.6) 以免下一个人按 1.6 反推
上界额外纳入托举光束 / 超新星中心改用 getSupernovaCenterY() 且半径并入 12×√进度×scale :1657-1661:1632-1655pushRing/submitMechanicalRing/submitCelestialBody/submitCelestialRing(0.5, centerY, 0.5) 中心一致
「非增幅无天体 8.5 格、增幅无天体 12.5 格、小型恒星戴森球 9.5 格、有岩石行星 5.8 格」 复算:0.708×6×2=8.501.039×6×2=12.470.796×6×2=9.550.487×6×2=5.84
「遮挡剔除仍使用原 getRenderBoundingBox(按要求未改动)」 ✅ 与代码一致 :195-196 存旧盒、:757-759 喂旧盒;新盒只在调试渲染器用(:47)。但这与配置文案冲突,见下 ⚠️2

anvillib / NeoForge API 契约核对(常量池级别)

  • OcclusionKey.hashCode() = System.identityHashCode(this)equals 为身份语义 ⇒ 必须跨帧复用同一实例(:176-180 的按 BE 身份缓存是必需且写法正确 ✅);setBoundingBox 每帧改盒不会破坏哈希 ✅。
  • OcclusionSubmitNodeStorage extends net.minecraft.client.renderer.SubmitNodeStorage,只覆写 order(int) ⇒ 所有提交入口(submitModel/submitCustomGeometry/submitBlockModel…)都被统一拦截,不存在某类几何被漏转发的风险 ✅(这是本 PR 最值得担心的点,已验证排除)。
  • RegisterDebugRenderersEvent.register(SimpleDebugRenderer)RegisterDebugEntriesEvent.register(Identifier, DebugEntry) 两个重载都存在 ✅;emitGizmos(DDD,DebugValueAccess,Frustum,F) 与 26.1 原版 BeeDebugRenderer 签名一致 ✅;iterateVisibleBlockEntities 是 NeoForge 对 LevelRenderer 的补丁方法(userdev patch 内可见)✅;DebugScreenEntryList.isCurrentlyEnabled(Identifier) 存在 ✅;ALRDebugEntries(Javadoc 引用的 anvillib 参考实现)也只 register(id, entry)、不加 profile ⇒ 注册方式与库内一致 ✅。

🔴 关键(仍未修,建议合并前处理)

  1. :1654 超新星半径仍漏掉放射光束的随机长度系数(最多少算 30%)
    实际绘制 len = rayLen * (0.7f + 0.6f * rand.nextFloat()):1338,上限 1.3×),而包围盒只取 SUPERNOVA_RAY_LENGTH * grow * max(1, scale)。t→1 时真实可达 12×1.3=15.6 格,盒仅 12 格 ⇒ 小约 23%。这正是「新盒与可见几何贴合」这条验收标准的反例,切换到该盒做剔除后会出现可见几何被误剔。建议提常量(与 :1338 共用)而不是就地写 1.3f

  2. :315-317 + :832 束星环淡出动画被这次重构静默关闭(观感回归)
    旧代码 present = hasMechanicalRings存在性:仅排除玩家头颅),新代码 present = VisibleRings.*当前可见性)。而 present == false 会在 submitRingMaybeif (model == null || !present) return;先于淡出分支返回,于是 visibleNow=false && wasVisible=true 的缩放淡出永远走不到。受影响(旧=缩小淡出,新=瞬间消失):恒星→岩石行星(R3)、任意→巨行星(R1)、任意→SpecialCelestialBodyData(R3)、增幅下大小恒星跨越 48 阈值(R6/R4)。静止态渲染无差异,只在过渡窗口内。最小修法:presentstate.isHasOuterRing() || state.isOuterWasVisible(),或把 VisibleRings 拆成 present/visibleNow 两组、has*Ring 恢复存在性语义。

  3. :757-759 遮挡记录喂的仍是旧 getRenderBoundingBox,新配置当前「开着也不剔任何东西」
    旧盒尺寸为 max(bodyScale,ringScale)×3×1.5(无天体时 6×3×1.5=27 格),HZB 判定基本永远可见 ⇒ 开 cfaOcclusionCulling 只白付记录开销;而 F3 里看到的框是新盒,不是剔除实际使用的框(PR 描述的「逐状态比对贴合」验证不到真正上线的那个盒)。描述里已注明「按要求未改动」,这点我可以接受,但新增 lang/配置文案写的是 “geometry fully hidden behind terrain is skipped”,对玩家是过度承诺。二选一:本期直接切到 getOcclusionBoundingBox(...),或把 tooltip 改成「当前仅提交遮挡记录,剔除待新包围盒验收后启用」。

⚠️ 警告

  1. :456-457 有环行星的半径系数偏小约 21–27%(新发现的关键数值缺口)
    renderRing 画的是 x,z ∈ [-0.5, 1.5] 的 2×2 方片,UV 映射到贴图 [0,0.5]²(16 px = 1 格,环心 = 象限中心)。我逐像素量了 planet_giant_ring_0.png(64×64):不透明像素到环心的最大距离 = 21.9 px = 1.37 格(几何角点 1.414,仅角尖透明)。
    描述里「半径 1.0 × 最大倍率 1.4」的 1.0 只是未旋转方片沿轴的半宽;方片每帧绕 Y 连续自转(:1197-1199),转到 ~45° 时那条 1.37 的对角方向就变成轴方向 ⇒ AABB 需要 1.37 × ringMultiplier:岩石 1.85 / 巨行星 1.78 / 其它 1.92 格,而 bodyRadiusFactor 只给 1.4 ⇒ 盒比可见几何小 21–27%。建议把环单列一项:ringType != NONE ? 1.37f * ringMultiplier(body) * smoothBodyScale : ... 再与天体项取 max(顺便把 1.37 的来历写进注释,避免下次又被「半径 1.0」误导)。

  2. :197-201 OCCLUSION_KEYS 的清理时机与规模
    清理写在 if (CONFIG.cfaOcclusionCulling) 内部,且该 map 持有 BE 强引用(间接强引用 ClientLevel):关闭配置后旧条目永不清理(也不再新增,纯残留);退出世界/换存档后不再有 CFA 提取 ⇒ 最后一批条目 + 世界对象常驻;removeIf 在每个 CFA 每次提取全表遍历 ⇒ n 个锻星砧每帧 O(n²)。建议把清理移出配置判断(或 if (!map.isEmpty()) 守卫),并在客户端世界卸载/LoggingOutclear()

  3. :1172-1175 褐矮星光晕超出 1.0 系数约 14%
    brownDwarf() 额外画 submitHalo(..., 3, 1.15f, 0.25f, ...),最大缩放 1.15 + (2/3)×0.25 = 1.317 ⇒ 半径 0.866×1.317 ≈ 1.14;无环巨行星走 1.0f 分支 ⇒ 光晕外缘被切约 12–14%。建议褐矮星至少取 1.14。

💡 建议

  • 调试盒最好直接对比「真正上线的盒」:NeoForge 26.1.2 自带 net.neoforged.neoforge.client.BlockEntityRenderBoundsDebugRenderer(同样走 iterateVisibleBlockEntities,画的是每个 BE 的 getRenderBoundingBox,客户端指令开关)。也就是说现在两个调试视图会互相打架:原版的框=剔除真正用的框,本 PR 的框=「待验收的新框」。建议在描述/注释里点明这一点(或在本 PR 里让记录盒与新盒同源,问题自然消失)。
  • 半径常量建议从模型/贴图推导而不是硬编码ringRadius() 的 6 个值与实测完全相等(差 ≤0.05%),巨构变体也刚好等于所在层半径(0.6144/0.7961/1.0390)——这意味着「变体 ≤ 所在层」这条不变量没有任何余量,任何一次模型微调都会静默破坏包围盒。可考虑在 bake 时取模型 bbox、或至少加注释断言并附上实测脚本/数值出处。
  • :747-749 endOcclusionRecord() 仍无 try/finallysubmit() 中途抛异常时本帧记录不会转发给真正的 collector(几何整体消失且记录悬空)。库侧 beginOcclusionRecordIllegalStateException 守卫,建议对称加保护。
  • :51 调试描边色 bounds.hashCode() 会逐帧闪烁:盒子含平滑缩放、红石系数、超新星进度(含 partialTicks)⇒ 颜色持续抖动,反而难以「辨识尺寸变化」。建议用量化后的稳定依据(环层数/是否增幅/是否超新星)派生。
  • :195-196 每帧无条件算/存旧盒:配置关闭时这次计算(以及 state 字段赋值)是纯开销,且原版 frustum 剔除路径本身也会调用 getRenderBoundingBox ⇒ 同一帧对同一 BE 算两遍。建议移进 if (cfaOcclusionCulling) 分支,或与剔除路径复用。
  • :1632-1635 超新星期间中心取「瞬时」getSupernovaCenterY(),而环/天体渲染用的是平滑getSmoothCenterY():236-237):闪光期间若红石/缩放变化,平滑值滞后 ⇒ 盒心与几何中心有偏差(量级为平滑残差,目前小,但和 在REI渲染方块(初步测试) #1 叠加时更容易漏边)。另:托举光束只纳入了上界,BEAM_BASE_Y=1.5:161)在极端过渡帧(半宽 < 3.0)可能比盒底低约 0.1 格。
  • 描述里 1.0f(无环行星)的括号注释只写了大气层立方体,建议补一句「褐矮星光晕另计」(见 ⚠️6)。
  • 新增 lang 键只进了 en_us/en_ud,其余语言走 Weblate,属正常流程,仅提示。

🟢 看起来不错

  • 6 个环半径常量与模型文件逐顶点复算完全对得上0.3358/0.4871/0.7078/0.6144/0.7961/1.0390),且模型关于原点对称 ⇒ pushRing 的「原点即中心」前提成立;VisibleRings 把「提取」与「包围盒」两处判定收拢到一处,消除了规则漂移的可能(本轮重点验收方向是对的)。
  • 巨构/戴森球/加速器的层占用与半径映射逐条核对无误,戴森球外层环的 R5/R6 选择与 extractMegastructureRings 完全一致;option.id()null 时的 equals 调用是 null 安全的。
  • anvillib 的遮挡 API 用法正确:身份哈希键 + 按 BE 缓存的写法与库设计匹配;OcclusionSubmitNodeStorage 只覆写 order(int),因此 submitModel(tessellation)与 submitCustomGeometry(天体环/光晕/新星)都会被完整拦截,不会出现「开了 culling 就掉几何」。
  • 描述主动披露了两处不参与剔除的独立绘制(托举光束 AFTER_WEATHER、恒星 bloom),并明确「实验性、默认关闭」——诚实且降低误解。
  • 调试渲染器放在提取阶段用 Gizmos 提交、由原版每帧收集器统一绘制,符合 26.1 原版调试渲染器机制(与 BeeDebugRenderer 同构);选 iterateVisibleBlockEntities 也意味着即使某个锻星砧被剔除、其调试框仍会画出,便于验收。

🧪 测试建议

被测目标 推荐测试场景 优先级
getOcclusionBoundingBox 纯数学函数:对每种天体/巨构组合断言盒包含该状态下所有可见几何(尤其有环行星 ×45° 自转、超新星 t→1、戴森球小型/大型) 🔴
CFARenderer.submitRingMaybe 过渡回归:恒星→岩石行星/巨行星/特殊天体,断言过渡窗口内 submitRingScaled 仍被调用(当前语义已破坏) 🔴
OCCLUSION_KEYS 开关切换 + 多次进出世界/换维度后断言 map 为空(当前会残留) 🟡
getOrCreateKey 同一 BE 跨帧返回同一实例;BE 移除后条目被回收;名字供应器不捕获 BE 🟡

结论: REQUEST_CHANGES — 描述与推导表本身质量很高(半径表、巨构映射我逐项复算全部吻合),但 #1(超新星光束少算 30%)⚠️4(有环行星少算 21–27%) 说明新盒尚未达到「与可见几何贴合」的验收标准,#2(淡出回归) 是本次重构引入的观感退化,建议合并前一并修掉;#3 若确按维护者要求保留旧盒,请同步收敛配置文案/描述,避免「开着不生效」的误导。PR 标题 Add CFA occlusion culling and render bounds debug renderer 锻星砧遮挡剔除与渲染包围盒调试渲染器 准确,无需修改。


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/occlusion-culling-bounds-integration-review.md in skill 'rendering-gpu-pr-review' (1 replacement). · Patched scripts/model_json_radius_probe.py in skill 'rendering-gpu-pr-review' (1 replacement). · Patched SKILL.md in skill 'rendering-gpu-pr-review' (1 replacement).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants