Skip to content

[R2-2][P1] Encapsulate LODMesh/LODChunk behind behavior methods #841

Description

@MichaelFisher1997

Part of #839 — [Audit][Round 2] A++ umbrella.
Phase: P1 · Finding: R2-2 · Depends on: nothing (unblocks R2-1)

Problem

LODMesh and LODChunk are treated as open structs by both lod_manager and lod_renderer. A field change to either propagates to two consumers — classic Law-of-Demeter / shotgun-surgery smell. This blocks clean extraction boundaries in R2-1.

Evidence

Both consumers reach into LODMesh bare fields:

  • lod_renderer.zig:246, 248, 355, 425, 429, 592, 677-686 — reads buffer_handle, vertex_count, opaque_vertex_count, water_vertex_offset, water_vertex_count, vertex_offset, pooled, ready
  • lod_manager.zig:1303, 1588-1589, 2515-2517, 2525, 2548-2553 — reads/writes ready, vertex_count, capacity, pending_vertices, opaque_vertex_count

Both consumers reach into LODChunk bare fields:

  • lod_renderer.zig:360, 503-507, 666-670 — reads state, ready_children, transition_frames_remaining, lod_level
  • lod_manager.zig — reads/writes state extensively across the worker pipeline

Manager hands raw &meshes and &regions HashMap pointers to the rendererlod_manager.zig:1685. The renderer iterates these maps directly (lod_renderer.zig:347), exposing internal storage layout.

SchedulerContext (lod_scheduler.SchedulerContext) is a 14-field struct carrying raw pointers into the manager's regions, gen_queues, mutex, next_job_token, plus a callback coverage_ptr. The decoupling is structurally present but semantically hollow — "manager exposes its entire interior for one call."

Fix

Add behavior methods to LODMesh and LODChunk; collapse open-field access. Target methods (initial sketch — adjust during implementation):

LODMesh

pub fn drawRange(self: *LODMesh, layer: RenderLayer) ?MeshDrawRange
pub fn byteSize(self: *const LODMesh) usize
pub fn markUploaded(self: *LODMesh, vertex_count: u32, opaque_count: u32) void
pub fn isReady(self: *const LODMesh) bool
pub fn acquireWriteLock(self: *LODMesh) void  // wraps existing sync.Mutex

LODChunk

pub fn isRenderable(self: *const LODChunk) bool
pub fn transitionFadeProgress(self: *const LODChunk) f32
pub fn isCoveredByFinerLOD(self: *const LODChunk, config: CoverageConfig) bool
pub fn beginTransition(self: *LODChunk, target_level: LODLevel) void
pub fn lodLevel(self: *const LODChunk) LODLevel

LODMeshStorage iterator

Replace &meshes HashMap pointer exposure with: pub fn iterateRenderable(self: *LODMeshStorage, layer: RenderLayer) RenderableIter. The renderer asks for an iterator of (mesh_ref, draw_range) tuples instead of indexing the map.

Slim down SchedulerContext

Convert the 14-field struct to 3 fields: regions_view: RegionsView, queue_handle: QueueHandle, coverage: *const CoverageQuery. The view types are read-only facades.

Verification

  • nix develop --command zig build test (includes shader validation)
  • Headless screenshot baseline before/after (-Dskip-present, low/medium presets) — must be bit-exact
  • nix develop --command zig build test -- --test-filter "LODMesh" and --test-filter "LODChunk"
  • grep -rE "\.(vertex_count|buffer_handle|opaque_vertex_count|ready_children|transition_frames_remaining)" modules/world-lod/ should return only the new method bodies, no callers

Constraints

  • Behavior-preserving. Render output unchanged.
  • Small reviewable PR targeting dev; conventional commit (refactor(world-lod): encapsulate LODMesh/LODChunk).
  • Per-method PRs are acceptable (one PR per struct is also fine if small).
  • Keep worker-thread safety: encapsulation must not introduce new shared-mutable-state races. Existing per-mesh sync.Mutex moves into the methods.

Notes

  • This is the prerequisite for clean R2-1 extraction boundaries. Land first.
  • The duplicated coverage logic (areAllChunksLoaded vs isCoveredByChunks) is addressed separately in R2-1's bonus section.

Tracking: #839

Metadata

Metadata

Assignees

No one assigned

    Labels

    automated-auditIssues found by automated opencode audit scansbugSomething isn't workingenhancementNew feature or requestquestionFurther information is requestedworld

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions