Skip to content

fix(terrain): terrain invisible at high altitude and chunk loading holes - #113

Merged
DHJComical merged 3 commits into
mainfrom
fix/chunk-visibility-and-loading
Sep 3, 2026
Merged

fix(terrain): terrain invisible at high altitude and chunk loading holes#113
DHJComical merged 3 commits into
mainfrom
fix/chunk-visibility-and-loading

Conversation

@DHJComical

Copy link
Copy Markdown
Owner

改了什么

三个独立提交,修复两类区块渲染/加载缺陷:

  1. fix(terrain): drop vertical render-distance cutoff — 区块遮挡搜索移除了沿自 Sodium 的圆柱体视距中的垂直截断项(section 级 OcclusionCuller.isWithinRenderDistance 与 region 级 RegionCullCache.compute),只保留水平圆形距离判定,与 vanilla 1.12.2 行为对齐。
  2. fix(terrain): unify section frame clock and release tokens of discarded buildsRenderSectionManager 两个入口(update/updateForShadowPass)的外部帧号统一经新增的 SectionFrameClock 归一化为严格递增序列;filterChunkBuildResults 丢弃「过期」结果时补放 buildCancellationToken 并输出 WARN 日志。
  3. fix(terrain): reconcile chunk tracker with loaded chunksChunkTracker.reconcile() 每 client tick 与 ChunkProviderClient.loadedChunks 对账,经原有事件通道重放差异(稳态下 diff 为空、零事件)。

为什么改

  • 高空地形不渲染(稳定复现):开光影飞高后地形完全不渲染、靠近地面恢复。根因是垂直截断:相机高出地面超过 renderDistance × 16 格后正下方地形被整片裁掉,渲染列表清空且 INITIAL_BUILD 停止收集。vanilla 1.12.2 无垂直视距裁剪;且 Depths Update 等模组可扩展世界高度(本仓库已有 DepthsUpdateCompat),任何固定垂直截断在扩展高度世界同样错误。
  • 跑图整片区块不加载、靠近不触发、重进恢复(不稳定复现):帧时钟混用(主 pass 的 vanilla frameCount vs 阴影 pass 自建且管线重建即归零的 celeritasShadowFrame)导致 lattice 访问戳毒化(搜索永久跳过 cell)与构建结果误丢后 token 泄漏(buildInFlight 卡死、section 永不再被收集)。另对 ChunkTracker 与世界加载集可能脱节(非 full-chunk 包、模组绕过 loadChunk 直写)增加了对账自愈。

如何验证

  • ./gradlew check --no-daemon./gradlew build --no-daemon 全绿(含 remap jar 结构校验、compatBridge 契约测试、模块边界校验)。
  • 新增 3 个测试类共 18 个用例(TDD 先行):OcclusionCullerRenderDistanceTest(高空/负高度/水平超界)、SectionFrameClockTest(单调性/回退/饱和)、ChunkTrackerReconcileTest(补缺失/清多余/幂等/孤立不就绪)。
  • dev 实机回归(维护者验证):开光影高空飞行地形持续渲染;同高度无光影对照正常;光影开关/重载与常规跑图无回归。

兼容性

  • HBM-CE(NTM-CE)mixin 注入面已反编译 dev 依赖 jar 复核:OcclusionCuller legacy object-node API 与 RenderSectionManager.update() 的 CameraTransform getfield 内联均原样保留,HbmCameraRedirectContractTest 通过。
  • compatBridge 未改动,CeleritasCompatBridgeJarTest 通过。
  • 无光影包特定改动;fog occlusion 的水平距离收缩行为不变。

相关 issue

The occlusion search inherited Sodium's cylindrical render distance: besides
the horizontal X/Z circle, sections were rejected when their vertical
distance to the camera exceeded maxDistance (~ renderDistance * 16), both at
section level (OcclusionCuller.isWithinRenderDistance) and region level
(RegionCullCache.compute). Flying higher than that threshold above the
ground culled the terrain directly underneath: render lists emptied and
initial builds stopped being collected until the player descended again.

Vanilla 1.12.2 has no vertical render-distance cull, and world height can be
extended by mods such as Depths Update, so the vertical term is removed
entirely; the legacy object-node API (HBM-CE mixin injection target) keeps
its original semantics as it is not used by the render path.
…ed builds

The section manager archived frame numbers from two independent counters:
the vanilla frame counter on the terrain pass and ShadowRenderer's own
counter, which restarts from zero on every shader-pipeline rebuild. Frames
where the terrain setup ran but the shadow pass did not (pipeline rebuild
windows, nested portal rendering) mixed both counters into one comparison
space:

- lattice visit stamps written by the larger counter made later searches
  with the smaller counter treat cells as already visited, so sections
  never re-entered render lists or rebuild queues;
- filterChunkBuildResults misclassified fresh results as stale, and the
  dropped results never reached the token release in
  processChunkBuildResults, pinning the section's buildInFlight bit so the
  graph search skipped it permanently.

Both entry points now normalize the external frame through SectionFrameClock,
a strictly increasing sequence, and the stale-result discard path releases
the build cancellation token (with a WARN carrying the section coordinates
and frame numbers) instead of leaking it.
The tracker's only event sources are the loadChunk/unloadChunk hooks, so any
chunk transition that bypasses them (chunk data written to a chunk the
tracker never saw load, mods mutating the loaded-chunk map directly) leaves
the tracker permanently out of sync: the affected chunks never satisfy the
3x3 neighbor-readiness gate, are never published to the renderer, and produce
holes that approaching does not heal until the world is re-entered.

ChunkTracker.reconcile diffs the tracked set against the world's
authoritative loaded set and replays the difference through the regular
event methods, and the client-chunk-manager mixin invokes it once per client
tick; in steady state the diff is empty and no events fire.
@DHJComical
DHJComical merged commit 744df02 into main Sep 3, 2026
1 check passed
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.

1 participant