Rendering: frustum culling + material-sorted batching for large scenes

Current State

Once the 32-submesh cap is lifted (#140 (closed)), a mesh renderer draws all of its submeshes (1,591 for Bistro exterior) every frame with no visibility culling and no draw-call sorting — every submesh is a separate draw (subsystems/render/root.zig).

Goal

Keep the Studio viewport and packaged game interactive on Bistro-scale scenes via frustum culling and material-sorted batching.

Scope

  • Frustum culling using per-submesh (or per-node, once #142 (closed) lands) bounds — skip draws outside the camera frustum.
  • Material-sorted batching: sort submeshes by material/pipeline to minimize pipeline and binding switches (the profiler already counts material switches).
  • Optionally coalesce adjacent same-material submesh ranges into a single draw.

Acceptance

  • Free-fly navigation through Bistro exterior in Studio at interactive framerate (the #132 acceptance criterion), verified via the debug-server FPS counter.

Depends on #140 (closed); benefits from per-node bounds in #142 (closed).

Edited by Bruno Massa