Skip to content

ENG2-P8-11: Terrain LOD + texture/mesh streaming #809

Description

@aram-devdocs

Parent

  • Program: ENG2 — GoudEngine v2 Rebuild (see the pinned master tracking issue)
  • Phase / Milestone: Phase 8 — Capability Gaps (eng2-p8-capabilities)
  • Batch / Group: Batch 8.3 — Big-world, Group B (sequential after ENG2-P8-10)
  • Runbook spec: docs/src/runbook/phases/phase-8.md (committed with the roadmap)

Summary

Add distance-based mesh LOD for chunked heightmap terrain (ENG2-P8-10) plus asynchronous texture/mesh streaming so distant chunks load progressively without blocking the frame, using the Phase 7 async asset pipeline.

Architecture Context

Layer: Layer 4 (Engine, libs/graphics/renderer3d/terrain.rs) for LOD mesh generation; Layer 3 (Services, assets/) integration for streaming, reusing the async asset pipeline wired up in ENG2-P7-01.
Modules/types touched:

  • goud_engine/src/libs/graphics/renderer3d/terrain.rs (extends ENG2-P8-10) — multiple mesh resolutions per chunk, selected by camera distance each frame (or on chunk load).
  • goud_engine/src/assets/server/ — terrain chunk mesh/texture loads routed through load_async/process_loads (the pipeline ENG2-P7-01 wires into the runtime), not synchronous main-thread loads.
  • goud_engine/src/ffi/renderer3d/terrain.rs — new exports for LOD distance configuration and streaming status query.

Boundary constraints (only those that apply):

  • Raw GPU calls stay in libs/graphics/backend/.
  • FFI exports: #[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments.

Pattern to follow: ENG2-P7-01's runtime wiring of AssetServer::load_async/process_loads — terrain chunk streaming must be a consumer of that general-purpose async pipeline, not a bespoke terrain-only background-thread mechanism.

Scope

  • Distance-based mesh LOD: generate (or downsample) 2-3 resolution tiers per terrain chunk; select tier per chunk based on camera distance each frame or on a coarser cadence.
  • LOD transition handling: avoid popping artifacts at tier boundaries (documented approach — e.g. hysteresis band or geomorphing; a simple hysteresis band is an acceptable first cut).
  • Streaming integration: route terrain chunk mesh/texture generation and upload through the async asset pipeline (ENG2-P7-01) so loading distant chunks does not block the main thread.
  • FFI exports: configure LOD distance thresholds, query per-chunk streaming/load state.
  • Tests: spec test + unit tests for LOD tier selection (correct tier chosen at known distances) and a streaming integration test (chunk transitions from not-loaded -> loading -> loaded without blocking the calling thread).
  • Docs/rules updates: mdBook terrain page gains an LOD/streaming tuning section.

Acceptance Criteria

  • Terrain chunks at increasing camera distance select progressively lower mesh resolution tiers, verified against configured thresholds.
  • Chunk streaming loads occur off the main thread (via the ENG2-P7-01 async pipeline) with no measurable frame-time spike attributable to a terrain chunk load, per a scripted large-terrain scene.
  • FFI LOD/streaming exports have wrappers in all 10 SDKs and pass python codegen/validate_coverage.py.
  • cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings clean; cargo test green; ./codegen.sh && git diff --exit-code (drift gate)

Breaking Change & Throne Follow-up

None — additive/internal (extends the ENG2-P8-10 terrain component; no existing exports change).

Blocked By

ENG2-P8-10 (chunked heightmap terrain component — this issue adds LOD/streaming on top of that chunk representation and cannot exist independently of it), ENG2-P7-01 (wire async asset pipeline into the runtime; background decode — terrain streaming is a consumer of that pipeline, not a second background-loading mechanism).

Files Likely Touched

  • Modified: goud_engine/src/libs/graphics/renderer3d/terrain.rs, goud_engine/src/ffi/renderer3d/terrain.rs (both from ENG2-P8-10)
  • Modified: goud_engine/src/assets/server/ (terrain-specific asset type/loader registration, if needed)
  • Generated: SDK bindings under sdks/*/ for the new LOD/streaming FFI exports
  • Modified: codegen/goud_sdk.schema.json

Agent Notes

  • No mesh or texture streaming exists anywhere today: grep for mesh.?stream|texture.?stream|async.*load.*mesh (case-insensitive) across goud_engine/src returns zero matches. The only "async load" infrastructure in the engine is the general asset pipeline (assets/server/core.rs:86's comment: "Sender for background load results (used by native load_async)"; load_async/process_loads are implemented and unit-tested in assets/server/async_tests.rs), but per the roadmap's W5 finding, that pipeline currently has zero non-test callers in the runtime — it is real, tested code that nothing actually invokes yet. This is exactly why ENG2-P7-01 (wiring it into the runtime) is a hard blocker here: building terrain streaming against a pipeline that isn't wired into the actual frame loop would mean re-plumbing it twice.
  • Do not conflate this issue's LOD with the codebase's existing LOD terminology: every current LOD reference (libs/graphics/renderer3d/config.rs:80,195,199; core_model_animation/mod.rs:134,195) is animation-evaluation LOD (skipping/half-rating skeletal animation updates at distance) — a completely different mechanism operating on a completely different data path (bone matrices, not mesh geometry). This issue introduces the engine's first genuine geometric/mesh LOD system; do not attempt to reuse the animation LOD's distance-bucket code, since it is not built for mesh-resolution switching.
  • LOD transition popping is a known visual artifact class for heightmap terrain specifically (unlike the animation LOD, which is imperceptible by design) — the Scope explicitly requires a documented mitigation approach rather than leaving raw tier-snapping as the only option.

Verification

cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings
cargo test
./codegen.sh && git diff --exit-code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions